diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ca8edf6 --- /dev/null +++ b/.clang-format @@ -0,0 +1,103 @@ +--- +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 1 +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: true +AlignOperands: false +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: All +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: true +ColumnLimit: 100 +CommentPragmas: "^ IWYU pragma:" +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [Q_FOREACH, BOOST_FOREACH] +IncludeBlocks: Regroup +IncludeCategories: + - Regex: "^<(test)/" + Priority: 0 + - Regex: "^<(xrpld)/" + Priority: 1 + - Regex: "^<(xrpl)/" + Priority: 2 + - Regex: "^<(boost)/" + Priority: 3 + - Regex: "^.*/" + Priority: 4 + - Regex: '^.*\.h' + Priority: 5 + - Regex: ".*" + Priority: 6 +IncludeIsMainRegex: "$" +IndentCaseLabels: true +IndentFunctionDeclarationAfterType: false +IndentRequiresClause: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +NamespaceIndentation: None +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +RequiresClausePosition: OwnLine +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +QualifierAlignment: Right +--- +Language: Proto +BasedOnStyle: Google +ColumnLimit: 0 +IndentWidth: 2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d659c79..46f02f6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,6 +39,13 @@ repos: hooks: - id: black + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: 75ca4ad908dc4a99f57921f29b7e6c1521e10b26 # frozen: v21.1.8 + hooks: + - id: clang-format + args: [--style=file] + "types_or": [c++, c] + - repo: https://github.com/cheshirekow/cmake-format-precommit rev: e2c2116d86a80e72e7146a06e68b7c228afc6319 # frozen: v0.6.13 hooks: diff --git a/include/secp256k1_mpt.h b/include/secp256k1_mpt.h index 62dcd8a..9aea43d 100644 --- a/include/secp256k1_mpt.h +++ b/include/secp256k1_mpt.h @@ -11,61 +11,60 @@ extern "C" { /** * @brief Generates a new secp256k1 key pair. */ -SECP256K1_API int secp256k1_elgamal_generate_keypair( - const secp256k1_context* ctx, - unsigned char* privkey, - secp256k1_pubkey* pubkey -); +SECP256K1_API int +secp256k1_elgamal_generate_keypair( + secp256k1_context const* ctx, + unsigned char* privkey, + secp256k1_pubkey* pubkey); /** * @brief Encrypts a 64-bit amount using ElGamal. */ -SECP256K1_API int secp256k1_elgamal_encrypt( - const secp256k1_context* ctx, - secp256k1_pubkey* c1, - secp256k1_pubkey* c2, - const secp256k1_pubkey* pubkey_Q, - uint64_t amount, - const unsigned char* blinding_factor -); +SECP256K1_API int +secp256k1_elgamal_encrypt( + secp256k1_context const* ctx, + secp256k1_pubkey* c1, + secp256k1_pubkey* c2, + secp256k1_pubkey const* pubkey_Q, + uint64_t amount, + unsigned char const* blinding_factor); /** * @brief Decrypts an ElGamal ciphertext to recover the amount. */ -SECP256K1_API int secp256k1_elgamal_decrypt( - const secp256k1_context* ctx, - uint64_t* amount, - const secp256k1_pubkey* c1, - const secp256k1_pubkey* c2, - const unsigned char* privkey -); +SECP256K1_API int +secp256k1_elgamal_decrypt( + secp256k1_context const* ctx, + uint64_t* amount, + secp256k1_pubkey const* c1, + secp256k1_pubkey const* c2, + unsigned char const* privkey); /** * @brief Homomorphically adds two ElGamal ciphertexts. */ -SECP256K1_API int secp256k1_elgamal_add( - const secp256k1_context* ctx, - secp256k1_pubkey* sum_c1, - secp256k1_pubkey* sum_c2, - const secp256k1_pubkey* a_c1, - const secp256k1_pubkey* a_c2, - const secp256k1_pubkey* b_c1, - const secp256k1_pubkey* b_c2 -); +SECP256K1_API int +secp256k1_elgamal_add( + secp256k1_context const* ctx, + secp256k1_pubkey* sum_c1, + secp256k1_pubkey* sum_c2, + secp256k1_pubkey const* a_c1, + secp256k1_pubkey const* a_c2, + secp256k1_pubkey const* b_c1, + secp256k1_pubkey const* b_c2); /** * @brief Homomorphically subtracts two ElGamal ciphertexts. */ -SECP256K1_API int secp256k1_elgamal_subtract( - const secp256k1_context* ctx, - secp256k1_pubkey* diff_c1, - secp256k1_pubkey* diff_c2, - const secp256k1_pubkey* a_c1, - const secp256k1_pubkey* a_c2, - const secp256k1_pubkey* b_c1, - const secp256k1_pubkey* b_c2 -); - +SECP256K1_API int +secp256k1_elgamal_subtract( + secp256k1_context const* ctx, + secp256k1_pubkey* diff_c1, + secp256k1_pubkey* diff_c2, + secp256k1_pubkey const* a_c1, + secp256k1_pubkey const* a_c2, + secp256k1_pubkey const* b_c1, + secp256k1_pubkey const* b_c2); /** * @brief Generates the canonical encrypted zero for a given MPT token instance. @@ -82,16 +81,16 @@ SECP256K1_API int secp256k1_elgamal_subtract( * * @return 1 on success, 0 on failure. */ -SECP256K1_API int generate_canonical_encrypted_zero( - const secp256k1_context* ctx, - secp256k1_pubkey* enc_zero_c1, - secp256k1_pubkey* enc_zero_c2, - const secp256k1_pubkey* pubkey, - const unsigned char* account_id, // 20 bytes - const unsigned char* mpt_issuance_id // 24 bytes +SECP256K1_API int +generate_canonical_encrypted_zero( + secp256k1_context const* ctx, + secp256k1_pubkey* enc_zero_c1, + secp256k1_pubkey* enc_zero_c2, + secp256k1_pubkey const* pubkey, + unsigned char const* account_id, // 20 bytes + unsigned char const* mpt_issuance_id // 24 bytes ); - // ... (includes and previous ElGamal declarations) ... /* @@ -119,15 +118,16 @@ SECP256K1_API int generate_canonical_encrypted_zero( * * @return 1 on success, 0 on failure. */ -SECP256K1_API int secp256k1_equality_plaintext_prove( - const secp256k1_context* ctx, - unsigned char* proof, // Output: 98 bytes - const secp256k1_pubkey* c1, - const secp256k1_pubkey* c2, - const secp256k1_pubkey* pk_recipient, - uint64_t amount, - const unsigned char* randomness_r, // Secret input - const unsigned char* tx_context_id // 32 bytes +SECP256K1_API int +secp256k1_equality_plaintext_prove( + secp256k1_context const* ctx, + unsigned char* proof, // Output: 98 bytes + secp256k1_pubkey const* c1, + secp256k1_pubkey const* c2, + secp256k1_pubkey const* pk_recipient, + uint64_t amount, + unsigned char const* randomness_r, // Secret input + unsigned char const* tx_context_id // 32 bytes ); /** @@ -147,19 +147,19 @@ SECP256K1_API int secp256k1_equality_plaintext_prove( * * @return 1 if the proof is valid, 0 otherwise. */ -SECP256K1_API int secp256k1_equality_plaintext_verify( - const secp256k1_context* ctx, - const unsigned char* proof, // Input: 98 bytes - const secp256k1_pubkey* c1, - const secp256k1_pubkey* c2, - const secp256k1_pubkey* pk_recipient, - uint64_t amount, - const unsigned char* tx_context_id // 32 bytes +SECP256K1_API int +secp256k1_equality_plaintext_verify( + secp256k1_context const* ctx, + unsigned char const* proof, // Input: 98 bytes + secp256k1_pubkey const* c1, + secp256k1_pubkey const* c2, + secp256k1_pubkey const* pk_recipient, + uint64_t amount, + unsigned char const* tx_context_id // 32 bytes ); // ... (rest of header, #endif etc.) - /* ================================================================================ | | @@ -183,16 +183,20 @@ SECP256K1_API int secp256k1_equality_plaintext_verify( * * @return 1 on success, 0 on failure. */ -SECP256K1_API int secp256k1_mpt_prove_same_plaintext( - const secp256k1_context* ctx, - unsigned char* proof_out, // Output: 261 bytes - const secp256k1_pubkey* R1, const secp256k1_pubkey* S1, const secp256k1_pubkey* P1, - const secp256k1_pubkey* R2, const secp256k1_pubkey* S2, const secp256k1_pubkey* P2, - uint64_t amount_m, - const unsigned char* randomness_r1, - const unsigned char* randomness_r2, - const unsigned char* tx_context_id -); +SECP256K1_API int +secp256k1_mpt_prove_same_plaintext( + secp256k1_context const* ctx, + unsigned char* proof_out, // Output: 261 bytes + secp256k1_pubkey const* R1, + secp256k1_pubkey const* S1, + secp256k1_pubkey const* P1, + secp256k1_pubkey const* R2, + secp256k1_pubkey const* S2, + secp256k1_pubkey const* P2, + uint64_t amount_m, + unsigned char const* randomness_r1, + unsigned char const* randomness_r2, + unsigned char const* tx_context_id); /** * @brief Verifies a proof that two ciphertexts encrypt the same secret amount. @@ -205,20 +209,23 @@ SECP256K1_API int secp256k1_mpt_prove_same_plaintext( * * @return 1 if the proof is valid, 0 otherwise. */ -SECP256K1_API int secp256k1_mpt_verify_same_plaintext( - const secp256k1_context* ctx, - const unsigned char* proof, // Input: 261 bytes - const secp256k1_pubkey* R1, const secp256k1_pubkey* S1, const secp256k1_pubkey* P1, - const secp256k1_pubkey* R2, const secp256k1_pubkey* S2, const secp256k1_pubkey* P2, - const unsigned char* tx_context_id -); - - +SECP256K1_API int +secp256k1_mpt_verify_same_plaintext( + secp256k1_context const* ctx, + unsigned char const* proof, // Input: 261 bytes + secp256k1_pubkey const* R1, + secp256k1_pubkey const* S1, + secp256k1_pubkey const* P1, + secp256k1_pubkey const* R2, + secp256k1_pubkey const* S2, + secp256k1_pubkey const* P2, + unsigned char const* tx_context_id); /** * @brief Calculates the expected proof size for a given number of ciphertexts. */ -SECP256K1_API size_t secp256k1_mpt_prove_same_plaintext_multi_size(size_t n_ciphertexts); +SECP256K1_API size_t +secp256k1_mpt_prove_same_plaintext_multi_size(size_t n_ciphertexts); /** * @brief Generates a proof that N ciphertexts encrypt the same secret amount 'm'. @@ -236,33 +243,32 @@ SECP256K1_API size_t secp256k1_mpt_prove_same_plaintext_multi_size(size_t n_ciph * * @return 1 on success, 0 on failure. */ -SECP256K1_API int secp256k1_mpt_prove_same_plaintext_multi( - const secp256k1_context* ctx, - unsigned char* proof_out, - size_t* proof_len, - uint64_t amount_m, - size_t n_ciphertexts, - const secp256k1_pubkey* R_array, - const secp256k1_pubkey* S_array, - const secp256k1_pubkey* Pk_array, - const unsigned char* r_array, // Flat array: r1 || r2 || ... (N * 32 bytes) - const unsigned char* tx_context_id -); +SECP256K1_API int +secp256k1_mpt_prove_same_plaintext_multi( + secp256k1_context const* ctx, + unsigned char* proof_out, + size_t* proof_len, + uint64_t amount_m, + size_t n_ciphertexts, + secp256k1_pubkey const* R_array, + secp256k1_pubkey const* S_array, + secp256k1_pubkey const* Pk_array, + unsigned char const* r_array, // Flat array: r1 || r2 || ... (N * 32 bytes) + unsigned char const* tx_context_id); /** * @brief Verifies a proof that N ciphertexts encrypt the same secret amount. */ -SECP256K1_API int secp256k1_mpt_verify_same_plaintext_multi( - const secp256k1_context* ctx, - const unsigned char* proof, - size_t proof_len, - size_t n_ciphertexts, - const secp256k1_pubkey* R_array, - const secp256k1_pubkey* S_array, - const secp256k1_pubkey* Pk_array, - const unsigned char* tx_context_id -); - +SECP256K1_API int +secp256k1_mpt_verify_same_plaintext_multi( + secp256k1_context const* ctx, + unsigned char const* proof, + size_t proof_len, + size_t n_ciphertexts, + secp256k1_pubkey const* R_array, + secp256k1_pubkey const* S_array, + secp256k1_pubkey const* Pk_array, + unsigned char const* tx_context_id); /** * @brief Computes a Pedersen Commitment: C = value*G + blinding_factor*Pk_base. @@ -278,37 +284,35 @@ SECP256K1_API int secp256k1_mpt_verify_same_plaintext_multi( * * @return 1 on success, 0 on failure. */ -SECP256K1_API int secp256k1_bulletproof_create_commitment( - const secp256k1_context* ctx, - secp256k1_pubkey* commitment_C, - uint64_t value, - const unsigned char* blinding_factor, - const secp256k1_pubkey* pk_base -); - - -int secp256k1_bulletproof_prove( - const secp256k1_context* ctx, - unsigned char* proof_out, - size_t* proof_len, - uint64_t value, - const unsigned char* blinding_factor, - const secp256k1_pubkey* pk_base, - const unsigned char* context_id, /* <--- AND HERE */ - unsigned int proof_type -); - - -int secp256k1_bulletproof_verify( - const secp256k1_context* ctx, - const secp256k1_pubkey* G_vec, - const secp256k1_pubkey* H_vec, - const unsigned char* proof, - size_t proof_len, - const secp256k1_pubkey* commitment_C, - const secp256k1_pubkey* pk_base, /* This is generator H */ - const unsigned char* context_id -); +SECP256K1_API int +secp256k1_bulletproof_create_commitment( + secp256k1_context const* ctx, + secp256k1_pubkey* commitment_C, + uint64_t value, + unsigned char const* blinding_factor, + secp256k1_pubkey const* pk_base); + +int +secp256k1_bulletproof_prove( + secp256k1_context const* ctx, + unsigned char* proof_out, + size_t* proof_len, + uint64_t value, + unsigned char const* blinding_factor, + secp256k1_pubkey const* pk_base, + unsigned char const* context_id, /* <--- AND HERE */ + unsigned int proof_type); + +int +secp256k1_bulletproof_verify( + secp256k1_context const* ctx, + secp256k1_pubkey const* G_vec, + secp256k1_pubkey const* H_vec, + unsigned char const* proof, + size_t proof_len, + secp256k1_pubkey const* commitment_C, + secp256k1_pubkey const* pk_base, /* This is generator H */ + unsigned char const* context_id); /** * @brief Proves the link between an ElGamal ciphertext and a Pedersen commitment. * * Formal Statement: Knowledge of (m, r, rho) such that: @@ -325,155 +329,160 @@ int secp256k1_bulletproof_verify( * @param context_id 32-byte unique transaction context identifier. * @return 1 on success, 0 on failure. */ -int secp256k1_elgamal_pedersen_link_prove( - const secp256k1_context* ctx, - unsigned char* proof, - const secp256k1_pubkey* c1, - const secp256k1_pubkey* c2, - const secp256k1_pubkey* pk, - const secp256k1_pubkey* pcm, - uint64_t amount, - const unsigned char* r, - const unsigned char* rho, - const unsigned char* context_id); +int +secp256k1_elgamal_pedersen_link_prove( + secp256k1_context const* ctx, + unsigned char* proof, + secp256k1_pubkey const* c1, + secp256k1_pubkey const* c2, + secp256k1_pubkey const* pk, + secp256k1_pubkey const* pcm, + uint64_t amount, + unsigned char const* r, + unsigned char const* rho, + unsigned char const* context_id); /** * @brief Verifies the link proof between ElGamal and Pedersen commitments. * * @return 1 if the proof is valid, 0 otherwise. */ -int secp256k1_elgamal_pedersen_link_verify( - const secp256k1_context* ctx, - const unsigned char* proof, - const secp256k1_pubkey* c1, - const secp256k1_pubkey* c2, - const secp256k1_pubkey* pk, - const secp256k1_pubkey* pcm, - const unsigned char* context_id); +int +secp256k1_elgamal_pedersen_link_verify( + secp256k1_context const* ctx, + unsigned char const* proof, + secp256k1_pubkey const* c1, + secp256k1_pubkey const* c2, + secp256k1_pubkey const* pk, + secp256k1_pubkey const* pcm, + unsigned char const* context_id); /** * Verifies that (c1, c2) is a valid ElGamal encryption of 'amount' * for 'pubkey_Q' using the revealed 'blinding_factor'. */ -int secp256k1_elgamal_verify_encryption( - const secp256k1_context* ctx, - const secp256k1_pubkey* c1, - const secp256k1_pubkey* c2, - const secp256k1_pubkey* pubkey_Q, - uint64_t amount, - const unsigned char* blinding_factor -); +int +secp256k1_elgamal_verify_encryption( + secp256k1_context const* ctx, + secp256k1_pubkey const* c1, + secp256k1_pubkey const* c2, + secp256k1_pubkey const* pubkey_Q, + uint64_t amount, + unsigned char const* blinding_factor); /** Proof of Knowledge of Secret Key for Registration */ -int secp256k1_mpt_pok_sk_prove( - const secp256k1_context* ctx, - unsigned char* proof, /* Expected size: 65 bytes */ - const secp256k1_pubkey* pk, - const unsigned char* sk, - const unsigned char* context_id -); - -int secp256k1_mpt_pok_sk_verify( - const secp256k1_context* ctx, - const unsigned char* proof, /* Expected size: 65 bytes */ - const secp256k1_pubkey* pk, - const unsigned char* context_id -); +int +secp256k1_mpt_pok_sk_prove( + secp256k1_context const* ctx, + unsigned char* proof, /* Expected size: 65 bytes */ + secp256k1_pubkey const* pk, + unsigned char const* sk, + unsigned char const* context_id); + +int +secp256k1_mpt_pok_sk_verify( + secp256k1_context const* ctx, + unsigned char const* proof, /* Expected size: 65 bytes */ + secp256k1_pubkey const* pk, + unsigned char const* context_id); /** * Compute a Pedersen Commitment: PC = m*G + rho*H * Returns 1 on success, 0 on failure. */ -int secp256k1_mpt_pedersen_commit( - const secp256k1_context* ctx, - secp256k1_pubkey* commitment, - uint64_t amount, - const unsigned char* blinding_factor_rho /* 32 bytes */ +int +secp256k1_mpt_pedersen_commit( + secp256k1_context const* ctx, + secp256k1_pubkey* commitment, + uint64_t amount, + unsigned char const* blinding_factor_rho /* 32 bytes */ ); /** Get the standardized H generator for Pedersen Commitments */ -int secp256k1_mpt_get_h_generator(const secp256k1_context* ctx, secp256k1_pubkey* h); +int +secp256k1_mpt_get_h_generator(secp256k1_context const* ctx, secp256k1_pubkey* h); /** * @brief Generates a vector of N independent NUMS generators. */ -int secp256k1_mpt_get_generator_vector( - const secp256k1_context* ctx, - secp256k1_pubkey* vec, - size_t n, - const unsigned char* label, - size_t label_len -); - -void secp256k1_mpt_scalar_add(unsigned char *res, const unsigned char *a, const unsigned char *b); -void secp256k1_mpt_scalar_mul(unsigned char *res, const unsigned char *a, const unsigned char *b); -void secp256k1_mpt_scalar_inverse(unsigned char *res, const unsigned char *in); -void secp256k1_mpt_scalar_negate(unsigned char *res, const unsigned char *in); -void secp256k1_mpt_scalar_reduce32(unsigned char out32[32], const unsigned char in32[32]); - +int +secp256k1_mpt_get_generator_vector( + secp256k1_context const* ctx, + secp256k1_pubkey* vec, + size_t n, + unsigned char const* label, + size_t label_len); + +void +secp256k1_mpt_scalar_add(unsigned char* res, unsigned char const* a, unsigned char const* b); +void +secp256k1_mpt_scalar_mul(unsigned char* res, unsigned char const* a, unsigned char const* b); +void +secp256k1_mpt_scalar_inverse(unsigned char* res, unsigned char const* in); +void +secp256k1_mpt_scalar_negate(unsigned char* res, unsigned char const* in); +void +secp256k1_mpt_scalar_reduce32(unsigned char out32[32], unsigned char const in32[32]); /** * Returns the size of the serialized proof for N recipients. * Size: (1 + N) * 33 bytes for points + 2 * 32 bytes for scalars. */ -size_t secp256k1_mpt_proof_equality_shared_r_size(size_t n); +size_t +secp256k1_mpt_proof_equality_shared_r_size(size_t n); /** * Generates a proof that multiple ciphertexts encrypt the same amount m * using the SAME shared randomness r. */ -int secp256k1_mpt_prove_equality_shared_r( - const secp256k1_context* ctx, - unsigned char* proof_out, - uint64_t amount, - const unsigned char* r_shared, - size_t n, - const secp256k1_pubkey* C1, - const secp256k1_pubkey* C2_vec, - const secp256k1_pubkey* Pk_vec, - const unsigned char* context_id -); +int +secp256k1_mpt_prove_equality_shared_r( + secp256k1_context const* ctx, + unsigned char* proof_out, + uint64_t amount, + unsigned char const* r_shared, + size_t n, + secp256k1_pubkey const* C1, + secp256k1_pubkey const* C2_vec, + secp256k1_pubkey const* Pk_vec, + unsigned char const* context_id); /** * Verifies the proof of equality with shared randomness. */ -int secp256k1_mpt_verify_equality_shared_r( - const secp256k1_context* ctx, - const unsigned char* proof, - size_t n, - const secp256k1_pubkey* C1, - const secp256k1_pubkey* C2_vec, - const secp256k1_pubkey* Pk_vec, - const unsigned char* context_id -); - -int secp256k1_bulletproof_prove_agg( - const secp256k1_context* ctx, - unsigned char* proof_out, - size_t* proof_len, - const uint64_t* values, - const unsigned char* blindings_flat, - size_t m, - const secp256k1_pubkey* pk_base, - const unsigned char* context_id -); -int secp256k1_bulletproof_verify_agg( - const secp256k1_context* ctx, - const secp256k1_pubkey* G_vec, /* length n = 64*m */ - const secp256k1_pubkey* H_vec, /* length n = 64*m */ - const unsigned char* proof, - size_t proof_len, - const secp256k1_pubkey* commitment_C_vec, /* length m */ - size_t m, - const secp256k1_pubkey* pk_base, - const unsigned char* context_id -); - - - - +int +secp256k1_mpt_verify_equality_shared_r( + secp256k1_context const* ctx, + unsigned char const* proof, + size_t n, + secp256k1_pubkey const* C1, + secp256k1_pubkey const* C2_vec, + secp256k1_pubkey const* Pk_vec, + unsigned char const* context_id); + +int +secp256k1_bulletproof_prove_agg( + secp256k1_context const* ctx, + unsigned char* proof_out, + size_t* proof_len, + uint64_t const* values, + unsigned char const* blindings_flat, + size_t m, + secp256k1_pubkey const* pk_base, + unsigned char const* context_id); +int +secp256k1_bulletproof_verify_agg( + secp256k1_context const* ctx, + secp256k1_pubkey const* G_vec, /* length n = 64*m */ + secp256k1_pubkey const* H_vec, /* length n = 64*m */ + unsigned char const* proof, + size_t proof_len, + secp256k1_pubkey const* commitment_C_vec, /* length m */ + size_t m, + secp256k1_pubkey const* pk_base, + unsigned char const* context_id); #ifdef __cplusplus } #endif -#endif // SECP256K1_MPT_H +#endif // SECP256K1_MPT_H diff --git a/src/bulletproof_aggregated.c b/src/bulletproof_aggregated.c index a6fd33b..2b0bad1 100644 --- a/src/bulletproof_aggregated.c +++ b/src/bulletproof_aggregated.c @@ -3,28 +3,34 @@ * @brief Aggregated Bulletproof Range Proofs (Logarithmic Size). * * This module implements non-interactive zero-knowledge range proofs based on - * the Bulletproofs protocol (Bünz et al., 2018). It allows a prover to demonstrate - * that a committed value lies within the range \f$ [0, 2^{64}) \f$ without revealing - * the value itself. + * the Bulletproofs protocol (Bünz et al., 2018). It allows a prover to + * demonstrate that a committed value lies within the range \f$ [0, 2^{64}) \f$ + * without revealing the value itself. * * @details * **Protocol Overview:** - * The implementation follows the standard single-value and aggregated Bulletproof logic: - * 1. **Pedersen Commitment:** The value \f$ v \f$ is committed as \f$ V = v \cdot G + r \cdot H \f$. - * 2. **Bit Decomposition:** The value is decomposed into 64 bits \f$ \mathbf{a}_L \f$. - * 3. **Polynomial Commitment:** The prover commits to polynomials defining the range constraints. - * 4. **Inner Product Argument (IPA):** A recursive argument reduces the proof size to - * logarithmic complexity \f$ \mathcal{O}(\log n) \f$. + * The implementation follows the standard single-value and aggregated + * Bulletproof logic: + * 1. **Pedersen Commitment:** The value \f$ v \f$ is committed as \f$ V = v + * \cdot G + r \cdot H \f$. + * 2. **Bit Decomposition:** The value is decomposed into 64 bits \f$ + * \mathbf{a}_L \f$. + * 3. **Polynomial Commitment:** The prover commits to polynomials defining the + * range constraints. + * 4. **Inner Product Argument (IPA):** A recursive argument reduces the proof + * size to logarithmic complexity \f$ \mathcal{O}(\log n) \f$. * * **Aggregation:** - * This implementation supports aggregating \f$ m \f$ proofs into a single verification - * process. The total vector length is \f$ n = 64 \cdot m \f$. Aggregation significantly - * reduces the on-chain footprint compared to \f$ m \f$ individual proofs. + * This implementation supports aggregating \f$ m \f$ proofs into a single + * verification process. The total vector length is \f$ n = 64 \cdot m \f$. + * Aggregation significantly reduces the on-chain footprint compared to \f$ m + * \f$ individual proofs. * * **Fiat-Shamir Transcript:** - * The non-interactive challenge generation follows a strict dependency chain to ensure - * binding and special soundness: - * - \f$ \mathcal{T}_0 \f$: Domain Tag || ContextID || Value Commitments (\f$ V \f$) + * The non-interactive challenge generation follows a strict dependency chain to + * ensure binding and special soundness: + * - \f$ \mathcal{T}_0 \f$: Domain Tag || ContextID || Value Commitments (\f$ V + * \f$) * - \f$ y, z \f$: Derived from \f$ \mathcal{T}_0 \parallel A \parallel S \f$ * - \f$ x \f$: Derived from \f$ z \parallel T_1 \parallel T_2 \f$ * - \f$ \mu \f$: Derived from \f$ x \f$ (for the IPA) @@ -33,15 +39,16 @@ * - Relies on `secp256k1` for elliptic curve arithmetic. * - Uses `SHA256` for the Fiat-Shamir transformation. * - * @see [Spec (ConfidentialMPT_20260201.pdf) Section 3.3.6 Range Proof (using Bulletproofs)] + * @see [Spec (ConfidentialMPT_20260201.pdf) Section 3.3.6 Range Proof (using + * Bulletproofs)] */ #include "secp256k1_mpt.h" -#include -#include -#include #include -#include +#include +#include #include +#include +#include /* Bit-size of each value proved in range */ #define BP_VALUE_BITS 64 @@ -50,86 +57,89 @@ #define BP_TOTAL_BITS(m) ((size_t)(BP_VALUE_BITS * (m))) /* Compute IPA rounds = log2(total_bits) */ -static inline size_t bp_ipa_rounds(size_t total_bits) { - size_t r = 0; - while (total_bits > 1) { - total_bits >>= 1; - r++; - } - return r; +static inline size_t bp_ipa_rounds(size_t total_bits) +{ + size_t r = 0; + while (total_bits > 1) + { + total_bits >>= 1; + r++; + } + return r; } /** Generates a secure 32-byte random scalar. * Returns 1 on success, 0 on failure. */ -static int generate_random_scalar( - const secp256k1_context* ctx, - unsigned char* scalar_bytes) +static int generate_random_scalar(const secp256k1_context *ctx, + unsigned char *scalar_bytes) { - do { - if (RAND_bytes(scalar_bytes, 32) != 1) { - return 0; // Randomness failure - } - } while (secp256k1_ec_seckey_verify(ctx, scalar_bytes) != 1); - return 1; + do + { + if (RAND_bytes(scalar_bytes, 32) != 1) + { + return 0; // Randomness failure + } + } while (secp256k1_ec_seckey_verify(ctx, scalar_bytes) != 1); + return 1; } /** * Computes the point M = amount * G. * Internal helper used by commitment construction. */ -static int compute_amount_point( - const secp256k1_context* ctx, - secp256k1_pubkey* mG, - uint64_t amount) +static int compute_amount_point(const secp256k1_context *ctx, + secp256k1_pubkey *mG, uint64_t amount) { - unsigned char amount_scalar[32] = {0}; + unsigned char amount_scalar[32] = {0}; - /* Zero amount is handled by the caller (no G term needed) */ - if (amount == 0) { - return 0; - } + /* Zero amount is handled by the caller (no G term needed) */ + if (amount == 0) + { + return 0; + } - for (int i = 0; i < 8; ++i) { - amount_scalar[31 - i] = (amount >> (i * 8)) & 0xFF; - } - return secp256k1_ec_pubkey_create(ctx, mG, amount_scalar); + for (int i = 0; i < 8; ++i) + { + amount_scalar[31 - i] = (amount >> (i * 8)) & 0xFF; + } + return secp256k1_ec_pubkey_create(ctx, mG, amount_scalar); } /** * Safely adds a point to an accumulator (acc += term). * Handles uninitialized accumulators by assignment instead of addition. */ -static int add_term( - const secp256k1_context* ctx, - secp256k1_pubkey* acc, - int* acc_inited, - const secp256k1_pubkey* term -) { - if (!(*acc_inited)) { - *acc = *term; - *acc_inited = 1; - return 1; - } else { - const secp256k1_pubkey* pts[2] = { acc, term }; - secp256k1_pubkey sum; - if (!secp256k1_ec_pubkey_combine(ctx, &sum, pts, 2)) return 0; - *acc = sum; - return 1; - } +static int add_term(const secp256k1_context *ctx, secp256k1_pubkey *acc, + int *acc_inited, const secp256k1_pubkey *term) +{ + if (!(*acc_inited)) + { + *acc = *term; + *acc_inited = 1; + return 1; + } + else + { + const secp256k1_pubkey *pts[2] = {acc, term}; + secp256k1_pubkey sum; + if (!secp256k1_ec_pubkey_combine(ctx, &sum, pts, 2)) + return 0; + *acc = sum; + return 1; + } } /** * Computes modular subtraction of two scalars: res = a - b (mod q). */ -static void secp256k1_mpt_scalar_sub(unsigned char *res, - const unsigned char *a, - const unsigned char *b) +static void secp256k1_mpt_scalar_sub(unsigned char *res, const unsigned char *a, + const unsigned char *b) { - unsigned char neg_b[32]; - memcpy(neg_b, b, 32); - secp256k1_mpt_scalar_negate(neg_b, neg_b); /* neg_b = -b mod q */ - secp256k1_mpt_scalar_add(res, a, neg_b); /* res = a + (-b) */ - OPENSSL_cleanse(neg_b, 32); + unsigned char neg_b[32]; + memcpy(neg_b, b, 32); + secp256k1_mpt_scalar_negate(neg_b, neg_b); /* neg_b = -b mod q */ + secp256k1_mpt_scalar_add(res, a, neg_b); /* res = a + (-b) */ + OPENSSL_cleanse(neg_b, 32); } /** * Computes the modular dot product c = = sum(a[i] * b[i]) mod q. @@ -141,39 +151,37 @@ static void secp256k1_mpt_scalar_sub(unsigned char *res, * n The length of the vectors. * 1 on success, 0 on failure. */ -int secp256k1_bulletproof_ipa_dot( - const secp256k1_context* ctx, - unsigned char* out, - const unsigned char* a, - const unsigned char* b, - size_t n -) { - unsigned char acc[32] = {0}; - unsigned char term[32]; - - for (size_t i = 0; i < n; i++) { - secp256k1_mpt_scalar_mul(term, a + i * 32, b + i * 32); - secp256k1_mpt_scalar_add(acc, acc, term); - } - memcpy(out, acc, 32); - return 1; +int secp256k1_bulletproof_ipa_dot(const secp256k1_context *ctx, + unsigned char *out, const unsigned char *a, + const unsigned char *b, size_t n) +{ + unsigned char acc[32] = {0}; + unsigned char term[32]; + + for (size_t i = 0; i < n; i++) + { + secp256k1_mpt_scalar_mul(term, a + i * 32, b + i * 32); + secp256k1_mpt_scalar_add(acc, acc, term); + } + memcpy(out, acc, 32); + return 1; } /** * Internal helper for multi-scalar multiplication. * Adds a point to the accumulator. */ -int secp256k1_bulletproof_add_point_to_accumulator( - const secp256k1_context* ctx, - secp256k1_pubkey* acc, - const secp256k1_pubkey* term) +int secp256k1_bulletproof_add_point_to_accumulator(const secp256k1_context *ctx, + secp256k1_pubkey *acc, + const secp256k1_pubkey *term) { - const secp256k1_pubkey* points[2] = {acc, term}; - secp256k1_pubkey temp_sum; + const secp256k1_pubkey *points[2] = {acc, term}; + secp256k1_pubkey temp_sum; - if (secp256k1_ec_pubkey_combine(ctx, &temp_sum, points, 2) != 1) return 0; - *acc = temp_sum; - return 1; + if (secp256k1_ec_pubkey_combine(ctx, &temp_sum, points, 2) != 1) + return 0; + *acc = temp_sum; + return 1; } /** * Computes Multiscalar Multiplication (MSM): R = sum(s[i] * P[i]). @@ -187,141 +195,136 @@ int secp256k1_bulletproof_add_point_to_accumulator( * It is NOT constant-time with respect to scalars and MUST NOT be used * for secret-key operations. */ -int secp256k1_bulletproof_ipa_msm( - const secp256k1_context* ctx, - secp256k1_pubkey* r_out, - const secp256k1_pubkey* points, - const unsigned char* scalars, - size_t n -) { - secp256k1_pubkey acc; - memset(&acc, 0, sizeof(acc)); - int initialized = 0; - unsigned char zero[32] = {0}; - - for (size_t i = 0; i < n; ++i) { - if (memcmp(scalars + i * 32, zero, 32) == 0) - continue; - - secp256k1_pubkey term = points[i]; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, scalars + i * 32)) - return 0; - - if (!add_term(ctx, &acc, &initialized, &term)) - return 0; - } - - /* All scalars zero → result is infinity (not representable here) */ - if (!initialized) - return 0; +int secp256k1_bulletproof_ipa_msm(const secp256k1_context *ctx, + secp256k1_pubkey *r_out, + const secp256k1_pubkey *points, + const unsigned char *scalars, size_t n) +{ + secp256k1_pubkey acc; + memset(&acc, 0, sizeof(acc)); + int initialized = 0; + unsigned char zero[32] = {0}; + + for (size_t i = 0; i < n; ++i) + { + if (memcmp(scalars + i * 32, zero, 32) == 0) + continue; + + secp256k1_pubkey term = points[i]; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, scalars + i * 32)) + return 0; + + if (!add_term(ctx, &acc, &initialized, &term)) + return 0; + } + + /* All scalars zero → result is infinity (not representable here) */ + if (!initialized) + return 0; - *r_out = acc; - return 1; + *r_out = acc; + return 1; } /* Try to add MSM(points, scalars) into acc. * If MSM is all-zero, do nothing and succeed. */ -static int msm_try_add( - const secp256k1_context* ctx, - secp256k1_pubkey* acc, - int* acc_inited, - const secp256k1_pubkey* points, - const unsigned char* scalars, - size_t n -) { - secp256k1_pubkey tmp; - - /* MSM returns 0 iff all scalars are zero. - * In that case, we have nothing to add, so we return success (1). */ - if (!secp256k1_bulletproof_ipa_msm(ctx, &tmp, points, scalars, n)) { - return 1; - } - return add_term(ctx, acc, acc_inited, &tmp); +static int msm_try_add(const secp256k1_context *ctx, secp256k1_pubkey *acc, + int *acc_inited, const secp256k1_pubkey *points, + const unsigned char *scalars, size_t n) +{ + secp256k1_pubkey tmp; + + /* MSM returns 0 iff all scalars are zero. + * In that case, we have nothing to add, so we return success (1). */ + if (!secp256k1_bulletproof_ipa_msm(ctx, &tmp, points, scalars, n)) + { + return 1; + } + return add_term(ctx, acc, acc_inited, &tmp); } /** * Computes component-wise: result[i] = a[i] * b[i] (Hadamard product) */ -void scalar_vector_mul(const secp256k1_context* ctx, unsigned char res[][32], - unsigned char a[][32], unsigned char b[][32], size_t n) { - for (size_t i = 0; i < n; i++) { - secp256k1_mpt_scalar_mul(res[i], a[i], b[i]); - } +void scalar_vector_mul(const secp256k1_context *ctx, unsigned char res[][32], + unsigned char a[][32], unsigned char b[][32], size_t n) +{ + for (size_t i = 0; i < n; i++) + { + secp256k1_mpt_scalar_mul(res[i], a[i], b[i]); + } } /** * Computes component-wise: result[i] = a[i] + b[i] */ -void scalar_vector_add(const secp256k1_context* ctx, unsigned char res[][32], - unsigned char a[][32], unsigned char b[][32], size_t n) { - for (size_t i = 0; i < n; i++) { - secp256k1_mpt_scalar_add(res[i], a[i], b[i]); - } - +void scalar_vector_add(const secp256k1_context *ctx, unsigned char res[][32], + unsigned char a[][32], unsigned char b[][32], size_t n) +{ + for (size_t i = 0; i < n; i++) + { + secp256k1_mpt_scalar_add(res[i], a[i], b[i]); + } } /** * Fills a vector with powers of a scalar: [1, y, y^2, ..., y^{n-1}] */ -void scalar_vector_powers( - const secp256k1_context* ctx, - unsigned char res[][32], - const unsigned char* y, - size_t n -) { - if (n == 0) return; - - unsigned char one[32] = {0}; - one[31] = 1; - memcpy(res[0], one, 32); - - for (size_t i = 1; i < n; i++) { - secp256k1_mpt_scalar_mul(res[i], res[i-1], y); - } +void scalar_vector_powers(const secp256k1_context *ctx, unsigned char res[][32], + const unsigned char *y, size_t n) +{ + if (n == 0) + return; + + unsigned char one[32] = {0}; + one[31] = 1; + memcpy(res[0], one, 32); + + for (size_t i = 1; i < n; i++) + { + secp256k1_mpt_scalar_mul(res[i], res[i - 1], y); + } } /** * Compute y^i for small i. */ -static void scalar_pow_u32( - const secp256k1_context* ctx, - unsigned char y_pow_out[32], - const unsigned char y[32], - unsigned int i) +static void scalar_pow_u32(const secp256k1_context *ctx, + unsigned char y_pow_out[32], + const unsigned char y[32], unsigned int i) { - (void)ctx; - unsigned char one[32] = {0}; - one[31] = 1; - memcpy(y_pow_out, one, 32); - - while (i--) { - secp256k1_mpt_scalar_mul(y_pow_out, y_pow_out, y); - } + (void)ctx; + unsigned char one[32] = {0}; + one[31] = 1; + memcpy(y_pow_out, one, 32); + + while (i--) + { + secp256k1_mpt_scalar_mul(y_pow_out, y_pow_out, y); + } } /** * z_j2 = z^(j+2) for j = 0..m-1 (small exponent) -*/ -static void compute_z_pows_j2( - const secp256k1_context* ctx, - unsigned char (*z_j2)[32], /* m x 32 */ - const unsigned char z[32], - size_t m -) { - for (size_t j = 0; j < m; j++) { - scalar_pow_u32(ctx, z_j2[j], z, (unsigned int)(j + 2)); - } + */ +static void compute_z_pows_j2(const secp256k1_context *ctx, + unsigned char (*z_j2)[32], /* m x 32 */ + const unsigned char z[32], size_t m) +{ + for (size_t j = 0; j < m; j++) + { + scalar_pow_u32(ctx, z_j2[j], z, (unsigned int)(j + 2)); + } } /** * Point = Scalar * Point (using public API) */ -static int secp256k1_bulletproof_point_scalar_mul( - const secp256k1_context* ctx, - secp256k1_pubkey* r_out, - const secp256k1_pubkey* p_in, - const unsigned char* s_scalar) +static int secp256k1_bulletproof_point_scalar_mul(const secp256k1_context *ctx, + secp256k1_pubkey *r_out, + const secp256k1_pubkey *p_in, + const unsigned char *s_scalar) { - *r_out = *p_in; - return secp256k1_ec_pubkey_tweak_mul(ctx, r_out, s_scalar); + *r_out = *p_in; + return secp256k1_ec_pubkey_tweak_mul(ctx, r_out, s_scalar); } /** @@ -332,104 +335,113 @@ static int secp256k1_bulletproof_point_scalar_mul( * two_sum = sum_{i=0}^{63} 2^i * These are used by the caller to construct delta(y, z). */ -static void compute_delta_scalars( - const secp256k1_context* ctx, - unsigned char (*y_block_sum)[32], /* m blocks */ - unsigned char two_sum[32], - const unsigned char y[32], - size_t m -) { - (void)ctx; - - unsigned char one[32] = {0}; - unsigned char y_pow[32]; - unsigned char two_pow[32]; - - one[31] = 1; - - /* Compute two_sum = sum_{i=0}^{63} 2^i */ - memset(two_sum, 0, 32); - memcpy(two_pow, one, 32); - for (size_t i = 0; i < 64; i++) { - secp256k1_mpt_scalar_add(two_sum, two_sum, two_pow); - secp256k1_mpt_scalar_add(two_pow, two_pow, two_pow); - } +static void +compute_delta_scalars(const secp256k1_context *ctx, + unsigned char (*y_block_sum)[32], /* m blocks */ + unsigned char two_sum[32], const unsigned char y[32], + size_t m) +{ + (void)ctx; - /* Compute y_block_sum[j] = sum_{i=0}^{63} y^{64j + i} */ - memcpy(y_pow, one, 32); /* y^0 */ + unsigned char one[32] = {0}; + unsigned char y_pow[32]; + unsigned char two_pow[32]; - for (size_t j = 0; j < m; j++) { - memset(y_block_sum[j], 0, 32); + one[31] = 1; - for (size_t i = 0; i < 64; i++) { - secp256k1_mpt_scalar_add(y_block_sum[j], y_block_sum[j], y_pow); - secp256k1_mpt_scalar_mul(y_pow, y_pow, y); /* advance y^k */ - } + /* Compute two_sum = sum_{i=0}^{63} 2^i */ + memset(two_sum, 0, 32); + memcpy(two_pow, one, 32); + for (size_t i = 0; i < 64; i++) + { + secp256k1_mpt_scalar_add(two_sum, two_sum, two_pow); + secp256k1_mpt_scalar_add(two_pow, two_pow, two_pow); + } + + /* Compute y_block_sum[j] = sum_{i=0}^{63} y^{64j + i} */ + memcpy(y_pow, one, 32); /* y^0 */ + + for (size_t j = 0; j < m; j++) + { + memset(y_block_sum[j], 0, 32); + + for (size_t i = 0; i < 64; i++) + { + secp256k1_mpt_scalar_add(y_block_sum[j], y_block_sum[j], y_pow); + secp256k1_mpt_scalar_mul(y_pow, y_pow, y); /* advance y^k */ } + } } /** * Compare two secp256k1 public keys for equality. */ -static int pubkey_equal(const secp256k1_context* ctx, const secp256k1_pubkey* a, const secp256k1_pubkey* b) { - return secp256k1_ec_pubkey_cmp(ctx, a, b) == 0; +static int pubkey_equal(const secp256k1_context *ctx, const secp256k1_pubkey *a, + const secp256k1_pubkey *b) +{ + return secp256k1_ec_pubkey_cmp(ctx, a, b) == 0; } /** * u_flat and uinv_flat are arrays of length (rounds * 32): * u_j = u_flat + 32*j * u_j_inv = uinv_flat + 32*j */ -int fold_generators( - const secp256k1_context* ctx, - secp256k1_pubkey* final_point, - const secp256k1_pubkey* generators, - const unsigned char* u_flat, - const unsigned char* uinv_flat, - size_t n, - size_t rounds, - int is_H /* 0 = G folding, 1 = H folding */ -) { - /* n must be power-of-two and rounds must match log2(n) */ - if (n == 0 || (n & (n - 1)) != 0) return 0; - if (((size_t)1 << rounds) != n) return 0; - - /* Allocate scalars for MSM: n * 32 bytes */ - unsigned char* s_flat = (unsigned char*)malloc(n * 32); - if (!s_flat) return 0; - - unsigned char current_s[32]; - int ok = 0; - - for (size_t i = 0; i < n; i++) { - /* current_s = 1 */ - memset(current_s, 0, 32); - current_s[31] = 1; - - for (size_t j = 0; j < rounds; j++) { - /* bit from MSB to LSB across 'rounds' bits */ - int bit = (int)((i >> (rounds - 1 - j)) & 1); - - const unsigned char* uj = u_flat + 32 * j; - const unsigned char* ujinv = uinv_flat + 32 * j; - - if (!is_H) { - /* G folding: bit 0 -> u_inv, bit 1 -> u */ - secp256k1_mpt_scalar_mul(current_s, current_s, bit ? uj : ujinv); - } else { - /* H folding: bit 0 -> u, bit 1 -> u_inv */ - secp256k1_mpt_scalar_mul(current_s, current_s, bit ? ujinv : uj); - } - } +int fold_generators(const secp256k1_context *ctx, secp256k1_pubkey *final_point, + const secp256k1_pubkey *generators, + const unsigned char *u_flat, const unsigned char *uinv_flat, + size_t n, size_t rounds, + int is_H /* 0 = G folding, 1 = H folding */ +) +{ + /* n must be power-of-two and rounds must match log2(n) */ + if (n == 0 || (n & (n - 1)) != 0) + return 0; + if (((size_t)1 << rounds) != n) + return 0; + + /* Allocate scalars for MSM: n * 32 bytes */ + unsigned char *s_flat = (unsigned char *)malloc(n * 32); + if (!s_flat) + return 0; + + unsigned char current_s[32]; + int ok = 0; - memcpy(s_flat + (i * 32), current_s, 32); + for (size_t i = 0; i < n; i++) + { + /* current_s = 1 */ + memset(current_s, 0, 32); + current_s[31] = 1; + + for (size_t j = 0; j < rounds; j++) + { + /* bit from MSB to LSB across 'rounds' bits */ + int bit = (int)((i >> (rounds - 1 - j)) & 1); + + const unsigned char *uj = u_flat + 32 * j; + const unsigned char *ujinv = uinv_flat + 32 * j; + + if (!is_H) + { + /* G folding: bit 0 -> u_inv, bit 1 -> u */ + secp256k1_mpt_scalar_mul(current_s, current_s, bit ? uj : ujinv); + } + else + { + /* H folding: bit 0 -> u, bit 1 -> u_inv */ + secp256k1_mpt_scalar_mul(current_s, current_s, bit ? ujinv : uj); + } } - ok = secp256k1_bulletproof_ipa_msm(ctx, final_point, generators, s_flat, n); + memcpy(s_flat + (i * 32), current_s, 32); + } - OPENSSL_cleanse(current_s, 32); - OPENSSL_cleanse(s_flat, n * 32); - free(s_flat); + ok = secp256k1_bulletproof_ipa_msm(ctx, final_point, generators, s_flat, n); - return ok; + OPENSSL_cleanse(current_s, 32); + OPENSSL_cleanse(s_flat, n * 32); + free(s_flat); + + return ok; } /* * Apply verifier-side IPA updates to P for `rounds` rounds. @@ -439,61 +451,61 @@ int fold_generators( * u_i = u_flat + 32*i * u_iinv = uinv_flat + 32*i */ -int apply_ipa_folding_to_P( - const secp256k1_context* ctx, - secp256k1_pubkey* P, - const secp256k1_pubkey* L_vec, - const secp256k1_pubkey* R_vec, - const unsigned char* u_flat, - const unsigned char* uinv_flat, - size_t rounds -) { - unsigned char u_sq[32], uinv_sq[32]; - secp256k1_pubkey tL, tR; - const secp256k1_pubkey* pts[3]; - - for (size_t i = 0; i < rounds; i++) { - const unsigned char* ui = u_flat + 32 * i; - const unsigned char* uiinv = uinv_flat + 32 * i; - - /* u_sq = u_i^2, uinv_sq = (u_i^{-1})^2 = u_i^{-2} */ - secp256k1_mpt_scalar_mul(u_sq, ui, ui); - secp256k1_mpt_scalar_mul(uinv_sq, uiinv, uiinv); - - /* tL = (u_i^2) * L_i */ - tL = L_vec[i]; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tL, u_sq)) { - OPENSSL_cleanse(u_sq, 32); - OPENSSL_cleanse(uinv_sq, 32); - return 0; - } - - /* tR = (u_i^{-2}) * R_i */ - tR = R_vec[i]; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tR, uinv_sq)) { - OPENSSL_cleanse(u_sq, 32); - OPENSSL_cleanse(uinv_sq, 32); - return 0; - } +int apply_ipa_folding_to_P(const secp256k1_context *ctx, secp256k1_pubkey *P, + const secp256k1_pubkey *L_vec, + const secp256k1_pubkey *R_vec, + const unsigned char *u_flat, + const unsigned char *uinv_flat, size_t rounds) +{ + unsigned char u_sq[32], uinv_sq[32]; + secp256k1_pubkey tL, tR; + const secp256k1_pubkey *pts[3]; + + for (size_t i = 0; i < rounds; i++) + { + const unsigned char *ui = u_flat + 32 * i; + const unsigned char *uiinv = uinv_flat + 32 * i; + + /* u_sq = u_i^2, uinv_sq = (u_i^{-1})^2 = u_i^{-2} */ + secp256k1_mpt_scalar_mul(u_sq, ui, ui); + secp256k1_mpt_scalar_mul(uinv_sq, uiinv, uiinv); + + /* tL = (u_i^2) * L_i */ + tL = L_vec[i]; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tL, u_sq)) + { + OPENSSL_cleanse(u_sq, 32); + OPENSSL_cleanse(uinv_sq, 32); + return 0; + } - /* P <- P + tL + tR */ - pts[0] = P; - pts[1] = &tL; - pts[2] = &tR; + /* tR = (u_i^{-2}) * R_i */ + tR = R_vec[i]; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tR, uinv_sq)) + { + OPENSSL_cleanse(u_sq, 32); + OPENSSL_cleanse(uinv_sq, 32); + return 0; + } - secp256k1_pubkey newP; - if (!secp256k1_ec_pubkey_combine(ctx, &newP, pts, 3)) { - OPENSSL_cleanse(u_sq, 32); - OPENSSL_cleanse(uinv_sq, 32); - return 0; - } - *P = newP; + /* P <- P + tL + tR */ + pts[0] = P; + pts[1] = &tL; + pts[2] = &tR; + secp256k1_pubkey newP; + if (!secp256k1_ec_pubkey_combine(ctx, &newP, pts, 3)) + { + OPENSSL_cleanse(u_sq, 32); + OPENSSL_cleanse(uinv_sq, 32); + return 0; } + *P = newP; + } - OPENSSL_cleanse(u_sq, 32); - OPENSSL_cleanse(uinv_sq, 32); - return 1; + OPENSSL_cleanse(u_sq, 32); + OPENSSL_cleanse(uinv_sq, 32); + return 1; } /** @@ -509,84 +521,90 @@ int apply_ipa_folding_to_P( */ int secp256k1_bulletproof_ipa_compute_LR( - const secp256k1_context* ctx, - secp256k1_pubkey* L, - secp256k1_pubkey* R, - const unsigned char* a_L, - const unsigned char* a_R, - const unsigned char* b_L, - const unsigned char* b_R, - const secp256k1_pubkey* G_L, - const secp256k1_pubkey* G_R, - const secp256k1_pubkey* H_L, - const secp256k1_pubkey* H_R, - const secp256k1_pubkey* U, - const unsigned char* ux, - size_t half_n -) { - unsigned char cL[32], cR[32]; - unsigned char cLux[32], cRux[32]; - unsigned char zero[32] = {0}; - - secp256k1_pubkey acc, term; - int acc_inited; /* Tracks if acc contains a valid point */ - - /* cL = , cR = */ - if (!secp256k1_bulletproof_ipa_dot(ctx, cL, a_L, b_R, half_n)) return 0; - if (!secp256k1_bulletproof_ipa_dot(ctx, cR, a_R, b_L, half_n)) return 0; - - /* ---------------- L Calculation ---------------- */ - acc_inited = 0; - - /* Try adding terms. correct logic updates acc_inited to 1 */ - if (!msm_try_add(ctx, &acc, &acc_inited, G_R, a_L, half_n)) goto cleanup; - if (!msm_try_add(ctx, &acc, &acc_inited, H_L, b_R, half_n)) goto cleanup; - - secp256k1_mpt_scalar_mul(cLux, cL, ux); - if (memcmp(cLux, zero, 32) != 0) { - term = *U; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, cLux)) goto cleanup; - if (!add_term(ctx, &acc, &acc_inited, &term)) goto cleanup; - } - - /* Check initialization explicitly */ - if (acc_inited == 0) { - /* L resulted in Infinity. This is theoretically possible but invalid for serialization. - * We cannot proceed. */ - goto cleanup; - } - *L = acc; - - /* ---------------- R Calculation ---------------- */ - acc_inited = 0; - - if (!msm_try_add(ctx, &acc, &acc_inited, G_L, a_R, half_n)) goto cleanup; - if (!msm_try_add(ctx, &acc, &acc_inited, H_R, b_L, half_n)) goto cleanup; - - secp256k1_mpt_scalar_mul(cRux, cR, ux); - if (memcmp(cRux, zero, 32) != 0) { - term = *U; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, cRux)) goto cleanup; - if (!add_term(ctx, &acc, &acc_inited, &term)) goto cleanup; - } - - if (acc_inited == 0) { - goto cleanup; - } - *R = acc; + const secp256k1_context *ctx, secp256k1_pubkey *L, secp256k1_pubkey *R, + const unsigned char *a_L, const unsigned char *a_R, + const unsigned char *b_L, const unsigned char *b_R, + const secp256k1_pubkey *G_L, const secp256k1_pubkey *G_R, + const secp256k1_pubkey *H_L, const secp256k1_pubkey *H_R, + const secp256k1_pubkey *U, const unsigned char *ux, size_t half_n) +{ + unsigned char cL[32], cR[32]; + unsigned char cLux[32], cRux[32]; + unsigned char zero[32] = {0}; - OPENSSL_cleanse(cL, 32); - OPENSSL_cleanse(cR, 32); - OPENSSL_cleanse(cLux, 32); - OPENSSL_cleanse(cRux, 32); - return 1; + secp256k1_pubkey acc, term; + int acc_inited; /* Tracks if acc contains a valid point */ - cleanup: - OPENSSL_cleanse(cL, 32); - OPENSSL_cleanse(cR, 32); - OPENSSL_cleanse(cLux, 32); - OPENSSL_cleanse(cRux, 32); + /* cL = , cR = */ + if (!secp256k1_bulletproof_ipa_dot(ctx, cL, a_L, b_R, half_n)) return 0; + if (!secp256k1_bulletproof_ipa_dot(ctx, cR, a_R, b_L, half_n)) + return 0; + + /* ---------------- L Calculation ---------------- */ + acc_inited = 0; + + /* Try adding terms. correct logic updates acc_inited to 1 */ + if (!msm_try_add(ctx, &acc, &acc_inited, G_R, a_L, half_n)) + goto cleanup; + if (!msm_try_add(ctx, &acc, &acc_inited, H_L, b_R, half_n)) + goto cleanup; + + secp256k1_mpt_scalar_mul(cLux, cL, ux); + if (memcmp(cLux, zero, 32) != 0) + { + term = *U; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, cLux)) + goto cleanup; + if (!add_term(ctx, &acc, &acc_inited, &term)) + goto cleanup; + } + + /* Check initialization explicitly */ + if (acc_inited == 0) + { + /* L resulted in Infinity. This is theoretically possible but invalid for + * serialization. We cannot proceed. */ + goto cleanup; + } + *L = acc; + + /* ---------------- R Calculation ---------------- */ + acc_inited = 0; + + if (!msm_try_add(ctx, &acc, &acc_inited, G_L, a_R, half_n)) + goto cleanup; + if (!msm_try_add(ctx, &acc, &acc_inited, H_R, b_L, half_n)) + goto cleanup; + + secp256k1_mpt_scalar_mul(cRux, cR, ux); + if (memcmp(cRux, zero, 32) != 0) + { + term = *U; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, cRux)) + goto cleanup; + if (!add_term(ctx, &acc, &acc_inited, &term)) + goto cleanup; + } + + if (acc_inited == 0) + { + goto cleanup; + } + *R = acc; + + OPENSSL_cleanse(cL, 32); + OPENSSL_cleanse(cR, 32); + OPENSSL_cleanse(cLux, 32); + OPENSSL_cleanse(cRux, 32); + return 1; + +cleanup: + OPENSSL_cleanse(cL, 32); + OPENSSL_cleanse(cR, 32); + OPENSSL_cleanse(cLux, 32); + OPENSSL_cleanse(cRux, 32); + return 0; } /** * One IPA compression step (in-place). @@ -604,74 +622,85 @@ int secp256k1_bulletproof_ipa_compute_LR( * G'[i] = GL[i]*x_inv + GR[i]*x * H'[i] = HL[i]*x + HR[i]*x_inv */ -int secp256k1_bulletproof_ipa_compress_step( - const secp256k1_context* ctx, - unsigned char* a, - unsigned char* b, - secp256k1_pubkey* G, - secp256k1_pubkey* H, - size_t half_n, - const unsigned char* x, - const unsigned char* x_inv -) { - size_t i; - int ok = 0; - - unsigned char t1[32], t2[32]; - secp256k1_pubkey left, right; - const secp256k1_pubkey* pts[2]; - - if (ctx == NULL || a == NULL || b == NULL || G == NULL || H == NULL) return 0; - if (half_n == 0) return 0; - - /* x and x_inv must be valid non-zero scalars */ - if (secp256k1_ec_seckey_verify(ctx, x) != 1) return 0; - if (secp256k1_ec_seckey_verify(ctx, x_inv) != 1) return 0; - - for (i = 0; i < half_n; ++i) { - unsigned char* aL = a + (i * 32); - unsigned char* aR = a + ((i + half_n) * 32); - - unsigned char* bL = b + (i * 32); - unsigned char* bR = b + ((i + half_n) * 32); - - /* a'[i] = aL*x + aR*x_inv */ - secp256k1_mpt_scalar_mul(t1, aL, x); - secp256k1_mpt_scalar_mul(t2, aR, x_inv); - secp256k1_mpt_scalar_add(aL, t1, t2); - - /* b'[i] = bL*x_inv + bR*x */ - secp256k1_mpt_scalar_mul(t1, bL, x_inv); - secp256k1_mpt_scalar_mul(t2, bR, x); - secp256k1_mpt_scalar_add(bL, t1, t2); - - /* G'[i] = GL*x_inv + GR*x */ - left = G[i]; - right = G[i + half_n]; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &left, x_inv)) goto cleanup; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &right, x)) goto cleanup; - pts[0] = &left; pts[1] = &right; - if (!secp256k1_ec_pubkey_combine(ctx, &G[i], pts, 2)) goto cleanup; - - /* H'[i] = HL*x + HR*x_inv */ - left = H[i]; - right = H[i + half_n]; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &left, x)) goto cleanup; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &right, x_inv)) goto cleanup; - pts[0] = &left; pts[1] = &right; - if (!secp256k1_ec_pubkey_combine(ctx, &H[i], pts, 2)) goto cleanup; - } +int secp256k1_bulletproof_ipa_compress_step(const secp256k1_context *ctx, + unsigned char *a, unsigned char *b, + secp256k1_pubkey *G, + secp256k1_pubkey *H, size_t half_n, + const unsigned char *x, + const unsigned char *x_inv) +{ + size_t i; + int ok = 0; - ok = 1; + unsigned char t1[32], t2[32]; + secp256k1_pubkey left, right; + const secp256k1_pubkey *pts[2]; + + if (ctx == NULL || a == NULL || b == NULL || G == NULL || H == NULL) + return 0; + if (half_n == 0) + return 0; + + /* x and x_inv must be valid non-zero scalars */ + if (secp256k1_ec_seckey_verify(ctx, x) != 1) + return 0; + if (secp256k1_ec_seckey_verify(ctx, x_inv) != 1) + return 0; - cleanup: - OPENSSL_cleanse(t1, 32); - OPENSSL_cleanse(t2, 32); - return ok; + for (i = 0; i < half_n; ++i) + { + unsigned char *aL = a + (i * 32); + unsigned char *aR = a + ((i + half_n) * 32); + + unsigned char *bL = b + (i * 32); + unsigned char *bR = b + ((i + half_n) * 32); + + /* a'[i] = aL*x + aR*x_inv */ + secp256k1_mpt_scalar_mul(t1, aL, x); + secp256k1_mpt_scalar_mul(t2, aR, x_inv); + secp256k1_mpt_scalar_add(aL, t1, t2); + + /* b'[i] = bL*x_inv + bR*x */ + secp256k1_mpt_scalar_mul(t1, bL, x_inv); + secp256k1_mpt_scalar_mul(t2, bR, x); + secp256k1_mpt_scalar_add(bL, t1, t2); + + /* G'[i] = GL*x_inv + GR*x */ + left = G[i]; + right = G[i + half_n]; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &left, x_inv)) + goto cleanup; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &right, x)) + goto cleanup; + pts[0] = &left; + pts[1] = &right; + if (!secp256k1_ec_pubkey_combine(ctx, &G[i], pts, 2)) + goto cleanup; + + /* H'[i] = HL*x + HR*x_inv */ + left = H[i]; + right = H[i + half_n]; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &left, x)) + goto cleanup; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &right, x_inv)) + goto cleanup; + pts[0] = &left; + pts[1] = &right; + if (!secp256k1_ec_pubkey_combine(ctx, &H[i], pts, 2)) + goto cleanup; + } + + ok = 1; + +cleanup: + OPENSSL_cleanse(t1, 32); + OPENSSL_cleanse(t2, 32); + return ok; } -static int scalar_is_zero(const unsigned char s[32]) { - unsigned char z[32] = {0}; - return memcmp(s, z, 32) == 0; +static int scalar_is_zero(const unsigned char s[32]) +{ + unsigned char z[32] = {0}; + return memcmp(s, z, 32) == 0; } /* @@ -687,68 +716,72 @@ static int scalar_is_zero(const unsigned char s[32]) { * It MUST NOT depend on per-round challenges (u_i), * and MUST be identical for prover and verifier. */ -int derive_ipa_binding_challenge( - const secp256k1_context* ctx, - unsigned char* ux_out, - const unsigned char* commit_inp_32, - const unsigned char* dot_32) +int derive_ipa_binding_challenge(const secp256k1_context *ctx, + unsigned char *ux_out, + const unsigned char *commit_inp_32, + const unsigned char *dot_32) { - unsigned char hash_input[64]; - unsigned char hash_output[32]; - - /* 1. Build hash input = commit_inp || dot */ - memcpy(hash_input, commit_inp_32, 32); - memcpy(hash_input + 32, dot_32, 32); - - /* 2. Hash */ - SHA256(hash_input, 64, hash_output); - - /* 3. Reduce hash to a valid scalar */ - /* CRITICAL: Wraps the 32-byte random string into the curve order */ - secp256k1_mpt_scalar_reduce32(ux_out, hash_output); - - /* * 4. Verify (Sanity check) - * Reduce32 guarantees the value is < Order. - * This checks for the virtually impossible case where hash is exactly 0. - */ - if (secp256k1_ec_seckey_verify(ctx, ux_out) != 1) { - return 0; - } + unsigned char hash_input[64]; + unsigned char hash_output[32]; + + /* 1. Build hash input = commit_inp || dot */ + memcpy(hash_input, commit_inp_32, 32); + memcpy(hash_input + 32, dot_32, 32); + + /* 2. Hash */ + SHA256(hash_input, 64, hash_output); + + /* 3. Reduce hash to a valid scalar */ + /* CRITICAL: Wraps the 32-byte random string into the curve order */ + secp256k1_mpt_scalar_reduce32(ux_out, hash_output); + + /* * 4. Verify (Sanity check) + * Reduce32 guarantees the value is < Order. + * This checks for the virtually impossible case where hash is exactly 0. + */ + if (secp256k1_ec_seckey_verify(ctx, ux_out) != 1) + { + return 0; + } - return 1; + return 1; } /** * Derive u = H(last_challenge || L || R) reduced to a valid scalar. * IMPORTANT: use the SAME exact logic in verifier. */ -int derive_ipa_round_challenge( - const secp256k1_context* ctx, - unsigned char u_out[32], - const unsigned char last_challenge[32], - const secp256k1_pubkey* L, - const secp256k1_pubkey* R) +int derive_ipa_round_challenge(const secp256k1_context *ctx, + unsigned char u_out[32], + const unsigned char last_challenge[32], + const secp256k1_pubkey *L, + const secp256k1_pubkey *R) { - unsigned char L_ser[33], R_ser[33]; - size_t len = 33; - SHA256_CTX sha; - unsigned char hash[32]; + unsigned char L_ser[33], R_ser[33]; + size_t len = 33; + SHA256_CTX sha; + unsigned char hash[32]; - if (!secp256k1_ec_pubkey_serialize(ctx, L_ser, &len, L, SECP256K1_EC_COMPRESSED)) return 0; - len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, R_ser, &len, R, SECP256K1_EC_COMPRESSED)) return 0; + if (!secp256k1_ec_pubkey_serialize(ctx, L_ser, &len, L, + SECP256K1_EC_COMPRESSED)) + return 0; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, R_ser, &len, R, + SECP256K1_EC_COMPRESSED)) + return 0; - SHA256_Init(&sha); - SHA256_Update(&sha, last_challenge, 32); - SHA256_Update(&sha, L_ser, 33); - SHA256_Update(&sha, R_ser, 33); - SHA256_Final(hash, &sha); - secp256k1_mpt_scalar_reduce32(hash, hash); - memcpy(u_out, hash, 32); + SHA256_Init(&sha); + SHA256_Update(&sha, last_challenge, 32); + SHA256_Update(&sha, L_ser, 33); + SHA256_Update(&sha, R_ser, 33); + SHA256_Final(hash, &sha); + secp256k1_mpt_scalar_reduce32(hash, hash); + memcpy(u_out, hash, 32); - /* Reject invalid scalar (0 or >= group order). */ - if (secp256k1_ec_seckey_verify(ctx, u_out) != 1) return 0; + /* Reject invalid scalar (0 or >= group order). */ + if (secp256k1_ec_seckey_verify(ctx, u_out) != 1) + return 0; - return 1; + return 1; } /** * Runs the Inner Product Argument (IPA) prover. @@ -757,92 +790,87 @@ int derive_ipa_round_challenge( * Returns 1 on success, 0 on failure. */ int secp256k1_bulletproof_run_ipa_prover( - const secp256k1_context* ctx, - const secp256k1_pubkey* g, - secp256k1_pubkey* G_vec, - secp256k1_pubkey* H_vec, - unsigned char* a_vec, - unsigned char* b_vec, - size_t n, - const unsigned char ipa_transcript_id[32], - const unsigned char ux_scalar[32], - secp256k1_pubkey* L_out, - secp256k1_pubkey* R_out, - size_t max_rounds, - size_t* rounds_out, - unsigned char a_final[32], - unsigned char b_final[32] -) { - size_t rounds = 0; - size_t cur_n = n; - int ok = 0; - - unsigned char u_scalar[32], u_inv[32]; - unsigned char last_challenge[32]; - - /* Validate n is power of 2 */ - if (n == 0 || (n & (n - 1)) != 0) return 0; - - /* rounds = log2(n) */ - while (cur_n > 1) { cur_n >>= 1; rounds++; } - cur_n = n; - - /* Bounds check (CRITICAL for aggregated proofs) */ - if (rounds > max_rounds) return 0; - - /* Seed transcript */ - memcpy(last_challenge, ipa_transcript_id, 32); - - for (size_t r = 0; r < rounds; ++r) { - size_t half_n = cur_n >> 1; - secp256k1_pubkey Lr, Rr; - - /* 1) Compute cross-term commitments Lr, Rr */ - if (!secp256k1_bulletproof_ipa_compute_LR( - ctx, &Lr, &Rr, - a_vec, a_vec + half_n * 32, - b_vec, b_vec + half_n * 32, - G_vec, G_vec + half_n, - H_vec, H_vec + half_n, - g, - ux_scalar, - half_n - )) goto cleanup; - - /* 2) Store L/R */ - L_out[r] = Lr; - R_out[r] = Rr; - - /* 3) Fiat–Shamir round challenge u_r */ - if (!derive_ipa_round_challenge(ctx, u_scalar, last_challenge, &Lr, &Rr)) - goto cleanup; - - /* 4) u_r^{-1} */ - secp256k1_mpt_scalar_inverse(u_inv, u_scalar); - if (!secp256k1_ec_seckey_verify(ctx, u_inv)) goto cleanup; - - /* 5) Update transcript chaining state */ - memcpy(last_challenge, u_scalar, 32); - - /* 6) Fold vectors in-place */ - if (!secp256k1_bulletproof_ipa_compress_step( - ctx, a_vec, b_vec, G_vec, H_vec, half_n, u_scalar, u_inv - )) goto cleanup; - - cur_n = half_n; - } + const secp256k1_context *ctx, const secp256k1_pubkey *g, + secp256k1_pubkey *G_vec, secp256k1_pubkey *H_vec, unsigned char *a_vec, + unsigned char *b_vec, size_t n, const unsigned char ipa_transcript_id[32], + const unsigned char ux_scalar[32], secp256k1_pubkey *L_out, + secp256k1_pubkey *R_out, size_t max_rounds, size_t *rounds_out, + unsigned char a_final[32], unsigned char b_final[32]) +{ + size_t rounds = 0; + size_t cur_n = n; + int ok = 0; - /* Final folded scalars */ - memcpy(a_final, a_vec, 32); - memcpy(b_final, b_vec, 32); + unsigned char u_scalar[32], u_inv[32]; + unsigned char last_challenge[32]; - if (rounds_out) *rounds_out = rounds; - ok = 1; + /* Validate n is power of 2 */ + if (n == 0 || (n & (n - 1)) != 0) + return 0; - cleanup: - OPENSSL_cleanse(u_scalar, 32); - OPENSSL_cleanse(u_inv, 32); - return ok; + /* rounds = log2(n) */ + while (cur_n > 1) + { + cur_n >>= 1; + rounds++; + } + cur_n = n; + + /* Bounds check (CRITICAL for aggregated proofs) */ + if (rounds > max_rounds) + return 0; + + /* Seed transcript */ + memcpy(last_challenge, ipa_transcript_id, 32); + + for (size_t r = 0; r < rounds; ++r) + { + size_t half_n = cur_n >> 1; + secp256k1_pubkey Lr, Rr; + + /* 1) Compute cross-term commitments Lr, Rr */ + if (!secp256k1_bulletproof_ipa_compute_LR( + ctx, &Lr, &Rr, a_vec, a_vec + half_n * 32, b_vec, + b_vec + half_n * 32, G_vec, G_vec + half_n, H_vec, H_vec + half_n, + g, ux_scalar, half_n)) + goto cleanup; + + /* 2) Store L/R */ + L_out[r] = Lr; + R_out[r] = Rr; + + /* 3) Fiat–Shamir round challenge u_r */ + if (!derive_ipa_round_challenge(ctx, u_scalar, last_challenge, &Lr, &Rr)) + goto cleanup; + + /* 4) u_r^{-1} */ + secp256k1_mpt_scalar_inverse(u_inv, u_scalar); + if (!secp256k1_ec_seckey_verify(ctx, u_inv)) + goto cleanup; + + /* 5) Update transcript chaining state */ + memcpy(last_challenge, u_scalar, 32); + + /* 6) Fold vectors in-place */ + if (!secp256k1_bulletproof_ipa_compress_step( + ctx, a_vec, b_vec, G_vec, H_vec, half_n, u_scalar, u_inv)) + goto cleanup; + + cur_n = half_n; + } + + /* Final folded scalars */ + memcpy(a_final, a_vec, 32); + memcpy(b_final, b_vec, 32); + + if (rounds_out) + *rounds_out = rounds; + ok = 1; + +cleanup: + OPENSSL_cleanse(u_scalar, 32); + OPENSSL_cleanse(u_inv, 32); + return ok; } /* @@ -864,128 +892,129 @@ int secp256k1_bulletproof_run_ipa_prover( * + b_final * H_f * + (a_final * b_final * ux) * U * - * where G_f and H_f are obtained by folding G_vec and H_vec using the challenges - * u_i and their inverses, and P' is obtained by applying the same folding - * operations to P using the L_i and R_i commitments. + * where G_f and H_f are obtained by folding G_vec and H_vec using the + * challenges u_i and their inverses, and P' is obtained by applying the same + * folding operations to P using the L_i and R_i commitments. * * All group operations avoid explicit construction of the point at infinity, * which is not representable via the libsecp256k1 public-key API. */ static int ipa_verify_explicit( - const secp256k1_context* ctx, - const secp256k1_pubkey* G_vec, /* original G generators (length n) */ - const secp256k1_pubkey* H_vec, /* original H generators (length n) */ - const secp256k1_pubkey* U, - const secp256k1_pubkey* P_in, /* initial P */ - const secp256k1_pubkey* L_vec, /* length = rounds */ - const secp256k1_pubkey* R_vec, /* length = rounds */ - size_t n, /* total vector length (64*m) */ - const unsigned char a_final[32], - const unsigned char b_final[32], - const unsigned char ux[32], - const unsigned char ipa_transcript_id[32] -) { - secp256k1_pubkey P = *P_in; - secp256k1_pubkey Gf, Hf, RHS, tmp; - int RHS_inited = 0; - int ok = 0; - - /* --- derive rounds --- */ - if (n == 0 || (n & (n - 1)) != 0) - return 0; - - size_t rounds = bp_ipa_rounds(n); - - /* --- allocate u / u_inv --- */ - unsigned char* u_flat = (unsigned char*)malloc(rounds * 32); - unsigned char* uinv_flat = (unsigned char*)malloc(rounds * 32); - if (!u_flat || !uinv_flat) - goto cleanup_alloc; - - unsigned char last[32]; - memcpy(last, ipa_transcript_id, 32); - - /* ---- 1. Re-derive u_i ---- */ - for (size_t i = 0; i < rounds; i++) { - unsigned char* ui = u_flat + 32 * i; - unsigned char* uiinv = uinv_flat + 32 * i; - - if (!derive_ipa_round_challenge(ctx, ui, last, &L_vec[i], &R_vec[i])) - goto cleanup; - - secp256k1_mpt_scalar_inverse(uiinv, ui); - if (!secp256k1_ec_seckey_verify(ctx, uiinv)) - goto cleanup; - - memcpy(last, ui, 32); - } + const secp256k1_context *ctx, + const secp256k1_pubkey *G_vec, /* original G generators (length n) */ + const secp256k1_pubkey *H_vec, /* original H generators (length n) */ + const secp256k1_pubkey *U, const secp256k1_pubkey *P_in, /* initial P */ + const secp256k1_pubkey *L_vec, /* length = rounds */ + const secp256k1_pubkey *R_vec, /* length = rounds */ + size_t n, /* total vector length (64*m) */ + const unsigned char a_final[32], const unsigned char b_final[32], + const unsigned char ux[32], const unsigned char ipa_transcript_id[32]) +{ + secp256k1_pubkey P = *P_in; + secp256k1_pubkey Gf, Hf, RHS, tmp; + int RHS_inited = 0; + int ok = 0; - /* ---- 2. Fold generators ---- */ - if (!fold_generators(ctx, &Gf, G_vec, u_flat, uinv_flat, n, rounds, 0)) - goto cleanup; + /* --- derive rounds --- */ + if (n == 0 || (n & (n - 1)) != 0) + return 0; - if (!fold_generators(ctx, &Hf, H_vec, u_flat, uinv_flat, n, rounds, 1)) + size_t rounds = bp_ipa_rounds(n); + + /* --- allocate u / u_inv --- */ + unsigned char *u_flat = (unsigned char *)malloc(rounds * 32); + unsigned char *uinv_flat = (unsigned char *)malloc(rounds * 32); + if (!u_flat || !uinv_flat) + goto cleanup_alloc; + + unsigned char last[32]; + memcpy(last, ipa_transcript_id, 32); + + /* ---- 1. Re-derive u_i ---- */ + for (size_t i = 0; i < rounds; i++) + { + unsigned char *ui = u_flat + 32 * i; + unsigned char *uiinv = uinv_flat + 32 * i; + + if (!derive_ipa_round_challenge(ctx, ui, last, &L_vec[i], &R_vec[i])) + goto cleanup; + + secp256k1_mpt_scalar_inverse(uiinv, ui); + if (!secp256k1_ec_seckey_verify(ctx, uiinv)) + goto cleanup; + + memcpy(last, ui, 32); + } + + /* ---- 2. Fold generators ---- */ + if (!fold_generators(ctx, &Gf, G_vec, u_flat, uinv_flat, n, rounds, 0)) + goto cleanup; + + if (!fold_generators(ctx, &Hf, H_vec, u_flat, uinv_flat, n, rounds, 1)) + goto cleanup; + + /* ---- 3. RHS = a*Gf + b*Hf + (a*b*ux)*U ---- */ + + if (!scalar_is_zero(a_final)) + { + tmp = Gf; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, a_final)) + goto cleanup; + if (!add_term(ctx, &RHS, &RHS_inited, &tmp)) + goto cleanup; + } + + if (!scalar_is_zero(b_final)) + { + tmp = Hf; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, b_final)) + goto cleanup; + if (!add_term(ctx, &RHS, &RHS_inited, &tmp)) + goto cleanup; + } + + { + unsigned char ab[32], ab_ux[32]; + secp256k1_mpt_scalar_mul(ab, a_final, b_final); + secp256k1_mpt_scalar_mul(ab_ux, ab, ux); + + if (!scalar_is_zero(ab_ux)) + { + tmp = *U; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, ab_ux)) + goto cleanup; + if (!add_term(ctx, &RHS, &RHS_inited, &tmp)) goto cleanup; - - /* ---- 3. RHS = a*Gf + b*Hf + (a*b*ux)*U ---- */ - - if (!scalar_is_zero(a_final)) { - tmp = Gf; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, a_final)) - goto cleanup; - if (!add_term(ctx, &RHS, &RHS_inited, &tmp)) - goto cleanup; - } - - if (!scalar_is_zero(b_final)) { - tmp = Hf; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, b_final)) - goto cleanup; - if (!add_term(ctx, &RHS, &RHS_inited, &tmp)) - goto cleanup; } - { - unsigned char ab[32], ab_ux[32]; - secp256k1_mpt_scalar_mul(ab, a_final, b_final); - secp256k1_mpt_scalar_mul(ab_ux, ab, ux); - - if (!scalar_is_zero(ab_ux)) { - tmp = *U; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, ab_ux)) - goto cleanup; - if (!add_term(ctx, &RHS, &RHS_inited, &tmp)) - goto cleanup; - } - - OPENSSL_cleanse(ab, 32); - OPENSSL_cleanse(ab_ux, 32); - } + OPENSSL_cleanse(ab, 32); + OPENSSL_cleanse(ab_ux, 32); + } - if (!RHS_inited) - goto cleanup; + if (!RHS_inited) + goto cleanup; - /* ---- 4. Fold P using L/R ---- */ - if (!apply_ipa_folding_to_P( - ctx, &P, L_vec, R_vec, u_flat, uinv_flat, rounds)) - goto cleanup; + /* ---- 4. Fold P using L/R ---- */ + if (!apply_ipa_folding_to_P(ctx, &P, L_vec, R_vec, u_flat, uinv_flat, rounds)) + goto cleanup; - /* ---- 5. Compare P and RHS ---- */ - if (pubkey_equal(ctx, &P, &RHS)) - ok = 1; + /* ---- 5. Compare P and RHS ---- */ + if (pubkey_equal(ctx, &P, &RHS)) + ok = 1; - cleanup: - OPENSSL_cleanse(u_flat, rounds * 32); - OPENSSL_cleanse(uinv_flat, rounds * 32); +cleanup: + OPENSSL_cleanse(u_flat, rounds * 32); + OPENSSL_cleanse(uinv_flat, rounds * 32); - cleanup_alloc: - free(u_flat); - free(uinv_flat); - return ok; +cleanup_alloc: + free(u_flat); + free(uinv_flat); + return ok; } /** * Phase 1, Step 3 (Aggregated): - * Compute al, ar, sl, sr vectors for ONE value block inside an aggregated proof. + * Compute al, ar, sl, sr vectors for ONE value block inside an aggregated + * proof. * * The caller is responsible for: * - allocating al/ar/sl/sr of length (BP_VALUE_BITS * m) @@ -996,156 +1025,163 @@ static int ipa_verify_explicit( * [BP_VALUE_BITS * j .. BP_VALUE_BITS * j + BP_VALUE_BITS - 1] */ int secp256k1_bulletproof_compute_vectors_block( - const secp256k1_context* ctx, - uint64_t value, - size_t block_index, /* j-th value */ - unsigned char* al, /* length = BP_TOTAL_BITS(m) * 32 */ - unsigned char* ar, - unsigned char* sl, - unsigned char* sr -) { - const size_t offset = BP_VALUE_BITS * block_index; - int ok = 1; - - /* Scalars */ - unsigned char one[32] = {0}; - unsigned char minus_one[32]; - unsigned char zero[32] = {0}; - - one[31] = 1; - memcpy(minus_one, one, 32); - secp256k1_mpt_scalar_negate(minus_one, minus_one); - - /* ---- 1. Encode value bits into al/ar ---- */ - for (size_t i = 0; i < BP_VALUE_BITS; i++) { - size_t idx = offset + i; - - if ((value >> i) & 1) { - /* bit = 1 => al = 1, ar = 0 */ - memcpy(al + idx * 32, one, 32); - memcpy(ar + idx * 32, zero, 32); - } else { - /* bit = 0 => al = 0, ar = -1 */ - memcpy(al + idx * 32, zero, 32); - memcpy(ar + idx * 32, minus_one, 32); - } + const secp256k1_context *ctx, uint64_t value, + size_t block_index, /* j-th value */ + unsigned char *al, /* length = BP_TOTAL_BITS(m) * 32 */ + unsigned char *ar, unsigned char *sl, unsigned char *sr) +{ + const size_t offset = BP_VALUE_BITS * block_index; + int ok = 1; + + /* Scalars */ + unsigned char one[32] = {0}; + unsigned char minus_one[32]; + unsigned char zero[32] = {0}; + + one[31] = 1; + memcpy(minus_one, one, 32); + secp256k1_mpt_scalar_negate(minus_one, minus_one); + + /* ---- 1. Encode value bits into al/ar ---- */ + for (size_t i = 0; i < BP_VALUE_BITS; i++) + { + size_t idx = offset + i; + + if ((value >> i) & 1) + { + /* bit = 1 => al = 1, ar = 0 */ + memcpy(al + idx * 32, one, 32); + memcpy(ar + idx * 32, zero, 32); } + else + { + /* bit = 0 => al = 0, ar = -1 */ + memcpy(al + idx * 32, zero, 32); + memcpy(ar + idx * 32, minus_one, 32); + } + } - /* ---- 2. Generate random blinding vectors sl/sr ---- */ - for (size_t i = 0; i < BP_VALUE_BITS; i++) { - size_t idx = offset + i; + /* ---- 2. Generate random blinding vectors sl/sr ---- */ + for (size_t i = 0; i < BP_VALUE_BITS; i++) + { + size_t idx = offset + i; - if (!generate_random_scalar(ctx, sl + idx * 32)) { - ok = 0; - goto cleanup; - } - if (!generate_random_scalar(ctx, sr + idx * 32)) { - ok = 0; - goto cleanup; - } + if (!generate_random_scalar(ctx, sl + idx * 32)) + { + ok = 0; + goto cleanup; + } + if (!generate_random_scalar(ctx, sr + idx * 32)) + { + ok = 0; + goto cleanup; } + } - return 1; + return 1; - cleanup: - /* Wipe only the affected block */ - OPENSSL_cleanse(al + offset * 32, BP_VALUE_BITS * 32); - OPENSSL_cleanse(ar + offset * 32, BP_VALUE_BITS * 32); - OPENSSL_cleanse(sl + offset * 32, BP_VALUE_BITS * 32); - OPENSSL_cleanse(sr + offset * 32, BP_VALUE_BITS * 32); - return 0; +cleanup: + /* Wipe only the affected block */ + OPENSSL_cleanse(al + offset * 32, BP_VALUE_BITS * 32); + OPENSSL_cleanse(ar + offset * 32, BP_VALUE_BITS * 32); + OPENSSL_cleanse(sl + offset * 32, BP_VALUE_BITS * 32); + OPENSSL_cleanse(sr + offset * 32, BP_VALUE_BITS * 32); + return 0; } /** * Computes the Pedersen Commitment: C = value*G + blinding_factor*Pk_base. */ int secp256k1_bulletproof_create_commitment( - const secp256k1_context* ctx, - secp256k1_pubkey* commitment_C, - uint64_t value, - const unsigned char* blinding_factor, - const secp256k1_pubkey* pk_base -) { - secp256k1_pubkey G_term, Pk_term; - const secp256k1_pubkey* points_to_add[2]; - int v_is_zero = (value == 0); - - /* 1. Compute r * Pk_base (The Blinding Term) */ - Pk_term = *pk_base; - if (secp256k1_ec_pubkey_tweak_mul(ctx, &Pk_term, blinding_factor) != 1) return 0; - - /* 2. Handle Value Term */ - if (v_is_zero) { - /* If v=0, C = 0*G + r*H = r*H. - We skip G_term entirely because libsecp cannot represent infinity. */ - *commitment_C = Pk_term; - return 1; - } - - /* 3. Compute v * G (The Value Term) */ - if (!compute_amount_point(ctx, &G_term, value)) return 0; + const secp256k1_context *ctx, secp256k1_pubkey *commitment_C, + uint64_t value, const unsigned char *blinding_factor, + const secp256k1_pubkey *pk_base) +{ + secp256k1_pubkey G_term, Pk_term; + const secp256k1_pubkey *points_to_add[2]; + int v_is_zero = (value == 0); - /* 4. Combine: C = v*G + r*Pk_base */ - points_to_add[0] = &G_term; - points_to_add[1] = &Pk_term; - if (secp256k1_ec_pubkey_combine(ctx, commitment_C, points_to_add, 2) != 1) return 0; + /* 1. Compute r * Pk_base (The Blinding Term) */ + Pk_term = *pk_base; + if (secp256k1_ec_pubkey_tweak_mul(ctx, &Pk_term, blinding_factor) != 1) + return 0; + /* 2. Handle Value Term */ + if (v_is_zero) + { + /* If v=0, C = 0*G + r*H = r*H. + We skip G_term entirely because libsecp cannot represent infinity. */ + *commitment_C = Pk_term; return 1; + } + + /* 3. Compute v * G (The Value Term) */ + if (!compute_amount_point(ctx, &G_term, value)) + return 0; + + /* 4. Combine: C = v*G + r*Pk_base */ + points_to_add[0] = &G_term; + points_to_add[1] = &Pk_term; + if (secp256k1_ec_pubkey_combine(ctx, commitment_C, points_to_add, 2) != 1) + return 0; + + return 1; } /* Helper for a vector 0 */ -static int scalar_vector_all_zero(const unsigned char* scalars, size_t n) { - unsigned char zero[32] = {0}; - for (size_t i = 0; i < n; ++i) { - if (memcmp(scalars + 32*i, zero, 32) != 0) - return 0; /* found non-zero */ - } - return 1; /* all zero */ +static int scalar_vector_all_zero(const unsigned char *scalars, size_t n) +{ + unsigned char zero[32] = {0}; + for (size_t i = 0; i < n; ++i) + { + if (memcmp(scalars + 32 * i, zero, 32) != 0) + return 0; /* found non-zero */ + } + return 1; /* all zero */ } /* Helper to calculate commitment terms like A and S */ static int calculate_commitment_term( - const secp256k1_context* ctx, - secp256k1_pubkey* out, - const secp256k1_pubkey* pk_base, - const unsigned char* base_scalar, - const unsigned char* vec_l, - const unsigned char* vec_r, - const secp256k1_pubkey* G_vec, - const secp256k1_pubkey* H_vec, - size_t n -) { - secp256k1_pubkey tG, tH, tB; - const secp256k1_pubkey* pts[3]; - int n_pts = 0; - - /* 1. base_scalar * Base */ - tB = *pk_base; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tB, base_scalar)) return 0; - pts[n_pts++] = &tB; - - /* 2. */ - if (!scalar_vector_all_zero(vec_l, n)) { - if (!secp256k1_bulletproof_ipa_msm(ctx, &tG, G_vec, vec_l, n)) - return 0; /* REAL FAILURE */ - pts[n_pts++] = &tG; - } - - /* 3. */ - if (!scalar_vector_all_zero(vec_r, n)) { - if (!secp256k1_bulletproof_ipa_msm(ctx, &tH, H_vec, vec_r, n)) - return 0; /* REAL FAILURE */ - pts[n_pts++] = &tH; - } + const secp256k1_context *ctx, secp256k1_pubkey *out, + const secp256k1_pubkey *pk_base, const unsigned char *base_scalar, + const unsigned char *vec_l, const unsigned char *vec_r, + const secp256k1_pubkey *G_vec, const secp256k1_pubkey *H_vec, size_t n) +{ + secp256k1_pubkey tG, tH, tB; + const secp256k1_pubkey *pts[3]; + int n_pts = 0; - if (n_pts == 1) { - *out = *pts[0]; - return 1; - } + /* 1. base_scalar * Base */ + tB = *pk_base; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tB, base_scalar)) + return 0; + pts[n_pts++] = &tB; + + /* 2. */ + if (!scalar_vector_all_zero(vec_l, n)) + { + if (!secp256k1_bulletproof_ipa_msm(ctx, &tG, G_vec, vec_l, n)) + return 0; /* REAL FAILURE */ + pts[n_pts++] = &tG; + } + + /* 3. */ + if (!scalar_vector_all_zero(vec_r, n)) + { + if (!secp256k1_bulletproof_ipa_msm(ctx, &tH, H_vec, vec_r, n)) + return 0; /* REAL FAILURE */ + pts[n_pts++] = &tH; + } + + if (n_pts == 1) + { + *out = *pts[0]; + return 1; + } - if (!secp256k1_ec_pubkey_combine(ctx, out, pts, n_pts)) - return 0; + if (!secp256k1_ec_pubkey_combine(ctx, out, pts, n_pts)) + return 0; - return 1; + return 1; } /** @@ -1167,565 +1203,705 @@ static int calculate_commitment_term( * * Returns 1 on success, 0 on failure. */ -int secp256k1_bulletproof_prove_agg( - const secp256k1_context* ctx, - unsigned char* proof_out, - size_t* proof_len, - const uint64_t* values, - const unsigned char* blindings_flat, - size_t m, - const secp256k1_pubkey* pk_base, - const unsigned char* context_id -) { - /* ---- 0. Dimensions ---- */ - const size_t n = BP_TOTAL_BITS(m); /* 64*m */ - const size_t rounds = bp_ipa_rounds(n); /* log2(64*m) */ - - /* 64*m must be power-of-two -> m must be power-of-two */ - if (m == 0) return 0; - if ((n & (n - 1)) != 0) return 0; - - /* Proof length = 4*33 + 2*rounds*33 + 5*32 */ - const size_t proof_size = 292 + 66 * rounds; - if (proof_len) *proof_len = proof_size; - - int ok = 0; - - /* ---- 1. Allocate vectors ---- */ - secp256k1_pubkey* G_vec = (secp256k1_pubkey*)malloc(n * sizeof(secp256k1_pubkey)); - secp256k1_pubkey* H_vec = (secp256k1_pubkey*)malloc(n * sizeof(secp256k1_pubkey)); - secp256k1_pubkey* H_prime = (secp256k1_pubkey*)malloc(n * sizeof(secp256k1_pubkey)); - unsigned char* al = (unsigned char*)malloc(n * 32); - unsigned char* ar = (unsigned char*)malloc(n * 32); - unsigned char* sl = (unsigned char*)malloc(n * 32); - unsigned char* sr = (unsigned char*)malloc(n * 32); - unsigned char* l_vec = (unsigned char*)malloc(n * 32); - unsigned char* r_vec = (unsigned char*)malloc(n * 32); - unsigned char* r1_vec = (unsigned char*)malloc(n * 32); - - secp256k1_pubkey* L_vec = (secp256k1_pubkey*)malloc(rounds * sizeof(secp256k1_pubkey)); - secp256k1_pubkey* R_vec = (secp256k1_pubkey*)malloc(rounds * sizeof(secp256k1_pubkey)); - - unsigned char* y_powers = (unsigned char*)malloc(n * 32); /* y^i */ - unsigned char* z_j2 = (unsigned char*)malloc(m * 32); /* z^(j+2) */ - - if (!G_vec || !H_vec || !H_prime || - !al || !ar || !sl || !sr || !l_vec || !r_vec || !r1_vec || - !L_vec || !R_vec || - !y_powers || !z_j2) { - goto cleanup; - } - - /* ---- 2. Scalars / points ---- */ - secp256k1_pubkey A, S, T1, T2, U; - - unsigned char alpha[32], rho[32]; - unsigned char tau1[32], tau2[32]; - unsigned char t1[32], t2[32]; - unsigned char t_hat[32], tau_x[32], mu[32]; - unsigned char a_final[32], b_final[32]; - - unsigned char y[32], z[32], x[32]; - unsigned char z_sq[32], z_neg[32], x_sq[32]; - unsigned char ux_scalar[32]; - unsigned char ipa_transcript[32]; +int secp256k1_bulletproof_prove_agg(const secp256k1_context *ctx, + unsigned char *proof_out, size_t *proof_len, + const uint64_t *values, + const unsigned char *blindings_flat, + size_t m, const secp256k1_pubkey *pk_base, + const unsigned char *context_id) +{ + /* ---- 0. Dimensions ---- */ + const size_t n = BP_TOTAL_BITS(m); /* 64*m */ + const size_t rounds = bp_ipa_rounds(n); /* log2(64*m) */ - unsigned char one[32] = {0}; one[31] = 1; - unsigned char minus_one[32]; secp256k1_mpt_scalar_negate(minus_one, one); - unsigned char zero[32] = {0}; + /* 64*m must be power-of-two -> m must be power-of-two */ + if (m == 0) + return 0; + if ((n & (n - 1)) != 0) + return 0; - /* ---- 3. Generator vectors ---- */ - if (!secp256k1_mpt_get_generator_vector(ctx, G_vec, n, (const unsigned char*)"G", 1)) goto cleanup; - if (!secp256k1_mpt_get_generator_vector(ctx, H_vec, n, (const unsigned char*)"H", 1)) goto cleanup; + /* Proof length = 4*33 + 2*rounds*33 + 5*32 */ + const size_t proof_size = 292 + 66 * rounds; + if (proof_len) + *proof_len = proof_size; + + int ok = 0; + + /* ---- 1. Allocate vectors ---- */ + secp256k1_pubkey *G_vec = + (secp256k1_pubkey *)malloc(n * sizeof(secp256k1_pubkey)); + secp256k1_pubkey *H_vec = + (secp256k1_pubkey *)malloc(n * sizeof(secp256k1_pubkey)); + secp256k1_pubkey *H_prime = + (secp256k1_pubkey *)malloc(n * sizeof(secp256k1_pubkey)); + unsigned char *al = (unsigned char *)malloc(n * 32); + unsigned char *ar = (unsigned char *)malloc(n * 32); + unsigned char *sl = (unsigned char *)malloc(n * 32); + unsigned char *sr = (unsigned char *)malloc(n * 32); + unsigned char *l_vec = (unsigned char *)malloc(n * 32); + unsigned char *r_vec = (unsigned char *)malloc(n * 32); + unsigned char *r1_vec = (unsigned char *)malloc(n * 32); + + secp256k1_pubkey *L_vec = + (secp256k1_pubkey *)malloc(rounds * sizeof(secp256k1_pubkey)); + secp256k1_pubkey *R_vec = + (secp256k1_pubkey *)malloc(rounds * sizeof(secp256k1_pubkey)); + + unsigned char *y_powers = (unsigned char *)malloc(n * 32); /* y^i */ + unsigned char *z_j2 = (unsigned char *)malloc(m * 32); /* z^(j+2) */ + + if (!G_vec || !H_vec || !H_prime || !al || !ar || !sl || !sr || !l_vec || + !r_vec || !r1_vec || !L_vec || !R_vec || !y_powers || !z_j2) + { + goto cleanup; + } + + /* ---- 2. Scalars / points ---- */ + secp256k1_pubkey A, S, T1, T2, U; + + unsigned char alpha[32], rho[32]; + unsigned char tau1[32], tau2[32]; + unsigned char t1[32], t2[32]; + unsigned char t_hat[32], tau_x[32], mu[32]; + unsigned char a_final[32], b_final[32]; + + unsigned char y[32], z[32], x[32]; + unsigned char z_sq[32], z_neg[32], x_sq[32]; + unsigned char ux_scalar[32]; + unsigned char ipa_transcript[32]; + + unsigned char one[32] = {0}; + one[31] = 1; + unsigned char minus_one[32]; + secp256k1_mpt_scalar_negate(minus_one, one); + unsigned char zero[32] = {0}; + + /* ---- 3. Generator vectors ---- */ + if (!secp256k1_mpt_get_generator_vector(ctx, G_vec, n, + (const unsigned char *)"G", 1)) + goto cleanup; + if (!secp256k1_mpt_get_generator_vector(ctx, H_vec, n, + (const unsigned char *)"H", 1)) + goto cleanup; + { + secp256k1_pubkey U_arr[1]; + if (!secp256k1_mpt_get_generator_vector(ctx, U_arr, 1, + (const unsigned char *)"BP_U", 4)) + goto cleanup; + U = U_arr[0]; + } + + /* ---- 4. Bit-decomposition for m values into al/ar (concat) + random sl/sr + * ---- */ + for (size_t j = 0; j < m; j++) + { + uint64_t v = values[j]; + for (size_t i = 0; i < BP_VALUE_BITS; i++) { - secp256k1_pubkey U_arr[1]; - if (!secp256k1_mpt_get_generator_vector(ctx, U_arr, 1, (const unsigned char*)"BP_U", 4)) goto cleanup; - U = U_arr[0]; - } - - /* ---- 4. Bit-decomposition for m values into al/ar (concat) + random sl/sr ---- */ - for (size_t j = 0; j < m; j++) { - uint64_t v = values[j]; - for (size_t i = 0; i < BP_VALUE_BITS; i++) { - const size_t k = j * BP_VALUE_BITS + i; /* 0..n-1 */ - unsigned char* al_k = al + 32*k; - unsigned char* ar_k = ar + 32*k; - unsigned char* sl_k = sl + 32*k; - unsigned char* sr_k = sr + 32*k; - - if ((v >> i) & 1) { - memcpy(al_k, one, 32); - memset(ar_k, 0, 32); - } else { - memset(al_k, 0, 32); - memcpy(ar_k, minus_one, 32); - } - - if (!generate_random_scalar(ctx, sl_k)) goto cleanup; - if (!generate_random_scalar(ctx, sr_k)) goto cleanup; - } + const size_t k = j * BP_VALUE_BITS + i; /* 0..n-1 */ + unsigned char *al_k = al + 32 * k; + unsigned char *ar_k = ar + 32 * k; + unsigned char *sl_k = sl + 32 * k; + unsigned char *sr_k = sr + 32 * k; + + if ((v >> i) & 1) + { + memcpy(al_k, one, 32); + memset(ar_k, 0, 32); + } + else + { + memset(al_k, 0, 32); + memcpy(ar_k, minus_one, 32); + } + + if (!generate_random_scalar(ctx, sl_k)) + goto cleanup; + if (!generate_random_scalar(ctx, sr_k)) + goto cleanup; } + } + + if (!generate_random_scalar(ctx, alpha)) + goto cleanup; + if (!generate_random_scalar(ctx, rho)) + goto cleanup; + + /* ---- 5. Commitments A and S ---- + * A = alpha*Base + + + * S = rho*Base + + + */ + if (!calculate_commitment_term(ctx, &A, pk_base, alpha, al, ar, G_vec, H_vec, + n)) + goto cleanup; + if (!calculate_commitment_term(ctx, &S, pk_base, rho, sl, sr, G_vec, H_vec, + n)) + goto cleanup; + + /* ---- 6. Fiat–Shamir y,z ---- */ + { + unsigned char A_ser[33], S_ser[33]; + size_t len = 33; + SHA256_CTX sha; - if (!generate_random_scalar(ctx, alpha)) goto cleanup; - if (!generate_random_scalar(ctx, rho)) goto cleanup; - - /* ---- 5. Commitments A and S ---- - * A = alpha*Base + + - * S = rho*Base + + - */ - if (!calculate_commitment_term(ctx, &A, pk_base, alpha, al, ar, G_vec, H_vec, n)) goto cleanup; - if (!calculate_commitment_term(ctx, &S, pk_base, rho, sl, sr, G_vec, H_vec, n)) goto cleanup; - - /* ---- 6. Fiat–Shamir y,z ---- */ - { - unsigned char A_ser[33], S_ser[33]; - size_t len = 33; - SHA256_CTX sha; - - if (!secp256k1_ec_pubkey_serialize(ctx, A_ser, &len, &A, SECP256K1_EC_COMPRESSED)) goto cleanup; - len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, S_ser, &len, &S, SECP256K1_EC_COMPRESSED)) goto cleanup; - - /* --- START OF TRANSCRIPT --- */ - SHA256_Init(&sha); - - /* 1. Domain Separation */ - SHA256_Update(&sha, "MPT_BULLETPROOF_RANGE", 21); - - /* 2. Transaction Context [cite: 431] */ - if (context_id) SHA256_Update(&sha, context_id, 32); - - /* 3. Statement: Value Commitments*/ - for (size_t i = 0; i < m; i++) { - secp256k1_pubkey V_temp; - unsigned char V_ser[33]; - size_t v_len = 33; - /* Reconstruct commitment for the transcript */ - if (!secp256k1_bulletproof_create_commitment(ctx, &V_temp, values[i], blindings_flat + 32*i, pk_base)) goto cleanup; - if (!secp256k1_ec_pubkey_serialize(ctx, V_ser, &v_len, &V_temp, SECP256K1_EC_COMPRESSED)) goto cleanup; - SHA256_Update(&sha, V_ser, 33); - } - - - SHA256_Update(&sha, A_ser, 33); - SHA256_Update(&sha, S_ser, 33); - - SHA256_Final(y, &sha); - secp256k1_mpt_scalar_reduce32(y, y); + if (!secp256k1_ec_pubkey_serialize(ctx, A_ser, &len, &A, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, S_ser, &len, &S, + SECP256K1_EC_COMPRESSED)) + goto cleanup; - /* To match spec H(T1 || y), we continue from the previous state (or re-hash) */ - SHA256_Init(&sha); - SHA256_Update(&sha, "MPT_BULLETPROOF_RANGE", 21); - if (context_id) SHA256_Update(&sha, context_id, 32); + /* --- START OF TRANSCRIPT --- */ + SHA256_Init(&sha); - for (size_t i = 0; i < m; i++) { - /* ... (re-add commitments) ... */ - secp256k1_pubkey V_temp; - unsigned char V_ser[33]; - size_t v_len = 33; - secp256k1_bulletproof_create_commitment(ctx, &V_temp, values[i], blindings_flat + 32*i, pk_base); - secp256k1_ec_pubkey_serialize(ctx, V_ser, &v_len, &V_temp, SECP256K1_EC_COMPRESSED); - SHA256_Update(&sha, V_ser, 33); - } - SHA256_Update(&sha, A_ser, 33); - SHA256_Update(&sha, S_ser, 33); - SHA256_Update(&sha, y, 32); - SHA256_Final(z, &sha); - secp256k1_mpt_scalar_reduce32(z, z); - memcpy(z_neg, z, 32); - secp256k1_mpt_scalar_negate(z_neg, z_neg); - } + /* 1. Domain Separation */ + SHA256_Update(&sha, "MPT_BULLETPROOF_RANGE", 21); - /* ---- 7. Aggregated polynomial setup ---- */ + /* 2. Transaction Context [cite: 431] */ + if (context_id) + SHA256_Update(&sha, context_id, 32); -/* y_powers[k] = y^k */ + /* 3. Statement: Value Commitments*/ + for (size_t i = 0; i < m; i++) { - unsigned char ypow[32]; - memcpy(ypow, one, 32); - - for (size_t k = 0; k < n; k++) { - memcpy(y_powers + 32*k, ypow, 32); - secp256k1_mpt_scalar_mul(ypow, ypow, y); - } - OPENSSL_cleanse(ypow, 32); + secp256k1_pubkey V_temp; + unsigned char V_ser[33]; + size_t v_len = 33; + /* Reconstruct commitment for the transcript */ + if (!secp256k1_bulletproof_create_commitment( + ctx, &V_temp, values[i], blindings_flat + 32 * i, pk_base)) + goto cleanup; + if (!secp256k1_ec_pubkey_serialize(ctx, V_ser, &v_len, &V_temp, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + SHA256_Update(&sha, V_ser, 33); } -/* z_j2[j] = z^(j+2) */ - compute_z_pows_j2(ctx, (unsigned char (*)[32])z_j2, z, m); - -/* l0, r0, r1 */ - for (size_t block = 0; block < m; block++) { - const unsigned char* zblk = z_j2 + 32*block; /* z^(block+2) */ - - for (size_t i = 0; i < BP_VALUE_BITS; i++) { - size_t k = block * BP_VALUE_BITS + i; - - unsigned char* l0 = l_vec + 32*k; - unsigned char* r0 = r_vec + 32*k; - unsigned char* r1 = r1_vec + 32*k; - - const unsigned char* al_k = al + 32*k; - const unsigned char* ar_k = ar + 32*k; - const unsigned char* sr_k = sr + 32*k; - const unsigned char* yk = y_powers + 32*k; - - unsigned char two_i[32] = {0}; - two_i[31 - (i >> 3)] = (unsigned char)(1u << (i & 7)); - - /* l0 = aL - z */ - secp256k1_mpt_scalar_add(l0, al_k, z_neg); - - /* r0 = y^k * (aR + z) + z^(block+2) * 2^i */ - { - unsigned char tmp1[32], tmp2[32]; - - /* tmp1 = aR + z */ - secp256k1_mpt_scalar_add(tmp1, ar_k, z); - - /* r0 = y^k * tmp1 */ - secp256k1_mpt_scalar_mul(r0, tmp1, yk); - - /* tmp2 = z^(block+2) * 2^i */ - secp256k1_mpt_scalar_mul(tmp2, zblk, two_i); - - /* r0 += tmp2 */ - secp256k1_mpt_scalar_add(r0, r0, tmp2); + SHA256_Update(&sha, A_ser, 33); + SHA256_Update(&sha, S_ser, 33); - OPENSSL_cleanse(tmp1, 32); - OPENSSL_cleanse(tmp2, 32); - } + SHA256_Final(y, &sha); + secp256k1_mpt_scalar_reduce32(y, y); - /* r1 = sR * y^k */ - secp256k1_mpt_scalar_mul(r1, sr_k, yk); - } - } + /* To match spec H(T1 || y), we continue from the previous state (or + * re-hash) */ + SHA256_Init(&sha); + SHA256_Update(&sha, "MPT_BULLETPROOF_RANGE", 21); + if (context_id) + SHA256_Update(&sha, context_id, 32); -/* t1 = + */ + for (size_t i = 0; i < m; i++) { - unsigned char dot1[32], dot2[32]; - if (!secp256k1_bulletproof_ipa_dot(ctx, dot1, l_vec, r1_vec, n)) goto cleanup; - if (!secp256k1_bulletproof_ipa_dot(ctx, dot2, sl, r_vec, n)) goto cleanup; - secp256k1_mpt_scalar_add(t1, dot1, dot2); - OPENSSL_cleanse(dot1, 32); - OPENSSL_cleanse(dot2, 32); + /* ... (re-add commitments) ... */ + secp256k1_pubkey V_temp; + unsigned char V_ser[33]; + size_t v_len = 33; + secp256k1_bulletproof_create_commitment(ctx, &V_temp, values[i], + blindings_flat + 32 * i, pk_base); + secp256k1_ec_pubkey_serialize(ctx, V_ser, &v_len, &V_temp, + SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, V_ser, 33); } + SHA256_Update(&sha, A_ser, 33); + SHA256_Update(&sha, S_ser, 33); + SHA256_Update(&sha, y, 32); + SHA256_Final(z, &sha); + secp256k1_mpt_scalar_reduce32(z, z); + memcpy(z_neg, z, 32); + secp256k1_mpt_scalar_negate(z_neg, z_neg); + } -/* t2 = */ - if (!secp256k1_bulletproof_ipa_dot(ctx, t2, sl, r1_vec, n)) goto cleanup; + /* ---- 7. Aggregated polynomial setup ---- */ -/* Make sure these exist before T1/T2 */ - if (!generate_random_scalar(ctx, tau1)) goto cleanup; - if (!generate_random_scalar(ctx, tau2)) goto cleanup; + /* y_powers[k] = y^k */ + { + unsigned char ypow[32]; + memcpy(ypow, one, 32); -/* ---- 8. Commit T1, T2 ---- */ -/* T1 = t1*G + tau1*Base where G = G_vec[0] */ + for (size_t k = 0; k < n; k++) { - secp256k1_pubkey tG, tB; - const secp256k1_pubkey* pts[2]; - - if (memcmp(t1, zero, 32) == 0) goto cleanup; /* prototype guard */ - /* tG = t1 * (curve base generator) */ - if (!secp256k1_ec_pubkey_create(ctx, &tG, t1)) goto cleanup; - - - tB = *pk_base; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tB, tau1)) goto cleanup; - - pts[0] = &tG; pts[1] = &tB; - if (!secp256k1_ec_pubkey_combine(ctx, &T1, pts, 2)) goto cleanup; + memcpy(y_powers + 32 * k, ypow, 32); + secp256k1_mpt_scalar_mul(ypow, ypow, y); } + OPENSSL_cleanse(ypow, 32); + } -/* T2 = t2*G + tau2*Base */ - { - secp256k1_pubkey tG, tB; - const secp256k1_pubkey* pts[2]; - - if (memcmp(t2, zero, 32) == 0) goto cleanup; /* prototype guard */ - /* tG = t2 * (curve base generator) */ - if (!secp256k1_ec_pubkey_create(ctx, &tG, t2)) goto cleanup; + /* z_j2[j] = z^(j+2) */ + compute_z_pows_j2(ctx, (unsigned char (*)[32])z_j2, z, m); + /* l0, r0, r1 */ + for (size_t block = 0; block < m; block++) + { + const unsigned char *zblk = z_j2 + 32 * block; /* z^(block+2) */ - tB = *pk_base; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tB, tau2)) goto cleanup; - - pts[0] = &tG; pts[1] = &tB; - if (!secp256k1_ec_pubkey_combine(ctx, &T2, pts, 2)) goto cleanup; - } - -/* ---- 9. Challenge x ---- */ -/* x = H(context_id || A || S || y || z || T1 || T2) */ + for (size_t i = 0; i < BP_VALUE_BITS; i++) { - unsigned char A_ser[33], S_ser[33], T1_ser[33], T2_ser[33]; - size_t len = 33; - SHA256_CTX sha; - - if (!secp256k1_ec_pubkey_serialize(ctx, A_ser, &len, &A, SECP256K1_EC_COMPRESSED)) goto cleanup; - len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, S_ser, &len, &S, SECP256K1_EC_COMPRESSED)) goto cleanup; - len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, T1_ser, &len, &T1, SECP256K1_EC_COMPRESSED)) goto cleanup; - len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, T2_ser, &len, &T2, SECP256K1_EC_COMPRESSED)) goto cleanup; - - SHA256_Init(&sha); - if (context_id) SHA256_Update(&sha, context_id, 32); - SHA256_Update(&sha, A_ser, 33); - SHA256_Update(&sha, S_ser, 33); - SHA256_Update(&sha, y, 32); - SHA256_Update(&sha, z, 32); - SHA256_Update(&sha, T1_ser, 33); - SHA256_Update(&sha, T2_ser, 33); - SHA256_Final(x, &sha); - - secp256k1_mpt_scalar_reduce32(x, x); - if (memcmp(x, zero, 32) == 0) goto cleanup; /* avoid infinity later */ - } - - /* ---- 10. Evaluate l(x), r(x), t_hat ---- */ - for (size_t k = 0; k < n; k++) { - unsigned char tmp[32]; + size_t k = block * BP_VALUE_BITS + i; + + unsigned char *l0 = l_vec + 32 * k; + unsigned char *r0 = r_vec + 32 * k; + unsigned char *r1 = r1_vec + 32 * k; + + const unsigned char *al_k = al + 32 * k; + const unsigned char *ar_k = ar + 32 * k; + const unsigned char *sr_k = sr + 32 * k; + const unsigned char *yk = y_powers + 32 * k; + + unsigned char two_i[32] = {0}; + two_i[31 - (i >> 3)] = (unsigned char)(1u << (i & 7)); + + /* l0 = aL - z */ + secp256k1_mpt_scalar_add(l0, al_k, z_neg); + + /* r0 = y^k * (aR + z) + z^(block+2) * 2^i */ + { + unsigned char tmp1[32], tmp2[32]; + + /* tmp1 = aR + z */ + secp256k1_mpt_scalar_add(tmp1, ar_k, z); + + /* r0 = y^k * tmp1 */ + secp256k1_mpt_scalar_mul(r0, tmp1, yk); + + /* tmp2 = z^(block+2) * 2^i */ + secp256k1_mpt_scalar_mul(tmp2, zblk, two_i); + + /* r0 += tmp2 */ + secp256k1_mpt_scalar_add(r0, r0, tmp2); + + OPENSSL_cleanse(tmp1, 32); + OPENSSL_cleanse(tmp2, 32); + } + + /* r1 = sR * y^k */ + secp256k1_mpt_scalar_mul(r1, sr_k, yk); + } + } + + /* t1 = + */ + { + unsigned char dot1[32], dot2[32]; + if (!secp256k1_bulletproof_ipa_dot(ctx, dot1, l_vec, r1_vec, n)) + goto cleanup; + if (!secp256k1_bulletproof_ipa_dot(ctx, dot2, sl, r_vec, n)) + goto cleanup; + secp256k1_mpt_scalar_add(t1, dot1, dot2); + OPENSSL_cleanse(dot1, 32); + OPENSSL_cleanse(dot2, 32); + } + + /* t2 = */ + if (!secp256k1_bulletproof_ipa_dot(ctx, t2, sl, r1_vec, n)) + goto cleanup; + + /* Make sure these exist before T1/T2 */ + if (!generate_random_scalar(ctx, tau1)) + goto cleanup; + if (!generate_random_scalar(ctx, tau2)) + goto cleanup; + + /* ---- 8. Commit T1, T2 ---- */ + /* T1 = t1*G + tau1*Base where G = G_vec[0] */ + { + secp256k1_pubkey tG, tB; + const secp256k1_pubkey *pts[2]; + + if (memcmp(t1, zero, 32) == 0) + goto cleanup; /* prototype guard */ + /* tG = t1 * (curve base generator) */ + if (!secp256k1_ec_pubkey_create(ctx, &tG, t1)) + goto cleanup; - /* l = l0 + sL*x */ - secp256k1_mpt_scalar_mul(tmp, sl + 32*k, x); - secp256k1_mpt_scalar_add(l_vec + 32*k, l_vec + 32*k, tmp); - - /* r = r0 + r1*x */ - secp256k1_mpt_scalar_mul(tmp, r1_vec + 32*k, x); - secp256k1_mpt_scalar_add(r_vec + 32*k, r_vec + 32*k, tmp); - - OPENSSL_cleanse(tmp, 32); - } + tB = *pk_base; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tB, tau1)) + goto cleanup; + + pts[0] = &tG; + pts[1] = &tB; + if (!secp256k1_ec_pubkey_combine(ctx, &T1, pts, 2)) + goto cleanup; + } + + /* T2 = t2*G + tau2*Base */ + { + secp256k1_pubkey tG, tB; + const secp256k1_pubkey *pts[2]; + + if (memcmp(t2, zero, 32) == 0) + goto cleanup; /* prototype guard */ + /* tG = t2 * (curve base generator) */ + if (!secp256k1_ec_pubkey_create(ctx, &tG, t2)) + goto cleanup; - if (!secp256k1_bulletproof_ipa_dot(ctx, t_hat, l_vec, r_vec, n)) goto cleanup; + tB = *pk_base; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tB, tau2)) + goto cleanup; + + pts[0] = &tG; + pts[1] = &tB; + if (!secp256k1_ec_pubkey_combine(ctx, &T2, pts, 2)) + goto cleanup; + } + + /* ---- 9. Challenge x ---- */ + /* x = H(context_id || A || S || y || z || T1 || T2) */ + { + unsigned char A_ser[33], S_ser[33], T1_ser[33], T2_ser[33]; + size_t len = 33; + SHA256_CTX sha; - /* ---- 11. tau_x and mu (aggregation changes tau_x) ---- */ - secp256k1_mpt_scalar_mul(x_sq, x, x); + if (!secp256k1_ec_pubkey_serialize(ctx, A_ser, &len, &A, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, S_ser, &len, &S, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, T1_ser, &len, &T1, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, T2_ser, &len, &T2, + SECP256K1_EC_COMPRESSED)) + goto cleanup; - /* tau_x = tau2*x^2 + tau1*x + sum_j z^(j+2) * blinding_j */ - secp256k1_mpt_scalar_mul(tau_x, tau2, x_sq); - { - unsigned char tmp[32]; - secp256k1_mpt_scalar_mul(tmp, tau1, x); - secp256k1_mpt_scalar_add(tau_x, tau_x, tmp); - - /* + sum_j z^(j+2) * r_j */ - for (size_t j = 0; j < m; j++) { - unsigned char add[32]; - secp256k1_mpt_scalar_mul(add, z_j2 + 32*j, blindings_flat + 32*j); - secp256k1_mpt_scalar_add(tau_x, tau_x, add); - OPENSSL_cleanse(add, 32); - } + SHA256_Init(&sha); + if (context_id) + SHA256_Update(&sha, context_id, 32); + SHA256_Update(&sha, A_ser, 33); + SHA256_Update(&sha, S_ser, 33); + SHA256_Update(&sha, y, 32); + SHA256_Update(&sha, z, 32); + SHA256_Update(&sha, T1_ser, 33); + SHA256_Update(&sha, T2_ser, 33); + SHA256_Final(x, &sha); - OPENSSL_cleanse(tmp, 32); - } + secp256k1_mpt_scalar_reduce32(x, x); + if (memcmp(x, zero, 32) == 0) + goto cleanup; /* avoid infinity later */ + } - /* mu = alpha + rho*x */ - { - unsigned char tmp[32]; - secp256k1_mpt_scalar_mul(tmp, rho, x); - secp256k1_mpt_scalar_add(mu, alpha, tmp); - OPENSSL_cleanse(tmp, 32); - } + /* ---- 10. Evaluate l(x), r(x), t_hat ---- */ + for (size_t k = 0; k < n; k++) + { + unsigned char tmp[32]; - /* ---- 12. IPA transcript + ux (binding), and H' normalization ---- */ + /* l = l0 + sL*x */ + secp256k1_mpt_scalar_mul(tmp, sl + 32 * k, x); + secp256k1_mpt_scalar_add(l_vec + 32 * k, l_vec + 32 * k, tmp); -/* 12a. Build a stable IPA transcript seed (32 bytes). - * - * IMPORTANT: - * - Prover and verifier MUST hash the exact same bytes in the exact same order. - * - Use only public elements that both sides already know. - * - Do NOT depend on internal intermediate buffers. - * - * Minimal, safe choice: context_id || A||S||T1||T2 || y||z||x || t_hat - * (All points are serialized compressed 33 bytes.) - */ - { - unsigned char A_ser[33], S_ser[33], T1_ser[33], T2_ser[33]; - size_t ser_len; - SHA256_CTX sha; - - ser_len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, A_ser, &ser_len, &A, SECP256K1_EC_COMPRESSED)) goto cleanup; - ser_len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, S_ser, &ser_len, &S, SECP256K1_EC_COMPRESSED)) goto cleanup; - ser_len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, T1_ser, &ser_len, &T1, SECP256K1_EC_COMPRESSED)) goto cleanup; - ser_len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, T2_ser, &ser_len, &T2, SECP256K1_EC_COMPRESSED)) goto cleanup; - - SHA256_Init(&sha); - - /* Domain/context binding */ - if (context_id) SHA256_Update(&sha, context_id, 32); - - /* Outer commitments */ - SHA256_Update(&sha, A_ser, 33); - SHA256_Update(&sha, S_ser, 33); - SHA256_Update(&sha, T1_ser, 33); - SHA256_Update(&sha, T2_ser, 33); - - /* Outer challenges */ - SHA256_Update(&sha, y, 32); - SHA256_Update(&sha, z, 32); - SHA256_Update(&sha, x, 32); - - /* Bind to t_hat as well (public scalar) */ - SHA256_Update(&sha, t_hat, 32); - - SHA256_Final(ipa_transcript, &sha); - } + /* r = r0 + r1*x */ + secp256k1_mpt_scalar_mul(tmp, r1_vec + 32 * k, x); + secp256k1_mpt_scalar_add(r_vec + 32 * k, r_vec + 32 * k, tmp); -/* 12b. Derive u_x = H(ipa_transcript || t_hat) reduced to scalar. */ - if (!derive_ipa_binding_challenge(ctx, ux_scalar, ipa_transcript, t_hat)) goto cleanup; + OPENSSL_cleanse(tmp, 32); + } -/* 12c. Normalize H: H'[k] = H[k] * y^{-k}. - * - * NOTE: - * - Requires y != 0. - * - If y==0 (mod q), abort (cannot invert). - */ - { - unsigned char y_inv[32]; - unsigned char y_inv_pow[32]; /* (y^{-1})^k */ - if (memcmp(y, zero, 32) == 0) goto cleanup; - - secp256k1_mpt_scalar_inverse(y_inv, y); - memcpy(y_inv_pow, one, 32); - - for (size_t k = 0; k < n; k++) { - H_prime[k] = H_vec[k]; - /* H_prime[k] = H_vec[k] * (y^{-1})^k */ - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &H_prime[k], y_inv_pow)) goto cleanup; - secp256k1_mpt_scalar_mul(y_inv_pow, y_inv_pow, y_inv); - } + if (!secp256k1_bulletproof_ipa_dot(ctx, t_hat, l_vec, r_vec, n)) + goto cleanup; - OPENSSL_cleanse(y_inv, 32); - OPENSSL_cleanse(y_inv_pow, 32); - } + /* ---- 11. tau_x and mu (aggregation changes tau_x) ---- */ + secp256k1_mpt_scalar_mul(x_sq, x, x); -/* 12d. Run IPA prover */ - { - size_t rounds_used = 0; - - if (!secp256k1_bulletproof_run_ipa_prover( - ctx, - &U, /* binding generator point */ - G_vec, /* G */ - H_prime, /* H' */ - l_vec, /* l(x) scalars (flat 32*n) */ - r_vec, /* r(x) scalars (flat 32*n) */ - n, - ipa_transcript, /* 32-byte seed */ - ux_scalar, /* u_x scalar */ - L_vec, - R_vec, - rounds, /* max_rounds = log2(n) */ - &rounds_used, - a_final, - b_final - )) goto cleanup; - - if (rounds_used != rounds) goto cleanup; - } + /* tau_x = tau2*x^2 + tau1*x + sum_j z^(j+2) * blinding_j */ + secp256k1_mpt_scalar_mul(tau_x, tau2, x_sq); + { + unsigned char tmp[32]; + secp256k1_mpt_scalar_mul(tmp, tau1, x); + secp256k1_mpt_scalar_add(tau_x, tau_x, tmp); - /* ---- 13. Serialize (uses rounds) ---- */ + /* + sum_j z^(j+2) * r_j */ + for (size_t j = 0; j < m; j++) { - const size_t expected = 292 + 66 * rounds; /* 4*33 + 2*rounds*33 + 5*32 */ - - /* Standard pattern: query size only */ - if (proof_out == NULL) { - if (proof_len) *proof_len = expected; - ok = 1; - goto cleanup; - } - - if (proof_len == NULL) goto cleanup; - - if (*proof_len < expected) { - *proof_len = expected; - goto cleanup; /* not enough space */ - } - - unsigned char* ptr = proof_out; - size_t ser_len; + unsigned char add[32]; + secp256k1_mpt_scalar_mul(add, z_j2 + 32 * j, blindings_flat + 32 * j); + secp256k1_mpt_scalar_add(tau_x, tau_x, add); + OPENSSL_cleanse(add, 32); + } + + OPENSSL_cleanse(tmp, 32); + } + + /* mu = alpha + rho*x */ + { + unsigned char tmp[32]; + secp256k1_mpt_scalar_mul(tmp, rho, x); + secp256k1_mpt_scalar_add(mu, alpha, tmp); + OPENSSL_cleanse(tmp, 32); + } + + /* ---- 12. IPA transcript + ux (binding), and H' normalization ---- */ + + /* 12a. Build a stable IPA transcript seed (32 bytes). + * + * IMPORTANT: + * - Prover and verifier MUST hash the exact same bytes in the exact same + * order. + * - Use only public elements that both sides already know. + * - Do NOT depend on internal intermediate buffers. + * + * Minimal, safe choice: context_id || A||S||T1||T2 || y||z||x || t_hat + * (All points are serialized compressed 33 bytes.) + */ + { + unsigned char A_ser[33], S_ser[33], T1_ser[33], T2_ser[33]; + size_t ser_len; + SHA256_CTX sha; -#define SER_PT(P) do { \ - ser_len = 33; \ - if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &ser_len, &(P), SECP256K1_EC_COMPRESSED)) goto cleanup; \ - if (ser_len != 33) goto cleanup; \ - ptr += 33; \ - } while (0) + ser_len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, A_ser, &ser_len, &A, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + ser_len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, S_ser, &ser_len, &S, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + ser_len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, T1_ser, &ser_len, &T1, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + ser_len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, T2_ser, &ser_len, &T2, + SECP256K1_EC_COMPRESSED)) + goto cleanup; - SER_PT(A); SER_PT(S); SER_PT(T1); SER_PT(T2); + SHA256_Init(&sha); - for (size_t r = 0; r < rounds; r++) SER_PT(L_vec[r]); - for (size_t r = 0; r < rounds; r++) SER_PT(R_vec[r]); + /* Domain/context binding */ + if (context_id) + SHA256_Update(&sha, context_id, 32); - memcpy(ptr, a_final, 32); ptr += 32; - memcpy(ptr, b_final, 32); ptr += 32; - memcpy(ptr, t_hat, 32); ptr += 32; - memcpy(ptr, tau_x, 32); ptr += 32; - memcpy(ptr, mu, 32); ptr += 32; + /* Outer commitments */ + SHA256_Update(&sha, A_ser, 33); + SHA256_Update(&sha, S_ser, 33); + SHA256_Update(&sha, T1_ser, 33); + SHA256_Update(&sha, T2_ser, 33); -#undef SER_PT + /* Outer challenges */ + SHA256_Update(&sha, y, 32); + SHA256_Update(&sha, z, 32); + SHA256_Update(&sha, x, 32); + + /* Bind to t_hat as well (public scalar) */ + SHA256_Update(&sha, t_hat, 32); + + SHA256_Final(ipa_transcript, &sha); + } + + /* 12b. Derive u_x = H(ipa_transcript || t_hat) reduced to scalar. */ + if (!derive_ipa_binding_challenge(ctx, ux_scalar, ipa_transcript, t_hat)) + goto cleanup; + + /* 12c. Normalize H: H'[k] = H[k] * y^{-k}. + * + * NOTE: + * - Requires y != 0. + * - If y==0 (mod q), abort (cannot invert). + */ + { + unsigned char y_inv[32]; + unsigned char y_inv_pow[32]; /* (y^{-1})^k */ + if (memcmp(y, zero, 32) == 0) + goto cleanup; - /* Final sanity */ - if ((size_t)(ptr - proof_out) != expected) goto cleanup; + secp256k1_mpt_scalar_inverse(y_inv, y); + memcpy(y_inv_pow, one, 32); + for (size_t k = 0; k < n; k++) + { + H_prime[k] = H_vec[k]; + /* H_prime[k] = H_vec[k] * (y^{-1})^k */ + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &H_prime[k], y_inv_pow)) + goto cleanup; + secp256k1_mpt_scalar_mul(y_inv_pow, y_inv_pow, y_inv); + } + + OPENSSL_cleanse(y_inv, 32); + OPENSSL_cleanse(y_inv_pow, 32); + } + + /* 12d. Run IPA prover */ + { + size_t rounds_used = 0; + + if (!secp256k1_bulletproof_run_ipa_prover( + ctx, &U, /* binding generator point */ + G_vec, /* G */ + H_prime, /* H' */ + l_vec, /* l(x) scalars (flat 32*n) */ + r_vec, /* r(x) scalars (flat 32*n) */ + n, ipa_transcript, /* 32-byte seed */ + ux_scalar, /* u_x scalar */ + L_vec, R_vec, rounds, /* max_rounds = log2(n) */ + &rounds_used, a_final, b_final)) + goto cleanup; + + if (rounds_used != rounds) + goto cleanup; + } + + /* ---- 13. Serialize (uses rounds) ---- */ + { + const size_t expected = 292 + 66 * rounds; /* 4*33 + 2*rounds*33 + 5*32 */ + + /* Standard pattern: query size only */ + if (proof_out == NULL) + { + if (proof_len) *proof_len = expected; + ok = 1; + goto cleanup; } - ok = 1; + if (proof_len == NULL) + goto cleanup; - cleanup: - - /* wipe sensitive scalars; free buffers */ - if (al) OPENSSL_cleanse(al, n * 32); - if (ar) OPENSSL_cleanse(ar, n * 32); - if (sl) OPENSSL_cleanse(sl, n * 32); - if (sr) OPENSSL_cleanse(sr, n * 32); - if (l_vec) OPENSSL_cleanse(l_vec, n * 32); - if (r_vec) OPENSSL_cleanse(r_vec, n * 32); - if (r1_vec) OPENSSL_cleanse(r1_vec, n * 32); - - OPENSSL_cleanse(alpha, 32); - OPENSSL_cleanse(rho, 32); - OPENSSL_cleanse(tau1, 32); - OPENSSL_cleanse(tau2, 32); - OPENSSL_cleanse(t1, 32); - OPENSSL_cleanse(t2, 32); - OPENSSL_cleanse(t_hat, 32); - OPENSSL_cleanse(tau_x, 32); - OPENSSL_cleanse(mu, 32); - - OPENSSL_cleanse(y, 32); - OPENSSL_cleanse(z, 32); - OPENSSL_cleanse(x, 32); - OPENSSL_cleanse(z_sq, 32); - OPENSSL_cleanse(z_neg, 32); - OPENSSL_cleanse(x_sq, 32); - OPENSSL_cleanse(ux_scalar, 32); - OPENSSL_cleanse(ipa_transcript, 32); - - if (G_vec) { OPENSSL_cleanse(G_vec, n * sizeof(secp256k1_pubkey)); free(G_vec); } - if (H_vec) { OPENSSL_cleanse(H_vec, n * sizeof(secp256k1_pubkey)); free(H_vec); } - if (H_prime) { OPENSSL_cleanse(H_prime, n * sizeof(secp256k1_pubkey)); free(H_prime); } - - if (al) free(al); - if (ar) free(ar); - if (sl) free(sl); - if (sr) free(sr); - if (l_vec) free(l_vec); - if (r_vec) free(r_vec); - if (r1_vec) free(r1_vec); + if (*proof_len < expected) + { + *proof_len = expected; + goto cleanup; /* not enough space */ + } + + unsigned char *ptr = proof_out; + size_t ser_len; + +#define SER_PT(P) \ + do \ + { \ + ser_len = 33; \ + if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &ser_len, &(P), \ + SECP256K1_EC_COMPRESSED)) \ + goto cleanup; \ + if (ser_len != 33) \ + goto cleanup; \ + ptr += 33; \ + } while (0) + + SER_PT(A); + SER_PT(S); + SER_PT(T1); + SER_PT(T2); + + for (size_t r = 0; r < rounds; r++) + SER_PT(L_vec[r]); + for (size_t r = 0; r < rounds; r++) + SER_PT(R_vec[r]); + + memcpy(ptr, a_final, 32); + ptr += 32; + memcpy(ptr, b_final, 32); + ptr += 32; + memcpy(ptr, t_hat, 32); + ptr += 32; + memcpy(ptr, tau_x, 32); + ptr += 32; + memcpy(ptr, mu, 32); + ptr += 32; - if (L_vec) { OPENSSL_cleanse(L_vec, rounds * sizeof(secp256k1_pubkey)); free(L_vec); } - if (R_vec) { OPENSSL_cleanse(R_vec, rounds * sizeof(secp256k1_pubkey)); free(R_vec); } +#undef SER_PT - if (y_powers) { OPENSSL_cleanse(y_powers, n * 32); free(y_powers); } - if (z_j2) { OPENSSL_cleanse(z_j2, m * 32); free(z_j2); } + /* Final sanity */ + if ((size_t)(ptr - proof_out) != expected) + goto cleanup; + + *proof_len = expected; + } + + ok = 1; + +cleanup: + + /* wipe sensitive scalars; free buffers */ + if (al) + OPENSSL_cleanse(al, n * 32); + if (ar) + OPENSSL_cleanse(ar, n * 32); + if (sl) + OPENSSL_cleanse(sl, n * 32); + if (sr) + OPENSSL_cleanse(sr, n * 32); + if (l_vec) + OPENSSL_cleanse(l_vec, n * 32); + if (r_vec) + OPENSSL_cleanse(r_vec, n * 32); + if (r1_vec) + OPENSSL_cleanse(r1_vec, n * 32); + + OPENSSL_cleanse(alpha, 32); + OPENSSL_cleanse(rho, 32); + OPENSSL_cleanse(tau1, 32); + OPENSSL_cleanse(tau2, 32); + OPENSSL_cleanse(t1, 32); + OPENSSL_cleanse(t2, 32); + OPENSSL_cleanse(t_hat, 32); + OPENSSL_cleanse(tau_x, 32); + OPENSSL_cleanse(mu, 32); + + OPENSSL_cleanse(y, 32); + OPENSSL_cleanse(z, 32); + OPENSSL_cleanse(x, 32); + OPENSSL_cleanse(z_sq, 32); + OPENSSL_cleanse(z_neg, 32); + OPENSSL_cleanse(x_sq, 32); + OPENSSL_cleanse(ux_scalar, 32); + OPENSSL_cleanse(ipa_transcript, 32); + + if (G_vec) + { + OPENSSL_cleanse(G_vec, n * sizeof(secp256k1_pubkey)); + free(G_vec); + } + if (H_vec) + { + OPENSSL_cleanse(H_vec, n * sizeof(secp256k1_pubkey)); + free(H_vec); + } + if (H_prime) + { + OPENSSL_cleanse(H_prime, n * sizeof(secp256k1_pubkey)); + free(H_prime); + } + + if (al) + free(al); + if (ar) + free(ar); + if (sl) + free(sl); + if (sr) + free(sr); + if (l_vec) + free(l_vec); + if (r_vec) + free(r_vec); + if (r1_vec) + free(r1_vec); + + if (L_vec) + { + OPENSSL_cleanse(L_vec, rounds * sizeof(secp256k1_pubkey)); + free(L_vec); + } + if (R_vec) + { + OPENSSL_cleanse(R_vec, rounds * sizeof(secp256k1_pubkey)); + free(R_vec); + } - return ok; + if (y_powers) + { + OPENSSL_cleanse(y_powers, n * 32); + free(y_powers); + } + if (z_j2) + { + OPENSSL_cleanse(z_j2, m * 32); + free(z_j2); + } + + return ok; } /** * Verifies an aggregated Bulletproof range proof for m commitments. @@ -1753,475 +1929,667 @@ int secp256k1_bulletproof_prove_agg( */ int secp256k1_bulletproof_verify_agg( - const secp256k1_context* ctx, - const secp256k1_pubkey* G_vec, /* length n = 64*m */ - const secp256k1_pubkey* H_vec, /* length n = 64*m */ - const unsigned char* proof, - size_t proof_len, - const secp256k1_pubkey* commitment_C_vec, /* length m */ - size_t m, - const secp256k1_pubkey* pk_base, - const unsigned char* context_id -) { - - if (!ctx || !G_vec || !H_vec || !proof || !commitment_C_vec || !pk_base) return 0; - if (m == 0) return 0; - /* Aggregation requires n = 64*m to be power-of-two => m must be power-of-two. */ - if ((m & (m - 1)) != 0) return 0; - - const size_t n = BP_TOTAL_BITS(m); /* 64*m */ - const size_t rounds = bp_ipa_rounds(n); - - /* Proof length is dynamic in aggregated mode */ - const size_t expected_len = 292 + 66 * rounds; - if (proof_len != expected_len) return 0; - - /* --- Unpack proof --- */ - secp256k1_pubkey A, S, T1, T2; - secp256k1_pubkey U; - - secp256k1_pubkey* L_vec = (secp256k1_pubkey*)malloc(rounds * sizeof(secp256k1_pubkey)); - secp256k1_pubkey* R_vec = (secp256k1_pubkey*)malloc(rounds * sizeof(secp256k1_pubkey)); - if (!L_vec || !R_vec) { free(L_vec); free(R_vec); return 0; } - - unsigned char a_final[32], b_final[32]; - unsigned char t_hat[32], tau_x[32], mu[32]; - - unsigned char y[32], z[32], x[32]; - unsigned char ux_scalar[32]; - unsigned char z_sq[32]; - - const unsigned char* ptr = proof; - - if (!secp256k1_ec_pubkey_parse(ctx, &A, ptr, 33)) goto fail; ptr += 33; - if (!secp256k1_ec_pubkey_parse(ctx, &S, ptr, 33)) goto fail; ptr += 33; - if (!secp256k1_ec_pubkey_parse(ctx, &T1, ptr, 33)) goto fail; ptr += 33; - if (!secp256k1_ec_pubkey_parse(ctx, &T2, ptr, 33)) goto fail; ptr += 33; - - for (size_t i = 0; i < rounds; i++) { - if (!secp256k1_ec_pubkey_parse(ctx, &L_vec[i], ptr, 33)) goto fail; - ptr += 33; - } + const secp256k1_context *ctx, + const secp256k1_pubkey *G_vec, /* length n = 64*m */ + const secp256k1_pubkey *H_vec, /* length n = 64*m */ + const unsigned char *proof, size_t proof_len, + const secp256k1_pubkey *commitment_C_vec, /* length m */ + size_t m, const secp256k1_pubkey *pk_base, const unsigned char *context_id) +{ + if (!ctx || !G_vec || !H_vec || !proof || !commitment_C_vec || !pk_base) + return 0; + if (m == 0) + return 0; + /* Aggregation requires n = 64*m to be power-of-two => m must be power-of-two. + */ + if ((m & (m - 1)) != 0) + return 0; - for (size_t i = 0; i < rounds; i++) { - if (!secp256k1_ec_pubkey_parse(ctx, &R_vec[i], ptr, 33)) goto fail; - ptr += 33; - } + const size_t n = BP_TOTAL_BITS(m); /* 64*m */ + const size_t rounds = bp_ipa_rounds(n); - memcpy(a_final, ptr, 32); ptr += 32; - memcpy(b_final, ptr, 32); ptr += 32; - memcpy(t_hat, ptr, 32); ptr += 32; - memcpy(tau_x, ptr, 32); ptr += 32; - memcpy(mu, ptr, 32); ptr += 32; + /* Proof length is dynamic in aggregated mode */ + const size_t expected_len = 292 + 66 * rounds; + if (proof_len != expected_len) + return 0; - /* Basic scalar validity */ - if (!secp256k1_ec_seckey_verify(ctx, a_final)) goto fail; - if (!secp256k1_ec_seckey_verify(ctx, b_final)) goto fail; - if (!secp256k1_ec_seckey_verify(ctx, t_hat)) goto fail; - if (!secp256k1_ec_seckey_verify(ctx, tau_x)) goto fail; - if (!secp256k1_ec_seckey_verify(ctx, mu)) goto fail; + /* --- Unpack proof --- */ + secp256k1_pubkey A, S, T1, T2; + secp256k1_pubkey U; - /* Derive U */ + secp256k1_pubkey *L_vec = + (secp256k1_pubkey *)malloc(rounds * sizeof(secp256k1_pubkey)); + secp256k1_pubkey *R_vec = + (secp256k1_pubkey *)malloc(rounds * sizeof(secp256k1_pubkey)); + if (!L_vec || !R_vec) + { + free(L_vec); + free(R_vec); + return 0; + } + + unsigned char a_final[32], b_final[32]; + unsigned char t_hat[32], tau_x[32], mu[32]; + + unsigned char y[32], z[32], x[32]; + unsigned char ux_scalar[32]; + unsigned char z_sq[32]; + + const unsigned char *ptr = proof; + + if (!secp256k1_ec_pubkey_parse(ctx, &A, ptr, 33)) + goto fail; + ptr += 33; + if (!secp256k1_ec_pubkey_parse(ctx, &S, ptr, 33)) + goto fail; + ptr += 33; + if (!secp256k1_ec_pubkey_parse(ctx, &T1, ptr, 33)) + goto fail; + ptr += 33; + if (!secp256k1_ec_pubkey_parse(ctx, &T2, ptr, 33)) + goto fail; + ptr += 33; + + for (size_t i = 0; i < rounds; i++) + { + if (!secp256k1_ec_pubkey_parse(ctx, &L_vec[i], ptr, 33)) + goto fail; + ptr += 33; + } + + for (size_t i = 0; i < rounds; i++) + { + if (!secp256k1_ec_pubkey_parse(ctx, &R_vec[i], ptr, 33)) + goto fail; + ptr += 33; + } + + memcpy(a_final, ptr, 32); + ptr += 32; + memcpy(b_final, ptr, 32); + ptr += 32; + memcpy(t_hat, ptr, 32); + ptr += 32; + memcpy(tau_x, ptr, 32); + ptr += 32; + memcpy(mu, ptr, 32); + ptr += 32; + + /* Basic scalar validity */ + if (!secp256k1_ec_seckey_verify(ctx, a_final)) + goto fail; + if (!secp256k1_ec_seckey_verify(ctx, b_final)) + goto fail; + if (!secp256k1_ec_seckey_verify(ctx, t_hat)) + goto fail; + if (!secp256k1_ec_seckey_verify(ctx, tau_x)) + goto fail; + if (!secp256k1_ec_seckey_verify(ctx, mu)) + goto fail; + + /* Derive U */ + { + secp256k1_pubkey U_arr[1]; + if (!secp256k1_mpt_get_generator_vector(ctx, U_arr, 1, + (const unsigned char *)"BP_U", 4)) + goto fail; + U = U_arr[0]; + } + + /* --- Fiat–Shamir: y,z --- */ + unsigned char A_ser[33], S_ser[33], T1_ser[33], T2_ser[33]; + size_t slen = 33; + SHA256_CTX sha; + + if (!secp256k1_ec_pubkey_serialize(ctx, A_ser, &slen, &A, + SECP256K1_EC_COMPRESSED)) + goto fail; + slen = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, S_ser, &slen, &S, + SECP256K1_EC_COMPRESSED)) + goto fail; + slen = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, T1_ser, &slen, &T1, + SECP256K1_EC_COMPRESSED)) + goto fail; + slen = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, T2_ser, &slen, &T2, + SECP256K1_EC_COMPRESSED)) + goto fail; + + SHA256_Init(&sha); + + /* 1. Domain Separation */ + SHA256_Update(&sha, "MPT_BULLETPROOF_RANGE", 21); + + /* 2. Transaction Context */ + if (context_id) + SHA256_Update(&sha, context_id, 32); + + /* 3. Value Commitments (Inputs to Verify) */ + for (size_t i = 0; i < m; i++) + { + unsigned char C_ser[33]; + size_t c_len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, C_ser, &c_len, &commitment_C_vec[i], + SECP256K1_EC_COMPRESSED)) + goto fail; + SHA256_Update(&sha, C_ser, 33); + } + + /* 4. A, S */ + SHA256_Update(&sha, A_ser, 33); + SHA256_Update(&sha, S_ser, 33); + SHA256_Final(y, &sha); + secp256k1_mpt_scalar_reduce32(y, y); + + /* Generate z (Hash T1 || y) */ + SHA256_Init(&sha); + SHA256_Update(&sha, "MPT_BULLETPROOF_RANGE", 21); + if (context_id) + SHA256_Update(&sha, context_id, 32); + for (size_t i = 0; i < m; i++) + { + unsigned char C_ser[33]; + size_t c_len = 33; + secp256k1_ec_pubkey_serialize(ctx, C_ser, &c_len, &commitment_C_vec[i], + SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, C_ser, 33); + } + SHA256_Update(&sha, A_ser, 33); + SHA256_Update(&sha, S_ser, 33); + SHA256_Update(&sha, y, 32); + SHA256_Final(z, &sha); + secp256k1_mpt_scalar_reduce32(z, z); + + if (!secp256k1_ec_seckey_verify(ctx, y) || + !secp256k1_ec_seckey_verify(ctx, z)) + goto fail; + + /* Powers */ + unsigned char *y_powers = (unsigned char *)malloc(n * 32); + unsigned char *y_inv_powers = (unsigned char *)malloc(n * 32); + if (!y_powers || !y_inv_powers) + { + free(y_powers); + free(y_inv_powers); + goto fail; + } + + unsigned char y_inv[32]; + scalar_vector_powers(ctx, (unsigned char (*)[32])y_powers, y, n); + secp256k1_mpt_scalar_inverse(y_inv, y); + scalar_vector_powers(ctx, (unsigned char (*)[32])y_inv_powers, y_inv, n); + + /* --- Fiat–Shamir: x --- */ + SHA256_Init(&sha); + if (context_id) + SHA256_Update(&sha, context_id, 32); + SHA256_Update(&sha, A_ser, 33); + SHA256_Update(&sha, S_ser, 33); + SHA256_Update(&sha, y, 32); + SHA256_Update(&sha, z, 32); + SHA256_Update(&sha, T1_ser, 33); + SHA256_Update(&sha, T2_ser, 33); + SHA256_Final(x, &sha); + secp256k1_mpt_scalar_reduce32(x, x); + + if (!secp256k1_ec_seckey_verify(ctx, x)) + { + free(y_powers); + free(y_inv_powers); + goto fail; + } + + /* z^2 */ + secp256k1_mpt_scalar_mul(z_sq, z, z); + + /* ========================================================================= + * Step 3: Verify polynomial identity: + * t_hat*G + tau_x*H == (sum_j z^(j+2) * V_j) + delta(y,z)*G + x*T1 + x^2*T2 + * ========================================================================= + */ + + /* --- delta(y,z) for aggregation --- */ + unsigned char (*y_block_sum)[32] = (unsigned char (*)[32])malloc(m * 32); + if (!y_block_sum) + { + free(y_powers); + free(y_inv_powers); + goto fail; + } + + unsigned char two_sum[32]; + compute_delta_scalars(ctx, y_block_sum, two_sum, y, m); + + unsigned char delta[32] = {0}; + unsigned char sum_y_all[32] = {0}; + + /* sum_y_all = sum_{k=0}^{n-1} y^k = sum_j y_block_sum[j] */ + for (size_t j = 0; j < m; j++) + { + secp256k1_mpt_scalar_add(sum_y_all, sum_y_all, y_block_sum[j]); + } + + /* delta += (z - z^2) * sum_y_all */ + { + unsigned char z_minus_z2[32], tmp[32]; + secp256k1_mpt_scalar_sub(z_minus_z2, z, z_sq); + secp256k1_mpt_scalar_mul(tmp, z_minus_z2, sum_y_all); + secp256k1_mpt_scalar_add(delta, delta, tmp); + OPENSSL_cleanse(z_minus_z2, 32); + OPENSSL_cleanse(tmp, 32); + } + + /* delta -= sum_{j=0}^{m-1} z^(j+3) * two_sum */ + for (size_t j = 0; j < m; j++) + { + unsigned char z_j3[32], tmp[32]; + scalar_pow_u32(ctx, z_j3, z, (unsigned int)(j + 3)); + secp256k1_mpt_scalar_mul(tmp, z_j3, two_sum); + secp256k1_mpt_scalar_negate(tmp, tmp); + secp256k1_mpt_scalar_add(delta, delta, tmp); + OPENSSL_cleanse(z_j3, 32); + OPENSSL_cleanse(tmp, 32); + } + + OPENSSL_cleanse(sum_y_all, 32); + + /* LHS = t_hat*G + tau_x*Base */ + secp256k1_pubkey LHS; + { + unsigned char zero32[32] = {0}; + int have_t = 0, have_tau = 0; + secp256k1_pubkey tG, tauH; + + if (memcmp(t_hat, zero32, 32) != 0) { - secp256k1_pubkey U_arr[1]; - if (!secp256k1_mpt_get_generator_vector(ctx, U_arr, 1, (const unsigned char*)"BP_U", 4)) goto fail; - U = U_arr[0]; - } - - /* --- Fiat–Shamir: y,z --- */ - unsigned char A_ser[33], S_ser[33], T1_ser[33], T2_ser[33]; - size_t slen = 33; - SHA256_CTX sha; - - if (!secp256k1_ec_pubkey_serialize(ctx, A_ser, &slen, &A, SECP256K1_EC_COMPRESSED)) goto fail; - slen = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, S_ser, &slen, &S, SECP256K1_EC_COMPRESSED)) goto fail; - slen = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, T1_ser, &slen, &T1, SECP256K1_EC_COMPRESSED)) goto fail; - slen = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, T2_ser, &slen, &T2, SECP256K1_EC_COMPRESSED)) goto fail; - - SHA256_Init(&sha); - - /* 1. Domain Separation */ - SHA256_Update(&sha, "MPT_BULLETPROOF_RANGE", 21); - - /* 2. Transaction Context */ - if (context_id) SHA256_Update(&sha, context_id, 32); - - /* 3. Value Commitments (Inputs to Verify) */ - for (size_t i = 0; i < m; i++) { - unsigned char C_ser[33]; - size_t c_len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, C_ser, &c_len, &commitment_C_vec[i], SECP256K1_EC_COMPRESSED)) goto fail; - SHA256_Update(&sha, C_ser, 33); + if (!secp256k1_ec_pubkey_create(ctx, &tG, t_hat)) + { + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; + } + have_t = 1; } - - /* 4. A, S */ - SHA256_Update(&sha, A_ser, 33); - SHA256_Update(&sha, S_ser, 33); - SHA256_Final(y, &sha); - secp256k1_mpt_scalar_reduce32(y, y); - - /* Generate z (Hash T1 || y) */ - SHA256_Init(&sha); - SHA256_Update(&sha, "MPT_BULLETPROOF_RANGE", 21); - if (context_id) SHA256_Update(&sha, context_id, 32); - for (size_t i = 0; i < m; i++) { - unsigned char C_ser[33]; - size_t c_len = 33; - secp256k1_ec_pubkey_serialize(ctx, C_ser, &c_len, &commitment_C_vec[i], SECP256K1_EC_COMPRESSED); - SHA256_Update(&sha, C_ser, 33); + if (memcmp(tau_x, zero32, 32) != 0) + { + tauH = *pk_base; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tauH, tau_x)) + { + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; + } + have_tau = 1; } - SHA256_Update(&sha, A_ser, 33); - SHA256_Update(&sha, S_ser, 33); - SHA256_Update(&sha, y, 32); - SHA256_Final(z, &sha); - secp256k1_mpt_scalar_reduce32(z, z); - - if (!secp256k1_ec_seckey_verify(ctx, y) || !secp256k1_ec_seckey_verify(ctx, z)) goto fail; - - /* Powers */ - unsigned char* y_powers = (unsigned char*)malloc(n * 32); - unsigned char* y_inv_powers = (unsigned char*)malloc(n * 32); - if (!y_powers || !y_inv_powers) { free(y_powers); free(y_inv_powers); goto fail; } - - unsigned char y_inv[32]; - scalar_vector_powers(ctx, (unsigned char (*)[32])y_powers, y, n); - secp256k1_mpt_scalar_inverse(y_inv, y); - scalar_vector_powers(ctx, (unsigned char (*)[32])y_inv_powers, y_inv, n); - - /* --- Fiat–Shamir: x --- */ - SHA256_Init(&sha); - if (context_id) SHA256_Update(&sha, context_id, 32); - SHA256_Update(&sha, A_ser, 33); - SHA256_Update(&sha, S_ser, 33); - SHA256_Update(&sha, y, 32); - SHA256_Update(&sha, z, 32); - SHA256_Update(&sha, T1_ser, 33); - SHA256_Update(&sha, T2_ser, 33); - SHA256_Final(x, &sha); - secp256k1_mpt_scalar_reduce32(x, x); - - if (!secp256k1_ec_seckey_verify(ctx, x)) { free(y_powers); free(y_inv_powers); goto fail; } - - /* z^2 */ - secp256k1_mpt_scalar_mul(z_sq, z, z); - - /* ========================================================================= - * Step 3: Verify polynomial identity: - * t_hat*G + tau_x*H == (sum_j z^(j+2) * V_j) + delta(y,z)*G + x*T1 + x^2*T2 - * ========================================================================= */ - - /* --- delta(y,z) for aggregation --- */ - unsigned char (*y_block_sum)[32] = (unsigned char (*)[32])malloc(m * 32); - if (!y_block_sum) { free(y_powers); free(y_inv_powers); goto fail; } - - unsigned char two_sum[32]; - compute_delta_scalars(ctx, y_block_sum, two_sum, y, m); - - unsigned char delta[32] = {0}; - unsigned char sum_y_all[32] = {0}; - -/* sum_y_all = sum_{k=0}^{n-1} y^k = sum_j y_block_sum[j] */ - for (size_t j = 0; j < m; j++) { - secp256k1_mpt_scalar_add(sum_y_all, sum_y_all, y_block_sum[j]); + if (have_t && have_tau) + { + const secp256k1_pubkey *pts[2] = {&tG, &tauH}; + if (!secp256k1_ec_pubkey_combine(ctx, &LHS, pts, 2)) + { + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; + } } - -/* delta += (z - z^2) * sum_y_all */ + else if (have_t) { - unsigned char z_minus_z2[32], tmp[32]; - secp256k1_mpt_scalar_sub(z_minus_z2, z, z_sq); - secp256k1_mpt_scalar_mul(tmp, z_minus_z2, sum_y_all); - secp256k1_mpt_scalar_add(delta, delta, tmp); - OPENSSL_cleanse(z_minus_z2, 32); - OPENSSL_cleanse(tmp, 32); + LHS = tG; } - -/* delta -= sum_{j=0}^{m-1} z^(j+3) * two_sum */ - for (size_t j = 0; j < m; j++) { - unsigned char z_j3[32], tmp[32]; - scalar_pow_u32(ctx, z_j3, z, (unsigned int)(j + 3)); - secp256k1_mpt_scalar_mul(tmp, z_j3, two_sum); - secp256k1_mpt_scalar_negate(tmp, tmp); - secp256k1_mpt_scalar_add(delta, delta, tmp); - OPENSSL_cleanse(z_j3, 32); - OPENSSL_cleanse(tmp, 32); + else if (have_tau) + { + LHS = tauH; } - - OPENSSL_cleanse(sum_y_all, 32); - - - /* LHS = t_hat*G + tau_x*Base */ - secp256k1_pubkey LHS; + else { - unsigned char zero32[32] = {0}; - int have_t = 0, have_tau = 0; - secp256k1_pubkey tG, tauH; - - if (memcmp(t_hat, zero32, 32) != 0) { - if (!secp256k1_ec_pubkey_create(ctx, &tG, t_hat)) { free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } - have_t = 1; - } - if (memcmp(tau_x, zero32, 32) != 0) { - tauH = *pk_base; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tauH, tau_x)) { free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } - have_tau = 1; + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; + } + } + + /* RHS = (sum_j z^(j+2) V_j) + delta*G + x*T1 + x^2*T2 */ + secp256k1_pubkey RHS; + { + secp256k1_pubkey acc, tmpP; + int inited = 0; + unsigned char zero32[32] = {0}; + + /* sum_j z^(j+2) V_j */ + for (size_t j = 0; j < m; j++) + { + unsigned char z_j2[32]; + scalar_pow_u32(ctx, z_j2, z, (unsigned int)(j + 2)); + + if (memcmp(z_j2, zero32, 32) != 0) + { + tmpP = commitment_C_vec[j]; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmpP, z_j2)) + { + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; } - if (have_t && have_tau) { - const secp256k1_pubkey* pts[2] = { &tG, &tauH }; - if (!secp256k1_ec_pubkey_combine(ctx, &LHS, pts, 2)) { free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } - } else if (have_t) { - LHS = tG; - } else if (have_tau) { - LHS = tauH; - } else { - free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; + if (!add_term(ctx, &acc, &inited, &tmpP)) + { + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; } + } + OPENSSL_cleanse(z_j2, 32); } - /* RHS = (sum_j z^(j+2) V_j) + delta*G + x*T1 + x^2*T2 */ - secp256k1_pubkey RHS; + /* + delta*G */ + if (!scalar_is_zero(delta)) { - secp256k1_pubkey acc, tmpP; - int inited = 0; - unsigned char zero32[32] = {0}; - - /* sum_j z^(j+2) V_j */ - for (size_t j = 0; j < m; j++) { - unsigned char z_j2[32]; - scalar_pow_u32(ctx, z_j2, z, (unsigned int)(j + 2)); - - if (memcmp(z_j2, zero32, 32) != 0) { - tmpP = commitment_C_vec[j]; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmpP, z_j2)) { free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } - if (!add_term(ctx, &acc, &inited, &tmpP)) { free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } - } - OPENSSL_cleanse(z_j2, 32); - } - - /* + delta*G */ - if (!scalar_is_zero(delta)) { - secp256k1_pubkey deltaG; - if (!secp256k1_ec_pubkey_create(ctx, &deltaG, delta)) goto fail; - if (!add_term(ctx, &acc, &inited, &deltaG)) goto fail; - } - - - - /* + x*T1 */ - if (memcmp(x, zero32, 32) != 0) { - tmpP = T1; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmpP, x)) { free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } - if (!add_term(ctx, &acc, &inited, &tmpP)) { free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } - } - - /* + x^2*T2 */ - unsigned char x_sq[32]; - secp256k1_mpt_scalar_mul(x_sq, x, x); - if (memcmp(x_sq, zero32, 32) != 0) { - tmpP = T2; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmpP, x_sq)) { OPENSSL_cleanse(x_sq,32); free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } - if (!add_term(ctx, &acc, &inited, &tmpP)) { OPENSSL_cleanse(x_sq,32); free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } - } - OPENSSL_cleanse(x_sq, 32); - - // if (!inited) { free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } - RHS = acc; - + secp256k1_pubkey deltaG; + if (!secp256k1_ec_pubkey_create(ctx, &deltaG, delta)) + goto fail; + if (!add_term(ctx, &acc, &inited, &deltaG)) + goto fail; } - if (!pubkey_equal(ctx, &LHS, &RHS)) { - printf("[VERIFY] Step3 polynomial identity failed\n"); - free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; + /* + x*T1 */ + if (memcmp(x, zero32, 32) != 0) + { + tmpP = T1; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmpP, x)) + { + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; + } + if (!add_term(ctx, &acc, &inited, &tmpP)) + { + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; + } } - /* ========================================================================= - * Step 4: Build P and Verify IPA - * ========================================================================= */ - - unsigned char ipa_transcript_id[32]; + /* + x^2*T2 */ + unsigned char x_sq[32]; + secp256k1_mpt_scalar_mul(x_sq, x, x); + if (memcmp(x_sq, zero32, 32) != 0) { - SHA256_CTX sha; - unsigned char A_ser[33], S_ser[33], T1_ser[33], T2_ser[33]; - size_t len = 33; - - if (!secp256k1_ec_pubkey_serialize(ctx, A_ser, &len, &A, SECP256K1_EC_COMPRESSED)) goto fail; - len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, S_ser, &len, &S, SECP256K1_EC_COMPRESSED)) goto fail; - len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, T1_ser,&len, &T1, SECP256K1_EC_COMPRESSED)) goto fail; - len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, T2_ser,&len, &T2, SECP256K1_EC_COMPRESSED)) goto fail; - - - - SHA256_Init(&sha); - if (context_id) SHA256_Update(&sha, context_id, 32); - SHA256_Update(&sha, A_ser, 33); - SHA256_Update(&sha, S_ser, 33); - SHA256_Update(&sha, T1_ser,33); - SHA256_Update(&sha, T2_ser,33); - SHA256_Update(&sha, y, 32); - SHA256_Update(&sha, z, 32); - SHA256_Update(&sha, x, 32); - SHA256_Update(&sha, t_hat, 32); - SHA256_Final(ipa_transcript_id, &sha); + tmpP = T2; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmpP, x_sq)) + { + OPENSSL_cleanse(x_sq, 32); + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; + } + if (!add_term(ctx, &acc, &inited, &tmpP)) + { + OPENSSL_cleanse(x_sq, 32); + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; + } } + OPENSSL_cleanse(x_sq, 32); - if (!derive_ipa_binding_challenge(ctx, ux_scalar, ipa_transcript_id, t_hat)) - goto fail; + // if (!inited) { free(y_block_sum); free(y_powers); free(y_inv_powers); + // goto fail; } + RHS = acc; + } + if (!pubkey_equal(ctx, &LHS, &RHS)) + { + printf("[VERIFY] Step3 polynomial identity failed\n"); + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; + } - secp256k1_pubkey P = A; + /* ========================================================================= + * Step 4: Build P and Verify IPA + * ========================================================================= + */ - /* P += x*S */ - { - secp256k1_pubkey xS = S; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &xS, x)) { free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } + unsigned char ipa_transcript_id[32]; + { + SHA256_CTX sha; + unsigned char A_ser[33], S_ser[33], T1_ser[33], T2_ser[33]; + size_t len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, A_ser, &len, &A, + SECP256K1_EC_COMPRESSED)) + goto fail; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, S_ser, &len, &S, + SECP256K1_EC_COMPRESSED)) + goto fail; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, T1_ser, &len, &T1, + SECP256K1_EC_COMPRESSED)) + goto fail; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, T2_ser, &len, &T2, + SECP256K1_EC_COMPRESSED)) + goto fail; - secp256k1_pubkey newP; - const secp256k1_pubkey* pts[2] = { &P, &xS }; - if (!secp256k1_ec_pubkey_combine(ctx, &newP, pts, 2)) goto fail; - P = newP; + SHA256_Init(&sha); + if (context_id) + SHA256_Update(&sha, context_id, 32); + SHA256_Update(&sha, A_ser, 33); + SHA256_Update(&sha, S_ser, 33); + SHA256_Update(&sha, T1_ser, 33); + SHA256_Update(&sha, T2_ser, 33); + SHA256_Update(&sha, y, 32); + SHA256_Update(&sha, z, 32); + SHA256_Update(&sha, x, 32); + SHA256_Update(&sha, t_hat, 32); + SHA256_Final(ipa_transcript_id, &sha); + } - } + if (!derive_ipa_binding_challenge(ctx, ux_scalar, ipa_transcript_id, t_hat)) + goto fail; - /* P += sum_{k=0}^{n-1} [ (-z)*G_k + ( z*y^k + z^(block+2)*z^2*2^i ) * (y^{-k}*H_k) ] */ - unsigned char neg_z[32]; - memcpy(neg_z, z, 32); - if (!secp256k1_ec_seckey_negate(ctx, neg_z)) { free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } + secp256k1_pubkey P = A; - for (size_t j = 0; j < m; j++) { - unsigned char z_j2[32]; - scalar_pow_u32(ctx, z_j2, z, (unsigned int)(j + 2)); + /* P += x*S */ + { + secp256k1_pubkey xS = S; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &xS, x)) + { + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; + } + + secp256k1_pubkey newP; + const secp256k1_pubkey *pts[2] = {&P, &xS}; + if (!secp256k1_ec_pubkey_combine(ctx, &newP, pts, 2)) + goto fail; + P = newP; + } + + /* P += sum_{k=0}^{n-1} [ (-z)*G_k + ( z*y^k + z^(block+2)*z^2*2^i ) * + * (y^{-k}*H_k) ] */ + unsigned char neg_z[32]; + memcpy(neg_z, z, 32); + if (!secp256k1_ec_seckey_negate(ctx, neg_z)) + { + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; + } - for (size_t i = 0; i < 64; i++) { - const size_t k = j * 64 + i; + for (size_t j = 0; j < m; j++) + { + unsigned char z_j2[32]; + scalar_pow_u32(ctx, z_j2, z, (unsigned int)(j + 2)); - /* ---- Gi term: (-z) * G_k ---- */ - if (!scalar_is_zero(neg_z)) { - secp256k1_pubkey Gi = G_vec[k]; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &Gi, neg_z)) goto fail; + for (size_t i = 0; i < 64; i++) + { + const size_t k = j * 64 + i; - secp256k1_pubkey newP; - const secp256k1_pubkey* pts2[2] = { &P, &Gi }; - if (!secp256k1_ec_pubkey_combine(ctx, &newP, pts2, 2)) goto fail; - P = newP; - } + /* ---- Gi term: (-z) * G_k ---- */ + if (!scalar_is_zero(neg_z)) + { + secp256k1_pubkey Gi = G_vec[k]; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &Gi, neg_z)) + goto fail; - /* ---- Hi term: termH * (y^{-k} * H_k) ---- */ - unsigned char termH[32], tmp[32]; - unsigned char two_i[32] = {0}; + secp256k1_pubkey newP; + const secp256k1_pubkey *pts2[2] = {&P, &Gi}; + if (!secp256k1_ec_pubkey_combine(ctx, &newP, pts2, 2)) + goto fail; + P = newP; + } - secp256k1_mpt_scalar_mul(termH, z, (const unsigned char*)(y_powers + 32*k)); + /* ---- Hi term: termH * (y^{-k} * H_k) ---- */ + unsigned char termH[32], tmp[32]; + unsigned char two_i[32] = {0}; - two_i[31 - (i / 8)] = (unsigned char)(1u << (i % 8)); - secp256k1_mpt_scalar_mul(tmp, z_j2, two_i); - secp256k1_mpt_scalar_add(termH, termH, tmp); + secp256k1_mpt_scalar_mul(termH, z, + (const unsigned char *)(y_powers + 32 * k)); - if (!scalar_is_zero(termH)) { - secp256k1_pubkey Hi = H_vec[k]; + two_i[31 - (i / 8)] = (unsigned char)(1u << (i % 8)); + secp256k1_mpt_scalar_mul(tmp, z_j2, two_i); + secp256k1_mpt_scalar_add(termH, termH, tmp); - /* Hi = (y^{-k} * H_k) */ - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &Hi, (const unsigned char*)(y_inv_powers + 32*k))) - goto fail; + if (!scalar_is_zero(termH)) + { + secp256k1_pubkey Hi = H_vec[k]; - /* Hi = termH * Hi */ - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &Hi, termH)) - goto fail; + /* Hi = (y^{-k} * H_k) */ + if (!secp256k1_ec_pubkey_tweak_mul( + ctx, &Hi, (const unsigned char *)(y_inv_powers + 32 * k))) + goto fail; - secp256k1_pubkey newP; - const secp256k1_pubkey* pts2[2] = { &P, &Hi }; - if (!secp256k1_ec_pubkey_combine(ctx, &newP, pts2, 2)) goto fail; - P = newP; - } + /* Hi = termH * Hi */ + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &Hi, termH)) + goto fail; - OPENSSL_cleanse(tmp, 32); - OPENSSL_cleanse(termH, 32); - } + secp256k1_pubkey newP; + const secp256k1_pubkey *pts2[2] = {&P, &Hi}; + if (!secp256k1_ec_pubkey_combine(ctx, &newP, pts2, 2)) + goto fail; + P = newP; + } - OPENSSL_cleanse(z_j2, 32); + OPENSSL_cleanse(tmp, 32); + OPENSSL_cleanse(termH, 32); } + OPENSSL_cleanse(z_j2, 32); + } - /* P += (t_hat * ux) * U */ - { - unsigned char t_hat_ux[32]; - unsigned char zero32[32] = {0}; - secp256k1_mpt_scalar_mul(t_hat_ux, t_hat, ux_scalar); - - if (memcmp(t_hat_ux, zero32, 32) != 0) { - secp256k1_pubkey Q = U; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &Q, t_hat_ux)) { OPENSSL_cleanse(t_hat_ux,32); free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } - const secp256k1_pubkey* pts2[2] = { &P, &Q }; - secp256k1_pubkey newP; - if (!secp256k1_ec_pubkey_combine(ctx, &newP, pts2, 2)) goto fail; - P = newP; + /* P += (t_hat * ux) * U */ + { + unsigned char t_hat_ux[32]; + unsigned char zero32[32] = {0}; + secp256k1_mpt_scalar_mul(t_hat_ux, t_hat, ux_scalar); - } + if (memcmp(t_hat_ux, zero32, 32) != 0) + { + secp256k1_pubkey Q = U; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &Q, t_hat_ux)) + { OPENSSL_cleanse(t_hat_ux, 32); + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; + } + const secp256k1_pubkey *pts2[2] = {&P, &Q}; + secp256k1_pubkey newP; + if (!secp256k1_ec_pubkey_combine(ctx, &newP, pts2, 2)) + goto fail; + P = newP; } + OPENSSL_cleanse(t_hat_ux, 32); + } - /* P -= mu*pk_base */ + /* P -= mu*pk_base */ + { + unsigned char neg_mu[32]; + memcpy(neg_mu, mu, 32); + if (!secp256k1_ec_seckey_negate(ctx, neg_mu)) { - unsigned char neg_mu[32]; - memcpy(neg_mu, mu, 32); - if (!secp256k1_ec_seckey_negate(ctx, neg_mu)) { free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } - - secp256k1_pubkey mu_term = *pk_base; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &mu_term, neg_mu)) { OPENSSL_cleanse(neg_mu,32); free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } - OPENSSL_cleanse(neg_mu, 32); - - const secp256k1_pubkey* pts2[2] = { &P, &mu_term }; - secp256k1_pubkey newP; - if (!secp256k1_ec_pubkey_combine(ctx, &newP, pts2, 2)) goto fail; - P = newP; - + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; } - /* Build Hprime = y^{-k} * H_k (length n) */ - secp256k1_pubkey* Hprime = (secp256k1_pubkey*)malloc(n * sizeof(secp256k1_pubkey)); - if (!Hprime) { free(y_block_sum); free(y_powers); free(y_inv_powers); goto fail; } - - for (size_t k = 0; k < n; k++) { - Hprime[k] = H_vec[k]; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &Hprime[k], (const unsigned char*)(y_inv_powers + 32 * k))) { - free(Hprime); - free(y_block_sum); free(y_powers); free(y_inv_powers); - goto fail; - } - } - /* IPA verify */ - - int ok = ipa_verify_explicit( - ctx, - G_vec, - Hprime, - &U, - &P, - L_vec, - R_vec, - n, /* <-- add this */ - a_final, - b_final, - ux_scalar, - ipa_transcript_id - ); - - - free(Hprime); + secp256k1_pubkey mu_term = *pk_base; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &mu_term, neg_mu)) + { + OPENSSL_cleanse(neg_mu, 32); + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; + } + OPENSSL_cleanse(neg_mu, 32); + + const secp256k1_pubkey *pts2[2] = {&P, &mu_term}; + secp256k1_pubkey newP; + if (!secp256k1_ec_pubkey_combine(ctx, &newP, pts2, 2)) + goto fail; + P = newP; + } + + /* Build Hprime = y^{-k} * H_k (length n) */ + secp256k1_pubkey *Hprime = + (secp256k1_pubkey *)malloc(n * sizeof(secp256k1_pubkey)); + if (!Hprime) + { free(y_block_sum); free(y_powers); free(y_inv_powers); - free(L_vec); - free(R_vec); - - return ok ? 1 : 0; - - fail: - free(L_vec); - free(R_vec); - return 0; + goto fail; + } + + for (size_t k = 0; k < n; k++) + { + Hprime[k] = H_vec[k]; + if (!secp256k1_ec_pubkey_tweak_mul( + ctx, &Hprime[k], (const unsigned char *)(y_inv_powers + 32 * k))) + { + free(Hprime); + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + goto fail; + } + } + /* IPA verify */ + + int ok = ipa_verify_explicit(ctx, G_vec, Hprime, &U, &P, L_vec, R_vec, + n, /* <-- add this */ + a_final, b_final, ux_scalar, ipa_transcript_id); + + free(Hprime); + free(y_block_sum); + free(y_powers); + free(y_inv_powers); + free(L_vec); + free(R_vec); + + return ok ? 1 : 0; + +fail: + free(L_vec); + free(R_vec); + return 0; } diff --git a/src/commitments.c b/src/commitments.c index d54da91..61cd2ad 100644 --- a/src/commitments.c +++ b/src/commitments.c @@ -4,14 +4,14 @@ * * This module implements the core commitment scheme used in Confidential MPTs. * It provides functions to generate Pedersen commitments of the form - * \f$ C = v \cdot G + r \cdot H \f$, where \f$ G \f$ and \f$ H \f$ are independent - * generators of the secp256k1 curve. + * \f$ C = v \cdot G + r \cdot H \f$, where \f$ G \f$ and \f$ H \f$ are + * independent generators of the secp256k1 curve. * * @details * **NUMS Generators:** * To ensure the binding property of the commitments (i.e., that a user cannot - * open a commitment to two different values), the discrete logarithm of \f$ H \f$ - * with respect to \f$ G \f$ must be unknown. + * open a commitment to two different values), the discrete logarithm of \f$ H + * \f$ with respect to \f$ G \f$ must be unknown. * * This implementation uses a "Nothing-Up-My-Sleeve" (NUMS) construction: * - Generators are derived deterministically using SHA-256 hash-to-curve. @@ -20,23 +20,26 @@ * * **Commitment Logic:** * The commitment function handles the edge case where the amount \f$ v = 0 \f$. - * Since the point at infinity (identity) cannot be serialized in standard compressed form, - * the term \f$ 0 \cdot G \f$ is handled logically, resulting in \f$ C = r \cdot H \f$. + * Since the point at infinity (identity) cannot be serialized in standard + * compressed form, the term \f$ 0 \cdot G \f$ is handled logically, resulting + * in \f$ C = r \cdot H \f$. * - * @see [Spec (ConfidentialMPT_20260201.pdf) Section 3.3.5] Linking ElGamal Ciphertexts and Pedersen Commitments + * @see [Spec (ConfidentialMPT_20260201.pdf) Section 3.3.5] Linking ElGamal + * Ciphertexts and Pedersen Commitments */ #include "secp256k1_mpt.h" -#include -#include #include // For OPENSSL_cleanse +#include +#include /* --- Internal Helpers --- */ /** - * @brief Deterministically derives a NUMS (Nothing-Up-My-Sleeve) generator point. - * * Uses SHA-256 try-and-increment to find a valid x-coordinate. This ensures the - * discrete logarithm of the resulting point is unknown, which is - * a core security requirement for Bulletproof binding and vector commitments. + * @brief Deterministically derives a NUMS (Nothing-Up-My-Sleeve) generator + * point. + * * Uses SHA-256 try-and-increment to find a valid x-coordinate. This ensures + * the discrete logarithm of the resulting point is unknown, which is a core + * security requirement for Bulletproof binding and vector commitments. * * @param ctx secp256k1 context (VERIFY flag required). * @param out The derived public key generator. @@ -45,53 +48,53 @@ * @param index Vector index (enforced Big-Endian). * @return 1 on success, 0 on failure. */ -int secp256k1_mpt_hash_to_point_nums( - const secp256k1_context* ctx, - secp256k1_pubkey* out, - const unsigned char* label, - size_t label_len, - uint32_t index -) { - unsigned char hash[32]; - unsigned char compressed[33]; - uint32_t ctr = 0; - - unsigned char idx_be[4] = { - (unsigned char)(index >> 24), (unsigned char)(index >> 16), - (unsigned char)(index >> 8), (unsigned char)(index & 0xFF) - }; - - /* Try-and-increment loop */ - while (ctr < 0xFFFFFFFFu) { - unsigned char ctr_be[4] = { - (unsigned char)(ctr >> 24), (unsigned char)(ctr >> 16), - (unsigned char)(ctr >> 8), (unsigned char)(ctr & 0xFF) - }; - - SHA256_CTX sha; - SHA256_Init(&sha); - SHA256_Update(&sha, "MPT_BULLETPROOF_V1_NUMS", 23); // Domain Sep - SHA256_Update(&sha, "secp256k1", 9); // Curve Label - - if (label && label_len > 0) { - SHA256_Update(&sha, label, label_len); - } - - SHA256_Update(&sha, idx_be, 4); - SHA256_Update(&sha, ctr_be, 4); - SHA256_Final(hash, &sha); - - /* Construct compressed point candidate */ - compressed[0] = 0x02; /* Force even Y (standard convention for unique points) */ - memcpy(&compressed[1], hash, 32); - - /* Check validity on curve */ - if (secp256k1_ec_pubkey_parse(ctx, out, compressed, 33) == 1) { - return 1; - } - ctr++; +int secp256k1_mpt_hash_to_point_nums(const secp256k1_context *ctx, + secp256k1_pubkey *out, + const unsigned char *label, + size_t label_len, uint32_t index) +{ + unsigned char hash[32]; + unsigned char compressed[33]; + uint32_t ctr = 0; + + unsigned char idx_be[4] = { + (unsigned char)(index >> 24), (unsigned char)(index >> 16), + (unsigned char)(index >> 8), (unsigned char)(index & 0xFF)}; + + /* Try-and-increment loop */ + while (ctr < 0xFFFFFFFFu) + { + unsigned char ctr_be[4] = { + (unsigned char)(ctr >> 24), (unsigned char)(ctr >> 16), + (unsigned char)(ctr >> 8), (unsigned char)(ctr & 0xFF)}; + + SHA256_CTX sha; + SHA256_Init(&sha); + SHA256_Update(&sha, "MPT_BULLETPROOF_V1_NUMS", 23); // Domain Sep + SHA256_Update(&sha, "secp256k1", 9); // Curve Label + + if (label && label_len > 0) + { + SHA256_Update(&sha, label, label_len); } - return 0; // Extremely unlikely to reach here + + SHA256_Update(&sha, idx_be, 4); + SHA256_Update(&sha, ctr_be, 4); + SHA256_Final(hash, &sha); + + /* Construct compressed point candidate */ + compressed[0] = + 0x02; /* Force even Y (standard convention for unique points) */ + memcpy(&compressed[1], hash, 32); + + /* Check validity on curve */ + if (secp256k1_ec_pubkey_parse(ctx, out, compressed, 33) == 1) + { + return 1; + } + ctr++; + } + return 0; // Extremely unlikely to reach here } /** @@ -105,8 +108,11 @@ int secp256k1_mpt_hash_to_point_nums( * @param h The resulting H generator public key. * @return 1 on success, 0 on failure. */ -int secp256k1_mpt_get_h_generator(const secp256k1_context* ctx, secp256k1_pubkey* h) { - return secp256k1_mpt_hash_to_point_nums(ctx, h, (const unsigned char*)"H", 1, 0); +int secp256k1_mpt_get_h_generator(const secp256k1_context *ctx, + secp256k1_pubkey *h) +{ + return secp256k1_mpt_hash_to_point_nums(ctx, h, (const unsigned char *)"H", 1, + 0); } /** @@ -121,19 +127,19 @@ int secp256k1_mpt_get_h_generator(const secp256k1_context* ctx, secp256k1_pubkey * @param label_len Length of the label string. * @return 1 on success, 0 on failure. */ -int secp256k1_mpt_get_generator_vector( - const secp256k1_context* ctx, - secp256k1_pubkey* vec, - size_t n, - const unsigned char* label, - size_t label_len -) { - for (uint32_t i = 0; i < (uint32_t)n; i++) { - if (!secp256k1_mpt_hash_to_point_nums(ctx, &vec[i], label, label_len, i)) { - return 0; - } +int secp256k1_mpt_get_generator_vector(const secp256k1_context *ctx, + secp256k1_pubkey *vec, size_t n, + const unsigned char *label, + size_t label_len) +{ + for (uint32_t i = 0; i < (uint32_t)n; i++) + { + if (!secp256k1_mpt_hash_to_point_nums(ctx, &vec[i], label, label_len, i)) + { + return 0; } - return 1; + } + return 1; } /* --- Public API --- */ @@ -146,50 +152,55 @@ int secp256k1_mpt_get_generator_vector( * @param rho The blinding factor (randomness). * @return 1 on success, 0 on failure. */ -int secp256k1_mpt_pedersen_commit( - const secp256k1_context* ctx, - secp256k1_pubkey* commitment, - uint64_t amount, - const unsigned char* rho -) { - secp256k1_pubkey mG, rH, H; - unsigned char m_scalar[32] = {0}; - int ok = 0; - - /* 0. Input Check */ - if (!secp256k1_ec_seckey_verify(ctx, rho)) return 0; - - /* 1. Calculate rho*H (Blinding Term) */ - /* We do this first so we can use it directly if amount is 0 */ - if (!secp256k1_mpt_get_h_generator(ctx, &H)) return 0; - - rH = H; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &rH, rho)) return 0; - - /* 2. Handle Zero Amount Case */ - if (amount == 0) { - /* If m=0, C = 0*G + r*H = r*H */ - *commitment = rH; - return 1; - } - - /* 3. Calculate m*G (Value Term) */ - /* Convert uint64 amount to big-endian scalar */ - for (int i = 0; i < 8; i++) { - m_scalar[31 - i] = (amount >> (i * 8)) & 0xFF; - } - - /* This check passes now because we handled amount==0 above */ - if (!secp256k1_ec_pubkey_create(ctx, &mG, m_scalar)) goto cleanup; - - /* 4. Combine: C = mG + rH */ - const secp256k1_pubkey* points[2] = {&mG, &rH}; - if (!secp256k1_ec_pubkey_combine(ctx, commitment, points, 2)) goto cleanup; - - ok = 1; - - cleanup: - /* Securely clear the amount scalar from stack */ - OPENSSL_cleanse(m_scalar, 32); - return ok; +int secp256k1_mpt_pedersen_commit(const secp256k1_context *ctx, + secp256k1_pubkey *commitment, uint64_t amount, + const unsigned char *rho) +{ + secp256k1_pubkey mG, rH, H; + unsigned char m_scalar[32] = {0}; + int ok = 0; + + /* 0. Input Check */ + if (!secp256k1_ec_seckey_verify(ctx, rho)) + return 0; + + /* 1. Calculate rho*H (Blinding Term) */ + /* We do this first so we can use it directly if amount is 0 */ + if (!secp256k1_mpt_get_h_generator(ctx, &H)) + return 0; + + rH = H; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &rH, rho)) + return 0; + + /* 2. Handle Zero Amount Case */ + if (amount == 0) + { + /* If m=0, C = 0*G + r*H = r*H */ + *commitment = rH; + return 1; + } + + /* 3. Calculate m*G (Value Term) */ + /* Convert uint64 amount to big-endian scalar */ + for (int i = 0; i < 8; i++) + { + m_scalar[31 - i] = (amount >> (i * 8)) & 0xFF; + } + + /* This check passes now because we handled amount==0 above */ + if (!secp256k1_ec_pubkey_create(ctx, &mG, m_scalar)) + goto cleanup; + + /* 4. Combine: C = mG + rH */ + const secp256k1_pubkey *points[2] = {&mG, &rH}; + if (!secp256k1_ec_pubkey_combine(ctx, commitment, points, 2)) + goto cleanup; + + ok = 1; + +cleanup: + /* Securely clear the amount scalar from stack */ + OPENSSL_cleanse(m_scalar, 32); + return ok; } diff --git a/src/elgamal.c b/src/elgamal.c index 4f26fe0..186ba59 100644 --- a/src/elgamal.c +++ b/src/elgamal.c @@ -9,286 +9,321 @@ * @details * **Encryption Scheme:** * Given a public key \f$ Q = sk \cdot G \f$ and a plaintext amount \f$ m \f$, - * encryption with randomness \f$ r \f$ produces a ciphertext pair \f$ (C_1, C_2) \f$: + * encryption with randomness \f$ r \f$ produces a ciphertext pair \f$ (C_1, + * C_2) \f$: * - \f$ C_1 = r \cdot G \f$ (Ephemeral public key) * - \f$ C_2 = m \cdot G + r \cdot Q \f$ (Masked amount) * * **Homomorphism:** * The scheme is additively homomorphic: - * \f[ Enc(m_1) + Enc(m_2) = (C_{1,1}+C_{1,2}, C_{2,1}+C_{2,2}) = Enc(m_1 + m_2) \f] - * This allows validators to update balances (e.g., add incoming transfers) + * \f[ Enc(m_1) + Enc(m_2) = (C_{1,1}+C_{1,2}, C_{2,1}+C_{2,2}) = Enc(m_1 + m_2) + * \f] This allows validators to update balances (e.g., add incoming transfers) * without decrypting them. * * **Decryption (Discrete Logarithm):** * Decryption involves two steps: * 1. Remove the mask: \f$ M = C_2 - sk \cdot C_1 = m \cdot G \f$. - * 2. Recover \f$ m \f$ from \f$ M \f$: This requires solving the Discrete Logarithm - * Problem (DLP) for \f$ m \f$. Since balances are 64-bit integers but typically - * small in "human" terms, this implementation uses an optimized search - * for ranges relevant to transaction processing (e.g., 0 to 1,000,000). + * 2. Recover \f$ m \f$ from \f$ M \f$: This requires solving the Discrete + * Logarithm Problem (DLP) for \f$ m \f$. Since balances are 64-bit integers but + * typically small in "human" terms, this implementation uses an optimized + * search for ranges relevant to transaction processing (e.g., 0 to 1,000,000). * * **Canonical Zero:** - * To ensure deterministic ledger state for empty accounts, a "Canonical Encrypted Zero" - * is defined using randomness derived deterministically from the account ID and token ID. + * To ensure deterministic ledger state for empty accounts, a "Canonical + * Encrypted Zero" is defined using randomness derived deterministically from + * the account ID and token ID. * * @see [Spec (ConfidentialMPT_20260201.pdf) Section 3.2.2] ElGamal Encryption */ #include "secp256k1_mpt.h" #include #include -#include #include +#include /* --- Internal Helpers --- */ -static int pubkey_equal(const secp256k1_context* ctx, const secp256k1_pubkey* pk1, const secp256k1_pubkey* pk2) { - return secp256k1_ec_pubkey_cmp(ctx, pk1, pk2) == 0; +static int pubkey_equal(const secp256k1_context *ctx, + const secp256k1_pubkey *pk1, + const secp256k1_pubkey *pk2) +{ + return secp256k1_ec_pubkey_cmp(ctx, pk1, pk2) == 0; } -static int compute_amount_point(const secp256k1_context* ctx, secp256k1_pubkey* mG, uint64_t amount) { - unsigned char amount_scalar[32] = {0}; - int ret; - for (int i = 0; i < 8; ++i) { - amount_scalar[31 - i] = (amount >> (i * 8)) & 0xFF; - } - ret = secp256k1_ec_pubkey_create(ctx, mG, amount_scalar); - OPENSSL_cleanse(amount_scalar, 32); // Wipe scalar after use - return ret; +static int compute_amount_point(const secp256k1_context *ctx, + secp256k1_pubkey *mG, uint64_t amount) +{ + unsigned char amount_scalar[32] = {0}; + int ret; + for (int i = 0; i < 8; ++i) + { + amount_scalar[31 - i] = (amount >> (i * 8)) & 0xFF; + } + ret = secp256k1_ec_pubkey_create(ctx, mG, amount_scalar); + OPENSSL_cleanse(amount_scalar, 32); // Wipe scalar after use + return ret; } /* --- Key Generation --- */ -int secp256k1_elgamal_generate_keypair( - const secp256k1_context* ctx, - unsigned char* privkey, - secp256k1_pubkey* pubkey) +int secp256k1_elgamal_generate_keypair(const secp256k1_context *ctx, + unsigned char *privkey, + secp256k1_pubkey *pubkey) { - do { - if (RAND_bytes(privkey, 32) != 1) return 0; - } while (!secp256k1_ec_seckey_verify(ctx, privkey)); - - if (!secp256k1_ec_pubkey_create(ctx, pubkey, privkey)) { - OPENSSL_cleanse(privkey, 32); // Cleanup on failure - return 0; - } - return 1; + do + { + if (RAND_bytes(privkey, 32) != 1) + return 0; + } while (!secp256k1_ec_seckey_verify(ctx, privkey)); + + if (!secp256k1_ec_pubkey_create(ctx, pubkey, privkey)) + { + OPENSSL_cleanse(privkey, 32); // Cleanup on failure + return 0; + } + return 1; } /* --- Encryption --- */ -int secp256k1_elgamal_encrypt( - const secp256k1_context* ctx, - secp256k1_pubkey* c1, - secp256k1_pubkey* c2, - const secp256k1_pubkey* pubkey_Q, - uint64_t amount, - const unsigned char* blinding_factor -) { - secp256k1_pubkey S, mG; - const secp256k1_pubkey* pts[2]; - - /* 1. C1 = r * G */ - if (!secp256k1_ec_pubkey_create(ctx, c1, blinding_factor)) return 0; - - /* 2. S = r * Q (Shared Secret) */ - S = *pubkey_Q; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &S, blinding_factor)) return 0; - - /* 3. C2 = S + m*G */ - if (amount == 0) { - *c2 = S; // m*G is infinity, so C2 = S - } else { - if (!compute_amount_point(ctx, &mG, amount)) return 0; - pts[0] = &mG; pts[1] = &S; - if (!secp256k1_ec_pubkey_combine(ctx, c2, pts, 2)) return 0; - } - - return 1; +int secp256k1_elgamal_encrypt(const secp256k1_context *ctx, + secp256k1_pubkey *c1, secp256k1_pubkey *c2, + const secp256k1_pubkey *pubkey_Q, uint64_t amount, + const unsigned char *blinding_factor) +{ + secp256k1_pubkey S, mG; + const secp256k1_pubkey *pts[2]; + + /* 1. C1 = r * G */ + if (!secp256k1_ec_pubkey_create(ctx, c1, blinding_factor)) + return 0; + + /* 2. S = r * Q (Shared Secret) */ + S = *pubkey_Q; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &S, blinding_factor)) + return 0; + + /* 3. C2 = S + m*G */ + if (amount == 0) + { + *c2 = S; // m*G is infinity, so C2 = S + } + else + { + if (!compute_amount_point(ctx, &mG, amount)) + return 0; + pts[0] = &mG; + pts[1] = &S; + if (!secp256k1_ec_pubkey_combine(ctx, c2, pts, 2)) + return 0; + } + + return 1; } /* --- Decryption --- */ -int secp256k1_elgamal_decrypt( - const secp256k1_context* ctx, - uint64_t* amount, - const secp256k1_pubkey* c1, - const secp256k1_pubkey* c2, - const unsigned char* privkey -) { - secp256k1_pubkey S, M_target, current_M, G_point, next_M; - const secp256k1_pubkey* pts[2]; - uint64_t i; - unsigned char one[32] = {0}; one[31] = 1; - - /* 1. Recover Shared Secret: S = privkey * C1 */ - S = *c1; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &S, privkey)) return 0; - - /* 2. Check for Amount = 0 (C2 == S) */ - /* This is much faster than doing point subtraction first */ - if (pubkey_equal(ctx, c2, &S)) { - *amount = 0; - return 1; +int secp256k1_elgamal_decrypt(const secp256k1_context *ctx, uint64_t *amount, + const secp256k1_pubkey *c1, + const secp256k1_pubkey *c2, + const unsigned char *privkey) +{ + secp256k1_pubkey S, M_target, current_M, G_point, next_M; + const secp256k1_pubkey *pts[2]; + uint64_t i; + unsigned char one[32] = {0}; + one[31] = 1; + + /* 1. Recover Shared Secret: S = privkey * C1 */ + S = *c1; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &S, privkey)) + return 0; + + /* 2. Check for Amount = 0 (C2 == S) */ + /* This is much faster than doing point subtraction first */ + if (pubkey_equal(ctx, c2, &S)) + { + *amount = 0; + return 1; + } + + /* 3. Prepare Target: M_target = C2 - S */ + /* M_target = C2 + (-S) */ + if (!secp256k1_ec_pubkey_negate(ctx, &S)) + return 0; + pts[0] = c2; + pts[1] = &S; + if (!secp256k1_ec_pubkey_combine(ctx, &M_target, pts, 2)) + return 0; + + /* 4. Brute Force Search (1 to 1,000,000) */ + /* Optimization: Use point comparison, no serialization inside loop */ + + if (!secp256k1_ec_pubkey_create(ctx, &G_point, one)) + return 0; // G + current_M = G_point; // Start at 1*G + + for (i = 1; i <= 1000000; ++i) + { + // Fast comparison + if (pubkey_equal(ctx, ¤t_M, &M_target)) + { + *amount = i; + return 1; } - /* 3. Prepare Target: M_target = C2 - S */ - /* M_target = C2 + (-S) */ - if (!secp256k1_ec_pubkey_negate(ctx, &S)) return 0; - pts[0] = c2; pts[1] = &S; - if (!secp256k1_ec_pubkey_combine(ctx, &M_target, pts, 2)) return 0; - - /* 4. Brute Force Search (1 to 1,000,000) */ - /* Optimization: Use point comparison, no serialization inside loop */ - - if (!secp256k1_ec_pubkey_create(ctx, &G_point, one)) return 0; // G - current_M = G_point; // Start at 1*G - - for (i = 1; i <= 1000000; ++i) { - // Fast comparison - if (pubkey_equal(ctx, ¤t_M, &M_target)) { - *amount = i; - return 1; - } - - // Increment: current_M = current_M + G - pts[0] = ¤t_M; pts[1] = &G_point; - if (!secp256k1_ec_pubkey_combine(ctx, &next_M, pts, 2)) return 0; - current_M = next_M; - } + // Increment: current_M = current_M + G + pts[0] = ¤t_M; + pts[1] = &G_point; + if (!secp256k1_ec_pubkey_combine(ctx, &next_M, pts, 2)) + return 0; + current_M = next_M; + } - return 0; // Amount not found in range + return 0; // Amount not found in range } /* --- Homomorphic Operations --- */ -int secp256k1_elgamal_add( - const secp256k1_context* ctx, - secp256k1_pubkey* sum_c1, - secp256k1_pubkey* sum_c2, - const secp256k1_pubkey* a_c1, - const secp256k1_pubkey* a_c2, - const secp256k1_pubkey* b_c1, - const secp256k1_pubkey* b_c2 -) { - const secp256k1_pubkey* pts[2]; +int secp256k1_elgamal_add(const secp256k1_context *ctx, + secp256k1_pubkey *sum_c1, secp256k1_pubkey *sum_c2, + const secp256k1_pubkey *a_c1, + const secp256k1_pubkey *a_c2, + const secp256k1_pubkey *b_c1, + const secp256k1_pubkey *b_c2) +{ + const secp256k1_pubkey *pts[2]; - pts[0] = a_c1; pts[1] = b_c1; - if (!secp256k1_ec_pubkey_combine(ctx, sum_c1, pts, 2)) return 0; + pts[0] = a_c1; + pts[1] = b_c1; + if (!secp256k1_ec_pubkey_combine(ctx, sum_c1, pts, 2)) + return 0; - pts[0] = a_c2; pts[1] = b_c2; - if (!secp256k1_ec_pubkey_combine(ctx, sum_c2, pts, 2)) return 0; + pts[0] = a_c2; + pts[1] = b_c2; + if (!secp256k1_ec_pubkey_combine(ctx, sum_c2, pts, 2)) + return 0; - return 1; + return 1; } -int secp256k1_elgamal_subtract( - const secp256k1_context* ctx, - secp256k1_pubkey* diff_c1, - secp256k1_pubkey* diff_c2, - const secp256k1_pubkey* a_c1, - const secp256k1_pubkey* a_c2, - const secp256k1_pubkey* b_c1, - const secp256k1_pubkey* b_c2 -) { - secp256k1_pubkey neg_b_c1 = *b_c1; - secp256k1_pubkey neg_b_c2 = *b_c2; - const secp256k1_pubkey* pts[2]; - - if (!secp256k1_ec_pubkey_negate(ctx, &neg_b_c1)) return 0; - if (!secp256k1_ec_pubkey_negate(ctx, &neg_b_c2)) return 0; - - pts[0] = a_c1; pts[1] = &neg_b_c1; - if (!secp256k1_ec_pubkey_combine(ctx, diff_c1, pts, 2)) return 0; - - pts[0] = a_c2; pts[1] = &neg_b_c2; - if (!secp256k1_ec_pubkey_combine(ctx, diff_c2, pts, 2)) return 0; - - return 1; +int secp256k1_elgamal_subtract(const secp256k1_context *ctx, + secp256k1_pubkey *diff_c1, + secp256k1_pubkey *diff_c2, + const secp256k1_pubkey *a_c1, + const secp256k1_pubkey *a_c2, + const secp256k1_pubkey *b_c1, + const secp256k1_pubkey *b_c2) +{ + secp256k1_pubkey neg_b_c1 = *b_c1; + secp256k1_pubkey neg_b_c2 = *b_c2; + const secp256k1_pubkey *pts[2]; + + if (!secp256k1_ec_pubkey_negate(ctx, &neg_b_c1)) + return 0; + if (!secp256k1_ec_pubkey_negate(ctx, &neg_b_c2)) + return 0; + + pts[0] = a_c1; + pts[1] = &neg_b_c1; + if (!secp256k1_ec_pubkey_combine(ctx, diff_c1, pts, 2)) + return 0; + + pts[0] = a_c2; + pts[1] = &neg_b_c2; + if (!secp256k1_ec_pubkey_combine(ctx, diff_c2, pts, 2)) + return 0; + + return 1; } /* --- Canonical Encrypted Zero --- */ int generate_canonical_encrypted_zero( - const secp256k1_context* ctx, - secp256k1_pubkey* enc_zero_c1, - secp256k1_pubkey* enc_zero_c2, - const secp256k1_pubkey* pubkey, - const unsigned char* account_id, // 20 bytes - const unsigned char* mpt_issuance_id // 24 bytes -) { - unsigned char deterministic_scalar[32]; - unsigned char hash_input[51]; // 7 ("EncZero") + 20 + 24 - const char* domain = "EncZero"; - int ret; - SHA256_CTX sha; - - // Build static buffer part - memcpy(hash_input, domain, 7); - memcpy(hash_input + 7, account_id, 20); - memcpy(hash_input + 27, mpt_issuance_id, 24); - - /* Rejection sampling loop to ensure scalar is valid */ - do { - SHA256(hash_input, 51, deterministic_scalar); - - // If invalid, re-hash the hash (standard chain method for determinism) - // Or simply fail if strict canonical behavior is required. - // Assuming rejection sampling is the intended design for safety: - if (secp256k1_ec_seckey_verify(ctx, deterministic_scalar)) break; - - // Update input for next iteration to get new hash - // (Note: The original code just looped SHA256 on same input which is static, - // so it would loop forever if the first hash was invalid. - // Fixed here by re-hashing the output if needed, though highly unlikely to fail). - memcpy(hash_input, deterministic_scalar, 32); - - } while (1); - - ret = secp256k1_elgamal_encrypt( - ctx, - enc_zero_c1, - enc_zero_c2, - pubkey, - 0, - deterministic_scalar - ); - - OPENSSL_cleanse(deterministic_scalar, 32); // Secure cleanup - return ret; + const secp256k1_context *ctx, secp256k1_pubkey *enc_zero_c1, + secp256k1_pubkey *enc_zero_c2, const secp256k1_pubkey *pubkey, + const unsigned char *account_id, // 20 bytes + const unsigned char *mpt_issuance_id // 24 bytes +) +{ + unsigned char deterministic_scalar[32]; + unsigned char hash_input[51]; // 7 ("EncZero") + 20 + 24 + const char *domain = "EncZero"; + int ret; + SHA256_CTX sha; + + // Build static buffer part + memcpy(hash_input, domain, 7); + memcpy(hash_input + 7, account_id, 20); + memcpy(hash_input + 27, mpt_issuance_id, 24); + + /* Rejection sampling loop to ensure scalar is valid */ + do + { + SHA256(hash_input, 51, deterministic_scalar); + + // If invalid, re-hash the hash (standard chain method for determinism) + // Or simply fail if strict canonical behavior is required. + // Assuming rejection sampling is the intended design for safety: + if (secp256k1_ec_seckey_verify(ctx, deterministic_scalar)) + break; + + // Update input for next iteration to get new hash + // (Note: The original code just looped SHA256 on same input which is + // static, so it would loop forever if the first hash was invalid. Fixed + // here by re-hashing the output if needed, though highly unlikely to fail). + memcpy(hash_input, deterministic_scalar, 32); + + } while (1); + + ret = secp256k1_elgamal_encrypt(ctx, enc_zero_c1, enc_zero_c2, pubkey, 0, + deterministic_scalar); + + OPENSSL_cleanse(deterministic_scalar, 32); // Secure cleanup + return ret; } /* --- Direct Verification (Convert) --- */ -int secp256k1_elgamal_verify_encryption( - const secp256k1_context* ctx, - const secp256k1_pubkey* c1, - const secp256k1_pubkey* c2, - const secp256k1_pubkey* pubkey_Q, - uint64_t amount, - const unsigned char* blinding_factor) +int secp256k1_elgamal_verify_encryption(const secp256k1_context *ctx, + const secp256k1_pubkey *c1, + const secp256k1_pubkey *c2, + const secp256k1_pubkey *pubkey_Q, + uint64_t amount, + const unsigned char *blinding_factor) { - secp256k1_pubkey expected_c1, expected_c2, mG, S; - const secp256k1_pubkey* pts[2]; - - /* 1. Verify C1 == r * G */ - if (!secp256k1_ec_pubkey_create(ctx, &expected_c1, blinding_factor)) return 0; - if (!pubkey_equal(ctx, c1, &expected_c1)) return 0; - - /* 2. Verify C2 == r*Q + m*G */ - - // S = r * Q - S = *pubkey_Q; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &S, blinding_factor)) return 0; - - if (amount == 0) { - expected_c2 = S; - } else { - if (!compute_amount_point(ctx, &mG, amount)) return 0; - pts[0] = &mG; pts[1] = &S; - if (!secp256k1_ec_pubkey_combine(ctx, &expected_c2, pts, 2)) return 0; - } - - if (!pubkey_equal(ctx, c2, &expected_c2)) return 0; - - return 1; + secp256k1_pubkey expected_c1, expected_c2, mG, S; + const secp256k1_pubkey *pts[2]; + + /* 1. Verify C1 == r * G */ + if (!secp256k1_ec_pubkey_create(ctx, &expected_c1, blinding_factor)) + return 0; + if (!pubkey_equal(ctx, c1, &expected_c1)) + return 0; + + /* 2. Verify C2 == r*Q + m*G */ + + // S = r * Q + S = *pubkey_Q; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &S, blinding_factor)) + return 0; + + if (amount == 0) + { + expected_c2 = S; + } + else + { + if (!compute_amount_point(ctx, &mG, amount)) + return 0; + pts[0] = &mG; + pts[1] = &S; + if (!secp256k1_ec_pubkey_combine(ctx, &expected_c2, pts, 2)) + return 0; + } + + if (!pubkey_equal(ctx, c2, &expected_c2)) + return 0; + + return 1; } diff --git a/src/equality_proof.c b/src/equality_proof.c index 44ed4f8..956f6e0 100644 --- a/src/equality_proof.c +++ b/src/equality_proof.c @@ -1,255 +1,311 @@ /** -* @file equality_proof.c -* @brief Zero-Knowledge Proof of Knowledge of Plaintext and Randomness. -* -* This module implements a Sigma protocol (Chaum-Pedersen style) to prove that - * an ElGamal ciphertext \f$ (C_1, C_2) \f$ encrypts a specific known plaintext -* \f$ m \f$ under a public key \f$ P \f$, and that the prover knows the -* randomness \f$ r \f$ used in the encryption. -* -* @details - * **Statement:** -* The prover demonstrates knowledge of \f$ r \in \mathbb{Z}_q \f$ such that: -* \f[ C_1 = r \cdot G \f] -* \f[ C_2 = m \cdot G + r \cdot P \f] -* -* **Protocol:** -* 1. **Commitment:** -* Prover samples \f$ t \leftarrow \mathbb{Z}_q \f$ and computes: -* \f[ T_1 = t \cdot G \f] -* \f[ T_2 = t \cdot P \f] -* -* 2. **Challenge:** -* \f[ e = H(\text{"MPT_POK_PLAINTEXT_PROOF"} \parallel C_1 \parallel C_2 \parallel P \parallel T_1 \parallel T_2 \parallel \dots) \f] -* -* 3. **Response:** -* \f[ s = t + e \cdot r \pmod{q} \f] -* -* 4. **Verification:** -* Verifier checks: -* \f[ s \cdot G \stackrel{?}{=} T_1 + e \cdot C_1 \f] -* \f[ s \cdot P \stackrel{?}{=} T_2 + e \cdot (C_2 - m \cdot G) \f] -* -* **Context:** -* This proof is used in `ConfidentialMPTConvert` (explicit randomness verification) -* and `ConfidentialMPTClawback` (where the issuer proves the ciphertext matches - * a revealed amount using their secret key, a variant of this logic). -* -* @see [Spec (ConfidentialMPT_20260201.pdf) Section 3.3.3] Optimized Ciphertext-Amount Consistency Protocol -*/ + * @file equality_proof.c + * @brief Zero-Knowledge Proof of Knowledge of Plaintext and Randomness. + * + * This module implements a Sigma protocol (Chaum-Pedersen style) to prove that + * an ElGamal ciphertext \f$ (C_1, C_2) \f$ encrypts a specific known plaintext + * \f$ m \f$ under a public key \f$ P \f$, and that the prover knows the + * randomness \f$ r \f$ used in the encryption. + * + * @details + * **Statement:** + * The prover demonstrates knowledge of \f$ r \in \mathbb{Z}_q \f$ such that: + * \f[ C_1 = r \cdot G \f] + * \f[ C_2 = m \cdot G + r \cdot P \f] + * + * **Protocol:** + * 1. **Commitment:** + * Prover samples \f$ t \leftarrow \mathbb{Z}_q \f$ and computes: + * \f[ T_1 = t \cdot G \f] + * \f[ T_2 = t \cdot P \f] + * + * 2. **Challenge:** + * \f[ e = H(\text{"MPT_POK_PLAINTEXT_PROOF"} \parallel C_1 \parallel C_2 + * \parallel P \parallel T_1 \parallel T_2 \parallel \dots) \f] + * + * 3. **Response:** + * \f[ s = t + e \cdot r \pmod{q} \f] + * + * 4. **Verification:** + * Verifier checks: + * \f[ s \cdot G \stackrel{?}{=} T_1 + e \cdot C_1 \f] + * \f[ s \cdot P \stackrel{?}{=} T_2 + e \cdot (C_2 - m \cdot G) \f] + * + * **Context:** + * This proof is used in `ConfidentialMPTConvert` (explicit randomness + * verification) and `ConfidentialMPTClawback` (where the issuer proves the + * ciphertext matches a revealed amount using their secret key, a variant of + * this logic). + * + * @see [Spec (ConfidentialMPT_20260201.pdf) Section 3.3.3] Optimized + * Ciphertext-Amount Consistency Protocol + */ #include "secp256k1_mpt.h" -#include #include -#include +#include #include +#include /* --- Internal Helpers --- */ -static int pubkey_equal(const secp256k1_context* ctx, const secp256k1_pubkey* pk1, const secp256k1_pubkey* pk2) { - return secp256k1_ec_pubkey_cmp(ctx, pk1, pk2) == 0; +static int pubkey_equal(const secp256k1_context *ctx, + const secp256k1_pubkey *pk1, + const secp256k1_pubkey *pk2) +{ + return secp256k1_ec_pubkey_cmp(ctx, pk1, pk2) == 0; } -static int generate_random_scalar(const secp256k1_context* ctx, unsigned char* scalar) { - do { - if (RAND_bytes(scalar, 32) != 1) return 0; - } while (!secp256k1_ec_seckey_verify(ctx, scalar)); - return 1; +static int generate_random_scalar(const secp256k1_context *ctx, + unsigned char *scalar) +{ + do + { + if (RAND_bytes(scalar, 32) != 1) + return 0; + } while (!secp256k1_ec_seckey_verify(ctx, scalar)); + return 1; } -static int compute_amount_point(const secp256k1_context* ctx, secp256k1_pubkey* mG, uint64_t amount) { - unsigned char amount_scalar[32] = {0}; - /* Convert amount to 32-byte BIG-ENDIAN scalar */ - for (int i = 0; i < 8; ++i) { - amount_scalar[31 - i] = (amount >> (i * 8)) & 0xFF; - } - return secp256k1_ec_pubkey_create(ctx, mG, amount_scalar); +static int compute_amount_point(const secp256k1_context *ctx, + secp256k1_pubkey *mG, uint64_t amount) +{ + unsigned char amount_scalar[32] = {0}; + /* Convert amount to 32-byte BIG-ENDIAN scalar */ + for (int i = 0; i < 8; ++i) + { + amount_scalar[31 - i] = (amount >> (i * 8)) & 0xFF; + } + return secp256k1_ec_pubkey_create(ctx, mG, amount_scalar); } /** * Streaming Hash Builder (Avoids large stack buffers) */ static void compute_challenge_equality( - const secp256k1_context* ctx, - unsigned char* e_out, - const secp256k1_pubkey* c1, const secp256k1_pubkey* c2, - const secp256k1_pubkey* pk, - const secp256k1_pubkey* mG, /* NULL if amount == 0 */ - const secp256k1_pubkey* T1, const secp256k1_pubkey* T2, - const unsigned char* tx_context_id) + const secp256k1_context *ctx, unsigned char *e_out, + const secp256k1_pubkey *c1, const secp256k1_pubkey *c2, + const secp256k1_pubkey *pk, + const secp256k1_pubkey *mG, /* NULL if amount == 0 */ + const secp256k1_pubkey *T1, const secp256k1_pubkey *T2, + const unsigned char *tx_context_id) { - SHA256_CTX sha; - unsigned char buf[33]; - unsigned char h[32]; - size_t len; - const char* domain = "MPT_POK_PLAINTEXT_PROOF"; - - SHA256_Init(&sha); - SHA256_Update(&sha, domain, strlen(domain)); - - // C1, C2, Pk - len = 33; secp256k1_ec_pubkey_serialize(ctx, buf, &len, c1, SECP256K1_EC_COMPRESSED); SHA256_Update(&sha, buf, 33); - len = 33; secp256k1_ec_pubkey_serialize(ctx, buf, &len, c2, SECP256K1_EC_COMPRESSED); SHA256_Update(&sha, buf, 33); - len = 33; secp256k1_ec_pubkey_serialize(ctx, buf, &len, pk, SECP256K1_EC_COMPRESSED); SHA256_Update(&sha, buf, 33); - - // mG (Only if nonzero, logic from original code implied this structure) - // Note: The original code had two separate functions. We unify them here. - if (mG) { - len = 33; secp256k1_ec_pubkey_serialize(ctx, buf, &len, mG, SECP256K1_EC_COMPRESSED); SHA256_Update(&sha, buf, 33); - } - - // T1, T2 - len = 33; secp256k1_ec_pubkey_serialize(ctx, buf, &len, T1, SECP256K1_EC_COMPRESSED); SHA256_Update(&sha, buf, 33); - len = 33; secp256k1_ec_pubkey_serialize(ctx, buf, &len, T2, SECP256K1_EC_COMPRESSED); SHA256_Update(&sha, buf, 33); - - if (tx_context_id) { - SHA256_Update(&sha, tx_context_id, 32); - } - - SHA256_Final(h, &sha); - secp256k1_mpt_scalar_reduce32(e_out, h); + SHA256_CTX sha; + unsigned char buf[33]; + unsigned char h[32]; + size_t len; + const char *domain = "MPT_POK_PLAINTEXT_PROOF"; + + SHA256_Init(&sha); + SHA256_Update(&sha, domain, strlen(domain)); + + // C1, C2, Pk + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, c1, SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, c2, SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, pk, SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); + + // mG (Only if nonzero, logic from original code implied this structure) + // Note: The original code had two separate functions. We unify them here. + if (mG) + { + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, mG, SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); + } + + // T1, T2 + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, T1, SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, T2, SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); + + if (tx_context_id) + { + SHA256_Update(&sha, tx_context_id, 32); + } + + SHA256_Final(h, &sha); + secp256k1_mpt_scalar_reduce32(e_out, h); } /* --- Public API --- */ int secp256k1_equality_plaintext_prove( - const secp256k1_context* ctx, - unsigned char* proof, - const secp256k1_pubkey* c1, - const secp256k1_pubkey* c2, - const secp256k1_pubkey* pk_recipient, - uint64_t amount, - const unsigned char* randomness_r, - const unsigned char* tx_context_id) + const secp256k1_context *ctx, unsigned char *proof, + const secp256k1_pubkey *c1, const secp256k1_pubkey *c2, + const secp256k1_pubkey *pk_recipient, uint64_t amount, + const unsigned char *randomness_r, const unsigned char *tx_context_id) { - unsigned char t[32]; - unsigned char e[32]; - unsigned char s[32]; - unsigned char term[32]; - secp256k1_pubkey T1, T2; - secp256k1_pubkey mG; - secp256k1_pubkey* mG_ptr = NULL; - size_t len; - int ok = 0; - - /* 0. Validate witness */ - if (!secp256k1_ec_seckey_verify(ctx, randomness_r)) goto cleanup; - - /* 1. Generate random t */ - if (!generate_random_scalar(ctx, t)) goto cleanup; - - /* 2. Compute commitments T1 = t*G, T2 = t*Pk */ - if (!secp256k1_ec_pubkey_create(ctx, &T1, t)) goto cleanup; - - T2 = *pk_recipient; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &T2, t)) goto cleanup; - - /* 3. Compute Challenge */ - if (amount > 0) { - if (!compute_amount_point(ctx, &mG, amount)) goto cleanup; - mG_ptr = &mG; - } - compute_challenge_equality(ctx, e, c1, c2, pk_recipient, mG_ptr, &T1, &T2, tx_context_id); - - /* 4. Compute s = t + e * r */ - memcpy(s, t, 32); - memcpy(term, randomness_r, 32); - if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) goto cleanup; // term = e*r - if (!secp256k1_ec_seckey_tweak_add(ctx, s, term)) goto cleanup; // s = t + e*r - - /* 5. Serialize Proof */ - unsigned char* ptr = proof; - len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T1, SECP256K1_EC_COMPRESSED)) goto cleanup; - ptr += 33; - - len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T2, SECP256K1_EC_COMPRESSED)) goto cleanup; - ptr += 33; - - memcpy(ptr, s, 32); - - ok = 1; - - cleanup: - OPENSSL_cleanse(t, 32); - OPENSSL_cleanse(term, 32); - // s is public output, but good practice to clean stack copy - OPENSSL_cleanse(s, 32); - return ok; + unsigned char t[32]; + unsigned char e[32]; + unsigned char s[32]; + unsigned char term[32]; + secp256k1_pubkey T1, T2; + secp256k1_pubkey mG; + secp256k1_pubkey *mG_ptr = NULL; + size_t len; + int ok = 0; + + /* 0. Validate witness */ + if (!secp256k1_ec_seckey_verify(ctx, randomness_r)) + goto cleanup; + + /* 1. Generate random t */ + if (!generate_random_scalar(ctx, t)) + goto cleanup; + + /* 2. Compute commitments T1 = t*G, T2 = t*Pk */ + if (!secp256k1_ec_pubkey_create(ctx, &T1, t)) + goto cleanup; + + T2 = *pk_recipient; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &T2, t)) + goto cleanup; + + /* 3. Compute Challenge */ + if (amount > 0) + { + if (!compute_amount_point(ctx, &mG, amount)) + goto cleanup; + mG_ptr = &mG; + } + compute_challenge_equality(ctx, e, c1, c2, pk_recipient, mG_ptr, &T1, &T2, + tx_context_id); + + /* 4. Compute s = t + e * r */ + memcpy(s, t, 32); + memcpy(term, randomness_r, 32); + if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) + goto cleanup; // term = e*r + if (!secp256k1_ec_seckey_tweak_add(ctx, s, term)) + goto cleanup; // s = t + e*r + + /* 5. Serialize Proof */ + unsigned char *ptr = proof; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T1, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + ptr += 33; + + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T2, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + ptr += 33; + + memcpy(ptr, s, 32); + + ok = 1; + +cleanup: + OPENSSL_cleanse(t, 32); + OPENSSL_cleanse(term, 32); + // s is public output, but good practice to clean stack copy + OPENSSL_cleanse(s, 32); + return ok; } -int secp256k1_equality_plaintext_verify( - const secp256k1_context* ctx, - const unsigned char* proof, - const secp256k1_pubkey* c1, - const secp256k1_pubkey* c2, - const secp256k1_pubkey* pk_recipient, - uint64_t amount, - const unsigned char* tx_context_id) +int secp256k1_equality_plaintext_verify(const secp256k1_context *ctx, + const unsigned char *proof, + const secp256k1_pubkey *c1, + const secp256k1_pubkey *c2, + const secp256k1_pubkey *pk_recipient, + uint64_t amount, + const unsigned char *tx_context_id) { - secp256k1_pubkey T1, T2; - unsigned char s[32]; - unsigned char e[32]; - secp256k1_pubkey mG; - secp256k1_pubkey* mG_ptr = NULL; - const unsigned char* ptr = proof; - - secp256k1_pubkey LHS, RHS, term; - const secp256k1_pubkey* pts[2]; - int ok = 0; // Default to failure - - /* 1. Deserialize */ - if (!secp256k1_ec_pubkey_parse(ctx, &T1, ptr, 33)) goto cleanup; ptr += 33; - if (!secp256k1_ec_pubkey_parse(ctx, &T2, ptr, 33)) goto cleanup; ptr += 33; - memcpy(s, ptr, 32); - - // Check s != 0 - if (!secp256k1_ec_seckey_verify(ctx, s)) goto cleanup; - - /* 2. Recompute Challenge */ - if (amount > 0) { - if (!compute_amount_point(ctx, &mG, amount)) goto cleanup; - mG_ptr = &mG; - } - compute_challenge_equality(ctx, e, c1, c2, pk_recipient, mG_ptr, &T1, &T2, tx_context_id); - - /* 3. Verify Equations */ - - /* --- Eq 1: s * G == T1 + e * C1 --- */ - if (!secp256k1_ec_pubkey_create(ctx, &LHS, s)) goto cleanup; // s*G - - term = *c1; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) goto cleanup; // e*C1 - pts[0] = &T1; pts[1] = &term; - if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) goto cleanup; // T1 + e*C1 - - if (!pubkey_equal(ctx, &LHS, &RHS)) goto cleanup; - - /* --- Eq 2: s * Pk == T2 + e * (C2 - mG) --- */ - - /* LHS = s * Pk */ - LHS = *pk_recipient; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &LHS, s)) goto cleanup; - - /* RHS Construction: Y = C2 - mG */ - secp256k1_pubkey Y = *c2; - if (mG_ptr) { - secp256k1_pubkey neg_mG = *mG_ptr; - if (!secp256k1_ec_pubkey_negate(ctx, &neg_mG)) goto cleanup; - pts[0] = c2; pts[1] = &neg_mG; - if (!secp256k1_ec_pubkey_combine(ctx, &Y, pts, 2)) goto cleanup; - } - - /* RHS = T2 + e * Y */ - term = Y; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) goto cleanup; // e*Y - pts[0] = &T2; pts[1] = &term; - if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) goto cleanup; - - if (!pubkey_equal(ctx, &LHS, &RHS)) goto cleanup; - - ok = 1; - - cleanup: - return ok; + secp256k1_pubkey T1, T2; + unsigned char s[32]; + unsigned char e[32]; + secp256k1_pubkey mG; + secp256k1_pubkey *mG_ptr = NULL; + const unsigned char *ptr = proof; + + secp256k1_pubkey LHS, RHS, term; + const secp256k1_pubkey *pts[2]; + int ok = 0; // Default to failure + + /* 1. Deserialize */ + if (!secp256k1_ec_pubkey_parse(ctx, &T1, ptr, 33)) + goto cleanup; + ptr += 33; + if (!secp256k1_ec_pubkey_parse(ctx, &T2, ptr, 33)) + goto cleanup; + ptr += 33; + memcpy(s, ptr, 32); + + // Check s != 0 + if (!secp256k1_ec_seckey_verify(ctx, s)) + goto cleanup; + + /* 2. Recompute Challenge */ + if (amount > 0) + { + if (!compute_amount_point(ctx, &mG, amount)) + goto cleanup; + mG_ptr = &mG; + } + compute_challenge_equality(ctx, e, c1, c2, pk_recipient, mG_ptr, &T1, &T2, + tx_context_id); + + /* 3. Verify Equations */ + + /* --- Eq 1: s * G == T1 + e * C1 --- */ + if (!secp256k1_ec_pubkey_create(ctx, &LHS, s)) + goto cleanup; // s*G + + term = *c1; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) + goto cleanup; // e*C1 + pts[0] = &T1; + pts[1] = &term; + if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) + goto cleanup; // T1 + e*C1 + + if (!pubkey_equal(ctx, &LHS, &RHS)) + goto cleanup; + + /* --- Eq 2: s * Pk == T2 + e * (C2 - mG) --- */ + + /* LHS = s * Pk */ + LHS = *pk_recipient; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &LHS, s)) + goto cleanup; + + /* RHS Construction: Y = C2 - mG */ + secp256k1_pubkey Y = *c2; + if (mG_ptr) + { + secp256k1_pubkey neg_mG = *mG_ptr; + if (!secp256k1_ec_pubkey_negate(ctx, &neg_mG)) + goto cleanup; + pts[0] = c2; + pts[1] = &neg_mG; + if (!secp256k1_ec_pubkey_combine(ctx, &Y, pts, 2)) + goto cleanup; + } + + /* RHS = T2 + e * Y */ + term = Y; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) + goto cleanup; // e*Y + pts[0] = &T2; + pts[1] = &term; + if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) + goto cleanup; + + if (!pubkey_equal(ctx, &LHS, &RHS)) + goto cleanup; + + ok = 1; + +cleanup: + return ok; } diff --git a/src/mpt_scalar.c b/src/mpt_scalar.c index 9e9d388..08f4381 100644 --- a/src/mpt_scalar.c +++ b/src/mpt_scalar.c @@ -3,18 +3,20 @@ * @brief Scalar Field Arithmetic Abstraction Layer. * * This module provides a safe, portable interface for performing arithmetic - * in the scalar field of the secp256k1 curve (integers modulo \f$ n \f$, the group order). + * in the scalar field of the secp256k1 curve (integers modulo \f$ n \f$, the + * group order). * * @details * **Purpose:** * While `libsecp256k1` exposes point operations via its public API, it does not - * typically expose low-level scalar arithmetic. However, protocols like Bulletproofs - * and ElGamal require extensive scalar math (e.g., polynomial evaluation, inner products) - * to be performed by the client. + * typically expose low-level scalar arithmetic. However, protocols like + * Bulletproofs and ElGamal require extensive scalar math (e.g., polynomial + * evaluation, inner products) to be performed by the client. * * **Implementation:** - * This file includes internal `libsecp256k1` headers (`scalar.h`, `scalar_impl.h`) - * to access the optimized, constant-time scalar implementations. + * This file includes internal `libsecp256k1` headers (`scalar.h`, + * `scalar_impl.h`) to access the optimized, constant-time scalar + * implementations. * * **Operations:** * All operations are performed modulo the curve order \f$ n \f$: @@ -24,25 +26,24 @@ * - Negation: \f$ -a \pmod{n} \f$ * * **Platform Specifics:** - * Includes logic for 128-bit integer support (`int128.h`) required for efficient - * computation on modern architectures (e.g., ARM64/Apple Silicon). + * Includes logic for 128-bit integer support (`int128.h`) required for + * efficient computation on modern architectures (e.g., ARM64/Apple Silicon). * - * @warning These functions operate on 32-byte big-endian scalars. Inputs must be - * properly reduced or handled by `secp256k1_mpt_scalar_reduce32` before use if they - * might exceed \f$ n \f$. + * @warning These functions operate on 32-byte big-endian scalars. Inputs must + * be properly reduced or handled by `secp256k1_mpt_scalar_reduce32` before use + * if they might exceed \f$ n \f$. */ #include "secp256k1_mpt.h" -#include #include - +#include /* Include low-level utilities first. On ARM64/Apple Silicon, the scalar math depends on 128-bit integer helpers defined in these headers. */ -#include #include #include +#include /* Include the actual scalar implementations */ #include @@ -50,57 +51,65 @@ /* --- Implementation --- */ -void secp256k1_mpt_scalar_add(unsigned char *res, const unsigned char *a, const unsigned char *b) { - secp256k1_scalar s_res, s_a, s_b; - secp256k1_scalar_set_b32(&s_a, a, NULL); - secp256k1_scalar_set_b32(&s_b, b, NULL); - secp256k1_scalar_add(&s_res, &s_a, &s_b); - secp256k1_scalar_get_b32(res, &s_res); +void secp256k1_mpt_scalar_add(unsigned char *res, const unsigned char *a, + const unsigned char *b) +{ + secp256k1_scalar s_res, s_a, s_b; + secp256k1_scalar_set_b32(&s_a, a, NULL); + secp256k1_scalar_set_b32(&s_b, b, NULL); + secp256k1_scalar_add(&s_res, &s_a, &s_b); + secp256k1_scalar_get_b32(res, &s_res); - /* SECURE CLEANUP */ - OPENSSL_cleanse(&s_a, sizeof(s_a)); - OPENSSL_cleanse(&s_b, sizeof(s_b)); - OPENSSL_cleanse(&s_res, sizeof(s_res)); + /* SECURE CLEANUP */ + OPENSSL_cleanse(&s_a, sizeof(s_a)); + OPENSSL_cleanse(&s_b, sizeof(s_b)); + OPENSSL_cleanse(&s_res, sizeof(s_res)); } -void secp256k1_mpt_scalar_mul(unsigned char *res, const unsigned char *a, const unsigned char *b) { - secp256k1_scalar s_res, s_a, s_b; - secp256k1_scalar_set_b32(&s_a, a, NULL); - secp256k1_scalar_set_b32(&s_b, b, NULL); - secp256k1_scalar_mul(&s_res, &s_a, &s_b); - secp256k1_scalar_get_b32(res, &s_res); +void secp256k1_mpt_scalar_mul(unsigned char *res, const unsigned char *a, + const unsigned char *b) +{ + secp256k1_scalar s_res, s_a, s_b; + secp256k1_scalar_set_b32(&s_a, a, NULL); + secp256k1_scalar_set_b32(&s_b, b, NULL); + secp256k1_scalar_mul(&s_res, &s_a, &s_b); + secp256k1_scalar_get_b32(res, &s_res); - /* SECURE CLEANUP */ - OPENSSL_cleanse(&s_a, sizeof(s_a)); - OPENSSL_cleanse(&s_b, sizeof(s_b)); - OPENSSL_cleanse(&s_res, sizeof(s_res)); + /* SECURE CLEANUP */ + OPENSSL_cleanse(&s_a, sizeof(s_a)); + OPENSSL_cleanse(&s_b, sizeof(s_b)); + OPENSSL_cleanse(&s_res, sizeof(s_res)); } -void secp256k1_mpt_scalar_inverse(unsigned char *res, const unsigned char *in) { - secp256k1_scalar s; - secp256k1_scalar_set_b32(&s, in, NULL); - secp256k1_scalar_inverse(&s, &s); - secp256k1_scalar_get_b32(res, &s); +void secp256k1_mpt_scalar_inverse(unsigned char *res, const unsigned char *in) +{ + secp256k1_scalar s; + secp256k1_scalar_set_b32(&s, in, NULL); + secp256k1_scalar_inverse(&s, &s); + secp256k1_scalar_get_b32(res, &s); - /* SECURE CLEANUP */ - OPENSSL_cleanse(&s, sizeof(s)); + /* SECURE CLEANUP */ + OPENSSL_cleanse(&s, sizeof(s)); } -void secp256k1_mpt_scalar_negate(unsigned char *res, const unsigned char *in) { - secp256k1_scalar s; - secp256k1_scalar_set_b32(&s, in, NULL); - secp256k1_scalar_negate(&s, &s); - secp256k1_scalar_get_b32(res, &s); +void secp256k1_mpt_scalar_negate(unsigned char *res, const unsigned char *in) +{ + secp256k1_scalar s; + secp256k1_scalar_set_b32(&s, in, NULL); + secp256k1_scalar_negate(&s, &s); + secp256k1_scalar_get_b32(res, &s); - /* SECURE CLEANUP */ - OPENSSL_cleanse(&s, sizeof(s)); + /* SECURE CLEANUP */ + OPENSSL_cleanse(&s, sizeof(s)); } -void secp256k1_mpt_scalar_reduce32(unsigned char out32[32], const unsigned char in32[32]) { - secp256k1_scalar s; - secp256k1_scalar_set_b32(&s, in32, NULL); - secp256k1_scalar_get_b32(out32, &s); +void secp256k1_mpt_scalar_reduce32(unsigned char out32[32], + const unsigned char in32[32]) +{ + secp256k1_scalar s; + secp256k1_scalar_set_b32(&s, in32, NULL); + secp256k1_scalar_get_b32(out32, &s); - /* SECURE CLEANUP */ - OPENSSL_cleanse(&s, sizeof(s)); + /* SECURE CLEANUP */ + OPENSSL_cleanse(&s, sizeof(s)); } diff --git a/src/proof_link.c b/src/proof_link.c index 02ac867..c853c2b 100644 --- a/src/proof_link.c +++ b/src/proof_link.c @@ -1,305 +1,381 @@ /** -* @file proof_link.c -* @brief Zero-Knowledge Proof Linking ElGamal Ciphertexts and Pedersen Commitments. -* -* This module implements a Sigma protocol to prove that an ElGamal ciphertext -* and a Pedersen commitment encode the same underlying plaintext value \f$ m \f$, -* without revealing \f$ m \f$ or the blinding factors. -* -* @details - * **Statement:** -* The prover demonstrates knowledge of scalars \f$ (m, r, \rho) \f$ such that: -* 1. \f$ C_1 = r \cdot G \f$ (ElGamal Ephemeral Key) -* 2. \f$ C_2 = m \cdot G + r \cdot P \f$ (ElGamal Masked Value) -* 3. \f$ PC_m = m \cdot G + \rho \cdot H \f$ (Pedersen Commitment) -* -* **Protocol (Schnorr-style):** -* 1. **Commitment:** -* Prover samples nonces \f$ k_m, k_r, k_\rho \f$ and computes: -* - \f$ T_1 = k_r \cdot G \f$ -* - \f$ T_2 = k_m \cdot G + k_r \cdot P \f$ -* - \f$ T_3 = k_m \cdot G + k_\rho \cdot H \f$ -* -* 2. **Challenge:** -* \f[ e = H(\text{"MPT_ELGAMAL_PEDERSEN_LINK"} \parallel C_1 \parallel C_2 \parallel P \parallel PC_m \parallel T_1 \parallel T_2 \parallel T_3 \parallel \dots) \f] -* -* 3. **Response:** -* - \f$ s_m = k_m + e \cdot m \f$ -* - \f$ s_r = k_r + e \cdot r \f$ -* - \f$ s_\rho = k_\rho + e \cdot \rho \f$ -* -* 4. **Verification:** -* Verifier checks: -* - \f$ s_r \cdot G \stackrel{?}{=} T_1 + e \cdot C_1 \f$ -* - \f$ s_m \cdot G + s_r \cdot P \stackrel{?}{=} T_2 + e \cdot C_2 \f$ -* - \f$ s_m \cdot G + s_\rho \cdot H \stackrel{?}{=} T_3 + e \cdot PC_m \f$ - * - * **Security Context:** -* This proof prevents "bait-and-switch" attacks where a user sends a valid range proof -* for a small amount (e.g., 10) but updates the ledger balance with a large or negative -* amount (e.g., 1,000,000 or -5). It binds the two representations together. -* -* @see [Spec (ConfidentialMPT_20260201.pdf) Section 3.3.5] Linking ElGamal Ciphertexts and Pedersen Commitments -*/ + * @file proof_link.c + * @brief Zero-Knowledge Proof Linking ElGamal Ciphertexts and Pedersen + * Commitments. + * + * This module implements a Sigma protocol to prove that an ElGamal ciphertext + * and a Pedersen commitment encode the same underlying plaintext value \f$ m + * \f$, without revealing \f$ m \f$ or the blinding factors. + * + * @details + * **Statement:** + * The prover demonstrates knowledge of scalars \f$ (m, r, \rho) \f$ such that: + * 1. \f$ C_1 = r \cdot G \f$ (ElGamal Ephemeral Key) + * 2. \f$ C_2 = m \cdot G + r \cdot P \f$ (ElGamal Masked Value) + * 3. \f$ PC_m = m \cdot G + \rho \cdot H \f$ (Pedersen Commitment) + * + * **Protocol (Schnorr-style):** + * 1. **Commitment:** + * Prover samples nonces \f$ k_m, k_r, k_\rho \f$ and computes: + * - \f$ T_1 = k_r \cdot G \f$ + * - \f$ T_2 = k_m \cdot G + k_r \cdot P \f$ + * - \f$ T_3 = k_m \cdot G + k_\rho \cdot H \f$ + * + * 2. **Challenge:** + * \f[ e = H(\text{"MPT_ELGAMAL_PEDERSEN_LINK"} \parallel C_1 \parallel C_2 + * \parallel P \parallel PC_m \parallel T_1 \parallel T_2 \parallel T_3 + * \parallel \dots) \f] + * + * 3. **Response:** + * - \f$ s_m = k_m + e \cdot m \f$ + * - \f$ s_r = k_r + e \cdot r \f$ + * - \f$ s_\rho = k_\rho + e \cdot \rho \f$ + * + * 4. **Verification:** + * Verifier checks: + * - \f$ s_r \cdot G \stackrel{?}{=} T_1 + e \cdot C_1 \f$ + * - \f$ s_m \cdot G + s_r \cdot P \stackrel{?}{=} T_2 + e \cdot C_2 \f$ + * - \f$ s_m \cdot G + s_\rho \cdot H \stackrel{?}{=} T_3 + e \cdot PC_m \f$ + * + * **Security Context:** + * This proof prevents "bait-and-switch" attacks where a user sends a valid + * range proof for a small amount (e.g., 10) but updates the ledger balance with + * a large or negative amount (e.g., 1,000,000 or -5). It binds the two + * representations together. + * + * @see [Spec (ConfidentialMPT_20260201.pdf) Section 3.3.5] Linking ElGamal + * Ciphertexts and Pedersen Commitments + */ #include "secp256k1_mpt.h" -#include -#include -#include #include +#include +#include #include +#include /* --- Internal Helpers --- */ -static int pubkey_equal(const secp256k1_context* ctx, const secp256k1_pubkey* pk1, const secp256k1_pubkey* pk2) { - return secp256k1_ec_pubkey_cmp(ctx, pk1, pk2) == 0; +static int pubkey_equal(const secp256k1_context *ctx, + const secp256k1_pubkey *pk1, + const secp256k1_pubkey *pk2) +{ + return secp256k1_ec_pubkey_cmp(ctx, pk1, pk2) == 0; } -static int generate_random_scalar(const secp256k1_context* ctx, unsigned char* scalar_bytes) { - do { - if (RAND_bytes(scalar_bytes, 32) != 1) return 0; - } while (secp256k1_ec_seckey_verify(ctx, scalar_bytes) != 1); - return 1; +static int generate_random_scalar(const secp256k1_context *ctx, + unsigned char *scalar_bytes) +{ + do + { + if (RAND_bytes(scalar_bytes, 32) != 1) + return 0; + } while (secp256k1_ec_seckey_verify(ctx, scalar_bytes) != 1); + return 1; } static void build_link_challenge_hash( - const secp256k1_context* ctx, - unsigned char* e_out, - const secp256k1_pubkey* c1, const secp256k1_pubkey* c2, - const secp256k1_pubkey* pk, const secp256k1_pubkey* pcm, - const secp256k1_pubkey* T1, const secp256k1_pubkey* T2, const secp256k1_pubkey* T3, - const unsigned char* context_id) + const secp256k1_context *ctx, unsigned char *e_out, + const secp256k1_pubkey *c1, const secp256k1_pubkey *c2, + const secp256k1_pubkey *pk, const secp256k1_pubkey *pcm, + const secp256k1_pubkey *T1, const secp256k1_pubkey *T2, + const secp256k1_pubkey *T3, const unsigned char *context_id) { - SHA256_CTX sha; - unsigned char buf[33]; - unsigned char h[32]; - size_t len; - const char* domain = "MPT_ELGAMAL_PEDERSEN_LINK"; - - SHA256_Init(&sha); - SHA256_Update(&sha, domain, strlen(domain)); - - /* Helper Macro */ -#define SER_AND_HASH(pk_ptr) do { \ - len = 33; \ - secp256k1_ec_pubkey_serialize(ctx, buf, &len, pk_ptr, SECP256K1_EC_COMPRESSED); \ - SHA256_Update(&sha, buf, 33); \ - } while(0) - - SER_AND_HASH(c1); - SER_AND_HASH(c2); - SER_AND_HASH(pk); - SER_AND_HASH(pcm); - SER_AND_HASH(T1); - SER_AND_HASH(T2); - SER_AND_HASH(T3); + SHA256_CTX sha; + unsigned char buf[33]; + unsigned char h[32]; + size_t len; + const char *domain = "MPT_ELGAMAL_PEDERSEN_LINK"; + + SHA256_Init(&sha); + SHA256_Update(&sha, domain, strlen(domain)); + + /* Helper Macro */ +#define SER_AND_HASH(pk_ptr) \ + do \ + { \ + len = 33; \ + secp256k1_ec_pubkey_serialize(ctx, buf, &len, pk_ptr, \ + SECP256K1_EC_COMPRESSED); \ + SHA256_Update(&sha, buf, 33); \ + } while (0) + + SER_AND_HASH(c1); + SER_AND_HASH(c2); + SER_AND_HASH(pk); + SER_AND_HASH(pcm); + SER_AND_HASH(T1); + SER_AND_HASH(T2); + SER_AND_HASH(T3); #undef SER_AND_HASH - if (context_id) { - SHA256_Update(&sha, context_id, 32); - } + if (context_id) + { + SHA256_Update(&sha, context_id, 32); + } - SHA256_Final(h, &sha); - secp256k1_mpt_scalar_reduce32(e_out, h); + SHA256_Final(h, &sha); + secp256k1_mpt_scalar_reduce32(e_out, h); } /* --- Prover Implementation --- */ int secp256k1_elgamal_pedersen_link_prove( - const secp256k1_context* ctx, - unsigned char* proof, - const secp256k1_pubkey* c1, - const secp256k1_pubkey* c2, - const secp256k1_pubkey* pk, - const secp256k1_pubkey* pcm, - uint64_t amount, - const unsigned char* r, - const unsigned char* rho, - const unsigned char* context_id) + const secp256k1_context *ctx, unsigned char *proof, + const secp256k1_pubkey *c1, const secp256k1_pubkey *c2, + const secp256k1_pubkey *pk, const secp256k1_pubkey *pcm, uint64_t amount, + const unsigned char *r, const unsigned char *rho, + const unsigned char *context_id) { - unsigned char km[32], kr[32], krho[32]; - unsigned char e[32]; - unsigned char sm[32], sr[32], srho[32]; - unsigned char term[32]; - unsigned char m_scalar[32] = {0}; - - secp256k1_pubkey T1, T2, T3; - secp256k1_pubkey H, mG; - size_t len; - int ok = 0; - - /* 0. Validate Witnesses */ - if (!secp256k1_ec_seckey_verify(ctx, r)) return 0; - if (!secp256k1_ec_seckey_verify(ctx, rho)) return 0; - - /* 1. Generate Nonces */ - if (!generate_random_scalar(ctx, km)) goto cleanup; - if (!generate_random_scalar(ctx, kr)) goto cleanup; - if (!generate_random_scalar(ctx, krho)) goto cleanup; - - /* 2. Compute Commitments */ - - /* T1 = kr * G */ - if (!secp256k1_ec_pubkey_create(ctx, &T1, kr)) goto cleanup; - - /* T2 = km * G + kr * Pk */ - if (!secp256k1_ec_pubkey_create(ctx, &mG, km)) goto cleanup; // km*G - secp256k1_pubkey krPk = *pk; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &krPk, kr)) goto cleanup; // kr*Pk - const secp256k1_pubkey* add_t2[2] = {&mG, &krPk}; - if (!secp256k1_ec_pubkey_combine(ctx, &T2, add_t2, 2)) goto cleanup; - - /* T3 = km * G + krho * H */ - /* Note: mG (km*G) is reused here */ - if (!secp256k1_mpt_get_h_generator(ctx, &H)) goto cleanup; - secp256k1_pubkey krhoH = H; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &krhoH, krho)) goto cleanup; // krho*H - const secp256k1_pubkey* add_t3[2] = {&mG, &krhoH}; - if (!secp256k1_ec_pubkey_combine(ctx, &T3, add_t3, 2)) goto cleanup; - - /* 3. Challenge */ - build_link_challenge_hash(ctx, e, c1, c2, pk, pcm, &T1, &T2, &T3, context_id); - - /* 4. Responses */ - /* Convert amount to scalar */ - for (int i = 0; i < 8; i++) m_scalar[31 - i] = (amount >> (i * 8)) & 0xFF; - - /* sm = km + e * m */ - memcpy(sm, km, 32); - memcpy(term, m_scalar, 32); - if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) goto cleanup; - if (!secp256k1_ec_seckey_tweak_add(ctx, sm, term)) goto cleanup; - - /* sr = kr + e * r */ - memcpy(sr, kr, 32); - memcpy(term, r, 32); - if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) goto cleanup; - if (!secp256k1_ec_seckey_tweak_add(ctx, sr, term)) goto cleanup; - - /* srho = krho + e * rho */ - memcpy(srho, krho, 32); - memcpy(term, rho, 32); - if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) goto cleanup; - if (!secp256k1_ec_seckey_tweak_add(ctx, srho, term)) goto cleanup; - - /* 5. Serialize Proof (195 bytes) */ - unsigned char* ptr = proof; - len = 33; if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T1, SECP256K1_EC_COMPRESSED)) goto cleanup; ptr += 33; - len = 33; if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T2, SECP256K1_EC_COMPRESSED)) goto cleanup; ptr += 33; - len = 33; if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T3, SECP256K1_EC_COMPRESSED)) goto cleanup; ptr += 33; - - memcpy(ptr, sm, 32); ptr += 32; - memcpy(ptr, sr, 32); ptr += 32; - memcpy(ptr, srho, 32); ptr += 32; - - ok = 1; - - cleanup: - /* Securely clear secrets */ - OPENSSL_cleanse(km, 32); - OPENSSL_cleanse(kr, 32); - OPENSSL_cleanse(krho, 32); - OPENSSL_cleanse(m_scalar, 32); - OPENSSL_cleanse(term, 32); - OPENSSL_cleanse(sm, 32); - OPENSSL_cleanse(sr, 32); - OPENSSL_cleanse(srho, 32); - return ok; + unsigned char km[32], kr[32], krho[32]; + unsigned char e[32]; + unsigned char sm[32], sr[32], srho[32]; + unsigned char term[32]; + unsigned char m_scalar[32] = {0}; + + secp256k1_pubkey T1, T2, T3; + secp256k1_pubkey H, mG; + size_t len; + int ok = 0; + + /* 0. Validate Witnesses */ + if (!secp256k1_ec_seckey_verify(ctx, r)) + return 0; + if (!secp256k1_ec_seckey_verify(ctx, rho)) + return 0; + + /* 1. Generate Nonces */ + if (!generate_random_scalar(ctx, km)) + goto cleanup; + if (!generate_random_scalar(ctx, kr)) + goto cleanup; + if (!generate_random_scalar(ctx, krho)) + goto cleanup; + + /* 2. Compute Commitments */ + + /* T1 = kr * G */ + if (!secp256k1_ec_pubkey_create(ctx, &T1, kr)) + goto cleanup; + + /* T2 = km * G + kr * Pk */ + if (!secp256k1_ec_pubkey_create(ctx, &mG, km)) + goto cleanup; // km*G + secp256k1_pubkey krPk = *pk; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &krPk, kr)) + goto cleanup; // kr*Pk + const secp256k1_pubkey *add_t2[2] = {&mG, &krPk}; + if (!secp256k1_ec_pubkey_combine(ctx, &T2, add_t2, 2)) + goto cleanup; + + /* T3 = km * G + krho * H */ + /* Note: mG (km*G) is reused here */ + if (!secp256k1_mpt_get_h_generator(ctx, &H)) + goto cleanup; + secp256k1_pubkey krhoH = H; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &krhoH, krho)) + goto cleanup; // krho*H + const secp256k1_pubkey *add_t3[2] = {&mG, &krhoH}; + if (!secp256k1_ec_pubkey_combine(ctx, &T3, add_t3, 2)) + goto cleanup; + + /* 3. Challenge */ + build_link_challenge_hash(ctx, e, c1, c2, pk, pcm, &T1, &T2, &T3, context_id); + + /* 4. Responses */ + /* Convert amount to scalar */ + for (int i = 0; i < 8; i++) + m_scalar[31 - i] = (amount >> (i * 8)) & 0xFF; + + /* sm = km + e * m */ + memcpy(sm, km, 32); + memcpy(term, m_scalar, 32); + if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) + goto cleanup; + if (!secp256k1_ec_seckey_tweak_add(ctx, sm, term)) + goto cleanup; + + /* sr = kr + e * r */ + memcpy(sr, kr, 32); + memcpy(term, r, 32); + if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) + goto cleanup; + if (!secp256k1_ec_seckey_tweak_add(ctx, sr, term)) + goto cleanup; + + /* srho = krho + e * rho */ + memcpy(srho, krho, 32); + memcpy(term, rho, 32); + if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) + goto cleanup; + if (!secp256k1_ec_seckey_tweak_add(ctx, srho, term)) + goto cleanup; + + /* 5. Serialize Proof (195 bytes) */ + unsigned char *ptr = proof; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T1, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + ptr += 33; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T2, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + ptr += 33; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T3, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + ptr += 33; + + memcpy(ptr, sm, 32); + ptr += 32; + memcpy(ptr, sr, 32); + ptr += 32; + memcpy(ptr, srho, 32); + ptr += 32; + + ok = 1; + +cleanup: + /* Securely clear secrets */ + OPENSSL_cleanse(km, 32); + OPENSSL_cleanse(kr, 32); + OPENSSL_cleanse(krho, 32); + OPENSSL_cleanse(m_scalar, 32); + OPENSSL_cleanse(term, 32); + OPENSSL_cleanse(sm, 32); + OPENSSL_cleanse(sr, 32); + OPENSSL_cleanse(srho, 32); + return ok; } /* --- Verifier Implementation --- */ -int secp256k1_elgamal_pedersen_link_verify( - const secp256k1_context* ctx, - const unsigned char* proof, - const secp256k1_pubkey* c1, - const secp256k1_pubkey* c2, - const secp256k1_pubkey* pk, - const secp256k1_pubkey* pcm, - const unsigned char* context_id) +int secp256k1_elgamal_pedersen_link_verify(const secp256k1_context *ctx, + const unsigned char *proof, + const secp256k1_pubkey *c1, + const secp256k1_pubkey *c2, + const secp256k1_pubkey *pk, + const secp256k1_pubkey *pcm, + const unsigned char *context_id) { - secp256k1_pubkey T1, T2, T3; - secp256k1_pubkey LHS, RHS, term, mG, H; - const secp256k1_pubkey* pts[2]; - unsigned char sm[32], sr[32], srho[32], e[32]; - const unsigned char* ptr = proof; - int ok = 0; - - /* 1. Deserialize */ - if (!secp256k1_ec_pubkey_parse(ctx, &T1, ptr, 33)) goto cleanup; ptr += 33; - if (!secp256k1_ec_pubkey_parse(ctx, &T2, ptr, 33)) goto cleanup; ptr += 33; - if (!secp256k1_ec_pubkey_parse(ctx, &T3, ptr, 33)) goto cleanup; ptr += 33; - - memcpy(sm, ptr, 32); ptr += 32; - memcpy(sr, ptr, 32); ptr += 32; - memcpy(srho, ptr, 32); ptr += 32; - - /* Sanity Check Scalars */ - if (!secp256k1_ec_seckey_verify(ctx, sm)) goto cleanup; - if (!secp256k1_ec_seckey_verify(ctx, sr)) goto cleanup; - if (!secp256k1_ec_seckey_verify(ctx, srho)) goto cleanup; - - /* 2. Challenge */ - build_link_challenge_hash(ctx, e, c1, c2, pk, pcm, &T1, &T2, &T3, context_id); - - /* 3. Verification Equations */ - - /* Eq 1: sr * G == T1 + e * C1 */ - { - if (!secp256k1_ec_pubkey_create(ctx, &LHS, sr)) goto cleanup; // sr*G - - term = *c1; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) goto cleanup; // e*C1 - pts[0] = &T1; pts[1] = &term; - if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) goto cleanup; // T1 + e*C1 - - if (!pubkey_equal(ctx, &LHS, &RHS)) goto cleanup; - } - - /* Eq 2: sm * G + sr * Pk == T2 + e * C2 */ - { - /* LHS = sm*G + sr*Pk */ - if (!secp256k1_ec_pubkey_create(ctx, &mG, sm)) goto cleanup; // sm*G - - term = *pk; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, sr)) goto cleanup; // sr*Pk - - pts[0] = &mG; pts[1] = &term; - if (!secp256k1_ec_pubkey_combine(ctx, &LHS, pts, 2)) goto cleanup; - - /* RHS = T2 + e*C2 */ - term = *c2; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) goto cleanup; // e*C2 - - pts[0] = &T2; pts[1] = &term; - if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) goto cleanup; - - if (!pubkey_equal(ctx, &LHS, &RHS)) goto cleanup; - } - - /* Eq 3: sm * G + srho * H == T3 + e * Pcm */ - { - /* LHS = sm*G (reusing mG calculated above) + srho*H */ - if (!secp256k1_mpt_get_h_generator(ctx, &H)) goto cleanup; - - term = H; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, srho)) goto cleanup; // srho*H - - pts[0] = &mG; pts[1] = &term; - if (!secp256k1_ec_pubkey_combine(ctx, &LHS, pts, 2)) goto cleanup; - - /* RHS = T3 + e*Pcm */ - term = *pcm; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) goto cleanup; // e*Pcm - - pts[0] = &T3; pts[1] = &term; - if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) goto cleanup; - - if (!pubkey_equal(ctx, &LHS, &RHS)) goto cleanup; - } - - ok = 1; - - cleanup: - return ok; + secp256k1_pubkey T1, T2, T3; + secp256k1_pubkey LHS, RHS, term, mG, H; + const secp256k1_pubkey *pts[2]; + unsigned char sm[32], sr[32], srho[32], e[32]; + const unsigned char *ptr = proof; + int ok = 0; + + /* 1. Deserialize */ + if (!secp256k1_ec_pubkey_parse(ctx, &T1, ptr, 33)) + goto cleanup; + ptr += 33; + if (!secp256k1_ec_pubkey_parse(ctx, &T2, ptr, 33)) + goto cleanup; + ptr += 33; + if (!secp256k1_ec_pubkey_parse(ctx, &T3, ptr, 33)) + goto cleanup; + ptr += 33; + + memcpy(sm, ptr, 32); + ptr += 32; + memcpy(sr, ptr, 32); + ptr += 32; + memcpy(srho, ptr, 32); + ptr += 32; + + /* Sanity Check Scalars */ + if (!secp256k1_ec_seckey_verify(ctx, sm)) + goto cleanup; + if (!secp256k1_ec_seckey_verify(ctx, sr)) + goto cleanup; + if (!secp256k1_ec_seckey_verify(ctx, srho)) + goto cleanup; + + /* 2. Challenge */ + build_link_challenge_hash(ctx, e, c1, c2, pk, pcm, &T1, &T2, &T3, context_id); + + /* 3. Verification Equations */ + + /* Eq 1: sr * G == T1 + e * C1 */ + { + if (!secp256k1_ec_pubkey_create(ctx, &LHS, sr)) + goto cleanup; // sr*G + + term = *c1; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) + goto cleanup; // e*C1 + pts[0] = &T1; + pts[1] = &term; + if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) + goto cleanup; // T1 + e*C1 + + if (!pubkey_equal(ctx, &LHS, &RHS)) + goto cleanup; + } + + /* Eq 2: sm * G + sr * Pk == T2 + e * C2 */ + { + /* LHS = sm*G + sr*Pk */ + if (!secp256k1_ec_pubkey_create(ctx, &mG, sm)) + goto cleanup; // sm*G + + term = *pk; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, sr)) + goto cleanup; // sr*Pk + + pts[0] = &mG; + pts[1] = &term; + if (!secp256k1_ec_pubkey_combine(ctx, &LHS, pts, 2)) + goto cleanup; + + /* RHS = T2 + e*C2 */ + term = *c2; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) + goto cleanup; // e*C2 + + pts[0] = &T2; + pts[1] = &term; + if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) + goto cleanup; + + if (!pubkey_equal(ctx, &LHS, &RHS)) + goto cleanup; + } + + /* Eq 3: sm * G + srho * H == T3 + e * Pcm */ + { + /* LHS = sm*G (reusing mG calculated above) + srho*H */ + if (!secp256k1_mpt_get_h_generator(ctx, &H)) + goto cleanup; + + term = H; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, srho)) + goto cleanup; // srho*H + + pts[0] = &mG; + pts[1] = &term; + if (!secp256k1_ec_pubkey_combine(ctx, &LHS, pts, 2)) + goto cleanup; + + /* RHS = T3 + e*Pcm */ + term = *pcm; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) + goto cleanup; // e*Pcm + + pts[0] = &T3; + pts[1] = &term; + if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) + goto cleanup; + + if (!pubkey_equal(ctx, &LHS, &RHS)) + goto cleanup; + } + + ok = 1; + +cleanup: + return ok; } diff --git a/src/proof_pok_sk.c b/src/proof_pok_sk.c index 311b365..9bb7ab2 100644 --- a/src/proof_pok_sk.c +++ b/src/proof_pok_sk.c @@ -9,8 +9,8 @@ * @details * **Protocol (Schnorr Identification Scheme):** * - * Given a public key \f$ P = sk \cdot G \f$, the protocol proves knowledge of \f$ sk \f$. - * It is made non-interactive using the Fiat-Shamir transform. + * Given a public key \f$ P = sk \cdot G \f$, the protocol proves knowledge of + * \f$ sk \f$. It is made non-interactive using the Fiat-Shamir transform. * * 1. **Commitment:** * Prover samples random nonce \f$ k \leftarrow \mathbb{Z}_q \f$ and computes: @@ -18,7 +18,8 @@ * * 2. **Challenge (Fiat-Shamir):** * The challenge \f$ e \f$ is derived deterministically: - * \f[ e = H(\text{"MPT_POK_SK_REGISTER"} \parallel P \parallel T \parallel \text{ContextID}) \f] + * \f[ e = H(\text{"MPT_POK_SK_REGISTER"} \parallel P \parallel T \parallel + * \text{ContextID}) \f] * * 3. **Response:** * Prover computes scalar: @@ -34,139 +35,158 @@ * user's key to cancel it out) and to ensure the user actually controls the * ElGamal key being registered. * - * @see [Spec (ConfidentialMPT_20260201.pdf) Section 3.3.2] Proof of Knowledge of Secret Key + * @see [Spec (ConfidentialMPT_20260201.pdf) Section 3.3.2] Proof of Knowledge + * of Secret Key */ #include "secp256k1_mpt.h" -#include -#include #include +#include #include +#include /* --- Internal Helpers --- */ -static int pubkey_equal(const secp256k1_context* ctx, const secp256k1_pubkey* pk1, const secp256k1_pubkey* pk2) { - return secp256k1_ec_pubkey_cmp(ctx, pk1, pk2) == 0; +static int pubkey_equal(const secp256k1_context *ctx, + const secp256k1_pubkey *pk1, + const secp256k1_pubkey *pk2) +{ + return secp256k1_ec_pubkey_cmp(ctx, pk1, pk2) == 0; } -static int generate_random_scalar(const secp256k1_context* ctx, unsigned char* scalar) { - do { - if (RAND_bytes(scalar, 32) != 1) return 0; - } while (!secp256k1_ec_seckey_verify(ctx, scalar)); - return 1; +static int generate_random_scalar(const secp256k1_context *ctx, + unsigned char *scalar) +{ + do + { + if (RAND_bytes(scalar, 32) != 1) + return 0; + } while (!secp256k1_ec_seckey_verify(ctx, scalar)); + return 1; } -static void build_pok_challenge( - const secp256k1_context* ctx, - unsigned char* e_out, - const secp256k1_pubkey* pk, - const secp256k1_pubkey* T, - const unsigned char* context_id) +static void build_pok_challenge(const secp256k1_context *ctx, + unsigned char *e_out, + const secp256k1_pubkey *pk, + const secp256k1_pubkey *T, + const unsigned char *context_id) { - SHA256_CTX sha; - unsigned char buf[33]; - unsigned char h[32]; - size_t len; - const char* domain = "MPT_POK_SK_REGISTER"; - - SHA256_Init(&sha); - SHA256_Update(&sha, domain, strlen(domain)); - - len = 33; - secp256k1_ec_pubkey_serialize(ctx, buf, &len, pk, SECP256K1_EC_COMPRESSED); - SHA256_Update(&sha, buf, 33); - - len = 33; - secp256k1_ec_pubkey_serialize(ctx, buf, &len, T, SECP256K1_EC_COMPRESSED); - SHA256_Update(&sha, buf, 33); - - if (context_id) { - SHA256_Update(&sha, context_id, 32); - } - - SHA256_Final(h, &sha); - secp256k1_mpt_scalar_reduce32(e_out, h); + SHA256_CTX sha; + unsigned char buf[33]; + unsigned char h[32]; + size_t len; + const char *domain = "MPT_POK_SK_REGISTER"; + + SHA256_Init(&sha); + SHA256_Update(&sha, domain, strlen(domain)); + + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, pk, SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); + + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, T, SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); + + if (context_id) + { + SHA256_Update(&sha, context_id, 32); + } + + SHA256_Final(h, &sha); + secp256k1_mpt_scalar_reduce32(e_out, h); } /* --- Public API --- */ -int secp256k1_mpt_pok_sk_prove( - const secp256k1_context* ctx, - unsigned char* proof_out, - const secp256k1_pubkey* pk, - const unsigned char* sk, - const unsigned char* context_id) +int secp256k1_mpt_pok_sk_prove(const secp256k1_context *ctx, + unsigned char *proof_out, + const secp256k1_pubkey *pk, + const unsigned char *sk, + const unsigned char *context_id) { - unsigned char k[32]; - unsigned char e[32]; - unsigned char s[32]; - unsigned char term[32]; - secp256k1_pubkey T; - size_t len; - int ok = 0; - - if (!secp256k1_ec_seckey_verify(ctx, sk)) return 0; - if (!generate_random_scalar(ctx, k)) goto cleanup; - if (!secp256k1_ec_pubkey_create(ctx, &T, k)) goto cleanup; - - build_pok_challenge(ctx, e, pk, &T, context_id); - - // s = k + e*sk - memcpy(term, sk, 32); - if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) goto cleanup; - memcpy(s, k, 32); - if (!secp256k1_ec_seckey_tweak_add(ctx, s, term)) goto cleanup; - - // Serialize: T (33) || s (32) - unsigned char* ptr = proof_out; - len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T, SECP256K1_EC_COMPRESSED)) goto cleanup; - ptr += 33; - memcpy(ptr, s, 32); - - ok = 1; - - cleanup: - OPENSSL_cleanse(k, 32); - OPENSSL_cleanse(term, 32); - OPENSSL_cleanse(s, 32); - return ok; + unsigned char k[32]; + unsigned char e[32]; + unsigned char s[32]; + unsigned char term[32]; + secp256k1_pubkey T; + size_t len; + int ok = 0; + + if (!secp256k1_ec_seckey_verify(ctx, sk)) + return 0; + if (!generate_random_scalar(ctx, k)) + goto cleanup; + if (!secp256k1_ec_pubkey_create(ctx, &T, k)) + goto cleanup; + + build_pok_challenge(ctx, e, pk, &T, context_id); + + // s = k + e*sk + memcpy(term, sk, 32); + if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) + goto cleanup; + memcpy(s, k, 32); + if (!secp256k1_ec_seckey_tweak_add(ctx, s, term)) + goto cleanup; + + // Serialize: T (33) || s (32) + unsigned char *ptr = proof_out; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + ptr += 33; + memcpy(ptr, s, 32); + + ok = 1; + +cleanup: + OPENSSL_cleanse(k, 32); + OPENSSL_cleanse(term, 32); + OPENSSL_cleanse(s, 32); + return ok; } int secp256k1_mpt_pok_sk_verify( - const secp256k1_context* ctx, - const unsigned char* proof, // Caller MUST ensure this is at least 65 bytes - const secp256k1_pubkey* pk, - const unsigned char* context_id) + const secp256k1_context *ctx, + const unsigned char *proof, // Caller MUST ensure this is at least 65 bytes + const secp256k1_pubkey *pk, const unsigned char *context_id) { - secp256k1_pubkey T, LHS, RHS, ePk; - unsigned char e[32], s[32]; - const unsigned char* ptr = proof; - int ok = 0; + secp256k1_pubkey T, LHS, RHS, ePk; + unsigned char e[32], s[32]; + const unsigned char *ptr = proof; + int ok = 0; - /* 1. Parse T (33 bytes) */ - if (!secp256k1_ec_pubkey_parse(ctx, &T, ptr, 33)) goto cleanup; - ptr += 33; + /* 1. Parse T (33 bytes) */ + if (!secp256k1_ec_pubkey_parse(ctx, &T, ptr, 33)) + goto cleanup; + ptr += 33; - /* 2. Parse s (32 bytes) */ - memcpy(s, ptr, 32); - if (!secp256k1_ec_seckey_verify(ctx, s)) goto cleanup; + /* 2. Parse s (32 bytes) */ + memcpy(s, ptr, 32); + if (!secp256k1_ec_seckey_verify(ctx, s)) + goto cleanup; - /* 3. Recompute Challenge */ - build_pok_challenge(ctx, e, pk, &T, context_id); + /* 3. Recompute Challenge */ + build_pok_challenge(ctx, e, pk, &T, context_id); - /* 4. Verify Equation: s*G == T + e*Pk */ - if (!secp256k1_ec_pubkey_create(ctx, &LHS, s)) goto cleanup; + /* 4. Verify Equation: s*G == T + e*Pk */ + if (!secp256k1_ec_pubkey_create(ctx, &LHS, s)) + goto cleanup; - ePk = *pk; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &ePk, e)) goto cleanup; + ePk = *pk; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &ePk, e)) + goto cleanup; - const secp256k1_pubkey* addends[2] = {&T, &ePk}; - if (!secp256k1_ec_pubkey_combine(ctx, &RHS, addends, 2)) goto cleanup; + const secp256k1_pubkey *addends[2] = {&T, &ePk}; + if (!secp256k1_ec_pubkey_combine(ctx, &RHS, addends, 2)) + goto cleanup; - if (!pubkey_equal(ctx, &LHS, &RHS)) goto cleanup; + if (!pubkey_equal(ctx, &LHS, &RHS)) + goto cleanup; - ok = 1; + ok = 1; - cleanup: - return ok; +cleanup: + return ok; } diff --git a/src/proof_same_plaintext.c b/src/proof_same_plaintext.c index ce53a6b..d0d6de8 100644 --- a/src/proof_same_plaintext.c +++ b/src/proof_same_plaintext.c @@ -3,21 +3,22 @@ * @brief Zero-Knowledge Proof of Plaintext Equality (1-to-1). * * This module implements a multi-statement Sigma protocol to prove that two - * different ElGamal ciphertexts encrypt the **same** underlying plaintext amount, - * potentially under different public keys and using different randomness. + * different ElGamal ciphertexts encrypt the **same** underlying plaintext + * amount, potentially under different public keys and using different + * randomness. * * @details * **Statement:** - * Given two ciphertexts \f$ (R_1, S_1) \f$ and \f$ (R_2, S_2) \f$ encrypted under - * public keys \f$ P_1 \f$ and \f$ P_2 \f$ respectively, the prover demonstrates - * knowledge of scalars \f$ m, r_1, r_2 \f$ such that: + * Given two ciphertexts \f$ (R_1, S_1) \f$ and \f$ (R_2, S_2) \f$ encrypted + * under public keys \f$ P_1 \f$ and \f$ P_2 \f$ respectively, the prover + * demonstrates knowledge of scalars \f$ m, r_1, r_2 \f$ such that: * 1. \f$ R_1 = r_1 \cdot G \f$ and \f$ S_1 = m \cdot G + r_1 \cdot P_1 \f$ * 2. \f$ R_2 = r_2 \cdot G \f$ and \f$ S_2 = m \cdot G + r_2 \cdot P_2 \f$ * * **Protocol Logic (Shared Nonce):** - * To prove that \f$ m \f$ is identical in both ciphertexts without revealing it, - * the prover uses a **shared random nonce** \f$ k_m \f$ for the amount commitment - * across both logical branches of the proof. + * To prove that \f$ m \f$ is identical in both ciphertexts without revealing + * it, the prover uses a **shared random nonce** \f$ k_m \f$ for the amount + * commitment across both logical branches of the proof. * * 1. **Commitments:** * - \f$ T_m = k_m \cdot G \f$ (Shared commitment to amount nonce) @@ -34,259 +35,357 @@ * * 4. **Verification:** * The verifier checks 4 equations. Crucially, the "Amount" equations for both - * ciphertexts use the **same** \f$ s_m \f$ and \f$ T_m \f$, mathematically enforcing equality: - * - \f$ s_m \cdot G + s_{r1} \cdot P_1 \stackrel{?}{=} T_m + T_{r1,P1} + e \cdot S_1 \f$ - * - \f$ s_m \cdot G + s_{r2} \cdot P_2 \stackrel{?}{=} T_m + T_{r2,P2} + e \cdot S_2 \f$ + * ciphertexts use the **same** \f$ s_m \f$ and \f$ T_m \f$, mathematically + * enforcing equality: + * - \f$ s_m \cdot G + s_{r1} \cdot P_1 \stackrel{?}{=} T_m + T_{r1,P1} + e + * \cdot S_1 \f$ + * - \f$ s_m \cdot G + s_{r2} \cdot P_2 \stackrel{?}{=} T_m + T_{r2,P2} + e + * \cdot S_2 \f$ * * **Security Context:** - * This is used when transferring confidential tokens between accounts (re-encrypting - * the sender's balance for the receiver) or updating keys, ensuring no value is - * created or destroyed during the transformation. + * This is used when transferring confidential tokens between accounts + * (re-encrypting the sender's balance for the receiver) or updating keys, + * ensuring no value is created or destroyed during the transformation. * - * @see [Spec (ConfidentialMPT_20260106.pdf) Section 3.3.3] Proof of Equality of Plaintexts (Different Keys, Same Secret Amount) + * @see [Spec (ConfidentialMPT_20260106.pdf) Section 3.3.3] Proof of Equality of + * Plaintexts (Different Keys, Same Secret Amount) */ #include "secp256k1_mpt.h" -#include #include -#include +#include #include +#include /* --- Internal Helpers --- */ -static int pubkey_equal(const secp256k1_context* ctx, const secp256k1_pubkey* pk1, const secp256k1_pubkey* pk2) { - return secp256k1_ec_pubkey_cmp(ctx, pk1, pk2) == 0; +static int pubkey_equal(const secp256k1_context *ctx, + const secp256k1_pubkey *pk1, + const secp256k1_pubkey *pk2) +{ + return secp256k1_ec_pubkey_cmp(ctx, pk1, pk2) == 0; } -static int generate_random_scalar(const secp256k1_context* ctx, unsigned char* scalar) { - do { - if (RAND_bytes(scalar, 32) != 1) return 0; - } while (!secp256k1_ec_seckey_verify(ctx, scalar)); - return 1; +static int generate_random_scalar(const secp256k1_context *ctx, + unsigned char *scalar) +{ + do + { + if (RAND_bytes(scalar, 32) != 1) + return 0; + } while (!secp256k1_ec_seckey_verify(ctx, scalar)); + return 1; } /** * Builds the challenge hash input. */ static void build_same_plaintext_hash_input( - const secp256k1_context* ctx, - unsigned char* e_out, - const secp256k1_pubkey* R1, const secp256k1_pubkey* S1, const secp256k1_pubkey* P1, - const secp256k1_pubkey* R2, const secp256k1_pubkey* S2, const secp256k1_pubkey* P2, - const secp256k1_pubkey* T_m, - const secp256k1_pubkey* T_r1_G, const secp256k1_pubkey* T_r1_P1, - const secp256k1_pubkey* T_r2_G, const secp256k1_pubkey* T_r2_P2, - const unsigned char* tx_context_id) + const secp256k1_context *ctx, unsigned char *e_out, + const secp256k1_pubkey *R1, const secp256k1_pubkey *S1, + const secp256k1_pubkey *P1, const secp256k1_pubkey *R2, + const secp256k1_pubkey *S2, const secp256k1_pubkey *P2, + const secp256k1_pubkey *T_m, const secp256k1_pubkey *T_r1_G, + const secp256k1_pubkey *T_r1_P1, const secp256k1_pubkey *T_r2_G, + const secp256k1_pubkey *T_r2_P2, const unsigned char *tx_context_id) { - SHA256_CTX sha; - unsigned char buf[33]; - unsigned char h[32]; - size_t len; - const char* domain = "MPT_POK_SAME_PLAINTEXT_PROOF"; - - SHA256_Init(&sha); - SHA256_Update(&sha, domain, strlen(domain)); - - /* Helper macro to serialize and update */ -#define SER_AND_HASH(pk) do { \ - len = 33; \ - secp256k1_ec_pubkey_serialize(ctx, buf, &len, pk, SECP256K1_EC_COMPRESSED); \ - SHA256_Update(&sha, buf, 33); \ - } while(0) - - // 6 Public Inputs - SER_AND_HASH(R1); SER_AND_HASH(S1); SER_AND_HASH(P1); - SER_AND_HASH(R2); SER_AND_HASH(S2); SER_AND_HASH(P2); - - // 5 Commitments - SER_AND_HASH(T_m); - SER_AND_HASH(T_r1_G); SER_AND_HASH(T_r1_P1); - SER_AND_HASH(T_r2_G); SER_AND_HASH(T_r2_P2); + SHA256_CTX sha; + unsigned char buf[33]; + unsigned char h[32]; + size_t len; + const char *domain = "MPT_POK_SAME_PLAINTEXT_PROOF"; + + SHA256_Init(&sha); + SHA256_Update(&sha, domain, strlen(domain)); + + /* Helper macro to serialize and update */ +#define SER_AND_HASH(pk) \ + do \ + { \ + len = 33; \ + secp256k1_ec_pubkey_serialize(ctx, buf, &len, pk, \ + SECP256K1_EC_COMPRESSED); \ + SHA256_Update(&sha, buf, 33); \ + } while (0) + + // 6 Public Inputs + SER_AND_HASH(R1); + SER_AND_HASH(S1); + SER_AND_HASH(P1); + SER_AND_HASH(R2); + SER_AND_HASH(S2); + SER_AND_HASH(P2); + + // 5 Commitments + SER_AND_HASH(T_m); + SER_AND_HASH(T_r1_G); + SER_AND_HASH(T_r1_P1); + SER_AND_HASH(T_r2_G); + SER_AND_HASH(T_r2_P2); #undef SER_AND_HASH - if (tx_context_id) { - SHA256_Update(&sha, tx_context_id, 32); - } + if (tx_context_id) + { + SHA256_Update(&sha, tx_context_id, 32); + } - SHA256_Final(h, &sha); - secp256k1_mpt_scalar_reduce32(e_out, h); + SHA256_Final(h, &sha); + secp256k1_mpt_scalar_reduce32(e_out, h); } /* --- Public API --- */ int secp256k1_mpt_prove_same_plaintext( - const secp256k1_context* ctx, - unsigned char* proof_out, - const secp256k1_pubkey* R1, const secp256k1_pubkey* S1, const secp256k1_pubkey* P1, - const secp256k1_pubkey* R2, const secp256k1_pubkey* S2, const secp256k1_pubkey* P2, - uint64_t amount_m, - const unsigned char* randomness_r1, - const unsigned char* randomness_r2, - const unsigned char* tx_context_id -) { - unsigned char k_m[32], k_r1[32], k_r2[32]; - unsigned char m_scalar[32] = {0}; - unsigned char e[32]; - unsigned char s_m[32], s_r1[32], s_r2[32]; - unsigned char term[32]; - secp256k1_pubkey T_m, T_r1_G, T_r1_P1, T_r2_G, T_r2_P2; - size_t len; - int ok = 0; - - /* 1. Generate Randomness */ - if (!generate_random_scalar(ctx, k_m)) goto cleanup; - if (!generate_random_scalar(ctx, k_r1)) goto cleanup; - if (!generate_random_scalar(ctx, k_r2)) goto cleanup; - - /* 2. Commitments */ - if (!secp256k1_ec_pubkey_create(ctx, &T_m, k_m)) goto cleanup; - - if (!secp256k1_ec_pubkey_create(ctx, &T_r1_G, k_r1)) goto cleanup; - T_r1_P1 = *P1; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &T_r1_P1, k_r1)) goto cleanup; - - if (!secp256k1_ec_pubkey_create(ctx, &T_r2_G, k_r2)) goto cleanup; - T_r2_P2 = *P2; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &T_r2_P2, k_r2)) goto cleanup; - - /* 3. Challenge */ - build_same_plaintext_hash_input(ctx, e, R1, S1, P1, R2, S2, P2, - &T_m, &T_r1_G, &T_r1_P1, &T_r2_G, &T_r2_P2, - tx_context_id); - - /* 4. Responses */ - for (int i = 0; i < 8; ++i) m_scalar[31 - i] = (amount_m >> (i * 8)) & 0xFF; - - // s_m = k_m + e * m - memcpy(s_m, k_m, 32); - memcpy(term, m_scalar, 32); - if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) goto cleanup; - if (!secp256k1_ec_seckey_tweak_add(ctx, s_m, term)) goto cleanup; - - // s_r1 = k_r1 + e * r1 - memcpy(s_r1, k_r1, 32); - memcpy(term, randomness_r1, 32); - if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) goto cleanup; - if (!secp256k1_ec_seckey_tweak_add(ctx, s_r1, term)) goto cleanup; - - // s_r2 = k_r2 + e * r2 - memcpy(s_r2, k_r2, 32); - memcpy(term, randomness_r2, 32); - if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) goto cleanup; - if (!secp256k1_ec_seckey_tweak_add(ctx, s_r2, term)) goto cleanup; - - /* 5. Serialize */ - unsigned char* ptr = proof_out; - - len = 33; if(!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T_m, SECP256K1_EC_COMPRESSED)) goto cleanup; ptr += 33; - len = 33; if(!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T_r1_G, SECP256K1_EC_COMPRESSED)) goto cleanup; ptr += 33; - len = 33; if(!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T_r1_P1, SECP256K1_EC_COMPRESSED)) goto cleanup; ptr += 33; - len = 33; if(!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T_r2_G, SECP256K1_EC_COMPRESSED)) goto cleanup; ptr += 33; - len = 33; if(!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T_r2_P2, SECP256K1_EC_COMPRESSED)) goto cleanup; ptr += 33; - - memcpy(ptr, s_m, 32); ptr += 32; - memcpy(ptr, s_r1, 32); ptr += 32; - memcpy(ptr, s_r2, 32); ptr += 32; - - ok = 1; - - cleanup: - OPENSSL_cleanse(k_m, 32); - OPENSSL_cleanse(k_r1, 32); - OPENSSL_cleanse(k_r2, 32); - OPENSSL_cleanse(m_scalar, 32); - return ok; + const secp256k1_context *ctx, unsigned char *proof_out, + const secp256k1_pubkey *R1, const secp256k1_pubkey *S1, + const secp256k1_pubkey *P1, const secp256k1_pubkey *R2, + const secp256k1_pubkey *S2, const secp256k1_pubkey *P2, uint64_t amount_m, + const unsigned char *randomness_r1, const unsigned char *randomness_r2, + const unsigned char *tx_context_id) +{ + unsigned char k_m[32], k_r1[32], k_r2[32]; + unsigned char m_scalar[32] = {0}; + unsigned char e[32]; + unsigned char s_m[32], s_r1[32], s_r2[32]; + unsigned char term[32]; + secp256k1_pubkey T_m, T_r1_G, T_r1_P1, T_r2_G, T_r2_P2; + size_t len; + int ok = 0; + + /* 1. Generate Randomness */ + if (!generate_random_scalar(ctx, k_m)) + goto cleanup; + if (!generate_random_scalar(ctx, k_r1)) + goto cleanup; + if (!generate_random_scalar(ctx, k_r2)) + goto cleanup; + + /* 2. Commitments */ + if (!secp256k1_ec_pubkey_create(ctx, &T_m, k_m)) + goto cleanup; + + if (!secp256k1_ec_pubkey_create(ctx, &T_r1_G, k_r1)) + goto cleanup; + T_r1_P1 = *P1; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &T_r1_P1, k_r1)) + goto cleanup; + + if (!secp256k1_ec_pubkey_create(ctx, &T_r2_G, k_r2)) + goto cleanup; + T_r2_P2 = *P2; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &T_r2_P2, k_r2)) + goto cleanup; + + /* 3. Challenge */ + build_same_plaintext_hash_input(ctx, e, R1, S1, P1, R2, S2, P2, &T_m, &T_r1_G, + &T_r1_P1, &T_r2_G, &T_r2_P2, tx_context_id); + + /* 4. Responses */ + for (int i = 0; i < 8; ++i) + m_scalar[31 - i] = (amount_m >> (i * 8)) & 0xFF; + + // s_m = k_m + e * m + memcpy(s_m, k_m, 32); + memcpy(term, m_scalar, 32); + if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) + goto cleanup; + if (!secp256k1_ec_seckey_tweak_add(ctx, s_m, term)) + goto cleanup; + + // s_r1 = k_r1 + e * r1 + memcpy(s_r1, k_r1, 32); + memcpy(term, randomness_r1, 32); + if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) + goto cleanup; + if (!secp256k1_ec_seckey_tweak_add(ctx, s_r1, term)) + goto cleanup; + + // s_r2 = k_r2 + e * r2 + memcpy(s_r2, k_r2, 32); + memcpy(term, randomness_r2, 32); + if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) + goto cleanup; + if (!secp256k1_ec_seckey_tweak_add(ctx, s_r2, term)) + goto cleanup; + + /* 5. Serialize */ + unsigned char *ptr = proof_out; + + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T_m, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + ptr += 33; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T_r1_G, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + ptr += 33; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T_r1_P1, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + ptr += 33; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T_r2_G, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + ptr += 33; + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &T_r2_P2, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + ptr += 33; + + memcpy(ptr, s_m, 32); + ptr += 32; + memcpy(ptr, s_r1, 32); + ptr += 32; + memcpy(ptr, s_r2, 32); + ptr += 32; + + ok = 1; + +cleanup: + OPENSSL_cleanse(k_m, 32); + OPENSSL_cleanse(k_r1, 32); + OPENSSL_cleanse(k_r2, 32); + OPENSSL_cleanse(m_scalar, 32); + return ok; } int secp256k1_mpt_verify_same_plaintext( - const secp256k1_context* ctx, - const unsigned char* proof, // Caller MUST provide at least 261 bytes - const secp256k1_pubkey* R1, const secp256k1_pubkey* S1, const secp256k1_pubkey* P1, - const secp256k1_pubkey* R2, const secp256k1_pubkey* S2, const secp256k1_pubkey* P2, - const unsigned char* tx_context_id -) { - /* Fixed Size: 5 points (33) + 3 scalars (32) = 165 + 96 = 261 bytes */ - - secp256k1_pubkey T_m, T_r1_G, T_r1_P1, T_r2_G, T_r2_P2; - unsigned char s_m[32], s_r1[32], s_r2[32]; - unsigned char e[32]; - const unsigned char* ptr = proof; - int ok = 0; - - secp256k1_pubkey LHS, RHS, term, SmG; - const secp256k1_pubkey* pts[3]; - - /* 1. Deserialize (Strict 261 bytes) */ - - // Parse 5 Points (5 * 33 = 165 bytes) - if (!secp256k1_ec_pubkey_parse(ctx, &T_m, ptr, 33)) goto cleanup; ptr += 33; - if (!secp256k1_ec_pubkey_parse(ctx, &T_r1_G, ptr, 33)) goto cleanup; ptr += 33; - if (!secp256k1_ec_pubkey_parse(ctx, &T_r1_P1,ptr, 33)) goto cleanup; ptr += 33; - if (!secp256k1_ec_pubkey_parse(ctx, &T_r2_G, ptr, 33)) goto cleanup; ptr += 33; - if (!secp256k1_ec_pubkey_parse(ctx, &T_r2_P2,ptr, 33)) goto cleanup; ptr += 33; - - // Parse 3 Scalars (3 * 32 = 96 bytes) - memcpy(s_m, ptr, 32); ptr += 32; - memcpy(s_r1, ptr, 32); ptr += 32; - memcpy(s_r2, ptr, 32); ptr += 32; - - // Sanity Check Scalars - if (!secp256k1_ec_seckey_verify(ctx, s_m)) goto cleanup; - if (!secp256k1_ec_seckey_verify(ctx, s_r1)) goto cleanup; - if (!secp256k1_ec_seckey_verify(ctx, s_r2)) goto cleanup; - - /* 2. Challenge */ - build_same_plaintext_hash_input(ctx, e, R1, S1, P1, R2, S2, P2, - &T_m, &T_r1_G, &T_r1_P1, &T_r2_G, &T_r2_P2, - tx_context_id); - - /* 3. Verification Equations */ - - // Precompute SmG (used in Eq 2 & 4) - if (!secp256k1_ec_pubkey_create(ctx, &SmG, s_m)) goto cleanup; - - /* Eq 1: s_r1 * G == T_r1_G + e * R1 */ - if (!secp256k1_ec_pubkey_create(ctx, &LHS, s_r1)) goto cleanup; - term = *R1; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) goto cleanup; - pts[0] = &T_r1_G; pts[1] = &term; - if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) goto cleanup; - if (!pubkey_equal(ctx, &LHS, &RHS)) goto cleanup; - - /* Eq 2: s_m * G + s_r1 * P1 == T_m + T_r1_P1 + e * S1 */ - term = *P1; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, s_r1)) goto cleanup; - pts[0] = &SmG; pts[1] = &term; - if (!secp256k1_ec_pubkey_combine(ctx, &LHS, pts, 2)) goto cleanup; - - term = *S1; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) goto cleanup; - pts[0] = &T_m; pts[1] = &T_r1_P1; pts[2] = &term; - if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 3)) goto cleanup; - if (!pubkey_equal(ctx, &LHS, &RHS)) goto cleanup; - - /* Eq 3: s_r2 * G == T_r2_G + e * R2 */ - if (!secp256k1_ec_pubkey_create(ctx, &LHS, s_r2)) goto cleanup; - term = *R2; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) goto cleanup; - pts[0] = &T_r2_G; pts[1] = &term; - if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) goto cleanup; - if (!pubkey_equal(ctx, &LHS, &RHS)) goto cleanup; - - /* Eq 4: s_m * G + s_r2 * P2 == T_m + T_r2_P2 + e * S2 */ - term = *P2; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, s_r2)) goto cleanup; - pts[0] = &SmG; pts[1] = &term; - if (!secp256k1_ec_pubkey_combine(ctx, &LHS, pts, 2)) goto cleanup; - - term = *S2; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) goto cleanup; - pts[0] = &T_m; pts[1] = &T_r2_P2; pts[2] = &term; - if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 3)) goto cleanup; - if (!pubkey_equal(ctx, &LHS, &RHS)) goto cleanup; - - ok = 1; - - cleanup: - return ok; + const secp256k1_context *ctx, + const unsigned char *proof, // Caller MUST provide at least 261 bytes + const secp256k1_pubkey *R1, const secp256k1_pubkey *S1, + const secp256k1_pubkey *P1, const secp256k1_pubkey *R2, + const secp256k1_pubkey *S2, const secp256k1_pubkey *P2, + const unsigned char *tx_context_id) +{ + /* Fixed Size: 5 points (33) + 3 scalars (32) = 165 + 96 = 261 bytes */ + + secp256k1_pubkey T_m, T_r1_G, T_r1_P1, T_r2_G, T_r2_P2; + unsigned char s_m[32], s_r1[32], s_r2[32]; + unsigned char e[32]; + const unsigned char *ptr = proof; + int ok = 0; + + secp256k1_pubkey LHS, RHS, term, SmG; + const secp256k1_pubkey *pts[3]; + + /* 1. Deserialize (Strict 261 bytes) */ + + // Parse 5 Points (5 * 33 = 165 bytes) + if (!secp256k1_ec_pubkey_parse(ctx, &T_m, ptr, 33)) + goto cleanup; + ptr += 33; + if (!secp256k1_ec_pubkey_parse(ctx, &T_r1_G, ptr, 33)) + goto cleanup; + ptr += 33; + if (!secp256k1_ec_pubkey_parse(ctx, &T_r1_P1, ptr, 33)) + goto cleanup; + ptr += 33; + if (!secp256k1_ec_pubkey_parse(ctx, &T_r2_G, ptr, 33)) + goto cleanup; + ptr += 33; + if (!secp256k1_ec_pubkey_parse(ctx, &T_r2_P2, ptr, 33)) + goto cleanup; + ptr += 33; + + // Parse 3 Scalars (3 * 32 = 96 bytes) + memcpy(s_m, ptr, 32); + ptr += 32; + memcpy(s_r1, ptr, 32); + ptr += 32; + memcpy(s_r2, ptr, 32); + ptr += 32; + + // Sanity Check Scalars + if (!secp256k1_ec_seckey_verify(ctx, s_m)) + goto cleanup; + if (!secp256k1_ec_seckey_verify(ctx, s_r1)) + goto cleanup; + if (!secp256k1_ec_seckey_verify(ctx, s_r2)) + goto cleanup; + + /* 2. Challenge */ + build_same_plaintext_hash_input(ctx, e, R1, S1, P1, R2, S2, P2, &T_m, &T_r1_G, + &T_r1_P1, &T_r2_G, &T_r2_P2, tx_context_id); + + /* 3. Verification Equations */ + + // Precompute SmG (used in Eq 2 & 4) + if (!secp256k1_ec_pubkey_create(ctx, &SmG, s_m)) + goto cleanup; + + /* Eq 1: s_r1 * G == T_r1_G + e * R1 */ + if (!secp256k1_ec_pubkey_create(ctx, &LHS, s_r1)) + goto cleanup; + term = *R1; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) + goto cleanup; + pts[0] = &T_r1_G; + pts[1] = &term; + if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) + goto cleanup; + if (!pubkey_equal(ctx, &LHS, &RHS)) + goto cleanup; + + /* Eq 2: s_m * G + s_r1 * P1 == T_m + T_r1_P1 + e * S1 */ + term = *P1; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, s_r1)) + goto cleanup; + pts[0] = &SmG; + pts[1] = &term; + if (!secp256k1_ec_pubkey_combine(ctx, &LHS, pts, 2)) + goto cleanup; + + term = *S1; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) + goto cleanup; + pts[0] = &T_m; + pts[1] = &T_r1_P1; + pts[2] = &term; + if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 3)) + goto cleanup; + if (!pubkey_equal(ctx, &LHS, &RHS)) + goto cleanup; + + /* Eq 3: s_r2 * G == T_r2_G + e * R2 */ + if (!secp256k1_ec_pubkey_create(ctx, &LHS, s_r2)) + goto cleanup; + term = *R2; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) + goto cleanup; + pts[0] = &T_r2_G; + pts[1] = &term; + if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) + goto cleanup; + if (!pubkey_equal(ctx, &LHS, &RHS)) + goto cleanup; + + /* Eq 4: s_m * G + s_r2 * P2 == T_m + T_r2_P2 + e * S2 */ + term = *P2; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, s_r2)) + goto cleanup; + pts[0] = &SmG; + pts[1] = &term; + if (!secp256k1_ec_pubkey_combine(ctx, &LHS, pts, 2)) + goto cleanup; + + term = *S2; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term, e)) + goto cleanup; + pts[0] = &T_m; + pts[1] = &T_r2_P2; + pts[2] = &term; + if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 3)) + goto cleanup; + if (!pubkey_equal(ctx, &LHS, &RHS)) + goto cleanup; + + ok = 1; + +cleanup: + return ok; } diff --git a/src/proof_same_plaintext_multi.c b/src/proof_same_plaintext_multi.c index 40c948b..18edefe 100644 --- a/src/proof_same_plaintext_multi.c +++ b/src/proof_same_plaintext_multi.c @@ -2,21 +2,23 @@ * @file proof_same_plaintext_multi.c * @brief Zero-Knowledge Proof of Plaintext Equality (1-to-N). * - * This module implements a generalized multi-statement Sigma protocol to prove that + * This module implements a generalized multi-statement Sigma protocol to prove + * that * \f$ N \f$ distinct ElGamal ciphertexts all encrypt the **same** underlying * plaintext amount \f$ m \f$, using distinct randomness \f$ r_i \f$ for each. * * @details * **Statement:** - * Given \f$ N \f$ ciphertexts \f$ (R_i, S_i) \f$ encrypted under public keys \f$ P_i \f$, - * the prover demonstrates knowledge of scalars \f$ m \f$ and \f$ \{r_1, \dots, r_N\} \f$ - * such that for all \f$ i \in [1, N] \f$: + * Given \f$ N \f$ ciphertexts \f$ (R_i, S_i) \f$ encrypted under public keys + * \f$ P_i \f$, the prover demonstrates knowledge of scalars \f$ m \f$ and \f$ + * \{r_1, \dots, r_N\} \f$ such that for all \f$ i \in [1, N] \f$: * 1. \f$ R_i = r_i \cdot G \f$ * 2. \f$ S_i = m \cdot G + r_i \cdot P_i \f$ * * **Protocol (Shared Amount Nonce):** - * The efficiency gain comes from reusing the random nonce for the amount (\f$ k_m \f$) - * across all \f$ N \f$ proofs, tying them mathematically to the same value \f$ m \f$. + * The efficiency gain comes from reusing the random nonce for the amount (\f$ + * k_m \f$) across all \f$ N \f$ proofs, tying them mathematically to the same + * value \f$ m \f$. * * 1. **Commitments:** * - \f$ T_m = k_m \cdot G \f$ (Shared commitment to amount nonce) @@ -25,7 +27,8 @@ * - \f$ T_{r,P}^{(i)} = k_{r,i} \cdot P_i \f$ * * 2. **Challenge:** - * \f[ e = H(\dots \parallel T_m \parallel \{T_{r,G}^{(i)}, T_{r,P}^{(i)}\}_{i=1}^N \parallel \dots) \f] + * \f[ e = H(\dots \parallel T_m \parallel \{T_{r,G}^{(i)}, + * T_{r,P}^{(i)}\}_{i=1}^N \parallel \dots) \f] * * 3. **Responses:** * - \f$ s_m = k_m + e \cdot m \f$ (Shared response for amount) @@ -34,307 +37,367 @@ * 4. **Verification:** * For each \f$ i \in [1, N] \f$, the verifier checks: * - \f$ s_{r,i} \cdot G \stackrel{?}{=} T_{r,G}^{(i)} + e \cdot R_i \f$ - * - \f$ s_m \cdot G + s_{r,i} \cdot P_i \stackrel{?}{=} T_m + T_{r,P}^{(i)} + e \cdot S_i \f$ + * - \f$ s_m \cdot G + s_{r,i} \cdot P_i \stackrel{?}{=} T_m + T_{r,P}^{(i)} + e + * \cdot S_i \f$ * * **Security Context:** - * This is crucial for "fan-out" transactions or auditing scenarios where a single value - * must be proven correct against multiple encrypted destinations simultaneously, ensuring - * consistency without revealing the value. + * This is crucial for "fan-out" transactions or auditing scenarios where a + * single value must be proven correct against multiple encrypted destinations + * simultaneously, ensuring consistency without revealing the value. * - * @see [Spec (ConfidentialMPT_20260106.pdf) Section 3.3.4] Generalization for Multiple Ciphertexts + * @see [Spec (ConfidentialMPT_20260106.pdf) Section 3.3.4] Generalization for + * Multiple Ciphertexts */ #include "secp256k1_mpt.h" -#include #include -#include +#include #include +#include /* Helper for comparing public keys (from internal utils) */ -static int pubkey_equal(const secp256k1_context* ctx, const secp256k1_pubkey* pk1, const secp256k1_pubkey* pk2) { - return secp256k1_ec_pubkey_cmp(ctx, pk1, pk2) == 0; +static int pubkey_equal(const secp256k1_context *ctx, + const secp256k1_pubkey *pk1, + const secp256k1_pubkey *pk2) +{ + return secp256k1_ec_pubkey_cmp(ctx, pk1, pk2) == 0; } -static int generate_random_scalar(const secp256k1_context* ctx, unsigned char* scalar) { - do { - if (RAND_bytes(scalar, 32) != 1) return 0; - } while (!secp256k1_ec_seckey_verify(ctx, scalar)); - return 1; +static int generate_random_scalar(const secp256k1_context *ctx, + unsigned char *scalar) +{ + do + { + if (RAND_bytes(scalar, 32) != 1) + return 0; + } while (!secp256k1_ec_seckey_verify(ctx, scalar)); + return 1; } /* * Hash( Domain || {R_i, S_i, Pk_i} || Tm || {TrG_i, TrP_i} || TxID ) */ -static void compute_challenge_multi( - const secp256k1_context* ctx, - unsigned char* e_out, - size_t n, - const secp256k1_pubkey* R, - const secp256k1_pubkey* S, - const secp256k1_pubkey* Pk, - const secp256k1_pubkey* Tm, - const secp256k1_pubkey* TrG, - const secp256k1_pubkey* TrP, - const unsigned char* tx_id -) { - SHA256_CTX sha; - unsigned char buf[33]; - unsigned char h[32]; - size_t len; - size_t i; - const char* domain = "MPT_POK_SAME_PLAINTEXT_PROOF"; - - SHA256_Init(&sha); - SHA256_Update(&sha, domain, strlen(domain)); - - /* 1. Public Inputs */ - for (i = 0; i < n; ++i) { - len = 33; - secp256k1_ec_pubkey_serialize(ctx, buf, &len, &R[i], SECP256K1_EC_COMPRESSED); - SHA256_Update(&sha, buf, 33); - - len = 33; - secp256k1_ec_pubkey_serialize(ctx, buf, &len, &S[i], SECP256K1_EC_COMPRESSED); - SHA256_Update(&sha, buf, 33); - - len = 33; - secp256k1_ec_pubkey_serialize(ctx, buf, &len, &Pk[i], SECP256K1_EC_COMPRESSED); - SHA256_Update(&sha, buf, 33); - } - - /* 2. Commitments */ +static void +compute_challenge_multi(const secp256k1_context *ctx, unsigned char *e_out, + size_t n, const secp256k1_pubkey *R, + const secp256k1_pubkey *S, const secp256k1_pubkey *Pk, + const secp256k1_pubkey *Tm, const secp256k1_pubkey *TrG, + const secp256k1_pubkey *TrP, const unsigned char *tx_id) +{ + SHA256_CTX sha; + unsigned char buf[33]; + unsigned char h[32]; + size_t len; + size_t i; + const char *domain = "MPT_POK_SAME_PLAINTEXT_PROOF"; + + SHA256_Init(&sha); + SHA256_Update(&sha, domain, strlen(domain)); + + /* 1. Public Inputs */ + for (i = 0; i < n; ++i) + { len = 33; - secp256k1_ec_pubkey_serialize(ctx, buf, &len, Tm, SECP256K1_EC_COMPRESSED); + secp256k1_ec_pubkey_serialize(ctx, buf, &len, &R[i], + SECP256K1_EC_COMPRESSED); SHA256_Update(&sha, buf, 33); - for (i = 0; i < n; ++i) { - len = 33; - secp256k1_ec_pubkey_serialize(ctx, buf, &len, &TrG[i], SECP256K1_EC_COMPRESSED); - SHA256_Update(&sha, buf, 33); + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, &S[i], + SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); - len = 33; - secp256k1_ec_pubkey_serialize(ctx, buf, &len, &TrP[i], SECP256K1_EC_COMPRESSED); - SHA256_Update(&sha, buf, 33); - } + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, &Pk[i], + SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); + } + + /* 2. Commitments */ + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, Tm, SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); + + for (i = 0; i < n; ++i) + { + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, &TrG[i], + SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); + + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, &TrP[i], + SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); + } - /* 3. Context */ - if (tx_id) { - SHA256_Update(&sha, tx_id, 32); - } + /* 3. Context */ + if (tx_id) + { + SHA256_Update(&sha, tx_id, 32); + } - SHA256_Final(h, &sha); - secp256k1_mpt_scalar_reduce32(e_out, h); + SHA256_Final(h, &sha); + secp256k1_mpt_scalar_reduce32(e_out, h); } /* --- Public API --- */ -size_t secp256k1_mpt_prove_same_plaintext_multi_size(size_t n) { - // (1 Tm + 2N Tr) * 33 + (1 sm + N sr) * 32 - return ((1 + 2 * n) * 33) + ((1 + n) * 32); +size_t secp256k1_mpt_prove_same_plaintext_multi_size(size_t n) +{ + // (1 Tm + 2N Tr) * 33 + (1 sm + N sr) * 32 + return ((1 + 2 * n) * 33) + ((1 + n) * 32); } int secp256k1_mpt_prove_same_plaintext_multi( - const secp256k1_context* ctx, - unsigned char* proof_out, - size_t* proof_len, - uint64_t amount_m, - size_t n, - const secp256k1_pubkey* R, - const secp256k1_pubkey* S, - const secp256k1_pubkey* Pk, - const unsigned char* r_array, - const unsigned char* tx_id -) { - size_t required_len = secp256k1_mpt_prove_same_plaintext_multi_size(n); - if (!proof_len || *proof_len < required_len) { - if (proof_len) *proof_len = required_len; - return 0; - } - *proof_len = required_len; - - /* Heap Allocation to avoid Stack Overflow on large N */ - unsigned char* k_r_flat = NULL; // Stores n * 32 bytes - secp256k1_pubkey* TrG = NULL; - secp256k1_pubkey* TrP = NULL; - - unsigned char k_m[32]; - secp256k1_pubkey Tm; - unsigned char e[32]; - unsigned char s_m[32]; - - int ok = 0; - size_t i; - unsigned char* ptr = proof_out; - - /* Allocations */ - k_r_flat = (unsigned char*)malloc(n * 32); - TrG = (secp256k1_pubkey*)malloc(n * sizeof(secp256k1_pubkey)); - TrP = (secp256k1_pubkey*)malloc(n * sizeof(secp256k1_pubkey)); - - if (!k_r_flat || !TrG || !TrP) goto cleanup; - - /* 1. Generate Randomness & Commitments */ - - // km -> Tm = km * G - if (!generate_random_scalar(ctx, k_m)) goto cleanup; - if (!secp256k1_ec_pubkey_create(ctx, &Tm, k_m)) goto cleanup; - - for (i = 0; i < n; i++) { - unsigned char* kri = &k_r_flat[i * 32]; - - // kri -> TrG = kri * G - if (!generate_random_scalar(ctx, kri)) goto cleanup; - if (!secp256k1_ec_pubkey_create(ctx, &TrG[i], kri)) goto cleanup; - - // TrP = kri * Pk_i - TrP[i] = Pk[i]; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &TrP[i], kri)) goto cleanup; - } - - /* 2. Compute Challenge */ - compute_challenge_multi(ctx, e, n, R, S, Pk, &Tm, TrG, TrP, tx_id); - - /* 3. Compute Responses */ - - // s_m = k_m + e * m - { - unsigned char m_scalar[32] = {0}; - // Convert uint64 to big-endian 32-byte - for (i = 0; i < 8; ++i) m_scalar[31 - i] = (amount_m >> (i * 8)) & 0xFF; - - memcpy(s_m, k_m, 32); - if (!secp256k1_ec_seckey_tweak_mul(ctx, m_scalar, e)) goto cleanup; // m*e - if (!secp256k1_ec_seckey_tweak_add(ctx, s_m, m_scalar)) goto cleanup; // km + m*e - } - - // Serialize Points first (Protocol Format) - size_t len = 33; - secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &Tm, SECP256K1_EC_COMPRESSED); ptr += 33; - for(i=0; i Tm = km * G + if (!generate_random_scalar(ctx, k_m)) + goto cleanup; + if (!secp256k1_ec_pubkey_create(ctx, &Tm, k_m)) + goto cleanup; + + for (i = 0; i < n; i++) + { + unsigned char *kri = &k_r_flat[i * 32]; + + // kri -> TrG = kri * G + if (!generate_random_scalar(ctx, kri)) + goto cleanup; + if (!secp256k1_ec_pubkey_create(ctx, &TrG[i], kri)) + goto cleanup; + + // TrP = kri * Pk_i + TrP[i] = Pk[i]; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &TrP[i], kri)) + goto cleanup; + } + + /* 2. Compute Challenge */ + compute_challenge_multi(ctx, e, n, R, S, Pk, &Tm, TrG, TrP, tx_id); + + /* 3. Compute Responses */ + + // s_m = k_m + e * m + { + unsigned char m_scalar[32] = {0}; + // Convert uint64 to big-endian 32-byte + for (i = 0; i < 8; ++i) + m_scalar[31 - i] = (amount_m >> (i * 8)) & 0xFF; + + memcpy(s_m, k_m, 32); + if (!secp256k1_ec_seckey_tweak_mul(ctx, m_scalar, e)) + goto cleanup; // m*e + if (!secp256k1_ec_seckey_tweak_add(ctx, s_m, m_scalar)) + goto cleanup; // km + m*e + } + + // Serialize Points first (Protocol Format) + size_t len = 33; + secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &Tm, SECP256K1_EC_COMPRESSED); + ptr += 33; + for (i = 0; i < n; ++i) + { + secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &TrG[i], + SECP256K1_EC_COMPRESSED); + ptr += 33; + } + for (i = 0; i < n; ++i) + { + secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &TrP[i], + SECP256K1_EC_COMPRESSED); + ptr += 33; + } + + // Serialize sm + memcpy(ptr, s_m, 32); + ptr += 32; + + // Calculate and Serialize sri = kri + e * ri + for (i = 0; i < n; i++) + { + unsigned char s_ri[32]; + unsigned char term[32]; + + memcpy(s_ri, &k_r_flat[i * 32], 32); // k_ri + memcpy(term, &r_array[i * 32], 32); // r_i + + if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) + goto cleanup; // r*e + if (!secp256k1_ec_seckey_tweak_add(ctx, s_ri, term)) + goto cleanup; // k + r*e + + memcpy(ptr, s_ri, 32); + ptr += 32; + } + + ok = 1; + +cleanup: + if (k_r_flat) + { + // Secure wipe of randomness + OPENSSL_cleanse(k_r_flat, n * 32); + free(k_r_flat); + } + OPENSSL_cleanse(k_m, 32); + if (TrG) + free(TrG); + if (TrP) + free(TrP); + return ok; } int secp256k1_mpt_verify_same_plaintext_multi( - const secp256k1_context* ctx, - const unsigned char* proof, - size_t proof_len, - size_t n, - const secp256k1_pubkey* R, - const secp256k1_pubkey* S, - const secp256k1_pubkey* Pk, - const unsigned char* tx_id -) { - if (proof_len != secp256k1_mpt_prove_same_plaintext_multi_size(n)) return 0; - - secp256k1_pubkey Tm; - secp256k1_pubkey* TrG = NULL; - secp256k1_pubkey* TrP = NULL; - unsigned char s_m[32]; - unsigned char e[32]; - int ok = 0; - size_t i; - const unsigned char* ptr = proof; - - TrG = (secp256k1_pubkey*)malloc(n * sizeof(secp256k1_pubkey)); - TrP = (secp256k1_pubkey*)malloc(n * sizeof(secp256k1_pubkey)); - if (!TrG || !TrP) goto cleanup; - - /* 1. Deserialize */ - if (!secp256k1_ec_pubkey_parse(ctx, &Tm, ptr, 33)) goto cleanup; ptr += 33; - - for(i=0; i #include -#include +#include #include +#include /* --- Internal Helpers --- */ -static int pubkey_equal(const secp256k1_context* ctx, const secp256k1_pubkey* pk1, const secp256k1_pubkey* pk2) { - return secp256k1_ec_pubkey_cmp(ctx, pk1, pk2) == 0; +static int pubkey_equal(const secp256k1_context *ctx, + const secp256k1_pubkey *pk1, + const secp256k1_pubkey *pk2) +{ + return secp256k1_ec_pubkey_cmp(ctx, pk1, pk2) == 0; } -static int generate_random_scalar(const secp256k1_context* ctx, unsigned char* scalar) { - do { - if (RAND_bytes(scalar, 32) != 1) return 0; - } while (!secp256k1_ec_seckey_verify(ctx, scalar)); - return 1; +static int generate_random_scalar(const secp256k1_context *ctx, + unsigned char *scalar) +{ + do + { + if (RAND_bytes(scalar, 32) != 1) + return 0; + } while (!secp256k1_ec_seckey_verify(ctx, scalar)); + return 1; } -size_t secp256k1_mpt_proof_equality_shared_r_size(size_t n_recipients) { - // Tr (33) + N * Tm_i (33*N) + sm (32) + sr (32) - return (33 * (n_recipients + 1)) + 64; +size_t secp256k1_mpt_proof_equality_shared_r_size(size_t n_recipients) +{ + // Tr (33) + N * Tm_i (33*N) + sm (32) + sr (32) + return (33 * (n_recipients + 1)) + 64; } /* * Hash( Domain || C1 || {C2_i, Pk_i} || Tr || {Tm_i} || ContextID ) */ static void compute_challenge_equality_shared_r( - const secp256k1_context* ctx, - unsigned char* e_out, - size_t n, - const secp256k1_pubkey* C1, - const secp256k1_pubkey* C2_vec, - const secp256k1_pubkey* Pk_vec, - const secp256k1_pubkey* Tr, - const secp256k1_pubkey* Tm_vec, - const unsigned char* context_id -) { - SHA256_CTX sha; - unsigned char buf[33]; - unsigned char h[32]; - size_t len; - size_t i; - const char* domain = "MPT_POK_SAME_PLAINTEXT_SHARED_R"; - - SHA256_Init(&sha); - SHA256_Update(&sha, domain, strlen(domain)); - - /* 1. Shared C1 */ - len = 33; secp256k1_ec_pubkey_serialize(ctx, buf, &len, C1, SECP256K1_EC_COMPRESSED); + const secp256k1_context *ctx, unsigned char *e_out, size_t n, + const secp256k1_pubkey *C1, const secp256k1_pubkey *C2_vec, + const secp256k1_pubkey *Pk_vec, const secp256k1_pubkey *Tr, + const secp256k1_pubkey *Tm_vec, const unsigned char *context_id) +{ + SHA256_CTX sha; + unsigned char buf[33]; + unsigned char h[32]; + size_t len; + size_t i; + const char *domain = "MPT_POK_SAME_PLAINTEXT_SHARED_R"; + + SHA256_Init(&sha); + SHA256_Update(&sha, domain, strlen(domain)); + + /* 1. Shared C1 */ + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, C1, SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); + + /* 2. Pairs {C2_i, Pk_i} */ + for (i = 0; i < n; i++) + { + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, &C2_vec[i], + SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, &Pk_vec[i], + SECP256K1_EC_COMPRESSED); SHA256_Update(&sha, buf, 33); + } - /* 2. Pairs {C2_i, Pk_i} */ - for (i = 0; i < n; i++) { - len = 33; secp256k1_ec_pubkey_serialize(ctx, buf, &len, &C2_vec[i], SECP256K1_EC_COMPRESSED); - SHA256_Update(&sha, buf, 33); - len = 33; secp256k1_ec_pubkey_serialize(ctx, buf, &len, &Pk_vec[i], SECP256K1_EC_COMPRESSED); - SHA256_Update(&sha, buf, 33); - } + /* 3. Commitment Tr */ + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, Tr, SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); - /* 3. Commitment Tr */ - len = 33; secp256k1_ec_pubkey_serialize(ctx, buf, &len, Tr, SECP256K1_EC_COMPRESSED); + /* 4. Commitments {Tm_i} */ + for (i = 0; i < n; i++) + { + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, &Tm_vec[i], + SECP256K1_EC_COMPRESSED); SHA256_Update(&sha, buf, 33); + } - /* 4. Commitments {Tm_i} */ - for (i = 0; i < n; i++) { - len = 33; secp256k1_ec_pubkey_serialize(ctx, buf, &len, &Tm_vec[i], SECP256K1_EC_COMPRESSED); - SHA256_Update(&sha, buf, 33); - } - - /* 5. Transaction Context */ - if (context_id) { - SHA256_Update(&sha, context_id, 32); - } + /* 5. Transaction Context */ + if (context_id) + { + SHA256_Update(&sha, context_id, 32); + } - SHA256_Final(h, &sha); - secp256k1_mpt_scalar_reduce32(e_out, h); + SHA256_Final(h, &sha); + secp256k1_mpt_scalar_reduce32(e_out, h); } /* --- Public API --- */ int secp256k1_mpt_prove_equality_shared_r( - const secp256k1_context* ctx, - unsigned char* proof_out, // Caller MUST allocate secp256k1_mpt_proof_equality_shared_r_size(n) - uint64_t amount, - const unsigned char* r_shared, - size_t n, - const secp256k1_pubkey* C1, - const secp256k1_pubkey* C2_vec, - const secp256k1_pubkey* Pk_vec, - const unsigned char* context_id -) { - /* Local Variables */ - unsigned char k_m[32], k_r[32]; - unsigned char m_scalar[32] = {0}; - unsigned char e[32]; - unsigned char s_m[32], s_r[32]; - unsigned char term[32]; - - secp256k1_pubkey Tr; - secp256k1_pubkey* Tm_vec = NULL; - int ok = 0; - size_t i; - unsigned char* ptr = proof_out; - size_t len; - - /* 0. Validate Witness */ - if (!secp256k1_ec_seckey_verify(ctx, r_shared)) return 0; - - /* Allocate memory for commitments */ - if (n > 0) { - Tm_vec = (secp256k1_pubkey*)malloc(sizeof(secp256k1_pubkey) * n); - if (!Tm_vec) return 0; - } - - /* 1. Prepare Witness */ - for (i = 0; i < 8; i++) { - m_scalar[31 - i] = (amount >> (i * 8)) & 0xFF; - } - - /* 2. Sample Random Nonces */ - if (!generate_random_scalar(ctx, k_m)) goto cleanup; - if (!generate_random_scalar(ctx, k_r)) goto cleanup; - - /* 3. Compute Commitments */ - - /* Tr = kr * G */ - if (!secp256k1_ec_pubkey_create(ctx, &Tr, k_r)) goto cleanup; - - /* Tm_i = km * G + kr * Pk_i */ - secp256k1_pubkey kmG; - if (!secp256k1_ec_pubkey_create(ctx, &kmG, k_m)) goto cleanup; - - for (i = 0; i < n; i++) { - secp256k1_pubkey krPk = Pk_vec[i]; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &krPk, k_r)) goto cleanup; // kr * Pk - - const secp256k1_pubkey* pts[2] = {&kmG, &krPk}; - if (!secp256k1_ec_pubkey_combine(ctx, &Tm_vec[i], pts, 2)) goto cleanup; - } - - /* 4. Compute Challenge */ - compute_challenge_equality_shared_r(ctx, e, n, C1, C2_vec, Pk_vec, &Tr, Tm_vec, context_id); - - /* 5. Compute Responses */ - - /* s_m = k_m + e * m */ - memcpy(s_m, k_m, 32); - memcpy(term, m_scalar, 32); - if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) goto cleanup; - if (!secp256k1_ec_seckey_tweak_add(ctx, s_m, term)) goto cleanup; - - /* s_r = k_r + e * r */ - memcpy(s_r, k_r, 32); - memcpy(term, r_shared, 32); - if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) goto cleanup; - if (!secp256k1_ec_seckey_tweak_add(ctx, s_r, term)) goto cleanup; - - /* 6. Serialize Proof */ - - /* Serialize Tr */ + const secp256k1_context *ctx, + unsigned char *proof_out, // Caller MUST allocate + // secp256k1_mpt_proof_equality_shared_r_size(n) + uint64_t amount, const unsigned char *r_shared, size_t n, + const secp256k1_pubkey *C1, const secp256k1_pubkey *C2_vec, + const secp256k1_pubkey *Pk_vec, const unsigned char *context_id) +{ + /* Local Variables */ + unsigned char k_m[32], k_r[32]; + unsigned char m_scalar[32] = {0}; + unsigned char e[32]; + unsigned char s_m[32], s_r[32]; + unsigned char term[32]; + + secp256k1_pubkey Tr; + secp256k1_pubkey *Tm_vec = NULL; + int ok = 0; + size_t i; + unsigned char *ptr = proof_out; + size_t len; + + /* 0. Validate Witness */ + if (!secp256k1_ec_seckey_verify(ctx, r_shared)) + return 0; + + /* Allocate memory for commitments */ + if (n > 0) + { + Tm_vec = (secp256k1_pubkey *)malloc(sizeof(secp256k1_pubkey) * n); + if (!Tm_vec) + return 0; + } + + /* 1. Prepare Witness */ + for (i = 0; i < 8; i++) + { + m_scalar[31 - i] = (amount >> (i * 8)) & 0xFF; + } + + /* 2. Sample Random Nonces */ + if (!generate_random_scalar(ctx, k_m)) + goto cleanup; + if (!generate_random_scalar(ctx, k_r)) + goto cleanup; + + /* 3. Compute Commitments */ + + /* Tr = kr * G */ + if (!secp256k1_ec_pubkey_create(ctx, &Tr, k_r)) + goto cleanup; + + /* Tm_i = km * G + kr * Pk_i */ + secp256k1_pubkey kmG; + if (!secp256k1_ec_pubkey_create(ctx, &kmG, k_m)) + goto cleanup; + + for (i = 0; i < n; i++) + { + secp256k1_pubkey krPk = Pk_vec[i]; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &krPk, k_r)) + goto cleanup; // kr * Pk + + const secp256k1_pubkey *pts[2] = {&kmG, &krPk}; + if (!secp256k1_ec_pubkey_combine(ctx, &Tm_vec[i], pts, 2)) + goto cleanup; + } + + /* 4. Compute Challenge */ + compute_challenge_equality_shared_r(ctx, e, n, C1, C2_vec, Pk_vec, &Tr, + Tm_vec, context_id); + + /* 5. Compute Responses */ + + /* s_m = k_m + e * m */ + memcpy(s_m, k_m, 32); + memcpy(term, m_scalar, 32); + if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) + goto cleanup; + if (!secp256k1_ec_seckey_tweak_add(ctx, s_m, term)) + goto cleanup; + + /* s_r = k_r + e * r */ + memcpy(s_r, k_r, 32); + memcpy(term, r_shared, 32); + if (!secp256k1_ec_seckey_tweak_mul(ctx, term, e)) + goto cleanup; + if (!secp256k1_ec_seckey_tweak_add(ctx, s_r, term)) + goto cleanup; + + /* 6. Serialize Proof */ + + /* Serialize Tr */ + len = 33; + if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &Tr, + SECP256K1_EC_COMPRESSED)) + goto cleanup; + ptr += 33; + + /* Serialize Tm_i array */ + for (i = 0; i < n; i++) + { len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &Tr, SECP256K1_EC_COMPRESSED)) goto cleanup; + if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &Tm_vec[i], + SECP256K1_EC_COMPRESSED)) + goto cleanup; ptr += 33; - - /* Serialize Tm_i array */ - for (i = 0; i < n; i++) { - len = 33; - if (!secp256k1_ec_pubkey_serialize(ctx, ptr, &len, &Tm_vec[i], SECP256K1_EC_COMPRESSED)) goto cleanup; - ptr += 33; - } - - /* Serialize Scalars */ - memcpy(ptr, s_m, 32); ptr += 32; - memcpy(ptr, s_r, 32); ptr += 32; - - ok = 1; - - cleanup: - // Wipe Secrets - OPENSSL_cleanse(k_m, 32); - OPENSSL_cleanse(k_r, 32); - OPENSSL_cleanse(m_scalar, 32); - - // Wipe Intermediates - OPENSSL_cleanse(term, 32); - OPENSSL_cleanse(s_m, 32); - OPENSSL_cleanse(s_r, 32); - - if (Tm_vec) free(Tm_vec); - return ok; + } + + /* Serialize Scalars */ + memcpy(ptr, s_m, 32); + ptr += 32; + memcpy(ptr, s_r, 32); + ptr += 32; + + ok = 1; + +cleanup: + // Wipe Secrets + OPENSSL_cleanse(k_m, 32); + OPENSSL_cleanse(k_r, 32); + OPENSSL_cleanse(m_scalar, 32); + + // Wipe Intermediates + OPENSSL_cleanse(term, 32); + OPENSSL_cleanse(s_m, 32); + OPENSSL_cleanse(s_r, 32); + + if (Tm_vec) + free(Tm_vec); + return ok; } int secp256k1_mpt_verify_equality_shared_r( - const secp256k1_context* ctx, - const unsigned char* proof, // Caller MUST provide buffer of size: secp256k1_mpt_proof_equality_shared_r_size(n) - size_t n, - const secp256k1_pubkey* C1, - const secp256k1_pubkey* C2_vec, - const secp256k1_pubkey* Pk_vec, - const unsigned char* context_id -) { - /* Calculate expected size internally for strict checking later */ - size_t expected_len = secp256k1_mpt_proof_equality_shared_r_size(n); - - /* Local Variables */ - secp256k1_pubkey Tr; - secp256k1_pubkey* Tm_vec = NULL; - unsigned char s_m[32], s_r[32]; - unsigned char e[32]; - int ok = 0; - size_t i; - const unsigned char* ptr = proof; - - /* Allocate memory for commitments */ - Tm_vec = (secp256k1_pubkey*)malloc(sizeof(secp256k1_pubkey) * n); - if (!Tm_vec) return 0; - - /* 1. Deserialize Proof */ - - // Parse Tr (33 bytes) - if (!secp256k1_ec_pubkey_parse(ctx, &Tr, ptr, 33)) goto cleanup; + const secp256k1_context *ctx, + const unsigned char *proof, // Caller MUST provide buffer of size: + // secp256k1_mpt_proof_equality_shared_r_size(n) + size_t n, const secp256k1_pubkey *C1, const secp256k1_pubkey *C2_vec, + const secp256k1_pubkey *Pk_vec, const unsigned char *context_id) +{ + /* Calculate expected size internally for strict checking later */ + size_t expected_len = secp256k1_mpt_proof_equality_shared_r_size(n); + + /* Local Variables */ + secp256k1_pubkey Tr; + secp256k1_pubkey *Tm_vec = NULL; + unsigned char s_m[32], s_r[32]; + unsigned char e[32]; + int ok = 0; + size_t i; + const unsigned char *ptr = proof; + + /* Allocate memory for commitments */ + Tm_vec = (secp256k1_pubkey *)malloc(sizeof(secp256k1_pubkey) * n); + if (!Tm_vec) + return 0; + + /* 1. Deserialize Proof */ + + // Parse Tr (33 bytes) + if (!secp256k1_ec_pubkey_parse(ctx, &Tr, ptr, 33)) + goto cleanup; + ptr += 33; + + // Parse N commitments (N * 33 bytes) + for (i = 0; i < n; i++) + { + if (!secp256k1_ec_pubkey_parse(ctx, &Tm_vec[i], ptr, 33)) + goto cleanup; ptr += 33; - - // Parse N commitments (N * 33 bytes) - for (i = 0; i < n; i++) { - if (!secp256k1_ec_pubkey_parse(ctx, &Tm_vec[i], ptr, 33)) goto cleanup; - ptr += 33; - } - - // Parse Scalars (32 + 32 bytes) - memcpy(s_m, ptr, 32); ptr += 32; - memcpy(s_r, ptr, 32); ptr += 32; - - // Sanity check scalars - if (!secp256k1_ec_seckey_verify(ctx, s_m)) goto cleanup; - if (!secp256k1_ec_seckey_verify(ctx, s_r)) goto cleanup; - - /* 2. Challenge */ - compute_challenge_equality_shared_r(ctx, e, n, C1, C2_vec, Pk_vec, &Tr, Tm_vec, context_id); - - /* 3. Verification Equations */ - - /* Eq 1: sr * G == Tr + e * C1 */ - { - secp256k1_pubkey LHS, RHS; - secp256k1_pubkey eC1 = *C1; - - if (!secp256k1_ec_pubkey_create(ctx, &LHS, s_r)) goto cleanup; // sr*G - - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &eC1, e)) goto cleanup; // e*C1 - const secp256k1_pubkey* pts[2] = {&Tr, &eC1}; - if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) goto cleanup; // Tr + e*C1 - - if (!pubkey_equal(ctx, &LHS, &RHS)) goto cleanup; - } - - /* Eq 2: For each i, sm * G + sr * Pk_i == Tm_i + e * C2_i */ + } + + // Parse Scalars (32 + 32 bytes) + memcpy(s_m, ptr, 32); + ptr += 32; + memcpy(s_r, ptr, 32); + ptr += 32; + + // Sanity check scalars + if (!secp256k1_ec_seckey_verify(ctx, s_m)) + goto cleanup; + if (!secp256k1_ec_seckey_verify(ctx, s_r)) + goto cleanup; + + /* 2. Challenge */ + compute_challenge_equality_shared_r(ctx, e, n, C1, C2_vec, Pk_vec, &Tr, + Tm_vec, context_id); + + /* 3. Verification Equations */ + + /* Eq 1: sr * G == Tr + e * C1 */ + { + secp256k1_pubkey LHS, RHS; + secp256k1_pubkey eC1 = *C1; + + if (!secp256k1_ec_pubkey_create(ctx, &LHS, s_r)) + goto cleanup; // sr*G + + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &eC1, e)) + goto cleanup; // e*C1 + const secp256k1_pubkey *pts[2] = {&Tr, &eC1}; + if (!secp256k1_ec_pubkey_combine(ctx, &RHS, pts, 2)) + goto cleanup; // Tr + e*C1 + + if (!pubkey_equal(ctx, &LHS, &RHS)) + goto cleanup; + } + + /* Eq 2: For each i, sm * G + sr * Pk_i == Tm_i + e * C2_i */ + { + secp256k1_pubkey smG; + if (!secp256k1_ec_pubkey_create(ctx, &smG, s_m)) + goto cleanup; // Precompute sm*G + + for (i = 0; i < n; i++) { - secp256k1_pubkey smG; - if (!secp256k1_ec_pubkey_create(ctx, &smG, s_m)) goto cleanup; // Precompute sm*G - - for (i = 0; i < n; i++) { - secp256k1_pubkey LHS, RHS; + secp256k1_pubkey LHS, RHS; - /* LHS = sm*G + sr*Pk_i */ - secp256k1_pubkey srPk = Pk_vec[i]; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &srPk, s_r)) goto cleanup; + /* LHS = sm*G + sr*Pk_i */ + secp256k1_pubkey srPk = Pk_vec[i]; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &srPk, s_r)) + goto cleanup; - const secp256k1_pubkey* lhs_pts[2] = {&smG, &srPk}; - if (!secp256k1_ec_pubkey_combine(ctx, &LHS, lhs_pts, 2)) goto cleanup; + const secp256k1_pubkey *lhs_pts[2] = {&smG, &srPk}; + if (!secp256k1_ec_pubkey_combine(ctx, &LHS, lhs_pts, 2)) + goto cleanup; - /* RHS = Tm_i + e*C2_i */ - secp256k1_pubkey eC2 = C2_vec[i]; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &eC2, e)) goto cleanup; + /* RHS = Tm_i + e*C2_i */ + secp256k1_pubkey eC2 = C2_vec[i]; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &eC2, e)) + goto cleanup; - const secp256k1_pubkey* rhs_pts[2] = {&Tm_vec[i], &eC2}; - if (!secp256k1_ec_pubkey_combine(ctx, &RHS, rhs_pts, 2)) goto cleanup; + const secp256k1_pubkey *rhs_pts[2] = {&Tm_vec[i], &eC2}; + if (!secp256k1_ec_pubkey_combine(ctx, &RHS, rhs_pts, 2)) + goto cleanup; - if (!pubkey_equal(ctx, &LHS, &RHS)) goto cleanup; - } + if (!pubkey_equal(ctx, &LHS, &RHS)) + goto cleanup; } + } - // Strict Length Check: Ensure we read exactly what was expected - if ((size_t)(ptr - proof) != expected_len) goto cleanup; + // Strict Length Check: Ensure we read exactly what was expected + if ((size_t)(ptr - proof) != expected_len) + goto cleanup; - ok = 1; + ok = 1; - cleanup: - if (Tm_vec) free(Tm_vec); - return ok; +cleanup: + if (Tm_vec) + free(Tm_vec); + return ok; } diff --git a/tests/test_bulletproof_agg.c b/tests/test_bulletproof_agg.c index caa3e18..3083d23 100644 --- a/tests/test_bulletproof_agg.c +++ b/tests/test_bulletproof_agg.c @@ -1,190 +1,169 @@ +#include "secp256k1_mpt.h" +#include "test_utils.h" +#include #include -#include #include +#include #include -#include -#include "secp256k1_mpt.h" -#include "test_utils.h" #define BP_VALUE_BITS 64 #define BP_TOTAL_BITS(m) ((size_t)(BP_VALUE_BITS * (m))) #define VERIFY_RUNS 5 /* ---- Helpers ---- */ -static inline double elapsed_ms(struct timespec a, struct timespec b) { - return (b.tv_sec - a.tv_sec) * 1000.0 + - (b.tv_nsec - a.tv_nsec) / 1e6; +static inline double elapsed_ms(struct timespec a, struct timespec b) +{ + return (b.tv_sec - a.tv_sec) * 1000.0 + (b.tv_nsec - a.tv_nsec) / 1e6; } /* ---- Core Test Logic ---- */ -void run_test_case(secp256k1_context* ctx, const char* name, uint64_t* values, size_t num_values, int run_benchmarks) { - EXPECT(num_values > 0); - printf("\n[TEST] %s (num_values = %zu)\n", name, num_values); - - unsigned char (*blindings)[32] = malloc(num_values * sizeof(*blindings)); - secp256k1_pubkey* commitments = malloc(num_values * sizeof(*commitments)); - unsigned char context_id[32]; - - EXPECT(blindings != NULL && commitments != NULL); - EXPECT(RAND_bytes(context_id, 32) == 1); - - secp256k1_pubkey pk_base; - EXPECT(secp256k1_mpt_get_h_generator(ctx, &pk_base)); - - /* ---- Commitments ---- */ - for (size_t i = 0; i < num_values; i++) { - random_scalar(ctx, blindings[i]); - EXPECT(secp256k1_bulletproof_create_commitment( - ctx, - &commitments[i], - values[i], - blindings[i], - &pk_base)); - } - - /* ---- Generator vectors ---- */ - const size_t n = BP_TOTAL_BITS(num_values); - secp256k1_pubkey* G_vec = malloc(n * sizeof(secp256k1_pubkey)); - secp256k1_pubkey* H_vec = malloc(n * sizeof(secp256k1_pubkey)); - EXPECT(G_vec && H_vec); - - EXPECT(secp256k1_mpt_get_generator_vector( - ctx, G_vec, n, (const unsigned char*)"G", 1)); - EXPECT(secp256k1_mpt_get_generator_vector( - ctx, H_vec, n, (const unsigned char*)"H", 1)); - - /* ---- Prove ---- */ - unsigned char proof[4096]; - size_t proof_len = sizeof(proof); - - struct timespec t_p_start, t_p_end; - clock_gettime(CLOCK_MONOTONIC, &t_p_start); - - EXPECT(secp256k1_bulletproof_prove_agg( - ctx, - proof, - &proof_len, - values, - (const unsigned char*)blindings, - num_values, - &pk_base, - context_id)); - - clock_gettime(CLOCK_MONOTONIC, &t_p_end); - printf(" Proof size: %zu bytes\n", proof_len); - if (run_benchmarks) printf(" [BENCH] Proving time: %.3f ms\n", elapsed_ms(t_p_start, t_p_end)); - - /* ---- Verify ---- */ - struct timespec t_v_start, t_v_end; - clock_gettime(CLOCK_MONOTONIC, &t_v_start); - - int ok = secp256k1_bulletproof_verify_agg( - ctx, - G_vec, - H_vec, - proof, - proof_len, - commitments, - num_values, - &pk_base, - context_id); - - clock_gettime(CLOCK_MONOTONIC, &t_v_end); - EXPECT(ok); - printf(" PASSED (Verification)\n"); - if (run_benchmarks) printf(" [BENCH] Verification time: %.3f ms\n", elapsed_ms(t_v_start, t_v_end)); - - /* ---- Benchmark Verify ---- */ - if (run_benchmarks) { - double total_ms = 0.0; - for (int i = 0; i < VERIFY_RUNS; i++) { - struct timespec ts, te; - clock_gettime(CLOCK_MONOTONIC, &ts); - ok = secp256k1_bulletproof_verify_agg( - ctx, - G_vec, - H_vec, - proof, - proof_len, - commitments, - num_values, - &pk_base, - context_id); - clock_gettime(CLOCK_MONOTONIC, &te); - EXPECT(ok); - total_ms += elapsed_ms(ts, te); - } - printf(" [BENCH] Verification avg over %d runs: %.3f ms\n", VERIFY_RUNS, total_ms / VERIFY_RUNS); - } - - /* ---- Negative Test (Tamper) ---- */ - secp256k1_pubkey* bad_commitments = malloc(num_values * sizeof(*bad_commitments)); - EXPECT(bad_commitments != NULL); - - memcpy(bad_commitments, commitments, sizeof(*commitments) * num_values); - unsigned char bad_blinding[32]; - random_scalar(ctx, bad_blinding); - - /* Create fake commitment to (value + 1) */ +void run_test_case(secp256k1_context *ctx, const char *name, uint64_t *values, + size_t num_values, int run_benchmarks) +{ + EXPECT(num_values > 0); + printf("\n[TEST] %s (num_values = %zu)\n", name, num_values); + + unsigned char (*blindings)[32] = malloc(num_values * sizeof(*blindings)); + secp256k1_pubkey *commitments = malloc(num_values * sizeof(*commitments)); + unsigned char context_id[32]; + + EXPECT(blindings != NULL && commitments != NULL); + EXPECT(RAND_bytes(context_id, 32) == 1); + + secp256k1_pubkey pk_base; + EXPECT(secp256k1_mpt_get_h_generator(ctx, &pk_base)); + + /* ---- Commitments ---- */ + for (size_t i = 0; i < num_values; i++) + { + random_scalar(ctx, blindings[i]); EXPECT(secp256k1_bulletproof_create_commitment( - ctx, - &bad_commitments[num_values - 1], - (values[num_values - 1] == UINT64_MAX) - ? values[num_values - 1] - 1 - : values[num_values - 1] + 1, - bad_blinding, - &pk_base)); - - ok = secp256k1_bulletproof_verify_agg( - ctx, - G_vec, - H_vec, - proof, - proof_len, - bad_commitments, - num_values, - &pk_base, - context_id); - - if (ok) { - fprintf(stderr, "FAILED: Accepted invalid proof!\n"); - exit(EXIT_FAILURE); + ctx, &commitments[i], values[i], blindings[i], &pk_base)); + } + + /* ---- Generator vectors ---- */ + const size_t n = BP_TOTAL_BITS(num_values); + secp256k1_pubkey *G_vec = malloc(n * sizeof(secp256k1_pubkey)); + secp256k1_pubkey *H_vec = malloc(n * sizeof(secp256k1_pubkey)); + EXPECT(G_vec && H_vec); + + EXPECT(secp256k1_mpt_get_generator_vector(ctx, G_vec, n, + (const unsigned char *)"G", 1)); + EXPECT(secp256k1_mpt_get_generator_vector(ctx, H_vec, n, + (const unsigned char *)"H", 1)); + + /* ---- Prove ---- */ + unsigned char proof[4096]; + size_t proof_len = sizeof(proof); + + struct timespec t_p_start, t_p_end; + clock_gettime(CLOCK_MONOTONIC, &t_p_start); + + EXPECT(secp256k1_bulletproof_prove_agg(ctx, proof, &proof_len, values, + (const unsigned char *)blindings, + num_values, &pk_base, context_id)); + + clock_gettime(CLOCK_MONOTONIC, &t_p_end); + printf(" Proof size: %zu bytes\n", proof_len); + if (run_benchmarks) + printf(" [BENCH] Proving time: %.3f ms\n", elapsed_ms(t_p_start, t_p_end)); + + /* ---- Verify ---- */ + struct timespec t_v_start, t_v_end; + clock_gettime(CLOCK_MONOTONIC, &t_v_start); + + int ok = secp256k1_bulletproof_verify_agg(ctx, G_vec, H_vec, proof, proof_len, + commitments, num_values, &pk_base, + context_id); + + clock_gettime(CLOCK_MONOTONIC, &t_v_end); + EXPECT(ok); + printf(" PASSED (Verification)\n"); + if (run_benchmarks) + printf(" [BENCH] Verification time: %.3f ms\n", + elapsed_ms(t_v_start, t_v_end)); + + /* ---- Benchmark Verify ---- */ + if (run_benchmarks) + { + double total_ms = 0.0; + for (int i = 0; i < VERIFY_RUNS; i++) + { + struct timespec ts, te; + clock_gettime(CLOCK_MONOTONIC, &ts); + ok = secp256k1_bulletproof_verify_agg(ctx, G_vec, H_vec, proof, proof_len, + commitments, num_values, &pk_base, + context_id); + clock_gettime(CLOCK_MONOTONIC, &te); + EXPECT(ok); + total_ms += elapsed_ms(ts, te); } - printf(" PASSED (Rejected invalid proof)\n"); - - free(bad_commitments); - free(commitments); - free(blindings); - free(G_vec); - free(H_vec); + printf(" [BENCH] Verification avg over %d runs: %.3f ms\n", VERIFY_RUNS, + total_ms / VERIFY_RUNS); + } + + /* ---- Negative Test (Tamper) ---- */ + secp256k1_pubkey *bad_commitments = + malloc(num_values * sizeof(*bad_commitments)); + EXPECT(bad_commitments != NULL); + + memcpy(bad_commitments, commitments, sizeof(*commitments) * num_values); + unsigned char bad_blinding[32]; + random_scalar(ctx, bad_blinding); + + /* Create fake commitment to (value + 1) */ + EXPECT(secp256k1_bulletproof_create_commitment( + ctx, &bad_commitments[num_values - 1], + (values[num_values - 1] == UINT64_MAX) ? values[num_values - 1] - 1 + : values[num_values - 1] + 1, + bad_blinding, &pk_base)); + + ok = secp256k1_bulletproof_verify_agg(ctx, G_vec, H_vec, proof, proof_len, + bad_commitments, num_values, &pk_base, + context_id); + + if (ok) + { + fprintf(stderr, "FAILED: Accepted invalid proof!\n"); + exit(EXIT_FAILURE); + } + printf(" PASSED (Rejected invalid proof)\n"); + + free(bad_commitments); + free(commitments); + free(blindings); + free(G_vec); + free(H_vec); } /* ---- Main ---- */ -int main(void) { - secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - EXPECT(ctx != NULL); - - /* 1. Single proof, value 0 (The Bug Fix) */ - uint64_t v1[] = {0}; - run_test_case(ctx, "Single Proof (Value 0)", v1, 1, 0); - - /* 2. Single proof, value 1 */ - uint64_t v2[] = {1}; - run_test_case(ctx, "Single Proof (Value 1)", v2, 1, 0); - - /* 3. Single proof, MAX VALUE (Tests opposite vector edge case) */ - uint64_t v3[] = {0xFFFFFFFFFFFFFFFF}; // 2^64 - 1 - run_test_case(ctx, "Single Proof (MAX Value)", v3, 1, 0); - - /* 4. Aggregated proof, {0, 1} */ - uint64_t v4[] = {0, 1}; - run_test_case(ctx, "Aggregated Proof (0, 1)", v4, 2, 0); - - /* 5. Aggregated proof, {0, 0} with Benchmarks */ - uint64_t v5[] = {0, 0}; - run_test_case(ctx, "Aggregated Proof (0, 0)", v5, 2, 1); - - secp256k1_context_destroy(ctx); - printf("\n[TEST] All Bulletproof tests completed successfully\n"); - return 0; +int main(void) +{ + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | + SECP256K1_CONTEXT_VERIFY); + EXPECT(ctx != NULL); + + /* 1. Single proof, value 0 (The Bug Fix) */ + uint64_t v1[] = {0}; + run_test_case(ctx, "Single Proof (Value 0)", v1, 1, 0); + + /* 2. Single proof, value 1 */ + uint64_t v2[] = {1}; + run_test_case(ctx, "Single Proof (Value 1)", v2, 1, 0); + + /* 3. Single proof, MAX VALUE (Tests opposite vector edge case) */ + uint64_t v3[] = {0xFFFFFFFFFFFFFFFF}; // 2^64 - 1 + run_test_case(ctx, "Single Proof (MAX Value)", v3, 1, 0); + + /* 4. Aggregated proof, {0, 1} */ + uint64_t v4[] = {0, 1}; + run_test_case(ctx, "Aggregated Proof (0, 1)", v4, 2, 0); + + /* 5. Aggregated proof, {0, 0} with Benchmarks */ + uint64_t v5[] = {0, 0}; + run_test_case(ctx, "Aggregated Proof (0, 0)", v5, 2, 1); + + secp256k1_context_destroy(ctx); + printf("\n[TEST] All Bulletproof tests completed successfully\n"); + return 0; } diff --git a/tests/test_commitments.c b/tests/test_commitments.c index f9536d1..724a237 100644 --- a/tests/test_commitments.c +++ b/tests/test_commitments.c @@ -1,122 +1,135 @@ -#include -#include -#include #include "secp256k1_mpt.h" #include "test_utils.h" +#include +#include +#include /* --- Tests --- */ -void test_pedersen_commitment_basic() { - secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); +void test_pedersen_commitment_basic() +{ + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | + SECP256K1_CONTEXT_VERIFY); - uint64_t amount = 1000; - unsigned char rho[32]; - secp256k1_pubkey pc1, pc2; - unsigned char ser1[33], ser2[33]; - size_t len = 33; + uint64_t amount = 1000; + unsigned char rho[32]; + secp256k1_pubkey pc1, pc2; + unsigned char ser1[33], ser2[33]; + size_t len = 33; - printf("DEBUG: Starting Pedersen Commitment basic tests...\n"); + printf("DEBUG: Starting Pedersen Commitment basic tests...\n"); - // Generate valid random blinding factor - random_scalar(ctx, rho); + // Generate valid random blinding factor + random_scalar(ctx, rho); - // 1. Test Consistency: PC(m, rho) should always produce the same result - EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pc1, amount, rho) == 1); - EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pc2, amount, rho) == 1); + // 1. Test Consistency: PC(m, rho) should always produce the same result + EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pc1, amount, rho) == 1); + EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pc2, amount, rho) == 1); - secp256k1_ec_pubkey_serialize(ctx, ser1, &len, &pc1, SECP256K1_EC_COMPRESSED); - len = 33; - secp256k1_ec_pubkey_serialize(ctx, ser2, &len, &pc2, SECP256K1_EC_COMPRESSED); + secp256k1_ec_pubkey_serialize(ctx, ser1, &len, &pc1, SECP256K1_EC_COMPRESSED); + len = 33; + secp256k1_ec_pubkey_serialize(ctx, ser2, &len, &pc2, SECP256K1_EC_COMPRESSED); - EXPECT(memcmp(ser1, ser2, 33) == 0); - printf("SUCCESS: Deterministic commitment verified.\n"); + EXPECT(memcmp(ser1, ser2, 33) == 0); + printf("SUCCESS: Deterministic commitment verified.\n"); - // 2. Test Binding: Changing amount should change commitment - EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pc2, amount + 1, rho) == 1); - len = 33; - secp256k1_ec_pubkey_serialize(ctx, ser2, &len, &pc2, SECP256K1_EC_COMPRESSED); - EXPECT(memcmp(ser1, ser2, 33) != 0); - printf("SUCCESS: Binding property (amount) verified.\n"); + // 2. Test Binding: Changing amount should change commitment + EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pc2, amount + 1, rho) == 1); + len = 33; + secp256k1_ec_pubkey_serialize(ctx, ser2, &len, &pc2, SECP256K1_EC_COMPRESSED); + EXPECT(memcmp(ser1, ser2, 33) != 0); + printf("SUCCESS: Binding property (amount) verified.\n"); - secp256k1_context_destroy(ctx); + secp256k1_context_destroy(ctx); } -void test_pedersen_zero_value() { - secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - unsigned char rho[32]; - secp256k1_pubkey commitment, expected; - secp256k1_pubkey H; - unsigned char ser1[33], ser2[33]; - size_t len = 33; +void test_pedersen_zero_value() +{ + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | + SECP256K1_CONTEXT_VERIFY); + unsigned char rho[32]; + secp256k1_pubkey commitment, expected; + secp256k1_pubkey H; + unsigned char ser1[33], ser2[33]; + size_t len = 33; - printf("DEBUG: Starting Pedersen Zero Value test...\n"); + printf("DEBUG: Starting Pedersen Zero Value test...\n"); - // Generate valid random blinding factor - random_scalar(ctx, rho); + // Generate valid random blinding factor + random_scalar(ctx, rho); - // 1. Commit to 0 - EXPECT(secp256k1_mpt_pedersen_commit(ctx, &commitment, 0, rho) == 1); + // 1. Commit to 0 + EXPECT(secp256k1_mpt_pedersen_commit(ctx, &commitment, 0, rho) == 1); - // 2. Manual Check: C should equal rho*H (since m*G is infinity) - EXPECT(secp256k1_mpt_get_h_generator(ctx, &H)); - expected = H; - EXPECT(secp256k1_ec_pubkey_tweak_mul(ctx, &expected, rho)); + // 2. Manual Check: C should equal rho*H (since m*G is infinity) + EXPECT(secp256k1_mpt_get_h_generator(ctx, &H)); + expected = H; + EXPECT(secp256k1_ec_pubkey_tweak_mul(ctx, &expected, rho)); - // 3. Compare - secp256k1_ec_pubkey_serialize(ctx, ser1, &len, &commitment, SECP256K1_EC_COMPRESSED); - len = 33; - secp256k1_ec_pubkey_serialize(ctx, ser2, &len, &expected, SECP256K1_EC_COMPRESSED); + // 3. Compare + secp256k1_ec_pubkey_serialize(ctx, ser1, &len, &commitment, + SECP256K1_EC_COMPRESSED); + len = 33; + secp256k1_ec_pubkey_serialize(ctx, ser2, &len, &expected, + SECP256K1_EC_COMPRESSED); - EXPECT(memcmp(ser1, ser2, 33) == 0); - printf("SUCCESS: Committing to 0 correctly resulted in rho*H.\n"); + EXPECT(memcmp(ser1, ser2, 33) == 0); + printf("SUCCESS: Committing to 0 correctly resulted in rho*H.\n"); - secp256k1_context_destroy(ctx); + secp256k1_context_destroy(ctx); } -void test_pedersen_homomorphic_property() { - secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); +void test_pedersen_homomorphic_property() +{ + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | + SECP256K1_CONTEXT_VERIFY); - uint64_t m1 = 500, m2 = 300; - unsigned char r1[32], r2[32], r_sum[32]; - secp256k1_pubkey pc1, pc2, pc_sum_manual, pc_sum_computed; + uint64_t m1 = 500, m2 = 300; + unsigned char r1[32], r2[32], r_sum[32]; + secp256k1_pubkey pc1, pc2, pc_sum_manual, pc_sum_computed; - printf("DEBUG: Starting Pedersen Homomorphic property test...\n"); + printf("DEBUG: Starting Pedersen Homomorphic property test...\n"); - // Generate valid random blinding factors - random_scalar(ctx, r1); - random_scalar(ctx, r2); + // Generate valid random blinding factors + random_scalar(ctx, r1); + random_scalar(ctx, r2); - // Compute PC1 = PC(m1, r1) and PC2 = PC(m2, r2) - EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pc1, m1, r1) == 1); - EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pc2, m2, r2) == 1); + // Compute PC1 = PC(m1, r1) and PC2 = PC(m2, r2) + EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pc1, m1, r1) == 1); + EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pc2, m2, r2) == 1); - // Manual sum of points: PC1 + PC2 - const secp256k1_pubkey* points[2] = {&pc1, &pc2}; - EXPECT(secp256k1_ec_pubkey_combine(ctx, &pc_sum_manual, points, 2) == 1); + // Manual sum of points: PC1 + PC2 + const secp256k1_pubkey *points[2] = {&pc1, &pc2}; + EXPECT(secp256k1_ec_pubkey_combine(ctx, &pc_sum_manual, points, 2) == 1); - // Compute scalar sum of blinding factors: r_sum = r1 + r2 (mod n) - memcpy(r_sum, r1, 32); - EXPECT(secp256k1_ec_seckey_tweak_add(ctx, r_sum, r2) == 1); + // Compute scalar sum of blinding factors: r_sum = r1 + r2 (mod n) + memcpy(r_sum, r1, 32); + EXPECT(secp256k1_ec_seckey_tweak_add(ctx, r_sum, r2) == 1); - // Compute PC(m1 + m2, r1 + r2) - EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pc_sum_computed, m1 + m2, r_sum) == 1); + // Compute PC(m1 + m2, r1 + r2) + EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pc_sum_computed, m1 + m2, r_sum) == + 1); - // Compare - unsigned char ser1[33], ser2[33]; - size_t len = 33; - secp256k1_ec_pubkey_serialize(ctx, ser1, &len, &pc_sum_manual, SECP256K1_EC_COMPRESSED); - len = 33; - secp256k1_ec_pubkey_serialize(ctx, ser2, &len, &pc_sum_computed, SECP256K1_EC_COMPRESSED); + // Compare + unsigned char ser1[33], ser2[33]; + size_t len = 33; + secp256k1_ec_pubkey_serialize(ctx, ser1, &len, &pc_sum_manual, + SECP256K1_EC_COMPRESSED); + len = 33; + secp256k1_ec_pubkey_serialize(ctx, ser2, &len, &pc_sum_computed, + SECP256K1_EC_COMPRESSED); - EXPECT(memcmp(ser1, ser2, 33) == 0); - printf("SUCCESS: Homomorphic property (PC(m1,r1) + PC(m2,r2) == PC(m1+m2, r1+r2)) verified.\n"); + EXPECT(memcmp(ser1, ser2, 33) == 0); + printf("SUCCESS: Homomorphic property (PC(m1,r1) + PC(m2,r2) == PC(m1+m2, " + "r1+r2)) verified.\n"); - secp256k1_context_destroy(ctx); + secp256k1_context_destroy(ctx); } -int main() { - test_pedersen_commitment_basic(); - test_pedersen_zero_value(); - test_pedersen_homomorphic_property(); - printf("DEBUG: All Pedersen Commitment tests passed!\n"); - return 0; +int main() +{ + test_pedersen_commitment_basic(); + test_pedersen_zero_value(); + test_pedersen_homomorphic_property(); + printf("DEBUG: All Pedersen Commitment tests passed!\n"); + return 0; } diff --git a/tests/test_elgamal.c b/tests/test_elgamal.c index e0b4b10..f702ba1 100644 --- a/tests/test_elgamal.c +++ b/tests/test_elgamal.c @@ -1,179 +1,212 @@ +#include "secp256k1_mpt.h" +#include "test_utils.h" +#include #include #include #include -#include -#include "secp256k1_mpt.h" -#include "test_utils.h" // Forward declarations for all test functions -static void test_key_generation(const secp256k1_context* ctx); -static void test_encryption(const secp256k1_context* ctx); -static void test_encryption_decryption_roundtrip(const secp256k1_context* ctx); -static void test_homomorphic_operations(const secp256k1_context* ctx); -static void test_zero_encryption(const secp256k1_context* ctx); -static void test_canonical_zero(const secp256k1_context* ctx); -static void test_verify_encryption(const secp256k1_context* ctx); +static void test_key_generation(const secp256k1_context *ctx); +static void test_encryption(const secp256k1_context *ctx); +static void test_encryption_decryption_roundtrip(const secp256k1_context *ctx); +static void test_homomorphic_operations(const secp256k1_context *ctx); +static void test_zero_encryption(const secp256k1_context *ctx); +static void test_canonical_zero(const secp256k1_context *ctx); +static void test_verify_encryption(const secp256k1_context *ctx); // Main test runner -int main() { - secp256k1_context* ctx = secp256k1_context_create( - SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - EXPECT(ctx != NULL); - - unsigned char seed[32]; - EXPECT(RAND_bytes(seed, sizeof(seed)) == 1); - EXPECT(secp256k1_context_randomize(ctx, seed) == 1); - - test_key_generation(ctx); - test_encryption(ctx); - test_encryption_decryption_roundtrip(ctx); - test_homomorphic_operations(ctx); - test_zero_encryption(ctx); - test_canonical_zero(ctx); - test_verify_encryption(ctx); - - secp256k1_context_destroy(ctx); - printf("ALL TESTS PASSED\n"); - return 0; +int main() +{ + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | + SECP256K1_CONTEXT_VERIFY); + EXPECT(ctx != NULL); + + unsigned char seed[32]; + EXPECT(RAND_bytes(seed, sizeof(seed)) == 1); + EXPECT(secp256k1_context_randomize(ctx, seed) == 1); + + test_key_generation(ctx); + test_encryption(ctx); + test_encryption_decryption_roundtrip(ctx); + test_homomorphic_operations(ctx); + test_zero_encryption(ctx); + test_canonical_zero(ctx); + test_verify_encryption(ctx); + + secp256k1_context_destroy(ctx); + printf("ALL TESTS PASSED\n"); + return 0; } // --- Test Implementations --- -static void test_key_generation(const secp256k1_context* ctx) { - unsigned char privkey[32]; - secp256k1_pubkey pubkey; - printf("Running test: secp256k1_elgamal_generate_keypair...\n"); - EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); - printf("Test passed!\n"); +static void test_key_generation(const secp256k1_context *ctx) +{ + unsigned char privkey[32]; + secp256k1_pubkey pubkey; + printf("Running test: secp256k1_elgamal_generate_keypair...\n"); + EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); + printf("Test passed!\n"); } -static void test_encryption(const secp256k1_context* ctx) { - unsigned char privkey[32], blinding_factor[32]; - secp256k1_pubkey pubkey, c1, c2, temp_pubkey; - printf("Running test: secp256k1_elgamal_encrypt (smoke test)...\n"); - EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); - // Note: Reusing generate_keypair is a convenient way to get a random scalar - EXPECT(secp256k1_elgamal_generate_keypair(ctx, blinding_factor, &temp_pubkey) == 1); - EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, 12345, blinding_factor) == 1); - printf("Test passed!\n"); +static void test_encryption(const secp256k1_context *ctx) +{ + unsigned char privkey[32], blinding_factor[32]; + secp256k1_pubkey pubkey, c1, c2, temp_pubkey; + printf("Running test: secp256k1_elgamal_encrypt (smoke test)...\n"); + EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); + // Note: Reusing generate_keypair is a convenient way to get a random scalar + EXPECT(secp256k1_elgamal_generate_keypair(ctx, blinding_factor, + &temp_pubkey) == 1); + EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, 12345, + blinding_factor) == 1); + printf("Test passed!\n"); } -static void test_encryption_decryption_roundtrip(const secp256k1_context* ctx) { - unsigned char privkey[32], blinding_factor[32]; - secp256k1_pubkey pubkey, c1, c2, temp_pubkey; - uint64_t original_amount = 10001; - uint64_t decrypted_amount = 0; - printf("Running test: encryption-decryption round trip...\n"); - EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); - EXPECT(secp256k1_elgamal_generate_keypair(ctx, blinding_factor, &temp_pubkey) == 1); - EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, original_amount, blinding_factor) == 1); - EXPECT(secp256k1_elgamal_decrypt(ctx, &decrypted_amount, &c1, &c2, privkey) == 1); - EXPECT(original_amount == decrypted_amount); - printf("Test passed!\n"); +static void test_encryption_decryption_roundtrip(const secp256k1_context *ctx) +{ + unsigned char privkey[32], blinding_factor[32]; + secp256k1_pubkey pubkey, c1, c2, temp_pubkey; + uint64_t original_amount = 10001; + uint64_t decrypted_amount = 0; + printf("Running test: encryption-decryption round trip...\n"); + EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); + EXPECT(secp256k1_elgamal_generate_keypair(ctx, blinding_factor, + &temp_pubkey) == 1); + EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, original_amount, + blinding_factor) == 1); + EXPECT(secp256k1_elgamal_decrypt(ctx, &decrypted_amount, &c1, &c2, privkey) == + 1); + EXPECT(original_amount == decrypted_amount); + printf("Test passed!\n"); } -static void test_homomorphic_operations(const secp256k1_context* ctx) { - unsigned char privkey[32]; - secp256k1_pubkey pubkey; - uint64_t amount_a = 5000, amount_b = 1234; - secp256k1_pubkey a_c1, a_c2, b_c1, b_c2, result_c1, result_c2; - uint64_t decrypted_result; - - printf("Running test: homomorphic operations...\n"); - EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); - - { - unsigned char k_a[32]; - secp256k1_pubkey temp_pubkey; - EXPECT(secp256k1_elgamal_generate_keypair(ctx, k_a, &temp_pubkey) == 1); - EXPECT(secp256k1_elgamal_encrypt(ctx, &a_c1, &a_c2, &pubkey, amount_a, k_a) == 1); - } - { - unsigned char k_b[32]; - secp256k1_pubkey temp_pubkey; - EXPECT(secp256k1_elgamal_generate_keypair(ctx, k_b, &temp_pubkey) == 1); - EXPECT(secp256k1_elgamal_encrypt(ctx, &b_c1, &b_c2, &pubkey, amount_b, k_b) == 1); - } - - EXPECT(secp256k1_elgamal_add(ctx, &result_c1, &result_c2, &a_c1, &a_c2, &b_c1, &b_c2) == 1); - EXPECT(secp256k1_elgamal_decrypt(ctx, &decrypted_result, &result_c1, &result_c2, privkey) == 1); - EXPECT(decrypted_result == amount_a + amount_b); - - EXPECT(secp256k1_elgamal_subtract(ctx, &result_c1, &result_c2, &a_c1, &a_c2, &b_c1, &b_c2) == 1); - EXPECT(secp256k1_elgamal_decrypt(ctx, &decrypted_result, &result_c1, &result_c2, privkey) == 1); - EXPECT(decrypted_result == amount_a - amount_b); - printf("Test passed!\n"); +static void test_homomorphic_operations(const secp256k1_context *ctx) +{ + unsigned char privkey[32]; + secp256k1_pubkey pubkey; + uint64_t amount_a = 5000, amount_b = 1234; + secp256k1_pubkey a_c1, a_c2, b_c1, b_c2, result_c1, result_c2; + uint64_t decrypted_result; + + printf("Running test: homomorphic operations...\n"); + EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); + + { + unsigned char k_a[32]; + secp256k1_pubkey temp_pubkey; + EXPECT(secp256k1_elgamal_generate_keypair(ctx, k_a, &temp_pubkey) == 1); + EXPECT(secp256k1_elgamal_encrypt(ctx, &a_c1, &a_c2, &pubkey, amount_a, + k_a) == 1); + } + { + unsigned char k_b[32]; + secp256k1_pubkey temp_pubkey; + EXPECT(secp256k1_elgamal_generate_keypair(ctx, k_b, &temp_pubkey) == 1); + EXPECT(secp256k1_elgamal_encrypt(ctx, &b_c1, &b_c2, &pubkey, amount_b, + k_b) == 1); + } + + EXPECT(secp256k1_elgamal_add(ctx, &result_c1, &result_c2, &a_c1, &a_c2, &b_c1, + &b_c2) == 1); + EXPECT(secp256k1_elgamal_decrypt(ctx, &decrypted_result, &result_c1, + &result_c2, privkey) == 1); + EXPECT(decrypted_result == amount_a + amount_b); + + EXPECT(secp256k1_elgamal_subtract(ctx, &result_c1, &result_c2, &a_c1, &a_c2, + &b_c1, &b_c2) == 1); + EXPECT(secp256k1_elgamal_decrypt(ctx, &decrypted_result, &result_c1, + &result_c2, privkey) == 1); + EXPECT(decrypted_result == amount_a - amount_b); + printf("Test passed!\n"); } -static void test_zero_encryption(const secp256k1_context* ctx) { - unsigned char privkey[32], blinding_factor[32]; - secp256k1_pubkey pubkey, c1, c2, temp_pubkey; - uint64_t original_amount = 0; - uint64_t decrypted_amount = 999; // Non-zero initial value - printf("Running test: encrypting a random zero...\n"); - EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); - EXPECT(secp256k1_elgamal_generate_keypair(ctx, blinding_factor, &temp_pubkey) == 1); - EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, original_amount, blinding_factor) == 1); - EXPECT(secp256k1_elgamal_decrypt(ctx, &decrypted_amount, &c1, &c2, privkey) == 1); - EXPECT(original_amount == decrypted_amount); - printf("Test passed!\n"); +static void test_zero_encryption(const secp256k1_context *ctx) +{ + unsigned char privkey[32], blinding_factor[32]; + secp256k1_pubkey pubkey, c1, c2, temp_pubkey; + uint64_t original_amount = 0; + uint64_t decrypted_amount = 999; // Non-zero initial value + printf("Running test: encrypting a random zero...\n"); + EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); + EXPECT(secp256k1_elgamal_generate_keypair(ctx, blinding_factor, + &temp_pubkey) == 1); + EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, original_amount, + blinding_factor) == 1); + EXPECT(secp256k1_elgamal_decrypt(ctx, &decrypted_amount, &c1, &c2, privkey) == + 1); + EXPECT(original_amount == decrypted_amount); + printf("Test passed!\n"); } -static void test_canonical_zero(const secp256k1_context* ctx) { - unsigned char privkey[32]; - secp256k1_pubkey pubkey; - secp256k1_pubkey c1_a, c2_a, c1_b, c2_b; - uint64_t decrypted_amount = 999; +static void test_canonical_zero(const secp256k1_context *ctx) +{ + unsigned char privkey[32]; + secp256k1_pubkey pubkey; + secp256k1_pubkey c1_a, c2_a, c1_b, c2_b; + uint64_t decrypted_amount = 999; - // Use placeholder byte arrays for IDs (20 bytes for account, 24 for issuance) - unsigned char account_id[20] = {1}; // Example ID - unsigned char issuance_id[24] = {2}; // Example ID + // Use placeholder byte arrays for IDs (20 bytes for account, 24 for issuance) + unsigned char account_id[20] = {1}; // Example ID + unsigned char issuance_id[24] = {2}; // Example ID - printf("Running test: canonical encrypted zero...\n"); - EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); + printf("Running test: canonical encrypted zero...\n"); + EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); - // Generate it once - EXPECT(generate_canonical_encrypted_zero(ctx, &c1_a, &c2_a, &pubkey, account_id, issuance_id) == 1); + // Generate it once + EXPECT(generate_canonical_encrypted_zero(ctx, &c1_a, &c2_a, &pubkey, + account_id, issuance_id) == 1); - // Generate it a second time with the same inputs - EXPECT(generate_canonical_encrypted_zero(ctx, &c1_b, &c2_b, &pubkey, account_id, issuance_id) == 1); + // Generate it a second time with the same inputs + EXPECT(generate_canonical_encrypted_zero(ctx, &c1_b, &c2_b, &pubkey, + account_id, issuance_id) == 1); - // 1. Verify that it decrypts to zero - EXPECT(secp256k1_elgamal_decrypt(ctx, &decrypted_amount, &c1_a, &c2_a, privkey) == 1); - EXPECT(decrypted_amount == 0); + // 1. Verify that it decrypts to zero + EXPECT(secp256k1_elgamal_decrypt(ctx, &decrypted_amount, &c1_a, &c2_a, + privkey) == 1); + EXPECT(decrypted_amount == 0); - // 2. Verify that the output is deterministic (both ciphertexts are identical) - // We compare the internal structs directly for this test, assuming determinism holds internally. - EXPECT(memcmp(&c1_a, &c1_b, sizeof(c1_a)) == 0); - EXPECT(memcmp(&c2_a, &c2_b, sizeof(c2_a)) == 0); + // 2. Verify that the output is deterministic (both ciphertexts are identical) + // We compare the internal structs directly for this test, assuming + // determinism holds internally. + EXPECT(memcmp(&c1_a, &c1_b, sizeof(c1_a)) == 0); + EXPECT(memcmp(&c2_a, &c2_b, sizeof(c2_a)) == 0); - printf("Test passed!\n"); + printf("Test passed!\n"); } -static void test_verify_encryption(const secp256k1_context* ctx) { - unsigned char privkey[32], blinding_factor[32]; - secp256k1_pubkey pubkey, c1, c2, temp_pubkey; - uint64_t amount = 5000; - uint64_t zero_amount = 0; - - printf("Running test: secp256k1_elgamal_verify_encryption...\n"); - EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); - EXPECT(secp256k1_elgamal_generate_keypair(ctx, blinding_factor, &temp_pubkey) == 1); - - /* 1. Test standard encryption verification */ - EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, amount, blinding_factor) == 1); - EXPECT(secp256k1_elgamal_verify_encryption(ctx, &c1, &c2, &pubkey, amount, blinding_factor) == 1); - - /* 2. Test zero-value encryption verification (The special case we added) */ - EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, zero_amount, blinding_factor) == 1); - EXPECT(secp256k1_elgamal_verify_encryption(ctx, &c1, &c2, &pubkey, zero_amount, blinding_factor) == 1); - - /* 3. Test detection of incorrect amount */ - EXPECT(secp256k1_elgamal_verify_encryption(ctx, &c1, &c2, &pubkey, amount + 1, blinding_factor) == 0); - - /* 4. Test detection of tampered ciphertext */ - c2 = c1; // Force a mismatch - EXPECT(secp256k1_elgamal_verify_encryption(ctx, &c1, &c2, &pubkey, zero_amount, blinding_factor) == 0); - - printf("Test passed!\n"); +static void test_verify_encryption(const secp256k1_context *ctx) +{ + unsigned char privkey[32], blinding_factor[32]; + secp256k1_pubkey pubkey, c1, c2, temp_pubkey; + uint64_t amount = 5000; + uint64_t zero_amount = 0; + + printf("Running test: secp256k1_elgamal_verify_encryption...\n"); + EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); + EXPECT(secp256k1_elgamal_generate_keypair(ctx, blinding_factor, + &temp_pubkey) == 1); + + /* 1. Test standard encryption verification */ + EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, amount, + blinding_factor) == 1); + EXPECT(secp256k1_elgamal_verify_encryption(ctx, &c1, &c2, &pubkey, amount, + blinding_factor) == 1); + + /* 2. Test zero-value encryption verification (The special case we added) */ + EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, zero_amount, + blinding_factor) == 1); + EXPECT(secp256k1_elgamal_verify_encryption( + ctx, &c1, &c2, &pubkey, zero_amount, blinding_factor) == 1); + + /* 3. Test detection of incorrect amount */ + EXPECT(secp256k1_elgamal_verify_encryption(ctx, &c1, &c2, &pubkey, amount + 1, + blinding_factor) == 0); + + /* 4. Test detection of tampered ciphertext */ + c2 = c1; // Force a mismatch + EXPECT(secp256k1_elgamal_verify_encryption( + ctx, &c1, &c2, &pubkey, zero_amount, blinding_factor) == 0); + + printf("Test passed!\n"); } diff --git a/tests/test_elgamal_verify.c b/tests/test_elgamal_verify.c index 931d290..2a885c5 100644 --- a/tests/test_elgamal_verify.c +++ b/tests/test_elgamal_verify.c @@ -1,57 +1,62 @@ +#include "secp256k1_mpt.h" +#include "test_utils.h" +#include #include #include #include -#include -#include "secp256k1_mpt.h" -#include "test_utils.h" - -int main(void) { - // 1. Setup Context - secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - EXPECT(ctx != NULL); - - // Standardized Context Randomization - unsigned char seed[32]; - random_bytes(seed); - EXPECT(secp256k1_context_randomize(ctx, seed) == 1); - - unsigned char priv_q[32], r[32], r_bad[32]; - secp256k1_pubkey pub_q, c1, c2; - uint64_t amount = 12345; - uint64_t amount_bad = 54321; - - printf("DEBUG: Starting ElGamal Verification test...\n"); - - // 2. Generate Identity and Randomness - EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv_q, &pub_q) == 1); - - // REFACTOR: Use standard helper instead of generating a full keypair - random_scalar(ctx, r); - - memcpy(r_bad, r, 32); - r_bad[31] ^= 0xFF; // Flip bits to create a bad randomness factor - - // 3. Perform Encryption - EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pub_q, amount, r) == 1); - printf("DEBUG: Encryption successful.\n"); - - // 4. Test Case 1: Valid Verification (The Happy Path) - int res1 = secp256k1_elgamal_verify_encryption(ctx, &c1, &c2, &pub_q, amount, r); - EXPECT(res1 == 1); - printf("SUCCESS: Valid encryption correctly verified.\n"); - - // 5. Test Case 2: Invalid Amount - int res2 = secp256k1_elgamal_verify_encryption(ctx, &c1, &c2, &pub_q, amount_bad, r); - EXPECT(res2 == 0); - printf("SUCCESS: Incorrect amount correctly rejected.\n"); - - // 6. Test Case 3: Invalid Randomness (r) - int res3 = secp256k1_elgamal_verify_encryption(ctx, &c1, &c2, &pub_q, amount, r_bad); - EXPECT(res3 == 0); - printf("SUCCESS: Incorrect randomness correctly rejected.\n"); - // 7. Cleanup - secp256k1_context_destroy(ctx); - printf("DEBUG: All ElGamal Verification tests passed!\n"); - return 0; +int main(void) +{ + // 1. Setup Context + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | + SECP256K1_CONTEXT_VERIFY); + EXPECT(ctx != NULL); + + // Standardized Context Randomization + unsigned char seed[32]; + random_bytes(seed); + EXPECT(secp256k1_context_randomize(ctx, seed) == 1); + + unsigned char priv_q[32], r[32], r_bad[32]; + secp256k1_pubkey pub_q, c1, c2; + uint64_t amount = 12345; + uint64_t amount_bad = 54321; + + printf("DEBUG: Starting ElGamal Verification test...\n"); + + // 2. Generate Identity and Randomness + EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv_q, &pub_q) == 1); + + // REFACTOR: Use standard helper instead of generating a full keypair + random_scalar(ctx, r); + + memcpy(r_bad, r, 32); + r_bad[31] ^= 0xFF; // Flip bits to create a bad randomness factor + + // 3. Perform Encryption + EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pub_q, amount, r) == 1); + printf("DEBUG: Encryption successful.\n"); + + // 4. Test Case 1: Valid Verification (The Happy Path) + int res1 = + secp256k1_elgamal_verify_encryption(ctx, &c1, &c2, &pub_q, amount, r); + EXPECT(res1 == 1); + printf("SUCCESS: Valid encryption correctly verified.\n"); + + // 5. Test Case 2: Invalid Amount + int res2 = + secp256k1_elgamal_verify_encryption(ctx, &c1, &c2, &pub_q, amount_bad, r); + EXPECT(res2 == 0); + printf("SUCCESS: Incorrect amount correctly rejected.\n"); + + // 6. Test Case 3: Invalid Randomness (r) + int res3 = + secp256k1_elgamal_verify_encryption(ctx, &c1, &c2, &pub_q, amount, r_bad); + EXPECT(res3 == 0); + printf("SUCCESS: Incorrect randomness correctly rejected.\n"); + + // 7. Cleanup + secp256k1_context_destroy(ctx); + printf("DEBUG: All ElGamal Verification tests passed!\n"); + return 0; } diff --git a/tests/test_equality_proof.c b/tests/test_equality_proof.c index 0da5293..fe70a88 100644 --- a/tests/test_equality_proof.c +++ b/tests/test_equality_proof.c @@ -1,180 +1,195 @@ +#include "secp256k1_mpt.h" +#include "test_utils.h" +#include #include #include #include -#include -#include "secp256k1_mpt.h" -#include "test_utils.h" - // Forward declarations for all test functions -static void test_equality_proof_valid(const secp256k1_context* ctx); -static void test_equality_proof_invalid_amount(const secp256k1_context* ctx); -static void test_equality_proof_invalid_tampered(const secp256k1_context* ctx); -static void test_equality_proof_zero_amount(const secp256k1_context* ctx); +static void test_equality_proof_valid(const secp256k1_context *ctx); +static void test_equality_proof_invalid_amount(const secp256k1_context *ctx); +static void test_equality_proof_invalid_tampered(const secp256k1_context *ctx); +static void test_equality_proof_zero_amount(const secp256k1_context *ctx); // Main test runner -int main() { - secp256k1_context* ctx = secp256k1_context_create( - SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - EXPECT(ctx != NULL); - - unsigned char seed[32]; - random_bytes(seed); - EXPECT(secp256k1_context_randomize(ctx, seed) == 1); - - // Run tests for this module - test_equality_proof_valid(ctx); - test_equality_proof_invalid_amount(ctx); - test_equality_proof_invalid_tampered(ctx); - test_equality_proof_zero_amount(ctx); - - secp256k1_context_destroy(ctx); - printf("ALL TESTS PASSED\n"); - return 0; +int main() +{ + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | + SECP256K1_CONTEXT_VERIFY); + EXPECT(ctx != NULL); + + unsigned char seed[32]; + random_bytes(seed); + EXPECT(secp256k1_context_randomize(ctx, seed) == 1); + + // Run tests for this module + test_equality_proof_valid(ctx); + test_equality_proof_invalid_amount(ctx); + test_equality_proof_invalid_tampered(ctx); + test_equality_proof_zero_amount(ctx); + + secp256k1_context_destroy(ctx); + printf("ALL TESTS PASSED\n"); + return 0; } // --- Test Implementations --- // Test case 1: Generate and verify a valid proof -static void test_equality_proof_valid(const secp256k1_context* ctx) { - unsigned char privkey[32]; - secp256k1_pubkey pubkey; - unsigned char randomness_r[32]; - secp256k1_pubkey c1, c2; - uint64_t amount = 98765; - unsigned char tx_context_id[32]; - unsigned char proof[98]; - - printf("Running test: equality proof (valid case)...\n"); - - // 1. Setup: Generate keys, randomness, and encrypt - EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); +static void test_equality_proof_valid(const secp256k1_context *ctx) +{ + unsigned char privkey[32]; + secp256k1_pubkey pubkey; + unsigned char randomness_r[32]; + secp256k1_pubkey c1, c2; + uint64_t amount = 98765; + unsigned char tx_context_id[32]; + unsigned char proof[98]; + + printf("Running test: equality proof (valid case)...\n"); + + // 1. Setup: Generate keys, randomness, and encrypt + EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); + random_bytes(randomness_r); + + // Ensure randomness is a valid scalar (needed for proof gen) + while (secp256k1_ec_seckey_verify(ctx, randomness_r) != 1) + { random_bytes(randomness_r); + } - // Ensure randomness is a valid scalar (needed for proof gen) - while (secp256k1_ec_seckey_verify(ctx, randomness_r) != 1) { - random_bytes(randomness_r); - } - - EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, amount, randomness_r) == 1); - random_bytes(tx_context_id); + EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, amount, + randomness_r) == 1); + random_bytes(tx_context_id); - // 2. Generate the proof - int prove_result = secp256k1_equality_plaintext_prove( - ctx, proof, &c1, &c2, &pubkey, amount, randomness_r, tx_context_id); - EXPECT(prove_result == 1); + // 2. Generate the proof + int prove_result = secp256k1_equality_plaintext_prove( + ctx, proof, &c1, &c2, &pubkey, amount, randomness_r, tx_context_id); + EXPECT(prove_result == 1); - // 3. Verify the proof - int verify_result = secp256k1_equality_plaintext_verify( - ctx, proof, &c1, &c2, &pubkey, amount, tx_context_id); - EXPECT(verify_result == 1); + // 3. Verify the proof + int verify_result = secp256k1_equality_plaintext_verify( + ctx, proof, &c1, &c2, &pubkey, amount, tx_context_id); + EXPECT(verify_result == 1); - printf("Test passed!\n"); + printf("Test passed!\n"); } // Test case 2: Verify a valid proof with the wrong amount -static void test_equality_proof_invalid_amount(const secp256k1_context* ctx) { - unsigned char privkey[32]; - secp256k1_pubkey pubkey; - unsigned char randomness_r[32]; - secp256k1_pubkey c1, c2; - uint64_t correct_amount = 98765; - uint64_t wrong_amount = 11111; // Different amount - unsigned char tx_context_id[32]; - unsigned char proof[98]; - - printf("Running test: equality proof (invalid amount)...\n"); - - // 1. Setup: Generate keys, randomness, encrypt correct amount - EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); +static void test_equality_proof_invalid_amount(const secp256k1_context *ctx) +{ + unsigned char privkey[32]; + secp256k1_pubkey pubkey; + unsigned char randomness_r[32]; + secp256k1_pubkey c1, c2; + uint64_t correct_amount = 98765; + uint64_t wrong_amount = 11111; // Different amount + unsigned char tx_context_id[32]; + unsigned char proof[98]; + + printf("Running test: equality proof (invalid amount)...\n"); + + // 1. Setup: Generate keys, randomness, encrypt correct amount + EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); + random_bytes(randomness_r); + + while (secp256k1_ec_seckey_verify(ctx, randomness_r) != 1) + { random_bytes(randomness_r); + } - while (secp256k1_ec_seckey_verify(ctx, randomness_r) != 1) { - random_bytes(randomness_r); - } - - EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, correct_amount, randomness_r) == 1); - random_bytes(tx_context_id); + EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, correct_amount, + randomness_r) == 1); + random_bytes(tx_context_id); - // 2. Generate the proof for the correct amount - EXPECT(secp256k1_equality_plaintext_prove( - ctx, proof, &c1, &c2, &pubkey, correct_amount, randomness_r, tx_context_id) == 1); + // 2. Generate the proof for the correct amount + EXPECT(secp256k1_equality_plaintext_prove(ctx, proof, &c1, &c2, &pubkey, + correct_amount, randomness_r, + tx_context_id) == 1); - // 3. Verify the proof using the WRONG amount - int verify_result = secp256k1_equality_plaintext_verify( - ctx, proof, &c1, &c2, &pubkey, wrong_amount, tx_context_id); // Pass wrong amount - EXPECT(verify_result == 0); // Verification should fail + // 3. Verify the proof using the WRONG amount + int verify_result = secp256k1_equality_plaintext_verify( + ctx, proof, &c1, &c2, &pubkey, wrong_amount, + tx_context_id); // Pass wrong amount + EXPECT(verify_result == 0); // Verification should fail - printf("Test passed!\n"); + printf("Test passed!\n"); } // Test case 3: Verify a tampered proof -static void test_equality_proof_invalid_tampered(const secp256k1_context* ctx) { - unsigned char privkey[32]; - secp256k1_pubkey pubkey; - unsigned char randomness_r[32]; - secp256k1_pubkey c1, c2; - uint64_t amount = 98765; - unsigned char tx_context_id[32]; - unsigned char proof[98]; - - printf("Running test: equality proof (tampered proof)...\n"); - - // 1. Setup and generate a valid proof - EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); +static void test_equality_proof_invalid_tampered(const secp256k1_context *ctx) +{ + unsigned char privkey[32]; + secp256k1_pubkey pubkey; + unsigned char randomness_r[32]; + secp256k1_pubkey c1, c2; + uint64_t amount = 98765; + unsigned char tx_context_id[32]; + unsigned char proof[98]; + + printf("Running test: equality proof (tampered proof)...\n"); + + // 1. Setup and generate a valid proof + EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); + random_bytes(randomness_r); + + while (secp256k1_ec_seckey_verify(ctx, randomness_r) != 1) + { random_bytes(randomness_r); + } - while (secp256k1_ec_seckey_verify(ctx, randomness_r) != 1) { - random_bytes(randomness_r); - } + EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, amount, + randomness_r) == 1); + random_bytes(tx_context_id); + EXPECT(secp256k1_equality_plaintext_prove(ctx, proof, &c1, &c2, &pubkey, + amount, randomness_r, + tx_context_id) == 1); - EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, amount, randomness_r) == 1); - random_bytes(tx_context_id); - EXPECT(secp256k1_equality_plaintext_prove( - ctx, proof, &c1, &c2, &pubkey, amount, randomness_r, tx_context_id) == 1); + // 2. Tamper with the proof (e.g., flip a bit in the scalar 's') + proof[97] ^= 0x01; // Flip the last bit of the last byte - // 2. Tamper with the proof (e.g., flip a bit in the scalar 's') - proof[97] ^= 0x01; // Flip the last bit of the last byte + // 3. Verify the tampered proof + int verify_result = secp256k1_equality_plaintext_verify( + ctx, proof, &c1, &c2, &pubkey, amount, tx_context_id); + EXPECT(verify_result == 0); // Verification should fail - // 3. Verify the tampered proof - int verify_result = secp256k1_equality_plaintext_verify( - ctx, proof, &c1, &c2, &pubkey, amount, tx_context_id); - EXPECT(verify_result == 0); // Verification should fail - - printf("Test passed!\n"); + printf("Test passed!\n"); } -static void test_equality_proof_zero_amount(const secp256k1_context* ctx) { - unsigned char privkey[32]; - secp256k1_pubkey pubkey; - unsigned char randomness_r[32]; - secp256k1_pubkey c1, c2; - uint64_t amount = 0; // Test the zero case - unsigned char tx_context_id[32]; - unsigned char proof[98]; +static void test_equality_proof_zero_amount(const secp256k1_context *ctx) +{ + unsigned char privkey[32]; + secp256k1_pubkey pubkey; + unsigned char randomness_r[32]; + secp256k1_pubkey c1, c2; + uint64_t amount = 0; // Test the zero case + unsigned char tx_context_id[32]; + unsigned char proof[98]; - printf("Running test: equality proof (zero amount)...\n"); + printf("Running test: equality proof (zero amount)...\n"); - // 1. Setup: Generate keys, randomness, and encrypt - EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); - random_bytes(randomness_r); + // 1. Setup: Generate keys, randomness, and encrypt + EXPECT(secp256k1_elgamal_generate_keypair(ctx, privkey, &pubkey) == 1); + random_bytes(randomness_r); - while (secp256k1_ec_seckey_verify(ctx, randomness_r) != 1) { - random_bytes(randomness_r); - } + while (secp256k1_ec_seckey_verify(ctx, randomness_r) != 1) + { + random_bytes(randomness_r); + } - EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, amount, randomness_r) == 1); - random_bytes(tx_context_id); + EXPECT(secp256k1_elgamal_encrypt(ctx, &c1, &c2, &pubkey, amount, + randomness_r) == 1); + random_bytes(tx_context_id); - // 2. Generate the proof for amount 0 - int prove_result = secp256k1_equality_plaintext_prove( - ctx, proof, &c1, &c2, &pubkey, amount, randomness_r, tx_context_id); - EXPECT(prove_result == 1); + // 2. Generate the proof for amount 0 + int prove_result = secp256k1_equality_plaintext_prove( + ctx, proof, &c1, &c2, &pubkey, amount, randomness_r, tx_context_id); + EXPECT(prove_result == 1); - // 3. Verify the proof for amount 0 - int verify_result = secp256k1_equality_plaintext_verify( - ctx, proof, &c1, &c2, &pubkey, amount, tx_context_id); - EXPECT(verify_result == 1); + // 3. Verify the proof for amount 0 + int verify_result = secp256k1_equality_plaintext_verify( + ctx, proof, &c1, &c2, &pubkey, amount, tx_context_id); + EXPECT(verify_result == 1); - printf("Test passed!\n"); + printf("Test passed!\n"); } diff --git a/tests/test_ipa.c b/tests/test_ipa.c index a7ceacc..6db305a 100644 --- a/tests/test_ipa.c +++ b/tests/test_ipa.c @@ -1,308 +1,293 @@ +#include "secp256k1_mpt.h" +#include "test_utils.h" +#include #include #include #include -#include -#include "secp256k1_mpt.h" -#include "test_utils.h" #define N_BITS 64 /* log2(64) = 6 rounds */ #define IPA_ROUNDS 6 /* ---- Helper Macros ---- */ -static int scalar_is_zero(const unsigned char s[32]) { - unsigned char z[32] = {0}; - return memcmp(s, z, 32) == 0; +static int scalar_is_zero(const unsigned char s[32]) +{ + unsigned char z[32] = {0}; + return memcmp(s, z, 32) == 0; } -/* ---- Forward declarations from your bulletproof_aggregated.c implementation ---- */ +/* ---- Forward declarations from your bulletproof_aggregated.c implementation + * ---- */ -/** NOTE: These signatures must match exactly what is in bulletproof_aggregated.c */ +/** NOTE: These signatures must match exactly what is in + * bulletproof_aggregated.c */ extern int secp256k1_bulletproof_run_ipa_prover( - const secp256k1_context* ctx, - const secp256k1_pubkey* g, - secp256k1_pubkey* G_vec, - secp256k1_pubkey* H_vec, - unsigned char* a_vec, - unsigned char* b_vec, - size_t n, - const unsigned char ipa_transcript_id[32], - const unsigned char ux_scalar[32], - secp256k1_pubkey* L_out, - secp256k1_pubkey* R_out, - size_t max_rounds, - size_t* rounds_out, - unsigned char a_final[32], - unsigned char b_final[32] -); + const secp256k1_context *ctx, const secp256k1_pubkey *g, + secp256k1_pubkey *G_vec, secp256k1_pubkey *H_vec, unsigned char *a_vec, + unsigned char *b_vec, size_t n, const unsigned char ipa_transcript_id[32], + const unsigned char ux_scalar[32], secp256k1_pubkey *L_out, + secp256k1_pubkey *R_out, size_t max_rounds, size_t *rounds_out, + unsigned char a_final[32], unsigned char b_final[32]); /* These helpers are needed for the manual verification logic in this test */ -extern int derive_ipa_round_challenge( - const secp256k1_context* ctx, - unsigned char u_out[32], - const unsigned char last_challenge[32], - const secp256k1_pubkey* L, - const secp256k1_pubkey* R -); - -extern int fold_generators( - const secp256k1_context* ctx, - secp256k1_pubkey* final_point, - const secp256k1_pubkey* generators, - const unsigned char* u_flat, - const unsigned char* uinv_flat, - size_t n, - size_t rounds, - int is_H -); +extern int derive_ipa_round_challenge(const secp256k1_context *ctx, + unsigned char u_out[32], + const unsigned char last_challenge[32], + const secp256k1_pubkey *L, + const secp256k1_pubkey *R); + +extern int fold_generators(const secp256k1_context *ctx, + secp256k1_pubkey *final_point, + const secp256k1_pubkey *generators, + const unsigned char *u_flat, + const unsigned char *uinv_flat, size_t n, + size_t rounds, int is_H); extern int apply_ipa_folding_to_P( - const secp256k1_context* ctx, - secp256k1_pubkey* P, - const secp256k1_pubkey* L_vec, - const secp256k1_pubkey* R_vec, - const unsigned char* u_flat, - const unsigned char* uinv_flat, - size_t rounds -); - -extern int secp256k1_bulletproof_ipa_dot( - const secp256k1_context* ctx, - unsigned char* out, - const unsigned char* a, - const unsigned char* b, - size_t n -); - -static int add_term( - const secp256k1_context* ctx, - secp256k1_pubkey* acc, - int* acc_inited, - const secp256k1_pubkey* term -) { - if (!(*acc_inited)) { - *acc = *term; - *acc_inited = 1; - return 1; - } else { - secp256k1_pubkey sum; - const secp256k1_pubkey* pts[2] = { acc, term }; - if (!secp256k1_ec_pubkey_combine(ctx, &sum, pts, 2)) return 0; - *acc = sum; - return 1; - } + const secp256k1_context *ctx, secp256k1_pubkey *P, + const secp256k1_pubkey *L_vec, const secp256k1_pubkey *R_vec, + const unsigned char *u_flat, const unsigned char *uinv_flat, size_t rounds); + +extern int secp256k1_bulletproof_ipa_dot(const secp256k1_context *ctx, + unsigned char *out, + const unsigned char *a, + const unsigned char *b, size_t n); + +static int add_term(const secp256k1_context *ctx, secp256k1_pubkey *acc, + int *acc_inited, const secp256k1_pubkey *term) +{ + if (!(*acc_inited)) + { + *acc = *term; + *acc_inited = 1; + return 1; + } + else + { + secp256k1_pubkey sum; + const secp256k1_pubkey *pts[2] = {acc, term}; + if (!secp256k1_ec_pubkey_combine(ctx, &sum, pts, 2)) + return 0; + *acc = sum; + return 1; + } } -static int pubkey_equal(const secp256k1_context* ctx, const secp256k1_pubkey* a, const secp256k1_pubkey* b) { - return secp256k1_ec_pubkey_cmp(ctx, a, b) == 0; +static int pubkey_equal(const secp256k1_context *ctx, const secp256k1_pubkey *a, + const secp256k1_pubkey *b) +{ + return secp256k1_ec_pubkey_cmp(ctx, a, b) == 0; } static int test_ipa_verify_explicit( - const secp256k1_context* ctx, - const secp256k1_pubkey* G_vec, - const secp256k1_pubkey* H_vec, - const secp256k1_pubkey* U, - const secp256k1_pubkey* P_in, - const secp256k1_pubkey* L_vec, - const secp256k1_pubkey* R_vec, - size_t n, - const unsigned char a_final[32], - const unsigned char b_final[32], - const unsigned char ux[32], - const unsigned char ipa_transcript_id[32] -) { - secp256k1_pubkey P = *P_in; - secp256k1_pubkey Gf, Hf, RHS, tmp; - int RHS_inited = 0; - int ok = 0; - size_t i; - - /* 1. Calculate Rounds */ - size_t rounds = 0; - size_t tmp_n = n; - while (tmp_n > 1) { tmp_n >>= 1; rounds++; } - - /* 2. Allocate and Derive Challenges */ - unsigned char* u_flat = (unsigned char*)malloc(rounds * 32); - unsigned char* uinv_flat = (unsigned char*)malloc(rounds * 32); - unsigned char last[32]; - - if (!u_flat || !uinv_flat) goto cleanup; - - memcpy(last, ipa_transcript_id, 32); - - for (i = 0; i < rounds; i++) { - unsigned char* ui = u_flat + 32 * i; - unsigned char* uiinv = uinv_flat + 32 * i; - - if (!derive_ipa_round_challenge(ctx, ui, last, &L_vec[i], &R_vec[i])) - goto cleanup; - - secp256k1_mpt_scalar_inverse(uiinv, ui); - memcpy(last, ui, 32); - } - - /* 3. Fold Generators */ - if (!fold_generators(ctx, &Gf, G_vec, u_flat, uinv_flat, n, rounds, 0)) goto cleanup; - if (!fold_generators(ctx, &Hf, H_vec, u_flat, uinv_flat, n, rounds, 1)) goto cleanup; - - /* 4. Compute RHS = a*Gf + b*Hf + (a*b*ux)*U */ - if (!scalar_is_zero(a_final)) { - tmp = Gf; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, a_final)) goto cleanup; - add_term(ctx, &RHS, &RHS_inited, &tmp); - } - - if (!scalar_is_zero(b_final)) { - tmp = Hf; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, b_final)) goto cleanup; - add_term(ctx, &RHS, &RHS_inited, &tmp); - } - + const secp256k1_context *ctx, const secp256k1_pubkey *G_vec, + const secp256k1_pubkey *H_vec, const secp256k1_pubkey *U, + const secp256k1_pubkey *P_in, const secp256k1_pubkey *L_vec, + const secp256k1_pubkey *R_vec, size_t n, const unsigned char a_final[32], + const unsigned char b_final[32], const unsigned char ux[32], + const unsigned char ipa_transcript_id[32]) +{ + secp256k1_pubkey P = *P_in; + secp256k1_pubkey Gf, Hf, RHS, tmp; + int RHS_inited = 0; + int ok = 0; + size_t i; + + /* 1. Calculate Rounds */ + size_t rounds = 0; + size_t tmp_n = n; + while (tmp_n > 1) + { + tmp_n >>= 1; + rounds++; + } + + /* 2. Allocate and Derive Challenges */ + unsigned char *u_flat = (unsigned char *)malloc(rounds * 32); + unsigned char *uinv_flat = (unsigned char *)malloc(rounds * 32); + unsigned char last[32]; + + if (!u_flat || !uinv_flat) + goto cleanup; + + memcpy(last, ipa_transcript_id, 32); + + for (i = 0; i < rounds; i++) + { + unsigned char *ui = u_flat + 32 * i; + unsigned char *uiinv = uinv_flat + 32 * i; + + if (!derive_ipa_round_challenge(ctx, ui, last, &L_vec[i], &R_vec[i])) + goto cleanup; + + secp256k1_mpt_scalar_inverse(uiinv, ui); + memcpy(last, ui, 32); + } + + /* 3. Fold Generators */ + if (!fold_generators(ctx, &Gf, G_vec, u_flat, uinv_flat, n, rounds, 0)) + goto cleanup; + if (!fold_generators(ctx, &Hf, H_vec, u_flat, uinv_flat, n, rounds, 1)) + goto cleanup; + + /* 4. Compute RHS = a*Gf + b*Hf + (a*b*ux)*U */ + if (!scalar_is_zero(a_final)) + { + tmp = Gf; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, a_final)) + goto cleanup; + add_term(ctx, &RHS, &RHS_inited, &tmp); + } + + if (!scalar_is_zero(b_final)) + { + tmp = Hf; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, b_final)) + goto cleanup; + add_term(ctx, &RHS, &RHS_inited, &tmp); + } + + { + unsigned char ab[32], ab_ux[32]; + secp256k1_mpt_scalar_mul(ab, a_final, b_final); + secp256k1_mpt_scalar_mul(ab_ux, ab, ux); + + if (!scalar_is_zero(ab_ux)) { - unsigned char ab[32], ab_ux[32]; - secp256k1_mpt_scalar_mul(ab, a_final, b_final); - secp256k1_mpt_scalar_mul(ab_ux, ab, ux); - - if (!scalar_is_zero(ab_ux)) { - tmp = *U; - if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, ab_ux)) goto cleanup; - add_term(ctx, &RHS, &RHS_inited, &tmp); - } - } - - if (!RHS_inited) goto cleanup; - - /* 5. Fold P */ - if (!apply_ipa_folding_to_P(ctx, &P, L_vec, R_vec, u_flat, uinv_flat, rounds)) + tmp = *U; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, ab_ux)) goto cleanup; - - /* 6. Compare */ - if (pubkey_equal(ctx, &P, &RHS)) { - ok = 1; + add_term(ctx, &RHS, &RHS_inited, &tmp); } - - cleanup: - if (u_flat) free(u_flat); - if (uinv_flat) free(uinv_flat); - return ok; + } + + if (!RHS_inited) + goto cleanup; + + /* 5. Fold P */ + if (!apply_ipa_folding_to_P(ctx, &P, L_vec, R_vec, u_flat, uinv_flat, rounds)) + goto cleanup; + + /* 6. Compare */ + if (pubkey_equal(ctx, &P, &RHS)) + { + ok = 1; + } + +cleanup: + if (u_flat) + free(u_flat); + if (uinv_flat) + free(uinv_flat); + return ok; } -int main(void) { - secp256k1_context* ctx = - secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - EXPECT(ctx != NULL); - - printf("[IPA TEST] Setup...\n"); - - /* 1. Generators */ - secp256k1_pubkey G[N_BITS], H[N_BITS], U; - EXPECT(secp256k1_mpt_get_generator_vector(ctx, G, N_BITS, (unsigned char*)"G", 1) == 1); - EXPECT(secp256k1_mpt_get_generator_vector(ctx, H, N_BITS, (unsigned char*)"H", 1) == 1); - EXPECT(secp256k1_mpt_get_generator_vector(ctx, &U, 1, (unsigned char*)"BP_U", 4) == 1); - - /* Copy for verifier (since prover folds in-place) */ - secp256k1_pubkey G0[N_BITS], H0[N_BITS]; - memcpy(G0, G, sizeof(G)); - memcpy(H0, H, sizeof(H)); - - /* 2. Random Vectors a, b */ - unsigned char a_vec[N_BITS * 32]; - unsigned char b_vec[N_BITS * 32]; - for (int i = 0; i < N_BITS; i++) { - random_scalar(ctx, &a_vec[i*32]); - random_scalar(ctx, &b_vec[i*32]); - } - - /* 3. Dot Product */ - unsigned char dot[32]; - secp256k1_bulletproof_ipa_dot(ctx, dot, a_vec, b_vec, N_BITS); - - /* 4. Transcript & Binding Challenge */ - unsigned char ipa_transcript_id[32]; - SHA256((unsigned char*)"IPA_TEST", 8, ipa_transcript_id); - - unsigned char ux[32]; +int main(void) +{ + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | + SECP256K1_CONTEXT_VERIFY); + EXPECT(ctx != NULL); + + printf("[IPA TEST] Setup...\n"); + + /* 1. Generators */ + secp256k1_pubkey G[N_BITS], H[N_BITS], U; + EXPECT(secp256k1_mpt_get_generator_vector(ctx, G, N_BITS, + (unsigned char *)"G", 1) == 1); + EXPECT(secp256k1_mpt_get_generator_vector(ctx, H, N_BITS, + (unsigned char *)"H", 1) == 1); + EXPECT(secp256k1_mpt_get_generator_vector(ctx, &U, 1, (unsigned char *)"BP_U", + 4) == 1); + + /* Copy for verifier (since prover folds in-place) */ + secp256k1_pubkey G0[N_BITS], H0[N_BITS]; + memcpy(G0, G, sizeof(G)); + memcpy(H0, H, sizeof(H)); + + /* 2. Random Vectors a, b */ + unsigned char a_vec[N_BITS * 32]; + unsigned char b_vec[N_BITS * 32]; + for (int i = 0; i < N_BITS; i++) + { + random_scalar(ctx, &a_vec[i * 32]); + random_scalar(ctx, &b_vec[i * 32]); + } + + /* 3. Dot Product */ + unsigned char dot[32]; + secp256k1_bulletproof_ipa_dot(ctx, dot, a_vec, b_vec, N_BITS); + + /* 4. Transcript & Binding Challenge */ + unsigned char ipa_transcript_id[32]; + SHA256((unsigned char *)"IPA_TEST", 8, ipa_transcript_id); + + unsigned char ux[32]; + { + SHA256_CTX sha; + SHA256_Init(&sha); + SHA256_Update(&sha, ipa_transcript_id, 32); + SHA256_Update(&sha, dot, 32); + SHA256_Final(ux, &sha); + /* Reduce is handled in real code, here just check verify */ + secp256k1_mpt_scalar_reduce32(ux, ux); + } + + /* 5. Build Initial Commitment P = + + *ux*U */ + secp256k1_pubkey P, tmp; + int P_inited = 0; + + for (int i = 0; i < N_BITS; i++) + { + unsigned char *ai = &a_vec[i * 32]; + unsigned char *bi = &b_vec[i * 32]; + + if (!scalar_is_zero(ai)) { - SHA256_CTX sha; - SHA256_Init(&sha); - SHA256_Update(&sha, ipa_transcript_id, 32); - SHA256_Update(&sha, dot, 32); - SHA256_Final(ux, &sha); - /* Reduce is handled in real code, here just check verify */ - secp256k1_mpt_scalar_reduce32(ux, ux); - } - - /* 5. Build Initial Commitment P = + + *ux*U */ - secp256k1_pubkey P, tmp; - int P_inited = 0; - - for (int i = 0; i < N_BITS; i++) { - unsigned char* ai = &a_vec[i*32]; - unsigned char* bi = &b_vec[i*32]; - - if (!scalar_is_zero(ai)) { - tmp = G0[i]; - secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, ai); - add_term(ctx, &P, &P_inited, &tmp); - } - if (!scalar_is_zero(bi)) { - tmp = H0[i]; - secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, bi); - add_term(ctx, &P, &P_inited, &tmp); - } + tmp = G0[i]; + secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, ai); + add_term(ctx, &P, &P_inited, &tmp); } - - /* Add Binding Term: (*ux)*U */ - unsigned char dot_ux[32]; - secp256k1_mpt_scalar_mul(dot_ux, dot, ux); - if (!scalar_is_zero(dot_ux)) { - tmp = U; - secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, dot_ux); - add_term(ctx, &P, &P_inited, &tmp); + if (!scalar_is_zero(bi)) + { + tmp = H0[i]; + secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, bi); + add_term(ctx, &P, &P_inited, &tmp); } - EXPECT(P_inited); - - /* 6. Run Prover */ - printf("[IPA TEST] Running Prover...\n"); - secp256k1_pubkey L[IPA_ROUNDS], R[IPA_ROUNDS]; - unsigned char a_final[32], b_final[32]; - size_t rounds_out = 0; - - int res = secp256k1_bulletproof_run_ipa_prover( - ctx, - &U, - G, H, - a_vec, b_vec, /* These get folded in-place */ - N_BITS, - ipa_transcript_id, - ux, - L, R, - IPA_ROUNDS, - &rounds_out, - a_final, b_final - ); - EXPECT(res == 1); - EXPECT(rounds_out == IPA_ROUNDS); - - /* 7. Run Verifier */ - printf("[IPA TEST] Running Verifier...\n"); - int ok = test_ipa_verify_explicit( - ctx, - G0, H0, - &U, - &P, - L, R, - N_BITS, - a_final, b_final, - ux, - ipa_transcript_id - ); - - printf("[IPA TEST] Result: %s\n", ok ? "PASSED" : "FAILED"); - EXPECT(ok == 1); - - secp256k1_context_destroy(ctx); - return 0; + } + + /* Add Binding Term: (*ux)*U */ + unsigned char dot_ux[32]; + secp256k1_mpt_scalar_mul(dot_ux, dot, ux); + if (!scalar_is_zero(dot_ux)) + { + tmp = U; + secp256k1_ec_pubkey_tweak_mul(ctx, &tmp, dot_ux); + add_term(ctx, &P, &P_inited, &tmp); + } + EXPECT(P_inited); + + /* 6. Run Prover */ + printf("[IPA TEST] Running Prover...\n"); + secp256k1_pubkey L[IPA_ROUNDS], R[IPA_ROUNDS]; + unsigned char a_final[32], b_final[32]; + size_t rounds_out = 0; + + int res = secp256k1_bulletproof_run_ipa_prover( + ctx, &U, G, H, a_vec, b_vec, /* These get folded in-place */ + N_BITS, ipa_transcript_id, ux, L, R, IPA_ROUNDS, &rounds_out, a_final, + b_final); + EXPECT(res == 1); + EXPECT(rounds_out == IPA_ROUNDS); + + /* 7. Run Verifier */ + printf("[IPA TEST] Running Verifier...\n"); + int ok = test_ipa_verify_explicit(ctx, G0, H0, &U, &P, L, R, N_BITS, a_final, + b_final, ux, ipa_transcript_id); + + printf("[IPA TEST] Result: %s\n", ok ? "PASSED" : "FAILED"); + EXPECT(ok == 1); + + secp256k1_context_destroy(ctx); + return 0; } diff --git a/tests/test_link_proof.c b/tests/test_link_proof.c index 76ba308..bd6f921 100644 --- a/tests/test_link_proof.c +++ b/tests/test_link_proof.c @@ -1,88 +1,90 @@ -#include -#include -#include #include "secp256k1_mpt.h" -#include #include "test_utils.h" +#include +#include +#include +#include -void test_link_proof() { - secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - EXPECT(ctx != NULL); - - unsigned char sk[32], r[32], rho[32], context_id[32]; - uint64_t amount = 12345; - secp256k1_pubkey pk, c1, c2, pcm; - unsigned char proof[195]; - - printf("DEBUG: Starting Link Proof test with NUMS generators...\n"); - - // 1. Setup Keys and Randomness - random_scalar(ctx, sk); - EXPECT(secp256k1_ec_pubkey_create(ctx, &pk, sk)); - - random_scalar(ctx, r); - random_scalar(ctx, rho); - EXPECT(RAND_bytes(context_id, 32) == 1); - - // 2. Create ElGamal Ciphertext (C1, C2) manually for the test - // C1 = r * G - // Note: ec_pubkey_create computes scalar * G - EXPECT(secp256k1_ec_pubkey_create(ctx, &c1, r)); - - // C2 = m * G + r * Pk - secp256k1_pubkey mG, rPk; - - // Convert amount to scalar (big endian) - unsigned char m_scalar[32] = {0}; - for(int i=0; i<8; i++) { - m_scalar[31-i] = (amount >> (i*8)) & 0xFF; - } - - // mG = m * G - EXPECT(secp256k1_ec_pubkey_create(ctx, &mG, m_scalar)); - - // rPk = r * Pk - rPk = pk; // Copy pk first - EXPECT(secp256k1_ec_pubkey_tweak_mul(ctx, &rPk, r)); - - // Combine: C2 = mG + rPk - const secp256k1_pubkey* addends[] = {&mG, &rPk}; - EXPECT(secp256k1_ec_pubkey_combine(ctx, &c2, addends, 2)); - - // 3. Create Pedersen Commitment (PCm) - // Using the NEW centralized function that uses NUMS H internally - // Note: The library function handles the commitment logic (m*G + rho*H) - EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pcm, amount, rho)); - - // 4. Generate Proof - // This proves that (C1, C2) and PCm commit to the same 'amount' - EXPECT(secp256k1_elgamal_pedersen_link_prove( - ctx, proof, &c1, &c2, &pk, &pcm, amount, r, rho, context_id - ) == 1); - printf("SUCCESS: Link Proof generated.\n"); - - // 5. Verify Proof - EXPECT(secp256k1_elgamal_pedersen_link_verify( - ctx, proof, &c1, &c2, &pk, &pcm, context_id - ) == 1); - printf("SUCCESS: Link Proof verified against NUMS H.\n"); - - // 6. Test Failure (Wrong Amount) - // Create a commitment to (amount + 1) using the SAME blinding factor - secp256k1_pubkey pcm_wrong; - EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pcm_wrong, amount + 1, rho)); - - // Verification should fail because PCm doesn't match the amount in ElGamal ciphertext - EXPECT(secp256k1_elgamal_pedersen_link_verify( - ctx, proof, &c1, &c2, &pk, &pcm_wrong, context_id - ) == 0); - printf("SUCCESS: Invalid commitment correctly rejected.\n"); - - secp256k1_context_destroy(ctx); +void test_link_proof() +{ + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | + SECP256K1_CONTEXT_VERIFY); + EXPECT(ctx != NULL); + + unsigned char sk[32], r[32], rho[32], context_id[32]; + uint64_t amount = 12345; + secp256k1_pubkey pk, c1, c2, pcm; + unsigned char proof[195]; + + printf("DEBUG: Starting Link Proof test with NUMS generators...\n"); + + // 1. Setup Keys and Randomness + random_scalar(ctx, sk); + EXPECT(secp256k1_ec_pubkey_create(ctx, &pk, sk)); + + random_scalar(ctx, r); + random_scalar(ctx, rho); + EXPECT(RAND_bytes(context_id, 32) == 1); + + // 2. Create ElGamal Ciphertext (C1, C2) manually for the test + // C1 = r * G + // Note: ec_pubkey_create computes scalar * G + EXPECT(secp256k1_ec_pubkey_create(ctx, &c1, r)); + + // C2 = m * G + r * Pk + secp256k1_pubkey mG, rPk; + + // Convert amount to scalar (big endian) + unsigned char m_scalar[32] = {0}; + for (int i = 0; i < 8; i++) + { + m_scalar[31 - i] = (amount >> (i * 8)) & 0xFF; + } + + // mG = m * G + EXPECT(secp256k1_ec_pubkey_create(ctx, &mG, m_scalar)); + + // rPk = r * Pk + rPk = pk; // Copy pk first + EXPECT(secp256k1_ec_pubkey_tweak_mul(ctx, &rPk, r)); + + // Combine: C2 = mG + rPk + const secp256k1_pubkey *addends[] = {&mG, &rPk}; + EXPECT(secp256k1_ec_pubkey_combine(ctx, &c2, addends, 2)); + + // 3. Create Pedersen Commitment (PCm) + // Using the NEW centralized function that uses NUMS H internally + // Note: The library function handles the commitment logic (m*G + rho*H) + EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pcm, amount, rho)); + + // 4. Generate Proof + // This proves that (C1, C2) and PCm commit to the same 'amount' + EXPECT(secp256k1_elgamal_pedersen_link_prove( + ctx, proof, &c1, &c2, &pk, &pcm, amount, r, rho, context_id) == 1); + printf("SUCCESS: Link Proof generated.\n"); + + // 5. Verify Proof + EXPECT(secp256k1_elgamal_pedersen_link_verify(ctx, proof, &c1, &c2, &pk, &pcm, + context_id) == 1); + printf("SUCCESS: Link Proof verified against NUMS H.\n"); + + // 6. Test Failure (Wrong Amount) + // Create a commitment to (amount + 1) using the SAME blinding factor + secp256k1_pubkey pcm_wrong; + EXPECT(secp256k1_mpt_pedersen_commit(ctx, &pcm_wrong, amount + 1, rho)); + + // Verification should fail because PCm doesn't match the amount in ElGamal + // ciphertext + EXPECT(secp256k1_elgamal_pedersen_link_verify(ctx, proof, &c1, &c2, &pk, + &pcm_wrong, context_id) == 0); + printf("SUCCESS: Invalid commitment correctly rejected.\n"); + + secp256k1_context_destroy(ctx); } -int main() { - test_link_proof(); - printf("ALL TESTS PASSED\n"); - return 0; +int main() +{ + test_link_proof(); + printf("ALL TESTS PASSED\n"); + return 0; } diff --git a/tests/test_pok_sk.c b/tests/test_pok_sk.c index 8a7d236..5afc365 100644 --- a/tests/test_pok_sk.c +++ b/tests/test_pok_sk.c @@ -1,56 +1,61 @@ -#include -#include -#include #include "secp256k1_mpt.h" -#include #include "test_utils.h" +#include +#include +#include +#include -void test_pok_sk() { - secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - EXPECT(ctx != NULL); +void test_pok_sk() +{ + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | + SECP256K1_CONTEXT_VERIFY); + EXPECT(ctx != NULL); - unsigned char sk[32], context_id[32]; - unsigned char proof[65]; // Schnorr proof is 64 bytes + 1 byte header usually, or just 64 depending on impl. - // The previous code used 65, so we keep 65. - secp256k1_pubkey pk; + unsigned char sk[32], context_id[32]; + unsigned char proof[65]; // Schnorr proof is 64 bytes + 1 byte header usually, + // or just 64 depending on impl. + // The previous code used 65, so we keep 65. + secp256k1_pubkey pk; - printf("DEBUG: Starting PoK SK Registration test...\n"); + printf("DEBUG: Starting PoK SK Registration test...\n"); - // Setup: Generate keypair and random context - random_scalar(ctx, sk); - EXPECT(secp256k1_ec_pubkey_create(ctx, &pk, sk)); + // Setup: Generate keypair and random context + random_scalar(ctx, sk); + EXPECT(secp256k1_ec_pubkey_create(ctx, &pk, sk)); - EXPECT(RAND_bytes(context_id, 32) == 1); + EXPECT(RAND_bytes(context_id, 32) == 1); - // Test 1: Generate and Verify Valid Proof - // Returns 1 on success - EXPECT(secp256k1_mpt_pok_sk_prove(ctx, proof, &pk, sk, context_id) == 1); + // Test 1: Generate and Verify Valid Proof + // Returns 1 on success + EXPECT(secp256k1_mpt_pok_sk_prove(ctx, proof, &pk, sk, context_id) == 1); - // Returns 1 on success - EXPECT(secp256k1_mpt_pok_sk_verify(ctx, proof, &pk, context_id) == 1); - printf("SUCCESS: Valid PoK verified.\n"); + // Returns 1 on success + EXPECT(secp256k1_mpt_pok_sk_verify(ctx, proof, &pk, context_id) == 1); + printf("SUCCESS: Valid PoK verified.\n"); - // Test 2: Invalid Context - // Proof should bind to the specific context_id. Changing it should fail verification. - unsigned char wrong_context[32]; - memcpy(wrong_context, context_id, 32); - wrong_context[0] ^= 0xFF; // Corrupt the context + // Test 2: Invalid Context + // Proof should bind to the specific context_id. Changing it should fail + // verification. + unsigned char wrong_context[32]; + memcpy(wrong_context, context_id, 32); + wrong_context[0] ^= 0xFF; // Corrupt the context - EXPECT(secp256k1_mpt_pok_sk_verify(ctx, proof, &pk, wrong_context) == 0); - printf("SUCCESS: Invalid context correctly rejected.\n"); + EXPECT(secp256k1_mpt_pok_sk_verify(ctx, proof, &pk, wrong_context) == 0); + printf("SUCCESS: Invalid context correctly rejected.\n"); - // Test 3: Corrupted Proof Scalar - // Corrupt the last byte of the proof signature/scalar - proof[64] ^= 0xFF; + // Test 3: Corrupted Proof Scalar + // Corrupt the last byte of the proof signature/scalar + proof[64] ^= 0xFF; - EXPECT(secp256k1_mpt_pok_sk_verify(ctx, proof, &pk, context_id) == 0); - printf("SUCCESS: Corrupted proof correctly rejected.\n"); + EXPECT(secp256k1_mpt_pok_sk_verify(ctx, proof, &pk, context_id) == 0); + printf("SUCCESS: Corrupted proof correctly rejected.\n"); - secp256k1_context_destroy(ctx); + secp256k1_context_destroy(ctx); } -int main() { - test_pok_sk(); - printf("ALL TESTS PASSED\n"); - return 0; +int main() +{ + test_pok_sk(); + printf("ALL TESTS PASSED\n"); + return 0; } diff --git a/tests/test_same_plaintext.c b/tests/test_same_plaintext.c index 9de13ee..1402ae8 100644 --- a/tests/test_same_plaintext.c +++ b/tests/test_same_plaintext.c @@ -1,154 +1,152 @@ +#include "secp256k1_mpt.h" +#include "test_utils.h" +#include #include #include #include -#include -#include "secp256k1_mpt.h" -#include "test_utils.h" /** * Test 1: Valid proof generation and verification. */ -static void test_same_plaintext_valid(const secp256k1_context* ctx) { - unsigned char priv_1[32], priv_2[32]; - secp256k1_pubkey pub_1, pub_2; - unsigned char r1[32], r2[32]; - unsigned char tx_context_id[32]; - uint64_t amount_m = 123456; - - secp256k1_pubkey R1, S1, R2, S2; - unsigned char proof[261]; - - printf("Running test: same plaintext proof (valid case)...\n"); - - // 1. Setup: Generate keys and randomness - EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv_1, &pub_1) == 1); - EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv_2, &pub_2) == 1); - - // Use standardized helper (handles errors internally) - random_scalar(ctx, r1); - random_scalar(ctx, r2); - random_scalar(ctx, tx_context_id); - - // 2. Encrypt the same amount - EXPECT(secp256k1_elgamal_encrypt(ctx, &R1, &S1, &pub_1, amount_m, r1) == 1); - EXPECT(secp256k1_elgamal_encrypt(ctx, &R2, &S2, &pub_2, amount_m, r2) == 1); - - printf("Generating proof...\n"); - // 3. Generate the proof - EXPECT(secp256k1_mpt_prove_same_plaintext( - ctx, proof, - &R1, &S1, &pub_1, - &R2, &S2, &pub_2, - amount_m, r1, r2, tx_context_id - ) == 1); - - printf("Verifying proof...\n"); - // 4. Verify the proof - EXPECT(secp256k1_mpt_verify_same_plaintext( - ctx, proof, - &R1, &S1, &pub_1, - &R2, &S2, &pub_2, - tx_context_id - ) == 1); - - printf("Test passed!\n"); +static void test_same_plaintext_valid(const secp256k1_context *ctx) +{ + unsigned char priv_1[32], priv_2[32]; + secp256k1_pubkey pub_1, pub_2; + unsigned char r1[32], r2[32]; + unsigned char tx_context_id[32]; + uint64_t amount_m = 123456; + + secp256k1_pubkey R1, S1, R2, S2; + unsigned char proof[261]; + + printf("Running test: same plaintext proof (valid case)...\n"); + + // 1. Setup: Generate keys and randomness + EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv_1, &pub_1) == 1); + EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv_2, &pub_2) == 1); + + // Use standardized helper (handles errors internally) + random_scalar(ctx, r1); + random_scalar(ctx, r2); + random_scalar(ctx, tx_context_id); + + // 2. Encrypt the same amount + EXPECT(secp256k1_elgamal_encrypt(ctx, &R1, &S1, &pub_1, amount_m, r1) == 1); + EXPECT(secp256k1_elgamal_encrypt(ctx, &R2, &S2, &pub_2, amount_m, r2) == 1); + + printf("Generating proof...\n"); + // 3. Generate the proof + EXPECT(secp256k1_mpt_prove_same_plaintext(ctx, proof, &R1, &S1, &pub_1, &R2, + &S2, &pub_2, amount_m, r1, r2, + tx_context_id) == 1); + + printf("Verifying proof...\n"); + // 4. Verify the proof + EXPECT(secp256k1_mpt_verify_same_plaintext(ctx, proof, &R1, &S1, &pub_1, &R2, + &S2, &pub_2, tx_context_id) == 1); + + printf("Test passed!\n"); } /** * Test 2: Verifying a tampered proof (should fail). */ -static void test_same_plaintext_tampered_proof(const secp256k1_context* ctx) { - unsigned char priv_1[32], priv_2[32]; - secp256k1_pubkey pub_1, pub_2; - unsigned char r1[32], r2[32]; - unsigned char tx_context_id[32]; - uint64_t amount_m = 123456; - secp256k1_pubkey R1, S1, R2, S2; - unsigned char proof[261]; +static void test_same_plaintext_tampered_proof(const secp256k1_context *ctx) +{ + unsigned char priv_1[32], priv_2[32]; + secp256k1_pubkey pub_1, pub_2; + unsigned char r1[32], r2[32]; + unsigned char tx_context_id[32]; + uint64_t amount_m = 123456; + secp256k1_pubkey R1, S1, R2, S2; + unsigned char proof[261]; - printf("Running test: same plaintext proof (tampered proof)...\n"); + printf("Running test: same plaintext proof (tampered proof)...\n"); - EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv_1, &pub_1) == 1); - EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv_2, &pub_2) == 1); + EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv_1, &pub_1) == 1); + EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv_2, &pub_2) == 1); - random_scalar(ctx, r1); - random_scalar(ctx, r2); - random_scalar(ctx, tx_context_id); + random_scalar(ctx, r1); + random_scalar(ctx, r2); + random_scalar(ctx, tx_context_id); - EXPECT(secp256k1_elgamal_encrypt(ctx, &R1, &S1, &pub_1, amount_m, r1) == 1); - EXPECT(secp256k1_elgamal_encrypt(ctx, &R2, &S2, &pub_2, amount_m, r2) == 1); + EXPECT(secp256k1_elgamal_encrypt(ctx, &R1, &S1, &pub_1, amount_m, r1) == 1); + EXPECT(secp256k1_elgamal_encrypt(ctx, &R2, &S2, &pub_2, amount_m, r2) == 1); - EXPECT(secp256k1_mpt_prove_same_plaintext( - ctx, proof, &R1, &S1, &pub_1, &R2, &S2, &pub_2, - amount_m, r1, r2, tx_context_id) == 1); + EXPECT(secp256k1_mpt_prove_same_plaintext(ctx, proof, &R1, &S1, &pub_1, &R2, + &S2, &pub_2, amount_m, r1, r2, + tx_context_id) == 1); - // Tamper with the proof - proof[42] ^= 0x01; + // Tamper with the proof + proof[42] ^= 0x01; - // Verify should fail (return 0) - int result = secp256k1_mpt_verify_same_plaintext( - ctx, proof, &R1, &S1, &pub_1, &R2, &S2, &pub_2, tx_context_id); + // Verify should fail (return 0) + int result = secp256k1_mpt_verify_same_plaintext( + ctx, proof, &R1, &S1, &pub_1, &R2, &S2, &pub_2, tx_context_id); - EXPECT(result == 0); + EXPECT(result == 0); - printf("Test passed!\n"); + printf("Test passed!\n"); } /** * Test 3: Verifying with different-amount ciphertexts (should fail). */ -static void test_same_plaintext_wrong_ciphertext(const secp256k1_context* ctx) { - unsigned char priv_1[32], priv_2[32]; - secp256k1_pubkey pub_1, pub_2; - unsigned char r1[32], r2[32], r3[32]; - unsigned char tx_context_id[32]; - uint64_t amount_m1 = 123456; - uint64_t amount_m2 = 777777; +static void test_same_plaintext_wrong_ciphertext(const secp256k1_context *ctx) +{ + unsigned char priv_1[32], priv_2[32]; + secp256k1_pubkey pub_1, pub_2; + unsigned char r1[32], r2[32], r3[32]; + unsigned char tx_context_id[32]; + uint64_t amount_m1 = 123456; + uint64_t amount_m2 = 777777; - secp256k1_pubkey R1, S1, R2, S2; - secp256k1_pubkey R3, S3; - unsigned char proof[261]; + secp256k1_pubkey R1, S1, R2, S2; + secp256k1_pubkey R3, S3; + unsigned char proof[261]; - printf("Running test: same plaintext proof (wrong ciphertext)...\n"); + printf("Running test: same plaintext proof (wrong ciphertext)...\n"); - EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv_1, &pub_1) == 1); - EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv_2, &pub_2) == 1); + EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv_1, &pub_1) == 1); + EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv_2, &pub_2) == 1); - random_scalar(ctx, r1); - random_scalar(ctx, r2); - random_scalar(ctx, r3); - random_scalar(ctx, tx_context_id); + random_scalar(ctx, r1); + random_scalar(ctx, r2); + random_scalar(ctx, r3); + random_scalar(ctx, tx_context_id); - EXPECT(secp256k1_elgamal_encrypt(ctx, &R1, &S1, &pub_1, amount_m1, r1) == 1); - EXPECT(secp256k1_elgamal_encrypt(ctx, &R2, &S2, &pub_2, amount_m1, r2) == 1); - EXPECT(secp256k1_elgamal_encrypt(ctx, &R3, &S3, &pub_2, amount_m2, r3) == 1); + EXPECT(secp256k1_elgamal_encrypt(ctx, &R1, &S1, &pub_1, amount_m1, r1) == 1); + EXPECT(secp256k1_elgamal_encrypt(ctx, &R2, &S2, &pub_2, amount_m1, r2) == 1); + EXPECT(secp256k1_elgamal_encrypt(ctx, &R3, &S3, &pub_2, amount_m2, r3) == 1); - // Generate valid proof for m1 - EXPECT(secp256k1_mpt_prove_same_plaintext( - ctx, proof, &R1, &S1, &pub_1, &R2, &S2, &pub_2, - amount_m1, r1, r2, tx_context_id) == 1); + // Generate valid proof for m1 + EXPECT(secp256k1_mpt_prove_same_plaintext(ctx, proof, &R1, &S1, &pub_1, &R2, + &S2, &pub_2, amount_m1, r1, r2, + tx_context_id) == 1); - // Verify against R3/S3 (which is m2) - Should fail - int result = secp256k1_mpt_verify_same_plaintext( - ctx, proof, &R1, &S1, &pub_1, &R3, &S3, &pub_2, tx_context_id); + // Verify against R3/S3 (which is m2) - Should fail + int result = secp256k1_mpt_verify_same_plaintext( + ctx, proof, &R1, &S1, &pub_1, &R3, &S3, &pub_2, tx_context_id); - EXPECT(result == 0); + EXPECT(result == 0); - printf("Test passed!\n"); + printf("Test passed!\n"); } -int main(void) { - secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - EXPECT(ctx != NULL); +int main(void) +{ + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | + SECP256K1_CONTEXT_VERIFY); + EXPECT(ctx != NULL); - unsigned char seed[32]; - random_bytes(seed); - EXPECT(secp256k1_context_randomize(ctx, seed) == 1); + unsigned char seed[32]; + random_bytes(seed); + EXPECT(secp256k1_context_randomize(ctx, seed) == 1); - test_same_plaintext_valid(ctx); - test_same_plaintext_tampered_proof(ctx); - test_same_plaintext_wrong_ciphertext(ctx); + test_same_plaintext_valid(ctx); + test_same_plaintext_tampered_proof(ctx); + test_same_plaintext_wrong_ciphertext(ctx); - secp256k1_context_destroy(ctx); - return 0; + secp256k1_context_destroy(ctx); + return 0; } diff --git a/tests/test_same_plaintext_multi.c b/tests/test_same_plaintext_multi.c index 72153f9..4fd27ff 100644 --- a/tests/test_same_plaintext_multi.c +++ b/tests/test_same_plaintext_multi.c @@ -1,200 +1,209 @@ -#include -#include -#include -#include #include "secp256k1_mpt.h" #include "test_utils.h" +#include +#include +#include +#include /* --- Test Cases --- */ /** - * Test 1: Generates N ciphertexts encrypting the SAME amount and verifies the proof. + * Test 1: Generates N ciphertexts encrypting the SAME amount and verifies the + * proof. */ -static void test_valid_multi_proof(const secp256k1_context* ctx, size_t n) { - printf("Running test: same plaintext proof (N=%zu)... ", n); - - // C99 Variable Length Arrays (VLAs) for cleaner test code - // Note: MSVC might not support VLAs, but standard GCC/Clang does. - // If strict C90 compliance is needed, use malloc here. - secp256k1_pubkey* Pk = malloc(n * sizeof(secp256k1_pubkey)); - secp256k1_pubkey* R = malloc(n * sizeof(secp256k1_pubkey)); - secp256k1_pubkey* S = malloc(n * sizeof(secp256k1_pubkey)); - unsigned char* r = malloc(n * 32); // Randomness scalars flattened - - EXPECT(Pk && R && S && r); - - unsigned char tx_id[32]; - uint64_t amount = 555666; - unsigned char* proof; - size_t proof_len; - size_t i; - - // 1. Setup: Keys and Randomness - random_scalar(ctx, tx_id); - for (i = 0; i < n; ++i) { - unsigned char priv[32]; - EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv, &Pk[i]) == 1); - random_scalar(ctx, &r[i*32]); - } - - // 2. Encrypt the SAME amount N times - for (i = 0; i < n; ++i) { - EXPECT(secp256k1_elgamal_encrypt(ctx, &R[i], &S[i], &Pk[i], amount, &r[i*32]) == 1); - } - - // 3. Generate Proof - proof_len = secp256k1_mpt_prove_same_plaintext_multi_size(n); - proof = (unsigned char*)malloc(proof_len); - EXPECT(proof != NULL); - - size_t out_len = proof_len; - EXPECT(secp256k1_mpt_prove_same_plaintext_multi( - ctx, proof, &out_len, amount, n, - R, S, Pk, r, tx_id - ) == 1); - EXPECT(out_len == proof_len); - - // 4. Verify Proof - EXPECT(secp256k1_mpt_verify_same_plaintext_multi( - ctx, proof, proof_len, n, - R, S, Pk, tx_id - ) == 1); - - free(proof); - free(Pk); - free(R); - free(S); - free(r); - printf("Passed\n"); +static void test_valid_multi_proof(const secp256k1_context *ctx, size_t n) +{ + printf("Running test: same plaintext proof (N=%zu)... ", n); + + // C99 Variable Length Arrays (VLAs) for cleaner test code + // Note: MSVC might not support VLAs, but standard GCC/Clang does. + // If strict C90 compliance is needed, use malloc here. + secp256k1_pubkey *Pk = malloc(n * sizeof(secp256k1_pubkey)); + secp256k1_pubkey *R = malloc(n * sizeof(secp256k1_pubkey)); + secp256k1_pubkey *S = malloc(n * sizeof(secp256k1_pubkey)); + unsigned char *r = malloc(n * 32); // Randomness scalars flattened + + EXPECT(Pk && R && S && r); + + unsigned char tx_id[32]; + uint64_t amount = 555666; + unsigned char *proof; + size_t proof_len; + size_t i; + + // 1. Setup: Keys and Randomness + random_scalar(ctx, tx_id); + for (i = 0; i < n; ++i) + { + unsigned char priv[32]; + EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv, &Pk[i]) == 1); + random_scalar(ctx, &r[i * 32]); + } + + // 2. Encrypt the SAME amount N times + for (i = 0; i < n; ++i) + { + EXPECT(secp256k1_elgamal_encrypt(ctx, &R[i], &S[i], &Pk[i], amount, + &r[i * 32]) == 1); + } + + // 3. Generate Proof + proof_len = secp256k1_mpt_prove_same_plaintext_multi_size(n); + proof = (unsigned char *)malloc(proof_len); + EXPECT(proof != NULL); + + size_t out_len = proof_len; + EXPECT(secp256k1_mpt_prove_same_plaintext_multi(ctx, proof, &out_len, amount, + n, R, S, Pk, r, tx_id) == 1); + EXPECT(out_len == proof_len); + + // 4. Verify Proof + EXPECT(secp256k1_mpt_verify_same_plaintext_multi(ctx, proof, proof_len, n, R, + S, Pk, tx_id) == 1); + + free(proof); + free(Pk); + free(R); + free(S); + free(r); + printf("Passed\n"); } /** * Test 2: Mismatched amounts. - * Encrypts DIFFERENT amounts, attempts to generate a proof for one, checks verification fails. + * Encrypts DIFFERENT amounts, attempts to generate a proof for one, checks + * verification fails. */ -static void test_different_amounts_fail(const secp256k1_context* ctx) { - printf("Running test: different amounts (should fail)... "); - - size_t n = 2; - secp256k1_pubkey Pk[2], R[2], S[2]; - unsigned char r[2][32]; - unsigned char tx_id[32]; - - uint64_t amount_1 = 100; - uint64_t amount_2 = 200; // Different! - - random_scalar(ctx, tx_id); - for (int i = 0; i < 2; ++i) { - unsigned char priv[32]; - EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv, &Pk[i]) == 1); - random_scalar(ctx, r[i]); - } - - // Encrypt DIFFERENT amounts - EXPECT(secp256k1_elgamal_encrypt(ctx, &R[0], &S[0], &Pk[0], amount_1, r[0]) == 1); - EXPECT(secp256k1_elgamal_encrypt(ctx, &R[1], &S[1], &Pk[1], amount_2, r[1]) == 1); - - size_t proof_len = secp256k1_mpt_prove_same_plaintext_multi_size(n); - unsigned char* proof = (unsigned char*)malloc(proof_len); - EXPECT(proof != NULL); - - unsigned char r_flat[64]; - memcpy(&r_flat[0], r[0], 32); - memcpy(&r_flat[32], r[1], 32); - - // PROVER: We try to prove they both equal amount_1. - // The prover function will technically generate a valid proof math-wise - // based on the inputs we GIVE it (amount_1, r1, r2), - // but it won't match the actual C2 ciphertext because C2 uses amount_2. - // However, depending on implementation, the prover might succeed (generating a valid Sigma proof for the scalars provided), - // but the VERIFIER must catch the discrepancy against the public keys/ciphertexts. - size_t out_len = proof_len; - // We assume the prover succeeds in generating *something* (it doesn't validate ciphertexts, just scalars usually) - int prove_res = secp256k1_mpt_prove_same_plaintext_multi( - ctx, proof, &out_len, amount_1, n, - R, S, Pk, r_flat, tx_id - ); - // If prover is smart and checks consistency, it might fail here. - // If it's just a Sigma protocol engine, it might succeed. - // We only care that the SYSTEM fails eventually. - - if (prove_res == 1) { - // VERIFIER: Should fail because the proof corresponds to (amount_1, amount_1) - // but the public ciphertexts correspond to (amount_1, amount_2). - int verify_result = secp256k1_mpt_verify_same_plaintext_multi( - ctx, proof, proof_len, n, - R, S, Pk, tx_id - ); - EXPECT(verify_result == 0); // Must fail - } - - free(proof); - printf("Passed\n"); +static void test_different_amounts_fail(const secp256k1_context *ctx) +{ + printf("Running test: different amounts (should fail)... "); + + size_t n = 2; + secp256k1_pubkey Pk[2], R[2], S[2]; + unsigned char r[2][32]; + unsigned char tx_id[32]; + + uint64_t amount_1 = 100; + uint64_t amount_2 = 200; // Different! + + random_scalar(ctx, tx_id); + for (int i = 0; i < 2; ++i) + { + unsigned char priv[32]; + EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv, &Pk[i]) == 1); + random_scalar(ctx, r[i]); + } + + // Encrypt DIFFERENT amounts + EXPECT(secp256k1_elgamal_encrypt(ctx, &R[0], &S[0], &Pk[0], amount_1, r[0]) == + 1); + EXPECT(secp256k1_elgamal_encrypt(ctx, &R[1], &S[1], &Pk[1], amount_2, r[1]) == + 1); + + size_t proof_len = secp256k1_mpt_prove_same_plaintext_multi_size(n); + unsigned char *proof = (unsigned char *)malloc(proof_len); + EXPECT(proof != NULL); + + unsigned char r_flat[64]; + memcpy(&r_flat[0], r[0], 32); + memcpy(&r_flat[32], r[1], 32); + + // PROVER: We try to prove they both equal amount_1. + // The prover function will technically generate a valid proof math-wise + // based on the inputs we GIVE it (amount_1, r1, r2), + // but it won't match the actual C2 ciphertext because C2 uses amount_2. + // However, depending on implementation, the prover might succeed (generating + // a valid Sigma proof for the scalars provided), but the VERIFIER must catch + // the discrepancy against the public keys/ciphertexts. + size_t out_len = proof_len; + // We assume the prover succeeds in generating *something* (it doesn't + // validate ciphertexts, just scalars usually) + int prove_res = secp256k1_mpt_prove_same_plaintext_multi( + ctx, proof, &out_len, amount_1, n, R, S, Pk, r_flat, tx_id); + // If prover is smart and checks consistency, it might fail here. + // If it's just a Sigma protocol engine, it might succeed. + // We only care that the SYSTEM fails eventually. + + if (prove_res == 1) + { + // VERIFIER: Should fail because the proof corresponds to (amount_1, + // amount_1) but the public ciphertexts correspond to (amount_1, amount_2). + int verify_result = secp256k1_mpt_verify_same_plaintext_multi( + ctx, proof, proof_len, n, R, S, Pk, tx_id); + EXPECT(verify_result == 0); // Must fail + } + + free(proof); + printf("Passed\n"); } /** * Test 3: Tampered proof. * Generates valid proof, flips a bit, checks verification fails. */ -static void test_tampered_proof_fail(const secp256k1_context* ctx) { - printf("Running test: tampered proof (should fail)... "); - - size_t n = 2; - secp256k1_pubkey Pk[2], R[2], S[2]; - unsigned char r[64]; - unsigned char tx_id[32]; - uint64_t amount = 500; - - // Setup valid scenario - random_scalar(ctx, tx_id); - for(int i=0; i<2; ++i) { - unsigned char priv[32]; - EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv, &Pk[i]) == 1); - random_scalar(ctx, &r[i*32]); - EXPECT(secp256k1_elgamal_encrypt(ctx, &R[i], &S[i], &Pk[i], amount, &r[i*32]) == 1); - } - - size_t proof_len = secp256k1_mpt_prove_same_plaintext_multi_size(n); - unsigned char* proof = (unsigned char*)malloc(proof_len); - EXPECT(proof != NULL); - size_t out_len = proof_len; - - EXPECT(secp256k1_mpt_prove_same_plaintext_multi( - ctx, proof, &out_len, amount, n, R, S, Pk, r, tx_id) == 1); - - // Tamper: Flip a bit in the middle of the proof - proof[proof_len / 2] ^= 0xFF; - - EXPECT(secp256k1_mpt_verify_same_plaintext_multi( - ctx, proof, proof_len, n, R, S, Pk, tx_id) == 0); - - free(proof); - printf("Passed\n"); +static void test_tampered_proof_fail(const secp256k1_context *ctx) +{ + printf("Running test: tampered proof (should fail)... "); + + size_t n = 2; + secp256k1_pubkey Pk[2], R[2], S[2]; + unsigned char r[64]; + unsigned char tx_id[32]; + uint64_t amount = 500; + + // Setup valid scenario + random_scalar(ctx, tx_id); + for (int i = 0; i < 2; ++i) + { + unsigned char priv[32]; + EXPECT(secp256k1_elgamal_generate_keypair(ctx, priv, &Pk[i]) == 1); + random_scalar(ctx, &r[i * 32]); + EXPECT(secp256k1_elgamal_encrypt(ctx, &R[i], &S[i], &Pk[i], amount, + &r[i * 32]) == 1); + } + + size_t proof_len = secp256k1_mpt_prove_same_plaintext_multi_size(n); + unsigned char *proof = (unsigned char *)malloc(proof_len); + EXPECT(proof != NULL); + size_t out_len = proof_len; + + EXPECT(secp256k1_mpt_prove_same_plaintext_multi(ctx, proof, &out_len, amount, + n, R, S, Pk, r, tx_id) == 1); + + // Tamper: Flip a bit in the middle of the proof + proof[proof_len / 2] ^= 0xFF; + + EXPECT(secp256k1_mpt_verify_same_plaintext_multi(ctx, proof, proof_len, n, R, + S, Pk, tx_id) == 0); + + free(proof); + printf("Passed\n"); } -int main() { - secp256k1_context* ctx = secp256k1_context_create( - SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - EXPECT(ctx != NULL); +int main() +{ + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | + SECP256K1_CONTEXT_VERIFY); + EXPECT(ctx != NULL); - unsigned char seed[32]; - EXPECT(RAND_bytes(seed, sizeof(seed)) == 1); - EXPECT(secp256k1_context_randomize(ctx, seed) == 1); + unsigned char seed[32]; + EXPECT(RAND_bytes(seed, sizeof(seed)) == 1); + EXPECT(secp256k1_context_randomize(ctx, seed) == 1); - // Test N=2 (Standard Send) - test_valid_multi_proof(ctx, 2); + // Test N=2 (Standard Send) + test_valid_multi_proof(ctx, 2); - // Test N=3 (e.g., with Issuer) - test_valid_multi_proof(ctx, 3); + // Test N=3 (e.g., with Issuer) + test_valid_multi_proof(ctx, 3); - // Test N=5 (Stress test) - test_valid_multi_proof(ctx, 5); + // Test N=5 (Stress test) + test_valid_multi_proof(ctx, 5); - // Negative tests - test_different_amounts_fail(ctx); - test_tampered_proof_fail(ctx); + // Negative tests + test_different_amounts_fail(ctx); + test_tampered_proof_fail(ctx); - secp256k1_context_destroy(ctx); - printf("ALL TESTS PASSED\n"); - return 0; + secp256k1_context_destroy(ctx); + printf("ALL TESTS PASSED\n"); + return 0; } diff --git a/tests/test_same_plaintext_multi_shared_r.c b/tests/test_same_plaintext_multi_shared_r.c index 445bc6c..74a3ded 100644 --- a/tests/test_same_plaintext_multi_shared_r.c +++ b/tests/test_same_plaintext_multi_shared_r.c @@ -1,135 +1,116 @@ +#include "secp256k1_mpt.h" +#include "test_utils.h" +#include #include #include #include -#include -#include "secp256k1_mpt.h" -#include "test_utils.h" -int main(void) { - secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - EXPECT(ctx != NULL); - - // 1. Context Randomization - unsigned char seed[32]; - random_bytes(seed); - EXPECT(secp256k1_context_randomize(ctx, seed)); - - printf("=== Running Test: Proof of Equality (Shared Randomness) ===\n"); - - const int N_RECIPIENTS = 3; - printf("Generating proof for %d recipients...\n", N_RECIPIENTS); - - // 2. Setup Variables - uint64_t amount = 123456789; - unsigned char r[32]; - unsigned char tx_context[32]; - - random_scalar(ctx, r); - random_scalar(ctx, tx_context); - - // 3. Generate Recipient Keys & Encrypt - secp256k1_pubkey pks[3]; - secp256k1_pubkey C2s[3]; - secp256k1_pubkey C1; - - // Shared C1 = r*G - EXPECT(secp256k1_ec_pubkey_create(ctx, &C1, r)); - - for (int i = 0; i < N_RECIPIENTS; i++) { - unsigned char sk[32]; - random_scalar(ctx, sk); - EXPECT(secp256k1_ec_pubkey_create(ctx, &pks[i], sk)); - - // Construct C2[i] = amount*G + r*PK[i] - secp256k1_pubkey mG; - unsigned char m_scalar[32] = {0}; - for(int b=0; b<8; b++) m_scalar[31-b] = (amount >> (b*8)) & 0xFF; - - EXPECT(secp256k1_ec_pubkey_create(ctx, &mG, m_scalar)); - - secp256k1_pubkey rPK = pks[i]; - EXPECT(secp256k1_ec_pubkey_tweak_mul(ctx, &rPK, r)); - - const secp256k1_pubkey *summands[2]; - summands[0] = &mG; - summands[1] = &rPK; - EXPECT(secp256k1_ec_pubkey_combine(ctx, &C2s[i], summands, 2)); - } - - // 4. Generate Proof - size_t proof_len = secp256k1_mpt_proof_equality_shared_r_size(N_RECIPIENTS); - unsigned char proof[proof_len]; - - int res = secp256k1_mpt_prove_equality_shared_r( - ctx, proof, - amount, - r, - N_RECIPIENTS, - &C1, - C2s, - pks, - tx_context - ); - EXPECT(res == 1); - printf("Proof generated successfully.\n"); - - // 5. Verify Proof (Positive Case) - res = secp256k1_mpt_verify_equality_shared_r( - ctx, proof, - N_RECIPIENTS, - &C1, - C2s, - pks, - tx_context - ); - EXPECT(res == 1); - printf("Proof verified successfully.\n"); - - /* ---------------------------------------------------------------- */ - /* 6. Negative Test: Tamper with Transaction Context */ - /* ---------------------------------------------------------------- */ - printf("Verifying proof with wrong TxID (Expecting Failure)...\n"); - - unsigned char tx_context_fake[32]; - memcpy(tx_context_fake, tx_context, 32); - tx_context_fake[0] ^= 0xFF; // Corrupt first byte - - int res_fake_ctx = secp256k1_mpt_verify_equality_shared_r( - ctx, proof, - N_RECIPIENTS, - &C1, - C2s, - pks, - tx_context_fake - ); - EXPECT(res_fake_ctx == 0); - printf("Tamper detection (Context): OK.\n"); - - /* ---------------------------------------------------------------- */ - /* 7. Negative Test: Tamper with Ciphertext (C1) */ - /* ---------------------------------------------------------------- */ - printf("Verifying proof with tampered Ciphertext C1 (Expecting Failure)...\n"); - - secp256k1_pubkey C1_fake = C1; - // Tweak C1 by adding a small scalar to it, effectively changing the point - unsigned char tweak[32] = {0}; - tweak[31] = 1; - EXPECT(secp256k1_ec_pubkey_tweak_add(ctx, &C1_fake, tweak)); - - int res_fake_c1 = secp256k1_mpt_verify_equality_shared_r( - ctx, proof, - N_RECIPIENTS, - &C1_fake, // <--- Passing tampered C1 - C2s, - pks, - tx_context - ); - EXPECT(res_fake_c1 == 0); - printf("Tamper detection (Ciphertext): OK.\n"); - - /* ---------------------------------------------------------------- */ - - printf("Test passed!\n"); - secp256k1_context_destroy(ctx); - return 0; +int main(void) +{ + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | + SECP256K1_CONTEXT_VERIFY); + EXPECT(ctx != NULL); + + // 1. Context Randomization + unsigned char seed[32]; + random_bytes(seed); + EXPECT(secp256k1_context_randomize(ctx, seed)); + + printf("=== Running Test: Proof of Equality (Shared Randomness) ===\n"); + + const int N_RECIPIENTS = 3; + printf("Generating proof for %d recipients...\n", N_RECIPIENTS); + + // 2. Setup Variables + uint64_t amount = 123456789; + unsigned char r[32]; + unsigned char tx_context[32]; + + random_scalar(ctx, r); + random_scalar(ctx, tx_context); + + // 3. Generate Recipient Keys & Encrypt + secp256k1_pubkey pks[3]; + secp256k1_pubkey C2s[3]; + secp256k1_pubkey C1; + + // Shared C1 = r*G + EXPECT(secp256k1_ec_pubkey_create(ctx, &C1, r)); + + for (int i = 0; i < N_RECIPIENTS; i++) + { + unsigned char sk[32]; + random_scalar(ctx, sk); + EXPECT(secp256k1_ec_pubkey_create(ctx, &pks[i], sk)); + + // Construct C2[i] = amount*G + r*PK[i] + secp256k1_pubkey mG; + unsigned char m_scalar[32] = {0}; + for (int b = 0; b < 8; b++) + m_scalar[31 - b] = (amount >> (b * 8)) & 0xFF; + + EXPECT(secp256k1_ec_pubkey_create(ctx, &mG, m_scalar)); + + secp256k1_pubkey rPK = pks[i]; + EXPECT(secp256k1_ec_pubkey_tweak_mul(ctx, &rPK, r)); + + const secp256k1_pubkey *summands[2]; + summands[0] = &mG; + summands[1] = &rPK; + EXPECT(secp256k1_ec_pubkey_combine(ctx, &C2s[i], summands, 2)); + } + + // 4. Generate Proof + size_t proof_len = secp256k1_mpt_proof_equality_shared_r_size(N_RECIPIENTS); + unsigned char proof[proof_len]; + + int res = secp256k1_mpt_prove_equality_shared_r( + ctx, proof, amount, r, N_RECIPIENTS, &C1, C2s, pks, tx_context); + EXPECT(res == 1); + printf("Proof generated successfully.\n"); + + // 5. Verify Proof (Positive Case) + res = secp256k1_mpt_verify_equality_shared_r(ctx, proof, N_RECIPIENTS, &C1, + C2s, pks, tx_context); + EXPECT(res == 1); + printf("Proof verified successfully.\n"); + + /* ---------------------------------------------------------------- */ + /* 6. Negative Test: Tamper with Transaction Context */ + /* ---------------------------------------------------------------- */ + printf("Verifying proof with wrong TxID (Expecting Failure)...\n"); + + unsigned char tx_context_fake[32]; + memcpy(tx_context_fake, tx_context, 32); + tx_context_fake[0] ^= 0xFF; // Corrupt first byte + + int res_fake_ctx = secp256k1_mpt_verify_equality_shared_r( + ctx, proof, N_RECIPIENTS, &C1, C2s, pks, tx_context_fake); + EXPECT(res_fake_ctx == 0); + printf("Tamper detection (Context): OK.\n"); + + /* ---------------------------------------------------------------- */ + /* 7. Negative Test: Tamper with Ciphertext (C1) */ + /* ---------------------------------------------------------------- */ + printf( + "Verifying proof with tampered Ciphertext C1 (Expecting Failure)...\n"); + + secp256k1_pubkey C1_fake = C1; + // Tweak C1 by adding a small scalar to it, effectively changing the point + unsigned char tweak[32] = {0}; + tweak[31] = 1; + EXPECT(secp256k1_ec_pubkey_tweak_add(ctx, &C1_fake, tweak)); + + int res_fake_c1 = secp256k1_mpt_verify_equality_shared_r( + ctx, proof, N_RECIPIENTS, + &C1_fake, // <--- Passing tampered C1 + C2s, pks, tx_context); + EXPECT(res_fake_c1 == 0); + printf("Tamper detection (Ciphertext): OK.\n"); + + /* ---------------------------------------------------------------- */ + + printf("Test passed!\n"); + secp256k1_context_destroy(ctx); + return 0; } diff --git a/tests/test_utils.h b/tests/test_utils.h index b458191..84062ff 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -1,31 +1,39 @@ #ifndef MPT_TEST_UTILS_H #define MPT_TEST_UTILS_H -#include -#include -#include #include -#include +#include +#include +#include +#include /* --- Macro: Persistent Assertion --- */ /* Ensures checks run in both Debug and Release modes */ -#define EXPECT(condition) do { \ - if (!(condition)) { \ - fprintf(stderr, "TEST FAILED: %s at line %d\n", #condition, __LINE__); \ - abort(); \ - } \ -} while(0) +#define EXPECT(condition) \ + do \ + { \ + if (!(condition)) \ + { \ + fprintf(stderr, "TEST FAILED: %s at line %d\n", #condition, __LINE__); \ + abort(); \ + } \ + } while (0) /* Helper: Generate 32 raw random bytes (for seeds, IDs, etc.) */ -static inline void random_bytes(unsigned char* out) { +static inline void +random_bytes(unsigned char* out) +{ EXPECT(RAND_bytes(out, 32) == 1); } /* Helper: Generate a valid random scalar using OpenSSL RNG. */ -static inline void random_scalar(const secp256k1_context* ctx, unsigned char* out) { - do { +static inline void +random_scalar(secp256k1_context const* ctx, unsigned char* out) +{ + do + { EXPECT(RAND_bytes(out, 32) == 1); } while (!secp256k1_ec_seckey_verify(ctx, out)); } -#endif // MPT_TEST_UTILS_H +#endif // MPT_TEST_UTILS_H