From 734b11c0e32254abeff3aacc70683f29376a09bd Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Fri, 6 Mar 2026 18:28:35 -0500 Subject: [PATCH] Support shared r for ConfidentialMPTSend equality proof (#6496) * Support shared r for send equality proof --- include/xrpl/protocol/ConfidentialTransfer.h | 12 ----- src/libxrpl/protocol/ConfidentialTransfer.cpp | 53 ++++++++++++------- src/test/app/ConfidentialTransfer_test.cpp | 2 +- src/test/jtx/impl/mpt.cpp | 35 ++++++------ .../app/tx/detail/ConfidentialMPTSend.cpp | 4 +- 5 files changed, 52 insertions(+), 54 deletions(-) diff --git a/include/xrpl/protocol/ConfidentialTransfer.h b/include/xrpl/protocol/ConfidentialTransfer.h index f10f5451f2..88bc7b9b5f 100644 --- a/include/xrpl/protocol/ConfidentialTransfer.h +++ b/include/xrpl/protocol/ConfidentialTransfer.h @@ -329,18 +329,6 @@ getConfidentialRecipientCount(bool hasAuditor) return hasAuditor ? 4 : 3; } -/** - * @brief Calculates the size of a multi-ciphertext equality proof. - * - * The proof size varies based on the number of recipients because each - * additional recipient requires additional proof components. - * - * @param nRecipients The number of recipients in the transfer. - * @return The size in bytes of the equality proof. - */ -std::size_t -getMultiCiphertextEqualityProofSize(std::size_t nRecipients); - /** * @brief Verifies a multi-ciphertext equality proof. * diff --git a/src/libxrpl/protocol/ConfidentialTransfer.cpp b/src/libxrpl/protocol/ConfidentialTransfer.cpp index 3b2033e74c..0699571a0b 100644 --- a/src/libxrpl/protocol/ConfidentialTransfer.cpp +++ b/src/libxrpl/protocol/ConfidentialTransfer.cpp @@ -328,15 +328,6 @@ verifyRevealedAmount( return tesSUCCESS; } -std::size_t -getMultiCiphertextEqualityProofSize(std::size_t nRecipients) -{ - // Points (33 bytes): T_m (1) + T_rG (nRecipients) + T_rP (nRecipients) = 1 - // + 2nRecipients Scalars (32 bytes): s_m (1) + s_r (nRecipients) = 1 + - // nRecipients - return ((1 + (2 * nRecipients)) * 33) + ((1 + nRecipients) * 32); -} - TER verifyMultiCiphertextEqualityProof( Slice const& proof, @@ -347,31 +338,55 @@ verifyMultiCiphertextEqualityProof( if (recipients.size() != nRecipients) return tecINTERNAL; // LCOV_EXCL_LINE - if (proof.size() != getMultiCiphertextEqualityProofSize(nRecipients)) + if (proof.size() != secp256k1_mpt_proof_equality_shared_r_size(nRecipients)) return tecINTERNAL; // LCOV_EXCL_LINE - std::vector r(nRecipients); - std::vector s(nRecipients); - std::vector pk(nRecipients); + auto const ctx = secp256k1Context(); + + secp256k1_pubkey c1; + std::vector c2_vec(nRecipients); + std::vector pk_vec(nRecipients); for (size_t i = 0; i < nRecipients; ++i) { auto const& recipient = recipients[i]; - if (recipient.encryptedAmount.size() != ecGamalEncryptedTotalLength) - return tecINTERNAL; // LCOV_EXCL_LINE - if (!makeEcPair(recipient.encryptedAmount, r[i], s[i])) + if (recipient.encryptedAmount.size() != ecGamalEncryptedTotalLength) return tecINTERNAL; // LCOV_EXCL_LINE if (recipient.publicKey.size() != ecPubKeyLength) return tecINTERNAL; // LCOV_EXCL_LINE - if (secp256k1_ec_pubkey_parse(secp256k1Context(), &pk[i], recipient.publicKey.data(), ecPubKeyLength) != 1) + // Parse Shared C1 from the first recipient only + if (i == 0) + { + if (!secp256k1_ec_pubkey_parse(ctx, &c1, recipient.encryptedAmount.data(), ecGamalEncryptedLength)) + return tecINTERNAL; // LCOV_EXCL_LINE + } + else + { + // All C1 bytes must be the same + if (std::memcmp( + recipient.encryptedAmount.data(), recipients[0].encryptedAmount.data(), ecGamalEncryptedLength) != + 0) + { + return tecBAD_PROOF; + } + } + + if (secp256k1_ec_pubkey_parse( + ctx, &c2_vec[i], recipient.encryptedAmount.data() + ecGamalEncryptedLength, ecGamalEncryptedLength) != + 1) + { + return tecINTERNAL; // LCOV_EXCL_LINE + } + + if (secp256k1_ec_pubkey_parse(ctx, &pk_vec[i], recipient.publicKey.data(), ecPubKeyLength) != 1) return tecINTERNAL; // LCOV_EXCL_LINE } - int const result = secp256k1_mpt_verify_same_plaintext_multi( - secp256k1Context(), proof.data(), proof.size(), nRecipients, r.data(), s.data(), pk.data(), contextHash.data()); + int const result = secp256k1_mpt_verify_equality_shared_r( + ctx, proof.data(), nRecipients, &c1, c2_vec.data(), pk_vec.data(), contextHash.data()); if (result != 1) return tecBAD_PROOF; diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index afed5f30ff..e1bf792cf5 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -71,7 +71,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite std::string getTrivialSendProofHex(size_t nRecipients) { - size_t const sizeEquality = getMultiCiphertextEqualityProofSize(nRecipients); + size_t const sizeEquality = secp256k1_mpt_proof_equality_shared_r_size(nRecipients); size_t const totalSize = sizeEquality + (2 * ecPedersenProofLength) + ecDoubleBulletproofLength; Buffer buf(totalSize); diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 9666a0a4f9..29f1cb7179 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -762,9 +762,9 @@ MPTTester::getConfidentialSendProof( auto const ctx = secp256k1Context(); - std::vector r(nRecipients); - std::vector s(nRecipients); - std::vector pk(nRecipients); + secp256k1_pubkey c1; + std::vector c2_vec(nRecipients); + std::vector pk_vec(nRecipients); std::vector sr; sr.reserve(nRecipients * ecBlindingFactorLength); @@ -780,36 +780,31 @@ MPTTester::getConfidentialSendProof( if (recipient.publicKey.size() != ecPubKeyLength) return std::nullopt; - if (!secp256k1_ec_pubkey_parse(ctx, &r[i], ctData, ecGamalEncryptedLength)) + if (i == 0) { - return std::nullopt; + if (!secp256k1_ec_pubkey_parse(ctx, &c1, ctData, ecGamalEncryptedLength)) + return std::nullopt; } - if (!secp256k1_ec_pubkey_parse(ctx, &s[i], ctData + ecGamalEncryptedLength, ecGamalEncryptedLength)) - { - return std::nullopt; - } - - if (!secp256k1_ec_pubkey_parse(ctx, &pk[i], recipient.publicKey.data(), ecPubKeyLength)) + if (!secp256k1_ec_pubkey_parse(ctx, &c2_vec[i], ctData + ecGamalEncryptedLength, ecGamalEncryptedLength)) return std::nullopt; - sr.insert(sr.end(), blindingFactor.data(), blindingFactor.data() + ecBlindingFactorLength); + if (!secp256k1_ec_pubkey_parse(ctx, &pk_vec[i], recipient.publicKey.data(), ecPubKeyLength)) + return std::nullopt; } - size_t sizeEquality = secp256k1_mpt_prove_same_plaintext_multi_size(nRecipients); + size_t const sizeEquality = secp256k1_mpt_proof_equality_shared_r_size(nRecipients); Buffer equalityProof(sizeEquality); - // Get the multi-ciphertext equality proof - if (secp256k1_mpt_prove_same_plaintext_multi( + if (secp256k1_mpt_prove_equality_shared_r( ctx, equalityProof.data(), - &sizeEquality, amount, + blindingFactor.data(), // The shared 'r' witness nRecipients, - r.data(), - s.data(), - pk.data(), - sr.data(), + &c1, + c2_vec.data(), + pk_vec.data(), contextHash.data()) != 1) { return std::nullopt; diff --git a/src/xrpld/app/tx/detail/ConfidentialMPTSend.cpp b/src/xrpld/app/tx/detail/ConfidentialMPTSend.cpp index 354daf2046..ef09d2081e 100644 --- a/src/xrpld/app/tx/detail/ConfidentialMPTSend.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialMPTSend.cpp @@ -41,7 +41,7 @@ ConfidentialMPTSend::preflight(PreflightContext const& ctx) // Check the length of the ZKProof auto const recipientCount = getConfidentialRecipientCount(hasAuditor); - auto const sizeEquality = getMultiCiphertextEqualityProofSize(recipientCount); + auto const sizeEquality = secp256k1_mpt_proof_equality_shared_r_size(recipientCount); auto const sizePedersenLinkage = 2 * ecPedersenProofLength; if (ctx.tx[sfZKProof].length() != sizeEquality + sizePedersenLinkage + ecDoubleBulletproofLength) @@ -81,7 +81,7 @@ verifySendProofs( size_t currentOffset = 0; // Extract equality proof - auto const sizeEquality = getMultiCiphertextEqualityProofSize(recipientCount); + auto const sizeEquality = secp256k1_mpt_proof_equality_shared_r_size(recipientCount); if (remainingLength < sizeEquality) return tecINTERNAL; // LCOV_EXCL_LINE