From a5f20c129d6e2377dbdb7eaa762a76992ece3d85 Mon Sep 17 00:00:00 2001 From: Shawn Xie <35279399+shawnxie999@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:56:10 -0500 Subject: [PATCH] Copying over pedersen commitment from crypto lib (#6238) --- include/xrpl/protocol/ConfidentialTransfer.h | 80 ++++- include/xrpl/protocol/Protocol.h | 3 + src/libxrpl/protocol/ConfidentialTransfer.cpp | 320 +++++++++++++++++- .../app/tx/detail/ConfidentialConvertBack.cpp | 17 +- 4 files changed, 373 insertions(+), 47 deletions(-) diff --git a/include/xrpl/protocol/ConfidentialTransfer.h b/include/xrpl/protocol/ConfidentialTransfer.h index 29e51cac42..5ac0635d15 100644 --- a/include/xrpl/protocol/ConfidentialTransfer.h +++ b/include/xrpl/protocol/ConfidentialTransfer.h @@ -109,9 +109,6 @@ verifyClawbackEqualityProof( Slice const& ciphertext, uint256 const& contextHash); -std::vector -getEqualityProofs(Slice const& zkp); - NotTEC checkEncryptedAmountFormat(STObject const& object); @@ -130,24 +127,18 @@ verifyRevealedAmount( EncryptedAmountInfo const& issuer, std::optional const& auditor); -// returns the number of entries -size_t inline getEqualityProofSize(bool const hasAuditor) -{ - // Be careful if we ever need to change the numbers below, it will be a - // breaking change! - return (hasAuditor ? 3 : 2); -} - -// returns the total byte length of all the equality proofs combined -size_t inline getEqualityProofLength(bool const hasAuditor) -{ - return getEqualityProofSize(hasAuditor) * ecEqualityProofLength; -} - // generates a 32 byte randomness factor to be used in encryption and proofs Buffer generateBlindingFactor(); +TER +verifyPedersenLinkage( + Slice const& proof, + Slice const& encAmt, + Slice const& pubKeySlice, + Slice const& pcmSlice, + uint256 const& contextHash); + // The following functions belong to the mpt-crypto library, // they will be finally removed and we will use conan2 to manage the dependency. /** @@ -346,6 +337,61 @@ secp256k1_elgamal_verify_encryption( uint64_t amount, unsigned char const* blinding_factor); +/** + * @brief Proves the link between an ElGamal ciphertext and a Pedersen + * commitment. + * * Formal Statement: Knowledge of (m, r, rho) such that: + * C1 = r*G, C2 = m*G + r*Pk, and PCm = m*G + rho*H. + * * @param ctx Pointer to a secp256k1 context object. + * @param proof [OUT] Pointer to 195-byte buffer for the proof output. + * @param c1 Pointer to the ElGamal C1 point (r*G). + * @param c2 Pointer to the ElGamal C2 point (m*G + r*Pk). + * @param pk Pointer to the recipient's public key. + * @param pcm Pointer to the Pedersen Commitment (m*G + rho*H). + * @param amount The plaintext amount (m). + * @param r The 32-byte secret ElGamal blinding factor. + * @param rho The 32-byte secret Pedersen blinding factor. + * @param context_id 32-byte unique transaction context identifier. + * @return 1 on success, 0 on failure. + */ +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( + 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); + +/** + * Compute a Pedersen Commitment: PC = m*G + rho*H + * Returns 1 on success, 0 on failure. + */ +int +secp256k1_mpt_pedersen_commit( + secp256k1_context const* ctx, + secp256k1_pubkey* commitment, + uint64_t amount, + unsigned char const* blinding_factor_rho /* 32 bytes */ +); } // namespace ripple #endif diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index 1407a6d346..e00f1e57b8 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -317,6 +317,9 @@ std::size_t constexpr ecBlindingFactorLength = 32; /** Length of Schnorr ZKProof for public key registration */ std::size_t constexpr ecSchnorrProofLength = 65; + +/** Length of ElGamal Pedersen linkage proof */ +std::size_t constexpr ecPedersenProofLength = 195; } // namespace ripple #endif diff --git a/src/libxrpl/protocol/ConfidentialTransfer.cpp b/src/libxrpl/protocol/ConfidentialTransfer.cpp index 01d91a91cc..8a6acee0f0 100644 --- a/src/libxrpl/protocol/ConfidentialTransfer.cpp +++ b/src/libxrpl/protocol/ConfidentialTransfer.cpp @@ -378,23 +378,6 @@ verifyClawbackEqualityProof( return tesSUCCESS; } -std::vector -getEqualityProofs(Slice const& zkp) -{ - if (zkp.size() % ecEqualityProofLength != 0) - return {}; - auto const count = zkp.size() / ecEqualityProofLength; - - std::vector zkps; - zkps.reserve(count); - - for (size_t i = 0; i < count; ++i) - zkps.emplace_back( - zkp.data() + (i * ecEqualityProofLength), ecEqualityProofLength); - - return zkps; -} - NotTEC checkEncryptedAmountFormat(STObject const& object) { @@ -419,6 +402,42 @@ checkEncryptedAmountFormat(STObject const& object) return tesSUCCESS; } +TER +verifyPedersenLinkage( + Slice const& proof, + Slice const& encAmt, + Slice const& pubKeySlice, + Slice const& pcmSlice, + uint256 const& contextHash) +{ + if (proof.length() != ecPedersenProofLength) + return tecINTERNAL; + + secp256k1_pubkey c1; + secp256k1_pubkey c2; + + if (!makeEcPair(encAmt, c1, c2)) + return tecINTERNAL; + + secp256k1_pubkey pubKey; + std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength); + + secp256k1_pubkey pcm; + std::memcpy(pcm.data, pcmSlice.data(), ecPubKeyLength); + + if (secp256k1_elgamal_pedersen_link_verify( + secp256k1Context(), + proof.data(), + &c1, + &c2, + &pubKey, + &pcm, + contextHash.data()) != 1) + return tecBAD_PROOF; + + return tesSUCCESS; +} + // The following functions belong to the mpt-crypto library, // they will be finally removed and we will use conan2 to manage the dependency. int @@ -1246,4 +1265,271 @@ secp256k1_elgamal_verify_encryption( return 1; // Success: Encryption is valid } +void +get_h_generator(secp256k1_context const* ctx, secp256k1_pubkey* h) +{ + unsigned char h_scalar[32] = {0}; + h_scalar[31] = 0x03; + if (!secp256k1_ec_pubkey_create(ctx, h, h_scalar)) + { + fprintf(stderr, "ABORT: secp256k1_ec_pubkey_create failed\n"); + } +} + +void +build_link_challenge_hash( + secp256k1_context const* ctx, + unsigned char hash_input[290], + secp256k1_pubkey const* c1, + secp256k1_pubkey const* c2, + secp256k1_pubkey const* pk, + secp256k1_pubkey const* pcm, + secp256k1_pubkey const* T1, + secp256k1_pubkey const* T2, + secp256k1_pubkey const* T3, + unsigned char const* context_id) +{ + char const* domain_sep = "MPT_ELGAMAL_PEDERSEN_LINK"; + size_t offset = 0, len; + memset(hash_input, 0, 290); + memcpy(hash_input + offset, domain_sep, 25); + offset += 27; + + secp256k1_pubkey const* points[] = {c1, c2, pk, pcm, T1, T2, T3}; + for (int i = 0; i < 7; i++) + { + len = 33; + secp256k1_ec_pubkey_serialize( + ctx, hash_input + offset, &len, points[i], SECP256K1_EC_COMPRESSED); + offset += 33; + } + memcpy(hash_input + offset, context_id, 32); +} + +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) +{ + unsigned char km[32], kr[32], krho[32], e[32], sm[32], sr[32], srho[32], + m_sc[32] = {0}; + secp256k1_pubkey T1, T2, T3, H, mG, rPk, rhoH; + size_t len = 33; + + if (!generate_random_scalar(ctx, km) || !generate_random_scalar(ctx, kr) || + !generate_random_scalar(ctx, krho)) + return 0; + if (!secp256k1_ec_pubkey_create(ctx, &T1, kr)) + return 0; + if (!secp256k1_ec_pubkey_create(ctx, &mG, km)) + return 0; + rPk = *pk; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &rPk, kr)) + return 0; + secp256k1_pubkey const* add_t2[2] = {&mG, &rPk}; + if (!secp256k1_ec_pubkey_combine(ctx, &T2, add_t2, 2)) + return 0; + get_h_generator(ctx, &H); + rhoH = H; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &rhoH, krho)) + return 0; + secp256k1_pubkey const* add_t3[2] = {&mG, &rhoH}; + if (!secp256k1_ec_pubkey_combine(ctx, &T3, add_t3, 2)) + return 0; + + unsigned char hash_input[290]; + build_link_challenge_hash( + ctx, hash_input, c1, c2, pk, pcm, &T1, &T2, &T3, context_id); + SHA256(hash_input, 290, e); + + for (int i = 0; i < 8; i++) + m_sc[31 - i] = (amount >> (i * 8)) & 0xFF; + + memcpy(sm, m_sc, 32); + if (!secp256k1_ec_seckey_tweak_mul(ctx, sm, e)) + return 0; + if (!secp256k1_ec_seckey_tweak_add(ctx, sm, km)) + return 0; + memcpy(sr, r, 32); + if (!secp256k1_ec_seckey_tweak_mul(ctx, sr, e)) + return 0; + if (!secp256k1_ec_seckey_tweak_add(ctx, sr, kr)) + return 0; + memcpy(srho, rho, 32); + if (!secp256k1_ec_seckey_tweak_mul(ctx, srho, e)) + return 0; + if (!secp256k1_ec_seckey_tweak_add(ctx, srho, krho)) + return 0; + + len = 33; + secp256k1_ec_pubkey_serialize( + ctx, proof, &len, &T1, SECP256K1_EC_COMPRESSED); + len = 33; + secp256k1_ec_pubkey_serialize( + ctx, proof + 33, &len, &T2, SECP256K1_EC_COMPRESSED); + len = 33; + secp256k1_ec_pubkey_serialize( + ctx, proof + 66, &len, &T3, SECP256K1_EC_COMPRESSED); + memcpy(proof + 99, sm, 32); + memcpy(proof + 131, sr, 32); + memcpy(proof + 163, srho, 32); + return 1; +} + +/* --- Verifier Implementation --- */ + +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) +{ + secp256k1_pubkey T1_p, T2_p, T3_p; + secp256k1_pubkey lhs, rhs, H, mG, term2; + unsigned char sm[32], sr[32], srho[32], e[32], e_neg[32]; + unsigned char hash_input[290]; + + if (!secp256k1_ec_pubkey_parse(ctx, &T1_p, proof, 33)) + return 0; + if (!secp256k1_ec_pubkey_parse(ctx, &T2_p, proof + 33, 33)) + return 0; + if (!secp256k1_ec_pubkey_parse(ctx, &T3_p, proof + 66, 33)) + return 0; + + memcpy(sm, proof + 99, 32); + memcpy(sr, proof + 131, 32); + memcpy(srho, proof + 163, 32); + + if (secp256k1_ec_seckey_verify(ctx, sm) != 1) + return 0; + if (secp256k1_ec_seckey_verify(ctx, sr) != 1) + return 0; + if (secp256k1_ec_seckey_verify(ctx, srho) != 1) + return 0; + + build_link_challenge_hash( + ctx, hash_input, c1, c2, pk, pcm, &T1_p, &T2_p, &T3_p, context_id); + SHA256(hash_input, sizeof(hash_input), e); + if (secp256k1_ec_seckey_verify(ctx, e) != 1) + return 0; + + memcpy(e_neg, e, 32); + if (!secp256k1_ec_seckey_negate(ctx, e_neg)) + return 0; + +#define COMBINE2(out, A, B) \ + do \ + { \ + secp256k1_pubkey _sum; \ + const secp256k1_pubkey* _pts[2] = {(A), (B)}; \ + if (!secp256k1_ec_pubkey_combine(ctx, &_sum, _pts, 2)) \ + return 0; \ + (out) = _sum; \ + } while (0) + +#define EQ_PUBKEY(A, B) \ + do \ + { \ + unsigned char _a[33], _b[33]; \ + size_t _l = 33; \ + if (!secp256k1_ec_pubkey_serialize( \ + ctx, _a, &_l, (A), SECP256K1_EC_COMPRESSED)) \ + return 0; \ + _l = 33; \ + if (!secp256k1_ec_pubkey_serialize( \ + ctx, _b, &_l, (B), SECP256K1_EC_COMPRESSED)) \ + return 0; \ + if (memcmp(_a, _b, 33) != 0) \ + return 0; \ + } while (0) + + /* Eq 1 */ + if (!secp256k1_ec_pubkey_create(ctx, &lhs, sr)) + return 0; + rhs = *c1; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &rhs, e_neg)) + return 0; + COMBINE2(lhs, &lhs, &rhs); + EQ_PUBKEY(&lhs, &T1_p); + + /* Eq 2 */ + if (!secp256k1_ec_pubkey_create(ctx, &mG, sm)) + return 0; + term2 = *pk; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term2, sr)) + return 0; + COMBINE2(lhs, &mG, &term2); + rhs = *c2; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &rhs, e_neg)) + return 0; + COMBINE2(lhs, &lhs, &rhs); + EQ_PUBKEY(&lhs, &T2_p); + + /* Eq 3 */ + get_h_generator(ctx, &H); + term2 = H; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &term2, srho)) + return 0; + COMBINE2(lhs, &mG, &term2); + rhs = *pcm; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &rhs, e_neg)) + return 0; + COMBINE2(lhs, &lhs, &rhs); + EQ_PUBKEY(&lhs, &T3_p); + +#undef COMBINE2 +#undef EQ_PUBKEY + return 1; +} + +int +secp256k1_mpt_pedersen_commit( + secp256k1_context const* ctx, + secp256k1_pubkey* commitment, + uint64_t amount, + unsigned char const* rho) +{ + secp256k1_pubkey mG, rH, H; + unsigned char m_scalar[32] = {0}; + + // 1. Calculate m * G + for (int i = 0; i < 8; i++) + { + m_scalar[31 - i] = (amount >> (i * 8)) & 0xFF; + } + if (!secp256k1_ec_pubkey_create(ctx, &mG, m_scalar)) + { + return 0; + } + + // 2. Calculate rho * H + get_h_generator(ctx, &H); + rH = H; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &rH, rho)) + { + return 0; + } + + // 3. Combine: mG + rH + secp256k1_pubkey const* points[2] = {&mG, &rH}; + if (!secp256k1_ec_pubkey_combine(ctx, commitment, points, 2)) + { + return 0; + } + + return 1; +} + } // namespace ripple diff --git a/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp b/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp index 037186708a..b2d69ebd5e 100644 --- a/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp @@ -12,16 +12,6 @@ namespace ripple { -size_t -expectedProofLength(std::shared_ptr const& issuance) -{ - auto const equalityProofLength = getEqualityProofLength( - issuance->isFieldPresent(sfAuditorElGamalPublicKey)); - - // todo: add pederson and range proof length - return equalityProofLength; -} - NotTEC ConfidentialConvertBack::preflight(PreflightContext const& ctx) { @@ -74,9 +64,10 @@ verifyProofs( bool const hasAuditor = issuance->isFieldPresent(sfAuditorElGamalPublicKey); if (hasAuditor) { - auditor.emplace(EncryptedAmountInfo{ - (*issuance)[sfAuditorElGamalPublicKey], - tx[sfAuditorEncryptedAmount]}); + auditor.emplace( + EncryptedAmountInfo{ + (*issuance)[sfAuditorElGamalPublicKey], + tx[sfAuditorEncryptedAmount]}); } if (auto const ter = verifyRevealedAmount(