From c52d3178108159293b59e28684f975d21a2139fd Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Fri, 27 Feb 2026 11:50:22 -0500 Subject: [PATCH] Update hashing and support ticket (#6444) --- include/xrpl/protocol/ConfidentialTransfer.h | 29 ++++++--------- src/libxrpl/protocol/ConfidentialTransfer.cpp | 36 ++++++++++--------- src/test/app/ConfidentialTransfer_test.cpp | 35 +++++++----------- src/test/jtx/impl/mpt.cpp | 9 +++-- .../app/tx/detail/ConfidentialMPTClawback.cpp | 2 +- .../app/tx/detail/ConfidentialMPTConvert.cpp | 2 +- .../tx/detail/ConfidentialMPTConvertBack.cpp | 2 +- .../app/tx/detail/ConfidentialMPTSend.cpp | 2 +- 8 files changed, 50 insertions(+), 67 deletions(-) diff --git a/include/xrpl/protocol/ConfidentialTransfer.h b/include/xrpl/protocol/ConfidentialTransfer.h index a7f59773f4..f10f5451f2 100644 --- a/include/xrpl/protocol/ConfidentialTransfer.h +++ b/include/xrpl/protocol/ConfidentialTransfer.h @@ -50,15 +50,15 @@ incrementConfidentialVersion(STObject& mptoken) /** * @brief Adds common fields to a serializer for ZKP context hash generation. * - * Serializes the transaction type, account, sequence number, and issuance ID + * Serializes the transaction type, account, issuance ID and sequence/ticket number * into the provided serializer. These fields form the base of all context * hashes used in zero-knowledge proofs. * * @param s The serializer to append fields to. * @param txType The transaction type identifier. * @param account The account ID of the transaction sender. - * @param sequence The transaction sequence number. * @param issuanceID The MPToken Issuance ID. + * @param sequence The transaction sequence number or ticket number. */ void addCommonZKPFields( @@ -75,8 +75,8 @@ addCommonZKPFields( * this specific send transaction, preventing proof reuse across transactions. * * @param account The sender's account ID. - * @param sequence The transaction sequence number. * @param issuanceID The MPToken Issuance ID. + * @param sequence The transaction sequence number or ticket number. * @param destination The destination account ID. * @param version The sender's confidential balance version. * @return A 256-bit context hash unique to this transaction. @@ -84,8 +84,8 @@ addCommonZKPFields( uint256 getSendContextHash( AccountID const& account, - std::uint32_t sequence, uint192 const& issuanceID, + std::uint32_t sequence, AccountID const& destination, std::uint32_t version); @@ -96,18 +96,16 @@ getSendContextHash( * specific clawback transaction. * * @param account The issuer's account ID. - * @param sequence The transaction sequence number. * @param issuanceID The MPToken Issuance ID. - * @param amount The amount being clawed back. + * @param sequence The transaction sequence number or ticket number. * @param holder The holder's account ID being clawed back from. * @return A 256-bit context hash unique to this transaction. */ uint256 getClawbackContextHash( AccountID const& account, - std::uint32_t sequence, uint192 const& issuanceID, - std::uint64_t amount, + std::uint32_t sequence, AccountID const& holder); /** @@ -117,17 +115,12 @@ getClawbackContextHash( * registration) to this specific convert transaction. * * @param account The holder's account ID. - * @param sequence The transaction sequence number. * @param issuanceID The MPToken Issuance ID. - * @param amount The amount being converted to confidential. + * @param sequence The transaction sequence number or a ticket number. * @return A 256-bit context hash unique to this transaction. */ uint256 -getConvertContextHash( - AccountID const& account, - std::uint32_t sequence, - uint192 const& issuanceID, - std::uint64_t amount); +getConvertContextHash(AccountID const& account, uint192 const& issuanceID, std::uint32_t sequence); /** * @brief Generates the context hash for ConfidentialMPTConvertBack transactions. @@ -136,18 +129,16 @@ getConvertContextHash( * this specific convert-back transaction. * * @param account The holder's account ID. - * @param sequence The transaction sequence number. * @param issuanceID The MPToken Issuance ID. - * @param amount The amount being converted back to public. + * @param sequence The transaction sequence number or a ticket number. * @param version The holder's confidential balance version. * @return A 256-bit context hash unique to this transaction. */ uint256 getConvertBackContextHash( AccountID const& account, - std::uint32_t sequence, uint192 const& issuanceID, - std::uint64_t amount, + std::uint32_t sequence, std::uint32_t version); /** diff --git a/src/libxrpl/protocol/ConfidentialTransfer.cpp b/src/libxrpl/protocol/ConfidentialTransfer.cpp index cf4503d1f0..3b2033e74c 100644 --- a/src/libxrpl/protocol/ConfidentialTransfer.cpp +++ b/src/libxrpl/protocol/ConfidentialTransfer.cpp @@ -12,26 +12,28 @@ addCommonZKPFields( Serializer& s, std::uint16_t txType, AccountID const& account, - std::uint32_t sequence, - uint192 const& issuanceID) + uint192 const& issuanceID, + std::uint32_t sequence) { + // TxCommonHash = hash(TxType || Account || IssuanceID || SequenceOrTicket) s.add16(txType); s.addBitString(account); - s.add32(sequence); s.addBitString(issuanceID); + s.add32(sequence); } uint256 getSendContextHash( AccountID const& account, - std::uint32_t sequence, uint192 const& issuanceID, + std::uint32_t sequence, AccountID const& destination, std::uint32_t version) { Serializer s; - addCommonZKPFields(s, ttCONFIDENTIAL_MPT_SEND, account, sequence, issuanceID); + addCommonZKPFields(s, ttCONFIDENTIAL_MPT_SEND, account, issuanceID, sequence); + // TxSpecific = identity || freshness s.addBitString(destination); s.addInteger(version); @@ -41,27 +43,29 @@ getSendContextHash( uint256 getClawbackContextHash( AccountID const& account, - std::uint32_t sequence, uint192 const& issuanceID, - std::uint64_t amount, + std::uint32_t sequence, AccountID const& holder) { Serializer s; - addCommonZKPFields(s, ttCONFIDENTIAL_MPT_CLAWBACK, account, sequence, issuanceID); + addCommonZKPFields(s, ttCONFIDENTIAL_MPT_CLAWBACK, account, issuanceID, sequence); - s.add64(amount); + // TxSpecific = identity || freshness s.addBitString(holder); + s.addInteger(0); return s.getSHA512Half(); } uint256 -getConvertContextHash(AccountID const& account, std::uint32_t sequence, uint192 const& issuanceID, std::uint64_t amount) +getConvertContextHash(AccountID const& account, uint192 const& issuanceID, std::uint32_t sequence) { Serializer s; - addCommonZKPFields(s, ttCONFIDENTIAL_MPT_CONVERT, account, sequence, issuanceID); + addCommonZKPFields(s, ttCONFIDENTIAL_MPT_CONVERT, account, issuanceID, sequence); - s.add64(amount); + // TxSpecific = identity || freshness + s.addBitString(account); + s.addInteger(0); return s.getSHA512Half(); } @@ -69,15 +73,15 @@ getConvertContextHash(AccountID const& account, std::uint32_t sequence, uint192 uint256 getConvertBackContextHash( AccountID const& account, - std::uint32_t sequence, uint192 const& issuanceID, - std::uint64_t amount, + std::uint32_t sequence, std::uint32_t version) { Serializer s; - addCommonZKPFields(s, ttCONFIDENTIAL_MPT_CONVERT_BACK, account, sequence, issuanceID); + addCommonZKPFields(s, ttCONFIDENTIAL_MPT_CONVERT_BACK, account, issuanceID, sequence); - s.add64(amount); + // TxSpecific = identity || freshness + s.addBitString(account); s.addInteger(version); return s.getSHA512Half(); diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index 4804381606..aff18bf823 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -2068,8 +2068,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite Buffer const convertBlindingFactor = generateBlindingFactor(); auto const convertHolderCiphertext = mptAlice.encryptAmount(bob, maxMPTokenAmount, convertBlindingFactor); auto const convertIssuerCiphertext = mptAlice.encryptAmount(alice, maxMPTokenAmount, convertBlindingFactor); - auto const convertContextHash = - getConvertContextHash(bob.id(), env.seq(bob), mptAlice.issuanceID(), maxMPTokenAmount); + auto const convertContextHash = getConvertContextHash(bob.id(), mptAlice.issuanceID(), env.seq(bob)); auto const schnorrProof = mptAlice.getSchnorrProof(bob, convertContextHash); BEAST_EXPECT(schnorrProof.has_value()); @@ -2121,7 +2120,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // Generate the proof using known spending balance value auto const version = mptAlice.getMPTokenVersion(bob); uint256 const convertBackContextHash = - getConvertBackContextHash(bob.id(), env.seq(bob), mptAlice.issuanceID(), convertBackAmt, version); + getConvertBackContextHash(bob.id(), mptAlice.issuanceID(), env.seq(bob), version); Buffer const proof = mptAlice.getConvertBackProof( bob, @@ -3408,8 +3407,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // The proof uses PC(1, rho) but the transaction submits PC(balance, rho). // Verification fails because the proof doesn't match the submitted commitment. { - uint256 const contextHash = - getConvertBackContextHash(bob, env.seq(bob), mptAlice.issuanceID(), amt, version); + uint256 const contextHash = getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version); Buffer const badPedersenCommitment = mptAlice.getPedersenCommitment(1, pcBlindingFactor); Buffer const proof = mptAlice.getConvertBackProof( bob, @@ -3437,8 +3435,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // The pedersen commitment PC = balance*G + rho*H requires the same rho // used in proof generation. Using a different rho breaks the linkage. { - uint256 const contextHash = - getConvertBackContextHash(bob, env.seq(bob), mptAlice.issuanceID(), amt, version); + uint256 const contextHash = getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version); Buffer const proof = mptAlice.getConvertBackProof( bob, @@ -3466,8 +3463,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // The proof claims balance=1 but the encrypted spending balance contains // the actual balance. Verification fails because the values don't match. { - uint256 const contextHash = - getConvertBackContextHash(bob, env.seq(bob), mptAlice.issuanceID(), amt, version); + uint256 const contextHash = getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version); Buffer const proof = mptAlice.getConvertBackProof( bob, @@ -3496,8 +3492,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // different pedersen commitment. Verification fails because the // submitted commitment doesn't match what the proof was generated for. { - uint256 const contextHash = - getConvertBackContextHash(bob, env.seq(bob), mptAlice.issuanceID(), amt, version); + uint256 const contextHash = getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version); Buffer const badPedersenCommitment = mptAlice.getPedersenCommitment(1, pcBlindingFactor); Buffer const proof = mptAlice.getConvertBackProof( bob, @@ -3526,8 +3521,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // sequence, issuanceID, amount, version). Using a different context hash // makes the proof invalid for this transaction, preventing replay attacks. { - uint256 const contextHash = - getConvertBackContextHash(bob, env.seq(bob), mptAlice.issuanceID(), amt, version); + uint256 const contextHash = getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version); uint256 const badContextHash{1}; Buffer const pedersenProof = mptAlice.getBalanceLinkageProof( bob, @@ -3560,8 +3554,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // Test 6: Correct proof to verify the test setup is valid. // All parameters are correct, so the transaction should succeed. { - uint256 const contextHash = - getConvertBackContextHash(bob, env.seq(bob), mptAlice.issuanceID(), amt, version); + uint256 const contextHash = getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version); Buffer const proof = mptAlice.getConvertBackProof( bob, @@ -3675,8 +3668,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // commitment was created with (balance - amount). The verifier computes // PC_rem = PC - amount*G and checks if the bulletproof matches, which fails. { - uint256 const contextHash = - getConvertBackContextHash(bob, env.seq(bob), mptAlice.issuanceID(), amt, version); + uint256 const contextHash = getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version); Buffer const bulletproof = mptAlice.getBulletproof( {1}, // wrong remaining balance @@ -3701,8 +3693,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // commitment PC = (balance - amount)*G + rho*H. Using a different rho // creates a commitment mismatch and verification fails. { - uint256 const contextHash = - getConvertBackContextHash(bob, env.seq(bob), mptAlice.issuanceID(), amt, version); + uint256 const contextHash = getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version); Buffer const bulletproof = mptAlice.getBulletproof( {*spendingBalance - amt}, @@ -3727,8 +3718,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // sequence, issuanceID, amount, version). Using a different context hash // makes the proof invalid for this transaction, preventing replay attacks. { - uint256 const contextHash = - getConvertBackContextHash(bob, env.seq(bob), mptAlice.issuanceID(), amt, version); + uint256 const contextHash = getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version); uint256 const badContextHash{1}; Buffer const bulletproof = mptAlice.getBulletproof( @@ -3752,8 +3742,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // Test 4: Correct proof to verify the test setup is valid. // All parameters are correct, so the transaction should succeed. { - uint256 const contextHash = - getConvertBackContextHash(bob, env.seq(bob), mptAlice.issuanceID(), amt, version); + uint256 const contextHash = getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version); Buffer const proof = mptAlice.getConvertBackProof( bob, diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 049eb5c8bc..9666a0a4f9 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -1057,7 +1057,7 @@ MPTTester::convert(MPTConvert const& arg) // if fillSchnorrProof is explicitly set, follow its value; // otherwise, default to generating the proof only if holder pub key is // present. - auto const contextHash = getConvertContextHash(arg.account->id(), env_.seq(*arg.account), *id_, *arg.amt); + auto const contextHash = getConvertContextHash(arg.account->id(), *id_, env_.seq(*arg.account)); auto const proof = getSchnorrProof(*arg.account, contextHash); if (proof) @@ -1257,7 +1257,7 @@ MPTTester::send(MPTConfidentialSend const& arg) { auto const version = getMPTokenVersion(*arg.account); auto const ctxHash = - getSendContextHash(arg.account->id(), env_.seq(*arg.account), *id_, arg.dest->id(), version); + getSendContextHash(arg.account->id(), *id_, env_.seq(*arg.account), arg.dest->id(), version); auto const nRecipients = getConfidentialRecipientCount(auditorAmt.has_value()); std::vector recipients; @@ -1427,7 +1427,7 @@ MPTTester::confidentialClaw(MPTConfidentialClawback const& arg) else { std::uint32_t const seq = env_.seq(account); - uint256 const contextHash = getClawbackContextHash(account.id(), seq, *id_, *arg.amt, arg.holder->id()); + uint256 const contextHash = getClawbackContextHash(account.id(), *id_, seq, arg.holder->id()); auto const privKey = getPrivKey(account); if (!privKey || privKey->size() != ecPrivKeyLength) @@ -1707,8 +1707,7 @@ MPTTester::convertBack(MPTConvertBack const& arg) // if the caller generated ciphertexts themselves, they should also // generate the proof themselves from the blinding factor - uint256 const contextHash = - getConvertBackContextHash(arg.account->id(), env_.seq(*arg.account), *id_, *arg.amt, version); + uint256 const contextHash = getConvertBackContextHash(arg.account->id(), *id_, env_.seq(*arg.account), version); auto const prevEncryptedSpendingBalance = getEncryptedBalance(*arg.account, HOLDER_ENCRYPTED_SPENDING); Buffer proof; diff --git a/src/xrpld/app/tx/detail/ConfidentialMPTClawback.cpp b/src/xrpld/app/tx/detail/ConfidentialMPTClawback.cpp index 582d29e0c6..2dc737a066 100644 --- a/src/xrpld/app/tx/detail/ConfidentialMPTClawback.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialMPTClawback.cpp @@ -83,7 +83,7 @@ ConfidentialMPTClawback::preclaim(PreclaimContext const& ctx) if (amount > (*sleIssuance)[~sfConfidentialOutstandingAmount].value_or(0)) return tecINSUFFICIENT_FUNDS; - auto const contextHash = getClawbackContextHash(account, ctx.tx[sfSequence], mptIssuanceID, amount, holder); + auto const contextHash = getClawbackContextHash(account, mptIssuanceID, ctx.tx.getSeqProxy().value(), holder); // Verify the revealed confidential amount by the issuer matches the exact // confidential balance of the holder. diff --git a/src/xrpld/app/tx/detail/ConfidentialMPTConvert.cpp b/src/xrpld/app/tx/detail/ConfidentialMPTConvert.cpp index 34e5402689..f9e5a3dbe5 100644 --- a/src/xrpld/app/tx/detail/ConfidentialMPTConvert.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialMPTConvert.cpp @@ -120,7 +120,7 @@ ConfidentialMPTConvert::preclaim(PreclaimContext const& ctx) { holderPubKey = ctx.tx[sfHolderElGamalPublicKey]; - auto const contextHash = getConvertContextHash(account, ctx.tx[sfSequence], issuanceID, amount); + auto const contextHash = getConvertContextHash(account, issuanceID, ctx.tx.getSeqProxy().value()); // when register new pk, verify through schnorr proof if (!isTesSuccess(verifySchnorrProof(holderPubKey, ctx.tx[sfZKProof], contextHash))) diff --git a/src/xrpld/app/tx/detail/ConfidentialMPTConvertBack.cpp b/src/xrpld/app/tx/detail/ConfidentialMPTConvertBack.cpp index 6c136bd4d1..27cdf36c15 100644 --- a/src/xrpld/app/tx/detail/ConfidentialMPTConvertBack.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialMPTConvertBack.cpp @@ -69,7 +69,7 @@ verifyProofs(STTx const& tx, std::shared_ptr const& issuance, std::sh auto const holderPubKey = (*mptoken)[sfHolderElGamalPublicKey]; auto const contextHash = getConvertBackContextHash( - account, tx[sfSequence], mptIssuanceID, amount, (*mptoken)[~sfConfidentialBalanceVersion].value_or(0)); + account, mptIssuanceID, tx.getSeqProxy().value(), (*mptoken)[~sfConfidentialBalanceVersion].value_or(0)); // Prepare Auditor Info std::optional auditor; diff --git a/src/xrpld/app/tx/detail/ConfidentialMPTSend.cpp b/src/xrpld/app/tx/detail/ConfidentialMPTSend.cpp index 46cfbdfdfc..354daf2046 100644 --- a/src/xrpld/app/tx/detail/ConfidentialMPTSend.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialMPTSend.cpp @@ -132,8 +132,8 @@ verifySendProofs( // Prepare the context hash auto const contextHash = getSendContextHash( ctx.tx[sfAccount], - ctx.tx[sfSequence], ctx.tx[sfMPTokenIssuanceID], + ctx.tx.getSeqProxy().value(), ctx.tx[sfDestination], (*sleSenderMPToken)[~sfConfidentialBalanceVersion].value_or(0));