From 75d143a2a06af4ed1c72a28e39fbb6b24351cb20 Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Mon, 19 Jan 2026 13:07:19 -0500 Subject: [PATCH] support new design to reveal blinding factor (#6237) * reveal blinding factor and optimize * schnorr proof is added for registering holder pub key * clean env.close that already closed * clean up the lib functions --- include/xrpl/protocol/ConfidentialTransfer.h | 249 ++--- include/xrpl/protocol/Protocol.h | 3 + include/xrpl/protocol/detail/sfields.macro | 1 + .../xrpl/protocol/detail/transactions.macro | 4 +- src/libxrpl/protocol/ConfidentialTransfer.cpp | 921 ++++++++++-------- src/test/app/ConfidentialTransfer_test.cpp | 227 +++-- src/test/jtx/impl/mpt.cpp | 176 +--- src/test/jtx/mpt.h | 26 +- .../app/tx/detail/ConfidentialConvert.cpp | 125 ++- .../app/tx/detail/ConfidentialConvertBack.cpp | 67 +- 10 files changed, 928 insertions(+), 871 deletions(-) diff --git a/include/xrpl/protocol/ConfidentialTransfer.h b/include/xrpl/protocol/ConfidentialTransfer.h index 2edeb93525..29e51cac42 100644 --- a/include/xrpl/protocol/ConfidentialTransfer.h +++ b/include/xrpl/protocol/ConfidentialTransfer.h @@ -48,6 +48,108 @@ getConvertBackContextHash( uint192 const& issuanceID, 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 +bool +makeEcPair(Slice const& buffer, secp256k1_pubkey& out1, secp256k1_pubkey& out2); + +// serialize two secp256k1_pubkey components back into compressed 66-byte form +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); + +TER +homomorphicAdd(Slice const& a, Slice const& b, Buffer& out); + +TER +homomorphicSubtract(Slice const& a, Slice const& b, Buffer& out); + +// returns ciphertext and the blinding factor used +std::optional +encryptAmount( + uint64_t const amt, + Slice const& pubKeySlice, + Slice const& blindingFactor); + +Buffer +encryptCanonicalZeroAmount( + Slice const& pubKeySlice, + AccountID const& account, + MPTID const& mptId); + +TER +verifySchnorrProof( + Slice const& pubKeySlice, + Slice const& proofSlice, + uint256 const& contextHash); + +TER +verifyElGamalEncryption( + std::uint64_t const amount, + Slice const& blindingFactor, + Slice const& pubKeySlice, + Slice const& ciphertext); + +TER +verifyClawbackEqualityProof( + uint64_t const amount, + Slice const& proof, + Slice const& pubKeySlice, + Slice const& ciphertext, + uint256 const& contextHash); + +std::vector +getEqualityProofs(Slice const& zkp); + +NotTEC +checkEncryptedAmountFormat(STObject const& object); + +// Helper struct to bundle the ElGamal Public Key and the associated Ciphertext +struct EncryptedAmountInfo +{ + Slice const publicKey; + Slice const encryptedAmount; +}; + +TER +verifyRevealedAmount( + std::uint64_t const amount, + Slice const& blindingFactor, + EncryptedAmountInfo const& holder, + EncryptedAmountInfo const& issuer, + std::optional const& auditor); + +// returns the number of entries +size_t inline getEqualityProofSize(bool const hasAuditor) +{ + // Be careful if we ever need to change the numbers below, it will be a + // breaking change! + return (hasAuditor ? 3 : 2); +} + +// returns the total byte length of all the equality proofs combined +size_t inline getEqualityProofLength(bool const hasAuditor) +{ + return getEqualityProofSize(hasAuditor) * ecEqualityProofLength; +} + +// generates a 32 byte randomness factor to be used in encryption and proofs +Buffer +generateBlindingFactor(); + +// The following functions belong to the mpt-crypto library, +// they will be finally removed and we will use conan2 to manage the dependency. /** * @brief Generates a new secp256k1 key pair. */ @@ -207,125 +309,42 @@ secp256k1_equality_plaintext_verify( uint64_t amount, unsigned char const* tx_context_id); -// breaks a 66-byte encrypted amount into two 33-byte components -// then parses each 33-byte component into 64-byte secp256k1_pubkey format -bool -makeEcPair(Slice const& buffer, secp256k1_pubkey& out1, secp256k1_pubkey& out2); +void +build_pok_challenge( + unsigned char* e, + secp256k1_context const* ctx, + secp256k1_pubkey const* pk, + secp256k1_pubkey const* T, + unsigned char const* context_id); -// serialize two secp256k1_pubkey components back into compressed 66-byte form -bool -serializeEcPair( - secp256k1_pubkey const& in1, - secp256k1_pubkey const& in2, - Buffer& buffer); +/** Proof of Knowledge of Secret Key for Registration */ +int +secp256k1_mpt_pok_sk_prove( + secp256k1_context const* ctx, + unsigned char* proof, /* Expected size: 65 bytes */ + secp256k1_pubkey const* pk, + unsigned char const* sk, + unsigned char const* context_id); + +int +secp256k1_mpt_pok_sk_verify( + secp256k1_context const* ctx, + unsigned char const* proof, /* Expected size: 65 bytes */ + secp256k1_pubkey const* pk, + unsigned char const* context_id); /** - * @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. + * Verifies that (c1, c2) is a valid ElGamal encryption of 'amount' + * for 'pubkey_Q' using the revealed 'blinding_factor'. */ -bool -isValidCiphertext(Slice const& buffer); - -TER -homomorphicAdd(Slice const& a, Slice const& b, Buffer& out); - -TER -homomorphicSubtract(Slice const& a, Slice const& b, Buffer& out); - -TER -proveEquality( - Slice const& proof, - Slice const& encAmt, // encrypted amount - Slice const& pubkey, - uint64_t const amount, - uint256 const& txHash, // Transaction context data - std::uint32_t const spendVersion); - -// returns ciphertext and the blinding factor used -Buffer -encryptAmount( - uint64_t const amt, - Slice const& pubKeySlice, - Slice const& blindingFactor); - -Buffer -encryptCanonicalZeroAmount( - Slice const& pubKeySlice, - AccountID const& account, - MPTID const& mptId); - -TER -verifyConfidentialSendProof( - Slice const& proof, - Slice const& encSenderBalance, - Slice const& encSenderAmt, - Slice const& encDestAmt, - Slice const& encIssuerAmt, - Slice const& senderPubKey, - Slice const& destPubKey, - Slice const& issuerPubKey, - std::uint32_t const version, - uint256 const& txHash); - -TER -verifyEqualityProof( - uint64_t const amount, - Slice const& proof, - Slice const& pubKeySlice, - Slice const& ciphertext, - uint256 const& contextHash); - -TER -verifyClawbackEqualityProof( - uint64_t const amount, - Slice const& proof, - Slice const& pubKeySlice, - Slice const& ciphertext, - uint256 const& contextHash); - -std::vector -getEqualityProofs(Slice const& zkp); - -NotTEC -checkEncryptedAmountFormat(STObject const& object); - -// Helper struct to bundle the ElGamal Public Key and the associated Ciphertext -struct EncryptedAmountInfo -{ - Slice const publicKey; - Slice const encryptedAmount; -}; - -/** - * Verifies equality proofs for Holder, Issuer, and optionally Auditor. - */ -TER -verifyEqualityProofs( - std::uint64_t amount, - std::vector const& zkps, - EncryptedAmountInfo const& holder, - EncryptedAmountInfo const& issuer, - std::optional const& auditor, - uint256 const& contextHash); - -// returns the number of entries -size_t inline getEqualityProofSize(bool const hasAuditor) -{ - // Be careful if we ever need to change the numbers below, it will be a - // breaking change! - return (hasAuditor ? 3 : 2); -} - -// returns the total byte length of all the equality proofs combined -size_t inline getEqualityProofLength(bool const hasAuditor) -{ - return getEqualityProofSize(hasAuditor) * ecEqualityProofLength; -} - -// generates a 32 byte randomness factor to be used in encryption and proofs -Buffer -generateBlindingFactor(); +int +secp256k1_elgamal_verify_encryption( + secp256k1_context const* ctx, + secp256k1_pubkey const* c1, + secp256k1_pubkey const* c2, + secp256k1_pubkey const* pubkey_Q, + uint64_t amount, + unsigned char const* blinding_factor); } // namespace ripple diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index 6d67593fc5..1407a6d346 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -314,6 +314,9 @@ std::size_t constexpr ecPrivKeyLength = 32; /** Length of the EC blinding factor */ std::size_t constexpr ecBlindingFactorLength = 32; + +/** Length of Schnorr ZKProof for public key registration */ +std::size_t constexpr ecSchnorrProofLength = 65; } // namespace ripple #endif diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index f844d6bcae..ef1cc24048 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -312,6 +312,7 @@ TYPED_SFIELD(sfDestinationEncryptedAmount, VL, 41) TYPED_SFIELD(sfAuditorEncryptedBalance, VL, 42) TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43) TYPED_SFIELD(sfAuditorElGamalPublicKey, VL, 44) +TYPED_SFIELD(sfBlindingFactor, VL, 45) // account (common) TYPED_SFIELD(sfAccount, ACCOUNT, 1) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index ace51f6278..a9154a2e6c 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -1075,7 +1075,8 @@ TRANSACTION(ttCONFIDENTIAL_CONVERT, 85, ConfidentialConvert, {sfHolderEncryptedAmount, soeREQUIRED}, {sfIssuerEncryptedAmount, soeREQUIRED}, {sfAuditorEncryptedAmount, soeOPTIONAL}, - {sfZKProof, soeREQUIRED}, + {sfBlindingFactor, soeREQUIRED}, + {sfZKProof, soeOPTIONAL}, })) /** This transaction type merges MPT inbox. */ @@ -1104,6 +1105,7 @@ TRANSACTION(ttCONFIDENTIAL_CONVERT_BACK, 87, ConfidentialConvertBack, {sfHolderEncryptedAmount, soeREQUIRED}, {sfIssuerEncryptedAmount, soeREQUIRED}, {sfAuditorEncryptedAmount, soeOPTIONAL}, + {sfBlindingFactor, soeREQUIRED}, {sfZKProof, soeREQUIRED}, })) diff --git a/src/libxrpl/protocol/ConfidentialTransfer.cpp b/src/libxrpl/protocol/ConfidentialTransfer.cpp index dd62c3327f..01d91a91cc 100644 --- a/src/libxrpl/protocol/ConfidentialTransfer.cpp +++ b/src/libxrpl/protocol/ConfidentialTransfer.cpp @@ -70,6 +70,357 @@ getConvertBackContextHash( return s.getSHA512Half(); } +bool +makeEcPair(Slice const& buffer, secp256k1_pubkey& out1, secp256k1_pubkey& out2) +{ + auto parsePubKey = [](Slice const& slice, secp256k1_pubkey& out) { + return secp256k1_ec_pubkey_parse( + secp256k1Context(), + &out, + reinterpret_cast(slice.data()), + slice.length()); + }; + + Slice s1{buffer.data(), ecGamalEncryptedLength}; + Slice s2{buffer.data() + ecGamalEncryptedLength, ecGamalEncryptedLength}; + + int const ret1 = parsePubKey(s1, out1); + int const ret2 = parsePubKey(s2, out2); + + return ret1 == 1 && ret2 == 1; +} + +bool +serializeEcPair( + secp256k1_pubkey const& in1, + secp256k1_pubkey const& in2, + Buffer& buffer) +{ + auto serializePubKey = [](secp256k1_pubkey const& pub, unsigned char* out) { + size_t outLen = ecGamalEncryptedLength; // 33 bytes + int const ret = secp256k1_ec_pubkey_serialize( + secp256k1Context(), out, &outLen, &pub, SECP256K1_EC_COMPRESSED); + return ret == 1 && outLen == ecGamalEncryptedLength; + }; + + unsigned char* ptr = buffer.data(); + bool const res1 = serializePubKey(in1, ptr); + bool const res2 = serializePubKey(in2, ptr + ecGamalEncryptedLength); + + return res1 && res2; +} + +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); +} + +TER +homomorphicAdd(Slice const& a, Slice const& b, Buffer& out) +{ + if (a.length() != ecGamalEncryptedTotalLength || + b.length() != ecGamalEncryptedTotalLength) + return tecINTERNAL; + + secp256k1_pubkey aC1; + secp256k1_pubkey aC2; + secp256k1_pubkey bC1; + secp256k1_pubkey bC2; + + if (!makeEcPair(a, aC1, aC2) || !makeEcPair(b, bC1, bC2)) + return tecINTERNAL; + + secp256k1_pubkey sumC1; + secp256k1_pubkey sumC2; + + if (secp256k1_elgamal_add( + secp256k1Context(), &sumC1, &sumC2, &aC1, &aC2, &bC1, &bC2) != 1) + return tecINTERNAL; + + if (!serializeEcPair(sumC1, sumC2, out)) + return tecINTERNAL; + + return tesSUCCESS; +} + +TER +homomorphicSubtract(Slice const& a, Slice const& b, Buffer& out) +{ + if (a.length() != ecGamalEncryptedTotalLength || + b.length() != ecGamalEncryptedTotalLength) + return tecINTERNAL; + + secp256k1_pubkey aC1; + secp256k1_pubkey aC2; + secp256k1_pubkey bC1; + secp256k1_pubkey bC2; + + if (!makeEcPair(a, aC1, aC2) || !makeEcPair(b, bC1, bC2)) + return tecINTERNAL; + + secp256k1_pubkey diffC1; + secp256k1_pubkey diffC2; + + if (secp256k1_elgamal_subtract( + secp256k1Context(), &diffC1, &diffC2, &aC1, &aC2, &bC1, &bC2) != 1) + return tecINTERNAL; + + if (!serializeEcPair(diffC1, diffC2, out)) + return tecINTERNAL; + + return tesSUCCESS; +} + +Buffer +generateBlindingFactor() +{ + unsigned char blindingFactor[ecBlindingFactorLength]; + + // todo: might need to be updated using another RNG + if (RAND_bytes(blindingFactor, ecBlindingFactorLength) != 1) + Throw("Failed to generate random number"); + + return Buffer(blindingFactor, ecBlindingFactorLength); +} + +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; + + 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 + if (!serializeEcPair(c1, c2, buf)) + return std::nullopt; + + return buf; +} + +Buffer +encryptCanonicalZeroAmount( + Slice const& pubKeySlice, + AccountID const& account, + MPTID const& mptId) +{ + Buffer buf(ecGamalEncryptedTotalLength); + + // Allocate ciphertext placeholders + secp256k1_pubkey c1, c2; + secp256k1_pubkey pubKey; + + std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength); + + // Encrypt the amount + if (!generate_canonical_encrypted_zero( + secp256k1Context(), + &c1, + &c2, + &pubKey, + account.data(), + mptId.data())) + Throw("Failed to encrypt amount"); + + // Serialize the ciphertext pair into the buffer + if (!serializeEcPair(c1, c2, buf)) + Throw( + "Failed to serialize into 66 byte compressed format"); + + return buf; +} + +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) + return tecBAD_PROOF; + + return tesSUCCESS; +} + +TER +verifyElGamalEncryption( + std::uint64_t const amount, + Slice const& blindingFactor, + 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 + + secp256k1_pubkey pubKey; + std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength); + + secp256k1_pubkey c1, c2; + 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) + { + return tecBAD_PROOF; + } + + return tesSUCCESS; +} + +TER +verifyRevealedAmount( + std::uint64_t const amount, + Slice const& blindingFactor, + EncryptedAmountInfo const& holder, + EncryptedAmountInfo const& issuer, + std::optional const& auditor) +{ + if (auto const res = verifyElGamalEncryption( + amount, blindingFactor, holder.publicKey, holder.encryptedAmount); + !isTesSuccess(res)) + { + return res; + } + + if (auto const res = verifyElGamalEncryption( + amount, blindingFactor, issuer.publicKey, issuer.encryptedAmount); + !isTesSuccess(res)) + { + return res; + } + + if (auditor) + { + if (auto const res = verifyElGamalEncryption( + amount, + blindingFactor, + auditor->publicKey, + auditor->encryptedAmount); + !isTesSuccess(res)) + { + return res; + } + } + + return tesSUCCESS; +} + +TER +verifyClawbackEqualityProof( + uint64_t const amount, + Slice const& proof, + Slice const& pubKeySlice, + Slice const& ciphertext, + uint256 const& contextHash) +{ + secp256k1_pubkey c1, c2; + if (!makeEcPair(ciphertext, c1, c2)) + return tecINTERNAL; // LCOV_EXCL_LINE + + secp256k1_pubkey pubKey; + std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength); + + if (secp256k1_equality_plaintext_verify( + secp256k1Context(), + proof.data(), + &pubKey, + &c2, + &c1, + amount, + contextHash.data()) != 1) + { + return tecBAD_PROOF; + } + + return tesSUCCESS; +} + +std::vector +getEqualityProofs(Slice const& zkp) +{ + if (zkp.size() % ecEqualityProofLength != 0) + return {}; + auto const count = zkp.size() / ecEqualityProofLength; + + std::vector zkps; + zkps.reserve(count); + + for (size_t i = 0; i < count; ++i) + zkps.emplace_back( + zkp.data() + (i * ecEqualityProofLength), ecEqualityProofLength); + + return zkps; +} + +NotTEC +checkEncryptedAmountFormat(STObject const& object) +{ + if (object[sfHolderEncryptedAmount].length() != + ecGamalEncryptedTotalLength || + object[sfIssuerEncryptedAmount].length() != ecGamalEncryptedTotalLength) + return temBAD_CIPHERTEXT; + + bool const hasAuditor = object.isFieldPresent(sfAuditorEncryptedAmount); + if (hasAuditor && + object[sfAuditorEncryptedAmount].length() != + ecGamalEncryptedTotalLength) + return temBAD_CIPHERTEXT; + + if (!isValidCiphertext(object[sfHolderEncryptedAmount]) || + !isValidCiphertext(object[sfIssuerEncryptedAmount])) + return temBAD_CIPHERTEXT; + + if (hasAuditor && !isValidCiphertext(object[sfAuditorEncryptedAmount])) + return temBAD_CIPHERTEXT; + + return tesSUCCESS; +} + +// The following functions belong to the mpt-crypto library, +// they will be finally removed and we will use conan2 to manage the dependency. int secp256k1_elgamal_generate_keypair( secp256k1_context const* ctx, @@ -96,7 +447,6 @@ secp256k1_elgamal_generate_keypair( } // ... implementation of secp256k1_elgamal_encrypt ... - int secp256k1_elgamal_encrypt( secp256k1_context const* ctx, @@ -337,7 +687,6 @@ build_hash_input( } // The canonical encrypted zero - int generate_canonical_encrypted_zero( secp256k1_context const* ctx, @@ -724,437 +1073,177 @@ secp256k1_equality_plaintext_verify( return 1; /* Both equations passed */ } -bool -makeEcPair(Slice const& buffer, secp256k1_pubkey& out1, secp256k1_pubkey& out2) +void +build_pok_challenge( + unsigned char* e, + secp256k1_context const* ctx, + secp256k1_pubkey const* pk, + secp256k1_pubkey const* T, + unsigned char const* context_id) { - auto parsePubKey = [](Slice const& slice, secp256k1_pubkey& out) { - return secp256k1_ec_pubkey_parse( - secp256k1Context(), - &out, - reinterpret_cast(slice.data()), - slice.length()); - }; + SHA256_CTX sha; + unsigned char buf[33]; + size_t len = 33; - Slice s1{buffer.data(), ecGamalEncryptedLength}; - Slice s2{buffer.data() + ecGamalEncryptedLength, ecGamalEncryptedLength}; + SHA256_Init(&sha); + // Domain Separator from LaTeX spec + SHA256_Update(&sha, "MPT_POK_SK_REGISTER", 19); - int const ret1 = parsePubKey(s1, out1); - int const ret2 = parsePubKey(s2, out2); + secp256k1_ec_pubkey_serialize(ctx, buf, &len, pk, SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); - return ret1 == 1 && ret2 == 1; + len = 33; + secp256k1_ec_pubkey_serialize(ctx, buf, &len, T, SECP256K1_EC_COMPRESSED); + SHA256_Update(&sha, buf, 33); + + SHA256_Update(&sha, context_id, 32); + SHA256_Final(e, &sha); } -bool -serializeEcPair( - secp256k1_pubkey const& in1, - secp256k1_pubkey const& in2, - Buffer& buffer) +int +secp256k1_mpt_pok_sk_prove( + secp256k1_context const* ctx, + unsigned char* proof, + secp256k1_pubkey const* pk, + unsigned char const* sk, + unsigned char const* context_id) { - auto serializePubKey = [](secp256k1_pubkey const& pub, unsigned char* out) { - size_t outLen = ecGamalEncryptedLength; // 33 bytes - int const ret = secp256k1_ec_pubkey_serialize( - secp256k1Context(), out, &outLen, &pub, SECP256K1_EC_COMPRESSED); - return ret == 1 && outLen == ecGamalEncryptedLength; - }; + unsigned char k[32], e[32], s[32]; + secp256k1_pubkey T; - unsigned char* ptr = buffer.data(); - bool const res1 = serializePubKey(in1, ptr); - bool const res2 = serializePubKey(in2, ptr + ecGamalEncryptedLength); - - return res1 && res2; -} - -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); -} - -TER -homomorphicAdd(Slice const& a, Slice const& b, Buffer& out) -{ - if (a.length() != ecGamalEncryptedTotalLength || - b.length() != ecGamalEncryptedTotalLength) - return tecINTERNAL; - - secp256k1_pubkey aC1; - secp256k1_pubkey aC2; - secp256k1_pubkey bC1; - secp256k1_pubkey bC2; - - if (!makeEcPair(a, aC1, aC2) || !makeEcPair(b, bC1, bC2)) - return tecINTERNAL; - - secp256k1_pubkey sumC1; - secp256k1_pubkey sumC2; - - if (secp256k1_elgamal_add( - secp256k1Context(), &sumC1, &sumC2, &aC1, &aC2, &bC1, &bC2) != 1) - return tecINTERNAL; - - if (!serializeEcPair(sumC1, sumC2, out)) - return tecINTERNAL; - - return tesSUCCESS; -} - -TER -homomorphicSubtract(Slice const& a, Slice const& b, Buffer& out) -{ - if (a.length() != ecGamalEncryptedTotalLength || - b.length() != ecGamalEncryptedTotalLength) - return tecINTERNAL; - - secp256k1_pubkey aC1; - secp256k1_pubkey aC2; - secp256k1_pubkey bC1; - secp256k1_pubkey bC2; - - if (!makeEcPair(a, aC1, aC2) || !makeEcPair(b, bC1, bC2)) - return tecINTERNAL; - - secp256k1_pubkey diffC1; - secp256k1_pubkey diffC2; - - if (secp256k1_elgamal_subtract( - secp256k1Context(), &diffC1, &diffC2, &aC1, &aC2, &bC1, &bC2) != 1) - return tecINTERNAL; - - if (!serializeEcPair(diffC1, diffC2, out)) - return tecINTERNAL; - - return tesSUCCESS; -} - -TER -proveEquality( - Slice const& proof, - Slice const& encAmt, // encrypted amount - Slice const& pubkey, - uint64_t const amount, - uint256 const& txHash, // Transaction context data - std::uint32_t const spendVersion) -{ - if (proof.length() != ecEqualityProofLength) - return tecINTERNAL; - - secp256k1_pubkey c1; - secp256k1_pubkey c2; - - if (!makeEcPair(encAmt, c1, c2)) - return tecINTERNAL; - - // todo: might need to change how its hashed - Serializer s; - s.addRaw(txHash.data(), txHash.bytes); - s.add32(spendVersion); - // auto const txContextId = s.getSHA512Half(); - - // todo: support equality - // if (secp256k1_equality_verify( - // secp256k1Context(), - // reinterpret_cast(proof.data()), - // proof.length(), // Length of the proof byte array (98 bytes) - // &c1, - // &c2, - // reinterpret_cast(pubkey.data()), - // amount, - // txContextId.data(), // Transaction context data - // txContextId.bytes // Length of context data - // ) != 1) - // return tecBAD_PROOF; - - return tesSUCCESS; -} - -Buffer -generateBlindingFactor() -{ - unsigned char blindingFactor[ecBlindingFactorLength]; - - // todo: might need to be updated using another RNG - if (RAND_bytes(blindingFactor, ecBlindingFactorLength) != 1) - Throw("Failed to generate random number"); - - return Buffer(blindingFactor, ecBlindingFactorLength); -} - -Buffer -encryptAmount( - uint64_t const amt, - Slice const& pubKeySlice, - Slice const& blindingFactor) -{ - Buffer buf(ecGamalEncryptedTotalLength); - - // Allocate ciphertext placeholders - secp256k1_pubkey c1, c2; - - if (blindingFactor.size() != 32) - Throw("Random factor is not 32 bytes"); - - secp256k1_pubkey pubKey; - - std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength); - - // Encrypt the amount - if (!secp256k1_elgamal_encrypt( - secp256k1Context(), &c1, &c2, &pubKey, amt, blindingFactor.data())) - Throw("Failed to encrypt amount"); - - // Serialize the ciphertext pair into the buffer - if (!serializeEcPair(c1, c2, buf)) - Throw( - "Failed to serialize into 66 byte compressed format"); - - return buf; -} - -Buffer -encryptCanonicalZeroAmount( - Slice const& pubKeySlice, - AccountID const& account, - MPTID const& mptId) -{ - Buffer buf(ecGamalEncryptedTotalLength); - - // Allocate ciphertext placeholders - secp256k1_pubkey c1, c2; - secp256k1_pubkey pubKey; - - std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength); - - // Encrypt the amount - if (!generate_canonical_encrypted_zero( - secp256k1Context(), - &c1, - &c2, - &pubKey, - account.data(), - mptId.data())) - Throw("Failed to encrypt amount"); - - // Serialize the ciphertext pair into the buffer - if (!serializeEcPair(c1, c2, buf)) - Throw( - "Failed to serialize into 66 byte compressed format"); - - return buf; -} - -TER -verifyConfidentialSendProof( - Slice const& proof, - Slice const& encSenderBalance, - Slice const& encSenderAmt, - Slice const& encDestAmt, - Slice const& encIssuerAmt, - Slice const& senderPubKey, - Slice const& destPubKey, - Slice const& issuerPubKey, - std::uint32_t const version, - uint256 const& txHash) -{ - // if (proof.length() != ecConfidentialSendProofLength) - // return tecINTERNAL; - - secp256k1_pubkey balC1, balC2; - if (!makeEcPair(encSenderBalance, balC1, balC2)) - return tecINTERNAL; - - secp256k1_pubkey senderC1, senderC2; - if (!makeEcPair(encSenderAmt, senderC1, senderC2)) - return tecINTERNAL; - - secp256k1_pubkey destC1, destC2; - if (!makeEcPair(encDestAmt, destC1, destC2)) - return tecINTERNAL; - - secp256k1_pubkey issuerC1, issuerC2; - if (!makeEcPair(encIssuerAmt, issuerC1, issuerC2)) - return tecINTERNAL; - - Serializer s; - s.addRaw(txHash.data(), txHash.bytes); - s.add32(version); - // auto const txContextId = s.getSHA512Half(); - - // todo: equality and range proof verification - // if (secp256k1_equal_range_verify( - // secp256k1Context(), - // reinterpret_cast(proof.data()), - // proof.length(), - // txContextId.data(), - // &balC1, - // &balC2, - // &senderC1, - // &senderC2, - // reinterpret_cast(senderPubKey.data()), - // &destC1, - // &destC2, - // reinterpret_cast(destPubKey.data()), - // &issuerC1, - // &issuerC2, - // reinterpret_cast(issuerPubKey.data()), - // txContextId.data(), - // txContextId.bytes) != 1) - // return tecBAD_PROOF; - - return tesSUCCESS; -} - -TER -verifyEqualityProof( - uint64_t const amount, - Slice const& proof, - Slice const& pubKeySlice, - Slice const& ciphertext, - uint256 const& contextHash) -{ - secp256k1_pubkey c1, c2; - if (!makeEcPair(ciphertext, c1, c2)) - return tecINTERNAL; // LCOV_EXCL_LINE - - secp256k1_pubkey pubKey; - std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength); - - if (secp256k1_equality_plaintext_verify( - secp256k1Context(), - proof.data(), - &c1, - &c2, - &pubKey, - amount, - contextHash.data()) != 1) + // 1. Sample k and T = kG + do { - return tecBAD_PROOF; + if (RAND_bytes(k, 32) != 1) + return 0; + } while (!secp256k1_ec_seckey_verify(ctx, k)); + + if (!secp256k1_ec_pubkey_create(ctx, &T, k)) + return 0; + + // 2. Challenge e + build_pok_challenge(e, ctx, pk, &T, context_id); + + // 3. Response s = k + e*sk (mod n) + memcpy(s, sk, 32); + if (!secp256k1_ec_seckey_tweak_mul(ctx, s, e)) + return 0; + if (!secp256k1_ec_seckey_tweak_add(ctx, s, k)) + return 0; + + // 4. Serialize Proof: T (33 bytes) || s (32 bytes) + size_t clen = 33; + secp256k1_ec_pubkey_serialize( + ctx, proof, &clen, &T, SECP256K1_EC_COMPRESSED); + memcpy(proof + 33, s, 32); + + return 1; +} + +int +secp256k1_mpt_pok_sk_verify( + secp256k1_context const* ctx, + unsigned char const* proof, + secp256k1_pubkey const* pk, + unsigned char const* context_id) +{ + secp256k1_pubkey T, lhs, rhs, ePk; + unsigned char e[32], s[32]; + + // 1. Parse T and s + if (!secp256k1_ec_pubkey_parse(ctx, &T, proof, 33)) + return 0; + memcpy(s, proof + 33, 32); + + // 2. Challenge e + build_pok_challenge(e, ctx, pk, &T, context_id); + + // 3. Verify sG = T + ePk + // LHS = s*G + if (!secp256k1_ec_pubkey_create(ctx, &lhs, s)) + return 0; + + // RHS = T + e*Pk + ePk = *pk; + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &ePk, e)) + return 0; + + secp256k1_pubkey const* addends[2] = {&T, &ePk}; + if (!secp256k1_ec_pubkey_combine(ctx, &rhs, addends, 2)) + return 0; + + // 4. Compare serialized points + unsigned char ser_lhs[33], ser_rhs[33]; + size_t clen = 33; + secp256k1_ec_pubkey_serialize( + ctx, ser_lhs, &clen, &lhs, SECP256K1_EC_COMPRESSED); + clen = 33; + secp256k1_ec_pubkey_serialize( + ctx, ser_rhs, &clen, &rhs, SECP256K1_EC_COMPRESSED); + + return memcmp(ser_lhs, ser_rhs, 33) == 0; +} + +int +secp256k1_elgamal_verify_encryption( + secp256k1_context const* ctx, + secp256k1_pubkey const* c1, + secp256k1_pubkey const* c2, + secp256k1_pubkey const* pubkey_Q, + uint64_t amount, + unsigned char const* blinding_factor) +{ + secp256k1_pubkey expected_c1, mG, s_shared, expected_c2; + unsigned char amount_scalar[32] = {0}; + unsigned char ser1[33], ser2[33]; + size_t len = 33; + + if (secp256k1_ec_pubkey_create(ctx, &expected_c1, blinding_factor) != 1) + { + return 0; } - return tesSUCCESS; -} + secp256k1_ec_pubkey_serialize(ctx, ser1, &len, c1, SECP256K1_EC_COMPRESSED); + len = 33; + secp256k1_ec_pubkey_serialize( + ctx, ser2, &len, &expected_c1, SECP256K1_EC_COMPRESSED); + if (memcmp(ser1, ser2, 33) != 0) + return 0; -TER -verifyClawbackEqualityProof( - uint64_t const amount, - Slice const& proof, - Slice const& pubKeySlice, - Slice const& ciphertext, - uint256 const& contextHash) -{ - secp256k1_pubkey c1, c2; - if (!makeEcPair(ciphertext, c1, c2)) - return tecINTERNAL; // LCOV_EXCL_LINE + // Calculate Shared Secret S = k * Q + s_shared = *pubkey_Q; + if (secp256k1_ec_pubkey_tweak_mul(ctx, &s_shared, blinding_factor) != 1) + return 0; - secp256k1_pubkey pubKey; - std::memcpy(pubKey.data, pubKeySlice.data(), ecPubKeyLength); - - if (secp256k1_equality_plaintext_verify( - secp256k1Context(), - proof.data(), - &pubKey, - &c2, - &c1, - amount, - contextHash.data()) != 1) + // Compare C2 + if (amount == 0) { - return tecBAD_PROOF; + expected_c2 = s_shared; + } + else + { + for (int i = 0; i < 8; ++i) + { + amount_scalar[31 - i] = (amount >> (i * 8)) & 0xFF; + } + if (secp256k1_ec_pubkey_create(ctx, &mG, amount_scalar) != 1) + return 0; + + // Combine M + S + secp256k1_pubkey const* pts[2] = {&mG, &s_shared}; + if (secp256k1_ec_pubkey_combine(ctx, &expected_c2, pts, 2) != 1) + return 0; } - return tesSUCCESS; -} + len = 33; + secp256k1_ec_pubkey_serialize(ctx, ser1, &len, c2, SECP256K1_EC_COMPRESSED); + len = 33; + secp256k1_ec_pubkey_serialize( + ctx, ser2, &len, &expected_c2, SECP256K1_EC_COMPRESSED); + if (memcmp(ser1, ser2, 33) != 0) + return 0; -std::vector -getEqualityProofs(Slice const& zkp) -{ - if (zkp.size() % ecEqualityProofLength != 0) - return {}; - auto const count = zkp.size() / ecEqualityProofLength; - - std::vector zkps; - zkps.reserve(count); - - for (size_t i = 0; i < count; ++i) - zkps.emplace_back( - zkp.data() + (i * ecEqualityProofLength), ecEqualityProofLength); - - return zkps; -} - -NotTEC -checkEncryptedAmountFormat(STObject const& object) -{ - if (object[sfHolderEncryptedAmount].length() != - ecGamalEncryptedTotalLength || - object[sfIssuerEncryptedAmount].length() != ecGamalEncryptedTotalLength) - return temBAD_CIPHERTEXT; - - bool const hasAuditor = object.isFieldPresent(sfAuditorEncryptedAmount); - if (hasAuditor && - object[sfAuditorEncryptedAmount].length() != - ecGamalEncryptedTotalLength) - return temBAD_CIPHERTEXT; - - if (!isValidCiphertext(object[sfHolderEncryptedAmount]) || - !isValidCiphertext(object[sfIssuerEncryptedAmount])) - return temBAD_CIPHERTEXT; - - if (hasAuditor && !isValidCiphertext(object[sfAuditorEncryptedAmount])) - return temBAD_CIPHERTEXT; - - return tesSUCCESS; -} - -TER -verifyEqualityProofs( - std::uint64_t amount, - std::vector const& zkps, - EncryptedAmountInfo const& holder, - EncryptedAmountInfo const& issuer, - std::optional const& auditor, - uint256 const& contextHash) -{ - // Sanity check: Ensure we have enough proofs - size_t const required = getEqualityProofSize(auditor.has_value()); - if (zkps.size() != required) - return tecINTERNAL; // LCOV_EXCL_LINE - - // 1. Verify Holder Proof (Index 0) - if (!isTesSuccess(verifyEqualityProof( - amount, - zkps[0], - holder.publicKey, - holder.encryptedAmount, - contextHash))) - return tecBAD_PROOF; - - // 2. Verify Issuer Proof (Index 1) - if (!isTesSuccess(verifyEqualityProof( - amount, - zkps[1], - issuer.publicKey, - issuer.encryptedAmount, - contextHash))) - return tecBAD_PROOF; - - // 3. Verify Auditor Proof (Index 2) - if applicable - if (auditor) - { - if (!isTesSuccess(verifyEqualityProof( - amount, - zkps[2], - auditor->publicKey, - auditor->encryptedAmount, - contextHash))) - return tecBAD_PROOF; - } - - return tesSUCCESS; + return 1; // Success: Encryption is valid } } // namespace ripple diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index 4ac2bdaeea..ab6019e578 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -35,9 +35,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); @@ -86,9 +84,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(auditor); @@ -135,9 +131,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .flags = tfMPTCanTransfer | tfMPTCanLock}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); @@ -166,9 +160,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .flags = tfMPTCanTransfer | tfMPTCanLock}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); @@ -179,6 +171,14 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .holderPubKey = mptAlice.getPubKey(bob), .err = temMALFORMED}); + // blinding factor length is invalid + mptAlice.convert( + {.account = alice, + .amt = 10, + .holderPubKey = mptAlice.getPubKey(bob), + .blindingFactor = Buffer(10), + .err = temMALFORMED}); + mptAlice.convert( {.account = bob, .amt = 10, @@ -221,14 +221,89 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .amt = 10, .holderPubKey = Buffer{}, .err = temMALFORMED}); + } - // todo: change to to check proof size - // mptAlice.convert( - // {.account = bob, - // .amt = 10, - // .proof = "123", - // .holderPubKey = mptAlice.getPubKey(bob), - // .err = temMALFORMED}); + // when registering holder pub key, the transaction must include a + // Schnorr proof of knowledge for the corresponding secret key + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); + + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 100); + + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); + + mptAlice.set( + {.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)}); + + mptAlice.convert( + {.account = bob, + .amt = 10, + .fillSchnorrProof = false, + .holderPubKey = mptAlice.getPubKey(bob), + .err = temMALFORMED}); + + mptAlice.convert( + {.account = bob, + .amt = 0, + .fillSchnorrProof = false, + .holderPubKey = mptAlice.getPubKey(bob), + .err = temMALFORMED}); + + // proof length is invalid + mptAlice.convert( + {.account = bob, + .amt = 10, + .proof = std::string(10, 'A'), + .holderPubKey = mptAlice.getPubKey(bob), + .err = temMALFORMED}); + } + + // when holder pub key already registered, Schnorr proof must not be + // provided + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); + + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 100); + + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); + + mptAlice.set( + {.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)}); + + // this will register bob's pub key, + // and convert 10 to confidential balance + mptAlice.convert({ + .account = bob, + .amt = 10, + .holderPubKey = mptAlice.getPubKey(bob), + }); + + // proof must not be provided after pub key was registered + mptAlice.convert( + {.account = bob, + .amt = 20, + .fillSchnorrProof = true, + .err = temMALFORMED}); } } @@ -245,14 +320,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite MPTTester mptAlice(env, alice, {.holders = {bob}}); mptAlice.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + {.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanLock}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); @@ -272,13 +343,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); @@ -298,14 +366,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // no tfMPTCanPrivacy flag enabled mptAlice.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + {.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanLock}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); @@ -331,14 +395,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite MPTTester mptAlice(env, alice, {.holders = {bob}}); mptAlice.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + {.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanLock}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); @@ -359,13 +419,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); @@ -386,7 +443,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -414,7 +470,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.generateKeyPair(alice); @@ -439,13 +494,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); @@ -470,13 +522,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); @@ -507,13 +556,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); @@ -549,15 +595,12 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTRequireAuth | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = alice, .holder = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); @@ -589,7 +632,34 @@ class ConfidentialTransfer_test : public beast::unit_test::suite }); } - // todo: test well formed proof + // invalid proof when registering holder pub key + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); + + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 100); + + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); + + mptAlice.set( + {.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)}); + + mptAlice.convert( + {.account = bob, + .amt = 10, + .proof = std::string(ecSchnorrProofLength * 2, 'A'), + .holderPubKey = mptAlice.getPubKey(bob), + .err = tecBAD_PROOF}); + } } void @@ -604,13 +674,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); @@ -642,13 +709,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); @@ -714,9 +778,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .flags = tfMPTCanTransfer | tfMPTCanLock}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); @@ -757,9 +819,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); @@ -1006,7 +1066,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create(); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = carol}); - env.close(); mptAlice.send( {.account = bob, @@ -1044,7 +1103,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite {.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)}); mptAlice.pay(alice, bob, 100); mptAlice.pay(alice, carol, 50); - env.close(); // issuer can not be the same as sender mptAlice.send( @@ -1179,7 +1237,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // fund bob, carol (not dave or eve) mptAlice.pay(alice, bob, 100); mptAlice.pay(alice, carol, 50); - env.close(); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); @@ -1187,7 +1244,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.generateKeyPair(dave); mptAlice.set( {.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)}); - env.close(); // bob and carol convert some funds to confidential mptAlice.convert( @@ -1228,7 +1284,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // destroy the issuance mptAlice.destroy(); - env.close(); Json::Value jv; jv[jss::Account] = bob.human(); @@ -1455,13 +1510,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); @@ -1497,9 +1549,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = carol}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); @@ -1541,9 +1591,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - - env.close(); - mptAlice.generateKeyPair(alice); mptAlice.set( @@ -1573,13 +1620,9 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - - env.close(); - mptAlice.generateKeyPair(alice); mptAlice.set( @@ -1620,9 +1663,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); @@ -1665,9 +1706,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(auditor); @@ -1713,9 +1752,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .flags = tfMPTCanTransfer | tfMPTCanLock}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); @@ -1736,9 +1773,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); @@ -1768,6 +1803,13 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .amt = maxMPTokenAmount + 1, .err = temBAD_AMOUNT}); + // invalid blinding factor length + mptAlice.convertBack( + {.account = alice, + .amt = 30, + .blindingFactor = Buffer{}, + .err = temMALFORMED}); + mptAlice.convertBack( {.account = bob, .amt = 30, @@ -1840,9 +1882,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .flags = tfMPTCanTransfer | tfMPTCanLock}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); @@ -1886,9 +1926,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); @@ -1916,10 +1954,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = carol}); - env.close(); mptAlice.pay(alice, bob, 100); mptAlice.pay(alice, carol, 100); - env.close(); mptAlice.generateKeyPair(alice); @@ -1964,9 +2000,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = alice, .holder = bob}); - env.close(); mptAlice.pay(alice, bob, 100); - env.close(); mptAlice.generateKeyPair(alice); @@ -2352,7 +2386,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create(); mptAlice.authorize({.account = bob}); - env.close(); mptAlice.confidentialClaw( {.account = alice, @@ -2385,7 +2418,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite {.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)}); mptAlice.pay(alice, bob, 100); mptAlice.pay(alice, carol, 50); - env.close(); // only issuer can clawback mptAlice.confidentialClaw( @@ -2520,7 +2552,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.generateKeyPair(alice); mptAlice.set( {.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)}); - env.close(); mptAlice.confidentialClaw( {.account = alice, @@ -2538,7 +2569,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create({.flags = tfMPTCanClawback | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.generateKeyPair(alice); - env.close(); mptAlice.confidentialClaw( {.account = alice, @@ -2561,7 +2591,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // destroy the issuance mptAlice.destroy(); - env.close(); Json::Value jv; jv[jss::Account] = alice.human(); diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 4ffa57dbcf..fa04ec55b8 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -140,8 +140,7 @@ MPTTester::MPTTester(MPTInitDef const& arg) { } -MPTTester:: -operator MPT() const +MPTTester::operator MPT() const { if (!id_) Throw("MPT has not been created"); @@ -695,8 +694,7 @@ MPTTester::mpt(std::int64_t amount) const return ripple::test::jtx::MPT(issuer_.name(), *id_)(amount); } -MPTTester:: -operator Asset() const +MPTTester::operator Asset() const { if (!id_) Throw("MPT has not been created"); @@ -805,122 +803,30 @@ MPTTester::getClawbackProof( } Buffer -MPTTester::generateEqualityZKP( - Account const& holder, - std::uint64_t amount, - uint256 const& ctxHash, - Buffer const& holderCiphertext, - Buffer const& issuerCiphertext, - std::optional const& auditorCiphertext, - Buffer const& blindingFactor) const +MPTTester::getSchnorrProof(Account const& account, uint256 const& ctxHash) const { - if (!id_) - Throw("MPT has not been created"); + auto const pubKey = getPubKey(account); + auto const privKey = getPrivKey(account); - auto const sleHolder = env_.le(keylet::mptoken(*id_, holder.id())); - auto const sleIssuance = env_.le(keylet::mptIssuance(*id_)); + if (pubKey.size() != ecPubKeyLength) + Throw("Invalid public key size"); - size_t const zkpSize = auditorCiphertext ? 3 : 2; - size_t const zkpByteLength = zkpSize * ecEqualityProofLength; + secp256k1_pubkey pk; + std::memcpy(pk.data, pubKey.data(), ecPubKeyLength); - if (!sleHolder || !sleIssuance || holderCiphertext.size() == 0 || - issuerCiphertext.size() == 0) - return Buffer(zkpByteLength); + Buffer proof(ecSchnorrProofLength); - auto const generateProof = [amount, ctxHash]( - Buffer const& ciphertext, - Buffer const& pubKey, - Buffer const& blindingFactor) { - secp256k1_pubkey c1, c2; - auto const ctx = secp256k1Context(); - if (!secp256k1_ec_pubkey_parse( - ctx, &c1, ciphertext.data(), ecGamalEncryptedLength) || - !secp256k1_ec_pubkey_parse( - ctx, - &c2, - ciphertext.data() + ecGamalEncryptedLength, - ecGamalEncryptedLength)) - { - return Buffer(ecEqualityProofLength); - } - - secp256k1_pubkey pk; - std::memcpy(pk.data, pubKey.data(), ecPubKeyLength); - Buffer proof(ecEqualityProofLength); - - if (secp256k1_equality_plaintext_prove( - ctx, - proof.data(), - &c1, - &c2, - &pk, - amount, - blindingFactor.data(), - ctxHash.data()) != 1) - { - Throw("Proof generation failed"); - } - return proof; - }; - - Buffer zkp(zkpByteLength); - - Buffer holderZkp = - generateProof(holderCiphertext, getPubKey(holder), blindingFactor); - - Buffer issuerZkp = - generateProof(issuerCiphertext, getPubKey(issuer_), blindingFactor); - - // Pointer arithmetic to copy data into place - std::uint8_t* ptr = zkp.data(); - - // Copy Holder - std::memcpy(ptr, holderZkp.data(), holderZkp.size()); - ptr += holderZkp.size(); - - // Copy Issuer - std::memcpy(ptr, issuerZkp.data(), issuerZkp.size()); - ptr += issuerZkp.size(); - - if (auditorCiphertext) + if (secp256k1_mpt_pok_sk_prove( + secp256k1Context(), + proof.data(), + &pk, + privKey.data(), + ctxHash.data()) != 1) { - Buffer auditorZkp(ecEqualityProofLength); - - if (sleIssuance->isFieldPresent(sfAuditorElGamalPublicKey)) - { - Buffer const auditorPubKey( - sleIssuance->getFieldVL(sfAuditorElGamalPublicKey).data(), - sleIssuance->getFieldVL(sfAuditorElGamalPublicKey).size()); - auditorZkp = generateProof( - *auditorCiphertext, auditorPubKey, blindingFactor); - } - - // Copy auditor - std::memcpy(ptr, auditorZkp.data(), auditorZkp.size()); - ptr += auditorZkp.size(); + Throw("Schnorr Proof generation failed"); } - return zkp; -} - -Buffer -MPTTester::getConvertProof( - Account const& holder, - std::uint64_t amount, - uint256 const& ctxHash, - Buffer const& holderCiphertext, - Buffer const& issuerCiphertext, - std::optional const& auditorCiphertext, - Buffer const& blindingFactor) const -{ - return generateEqualityZKP( - holder, - amount, - ctxHash, - holderCiphertext, - issuerCiphertext, - auditorCiphertext, - blindingFactor); + return proof; } Buffer @@ -933,18 +839,9 @@ MPTTester::getConvertBackProof( std::optional const& auditorCiphertext, Buffer const& blindingFactor) const { - Buffer const equalityZkp = generateEqualityZKP( - holder, - amount, - ctxHash, - holderCiphertext, - issuerCiphertext, - auditorCiphertext, - blindingFactor); - // todo: incoporate pederson and range proof - return equalityZkp; + return Buffer{}; } std::optional @@ -1086,23 +983,20 @@ MPTTester::convert(MPTConvert const& arg) auditorCiphertext, blindingFactor); + jv[sfBlindingFactor.jsonName] = strHex(blindingFactor); if (arg.proof) jv[sfZKProof.jsonName] = *arg.proof; - else + else if (arg.fillSchnorrProof.value_or(arg.holderPubKey.has_value())) { - // if the caller generated ciphertexts themselves, they should also - // generate the proof themselves from the blinding factor - uint256 const ctxHash = getConvertContextHash( + // whether to automatically generate and attach a Schnorr proof: + // if fillSchnorrProof is explicitly set, follow its value; + // otherwise, default to generating the proof only if holder pub key is + // present. + auto const ctxHash = getConvertContextHash( arg.account->id(), env_.seq(*arg.account), *id_, *arg.amt); - Buffer proof = getConvertProof( - *arg.account, - *arg.amt, - ctxHash, - holderCiphertext, - issuerCiphertext, - auditorCiphertext, - blindingFactor); - jv[sfZKProof] = strHex(proof); + + Buffer proof = getSchnorrProof(*arg.account, ctxHash); + jv[sfZKProof.jsonName] = strHex(proof); } auto const holderAmt = getBalance(*arg.account); @@ -1473,7 +1367,15 @@ MPTTester::encryptAmount( uint64_t const amt, Buffer const& blindingFactor) const { - return ripple::encryptAmount(amt, getPubKey(account), blindingFactor); + auto const result = + ripple::encryptAmount(amt, getPubKey(account), blindingFactor); + + if (result) + return *result; + + // Return a dummy buffer on failure to allow testing of + // failures that occur prior to encryption. + return Buffer(ecGamalEncryptedTotalLength); } uint64_t @@ -1634,6 +1536,8 @@ MPTTester::convertBack(MPTConvertBack const& arg) auditorCiphertext, blindingFactor); + jv[sfBlindingFactor] = strHex(blindingFactor); + if (arg.proof) jv[sfZKProof.jsonName] = *arg.proof; else diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index 4e4a386485..6ef4fed80a 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -176,10 +176,17 @@ struct MPTConvert std::optional id = std::nullopt; std::optional amt = std::nullopt; std::optional proof = std::nullopt; + + // indicates whether to autofill schnorr proof. + // default : auto generate proof if holderPubKey is present. + // true: force proof generation. + // false: force proof omission. + std::optional fillSchnorrProof = std::nullopt; std::optional holderPubKey = std::nullopt; std::optional holderEncryptedAmt = std::nullopt; std::optional issuerEncryptedAmt = std::nullopt; std::optional auditorEncryptedAmt = std::nullopt; + // not an txn param, only used for autofilling std::optional blindingFactor = std::nullopt; std::optional ownerCount = std::nullopt; @@ -447,24 +454,7 @@ public: uint256 const& txHash) const; Buffer - generateEqualityZKP( - Account const& holder, - std::uint64_t amount, - uint256 const& ctxHash, - Buffer const& holderCiphertext, - Buffer const& issuerCiphertext, - std::optional const& auditorCiphertext, - Buffer const& blindingFactor) const; - - Buffer - getConvertProof( - Account const& holder, - std::uint64_t amount, - uint256 const& ctxHash, - Buffer const& holderCiphertext, - Buffer const& issuerCiphertext, - std::optional const& auditorCiphertext, - Buffer const& blindingFactor) const; + getSchnorrProof(Account const& account, uint256 const& ctxHash) const; Buffer getConvertBackProof( diff --git a/src/xrpld/app/tx/detail/ConfidentialConvert.cpp b/src/xrpld/app/tx/detail/ConfidentialConvert.cpp index efe43f828a..fabfc0c61d 100644 --- a/src/xrpld/app/tx/detail/ConfidentialConvert.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialConvert.cpp @@ -21,19 +21,37 @@ ConfidentialConvert::preflight(PreflightContext const& ctx) if (MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer() == ctx.tx[sfAccount]) return temMALFORMED; - if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res)) - return res; - if (ctx.tx[sfMPTAmount] > maxMPTokenAmount) return temBAD_AMOUNT; - if (ctx.tx.isFieldPresent(sfHolderElGamalPublicKey) && - ctx.tx[sfHolderElGamalPublicKey].length() != ecPubKeyLength) + if (ctx.tx[sfBlindingFactor].size() != ecBlindingFactorLength) return temMALFORMED; - if (ctx.tx[sfZKProof].size() != - getEqualityProofLength(ctx.tx.isFieldPresent(sfAuditorEncryptedAmount))) - return temMALFORMED; + if (ctx.tx.isFieldPresent(sfHolderElGamalPublicKey)) + { + if (ctx.tx[sfHolderElGamalPublicKey].length() != ecPubKeyLength) + return temMALFORMED; + + // proof of knowledge of the secret key corresponding to the provided + // public key is needed when holder ec public key is being set. + if (!ctx.tx.isFieldPresent(sfZKProof)) + return temMALFORMED; + + // verify schnorr proof length when registerring holder ec public key + if (ctx.tx[sfZKProof].size() != ecSchnorrProofLength) + return temMALFORMED; + } + else + { + // zkp should not be present if public key was already set + if (ctx.tx.isFieldPresent(sfZKProof)) + return temMALFORMED; + } + + // check encrypted amount format after the above basic checks + // this check is more expensive so put it at the end + if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res)) + return res; return tesSUCCESS; } @@ -41,9 +59,12 @@ ConfidentialConvert::preflight(PreflightContext const& ctx) TER ConfidentialConvert::preclaim(PreclaimContext const& ctx) { + auto const account = ctx.tx[sfAccount]; + auto const issuanceID = ctx.tx[sfMPTokenIssuanceID]; + auto const amount = ctx.tx[sfMPTAmount]; + // ensure that issuance exists - auto const sleIssuance = - ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID])); + auto const sleIssuance = ctx.view.read(keylet::mptIssuance(issuanceID)); if (!sleIssuance) return tecOBJECT_NOT_FOUND; @@ -52,7 +73,7 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx) // already checked in preflight, but should also check that issuer on the // issuance isn't the account either - if (sleIssuance->getAccountID(sfIssuer) == ctx.tx[sfAccount]) + if (sleIssuance->getAccountID(sfIssuer) == account) return tefINTERNAL; // LCOV_EXCL_LINE // issuer has not uploaded their pub key yet @@ -73,18 +94,16 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx) if (!requiresAuditor && hasAuditor) return tecNO_PERMISSION; - auto const sleMptoken = ctx.view.read( - keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], ctx.tx[sfAccount])); + auto const sleMptoken = ctx.view.read(keylet::mptoken(issuanceID, account)); if (!sleMptoken) return tecOBJECT_NOT_FOUND; - auto const mptIssue = MPTIssue{ctx.tx[sfMPTokenIssuanceID]}; + auto const mptIssue = MPTIssue{issuanceID}; STAmount const mptAmount = STAmount( - MPTAmount{static_cast(ctx.tx[sfMPTAmount])}, - mptIssue); + MPTAmount{static_cast(amount)}, mptIssue); if (accountHolds( ctx.view, - ctx.tx[sfAccount], + account, mptIssue, FreezeHandling::fhZERO_IF_FROZEN, AuthHandling::ahZERO_IF_UNAUTHORIZED, @@ -103,38 +122,42 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx) ctx.tx.isFieldPresent(sfHolderElGamalPublicKey)) return tecDUPLICATE; - auto const holderPubKey = ctx.tx.isFieldPresent(sfHolderElGamalPublicKey) - ? ctx.tx[sfHolderElGamalPublicKey] - : (*sleMptoken)[sfHolderElGamalPublicKey]; + Slice holderPubKey; + if (ctx.tx.isFieldPresent(sfHolderElGamalPublicKey)) + { + holderPubKey = ctx.tx[sfHolderElGamalPublicKey]; - auto const contextHash = getConvertContextHash( - ctx.tx[sfAccount], - ctx.tx[sfSequence], - ctx.tx[sfMPTokenIssuanceID], - ctx.tx[sfMPTAmount]); + auto const contextHash = getConvertContextHash( + account, ctx.tx[sfSequence], issuanceID, amount); - std::vector const zkps = getEqualityProofs(ctx.tx[sfZKProof]); + // when register new pk, verify through schnorr proof + if (!isTesSuccess(verifySchnorrProof( + holderPubKey, ctx.tx[sfZKProof], contextHash))) + { + return tecBAD_PROOF; + } + } + else + { + holderPubKey = (*sleMptoken)[sfHolderElGamalPublicKey]; + } // Prepare Auditor Info std::optional auditor; if (hasAuditor) { - auditor.emplace( - EncryptedAmountInfo{ - (*sleIssuance)[sfAuditorElGamalPublicKey], - ctx.tx[sfAuditorEncryptedAmount]}); + auditor.emplace(EncryptedAmountInfo{ + (*sleIssuance)[sfAuditorElGamalPublicKey], + ctx.tx[sfAuditorEncryptedAmount]}); } - return verifyEqualityProofs( - ctx.tx[sfMPTAmount], - zkps, - EncryptedAmountInfo{ - holderPubKey, ctx.tx[sfHolderEncryptedAmount]}, // Holder - EncryptedAmountInfo{ - (*sleIssuance)[sfIssuerElGamalPublicKey], - ctx.tx[sfIssuerEncryptedAmount]}, // Issuer - auditor, - contextHash); + return verifyRevealedAmount( + amount, + ctx.tx[sfBlindingFactor], + {holderPubKey, ctx.tx[sfHolderEncryptedAmount]}, + {(*sleIssuance)[sfIssuerElGamalPublicKey], + ctx.tx[sfIssuerEncryptedAmount]}, + auditor); } TER @@ -219,20 +242,16 @@ ConfidentialConvert::doApply() if (auditorEc) (*sleMptoken)[sfAuditorEncryptedBalance] = *auditorEc; - try - { - // encrypt sfConfidentialBalanceSpending with zero balance - Buffer out; - out = encryptAmount( - 0, - (*sleMptoken)[sfHolderElGamalPublicKey], - generateBlindingFactor()); - (*sleMptoken)[sfConfidentialBalanceSpending] = out; - } - catch (std::exception const& e) - { + // encrypt sfConfidentialBalanceSpending with zero balance + auto const zeroBalance = encryptAmount( + 0, + (*sleMptoken)[sfHolderElGamalPublicKey], + generateBlindingFactor()); + + if (!zeroBalance) return tecINTERNAL; // LCOV_EXCL_LINE - } + + (*sleMptoken)[sfConfidentialBalanceSpending] = *zeroBalance; } else { diff --git a/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp b/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp index 1f556d66d4..037186708a 100644 --- a/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp @@ -28,16 +28,21 @@ ConfidentialConvertBack::preflight(PreflightContext const& ctx) if (!ctx.rules.enabled(featureConfidentialTransfer)) return temDISABLED; - // issuer cannot convert + // issuer cannot convert back if (MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer() == ctx.tx[sfAccount]) return temMALFORMED; - if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res)) - return res; - if (ctx.tx[sfMPTAmount] == 0 || ctx.tx[sfMPTAmount] > maxMPTokenAmount) return temBAD_AMOUNT; + if (ctx.tx[sfBlindingFactor].size() != ecBlindingFactorLength) + return temMALFORMED; + + // check encrypted amount format after the above basic checks + // this check is more expensive so put it at the end + if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res)) + return res; + return tesSUCCESS; } @@ -47,47 +52,43 @@ verifyProofs( std::shared_ptr const& issuance, std::shared_ptr const& mptoken) { - if (expectedProofLength(issuance) != tx[sfZKProof].size()) - return tecBAD_PROOF; + if (!mptoken->isFieldPresent(sfHolderElGamalPublicKey)) + return tecINTERNAL; // LCOV_EXCL_LINE - auto const mptIssuanceID = tx[sfMPTokenIssuanceID]; - auto const account = tx[sfAccount]; + // auto const mptIssuanceID = tx[sfMPTokenIssuanceID]; + // auto const account = tx[sfAccount]; auto const amount = tx[sfMPTAmount]; + auto const blindingFactor = tx[sfBlindingFactor]; + auto const holderPubKey = (*mptoken)[sfHolderElGamalPublicKey]; - auto const contextHash = getConvertBackContextHash( - account, - tx[sfSequence], - mptIssuanceID, - amount, - (*mptoken)[~sfConfidentialBalanceVersion].value_or(0)); + // todo: commented out for now, will use for range proof + // auto const contextHash = getConvertBackContextHash( + // account, + // tx[sfSequence], + // mptIssuanceID, + // amount, + // (*mptoken)[~sfConfidentialBalanceVersion].value_or(0)); // Prepare Auditor Info std::optional auditor; bool const hasAuditor = issuance->isFieldPresent(sfAuditorElGamalPublicKey); if (hasAuditor) { - auditor.emplace( - EncryptedAmountInfo{ - (*issuance)[sfAuditorElGamalPublicKey], - tx[sfAuditorEncryptedAmount]}); + auditor.emplace(EncryptedAmountInfo{ + (*issuance)[sfAuditorElGamalPublicKey], + tx[sfAuditorEncryptedAmount]}); } - // verify equality proofs - { - auto const equalityZkps = getEqualityProofs( - Slice{tx[sfZKProof].data(), getEqualityProofLength(hasAuditor)}); - - return verifyEqualityProofs( + if (auto const ter = verifyRevealedAmount( amount, - equalityZkps, - EncryptedAmountInfo{ - (*mptoken)[sfHolderElGamalPublicKey], - tx[sfHolderEncryptedAmount]}, // Holder - EncryptedAmountInfo{ - (*issuance)[sfIssuerElGamalPublicKey], - tx[sfIssuerEncryptedAmount]}, // Issuer - auditor, // Optional auditor - contextHash); + blindingFactor, + {holderPubKey, tx[sfHolderEncryptedAmount]}, + {(*issuance)[sfIssuerElGamalPublicKey], + tx[sfIssuerEncryptedAmount]}, + auditor); + !isTesSuccess(ter)) + { + return ter; } return tesSUCCESS;