From 54e0f584249db75e61e69643d0c9ed2cbe3d2ef0 Mon Sep 17 00:00:00 2001 From: Peter Chen <34582813+PeterChen13579@users.noreply.github.com> Date: Wed, 17 Jun 2026 10:32:18 -0700 Subject: [PATCH] Address Confidential Transfer Feedback and uncomment tests (#7541) --- .../xrpl/ledger/helpers/CredentialHelpers.h | 141 +++++++-- include/xrpl/protocol/ConfidentialTransfer.h | 25 +- include/xrpl/protocol/LedgerFormats.h | 4 +- include/xrpl/protocol/Protocol.h | 32 +- .../xrpl/protocol/detail/ledger_entries.macro | 4 +- include/xrpl/protocol/detail/secp256k1.h | 4 +- include/xrpl/protocol/detail/sfields.macro | 28 +- include/xrpl/tx/invariants/MPTInvariant.h | 130 +++++++- .../token/ConfidentialMPTConvert.h | 1 - .../token/ConfidentialMPTConvertBack.h | 1 - .../token/ConfidentialMPTMergeInbox.h | 1 - .../transactors/token/ConfidentialMPTSend.h | 1 - src/libxrpl/protocol/ConfidentialTransfer.cpp | 28 +- src/libxrpl/tx/invariants/MPTInvariant.cpp | 5 +- .../token/ConfidentialMPTConvert.cpp | 14 +- .../token/ConfidentialMPTConvertBack.cpp | 16 +- .../transactors/token/ConfidentialMPTSend.cpp | 28 +- .../transactors/token/MPTokenIssuanceSet.cpp | 75 +++-- .../app/ConfidentialTransferExtended_test.cpp | 281 +++++++++--------- src/test/app/ConfidentialTransfer_test.cpp | 209 +++++++------ src/test/app/Delegate_test.cpp | 2 + src/test/app/Invariants_test.cpp | 3 +- src/test/jtx/ConfidentialTransfer.h | 139 ++++++--- src/test/jtx/batch.h | 38 ++- src/test/jtx/impl/mpt.cpp | 246 ++++++++------- src/test/jtx/mpt.h | 167 +++++++---- src/tests/libxrpl/CMakeLists.txt | 2 +- 27 files changed, 1026 insertions(+), 599 deletions(-) diff --git a/include/xrpl/ledger/helpers/CredentialHelpers.h b/include/xrpl/ledger/helpers/CredentialHelpers.h index b70b28ec02..bd6d538837 100644 --- a/include/xrpl/ledger/helpers/CredentialHelpers.h +++ b/include/xrpl/ledger/helpers/CredentialHelpers.h @@ -13,57 +13,131 @@ namespace xrpl { namespace credentials { -// These function will be used by the code that use DepositPreauth / Credentials -// (and any future pre-authorization modes) as part of authorization (all the -// transfer funds transactions) - -// Check if credential sfExpiration field has passed ledger's parentCloseTime +/** + * @brief Check whether a credential has expired. + * + * @param sleCredential Credential ledger entry to inspect. + * @param closed Parent close time to compare against sfExpiration. + * @return true if sfExpiration is before closed; otherwise false. + */ bool checkExpired(SLE const& sleCredential, NetClock::time_point const& closed); -// Actually remove a credentials object from the ledger +/** + * @brief Remove a Credential ledger object from the view. + * + * @param view Mutable ledger view. + * @param sleCredential Credential ledger entry to delete. + * @param j Journal for diagnostics. + * @return tesSUCCESS on success, or an error code if ledger cleanup fails. + */ [[nodiscard]] TER deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j); -// Amendment and parameters checks for sfCredentialIDs field +/** + * @brief Check amendment and parameter rules for sfCredentialIDs. + * + * @param tx Transaction to validate. + * @param j Journal for diagnostics. + * @return tesSUCCESS if sfCredentialIDs is absent or well-formed; otherwise a + * malformed transaction code. + */ NotTEC checkFields(STTx const& tx, beast::Journal j); -// Accessing the ledger to check if provided credentials are valid. Do not use -// in doApply (only in preclaim) since it does not remove expired credentials. -// If you call it in preclaim, you also must call verifyDepositPreauth in -// doApply +/** + * @brief Check whether the transaction's provided credentials are valid. + * + * Use this only from preclaim because it does not remove expired credentials. + * If this is called from preclaim, verifyDepositPreauth() must also be called + * from doApply(). + * + * @param tx Transaction containing optional sfCredentialIDs. + * @param view Read-only ledger view. + * @param src Source account that must own the credentials. + * @param j Journal for diagnostics. + * @return tesSUCCESS if credentials are valid, otherwise a credential error. + */ TER valid(STTx const& tx, ReadView const& view, AccountID const& src, beast::Journal j); -// Check if subject has any credential maching the given domain. If you call it -// in preclaim and it returns tecEXPIRED, you should call verifyValidDomain in -// doApply. This will ensure that expired credentials are deleted. +/** + * @brief Check whether an account has an accepted credential for a domain. + * + * If this is called from preclaim and returns tecEXPIRED, verifyValidDomain() + * should be called from doApply() so expired credentials are deleted. + * + * @param view Read-only ledger view. + * @param domainID PermissionedDomain ledger entry ID. + * @param subject Account whose credentials are checked. + * @return tesSUCCESS if a matching accepted credential exists, tecEXPIRED if + * only expired credentials match, or an authorization/object error. + */ TER validDomain(ReadView const& view, uint256 domainID, AccountID const& subject); -// This function is only called when we about to return tecNO_PERMISSION -// because all the checks for the DepositPreauth authorization failed. +/** + * @brief Check DepositPreauth authorization using credential IDs. + * + * @param view Read-only ledger view. + * @param ctx Credential IDs supplied by the transaction. + * @param dst Destination account requiring deposit authorization. + * @return tesSUCCESS if credentials match a DepositPreauth object, otherwise + * an authorization or internal error. + */ TER authorizedDepositPreauth(ReadView const& view, STVector256 const& ctx, AccountID const& dst); -// Sort credentials array, return empty set if there are duplicates +/** + * @brief Sort credential issuer/type pairs. + * + * @param credentials Credential descriptors from a transaction. + * @return Sorted issuer/type pairs, or an empty set if duplicates exist. + */ std::set> makeSorted(STArray const& credentials); -// Check credentials array passed to DepositPreauth/PermissionedDomainSet -// transactions +/** + * @brief Check a credentials array for DepositPreauth or PermissionedDomainSet. + * + * @param credentials Credential descriptors to validate. + * @param maxSize Maximum allowed array size. + * @param j Journal for diagnostics. + * @return tesSUCCESS if the array is well-formed, otherwise a malformed + * transaction code. + */ NotTEC checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j); } // namespace credentials -// Check expired credentials and for credentials maching DomainID of the ledger -// object +/** + * @brief Verify an account has a credential for a domain and delete expired + * credentials encountered while checking. + * + * @param view Mutable ledger view. + * @param account Account whose credentials are checked. + * @param domainID PermissionedDomain ledger entry ID. + * @param j Journal for diagnostics. + * @return tesSUCCESS if a matching accepted credential exists, tecEXPIRED if + * expired credentials were removed but no valid credential remains, or + * an authorization/object error. + */ TER verifyValidDomain(ApplyView& view, AccountID const& account, uint256 domainID, beast::Journal j); -// Check DepositPreauth authorization +/** + * @brief Check whether src is authorized to deposit to dst. + * + * @param tx Transaction containing optional credential IDs. + * @param view Read-only ledger view. + * @param src Source account. + * @param dst Destination account. + * @param sleDst Destination AccountRoot, if it exists. + * @param j Journal for diagnostics. + * @return tesSUCCESS if the deposit is allowed, otherwise an authorization + * error. + */ TER checkDepositPreauth( STTx const& tx, @@ -73,11 +147,30 @@ checkDepositPreauth( std::shared_ptr const& sleDst, beast::Journal j); -// Remove expired credentials +/** + * @brief Remove expired credentials referenced by the transaction. + * + * @param tx Transaction containing optional sfCredentialIDs. + * @param view Mutable ledger view. + * @param j Journal for diagnostics. + * @return tesSUCCESS if no referenced credentials expired, tecEXPIRED if any + * were removed, or an error from credential deletion. + */ TER cleanupExpiredCredentials(STTx const& tx, ApplyView& view, beast::Journal j); -// Check expired credentials and for existing DepositPreauth ledger object +/** + * @brief Remove expired credentials, then check DepositPreauth authorization. + * + * @param tx Transaction containing optional credential IDs. + * @param view Mutable ledger view. + * @param src Source account. + * @param dst Destination account. + * @param sleDst Destination AccountRoot, if it exists. + * @param j Journal for diagnostics. + * @return tesSUCCESS if the deposit is authorized, otherwise an expiration, + * authorization, or deletion error. + */ TER verifyDepositPreauth( STTx const& tx, diff --git a/include/xrpl/protocol/ConfidentialTransfer.h b/include/xrpl/protocol/ConfidentialTransfer.h index c1d48550df..98a834f42e 100644 --- a/include/xrpl/protocol/ConfidentialTransfer.h +++ b/include/xrpl/protocol/ConfidentialTransfer.h @@ -26,15 +26,26 @@ namespace xrpl { */ struct ConfidentialRecipient { - Slice publicKey; ///< The recipient's ElGamal public key (size=xrpl::ecPubKeyLength). - Slice encryptedAmount; ///< The encrypted amount ciphertext - ///< (size=xrpl::ecGamalEncryptedTotalLength). + /** @brief The recipient's ElGamal public key (size=xrpl::ecPubKeyLength). */ + Slice publicKey; + + /** + * @brief The encrypted amount ciphertext + * (size=xrpl::ecGamalEncryptedTotalLength). + */ + Slice encryptedAmount; }; -/// Holds two secp256k1 public key components representing an ElGamal ciphertext (C1, C2). +/** + * @brief Holds two secp256k1 public key components representing an ElGamal + * ciphertext (C1, C2). + */ struct EcPair { + /** @brief First ElGamal ciphertext component. */ secp256k1_pubkey c1; + + /** @brief Second ElGamal ciphertext component. */ secp256k1_pubkey c2; }; @@ -224,7 +235,7 @@ rerandomizeCiphertext(Slice const& ciphertext, Slice const& pubKeySlice, Slice c * @param amt The plaintext amount to encrypt. * @param pubKeySlice The recipient's ElGamal public key (size=xrpl::ecPubKeyLength). * @param blindingFactor The randomness used as blinding factor r - * (size=xrpl::ecBlindingFactorLength). + * (size=xrpl::ecBlindingFactorLength). * @return The ciphertext (size=xrpl::ecGamalEncryptedTotalLength), or std::nullopt on failure. */ std::optional @@ -281,7 +292,7 @@ checkEncryptedAmountFormat(STObject const& object); * * @param amount The revealed plaintext amount. * @param blindingFactor The blinding factor used in all encryptions - * (size=xrpl::ecBlindingFactorLength). + * (size=xrpl::ecBlindingFactorLength). * @param holder The holder's public key and encrypted amount. * @param issuer The issuer's public key and encrypted amount. * @param auditor Optional auditor's public key and encrypted amount. @@ -321,7 +332,7 @@ getConfidentialRecipientCount(bool hasAuditor) * @param proof The zero-knowledge proof bytes (ecClawbackProofLength). * @param pubKeySlice The issuer's ElGamal public key (ecPubKeyLength bytes). * @param ciphertext The issuer's encrypted balance on the holder's account - * (ecGamalEncryptedTotalLength bytes). + * (ecGamalEncryptedTotalLength bytes). * @param contextHash The 256-bit context hash binding the proof. * @return tesSUCCESS if the proof is valid, or an error code otherwise. */ diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index c22fcee851..f00244c0f1 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -178,7 +178,7 @@ enum LedgerEntryType : std::uint16_t { LSF_FLAG(lsfMPTCanTrade, 0x00000010) \ LSF_FLAG(lsfMPTCanTransfer, 0x00000020) \ LSF_FLAG(lsfMPTCanClawback, 0x00000040) \ - LSF_FLAG(lsfMPTCanConfidentialAmount, 0x00000080)) \ + LSF_FLAG(lsfMPTCanConfidentialAmount, 0x00000080)) \ \ LEDGER_OBJECT(MPTokenIssuanceMutable, \ LSF_FLAG(lsmfMPTCanMutateCanLock, 0x00000002) \ @@ -189,7 +189,7 @@ enum LedgerEntryType : std::uint16_t { LSF_FLAG(lsmfMPTCanMutateCanClawback, 0x00000040) \ LSF_FLAG(lsmfMPTCanMutateMetadata, 0x00010000) \ LSF_FLAG(lsmfMPTCanMutateTransferFee, 0x00020000) \ - LSF_FLAG(lsmfMPTCannotMutateCanConfidentialAmount, 0x00040000)) \ + LSF_FLAG(lsmfMPTCannotMutateCanConfidentialAmount, 0x00040000)) \ \ LEDGER_OBJECT(MPToken, \ LSF_FLAG2(lsfMPTLocked, 0x00000001) \ diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index c96c6c3dfe..80b1923094 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -308,53 +308,53 @@ constexpr std::size_t kPermissionMaxSize = 10; constexpr std::size_t kMaxBatchTxCount = 8; /** Length of one component of EC ElGamal ciphertext */ -std::size_t constexpr kEcGamalEncryptedLength = 33; +constexpr std::size_t kEcGamalEncryptedLength = 33; /** EC ElGamal ciphertext length: two 33-byte components concatenated */ -std::size_t constexpr kEcGamalEncryptedTotalLength = kEcGamalEncryptedLength * 2; +constexpr std::size_t kEcGamalEncryptedTotalLength = kEcGamalEncryptedLength * 2; /** Length of EC point (compressed) */ -std::size_t constexpr kCompressedEcPointLength = 33; +constexpr std::size_t kCompressedEcPointLength = 33; /** Length of EC public key (compressed) */ -std::size_t constexpr kEcPubKeyLength = kCompressedEcPointLength; +constexpr std::size_t kEcPubKeyLength = kCompressedEcPointLength; /** Length of EC private key in bytes */ -std::size_t constexpr kEcPrivKeyLength = 32; +constexpr std::size_t kEcPrivKeyLength = 32; /** Length of the EC blinding factor in bytes */ -std::size_t constexpr kEcBlindingFactorLength = 32; +constexpr std::size_t kEcBlindingFactorLength = 32; /** Length of Schnorr ZKProof for public key registration (compact form) in bytes */ -std::size_t constexpr kEcSchnorrProofLength = 64; +constexpr std::size_t kEcSchnorrProofLength = 64; /** Length of Pedersen Commitment (compressed) */ -std::size_t constexpr kEcPedersenCommitmentLength = kCompressedEcPointLength; +constexpr std::size_t kEcPedersenCommitmentLength = kCompressedEcPointLength; /** Length of single bulletproof (range proof for 1 commitment) in bytes */ -std::size_t constexpr kEcSingleBulletproofLength = 688; +constexpr std::size_t kEcSingleBulletproofLength = 688; /** Length of double bulletproof (range proof for 2 commitments) in bytes */ -std::size_t constexpr kEcDoubleBulletproofLength = 754; +constexpr std::size_t kEcDoubleBulletproofLength = 754; /** Length of the ZKProof for ConfidentialMPTSend. * 192 bytes compact sigma proof + 754 bytes double bulletproof. */ -std::size_t constexpr kEcSendProofLength = 946; +constexpr std::size_t kEcSendProofLength = 946; /** Length of the ZKProof for ConfidentialMPTConvertBack. * 128 bytes compact sigma proof + 688 bytes single bulletproof. */ -std::size_t constexpr kEcConvertBackProofLength = 816; +constexpr std::size_t kEcConvertBackProofLength = 816; /** Length of the ZKProof for ConfidentialMPTClawback. */ -std::size_t constexpr kEcClawbackProofLength = 64; +constexpr std::size_t kEcClawbackProofLength = 64; /** Extra base fee multiplier charged to confidential MPT transactions. */ -std::uint32_t constexpr kConfidentialFeeMultiplier = 9; +constexpr std::uint32_t kConfidentialFeeMultiplier = 9; /** Compressed EC point prefix for even y-coordinate */ -std::uint8_t constexpr kEcCompressedPrefixEvenY = 0x02; +constexpr std::uint8_t kEcCompressedPrefixEvenY = 0x02; /** Compressed EC point prefix for odd y-coordinate */ -std::uint8_t constexpr kEcCompressedPrefixOddY = 0x03; +constexpr std::uint8_t kEcCompressedPrefixOddY = 0x03; } // namespace xrpl diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 0310f2d908..bc283e2e4c 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -401,8 +401,8 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({ {sfDomainID, SoeOptional}, {sfMutableFlags, SoeDefault}, {sfReferenceHolding, SoeOptional}, - {sfIssuerEncryptionKey, SoeOptional}, - {sfAuditorEncryptionKey, SoeOptional}, + {sfIssuerEncryptionKey, SoeOptional}, + {sfAuditorEncryptionKey, SoeOptional}, {sfConfidentialOutstandingAmount, SoeDefault}, })) diff --git a/include/xrpl/protocol/detail/secp256k1.h b/include/xrpl/protocol/detail/secp256k1.h index e2926fad19..5ca9033eae 100644 --- a/include/xrpl/protocol/detail/secp256k1.h +++ b/include/xrpl/protocol/detail/secp256k1.h @@ -11,8 +11,8 @@ secp256k1Context() struct Holder { secp256k1_context* impl; - // SECP256K1_CONTEXT_SIGN and SECP256K1_CONTEXT_VERIFY were - // deprecated. All contexts support both signing and verification, so + // SECP256K1_CONTEXT_SIGN and SECP256K1_CONTEXT_VERIFY were deprecated. + // All contexts support both signing and verification, so // SECP256K1_CONTEXT_NONE is the correct flag to use. Holder() : impl(secp256k1_context_create(SECP256K1_CONTEXT_NONE)) { diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index d4bb3099a1..8acf3fe1c5 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -302,21 +302,21 @@ TYPED_SFIELD(sfAssetClass, VL, 28) TYPED_SFIELD(sfProvider, VL, 29) TYPED_SFIELD(sfMPTokenMetadata, VL, 30) TYPED_SFIELD(sfCredentialType, VL, 31) -TYPED_SFIELD(sfConfidentialBalanceInbox, VL, 32) +TYPED_SFIELD(sfConfidentialBalanceInbox, VL, 32) TYPED_SFIELD(sfConfidentialBalanceSpending, VL, 33) -TYPED_SFIELD(sfIssuerEncryptedBalance, VL, 34) -TYPED_SFIELD(sfIssuerEncryptionKey, VL, 35) -TYPED_SFIELD(sfHolderEncryptionKey, VL, 36) -TYPED_SFIELD(sfZKProof, VL, 37) -TYPED_SFIELD(sfHolderEncryptedAmount, VL, 38) -TYPED_SFIELD(sfIssuerEncryptedAmount, VL, 39) -TYPED_SFIELD(sfSenderEncryptedAmount, VL, 40) -TYPED_SFIELD(sfDestinationEncryptedAmount, VL, 41) -TYPED_SFIELD(sfAuditorEncryptedBalance, VL, 42) -TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43) -TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44) -TYPED_SFIELD(sfAmountCommitment, VL, 45) -TYPED_SFIELD(sfBalanceCommitment, VL, 46) +TYPED_SFIELD(sfIssuerEncryptedBalance, VL, 34) +TYPED_SFIELD(sfIssuerEncryptionKey, VL, 35) +TYPED_SFIELD(sfHolderEncryptionKey, VL, 36) +TYPED_SFIELD(sfZKProof, VL, 37) +TYPED_SFIELD(sfHolderEncryptedAmount, VL, 38) +TYPED_SFIELD(sfIssuerEncryptedAmount, VL, 39) +TYPED_SFIELD(sfSenderEncryptedAmount, VL, 40) +TYPED_SFIELD(sfDestinationEncryptedAmount, VL, 41) +TYPED_SFIELD(sfAuditorEncryptedBalance, VL, 42) +TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43) +TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44) +TYPED_SFIELD(sfAmountCommitment, VL, 45) +TYPED_SFIELD(sfBalanceCommitment, VL, 46) // account (common) TYPED_SFIELD(sfAccount, ACCOUNT, 1) diff --git a/include/xrpl/tx/invariants/MPTInvariant.h b/include/xrpl/tx/invariants/MPTInvariant.h index 01d91f6ae9..92ce67c252 100644 --- a/include/xrpl/tx/invariants/MPTInvariant.h +++ b/include/xrpl/tx/invariants/MPTInvariant.h @@ -36,17 +36,42 @@ class ValidMPTIssuance std::vector> deletedHoldings_; public: + /** + * @brief Track MPT issuance and holding creations, deletions, and + * mutations. + * + * @param isDelete Whether the ledger entry is being deleted. + * @param before The ledger entry before transaction application. + * @param after The ledger entry after transaction application. + */ void - visitEntry(bool, SLE::const_ref, SLE::const_ref); + visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after); + /** + * @brief Verify MPT issuance invariants after transaction application. + * + * @param tx The transaction being checked. + * @param result The transaction result code. + * @param fee The fee charged by the transaction. + * @param view The ledger view after transaction application. + * @param j Journal used for diagnostics. + * @return true if the invariant checks pass, otherwise false. + */ [[nodiscard]] bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; + finalize( + STTx const& tx, + TER const result, + XRPAmount const fee, + ReadView const& view, + beast::Journal const& j) const; }; -/** Verify: - * - OutstandingAmount <= MaximumAmount for any MPT - * - OutstandingAmount after = OutstandingAmount before + - * sum (MPT after - MPT before) - this is total MPT credit/debit +/** + * @brief Verify public MPT amount and outstanding amount accounting. + * + * Checks that OutstandingAmount does not exceed MaximumAmount and that + * OutstandingAmount after application equals OutstandingAmount before + * application plus the net holder balance delta. */ class ValidMPTPayment { @@ -64,11 +89,33 @@ class ValidMPTPayment hash_map data_; public: + /** + * @brief Track MPT amount and outstanding amount changes. + * + * @param isDelete Whether the ledger entry is being deleted. + * @param before The ledger entry before transaction application. + * @param after The ledger entry after transaction application. + */ void - visitEntry(bool, SLE::const_ref, SLE::const_ref); + visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after); + /** + * @brief Verify public MPT payment accounting invariants. + * + * @param tx The transaction being checked. + * @param result The transaction result code. + * @param fee The fee charged by the transaction. + * @param view The ledger view after transaction application. + * @param j Journal used for diagnostics. + * @return true if the invariant checks pass, otherwise false. + */ bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); + finalize( + STTx const& tx, + TER const result, + XRPAmount const fee, + ReadView const& view, + beast::Journal const& j); }; /** @@ -107,11 +154,37 @@ class ValidConfidentialMPToken std::map changes_; public: + /** + * @brief Track confidential MPT balance, issuance, and version changes. + * + * @param isDelete Whether the ledger entry is being deleted. + * @param before The ledger entry before transaction application. + * @param after The ledger entry after transaction application. + */ void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after); + /** + * @brief Verify confidential MPT accounting and encrypted-field + * invariants. + * + * @param tx The transaction being checked. + * @param result The transaction result code. + * @param fee The fee charged by the transaction. + * @param view The ledger view after transaction application. + * @param j Journal used for diagnostics. + * @return true if the invariant checks pass, otherwise false. + */ bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); + finalize( + STTx const& tx, + TER const result, + XRPAmount const fee, + ReadView const& view, + beast::Journal const& j); }; class ValidMPTTransfer @@ -128,11 +201,36 @@ class ValidMPTTransfer hash_map deletedAuthorized_; public: + /** + * @brief Track MPT balance changes and deleted authorization state. + * + * @param isDelete Whether the ledger entry is being deleted. + * @param before The ledger entry before transaction application. + * @param after The ledger entry after transaction application. + */ void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after); + /** + * @brief Verify MPT transfer authorization invariants. + * + * @param tx The transaction being checked. + * @param result The transaction result code. + * @param fee The fee charged by the transaction. + * @param view The ledger view after transaction application. + * @param j Journal used for diagnostics. + * @return true if the invariant checks pass, otherwise false. + */ bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); + finalize( + STTx const& tx, + TER const result, + XRPAmount const fee, + ReadView const& view, + beast::Journal const& j); private: /** @@ -142,7 +240,13 @@ private: * finalize() runs, so their authorization state is captured during * visitEntry() and stored in deletedAuthorized_. For deleted MPTokens, * returns true if reqAuth is false or lsfMPTAuthorized was set at deletion. - * For existing MPTokens, returns the result of requireAuth() + * For existing MPTokens, returns the result of requireAuth(). + * + * @param view The ledger view after transaction application. + * @param mptid The MPToken issuance ID. + * @param holder The holder account being checked. + * @param requireAuth Whether the issuance requires explicit authorization. + * @return true if the holder is authorized, otherwise false. */ [[nodiscard]] bool isAuthorized( diff --git a/include/xrpl/tx/transactors/token/ConfidentialMPTConvert.h b/include/xrpl/tx/transactors/token/ConfidentialMPTConvert.h index 993469ff48..2e2591844f 100644 --- a/include/xrpl/tx/transactors/token/ConfidentialMPTConvert.h +++ b/include/xrpl/tx/transactors/token/ConfidentialMPTConvert.h @@ -26,7 +26,6 @@ class ConfidentialMPTConvert : public Transactor { public: static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal; - ; explicit ConfidentialMPTConvert(ApplyContext& ctx) : Transactor(ctx) { diff --git a/include/xrpl/tx/transactors/token/ConfidentialMPTConvertBack.h b/include/xrpl/tx/transactors/token/ConfidentialMPTConvertBack.h index ddc7ec3be3..cb4b6295a0 100644 --- a/include/xrpl/tx/transactors/token/ConfidentialMPTConvertBack.h +++ b/include/xrpl/tx/transactors/token/ConfidentialMPTConvertBack.h @@ -27,7 +27,6 @@ class ConfidentialMPTConvertBack : public Transactor { public: static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal; - ; explicit ConfidentialMPTConvertBack(ApplyContext& ctx) : Transactor(ctx) { diff --git a/include/xrpl/tx/transactors/token/ConfidentialMPTMergeInbox.h b/include/xrpl/tx/transactors/token/ConfidentialMPTMergeInbox.h index 20648b89a6..585c273e12 100644 --- a/include/xrpl/tx/transactors/token/ConfidentialMPTMergeInbox.h +++ b/include/xrpl/tx/transactors/token/ConfidentialMPTMergeInbox.h @@ -28,7 +28,6 @@ class ConfidentialMPTMergeInbox : public Transactor { public: static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal; - ; explicit ConfidentialMPTMergeInbox(ApplyContext& ctx) : Transactor(ctx) { diff --git a/include/xrpl/tx/transactors/token/ConfidentialMPTSend.h b/include/xrpl/tx/transactors/token/ConfidentialMPTSend.h index 1684aed7ed..72599ab987 100644 --- a/include/xrpl/tx/transactors/token/ConfidentialMPTSend.h +++ b/include/xrpl/tx/transactors/token/ConfidentialMPTSend.h @@ -34,7 +34,6 @@ class ConfidentialMPTSend : public Transactor { public: static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal; - ; explicit ConfidentialMPTSend(ApplyContext& ctx) : Transactor(ctx) { diff --git a/src/libxrpl/protocol/ConfidentialTransfer.cpp b/src/libxrpl/protocol/ConfidentialTransfer.cpp index 5786820f74..15c0c1334f 100644 --- a/src/libxrpl/protocol/ConfidentialTransfer.cpp +++ b/src/libxrpl/protocol/ConfidentialTransfer.cpp @@ -26,13 +26,8 @@ #include namespace xrpl { +namespace { -/** - * @brief Converts an XRPL AccountID to mpt-crypto lib C struct. - * - * @param account The AccountID. - * @return The equivalent mpt-crypto lib account_id struct. - */ account_id toAccountId(AccountID const& account) { @@ -41,12 +36,6 @@ toAccountId(AccountID const& account) return res; } -/** - * @brief Converts an XRPL uint192 to mpt-crypto lib C struct. - * - * @param i The XRPL MPTokenIssuance ID. - * @return The equivalent mpt-crypto lib mpt_issuance_id struct. - */ mpt_issuance_id toIssuanceId(uint192 const& issuance) { @@ -55,6 +44,8 @@ toIssuanceId(uint192 const& issuance) return res; } +} // namespace + uint256 getSendContextHash( AccountID const& account, @@ -430,10 +421,11 @@ verifySendProof( return p; }; - std::vector participants(recipientCount); - participants[0] = makeParticipant(sender); - participants[1] = makeParticipant(destination); - participants[2] = makeParticipant(issuer); + std::vector participants; + participants.reserve(recipientCount); + participants.push_back(makeParticipant(sender)); + participants.push_back(makeParticipant(destination)); + participants.push_back(makeParticipant(issuer)); if (auditor) { if (auditor->publicKey.size() != kEcPubKeyLength || @@ -441,8 +433,10 @@ verifySendProof( { return tecINTERNAL; // LCOV_EXCL_LINE } - participants[3] = makeParticipant(*auditor); + participants.push_back(makeParticipant(*auditor)); } + if (participants.size() != recipientCount) + return tecINTERNAL; // LCOV_EXCL_LINE if (mpt_verify_send_proof( proof.data(), diff --git a/src/libxrpl/tx/invariants/MPTInvariant.cpp b/src/libxrpl/tx/invariants/MPTInvariant.cpp index 72926646c1..d019422de8 100644 --- a/src/libxrpl/tx/invariants/MPTInvariant.cpp +++ b/src/libxrpl/tx/invariants/MPTInvariant.cpp @@ -28,6 +28,7 @@ #include #include #include + namespace xrpl { static constexpr auto kConfidentialMptTxTypes = std::to_array({ @@ -454,7 +455,9 @@ ValidMPTPayment::finalize( // so ValidMPTPayment's accounting does not apply to them. if (std::ranges::find(kConfidentialMptTxTypes, tx.getTxnType()) != kConfidentialMptTxTypes.end()) + { return true; + } bool const invariantPasses = !view.rules().enabled(featureMPTokensV2); if (overflow_) @@ -597,9 +600,7 @@ ValidConfidentialMPToken::visitEntry( if (spendingBefore.has_value() && spendingBefore != spendingAfter) { if (versionBefore == versionAfter) - { changes_[id].badVersion = true; - } } } } diff --git a/src/libxrpl/tx/transactors/token/ConfidentialMPTConvert.cpp b/src/libxrpl/tx/transactors/token/ConfidentialMPTConvert.cpp index 5f5dc51773..6370d82e00 100644 --- a/src/libxrpl/tx/transactors/token/ConfidentialMPTConvert.cpp +++ b/src/libxrpl/tx/transactors/token/ConfidentialMPTConvert.cpp @@ -178,16 +178,22 @@ ConfidentialMPTConvert::preclaim(PreclaimContext const& ctx) auditor.emplace( ConfidentialRecipient{ .publicKey = (*sleIssuance)[sfAuditorEncryptionKey], - .encryptedAmount = ctx.tx[sfAuditorEncryptedAmount]}); + .encryptedAmount = ctx.tx[sfAuditorEncryptedAmount], + }); } auto const blindingFactor = ctx.tx[sfBlindingFactor]; if (auto const ter = verifyRevealedAmount( amount, Slice(blindingFactor.data(), blindingFactor.size()), - {.publicKey = holderPubKey, .encryptedAmount = ctx.tx[sfHolderEncryptedAmount]}, - {.publicKey = (*sleIssuance)[sfIssuerEncryptionKey], - .encryptedAmount = ctx.tx[sfIssuerEncryptedAmount]}, + { + .publicKey = holderPubKey, + .encryptedAmount = ctx.tx[sfHolderEncryptedAmount], + }, + { + .publicKey = (*sleIssuance)[sfIssuerEncryptionKey], + .encryptedAmount = ctx.tx[sfIssuerEncryptedAmount], + }, auditor); !isTesSuccess(ter)) { diff --git a/src/libxrpl/tx/transactors/token/ConfidentialMPTConvertBack.cpp b/src/libxrpl/tx/transactors/token/ConfidentialMPTConvertBack.cpp index 9af793b018..1754ec9c4e 100644 --- a/src/libxrpl/tx/transactors/token/ConfidentialMPTConvertBack.cpp +++ b/src/libxrpl/tx/transactors/token/ConfidentialMPTConvertBack.cpp @@ -100,7 +100,8 @@ verifyProofs( auditor.emplace( ConfidentialRecipient{ .publicKey = (*issuance)[sfAuditorEncryptionKey], - .encryptedAmount = tx[sfAuditorEncryptedAmount]}); + .encryptedAmount = tx[sfAuditorEncryptedAmount], + }); } // Run all verifications before returning any error to prevent timing attacks @@ -110,9 +111,14 @@ verifyProofs( if (auto const ter = verifyRevealedAmount( amount, Slice(blindingFactor.data(), blindingFactor.size()), - {.publicKey = holderPubKey, .encryptedAmount = tx[sfHolderEncryptedAmount]}, - {.publicKey = (*issuance)[sfIssuerEncryptionKey], - .encryptedAmount = tx[sfIssuerEncryptedAmount]}, + { + .publicKey = holderPubKey, + .encryptedAmount = tx[sfHolderEncryptedAmount], + }, + { + .publicKey = (*issuance)[sfIssuerEncryptionKey], + .encryptedAmount = tx[sfIssuerEncryptedAmount], + }, auditor); !isTesSuccess(ter)) { @@ -202,7 +208,7 @@ ConfidentialMPTConvertBack::preclaim(PreclaimContext const& ctx) if (auto const ter = requireAuth(ctx.view, mptIssue, account); !isTesSuccess(ter)) return ter; - if (TER const res = verifyProofs(ctx.tx, sleIssuance, sleMptoken); !isTesSuccess(res)) + if (auto const res = verifyProofs(ctx.tx, sleIssuance, sleMptoken); !isTesSuccess(res)) return res; return tesSUCCESS; diff --git a/src/libxrpl/tx/transactors/token/ConfidentialMPTSend.cpp b/src/libxrpl/tx/transactors/token/ConfidentialMPTSend.cpp index e7e52b945e..0959293fee 100644 --- a/src/libxrpl/tx/transactors/token/ConfidentialMPTSend.cpp +++ b/src/libxrpl/tx/transactors/token/ConfidentialMPTSend.cpp @@ -118,7 +118,8 @@ verifySendProofs( auditor.emplace( ConfidentialRecipient{ .publicKey = (*sleIssuance)[sfAuditorEncryptionKey], - .encryptedAmount = ctx.tx[sfAuditorEncryptedAmount]}); + .encryptedAmount = ctx.tx[sfAuditorEncryptedAmount], + }); } auto const contextHash = getSendContextHash( @@ -130,12 +131,18 @@ verifySendProofs( return verifySendProof( ctx.tx[sfZKProof], - {.publicKey = (*sleSenderMPToken)[sfHolderEncryptionKey], - .encryptedAmount = ctx.tx[sfSenderEncryptedAmount]}, - {.publicKey = (*sleDestinationMPToken)[sfHolderEncryptionKey], - .encryptedAmount = ctx.tx[sfDestinationEncryptedAmount]}, - {.publicKey = (*sleIssuance)[sfIssuerEncryptionKey], - .encryptedAmount = ctx.tx[sfIssuerEncryptedAmount]}, + { + .publicKey = (*sleSenderMPToken)[sfHolderEncryptionKey], + .encryptedAmount = ctx.tx[sfSenderEncryptedAmount], + }, + { + .publicKey = (*sleDestinationMPToken)[sfHolderEncryptionKey], + .encryptedAmount = ctx.tx[sfDestinationEncryptedAmount], + }, + { + .publicKey = (*sleIssuance)[sfIssuerEncryptionKey], + .encryptedAmount = ctx.tx[sfIssuerEncryptedAmount], + }, auditor, (*sleSenderMPToken)[sfConfidentialBalanceSpending], ctx.tx[sfAmountCommitment], @@ -256,9 +263,10 @@ ConfidentialMPTSend::preclaim(PreclaimContext const& ctx) // Check deposit preauth before the expensive ZK proof verification. // Uses read-only view. - if (auto const err = checkDepositPreauth(ctx.tx, ctx.view, account, destination, sleDst, ctx.j); - !isTesSuccess(err)) - return err; + auto const preauthErr = + checkDepositPreauth(ctx.tx, ctx.view, account, destination, sleDst, ctx.j); + if (!isTesSuccess(preauthErr)) + return preauthErr; return verifySendProofs(ctx, sleSenderMPToken, sleDestinationMPToken, sleIssuance); } diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp index 7899221b16..e50e1afcbc 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp @@ -54,36 +54,51 @@ struct MPTMutabilityFlags bool isCannotMutate = false; // if true, cannot mutate by default. }; -static constexpr std::array kMptMutabilityFlags = { - {{.setFlag = tmfMPTSetCanLock, - .clearFlag = tmfMPTClearCanLock, - .mutabilityFlag = lsmfMPTCanMutateCanLock, - .targetFlag = lsfMPTCanLock}, - {.setFlag = tmfMPTSetRequireAuth, - .clearFlag = tmfMPTClearRequireAuth, - .mutabilityFlag = lsmfMPTCanMutateRequireAuth, - .targetFlag = lsfMPTRequireAuth}, - {.setFlag = tmfMPTSetCanEscrow, - .clearFlag = tmfMPTClearCanEscrow, - .mutabilityFlag = lsmfMPTCanMutateCanEscrow, - .targetFlag = lsfMPTCanEscrow}, - {.setFlag = tmfMPTSetCanTrade, - .clearFlag = tmfMPTClearCanTrade, - .mutabilityFlag = lsmfMPTCanMutateCanTrade, - .targetFlag = lsfMPTCanTrade}, - {.setFlag = tmfMPTSetCanTransfer, - .clearFlag = tmfMPTClearCanTransfer, - .mutabilityFlag = lsmfMPTCanMutateCanTransfer, - .targetFlag = lsfMPTCanTransfer}, - {.setFlag = tmfMPTSetCanClawback, - .clearFlag = tmfMPTClearCanClawback, - .mutabilityFlag = lsmfMPTCanMutateCanClawback, - .targetFlag = lsfMPTCanClawback}, - {.setFlag = tmfMPTSetCanConfidentialAmount, - .clearFlag = tmfMPTClearCanConfidentialAmount, - .mutabilityFlag = lsmfMPTCannotMutateCanConfidentialAmount, - .targetFlag = lsfMPTCanConfidentialAmount, - .isCannotMutate = true}}}; +static constexpr auto kMptMutabilityFlags = std::to_array({ + { + .setFlag = tmfMPTSetCanLock, + .clearFlag = tmfMPTClearCanLock, + .mutabilityFlag = lsmfMPTCanMutateCanLock, + .targetFlag = lsfMPTCanLock, + }, + { + .setFlag = tmfMPTSetRequireAuth, + .clearFlag = tmfMPTClearRequireAuth, + .mutabilityFlag = lsmfMPTCanMutateRequireAuth, + .targetFlag = lsfMPTRequireAuth, + }, + { + .setFlag = tmfMPTSetCanEscrow, + .clearFlag = tmfMPTClearCanEscrow, + .mutabilityFlag = lsmfMPTCanMutateCanEscrow, + .targetFlag = lsfMPTCanEscrow, + }, + { + .setFlag = tmfMPTSetCanTrade, + .clearFlag = tmfMPTClearCanTrade, + .mutabilityFlag = lsmfMPTCanMutateCanTrade, + .targetFlag = lsfMPTCanTrade, + }, + { + .setFlag = tmfMPTSetCanTransfer, + .clearFlag = tmfMPTClearCanTransfer, + .mutabilityFlag = lsmfMPTCanMutateCanTransfer, + .targetFlag = lsfMPTCanTransfer, + }, + { + .setFlag = tmfMPTSetCanClawback, + .clearFlag = tmfMPTClearCanClawback, + .mutabilityFlag = lsmfMPTCanMutateCanClawback, + .targetFlag = lsfMPTCanClawback, + }, + { + .setFlag = tmfMPTSetCanConfidentialAmount, + .clearFlag = tmfMPTClearCanConfidentialAmount, + .mutabilityFlag = lsmfMPTCannotMutateCanConfidentialAmount, + .targetFlag = lsfMPTCanConfidentialAmount, + .isCannotMutate = true, + }, +}); NotTEC MPTokenIssuanceSet::preflight(PreflightContext const& ctx) diff --git a/src/test/app/ConfidentialTransferExtended_test.cpp b/src/test/app/ConfidentialTransferExtended_test.cpp index 538417b15b..92b34c419a 100644 --- a/src/test/app/ConfidentialTransferExtended_test.cpp +++ b/src/test/app/ConfidentialTransferExtended_test.cpp @@ -36,7 +36,7 @@ namespace xrpl { // NOLINTBEGIN(misc-const-correctness, bugprone-unchecked-optional-access) -class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase +class ConfidentialTransferExtended_test : public ConfidentialTransferTestBase { void testSendDepositPreauth(FeatureBitset features) @@ -567,8 +567,7 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase auto& mpt = confEnv.mpt; auto constexpr kCredIdx = - "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" - "E4"; + "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4"; mpt.send({ .account = carol, @@ -621,13 +620,13 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase BEAST_EXPECT(ammSle && ammSle->isFieldPresent(sfAMMID)); BEAST_EXPECT(mptAlice.getBalance(ammHolder) == 100); - BEAST_EXPECT(!mptAlice.getEncryptedBalance(ammHolder, MPTTester::HolderEncryptedInbox)); + BEAST_EXPECT(!mptAlice.getEncryptedBalance(ammHolder, MPTTester::holderEncryptedInbox)); BEAST_EXPECT( - !mptAlice.getEncryptedBalance(ammHolder, MPTTester::HolderEncryptedSpending)); + !mptAlice.getEncryptedBalance(ammHolder, MPTTester::holderEncryptedSpending)); BEAST_EXPECT( - !mptAlice.getEncryptedBalance(ammHolder, MPTTester::IssuerEncryptedBalance)); + !mptAlice.getEncryptedBalance(ammHolder, MPTTester::issuerEncryptedBalance)); BEAST_EXPECT( - !mptAlice.getEncryptedBalance(ammHolder, MPTTester::AuditorEncryptedBalance)); + !mptAlice.getEncryptedBalance(ammHolder, MPTTester::auditorEncryptedBalance)); mptAlice.confidentialClaw({ .account = alice, @@ -678,11 +677,12 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase { std::uint32_t const ticketSeq = env.seq(bob) + 1; env(ticket::create(bob, 1)); - mptAlice.convert( - {.account = bob, - .amt = 50, - .holderPubKey = mptAlice.getPubKey(bob), - .ticketSeq = ticketSeq}); + mptAlice.convert({ + .account = bob, + .amt = 50, + .holderPubKey = mptAlice.getPubKey(bob), + .ticketSeq = ticketSeq, + }); env.require(MptBalance(mptAlice, bob, 50)); } @@ -944,8 +944,8 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase // AllOrNothing: inner 3 fails // bob's spending must remain 100; carol's inbox must remain 0. - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 100); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 100); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 0); } // Bob sends to two recipients (Carol and Dave) in one batch. @@ -978,9 +978,9 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase // Nothing applied: bob stays 150, carol and dave inbox stay 0. BEAST_EXPECT( - mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 150); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedInbox) == 0); - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 0); + mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 150); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 0); } // If we change batch mode to be tfIndependent — txn 1 applies, inner 2 fails. @@ -999,10 +999,10 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase // bob 150→100, carol inbox 0→50 BEAST_EXPECT( - mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 100); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedInbox) == 50); + mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 100); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 50); // dave gets nothing - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 0); } } @@ -1042,10 +1042,10 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); // Both txns applied: bob 200→0, carol inbox=100, dave inbox=100. - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 0); BEAST_EXPECT( - mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedInbox) == 100); - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 100); + mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 100); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 100); } // Now Bob has 150, but tries to send two 100 in one batch. @@ -1079,10 +1079,10 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase // AllOrNothing: inner 2 fails → nothing applied. BEAST_EXPECT( - mpt2.getDecryptedBalance(bob2, MPTTester::HolderEncryptedSpending) == 150); + mpt2.getDecryptedBalance(bob2, MPTTester::holderEncryptedSpending) == 150); BEAST_EXPECT( - mpt2.getDecryptedBalance(carol2, MPTTester::HolderEncryptedInbox) == 0); - BEAST_EXPECT(mpt2.getDecryptedBalance(dave2, MPTTester::HolderEncryptedInbox) == 0); + mpt2.getDecryptedBalance(carol2, MPTTester::holderEncryptedInbox) == 0); + BEAST_EXPECT(mpt2.getDecryptedBalance(dave2, MPTTester::holderEncryptedInbox) == 0); } } } @@ -1131,8 +1131,8 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase // spending balance: 100 - 30 = 70 // inbox: 0 + 50 (from convert) = 50 env.require(MptBalance(mpt, bob, 30)); - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 70); - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedInbox) == 50); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 70); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedInbox) == 50); } // convert + mergeInbox + convertBack, stale convertBack proof. @@ -1171,8 +1171,8 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase // jv3 fails so nothing is applied. env.require(MptBalance(mpt, bob, 50)); - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 100); - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 100); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedInbox) == 0); } } @@ -1240,12 +1240,12 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); // All four applied: - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 70); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 70); // carol's inbox was merged: spending=80, inbox=0 - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedSpending) == 80); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedSpending) == 80); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 0); // dave: spending=30, regular=20 - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedSpending) == 30); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedSpending) == 30); env.require(MptBalance(mpt, dave, 20)); } @@ -1279,8 +1279,8 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); // AllOrNothing: jv2 fails (stale proof) → nothing applied. - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 100); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 100); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 0); } } @@ -1319,9 +1319,9 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); // Both txn applied: bob's balance 100→90, carol 60→55, dave inbox 0→15 - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 90); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedSpending) == 55); - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 15); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 90); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedSpending) == 55); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 15); } } @@ -1359,9 +1359,9 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); // No success found → nothing applied; balances unchanged - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 100); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedSpending) == 60); - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 100); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedSpending) == 60); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 0); } // bob sends dave 200 (invalid), carol sends dave 5 (valid) @@ -1381,9 +1381,9 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); // Only carol's send applied: carol 60→55, dave inbox 0→5, bob unchanged - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 100); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedSpending) == 55); - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 5); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 100); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedSpending) == 55); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 5); } } @@ -1420,8 +1420,8 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase Ter(tesSUCCESS)); env.close(); - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 100); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedSpending) == 60); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 100); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedSpending) == 60); } // Bob sends dave 10, Carol sends dave 5 — both valid and independent @@ -1441,9 +1441,9 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); // Both applied: bob 100→90, carol 60→55, dave inbox 0→15 - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 90); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedSpending) == 55); - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 15); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 90); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedSpending) == 55); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 15); } } @@ -1486,11 +1486,11 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); // inner 1 (bob→dave 10) applied: bob 100→90 - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 90); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 90); // inner 2 failed (carol not changed), inner 3 applied: carol 60→55 - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedSpending) == 55); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedSpending) == 55); // dave inbox: 10 (from bob) + 5 (from carol inner 3) = 15 - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 15); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 15); } } @@ -1541,9 +1541,9 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); // Both sends applied: bob 100→40, carol inbox=40, dave inbox=20. - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 40); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedInbox) == 40); - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 20); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 40); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 40); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 20); } // inner transactions each consume their own ticket. @@ -1582,9 +1582,9 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); // Both sends applied: bob 100→30, carol inbox=40, dave inbox=30. - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 30); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedInbox) == 40); - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 30); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 30); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 40); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 30); } // inner send uses wrong sequence (account seq instead of ticket seq) @@ -1615,8 +1615,8 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase Ter(tesSUCCESS)); env.close(); - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 100); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 100); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 0); } } @@ -1829,17 +1829,22 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase mptAlice.generateKeyPair(bob); mptAlice.generateKeyPair(carol); mptAlice.generateKeyPair(auditor); - mptAlice.set( - {.issuerPubKey = mptAlice.getPubKey(alice), - .auditorPubKey = mptAlice.getPubKey(auditor)}); + mptAlice.set({ + .issuerPubKey = mptAlice.getPubKey(alice), + .auditorPubKey = mptAlice.getPubKey(auditor), + }); // Bob delegates Convert and Send permissions to dave. env(delegate::set(bob, dave, {"ConfidentialMPTSend", "ConfidentialMPTConvert"})); env.close(); // Dave converts on behalf of bob. - mptAlice.convert( - {.account = bob, .amt = 50, .holderPubKey = mptAlice.getPubKey(bob), .delegate = dave}); + mptAlice.convert({ + .account = bob, + .amt = 50, + .holderPubKey = mptAlice.getPubKey(bob), + .delegate = dave, + }); mptAlice.mergeInbox({.account = bob}); mptAlice.convert({ @@ -1957,9 +1962,9 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); // Stale proof on jv2, AllOrNothing rolls back everything. - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 100); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedInbox) == 0); - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 100); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 0); } // AllOrNothing: two delegated sends with correctly chained proofs both apply. @@ -1994,9 +1999,9 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); // Both inner tx applied - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 20); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedInbox) == 40); - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 40); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 20); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 40); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 40); } } @@ -2042,9 +2047,9 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase // jv1 applied in the batch view, then jv2 failed, so // AllOrNothing discards both inner effects. - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 100); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedSpending) == 60); - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 100); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedSpending) == 60); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 0); } // Independent: the delegated confidential send is skipped because lack of permission. The @@ -2076,9 +2081,9 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); // jv1 failed and jv2 applied. - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 100); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedSpending) == 30); - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 30); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 100); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedSpending) == 30); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 30); } } @@ -2118,8 +2123,8 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase Ter(temBAD_SIGNER)); env.close(); - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 100); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 100); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 0); } // Dave submits a mixed batch: bob signs inner tx1, and @@ -2155,8 +2160,8 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); // Both sends applied: bob 100→30, carol inbox=70. - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 30); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedInbox) == 70); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 30); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 70); } // Verify the delegator Bob's BatchSigner does not bypass the missing delegation permission. @@ -2189,9 +2194,9 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); // jv1 fails before jv2 is attempted. - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 100); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedSpending) == 60); - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 100); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedSpending) == 60); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 0); } } @@ -2258,17 +2263,17 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase env.close(); env.require(MptBalance(mpt, bob, 60)); - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == 105); - BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 105); + BEAST_EXPECT(mpt.getDecryptedBalance(bob, MPTTester::holderEncryptedInbox) == 0); env.require(MptBalance(mpt, carol, 0)); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedSpending) == 45); - BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedSpending) == 45); + BEAST_EXPECT(mpt.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 0); env.require(MptBalance(mpt, frank, 15)); - BEAST_EXPECT(mpt.getDecryptedBalance(frank, MPTTester::HolderEncryptedSpending) == 25); - BEAST_EXPECT(mpt.getDecryptedBalance(frank, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(frank, MPTTester::holderEncryptedSpending) == 25); + BEAST_EXPECT(mpt.getDecryptedBalance(frank, MPTTester::holderEncryptedInbox) == 0); env.require(MptBalance(mpt, dave, 0)); - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedSpending) == 0); - BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedSpending) == 0); + BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::holderEncryptedInbox) == 0); auto const outstandingBalance = mpt.getIssuanceOutstandingBalance(); BEAST_EXPECT(outstandingBalance && *outstandingBalance == 250); BEAST_EXPECT(mpt.getIssuanceConfidentialBalance() == 175); @@ -2319,17 +2324,18 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase auto const badProof = mptAlice.getSchnorrProof(bob, badCtxHash); BEAST_EXPECT(badProof.has_value()); - mptAlice.convert( - {.account = bob, - .amt = amt, - .proof = strHex(*badProof), - .holderPubKey = mptAlice.getPubKey(bob), - .holderEncryptedAmt = holderCt, - .issuerEncryptedAmt = issuerCt, - .blindingFactor = bf, - .delegate = carol, - .ticketSeq = ticketSeq, - .err = tecBAD_PROOF}); + mptAlice.convert({ + .account = bob, + .amt = amt, + .proof = strHex(*badProof), + .holderPubKey = mptAlice.getPubKey(bob), + .holderEncryptedAmt = holderCt, + .issuerEncryptedAmt = issuerCt, + .blindingFactor = bf, + .delegate = carol, + .ticketSeq = ticketSeq, + .err = tecBAD_PROOF, + }); } // Invalid: proof built with account sequence instead of ticket sequence. @@ -2340,17 +2346,18 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase auto const badProof = mptAlice.getSchnorrProof(bob, badCtxHash); BEAST_EXPECT(badProof.has_value()); - mptAlice.convert( - {.account = bob, - .amt = amt, - .proof = strHex(*badProof), - .holderPubKey = mptAlice.getPubKey(bob), - .holderEncryptedAmt = holderCt, - .issuerEncryptedAmt = issuerCt, - .blindingFactor = bf, - .delegate = carol, - .ticketSeq = ticketSeq, - .err = tecBAD_PROOF}); + mptAlice.convert({ + .account = bob, + .amt = amt, + .proof = strHex(*badProof), + .holderPubKey = mptAlice.getPubKey(bob), + .holderEncryptedAmt = holderCt, + .issuerEncryptedAmt = issuerCt, + .blindingFactor = bf, + .delegate = carol, + .ticketSeq = ticketSeq, + .err = tecBAD_PROOF, + }); } // Invalid: ticket sequence is far in the future and hasn't been created yet. @@ -2388,16 +2395,17 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase auto const carolTicketSeq = env.seq(carol) + 1; env(ticket::create(carol, 1)); - mptAlice.convert( - {.account = bob, - .amt = amt, - .holderPubKey = mptAlice.getPubKey(bob), - .holderEncryptedAmt = holderCt, - .issuerEncryptedAmt = issuerCt, - .blindingFactor = bf, - .delegate = carol, - .ticketSeq = carolTicketSeq, - .err = tefNO_TICKET}); + mptAlice.convert({ + .account = bob, + .amt = amt, + .holderPubKey = mptAlice.getPubKey(bob), + .holderEncryptedAmt = holderCt, + .issuerEncryptedAmt = issuerCt, + .blindingFactor = bf, + .delegate = carol, + .ticketSeq = carolTicketSeq, + .err = tefNO_TICKET, + }); } // Invalid: proof bound to a ticket sequence but submitted without a ticket, @@ -2413,16 +2421,17 @@ class ConfidentialTransferExtendedTest : public ConfidentialTransferTestBase BEAST_EXPECT(proof.has_value()); // Submit without ticket. - mptAlice.convert( - {.account = bob, - .amt = amt, - .proof = strHex(*proof), - .holderPubKey = mptAlice.getPubKey(bob), - .holderEncryptedAmt = holderCt, - .issuerEncryptedAmt = issuerCt, - .blindingFactor = bf, - .delegate = carol, - .err = tecBAD_PROOF}); + mptAlice.convert({ + .account = bob, + .amt = amt, + .proof = strHex(*proof), + .holderPubKey = mptAlice.getPubKey(bob), + .holderEncryptedAmt = holderCt, + .issuerEncryptedAmt = issuerCt, + .blindingFactor = bf, + .delegate = carol, + .err = tecBAD_PROOF, + }); } } @@ -2583,6 +2592,6 @@ public: }; // NOLINTEND(misc-const-correctness, bugprone-unchecked-optional-access) -// BEAST_DEFINE_TESTSUITE(ConfidentialTransferExtended, app, xrpl); +BEAST_DEFINE_TESTSUITE(ConfidentialTransferExtended, app, xrpl); } // namespace xrpl diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index 3b475bd0c2..46a9970e10 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -228,10 +228,11 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(auditor); - mptAlice.set( - {.account = alice, - .issuerPubKey = mptAlice.getPubKey(alice), - .auditorPubKey = mptAlice.getPubKey(auditor)}); + mptAlice.set({ + .account = alice, + .issuerPubKey = mptAlice.getPubKey(alice), + .auditorPubKey = mptAlice.getPubKey(auditor), + }); mptAlice.generateKeyPair(bob); @@ -2922,7 +2923,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase mptAlice.send({ .account = bob, .dest = carol, - .amt = 0xFFFFFFFFFFFFFFFF, // Max uint64 + .amt = std::numeric_limits::max(), .err = tecBAD_PROOF, }); @@ -2965,23 +2966,20 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase }); BEAST_EXPECT( - mptAlice2.getDecryptedBalance(bob2, MPTTester::HolderEncryptedSpending) == 0); + mptAlice2.getDecryptedBalance(bob2, MPTTester::holderEncryptedSpending) == 0); } // todo: test m exceeding range, require using scala and refactor } - /* TODO: uncomment when MPT crypto supports proof generation with value 0 - * Tests verifier behavior when the send amount is 0. - * - * The equality proof library and range proof library do not + /* The equality proof library and range proof library do not * support generating proofs for amt=0 (they require a positive witness). * To test the VERIFIER without crashing the helper, we bypass normal proof * generation by supplying explicit ciphertexts, commitments, and a dummy * (all-zero) proof. The preflight has no temBAD_AMOUNT guard for * ConfidentialMPTSend, so all validation occurs in verifySendProofs. */ - /*void + void testSendZeroAmount(FeatureBitset features) { testcase("Send: zero amount — equality and range proof verifier behavior"); @@ -3054,11 +3052,9 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase }); // All rejected sends must leave balances unchanged. - BEAST_EXPECT( - mptAlice.getDecryptedBalance(bob, MPTTester::HOLDER_ENCRYPTED_SPENDING) == 100); - BEAST_EXPECT( - mptAlice.getDecryptedBalance(carol, MPTTester::HOLDER_ENCRYPTED_INBOX) == 0); - }*/ + BEAST_EXPECT(mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == 100); + BEAST_EXPECT(mptAlice.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 0); + } void testDelete(FeatureBitset features) @@ -3438,7 +3434,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase // Get the encrypted spending balance from ledger (no decryption needed) auto const encryptedSpendingBalance = - mptAlice.getEncryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getEncryptedBalance(bob, MPTTester::holderEncryptedSpending); BEAST_EXPECT(encryptedSpendingBalance.has_value()); // Generate pedersen commitment for the known spending balance @@ -5238,10 +5234,10 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase Buffer const pcBlindingFactor = generateBlindingFactor(); auto const spendingBalance = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); BEAST_EXPECT(spendingBalance.has_value()); auto const encryptedSpendingBalance = - mptAlice.getEncryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getEncryptedBalance(bob, MPTTester::holderEncryptedSpending); BEAST_EXPECT(encryptedSpendingBalance.has_value() && !encryptedSpendingBalance->empty()); Buffer const pedersenCommitment = @@ -5457,10 +5453,10 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase Buffer const pcBlindingFactor = generateBlindingFactor(); auto const spendingBalance = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); BEAST_EXPECT(spendingBalance.has_value()); auto const encryptedSpendingBalance = - mptAlice.getEncryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getEncryptedBalance(bob, MPTTester::holderEncryptedSpending); BEAST_EXPECT(encryptedSpendingBalance.has_value() && !encryptedSpendingBalance->empty()); Buffer const pedersenCommitment = @@ -5617,9 +5613,9 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase Buffer const pcBlindingFactor = generateBlindingFactor(); auto const spendingBalance = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); auto const encryptedSpendingBalance = - mptAlice.getEncryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getEncryptedBalance(bob, MPTTester::holderEncryptedSpending); if (!BEAST_EXPECT(spendingBalance && encryptedSpendingBalance)) return; @@ -5712,9 +5708,9 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase auto& mptAlice = confEnv.mpt; auto const spendingBalance = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); auto const encryptedSpendingBalance = - mptAlice.getEncryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getEncryptedBalance(bob, MPTTester::holderEncryptedSpending); auto const version = mptAlice.getMPTokenVersion(bob); Buffer const pcBlindingFactor = generateBlindingFactor(); Buffer const pedersenCommitment = @@ -5744,15 +5740,16 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase Buffer const issuerCiphertextB = mptAlice.encryptAmount(alice, amtB, blindingFactorB); // We attempt to verify the proof pi (for amt 10) against the new ciphertexts (for amt 20). - mptAlice.convertBack( - {.account = bob, - .amt = amtB, - .proof = proofA, // Extracted/Reused proof from Transaction A - .holderEncryptedAmt = bobCiphertextB, - .issuerEncryptedAmt = issuerCiphertextB, - .blindingFactor = blindingFactorB, - .pedersenCommitment = pedersenCommitment, - .err = tecBAD_PROOF}); // Expected failure + mptAlice.convertBack({ + .account = bob, + .amt = amtB, + .proof = proofA, // Extracted/Reused proof from Transaction A + .holderEncryptedAmt = bobCiphertextB, + .issuerEncryptedAmt = issuerCiphertextB, + .blindingFactor = blindingFactorB, + .pedersenCommitment = pedersenCommitment, + .err = tecBAD_PROOF, // Expected failure + }); } // This test simulates a valid proof π and ciphertext are @@ -5773,9 +5770,9 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase auto const versionV = mptAlice.getMPTokenVersion(bob); auto const spendingBalanceV = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); auto const encryptedSpendingBalanceV = - mptAlice.getEncryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getEncryptedBalance(bob, MPTTester::holderEncryptedSpending); // Parameters for the intended ConvertBack transaction uint64_t const amt = 10; @@ -5816,15 +5813,16 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase }); // Submit and verify failure - mptAlice.convertBack( - {.account = bob, - .amt = amt, - .proof = oldProof, - .holderEncryptedAmt = bobCiphertext, - .issuerEncryptedAmt = issuerCiphertext, - .blindingFactor = blindingFactor, - .pedersenCommitment = pedersenCommitment, - .err = tecBAD_PROOF}); // Fails because TransactionContextID differs + mptAlice.convertBack({ + .account = bob, + .amt = amt, + .proof = oldProof, + .holderEncryptedAmt = bobCiphertext, + .issuerEncryptedAmt = issuerCiphertext, + .blindingFactor = blindingFactor, + .pedersenCommitment = pedersenCommitment, + .err = tecBAD_PROOF, // Fails because TransactionContextID differs + }); } /* This test simulates an attack where the holder ciphertext is modified @@ -5863,9 +5861,9 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase // Generate a valid proof for the ORIGINAL amount (10) auto const spendingBal = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); auto const spendingBalEnc = - mptAlice.getEncryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getEncryptedBalance(bob, MPTTester::holderEncryptedSpending); Buffer const pcBf = generateBlindingFactor(); auto const pedersenCommitment = mptAlice.getPedersenCommitment(*spendingBal, pcBf); @@ -5889,15 +5887,16 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase // Holder Ciphertext encrypts 11. Issuer Ciphertext encrypts 10. // The consistency check (re-encryption of `amt` with `bf`) will match Issuer but FAIL for // Holder. - mptAlice.convertBack( - {.account = bob, - .amt = amt, - .proof = proof, - .holderEncryptedAmt = tamperedHolderCipherText, // Tampered (11) - .issuerEncryptedAmt = issuerCipherText, // Original (10) - .blindingFactor = bf, - .pedersenCommitment = pedersenCommitment, - .err = tecBAD_PROOF}); + mptAlice.convertBack({ + .account = bob, + .amt = amt, + .proof = proof, + .holderEncryptedAmt = tamperedHolderCipherText, // Tampered (11) + .issuerEncryptedAmt = issuerCipherText, // Original (10) + .blindingFactor = bf, + .pedersenCommitment = pedersenCommitment, + .err = tecBAD_PROOF, + }); } /* This test verifies that xrpld correctly rejects attempts to @@ -5928,7 +5927,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase // Bob's spending balance is 90 after the baseline send. auto const bobSpendingBefore = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); BEAST_EXPECT(bobSpendingBefore == 90); // Construct Enc(kMaxMpTokenAmount) with Bob's public key. @@ -5957,7 +5956,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase }); auto const bobSpendingAfter = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); BEAST_EXPECT(bobSpendingBefore == bobSpendingAfter); } @@ -5986,7 +5985,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase // Bob's spending balance is now 9; public balance is 1. auto const bobSpendingBefore = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); BEAST_EXPECT(bobSpendingBefore == 9); auto const bobPublicBefore = mptAlice.getBalance(bob); BEAST_EXPECT(bobPublicBefore == 1); @@ -6042,7 +6041,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase // after the rejected attack. BEAST_EXPECT(mptAlice.getBalance(bob) == bobPublicBefore); auto const bobSpendingAfter = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); BEAST_EXPECT(bobSpendingBefore == bobSpendingAfter); } @@ -6459,7 +6458,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase auto& mptAlice = confEnv.mpt; auto const bobSpendingBefore = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); // issuer ciphertext encrypted under carol's holder key // (should be under alice's registered issuer key). @@ -6493,9 +6492,9 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase // all balances unchanged BEAST_EXPECT( - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending) == + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending) == bobSpendingBefore); - BEAST_EXPECT(mptAlice.getDecryptedBalance(carol, MPTTester::HolderEncryptedInbox) == 0); + BEAST_EXPECT(mptAlice.getDecryptedBalance(carol, MPTTester::holderEncryptedInbox) == 0); } // This test verifies that the compact AND-composed Send sigma proof @@ -6581,7 +6580,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase // Verify balances. auto const spendingAfter = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); if (divergent) { BEAST_EXPECT(spendingAfter == setup.prevSpending); @@ -6815,7 +6814,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase // Balance commitment for Bob's actual balance. auto const prevSpending = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); BEAST_EXPECT(prevSpending.has_value()); auto const balanceBlindingFactor = generateBlindingFactor(); auto const balanceCommitment = @@ -6848,7 +6847,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase // Supply invariant: Bob's balance unchanged. auto const postSpending = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); BEAST_EXPECT(postSpending.has_value()); BEAST_EXPECT(*postSpending == *prevSpending); } @@ -6903,7 +6902,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase // Supply invariant: Bob's balance unchanged. auto const postSpending = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); BEAST_EXPECT(postSpending.has_value()); BEAST_EXPECT(*postSpending == setup.prevSpending); } @@ -6933,14 +6932,18 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase setup.recipients, setup.blindingFactor, makeContextHash(env, mptAlice, alice, bob, carol, setup.version), - {.pedersenCommitment = setup.amountCommitment, - .amt = setup.sendAmount, - .encryptedAmt = setup.senderAmt, - .blindingFactor = setup.amountBlindingFactor}, - {.pedersenCommitment = setup.balanceCommitment, - .amt = setup.prevSpending, - .encryptedAmt = setup.prevEncryptedSpending, - .blindingFactor = setup.balanceBlindingFactor}); + { + .pedersenCommitment = setup.amountCommitment, + .amt = setup.sendAmount, + .encryptedAmt = setup.senderAmt, + .blindingFactor = setup.amountBlindingFactor, + }, + { + .pedersenCommitment = setup.balanceCommitment, + .amt = setup.prevSpending, + .encryptedAmt = setup.prevEncryptedSpending, + .blindingFactor = setup.balanceBlindingFactor, + }); if (!BEAST_EXPECT(proof.has_value())) return; @@ -7256,10 +7259,10 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase // Generate a valid convertBack proof for bob auto const spendingBalance = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); BEAST_EXPECT(spendingBalance.has_value()); auto const encryptedSpending = - mptAlice.getEncryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getEncryptedBalance(bob, MPTTester::holderEncryptedSpending); BEAST_EXPECT(encryptedSpending.has_value()); Buffer const pcBlindingFactor = generateBlindingFactor(); @@ -7274,10 +7277,12 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase bob, sendAmount, convertBackCtxHash, - {.pedersenCommitment = pedersenCommitment, - .amt = *spendingBalance, - .encryptedAmt = *encryptedSpending, - .blindingFactor = pcBlindingFactor}); + { + .pedersenCommitment = pedersenCommitment, + .amt = *spendingBalance, + .encryptedAmt = *encryptedSpending, + .blindingFactor = pcBlindingFactor, + }); // Resize the convertBack proof to match the expected send proof // size so it passes preflight's size check and reaches the actual @@ -7315,14 +7320,18 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase setup.recipients, setup.blindingFactor, wrongCtxHash, - {.pedersenCommitment = setup.amountCommitment, - .amt = sendAmount, - .encryptedAmt = setup.senderAmt, - .blindingFactor = setup.amountBlindingFactor}, - {.pedersenCommitment = setup.balanceCommitment, - .amt = setup.prevSpending, - .encryptedAmt = setup.prevEncryptedSpending, - .blindingFactor = setup.balanceBlindingFactor}); + { + .pedersenCommitment = setup.amountCommitment, + .amt = sendAmount, + .encryptedAmt = setup.senderAmt, + .blindingFactor = setup.amountBlindingFactor, + }, + { + .pedersenCommitment = setup.balanceCommitment, + .amt = setup.prevSpending, + .encryptedAmt = setup.prevEncryptedSpending, + .blindingFactor = setup.balanceBlindingFactor, + }); if (!BEAST_EXPECT(wrongProof.has_value())) return; @@ -7403,14 +7412,18 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase setup.recipients, setup.blindingFactor, ctxHash, - {.pedersenCommitment = setup.amountCommitment, - .amt = sendAmount, - .encryptedAmt = setup.senderAmt, - .blindingFactor = setup.amountBlindingFactor}, - {.pedersenCommitment = setup.balanceCommitment, - .amt = setup.prevSpending, - .encryptedAmt = setup.prevEncryptedSpending, - .blindingFactor = setup.balanceBlindingFactor}); + { + .pedersenCommitment = setup.amountCommitment, + .amt = sendAmount, + .encryptedAmt = setup.senderAmt, + .blindingFactor = setup.amountBlindingFactor, + }, + { + .pedersenCommitment = setup.balanceCommitment, + .amt = setup.prevSpending, + .encryptedAmt = setup.prevEncryptedSpending, + .blindingFactor = setup.balanceBlindingFactor, + }); if (!BEAST_EXPECT(validProof.has_value())) return; @@ -7952,7 +7965,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase mptAlice.mergeInbox({.account = bob}); auto const bobSpending = - mptAlice.getDecryptedBalance(bob, MPTTester::HolderEncryptedSpending); + mptAlice.getDecryptedBalance(bob, MPTTester::holderEncryptedSpending); BEAST_EXPECT(bobSpending && *bobSpending == 25); } diff --git a/src/test/app/Delegate_test.cpp b/src/test/app/Delegate_test.cpp index 46936e4948..ad7ebc7248 100644 --- a/src/test/app/Delegate_test.cpp +++ b/src/test/app/Delegate_test.cpp @@ -2182,6 +2182,8 @@ class Delegate_test : public beast::unit_test::Suite // DO NOT modify expectedDelegableCount unless all scenarios, including // edge cases, have been fully tested and verified. // ==================================================================== + // Includes the five confidential MPT transaction types, which are + // explicitly marked Delegable in transactions.macro. std::size_t const expectedDelegableCount = 56; BEAST_EXPECTS( diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 23214a5f81..18594502a0 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -5058,10 +5058,11 @@ class Invariants_test : public beast::unit_test::Suite {"MPToken sfConfidentialBalanceVersion not updated when sfConfidentialBalanceSpending " "changed"}, [&mptID](Account const& a1, Account const& a2, ApplyContext& ac) { + Blob const kChangedConfidentialSpending = {0xBA, 0xDD}; auto sleToken = ac.view().peek(keylet::mptoken(mptID, a2.id())); if (!sleToken) return false; - sleToken->setFieldVL(sfConfidentialBalanceSpending, Blob{0xBA, 0xDD}); + sleToken->setFieldVL(sfConfidentialBalanceSpending, kChangedConfidentialSpending); // DO NOT update sfConfidentialBalanceVersion ac.view().update(sleToken); diff --git a/src/test/jtx/ConfidentialTransfer.h b/src/test/jtx/ConfidentialTransfer.h index 26f9a171fd..37487bdd25 100644 --- a/src/test/jtx/ConfidentialTransfer.h +++ b/src/test/jtx/ConfidentialTransfer.h @@ -55,10 +55,27 @@ namespace xrpl { -// NOLINTBEGIN(misc-const-correctness, bugprone-unchecked-optional-access) class ConfidentialTransferTestBase : public beast::unit_test::Suite { protected: + template + static T + requireOptional(std::optional value, char const* message) + { + if (!value) + Throw(message); + return std::move(*value); + } + + template + static T const& + requireOptionalRef(std::optional const& value, char const* message) + { + if (!value) + Throw(message); + return *value; + } + // Offset where the bulletproof begins in a send proof blob. // Proof layout: [compact_sigma | bulletproof] static constexpr size_t kBulletproofOffset = kEcSendProofLength - kEcDoubleBulletproofLength; @@ -233,23 +250,38 @@ protected: mpt.encryptAmount(auditor->get(), amount, blindingFactor)) : std::nullopt) , amountCommitment(mpt.getPedersenCommitment(amount, amountBlindingFactor)) - , senderPubKey(*mpt.getPubKey(sender)) - , destPubKey(*mpt.getPubKey(dest)) - , issuerPubKey(*mpt.getPubKey(issuer)) + , senderPubKey(requireOptional(mpt.getPubKey(sender), "Missing sender public key")) + , destPubKey(requireOptional(mpt.getPubKey(dest), "Missing destination public key")) + , issuerPubKey(requireOptional(mpt.getPubKey(issuer), "Missing issuer public key")) , auditorPubKey(auditor ? mpt.getPubKey(auditor->get()) : std::nullopt) - , prevSpending( - *mpt.getDecryptedBalance(sender, test::jtx::MPTTester::HolderEncryptedSpending)) - , prevEncryptedSpending( - *mpt.getEncryptedBalance(sender, test::jtx::MPTTester::HolderEncryptedSpending)) + , prevSpending(requireOptional( + mpt.getDecryptedBalance(sender, test::jtx::MPTTester::holderEncryptedSpending), + "Missing sender spending balance")) + , prevEncryptedSpending(requireOptional( + mpt.getEncryptedBalance(sender, test::jtx::MPTTester::holderEncryptedSpending), + "Missing sender encrypted spending balance")) , balanceCommitment(mpt.getPedersenCommitment(prevSpending, balanceBlindingFactor)) { - recipients.push_back({.publicKey = Slice(senderPubKey), .encryptedAmount = senderAmt}); - recipients.push_back({.publicKey = Slice(destPubKey), .encryptedAmount = destAmt}); - recipients.push_back({.publicKey = Slice(issuerPubKey), .encryptedAmount = issuerAmt}); + recipients.push_back({ + .publicKey = Slice(senderPubKey), + .encryptedAmount = senderAmt, + }); + recipients.push_back({ + .publicKey = Slice(destPubKey), + .encryptedAmount = destAmt, + }); + recipients.push_back({ + .publicKey = Slice(issuerPubKey), + .encryptedAmount = issuerAmt, + }); if (auditor) { - recipients.push_back( - {.publicKey = Slice(*auditorPubKey), .encryptedAmount = *auditorAmt}); + recipients.push_back({ + .publicKey = + Slice(requireOptionalRef(auditorPubKey, "Missing auditor public key")), + .encryptedAmount = + requireOptionalRef(auditorAmt, "Missing auditor encrypted amount"), + }); } } @@ -270,14 +302,18 @@ protected: recipients, blindingFactor, ctxHash, - {.pedersenCommitment = amountCommitment, - .amt = sendAmount, - .encryptedAmt = senderAmt, - .blindingFactor = amountBlindingFactor}, - {.pedersenCommitment = balanceCommitment, - .amt = prevSpending, - .encryptedAmt = prevEncryptedSpending, - .blindingFactor = balanceBlindingFactor}); + { + .pedersenCommitment = amountCommitment, + .amt = sendAmount, + .encryptedAmt = senderAmt, + .blindingFactor = amountBlindingFactor, + }, + { + .pedersenCommitment = balanceCommitment, + .amt = prevSpending, + .encryptedAmt = prevEncryptedSpending, + .blindingFactor = balanceBlindingFactor, + }); } [[nodiscard]] test::jtx::MPTConfidentialSend @@ -298,7 +334,8 @@ protected: .auditorEncryptedAmt = auditorAmt, .amountCommitment = amountCommitment, .balanceCommitment = balanceCommitment, - .err = err}; + .err = err, + }; } }; @@ -340,19 +377,23 @@ protected: for (auto const& h : holders) mpt.generateKeyPair(h.account); if (auditor) - mpt.generateKeyPair(*auditor); + mpt.generateKeyPair(requireOptionalRef(auditor, "Missing auditor")); - mpt.set( - {.account = issuer, - .issuerPubKey = mpt.getPubKey(issuer), - .auditorPubKey = auditor ? mpt.getPubKey(*auditor) : std::optional{}}); + mpt.set({ + .account = issuer, + .issuerPubKey = mpt.getPubKey(issuer), + .auditorPubKey = auditor + ? mpt.getPubKey(requireOptionalRef(auditor, "Missing auditor")) + : std::optional{}, + }); for (auto const& h : holders) { - mpt.convert( - {.account = h.account, - .amt = h.convertAmount, - .holderPubKey = mpt.getPubKey(h.account)}); + mpt.convert({ + .account = h.account, + .amt = h.convertAmount, + .holderPubKey = mpt.getPubKey(h.account), + }); mpt.mergeInbox({.account = h.account}); } } @@ -402,32 +443,54 @@ protected: mpt.generateKeyPair(carol); mpt.generateKeyPair(dave); - mpt.set({.account = alice, .issuerPubKey = mpt.getPubKey(alice)}); + mpt.set({ + .account = alice, + .issuerPubKey = mpt.getPubKey(alice), + }); if (bobAmt > 0) { - mpt.convert({.account = bob, .amt = bobAmt, .holderPubKey = mpt.getPubKey(bob)}); + mpt.convert({ + .account = bob, + .amt = bobAmt, + .holderPubKey = mpt.getPubKey(bob), + }); mpt.mergeInbox({.account = bob}); } else { - mpt.convert({.account = bob, .amt = 0, .holderPubKey = mpt.getPubKey(bob)}); + mpt.convert({ + .account = bob, + .amt = 0, + .holderPubKey = mpt.getPubKey(bob), + }); } if (carolAmt > 0) { - mpt.convert({.account = carol, .amt = carolAmt, .holderPubKey = mpt.getPubKey(carol)}); + mpt.convert({ + .account = carol, + .amt = carolAmt, + .holderPubKey = mpt.getPubKey(carol), + }); mpt.mergeInbox({.account = carol}); } else { - mpt.convert({.account = carol, .amt = 0, .holderPubKey = mpt.getPubKey(carol)}); + mpt.convert({ + .account = carol, + .amt = 0, + .holderPubKey = mpt.getPubKey(carol), + }); } // dave: register pubkey only (0 spending/inbox) - mpt.convert({.account = dave, .amt = 0, .holderPubKey = mpt.getPubKey(dave)}); + mpt.convert({ + .account = dave, + .amt = 0, + .holderPubKey = mpt.getPubKey(dave), + }); } }; -// NOLINTEND(misc-const-correctness, bugprone-unchecked-optional-access) } // namespace xrpl diff --git a/src/test/jtx/batch.h b/src/test/jtx/batch.h index c8547f2925..0e78d409fa 100644 --- a/src/test/jtx/batch.h +++ b/src/test/jtx/batch.h @@ -14,22 +14,46 @@ #include #include -/** Batch operations */ +/** @brief Helpers for constructing Batch test transactions. */ namespace xrpl::test::jtx::batch { -/** Calculate Batch Fee. */ +/** + * @brief Calculate the expected outer Batch transaction fee. + * + * @param env The test environment providing ledger fee settings. + * @param numSigners Number of outer transaction signers. + * @param txns Number of inner transactions in the batch. + * @return The expected Batch fee. + */ XRPAmount calcBatchFee(jtx::Env const& env, uint32_t const& numSigners, uint32_t const& txns = 0); -/** Calculate Batch Fee with Confidential MPT inner transactions. */ +/** + * @brief Calculate the expected Batch fee when inner transactions are + * confidential MPT transactions. + * + * @param env The test environment providing ledger fee settings. + * @param numSigners Number of outer transaction signers. + * @param txns Number of confidential MPT inner transactions in the batch. + * @return The expected Batch fee including confidential transaction fee + * multipliers. + */ XRPAmount calcConfidentialBatchFee(jtx::Env const& env, uint32_t const& numSigners, uint32_t const& txns = 0); -/** Batch. */ +/** + * @brief Build an outer Batch transaction JSON object. + * + * @param account The account submitting the outer Batch transaction. + * @param seq The sequence number for the outer Batch transaction. + * @param fee The fee to set on the outer Batch transaction. + * @param flags The transaction flags to set. + * @return The outer Batch transaction JSON object. + */ json::Value outer(jtx::Account const& account, uint32_t seq, STAmount const& fee, std::uint32_t flags); -/** Adds a new Batch Txn on a JTx and autofills. */ +/** @brief Adds an inner Batch transaction to a JTx and autofills it. */ class Inner { private: @@ -79,7 +103,7 @@ public: } }; -/** Set a batch signature on a JTx. */ +/** @brief Sets the Batch transaction signers on a JTx. */ class Sig { public: @@ -102,7 +126,7 @@ public: operator()(Env&, JTx& jt) const; }; -/** Set a batch nested multi-signature on a JTx. */ +/** @brief Sets a nested multi-signature for a Batch transaction on a JTx. */ class Msig { public: diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 2dc303dbcd..37272f7e55 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -42,7 +42,6 @@ #include #include #include -#include #include #include #include @@ -52,8 +51,7 @@ #include namespace xrpl::test::jtx { - -// NOLINTBEGIN(bugprone-unchecked-optional-access) +namespace { /** * @brief Helper function to convert a PedersenProofParams into the C library struct. @@ -61,7 +59,7 @@ namespace xrpl::test::jtx { * @param params The Pedersen commitment proof parameters. * @return The equivalent mpt_pedersen_proof_params for use with the C library. */ -static mpt_pedersen_proof_params +mpt_pedersen_proof_params makePedersenParams(PedersenProofParams const& params) { mpt_pedersen_proof_params res{}; @@ -73,6 +71,10 @@ makePedersenParams(PedersenProofParams const& params) return res; } +} // namespace + +// NOLINTBEGIN(bugprone-unchecked-optional-access) + void MptFlags::operator()(Env& env) const { @@ -619,18 +621,6 @@ MPTTester::checkDomainID(std::optional expected) const }); } -// todo: remove this function, which is only for debugging -[[nodiscard]] bool -MPTTester::printMPT(Account const& holder) const -{ - return forObject( - [&](SLEP const& sle) -> bool { - std::cout << "\n" << sle->getJson(); - return true; - }, - holder); -} - [[nodiscard]] bool MPTTester::checkMPTokenAmount(Account const& holder, std::int64_t expectedAmount) const { @@ -999,24 +989,24 @@ MPTTester::getEncryptedBalance(Account const& account, EncryptedBalanceType opti if (auto const sle = env_.le(keylet::mptoken(*id_, account.id()))) { - if (option == HolderEncryptedInbox && sle->isFieldPresent(sfConfidentialBalanceInbox)) + if (option == holderEncryptedInbox && sle->isFieldPresent(sfConfidentialBalanceInbox)) { return Buffer( (*sle)[sfConfidentialBalanceInbox].data(), (*sle)[sfConfidentialBalanceInbox].size()); } - if (option == HolderEncryptedSpending && sle->isFieldPresent(sfConfidentialBalanceSpending)) + if (option == holderEncryptedSpending && sle->isFieldPresent(sfConfidentialBalanceSpending)) { return Buffer( (*sle)[sfConfidentialBalanceSpending].data(), (*sle)[sfConfidentialBalanceSpending].size()); } - if (option == IssuerEncryptedBalance && sle->isFieldPresent(sfIssuerEncryptedBalance)) + if (option == issuerEncryptedBalance && sle->isFieldPresent(sfIssuerEncryptedBalance)) { return Buffer( (*sle)[sfIssuerEncryptedBalance].data(), (*sle)[sfIssuerEncryptedBalance].size()); } - if (option == AuditorEncryptedBalance && sle->isFieldPresent(sfAuditorEncryptedBalance)) + if (option == auditorEncryptedBalance && sle->isFieldPresent(sfAuditorEncryptedBalance)) { return Buffer( (*sle)[sfAuditorEncryptedBalance].data(), (*sle)[sfAuditorEncryptedBalance].size()); @@ -1169,9 +1159,9 @@ MPTTester::convert(MPTConvert const& arg) auto const holderAmt = getBalance(*arg.account); auto const prevConfidentialOutstanding = getIssuanceConfidentialBalance(); - auto const prevInboxBalance = getDecryptedBalance(*arg.account, HolderEncryptedInbox); - auto const prevSpendingBalance = getDecryptedBalance(*arg.account, HolderEncryptedSpending); - auto const prevIssuerBalance = getDecryptedBalance(*arg.account, IssuerEncryptedBalance); + auto const prevInboxBalance = getDecryptedBalance(*arg.account, holderEncryptedInbox); + auto const prevSpendingBalance = getDecryptedBalance(*arg.account, holderEncryptedSpending); + auto const prevIssuerBalance = getDecryptedBalance(*arg.account, issuerEncryptedBalance); if (!prevInboxBalance || !prevSpendingBalance || !prevIssuerBalance) Throw("Failed to get Pre-convert balance"); @@ -1179,7 +1169,7 @@ MPTTester::convert(MPTConvert const& arg) std::optional prevAuditorBalance; if (arg.auditorEncryptedAmt || auditor_) { - prevAuditorBalance = getDecryptedBalance(*arg.account, AuditorEncryptedBalance); + prevAuditorBalance = getDecryptedBalance(*arg.account, auditorEncryptedBalance); if (!prevAuditorBalance) Throw("Failed to get Pre-convert balance"); } @@ -1199,18 +1189,18 @@ MPTTester::convert(MPTConvert const& arg) })); env_.require(RequireAny([&]() -> bool { - return getEncryptedBalance(*arg.account, HolderEncryptedInbox).has_value(); + return getEncryptedBalance(*arg.account, holderEncryptedInbox).has_value(); })); env_.require(RequireAny([&]() -> bool { - return getEncryptedBalance(*arg.account, HolderEncryptedSpending).has_value(); + return getEncryptedBalance(*arg.account, holderEncryptedSpending).has_value(); })); env_.require(RequireAny([&]() -> bool { - return getEncryptedBalance(*arg.account, IssuerEncryptedBalance).has_value(); + return getEncryptedBalance(*arg.account, issuerEncryptedBalance).has_value(); })); - auto const postInboxBalance = getDecryptedBalance(*arg.account, HolderEncryptedInbox); - auto const postIssuerBalance = getDecryptedBalance(*arg.account, IssuerEncryptedBalance); - auto const postSpendingBalance = getDecryptedBalance(*arg.account, HolderEncryptedSpending); + auto const postInboxBalance = getDecryptedBalance(*arg.account, holderEncryptedInbox); + auto const postIssuerBalance = getDecryptedBalance(*arg.account, issuerEncryptedBalance); + auto const postSpendingBalance = getDecryptedBalance(*arg.account, holderEncryptedSpending); if (!postInboxBalance || !postIssuerBalance || !postSpendingBalance) Throw("Failed to get post-convert balance"); @@ -1218,13 +1208,13 @@ MPTTester::convert(MPTConvert const& arg) if (arg.auditorEncryptedAmt || auditor_) { auto const postAuditorBalance = - getDecryptedBalance(*arg.account, AuditorEncryptedBalance); + getDecryptedBalance(*arg.account, auditorEncryptedBalance); if (!postAuditorBalance) Throw("Failed to get post-convert auditor balance"); env_.require(RequireAny([&]() -> bool { - return getEncryptedBalance(*arg.account, AuditorEncryptedBalance).has_value(); + return getEncryptedBalance(*arg.account, auditorEncryptedBalance).has_value(); })); // auditor's encrypted balance is updated correctly @@ -1414,31 +1404,31 @@ MPTTester::send(MPTConfidentialSend const& arg) auto const prevDestVersion = getMPTokenVersion(*arg.dest); // Sender's previous confidential state - auto const prevSenderInbox = getDecryptedBalance(*arg.account, HolderEncryptedInbox); - auto const prevSenderSpending = getDecryptedBalance(*arg.account, HolderEncryptedSpending); - auto const prevSenderIssuer = getDecryptedBalance(*arg.account, IssuerEncryptedBalance); + auto const prevSenderInbox = getDecryptedBalance(*arg.account, holderEncryptedInbox); + auto const prevSenderSpending = getDecryptedBalance(*arg.account, holderEncryptedSpending); + auto const prevSenderIssuer = getDecryptedBalance(*arg.account, issuerEncryptedBalance); if (!prevSenderInbox || !prevSenderSpending || !prevSenderIssuer) Throw("Failed to get Pre-send balance"); std::optional prevSenderAuditor; if (arg.auditorEncryptedAmt || auditor_) { - prevSenderAuditor = getDecryptedBalance(*arg.account, AuditorEncryptedBalance); + prevSenderAuditor = getDecryptedBalance(*arg.account, auditorEncryptedBalance); if (!prevSenderAuditor) Throw("Failed to get Pre-send balance"); } // Destination's previous confidential state - auto const prevDestInbox = getDecryptedBalance(*arg.dest, HolderEncryptedInbox); - auto const prevDestSpending = getDecryptedBalance(*arg.dest, HolderEncryptedSpending); - auto const prevDestIssuer = getDecryptedBalance(*arg.dest, IssuerEncryptedBalance); + auto const prevDestInbox = getDecryptedBalance(*arg.dest, holderEncryptedInbox); + auto const prevDestSpending = getDecryptedBalance(*arg.dest, holderEncryptedSpending); + auto const prevDestIssuer = getDecryptedBalance(*arg.dest, issuerEncryptedBalance); if (!prevDestInbox || !prevDestSpending || !prevDestIssuer) Throw("Failed to get Pre-send balance"); std::optional prevDestAuditor; if (arg.auditorEncryptedAmt || auditor_) { - prevDestAuditor = getDecryptedBalance(*arg.dest, AuditorEncryptedBalance); + prevDestAuditor = getDecryptedBalance(*arg.dest, auditorEncryptedBalance); if (!prevDestAuditor) Throw("Failed to get Pre-send balance"); } @@ -1492,11 +1482,26 @@ MPTTester::send(MPTConfidentialSend const& arg) // If a key is missing, we skip adding the recipient. This intentionally // causes proof generation to fail, triggering the dummy proof fallback. if (senderPubKey) - recipients.push_back({.publicKey = Slice(*senderPubKey), .encryptedAmount = senderAmt}); + { + recipients.push_back({ + .publicKey = Slice(*senderPubKey), + .encryptedAmount = senderAmt, + }); + } if (destPubKey) - recipients.push_back({.publicKey = Slice(*destPubKey), .encryptedAmount = destAmt}); + { + recipients.push_back({ + .publicKey = Slice(*destPubKey), + .encryptedAmount = destAmt, + }); + } if (issuerPubKey) - recipients.push_back({.publicKey = Slice(*issuerPubKey), .encryptedAmount = issuerAmt}); + { + recipients.push_back({ + .publicKey = Slice(*issuerPubKey), + .encryptedAmount = issuerAmt, + }); + } std::optional auditorPubKey; if (auditorAmt) @@ -1507,13 +1512,15 @@ MPTTester::send(MPTConfidentialSend const& arg) auditorPubKey = getPubKey(*auditor_); if (auditorPubKey) { - recipients.push_back( - {.publicKey = Slice(*auditorPubKey), .encryptedAmount = *auditorAmt}); + recipients.push_back({ + .publicKey = Slice(*auditorPubKey), + .encryptedAmount = *auditorAmt, + }); } } auto const prevEncryptedSenderSpending = - getEncryptedBalance(*arg.account, HolderEncryptedSpending); + getEncryptedBalance(*arg.account, holderEncryptedSpending); std::optional proof; @@ -1529,14 +1536,18 @@ MPTTester::send(MPTConfidentialSend const& arg) recipients, blindingFactor, ctxHash, - {.pedersenCommitment = amountCommitment, - .amt = *arg.amt, - .encryptedAmt = senderAmt, - .blindingFactor = blindingFactor}, - {.pedersenCommitment = balanceCommitment, - .amt = *prevSenderSpending, - .encryptedAmt = *prevEncryptedSenderSpending, - .blindingFactor = balanceBlindingFactor}); + { + .pedersenCommitment = amountCommitment, + .amt = *arg.amt, + .encryptedAmt = senderAmt, + .blindingFactor = blindingFactor, + }, + { + .pedersenCommitment = balanceCommitment, + .amt = *prevSenderSpending, + .encryptedAmt = *prevEncryptedSenderSpending, + .blindingFactor = balanceBlindingFactor, + }); } if (proof) @@ -1560,17 +1571,17 @@ MPTTester::send(MPTConfidentialSend const& arg) auto const postOA = getIssuanceOutstandingBalance(); // Sender's post confidential state - auto const postSenderInbox = getDecryptedBalance(*arg.account, HolderEncryptedInbox); - auto const postSenderSpending = getDecryptedBalance(*arg.account, HolderEncryptedSpending); - auto const postSenderIssuer = getDecryptedBalance(*arg.account, IssuerEncryptedBalance); + auto const postSenderInbox = getDecryptedBalance(*arg.account, holderEncryptedInbox); + auto const postSenderSpending = getDecryptedBalance(*arg.account, holderEncryptedSpending); + auto const postSenderIssuer = getDecryptedBalance(*arg.account, issuerEncryptedBalance); if (!postSenderInbox || !postSenderSpending || !postSenderIssuer) Throw("Failed to get Post-send balance"); // Destination's post confidential state - auto const postDestInbox = getDecryptedBalance(*arg.dest, HolderEncryptedInbox); - auto const postDestSpending = getDecryptedBalance(*arg.dest, HolderEncryptedSpending); - auto const postDestIssuer = getDecryptedBalance(*arg.dest, IssuerEncryptedBalance); + auto const postDestInbox = getDecryptedBalance(*arg.dest, holderEncryptedInbox); + auto const postDestSpending = getDecryptedBalance(*arg.dest, holderEncryptedSpending); + auto const postDestIssuer = getDecryptedBalance(*arg.dest, issuerEncryptedBalance); if (!postDestInbox || !postDestSpending || !postDestIssuer) Throw("Failed to get Post-send balance"); @@ -1616,8 +1627,8 @@ MPTTester::send(MPTConfidentialSend const& arg) if (arg.auditorEncryptedAmt || auditor_) { auto const postSenderAuditor = - getDecryptedBalance(*arg.account, AuditorEncryptedBalance); - auto const postDestAuditor = getDecryptedBalance(*arg.dest, AuditorEncryptedBalance); + getDecryptedBalance(*arg.account, auditorEncryptedBalance); + auto const postDestAuditor = getDecryptedBalance(*arg.dest, auditorEncryptedBalance); if (!postSenderAuditor || !postDestAuditor) Throw("Failed to get Post-send balance"); @@ -1726,11 +1737,11 @@ MPTTester::sendJV( } else { - auto const ledgerSpending = getDecryptedBalance(*arg.account, HolderEncryptedSpending); + auto const ledgerSpending = getDecryptedBalance(*arg.account, holderEncryptedSpending); if (!ledgerSpending) Throw("Failed to get sender spending balance"); prevSenderSpending = *ledgerSpending; - prevEncryptedSenderSpending = getEncryptedBalance(*arg.account, HolderEncryptedSpending); + prevEncryptedSenderSpending = getEncryptedBalance(*arg.account, holderEncryptedSpending); version = getMPTokenVersion(*arg.account); } @@ -1776,11 +1787,26 @@ MPTTester::sendJV( auto const issuerPubKey = getPubKey(issuer_); if (senderPubKey) - recipients.push_back({.publicKey = Slice(*senderPubKey), .encryptedAmount = senderAmt}); + { + recipients.push_back({ + .publicKey = Slice(*senderPubKey), + .encryptedAmount = senderAmt, + }); + } if (destPubKey) - recipients.push_back({.publicKey = Slice(*destPubKey), .encryptedAmount = destAmt}); + { + recipients.push_back({ + .publicKey = Slice(*destPubKey), + .encryptedAmount = destAmt, + }); + } if (issuerPubKey) - recipients.push_back({.publicKey = Slice(*issuerPubKey), .encryptedAmount = issuerAmt}); + { + recipients.push_back({ + .publicKey = Slice(*issuerPubKey), + .encryptedAmount = issuerAmt, + }); + } std::optional auditorPubKey; if (auditorAmt) @@ -1790,8 +1816,10 @@ MPTTester::sendJV( auditorPubKey = getPubKey(*auditor_); if (auditorPubKey) { - recipients.push_back( - {.publicKey = Slice(*auditorPubKey), .encryptedAmount = *auditorAmt}); + recipients.push_back({ + .publicKey = Slice(*auditorPubKey), + .encryptedAmount = *auditorAmt, + }); } } @@ -1806,14 +1834,18 @@ MPTTester::sendJV( recipients, blindingFactor, ctxHash, - {.pedersenCommitment = amountCommitment, - .amt = *arg.amt, - .encryptedAmt = senderAmt, - .blindingFactor = blindingFactor}, - {.pedersenCommitment = balanceCommitment, - .amt = prevSenderSpending, - .encryptedAmt = *prevEncryptedSenderSpending, - .blindingFactor = balanceBlindingFactor}); + { + .pedersenCommitment = amountCommitment, + .amt = *arg.amt, + .encryptedAmt = senderAmt, + .blindingFactor = blindingFactor, + }, + { + .pedersenCommitment = balanceCommitment, + .amt = prevSenderSpending, + .encryptedAmt = *prevEncryptedSenderSpending, + .blindingFactor = balanceBlindingFactor, + }); } if (proof) @@ -1842,8 +1874,8 @@ parseSenderEncAmt(json::Value const& jv) ConfidentialSendChainState MPTTester::chainAfterSend(Account const& sender, std::uint64_t sendAmt, json::Value const& jv) const { - auto const prevSpending = getDecryptedBalance(sender, HolderEncryptedSpending); - auto const prevEncSpending = getEncryptedBalance(sender, HolderEncryptedSpending); + auto const prevSpending = getDecryptedBalance(sender, holderEncryptedSpending); + auto const prevEncSpending = getEncryptedBalance(sender, holderEncryptedSpending); auto const prevVersion = getMPTokenVersion(sender); if (!prevSpending || !prevEncSpending) @@ -1960,15 +1992,15 @@ MPTTester::confidentialClaw(MPTConfidentialClawback const& arg) // Verify holder's confidential balances are zeroed out env_.require(RequireAny( - [&]() -> bool { return getDecryptedBalance(*arg.holder, HolderEncryptedInbox) == 0; })); + [&]() -> bool { return getDecryptedBalance(*arg.holder, holderEncryptedInbox) == 0; })); env_.require(RequireAny([&]() -> bool { - return getDecryptedBalance(*arg.holder, HolderEncryptedSpending) == 0; + return getDecryptedBalance(*arg.holder, holderEncryptedSpending) == 0; })); env_.require(RequireAny([&]() -> bool { - return getDecryptedBalance(*arg.holder, IssuerEncryptedBalance) == 0; + return getDecryptedBalance(*arg.holder, issuerEncryptedBalance) == 0; })); env_.require(RequireAny([&]() -> bool { - return getDecryptedBalance(*arg.holder, AuditorEncryptedBalance) == 0; + return getDecryptedBalance(*arg.holder, auditorEncryptedBalance) == 0; })); // Verify version is incremented @@ -2067,11 +2099,11 @@ MPTTester::getDecryptedBalance(Account const& account, EncryptedBalanceType bala Account decryptor = account; - if (balanceType == IssuerEncryptedBalance) + if (balanceType == issuerEncryptedBalance) { decryptor = issuer_; } - else if (balanceType == AuditorEncryptedBalance) + else if (balanceType == auditorEncryptedBalance) { if (!auditor_) return std::nullopt; @@ -2134,11 +2166,11 @@ MPTTester::mergeInbox(MPTMergeInbox const& arg) auto const holderPubAmt = getBalance(*arg.account); auto const prevCOA = getIssuanceConfidentialBalance(); auto const prevOA = getIssuanceOutstandingBalance(); - auto const prevInboxBalance = getDecryptedBalance(*arg.account, HolderEncryptedInbox); - auto const prevSpendingBalance = getDecryptedBalance(*arg.account, HolderEncryptedSpending); - auto const prevIssuerBalance = getDecryptedBalance(*arg.account, IssuerEncryptedBalance); - auto const prevIssuerEncrypted = getEncryptedBalance(*arg.account, IssuerEncryptedBalance); - auto const prevAuditorEncrypted = getEncryptedBalance(*arg.account, AuditorEncryptedBalance); + auto const prevInboxBalance = getDecryptedBalance(*arg.account, holderEncryptedInbox); + auto const prevSpendingBalance = getDecryptedBalance(*arg.account, holderEncryptedSpending); + auto const prevIssuerBalance = getDecryptedBalance(*arg.account, issuerEncryptedBalance); + auto const prevIssuerEncrypted = getEncryptedBalance(*arg.account, issuerEncryptedBalance); + auto const prevAuditorEncrypted = getEncryptedBalance(*arg.account, auditorEncryptedBalance); auto const prevVersion = getMPTokenVersion(*arg.account); if (!prevInboxBalance || !prevSpendingBalance || !prevIssuerBalance) @@ -2148,13 +2180,13 @@ MPTTester::mergeInbox(MPTMergeInbox const& arg) { auto const postCOA = getIssuanceConfidentialBalance(); auto const postOA = getIssuanceOutstandingBalance(); - auto const postInboxBalance = getDecryptedBalance(*arg.account, HolderEncryptedInbox); - auto const postSpendingBalance = getDecryptedBalance(*arg.account, HolderEncryptedSpending); - auto const postIssuerBalance = getDecryptedBalance(*arg.account, IssuerEncryptedBalance); - auto const postInboxEncrypted = getEncryptedBalance(*arg.account, HolderEncryptedInbox); - auto const postIssuerEncrypted = getEncryptedBalance(*arg.account, IssuerEncryptedBalance); + auto const postInboxBalance = getDecryptedBalance(*arg.account, holderEncryptedInbox); + auto const postSpendingBalance = getDecryptedBalance(*arg.account, holderEncryptedSpending); + auto const postIssuerBalance = getDecryptedBalance(*arg.account, issuerEncryptedBalance); + auto const postInboxEncrypted = getEncryptedBalance(*arg.account, holderEncryptedInbox); + auto const postIssuerEncrypted = getEncryptedBalance(*arg.account, issuerEncryptedBalance); auto const postAuditorEncrypted = - getEncryptedBalance(*arg.account, AuditorEncryptedBalance); + getEncryptedBalance(*arg.account, auditorEncryptedBalance); auto const postVersion = getMPTokenVersion(*arg.account); if (!postInboxBalance || !postSpendingBalance || !postIssuerBalance || @@ -2265,9 +2297,9 @@ MPTTester::convertBack(MPTConvertBack const& arg) jv[sfBlindingFactor] = strHex(blindingFactor); - auto const prevInboxBalance = getDecryptedBalance(*arg.account, HolderEncryptedInbox); - auto const prevSpendingBalance = getDecryptedBalance(*arg.account, HolderEncryptedSpending); - auto const prevIssuerBalance = getDecryptedBalance(*arg.account, IssuerEncryptedBalance); + auto const prevInboxBalance = getDecryptedBalance(*arg.account, holderEncryptedInbox); + auto const prevSpendingBalance = getDecryptedBalance(*arg.account, holderEncryptedSpending); + auto const prevIssuerBalance = getDecryptedBalance(*arg.account, issuerEncryptedBalance); if (!prevInboxBalance || !prevSpendingBalance || !prevIssuerBalance) Throw("Failed to get Pre-convertBack balance"); @@ -2298,7 +2330,7 @@ MPTTester::convertBack(MPTConvertBack const& arg) auto const seq = arg.ticketSeq.value_or(env_.seq(*arg.account)); auto const contextHash = getConvertBackContextHash(arg.account->id(), *id_, seq, version); auto const prevEncryptedSpendingBalance = - getEncryptedBalance(*arg.account, HolderEncryptedSpending); + getEncryptedBalance(*arg.account, holderEncryptedSpending); Buffer proof; // generate a dummy proof if no encrypted amount field, so that other @@ -2329,7 +2361,7 @@ MPTTester::convertBack(MPTConvertBack const& arg) std::optional prevAuditorBalance; if (arg.auditorEncryptedAmt || auditor_) { - prevAuditorBalance = getDecryptedBalance(*arg.account, AuditorEncryptedBalance); + prevAuditorBalance = getDecryptedBalance(*arg.account, auditorEncryptedBalance); if (!prevAuditorBalance) Throw("Failed to get Pre-convertBack balance"); } @@ -2350,9 +2382,9 @@ MPTTester::convertBack(MPTConvertBack const& arg) return prevConfidentialOutstanding - *arg.amt == postConfidentialOutstanding; })); - auto const postInboxBalance = getDecryptedBalance(*arg.account, HolderEncryptedInbox); - auto const postIssuerBalance = getDecryptedBalance(*arg.account, IssuerEncryptedBalance); - auto const postSpendingBalance = getDecryptedBalance(*arg.account, HolderEncryptedSpending); + auto const postInboxBalance = getDecryptedBalance(*arg.account, holderEncryptedInbox); + auto const postIssuerBalance = getDecryptedBalance(*arg.account, issuerEncryptedBalance); + auto const postSpendingBalance = getDecryptedBalance(*arg.account, holderEncryptedSpending); if (!postInboxBalance || !postIssuerBalance || !postSpendingBalance) Throw("Failed to get post-convertBack balance"); @@ -2360,7 +2392,7 @@ MPTTester::convertBack(MPTConvertBack const& arg) if (arg.auditorEncryptedAmt || auditor_) { auto const postAuditorBalance = - getDecryptedBalance(*arg.account, AuditorEncryptedBalance); + getDecryptedBalance(*arg.account, auditorEncryptedBalance); if (!postAuditorBalance) Throw("Failed to get post-convertBack balance"); @@ -2430,7 +2462,7 @@ MPTTester::convertBackJV(MPTConvertBack const& arg, std::uint32_t seq) jv[sfBlindingFactor] = strHex(blindingFactor); - auto const prevSpendingBalance = getDecryptedBalance(*arg.account, HolderEncryptedSpending); + auto const prevSpendingBalance = getDecryptedBalance(*arg.account, holderEncryptedSpending); if (!prevSpendingBalance) Throw("convertBackJV: failed to read spending balance from ledger"); @@ -2454,7 +2486,7 @@ MPTTester::convertBackJV(MPTConvertBack const& arg, std::uint32_t seq) else { auto const version = getMPTokenVersion(*arg.account); - auto const prevEncSpending = getEncryptedBalance(*arg.account, HolderEncryptedSpending); + auto const prevEncSpending = getEncryptedBalance(*arg.account, holderEncryptedSpending); auto const contextHash = getConvertBackContextHash(arg.account->id(), *id_, seq, version); Buffer proof; diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index 3244875791..d5fba82e08 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -10,24 +10,13 @@ #include #include - -// The mpt-crypto library's (pulled in transitively via -// ConfidentialTransfer.h -> ) defines ttCONFIDENTIAL_MPT_* as -// preprocessor macros that collide with xrpld's TxType enumerators of the -// same name, rewriting e.g. `ttCONFIDENTIAL_MPT_SEND` into the bare integer -// `88` at every use site. Tests only use the TxType enum, so drop the macros. -// TODO: remove once mpt-crypto renames these macros (e.g. kMPT_TT_*). -#undef ttCONFIDENTIAL_MPT_CONVERT -#undef ttCONFIDENTIAL_MPT_MERGE_INBOX -#undef ttCONFIDENTIAL_MPT_CONVERT_BACK -#undef ttCONFIDENTIAL_MPT_SEND -#undef ttCONFIDENTIAL_MPT_CLAWBACK - #include #include #include +#include #include +#include namespace xrpl::test::jtx { @@ -35,23 +24,28 @@ class MPTTester; auto const kMptDexFlags = tfMPTCanTrade | tfMPTCanTransfer; -/*Helper lambda to create a zero-initialized buffer. -WHY THIS IS NEEDED: In C++, xrpl::Buffer(size) allocates uninitialized heap memory. -Because CI runs unit tests sequentially in the same process, uninitialized memory -often recycles "ghost data" (like valid SECP256k1 keys or Pedersen commitments) -left over from previously executed tests. -When testing malformed cryptography paths, passing uninitialized memory might -accidentally supply a valid curve point, causing the ledger's preflight checks -to falsely succeed and return tecBAD_PROOF instead of the expected temMALFORMED. -Explicitly zeroing the buffer guarantees it fails structural validation. */ -static auto gMakeZeroBuffer = [](size_t size) { +/** + * @brief Create a zero-initialized buffer for malformed cryptography test + * inputs. + * + * xrpl::Buffer(size) allocates uninitialized heap memory. Because CI runs unit + * tests sequentially in the same process, uninitialized memory can recycle + * valid secp256k1 keys or Pedersen commitments from earlier tests. Explicitly + * zeroing the buffer guarantees structural validation fails deterministically. + * + * @param size The number of zero bytes to allocate. + * @return A buffer containing size zero bytes. + */ +[[nodiscard]] inline Buffer +gMakeZeroBuffer(std::size_t size) +{ Buffer b(size); if (size > 0) std::memset(b.data(), 0, size); return b; -}; +} -// Check flags settings on MPT create +/** @brief Test helper that checks MPT flag settings after creation. */ class MptFlags { private: @@ -72,7 +66,7 @@ public: operator()(Env& env) const; }; -// Check mptissuance or mptoken amount balances on payment +/** @brief Test helper that checks MPT issuance or holder balances. */ class MptBalance { private: @@ -90,6 +84,7 @@ public: operator()(Env& env) const; }; +/** @brief Test helper that accepts any condition supplied by a callback. */ class RequireAny { private: @@ -106,6 +101,7 @@ public: using Holders = std::vector; +/** @brief Arguments for building an MPTokenIssuanceCreate test transaction. */ struct MPTCreate { static inline std::vector allHolders = {}; @@ -129,8 +125,11 @@ struct MPTCreate std::optional err = std::nullopt; }; +/** @brief Arguments for initializing funded MPT test accounts and issuance. */ struct MPTInit { + // Default-initialized so designated-initializer call sites that omit + // `holders` don't trip GCC's -Werror=missing-field-initializers. Holders holders = {}; // NOLINT(readability-redundant-member-init) std::optional auditor = std::nullopt; PrettyAmount const xrp = XRP(10'000); @@ -142,6 +141,7 @@ struct MPTInit }; static MPTInit const kMptInitNoFund{.fund = false}; +/** @brief Full constructor arguments for MPTTester initialization. */ struct MPTInitDef { Env& env; @@ -159,6 +159,7 @@ struct MPTInitDef std::optional err = std::nullopt; }; +/** @brief Arguments for building an MPTokenIssuanceDestroy test transaction. */ struct MPTDestroy { std::optional issuer = std::nullopt; @@ -169,6 +170,7 @@ struct MPTDestroy std::optional err = std::nullopt; }; +/** @brief Arguments for building an MPTokenAuthorize test transaction. */ struct MPTAuthorize { std::optional account = std::nullopt; @@ -180,6 +182,7 @@ struct MPTAuthorize std::optional err = std::nullopt; }; +/** @brief Arguments for building an MPTokenIssuanceSet test transaction. */ struct MPTSet { std::optional account = std::nullopt; @@ -199,6 +202,7 @@ struct MPTSet std::optional err = std::nullopt; }; +/** @brief Arguments for building a ConfidentialMPTConvert test transaction. */ struct MPTConvert { std::optional account = std::nullopt; @@ -226,6 +230,7 @@ struct MPTConvert std::optional err = std::nullopt; }; +/** @brief Arguments for building a ConfidentialMPTMergeInbox test transaction. */ struct MPTMergeInbox { std::optional account = std::nullopt; @@ -239,6 +244,7 @@ struct MPTMergeInbox std::optional err = std::nullopt; }; +/** @brief Arguments for building a ConfidentialMPTSend test transaction. */ struct MPTConfidentialSend { std::optional account = std::nullopt; @@ -267,6 +273,7 @@ struct MPTConfidentialSend std::optional err = std::nullopt; }; +/** @brief Arguments for building a ConfidentialMPTConvertBack test transaction. */ struct MPTConvertBack { std::optional account = std::nullopt; @@ -289,6 +296,7 @@ struct MPTConvertBack std::optional err = std::nullopt; }; +/** @brief Arguments for building a ConfidentialMPTClawback test transaction. */ struct MPTConfidentialClawback { std::optional account = std::nullopt; @@ -307,13 +315,20 @@ struct MPTConfidentialClawback /** * @brief Stores the parameters that are exclusively used to generate a - * pedersen linkage proof + * Pedersen linkage proof. */ struct PedersenProofParams { + /** @brief The Pedersen commitment used by the proof. */ Buffer const pedersenCommitment; - uint64_t const amt; // either spending balance or value to be transferred + + /** @brief Either the spending balance or the value being transferred. */ + uint64_t const amt; + + /** @brief The encrypted amount linked to the Pedersen commitment. */ Buffer const encryptedAmt; + + /** @brief The blinding factor used to create the Pedersen commitment. */ Buffer const blindingFactor; }; @@ -330,9 +345,14 @@ struct PedersenProofParams */ struct ConfidentialSendChainState { - std::uint64_t spending; // Decrypted spending balance after the previous send. - Buffer encSpending; // Encrypted spending balance after the previous send. - std::uint32_t version; // sfConfidentialBalanceVersion after the previous send. + /** @brief Decrypted spending balance after the previous send. */ + std::uint64_t spending; + + /** @brief Encrypted spending balance after the previous send. */ + Buffer encSpending; + + /** @brief sfConfidentialBalanceVersion after the previous send. */ + std::uint32_t version; }; /** @@ -346,7 +366,8 @@ struct ConfidentialSendChainState * @param currentVersion sfConfidentialBalanceVersion before the send. * @param sendAmt Plaintext amount being sent. * @param senderEncAmt sfSenderEncryptedAmount from the send transaction. - * @return The predicted chain state, or std::nullopt if homomorphic subtraction fails + * @return The predicted chain state, or std::nullopt if homomorphic + * subtraction fails. */ std::optional computeNextSendChainState( @@ -356,6 +377,10 @@ computeNextSendChainState( std::uint64_t sendAmt, Slice const& senderEncAmt); +/** + * @brief Test helper for creating, mutating, and asserting MPT and confidential + * MPT ledger state. + */ class MPTTester { Env& env_; @@ -368,14 +393,18 @@ class MPTTester std::unordered_map privKeys_; public: - // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) - enum EncryptedBalanceType { + enum class EncryptedBalanceType { IssuerEncryptedBalance, HolderEncryptedInbox, HolderEncryptedSpending, AuditorEncryptedBalance, }; + static constexpr auto issuerEncryptedBalance = EncryptedBalanceType::IssuerEncryptedBalance; + static constexpr auto holderEncryptedInbox = EncryptedBalanceType::HolderEncryptedInbox; + static constexpr auto holderEncryptedSpending = EncryptedBalanceType::HolderEncryptedSpending; + static constexpr auto auditorEncryptedBalance = EncryptedBalanceType::AuditorEncryptedBalance; + MPTTester(Env& env, Account issuer, MPTInit const& constr = {}); MPTTester(MPTInitDef const& constr); MPTTester( @@ -416,8 +445,14 @@ public: void convert(MPTConvert const& arg = MPTConvert{}); - // Build a confidential convert JV without submitting. 'seq' is the inner - // transaction sequence used in the Schnorr proof context hash. + /** + * @brief Build a confidential convert JV without submitting it. + * + * @param arg Transaction builder arguments. + * @param seq Inner transaction sequence used in the Schnorr proof context + * hash. + * @return The transaction JSON object. + */ json::Value convertJV(MPTConvert const& arg, std::uint32_t seq); @@ -430,37 +465,54 @@ public: void send(MPTConfidentialSend const& arg = MPTConfidentialSend{}); - // Build a confidential send JV. When 'chain' is provided the sender's - // proof parameters are taken from it instead of the ledger, enabling - // correct proof generation for a second (or later) send from the same - // account inside a single batch. + /** + * @brief Build a confidential send JV. + * + * When chain is provided, the sender's proof parameters are taken from it + * instead of the ledger, enabling proof generation for a second or later + * send from the same account inside a single batch. + * + * @param arg Transaction builder arguments. + * @param seq Inner transaction sequence used in the proof context hash. + * @param chain Optional predicted sender state from a previous batched + * send. + * @return The transaction JSON object. + */ json::Value sendJV( MPTConfidentialSend const& arg, std::uint32_t seq, std::optional chain = std::nullopt); - // Compute the projected sender state after a confidential send in a batch. - // - // Each confidential send requires a ZK proof that the sender's spending - // balance covers the transfer. In a batch, if there are more than one - // Confidential Send, the 2nd onwards send requires a proof that includes the - // updated spending balance. - // - // Example: Bob has 200, batches send 100 to Carol then 50 to Dave: - // jv1 = sendJV({bob->carol, 100}, seq1) - // chain = chainAfterSend(bob, 100, jv1) // projected balance after jv1 = 100 - // jv2 = sendJV({bob->dave, 50}, seq2, chain) + /** + * @brief Compute the projected sender state after a confidential send in a + * batch. + * + * Each confidential send requires a ZK proof that the sender's spending + * balance covers the transfer. In a batch, the second and later sends from + * the same sender need proofs built against the updated spending balance. + * + * @param sender The sender whose post-send state is being predicted. + * @param sendAmt The plaintext amount sent by the transaction. + * @param jv The confidential send transaction JSON object. + * @return The predicted sender state after applying the send. + */ [[nodiscard]] ConfidentialSendChainState chainAfterSend(Account const& sender, std::uint64_t sendAmt, json::Value const& jv) const; void convertBack(MPTConvertBack const& arg = MPTConvertBack{}); - // Build a confidential convertBack JV without submitting. 'seq' is the - // inner transaction sequence used in the proof context hash. Reads the - // current encrypted spending balance and version from the ledger, so call - // this before the batch is submitted. + /** + * @brief Build a confidential convertBack JV without submitting it. + * + * Reads the current encrypted spending balance and version from the ledger, + * so call this before the batch is submitted. + * + * @param arg Transaction builder arguments. + * @param seq Inner transaction sequence used in the proof context hash. + * @return The transaction JSON object. + */ json::Value convertBackJV(MPTConvertBack const& arg, std::uint32_t seq); @@ -535,7 +587,7 @@ public: getIssuanceConfidentialBalance() const; [[nodiscard]] std::optional - getEncryptedBalance(Account const& account, EncryptedBalanceType option = HolderEncryptedInbox) + getEncryptedBalance(Account const& account, EncryptedBalanceType option = holderEncryptedInbox) const; MPT @@ -546,9 +598,6 @@ public: operator Asset() const; - [[nodiscard]] bool - printMPT(Account const& holder) const; - void generateKeyPair(Account const& account); diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index 60288e5f20..2dae6fccb9 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -53,4 +53,4 @@ foreach(module IN LISTS test_modules) ) endforeach() -gtest_discover_tests(xrpl_tests) +gtest_discover_tests(xrpl_tests DISCOVERY_TIMEOUT 60)