diff --git a/include/xrpl/protocol/ConfidentialTransfer.h b/include/xrpl/protocol/ConfidentialTransfer.h index b2d6b93391..dd4186a609 100644 --- a/include/xrpl/protocol/ConfidentialTransfer.h +++ b/include/xrpl/protocol/ConfidentialTransfer.h @@ -16,13 +16,29 @@ #include namespace xrpl { -// Helper struct to bundle the ElGamal Public Key and the associated Ciphertext + +/** + * @brief Bundles an ElGamal public key with its associated encrypted amount. + * + * Used to represent a recipient in confidential transfers, containing both + * the recipient's ElGamal public key and the ciphertext encrypting the + * transfer amount under that key. + */ struct ConfidentialRecipient { - Slice const publicKey; - Slice const encryptedAmount; + Slice const publicKey; ///< The recipient's ElGamal public key (64 bytes). + Slice const encryptedAmount; ///< The encrypted amount ciphertext (128 bytes). }; +/** + * @brief Increments the confidential balance version counter on an MPToken. + * + * The version counter is used to prevent replay attacks by binding proofs + * to a specific state of the account's confidential balance. Wraps to 0 + * on overflow (defined behavior for unsigned integers). + * + * @param mptoken The MPToken ledger entry to update. + */ inline void incrementConfidentialVersion(STObject& mptoken) { @@ -32,6 +48,19 @@ incrementConfidentialVersion(STObject& mptoken) mptoken[sfConfidentialBalanceVersion] = mptoken[~sfConfidentialBalanceVersion].value_or(0u) + 1u; } +/** + * @brief Adds common fields to a serializer for ZKP context hash generation. + * + * Serializes the transaction type, account, sequence number, and issuance ID + * into the provided serializer. These fields form the base of all context + * hashes used in zero-knowledge proofs. + * + * @param s The serializer to append fields to. + * @param txType The transaction type identifier. + * @param account The account ID of the transaction sender. + * @param sequence The transaction sequence number. + * @param issuanceID The MPToken Issuance ID. + */ void addCommonZKPFields( Serializer& s, @@ -40,6 +69,19 @@ addCommonZKPFields( std::uint32_t sequence, uint192 const& issuanceID); +/** + * @brief Generates the context hash for ConfidentialMPTSend transactions. + * + * Creates a unique 256-bit hash that binds the zero-knowledge proofs to + * this specific send transaction, preventing proof reuse across transactions. + * + * @param account The sender's account ID. + * @param sequence The transaction sequence number. + * @param issuanceID The MPToken Issuance ID. + * @param destination The destination account ID. + * @param version The sender's confidential balance version. + * @return A 256-bit context hash unique to this transaction. + */ uint256 getSendContextHash( AccountID const& account, @@ -48,6 +90,19 @@ getSendContextHash( AccountID const& destination, std::uint32_t version); +/** + * @brief Generates the context hash for ConfidentialMPTClawback transactions. + * + * Creates a unique 256-bit hash that binds the equality proof to this + * specific clawback transaction. + * + * @param account The issuer's account ID. + * @param sequence The transaction sequence number. + * @param issuanceID The MPToken Issuance ID. + * @param amount The amount being clawed back. + * @param holder The holder's account ID being clawed back from. + * @return A 256-bit context hash unique to this transaction. + */ uint256 getClawbackContextHash( AccountID const& account, @@ -56,6 +111,18 @@ getClawbackContextHash( std::uint64_t amount, AccountID const& holder); +/** + * @brief Generates the context hash for ConfidentialMPTConvert transactions. + * + * Creates a unique 256-bit hash that binds the Schnorr proof (for key + * registration) to this specific convert transaction. + * + * @param account The holder's account ID. + * @param sequence The transaction sequence number. + * @param issuanceID The MPToken Issuance ID. + * @param amount The amount being converted to confidential. + * @return A 256-bit context hash unique to this transaction. + */ uint256 getConvertContextHash( AccountID const& account, @@ -63,6 +130,19 @@ getConvertContextHash( uint192 const& issuanceID, std::uint64_t amount); +/** + * @brief Generates the context hash for ConfidentialMPTConvertBack transactions. + * + * Creates a unique 256-bit hash that binds the zero-knowledge proofs to + * this specific convert-back transaction. + * + * @param account The holder's account ID. + * @param sequence The transaction sequence number. + * @param issuanceID The MPToken Issuance ID. + * @param amount The amount being converted back to public. + * @param version The holder's confidential balance version. + * @return A 256-bit context hash unique to this transaction. + */ uint256 getConvertBackContextHash( AccountID const& account, @@ -71,39 +151,125 @@ getConvertBackContextHash( std::uint64_t amount, std::uint32_t version); -// breaks a 66-byte encrypted amount into two 33-byte components -// then parses each 33-byte component into 64-byte secp256k1_pubkey format +/** + * @brief Parses an ElGamal ciphertext into two secp256k1 public key components. + * + * Breaks a 66-byte encrypted amount (two 33-byte compressed EC points) into + * two secp256k1_pubkey structures (C1, C2) for use in cryptographic operations. + * + * @param buffer The 66-byte buffer containing the compressed ciphertext. + * @param out1 Output: The C1 component of the ElGamal ciphertext. + * @param out2 Output: The C2 component of the ElGamal ciphertext. + * @return true if parsing succeeds, false if the buffer is invalid. + */ bool makeEcPair(Slice const& buffer, secp256k1_pubkey& out1, secp256k1_pubkey& out2); -// serialize two secp256k1_pubkey components back into compressed 66-byte form +/** + * @brief Serializes two secp256k1 public key components into compressed form. + * + * Converts two secp256k1_pubkey structures (C1, C2) back into a 66-byte + * buffer containing two 33-byte compressed EC points. + * + * @param in1 The C1 component to serialize. + * @param in2 The C2 component to serialize. + * @param buffer Output: The 66-byte buffer to write the compressed ciphertext. + * @return true if serialization succeeds, false otherwise. + */ bool serializeEcPair(secp256k1_pubkey const& in1, secp256k1_pubkey const& in2, Buffer& buffer); /** * @brief Verifies that a buffer contains two valid, parsable EC public keys. + * * @param buffer The input buffer containing two concatenated components. * @return true if both components can be parsed successfully, false otherwise. */ bool isValidCiphertext(Slice const& buffer); +/** + * @brief Homomorphically adds two ElGamal ciphertexts. + * + * Uses the additive homomorphic property of ElGamal encryption to compute + * Enc(a + b) from Enc(a) and Enc(b) without decryption. + * + * @param a The first ciphertext (66 bytes). + * @param b The second ciphertext (66 bytes). + * @param out Output: The resulting ciphertext Enc(a + b). + * @return tesSUCCESS on success, or an error code if parsing fails. + */ TER homomorphicAdd(Slice const& a, Slice const& b, Buffer& out); +/** + * @brief Homomorphically subtracts two ElGamal ciphertexts. + * + * Uses the additive homomorphic property of ElGamal encryption to compute + * Enc(a - b) from Enc(a) and Enc(b) without decryption. + * + * @param a The minuend ciphertext (66 bytes). + * @param b The subtrahend ciphertext (66 bytes). + * @param out Output: The resulting ciphertext Enc(a - b). + * @return tesSUCCESS on success, or an error code if parsing fails. + */ TER homomorphicSubtract(Slice const& a, Slice const& b, Buffer& out); -// returns ciphertext and the blinding factor used +/** + * @brief Encrypts an amount using ElGamal encryption. + * + * Produces a ciphertext C = (C1, C2) where C1 = r*G and C2 = m*G + r*Pk, + * using the provided blinding factor r. + * + * @param amt The plaintext amount to encrypt. + * @param pubKeySlice The recipient's ElGamal public key (64 bytes). + * @param blindingFactor The 32-byte randomness used as blinding factor r. + * @return The 66-byte ciphertext, or std::nullopt on failure. + */ std::optional encryptAmount(uint64_t const amt, Slice const& pubKeySlice, Slice const& blindingFactor); +/** + * @brief Generates the canonical zero encryption for a specific MPToken. + * + * Creates a deterministic encryption of zero that is unique to the account + * and MPT issuance. Used to initialize confidential balance fields. + * + * @param pubKeySlice The holder's ElGamal public key (64 bytes). + * @param account The account ID of the token holder. + * @param mptId The MPToken Issuance ID. + * @return The 66-byte canonical zero ciphertext, or std::nullopt on failure. + */ std::optional encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, MPTID const& mptId); +/** + * @brief Verifies a Schnorr proof of knowledge of an ElGamal private key. + * + * Proves that the submitter knows the secret key corresponding to the + * provided public key, without revealing the secret key itself. + * + * @param pubKeySlice The ElGamal public key (64 bytes). + * @param proofSlice The Schnorr proof (65 bytes). + * @param contextHash The 256-bit context hash binding the proof. + * @return tesSUCCESS if valid, or an error code otherwise. + */ TER verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 const& contextHash); +/** + * @brief Verifies that a ciphertext correctly encrypts a revealed amount. + * + * Given the plaintext amount and blinding factor, verifies that the + * ciphertext was correctly constructed using ElGamal encryption. + * + * @param amount The revealed plaintext amount. + * @param blindingFactor The 32-byte blinding factor used in encryption. + * @param pubKeySlice The recipient's ElGamal public key (64 bytes). + * @param ciphertext The ciphertext to verify (66 bytes). + * @return tesSUCCESS if the encryption is valid, or an error code otherwise. + */ TER verifyElGamalEncryption( std::uint64_t const amount, @@ -111,9 +277,31 @@ verifyElGamalEncryption( Slice const& pubKeySlice, Slice const& ciphertext); +/** + * @brief Validates the format of encrypted amount fields in a transaction. + * + * Checks that all ciphertext fields in the transaction object have the + * correct length and contain valid EC points. + * + * @param object The transaction object containing encrypted amount fields. + * @return tesSUCCESS if all formats are valid, or an error code otherwise. + */ NotTEC checkEncryptedAmountFormat(STObject const& object); +/** + * @brief Verifies revealed amount encryptions for all recipients. + * + * Validates that the same amount was correctly encrypted for the holder, + * issuer, and optionally the auditor using their respective public keys. + * + * @param amount The revealed plaintext amount. + * @param blindingFactor The 32-byte blinding factor used in all encryptions. + * @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. + * @return tesSUCCESS if all encryptions are valid, or an error code otherwise. + */ TER verifyRevealedAmount( std::uint64_t const amount, @@ -122,15 +310,45 @@ verifyRevealedAmount( ConfidentialRecipient const& issuer, std::optional const& auditor); +/** + * @brief Returns the number of recipients in a confidential transfer. + * + * Returns 4 if an auditor is present (sender, destination, issuer, auditor), + * or 3 if no auditor (sender, destination, issuer). + * + * @param hasAuditor Whether the issuance has an auditor configured. + * @return The number of recipients (3 or 4). + */ constexpr std::size_t getConfidentialRecipientCount(bool hasAuditor) { return hasAuditor ? 4 : 3; } +/** + * @brief Calculates the size of a multi-ciphertext equality proof. + * + * The proof size varies based on the number of recipients because each + * additional recipient requires additional proof components. + * + * @param nRecipients The number of recipients in the transfer. + * @return The size in bytes of the equality proof. + */ std::size_t getMultiCiphertextEqualityProofSize(std::size_t nRecipients); +/** + * @brief Verifies a multi-ciphertext equality proof. + * + * Proves that all ciphertexts in the recipients vector encrypt the same + * plaintext amount, without revealing the amount itself. + * + * @param proof The zero-knowledge proof bytes. + * @param recipients Vector of recipients with their public keys and ciphertexts. + * @param nRecipients The number of recipients (must match recipients.size()). + * @param contextHash The 256-bit context hash binding the proof. + * @return tesSUCCESS if the proof is valid, or an error code otherwise. + */ TER verifyMultiCiphertextEqualityProof( Slice const& proof, @@ -138,6 +356,20 @@ verifyMultiCiphertextEqualityProof( std::size_t const nRecipients, uint256 const& contextHash); +/** + * @brief Verifies a clawback equality proof. + * + * Proves that the issuer knows the exact amount encrypted in the holder's + * balance ciphertext. Used in ConfidentialMPTClawback to verify the issuer + * can decrypt the balance using their private key. + * + * @param amount The revealed plaintext amount. + * @param proof The zero-knowledge proof bytes. + * @param pubKeySlice The issuer's ElGamal public key (64 bytes). + * @param ciphertext The issuer's encrypted balance on the holder's account (66 bytes). + * @param contextHash The 256-bit context hash binding the proof. + * @return tesSUCCESS if the proof is valid, or an error code otherwise. + */ TER verifyClawbackEqualityProof( uint64_t const amount, @@ -146,7 +378,14 @@ verifyClawbackEqualityProof( Slice const& ciphertext, uint256 const& contextHash); -// generates a 32 byte randomness factor to be used in encryption and proofs +/** + * @brief Generates a cryptographically secure 32-byte blinding factor. + * + * Produces random bytes suitable for use as an ElGamal blinding factor + * or Pedersen commitment randomness. + * + * @return A 32-byte buffer containing the random blinding factor. + */ Buffer generateBlindingFactor(); diff --git a/src/libxrpl/protocol/ConfidentialTransfer.cpp b/src/libxrpl/protocol/ConfidentialTransfer.cpp index 1834d52c9d..12c4083b09 100644 --- a/src/libxrpl/protocol/ConfidentialTransfer.cpp +++ b/src/libxrpl/protocol/ConfidentialTransfer.cpp @@ -117,12 +117,8 @@ serializeEcPair(secp256k1_pubkey const& in1, secp256k1_pubkey const& in2, Buffer bool isValidCiphertext(Slice const& buffer) { - // Local/temporary variables to pass to makeEcPair. - // Their contents will be discarded when the function returns. secp256k1_pubkey key1; secp256k1_pubkey key2; - - // Call makeEcPair and return its result. return makeEcPair(buffer, key1, key2); } @@ -193,22 +189,16 @@ generateBlindingFactor() std::optional encryptAmount(uint64_t const amt, Slice const& pubKeySlice, Slice const& blindingFactor) { - Buffer buf(ecGamalEncryptedTotalLength); - - // Allocate ciphertext placeholders - secp256k1_pubkey c1, c2; - secp256k1_pubkey pubKey; - if (blindingFactor.size() != ecBlindingFactorLength) return std::nullopt; + secp256k1_pubkey c1, c2, pubKey; std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength); - // Encrypt the amount if (!secp256k1_elgamal_encrypt(secp256k1Context(), &c1, &c2, &pubKey, amt, blindingFactor.data())) return std::nullopt; - // Serialize the ciphertext pair into the buffer + Buffer buf(ecGamalEncryptedTotalLength); if (!serializeEcPair(c1, c2, buf)) return std::nullopt; @@ -221,18 +211,13 @@ encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, M if (pubKeySlice.size() != ecPubKeyLength) return std::nullopt; // LCOV_EXCL_LINE - secp256k1_pubkey c1, c2; - secp256k1_pubkey pubKey; - + secp256k1_pubkey c1, c2, pubKey; std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength); - // Encrypt the amount if (!generate_canonical_encrypted_zero(secp256k1Context(), &c1, &c2, &pubKey, account.data(), mptId.data())) return std::nullopt; Buffer buf(ecGamalEncryptedTotalLength); - - // Serialize the ciphertext pair into the buffer if (!serializeEcPair(c1, c2, buf)) return std::nullopt; @@ -242,20 +227,16 @@ encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, M TER verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 const& contextHash) { - // sanity check proof length if (proofSlice.size() != ecSchnorrProofLength) return tecINTERNAL; // LCOV_EXCL_LINE - // sanity check public key length if (pubKeySlice.size() != ecPubKeyLength) return tecINTERNAL; // LCOV_EXCL_LINE secp256k1_pubkey pubKey; std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength); - int result = secp256k1_mpt_pok_sk_verify(secp256k1Context(), proofSlice.data(), &pubKey, contextHash.data()); - - if (result != 1) + if (secp256k1_mpt_pok_sk_verify(secp256k1Context(), proofSlice.data(), &pubKey, contextHash.data()) != 1) return tecBAD_PROOF; return tesSUCCESS; @@ -268,11 +249,9 @@ verifyElGamalEncryption( Slice const& pubKeySlice, Slice const& ciphertext) { - // sanity check blinding factor length if (blindingFactor.size() != ecBlindingFactorLength) return tecINTERNAL; // LCOV_EXCL_LINE - // sanity check public key length if (pubKeySlice.size() != ecPubKeyLength) return tecINTERNAL; // LCOV_EXCL_LINE @@ -283,13 +262,8 @@ verifyElGamalEncryption( if (!makeEcPair(ciphertext, c1, c2)) return tecINTERNAL; // LCOV_EXCL_LINE - int result = - secp256k1_elgamal_verify_encryption(secp256k1Context(), &c1, &c2, &pubKey, amount, blindingFactor.data()); - - if (result != 1) - { + if (secp256k1_elgamal_verify_encryption(secp256k1Context(), &c1, &c2, &pubKey, amount, blindingFactor.data()) != 1) return tecBAD_PROOF; - } return tesSUCCESS; } @@ -392,6 +366,9 @@ verifyClawbackEqualityProof( secp256k1_pubkey pubKey; std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength); + // Note: c2, c1 order - the proof is generated with c2 first (the encrypted + // message component) because the equality proof structure expects the + // message-containing term before the blinding term. if (secp256k1_equality_plaintext_verify( secp256k1Context(), proof.data(), &pubKey, &c2, &c1, amount, contextHash.data()) != 1) { @@ -484,6 +461,8 @@ verifyBalancePcmLinkage( std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength); std::memcpy(pcm.data, pcmSlice.data(), ecPubKeyLength); + // Note: c2, c1 order - the linkage proof expects the message-containing + // component (c2 = m*G + r*Pk) before the blinding component (c1 = r*G). if (secp256k1_elgamal_pedersen_link_verify( secp256k1Context(), proof.data(), &pubKey, &c2, &c1, &pcm, contextHash.data()) != 1) { @@ -517,7 +496,6 @@ secp256k1_elgamal_generate_keypair(secp256k1_context const* ctx, unsigned char* return 1; // Success } -// ... implementation of secp256k1_elgamal_encrypt ... int secp256k1_elgamal_encrypt( secp256k1_context const* ctx, @@ -579,7 +557,6 @@ secp256k1_elgamal_encrypt( return 1; // Success } -// ... implementation of secp256k1_elgamal_decrypt ... int secp256k1_elgamal_decrypt( secp256k1_context const* ctx, diff --git a/src/xrpld/app/tx/detail/ConfidentialMPTClawback.h b/src/xrpld/app/tx/detail/ConfidentialMPTClawback.h index b080218af5..dcea4bc4ac 100644 --- a/src/xrpld/app/tx/detail/ConfidentialMPTClawback.h +++ b/src/xrpld/app/tx/detail/ConfidentialMPTClawback.h @@ -5,6 +5,22 @@ namespace xrpl { +/** + * @brief Allows an MPT issuer to clawback confidential balances from a holder. + * + * This transaction enables the issuer of an MPToken Issuance (with clawback + * enabled) to reclaim confidential tokens from a holder's account. Unlike + * regular clawback, the issuer cannot see the holder's balance directly. + * Instead, the issuer must provide a zero-knowledge proof that demonstrates + * they know the exact encrypted balance amount. + * + * @par Cryptographic Operations: + * - **Equality Proof Verification**: Verifies that the issuer's revealed + * amount matches the holder's encrypted balance using the issuer's + * ElGamal private key. + * + * @see ConfidentialMPTSend, ConfidentialMPTConvert + */ class ConfidentialMPTClawback : public Transactor { public: diff --git a/src/xrpld/app/tx/detail/ConfidentialMPTConvert.h b/src/xrpld/app/tx/detail/ConfidentialMPTConvert.h index d375ed9a39..8147f32e79 100644 --- a/src/xrpld/app/tx/detail/ConfidentialMPTConvert.h +++ b/src/xrpld/app/tx/detail/ConfidentialMPTConvert.h @@ -5,6 +5,24 @@ namespace xrpl { +/** + * @brief Converts public (plaintext) MPT balance to confidential (encrypted) + * balance. + * + * This transaction allows a token holder to convert their publicly visible + * MPToken balance into an encrypted confidential balance. Once converted, + * the balance can only be spent using ConfidentialMPTSend transactions and + * remains hidden from public view on the ledger. + * + * @par Cryptographic Operations: + * - **Schnorr Proof Verification**: When registering a new ElGamal public key, + * verifies proof of knowledge of the corresponding private key. + * - **Revealed Amount Verification**: Verifies that the provided encrypted + * amounts (for holder, issuer, and optionally auditor) all encrypt the + * same plaintext amount using the provided blinding factor. + * + * @see ConfidentialMPTConvertBack, ConfidentialMPTSend + */ class ConfidentialMPTConvert : public Transactor { public: diff --git a/src/xrpld/app/tx/detail/ConfidentialMPTConvertBack.h b/src/xrpld/app/tx/detail/ConfidentialMPTConvertBack.h index 8ef63c3d1e..c07c494c57 100644 --- a/src/xrpld/app/tx/detail/ConfidentialMPTConvertBack.h +++ b/src/xrpld/app/tx/detail/ConfidentialMPTConvertBack.h @@ -5,6 +5,25 @@ namespace xrpl { +/** + * @brief Converts confidential (encrypted) MPT balance back to public + * (plaintext) balance. + * + * This transaction allows a token holder to convert their encrypted + * confidential balance back into a publicly visible MPToken balance. The + * holder must prove they have sufficient confidential balance without + * revealing the actual balance amount. + * + * @par Cryptographic Operations: + * - **Revealed Amount Verification**: Verifies that the provided encrypted + * amounts correctly encrypt the conversion amount. + * - **Pedersen Linkage Proof**: Verifies that the provided balance commitment + * correctly links to the holder's encrypted spending balance. + * - **Bulletproof Range Proof**: Verifies that the remaining balance (after + * conversion) is non-negative, ensuring the holder has sufficient funds. + * + * @see ConfidentialMPTConvert, ConfidentialMPTSend + */ class ConfidentialMPTConvertBack : public Transactor { public: diff --git a/src/xrpld/app/tx/detail/ConfidentialMPTMergeInbox.h b/src/xrpld/app/tx/detail/ConfidentialMPTMergeInbox.h index 2eaa4e9fcf..f7de7d0005 100644 --- a/src/xrpld/app/tx/detail/ConfidentialMPTMergeInbox.h +++ b/src/xrpld/app/tx/detail/ConfidentialMPTMergeInbox.h @@ -5,6 +5,26 @@ namespace xrpl { +/** + * @brief Merges the confidential inbox balance into the spending balance. + * + * In the confidential transfer system, incoming funds are deposited into an + * "inbox" balance that the recipient cannot immediately spend. This prevents + * front-running attacks where an attacker could invalidate a pending + * transaction by sending funds to the sender. This transaction merges the + * inbox into the spending balance, making those funds available for spending. + * + * @par Cryptographic Operations: + * - **Homomorphic Addition**: Adds the encrypted inbox balance to the + * encrypted spending balance using ElGamal homomorphic properties. + * - **Zero Encryption**: Resets the inbox to an encryption of zero. + * + * @note This transaction requires no zero-knowledge proofs because it only + * combines encrypted values that the holder already owns. The + * homomorphic properties of ElGamal encryption ensure correctness. + * + * @see ConfidentialMPTSend, ConfidentialMPTConvert + */ class ConfidentialMPTMergeInbox : public Transactor { public: diff --git a/src/xrpld/app/tx/detail/ConfidentialMPTSend.h b/src/xrpld/app/tx/detail/ConfidentialMPTSend.h index 22c47ad771..f4ed8c9e18 100644 --- a/src/xrpld/app/tx/detail/ConfidentialMPTSend.h +++ b/src/xrpld/app/tx/detail/ConfidentialMPTSend.h @@ -5,6 +5,32 @@ namespace xrpl { +/** + * @brief Transfers confidential MPT tokens between holders privately. + * + * This transaction enables private token transfers where the transfer amount + * is hidden from public view. Both sender and recipient must have initialized + * confidential balances. The transaction provides encrypted amounts for all + * parties (sender, destination, issuer, and optionally auditor) along with + * zero-knowledge proofs that verify correctness without revealing the amount. + * + * @par Cryptographic Operations: + * - **Multi-Ciphertext Equality Proof**: Verifies that all encrypted amounts + * (sender, destination, issuer, auditor) encrypt the same plaintext value. + * - **Amount Pedersen Linkage Proof**: Verifies that the amount commitment + * correctly links to the sender's encrypted amount. + * - **Balance Pedersen Linkage Proof**: Verifies that the balance commitment + * correctly links to the sender's encrypted spending balance. + * - **Bulletproof Range Proof**: Verifies remaining balance and + * transfer amount are non-negative. + * + * @note Funds are deposited into the destination's inbox, not spending + * balance. The recipient must call ConfidentialMPTMergeInbox to make + * received funds spendable. + * + * @see ConfidentialMPTMergeInbox, ConfidentialMPTConvert, + * ConfidentialMPTConvertBack + */ class ConfidentialMPTSend : public Transactor { public: