diff --git a/include/xrpl/protocol/ConfidentialTransfer.h b/include/xrpl/protocol/ConfidentialTransfer.h index 1494497a8e..da257e2bd7 100644 --- a/include/xrpl/protocol/ConfidentialTransfer.h +++ b/include/xrpl/protocol/ConfidentialTransfer.h @@ -34,6 +34,13 @@ getClawbackContextHash( std::uint64_t amount, AccountID const& holder); +uint256 +getConvertContextHash( + AccountID const& account, + std::uint32_t sequence, + uint192 const& issuanceID, + std::uint64_t amount); + /** * @brief Generates a new secp256k1 key pair. */ @@ -228,7 +235,8 @@ proveEquality( uint256 const& txHash, // Transaction context data std::uint32_t const spendVersion); -Buffer +// returns ciphertext and the blinding factor used +std::pair encryptAmount(uint64_t amt, Slice const& pubKeySlice); Buffer @@ -258,6 +266,17 @@ verifyEqualityProof( 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); + } // namespace ripple #endif diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index ed9bab0e36..6f98160c03 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -399,6 +399,7 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({ {sfDomainID, soeOPTIONAL}, {sfMutableFlags, soeDEFAULT}, {sfIssuerElGamalPublicKey, soeOPTIONAL}, + {sfAuditorElGamalPublicKey, soeOPTIONAL}, {sfConfidentialOutstandingAmount, soeDEFAULT}, })) diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 23db45ae45..f844d6bcae 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -309,6 +309,9 @@ TYPED_SFIELD(sfHolderEncryptedAmount, VL, 38) TYPED_SFIELD(sfIssuerEncryptedAmount, VL, 39) TYPED_SFIELD(sfSenderEncryptedAmount, VL, 40) TYPED_SFIELD(sfDestinationEncryptedAmount, VL, 41) +TYPED_SFIELD(sfAuditorEncryptedBalance, VL, 42) +TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43) +TYPED_SFIELD(sfAuditorElGamalPublicKey, VL, 44) // account (common) TYPED_SFIELD(sfAccount, ACCOUNT, 1) diff --git a/src/libxrpl/protocol/ConfidentialTransfer.cpp b/src/libxrpl/protocol/ConfidentialTransfer.cpp index b9bae2ab1c..89178a0dda 100644 --- a/src/libxrpl/protocol/ConfidentialTransfer.cpp +++ b/src/libxrpl/protocol/ConfidentialTransfer.cpp @@ -39,6 +39,20 @@ getClawbackContextHash( return s.getSHA512Half(); } +uint256 +getConvertContextHash( + AccountID const& account, + std::uint32_t sequence, + uint192 const& issuanceID, + std::uint64_t amount) +{ + Serializer s; + addCommonZKPFields( + s, ttCONFIDENTIAL_CONVERT, account, sequence, issuanceID, amount); + + return s.getSHA512Half(); +} + int secp256k1_elgamal_generate_keypair( secp256k1_context const* ctx, @@ -842,7 +856,7 @@ proveEquality( return tesSUCCESS; } -Buffer +std::pair encryptAmount(uint64_t amt, Slice const& pubKeySlice) { Buffer buf(ecGamalEncryptedTotalLength); @@ -870,7 +884,7 @@ encryptAmount(uint64_t amt, Slice const& pubKeySlice) Throw( "Failed to serialize into 66 byte compressed format"); - return buf; + return std::make_pair(buf, Buffer(blindingFactor, 32)); } Buffer @@ -981,6 +995,36 @@ verifyEqualityProof( 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) + { + return tecBAD_PROOF; + } + + 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(), @@ -995,4 +1039,22 @@ verifyEqualityProof( 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; +} + } // namespace ripple diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index e0ae9943f6..3382bd54d7 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -47,26 +47,22 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert({ .account = bob, .amt = 0, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), }); mptAlice.convert({ .account = bob, .amt = 20, - .proof = "123", }); mptAlice.convert({ .account = bob, .amt = 40, - .proof = "123", }); mptAlice.convert({ .account = bob, .amt = 40, - .proof = "123", }); } @@ -76,104 +72,113 @@ class ConfidentialTransfer_test : public beast::unit_test::suite testcase("Convert preflight"); using namespace test::jtx; - Env env{*this, features - featureConfidentialTransfer}; - Account const alice("alice"); - Account const bob("bob"); - MPTTester mptAlice(env, alice, {.holders = {bob}}); + { + Env env{*this, features - featureConfidentialTransfer}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); - mptAlice.create( - {.ownerCount = 1, - .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanLock}); - mptAlice.authorize({.account = bob}); - env.close(); - mptAlice.pay(alice, bob, 100); - env.close(); + mptAlice.authorize({.account = bob}); + env.close(); + mptAlice.pay(alice, bob, 100); + env.close(); - mptAlice.generateKeyPair(alice); - mptAlice.generateKeyPair(bob); + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); - mptAlice.set( - {.account = alice, - .pubKey = mptAlice.getPubKey(alice), - .err = temDISABLED}); + mptAlice.set( + {.account = alice, + .pubKey = mptAlice.getPubKey(alice), + .err = temDISABLED}); - mptAlice.convert( - {.account = bob, - .amt = 10, - .proof = "123", - .holderPubKey = mptAlice.getPubKey(bob), - .err = temDISABLED}); + mptAlice.convert( + {.account = bob, + .amt = 10, + .holderPubKey = mptAlice.getPubKey(bob), + .err = temDISABLED}); + } - env.close(); + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); - env.enableFeature(featureConfidentialTransfer); - env.close(); + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanLock}); - mptAlice.convert( - {.account = alice, - .amt = 10, - .proof = "123", - .holderPubKey = mptAlice.getPubKey(bob), - .err = temMALFORMED}); + mptAlice.authorize({.account = bob}); + env.close(); + mptAlice.pay(alice, bob, 100); + env.close(); - mptAlice.convert( - {.account = bob, - .amt = 10, - .proof = "123", - .holderPubKey = mptAlice.getPubKey(bob), - .holderEncryptedAmt = Buffer{}, - .err = temBAD_CIPHERTEXT}); + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); - mptAlice.convert( - {.account = bob, - .amt = 10, - .proof = "123", - .holderPubKey = mptAlice.getPubKey(bob), - .issuerEncryptedAmt = Buffer{}, - .err = temBAD_CIPHERTEXT}); + mptAlice.convert( + {.account = alice, + .amt = 10, + .holderPubKey = mptAlice.getPubKey(bob), + .err = temMALFORMED}); - mptAlice.convert( - {.account = bob, - .amt = maxMPTokenAmount + 1, - .proof = "123", - .holderPubKey = mptAlice.getPubKey(bob), - .err = temBAD_AMOUNT}); + mptAlice.convert( + {.account = bob, + .amt = 10, + .holderPubKey = mptAlice.getPubKey(bob), + .holderEncryptedAmt = Buffer{}, + .err = temBAD_CIPHERTEXT}); - mptAlice.convert( - {.account = bob, - .amt = 1, - .proof = "123", - .holderPubKey = mptAlice.getPubKey(bob), - .holderEncryptedAmt = - Buffer{badCiphertext, ecGamalEncryptedTotalLength}, - .err = temBAD_CIPHERTEXT}); + mptAlice.convert( + {.account = bob, + .amt = 10, + .holderPubKey = mptAlice.getPubKey(bob), + .issuerEncryptedAmt = Buffer{}, + .err = temBAD_CIPHERTEXT}); - mptAlice.convert( - {.account = bob, - .amt = 1, - .proof = "123", - .holderPubKey = mptAlice.getPubKey(bob), - .issuerEncryptedAmt = - Buffer{badCiphertext, ecGamalEncryptedTotalLength}, - .err = temBAD_CIPHERTEXT}); + mptAlice.convert( + {.account = bob, + .amt = maxMPTokenAmount + 1, + .holderPubKey = mptAlice.getPubKey(bob), + .err = temBAD_AMOUNT}); - // invalid pub key - mptAlice.convert( - {.account = bob, - .amt = 10, - .proof = "123", - .holderPubKey = Buffer{}, - .err = temMALFORMED}); + mptAlice.convert( + {.account = bob, + .amt = 1, + .holderPubKey = mptAlice.getPubKey(bob), + .holderEncryptedAmt = + Buffer{badCiphertext, ecGamalEncryptedTotalLength}, + .err = temBAD_CIPHERTEXT}); - // todo: change to to check proof size - // mptAlice.convert( - // {.account = bob, - // .amt = 10, - // .proof = "123", - // .holderPubKey = mptAlice.getPubKey(bob), - // .err = temMALFORMED}); + mptAlice.convert( + {.account = bob, + .amt = 1, + .holderPubKey = mptAlice.getPubKey(bob), + .issuerEncryptedAmt = + Buffer{badCiphertext, ecGamalEncryptedTotalLength}, + .err = temBAD_CIPHERTEXT}); + + // invalid pub key + mptAlice.convert( + {.account = bob, + .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}); + } } void @@ -288,7 +293,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 10, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), .err = tecNO_PERMISSION}); } @@ -316,7 +320,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 10, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), .err = tecNO_PERMISSION}); } @@ -345,7 +348,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 10, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), .err = tecOBJECT_NOT_FOUND}); } @@ -371,7 +373,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 10, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), .err = tecOBJECT_NOT_FOUND}); } @@ -403,7 +404,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 200, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), .err = tecINSUFFICIENT_FUNDS}); } @@ -435,14 +435,12 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 10, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob)}); // cannot upload pk again mptAlice.convert( {.account = bob, .amt = 10, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), .err = tecDUPLICATE}); } @@ -476,7 +474,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 10, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), .err = tecINSUFFICIENT_FUNDS}); @@ -486,7 +483,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert({ .account = bob, .amt = 10, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), }); } @@ -524,7 +520,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 10, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), .err = tecINSUFFICIENT_FUNDS}); @@ -537,7 +532,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert({ .account = bob, .amt = 10, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), }); } @@ -574,7 +568,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert({ .account = bob, .amt = 40, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), }); @@ -612,7 +605,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert({ .account = bob, .amt = 40, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), }); @@ -757,7 +749,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 60, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), .err = tesSUCCESS}); @@ -780,7 +771,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = carol, .amt = 20, - .proof = "123", .holderPubKey = mptAlice.getPubKey(carol), .err = tesSUCCESS}); @@ -1032,13 +1022,11 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 60, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), .err = tesSUCCESS}); mptAlice.convert( {.account = carol, .amt = 20, - .proof = "123", .holderPubKey = mptAlice.getPubKey(carol), .err = tesSUCCESS}); @@ -1252,7 +1240,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 60, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), .err = tesSUCCESS}); @@ -1264,7 +1251,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = carol, .amt = 20, - .proof = "123", .holderPubKey = mptAlice.getPubKey(carol), .err = tesSUCCESS}); @@ -1316,7 +1302,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert({ .account = bob, .amt = 100, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), }); @@ -1356,14 +1341,12 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert({ .account = bob, .amt = 100, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), }); mptAlice.convert({ .account = carol, .amt = 0, - .proof = "123", .holderPubKey = mptAlice.getPubKey(carol), }); @@ -1400,7 +1383,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert({ .account = bob, .amt = 0, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), }); @@ -1437,7 +1419,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert({ .account = bob, .amt = 0, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), }); @@ -1480,7 +1461,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert({ .account = bob, .amt = 40, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), }); @@ -1559,7 +1539,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert({ .account = bob, .amt = 40, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), }); @@ -1765,7 +1744,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert({ .account = bob, .amt = 40, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), }); @@ -1776,7 +1754,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert({ .account = carol, .amt = 40, - .proof = "123", .holderPubKey = mptAlice.getPubKey(carol), }); @@ -1816,7 +1793,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert({ .account = bob, .amt = 40, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), }); mptAlice.mergeInbox({.account = bob}); @@ -1892,13 +1868,11 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = carol, .amt = 50, - .proof = "123", .holderPubKey = mptAlice.getPubKey(carol), .err = tesSUCCESS}); mptAlice.convert( {.account = bob, .amt = 50, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), .err = tesSUCCESS}); @@ -2021,7 +1995,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 60, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob)}); // bob merge inbox @@ -2037,7 +2010,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = carol, .amt = 120, - .proof = "123", .holderPubKey = mptAlice.getPubKey(carol)}); // carol merge inbox @@ -2052,7 +2024,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = dave, .amt = 200, - .proof = "123", .holderPubKey = mptAlice.getPubKey(dave)}); // setup: carol confidential send 50 to bob. @@ -2215,7 +2186,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert({ .account = bob, .amt = 60, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), }); mptAlice.mergeInbox({ @@ -2338,7 +2308,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 60, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob)}); mptAlice.mergeInbox({ .account = bob, @@ -2465,7 +2434,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 500, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob)}); mptAlice.mergeInbox({ .account = bob, @@ -2474,7 +2442,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = carol, .amt = 1000, - .proof = "123", .holderPubKey = mptAlice.getPubKey(carol)}); // verify proof fails with invalid clawback amount @@ -2506,7 +2473,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 300, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob)}); mptAlice.mergeInbox({ .account = bob, @@ -2514,7 +2480,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = carol, .amt = 400, - .proof = "123", .holderPubKey = mptAlice.getPubKey(carol)}); mptAlice.mergeInbox({ .account = carol, @@ -2625,14 +2590,12 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = amt, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), .err = expectedResult}); else mptAlice.convert({ .account = bob, .amt = amt, - .proof = "123", .err = expectedResult, }); @@ -2697,7 +2660,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 50, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob)}); // set or clear lsfMPTCanPrivacy should fail because of @@ -2729,14 +2691,16 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 10, - .proof = "123", .holderPubKey = mptAlice.getPubKey(bob), .err = tecNO_PERMISSION}); // can set lsfMPTCanPrivacy again when there's no confidential // outstanding balance mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetPrivacy}); - mptAlice.convert({.account = bob, .amt = 10, .proof = "123"}); + mptAlice.convert({ + .account = bob, + .amt = 10, + }); } } diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 0dfa55b64e..cfc66fc8e7 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -775,6 +775,98 @@ MPTTester::getClawbackProof( return proof; } +Buffer +MPTTester::getConvertProof( + Account const& holder, + std::uint64_t amount, + uint256 const& ctxHash, + std::pair holderCiphertext, + std::pair issuerCiphertext, + std::optional> auditorCiphertext) const +{ + if (!id_) + Throw("MPT has not been created"); + + auto const sleHolder = env_.le(keylet::mptoken(*id_, holder.id())); + auto const sleIssuance = env_.le(keylet::mptIssuance(*id_)); + + size_t const zkpSize = auditorCiphertext ? 3 : 2; + size_t const zkpByteLength = zkpSize * ecEqualityProofLength; + + if (!sleHolder || !sleIssuance || holderCiphertext.first.size() == 0 || + issuerCiphertext.first.size() == 0) + return Buffer(zkpByteLength); + + auto const generateProof = [amount, ctxHash]( + Slice const& ciphertext, + Slice const& pubKey, + Slice const& randomness) { + 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)) + { + Throw("Invalid Ciphertext"); + } + + 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, + randomness.data(), + ctxHash.data()) != 1) + { + Throw("Proof generation failed"); + } + return proof; + }; + + Buffer zkp(zkpByteLength); + + Buffer holderZkp = generateProof( + holderCiphertext.first, getPubKey(holder), holderCiphertext.second); + + Buffer issuerZkp = generateProof( + issuerCiphertext.first, getPubKey(issuer_), issuerCiphertext.second); + + // std::optional auditorZkp; + // if (auditor) + // { + // Slice auditorPubKey( + // sleIssuance->getFieldVL(sfAuditorElGamalPublicKey).data(), + // sleIssuance->getFieldVL(sfAuditorElGamalPublicKey).size()); + // Buffer auditorZkp = txArgs.auditorEncryptedAmt && + // sleIssuance->isFieldPresent(sfIssuerElGamalPublicKey) + // ? generateProof(*txArgs.issuerEncryptedAmt, issuerPubKey) + // : getDummyProof(); + // } + + // 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(); + + return zkp; +} + std::optional MPTTester::getEncryptedBalance( Account const& account, @@ -855,20 +947,39 @@ MPTTester::convert(MPTConvert const& arg) if (arg.holderPubKey) jv[sfHolderElGamalPublicKey.jsonName] = strHex(*arg.holderPubKey); + std::pair holderCiphertext; if (arg.holderEncryptedAmt) jv[sfHolderEncryptedAmount.jsonName] = strHex(*arg.holderEncryptedAmt); else - jv[sfHolderEncryptedAmount.jsonName] = - strHex(encryptAmount(*arg.account, *arg.amt)); + { + holderCiphertext = encryptAmount(*arg.account, *arg.amt); + jv[sfHolderEncryptedAmount.jsonName] = strHex(holderCiphertext.first); + } + std::pair issuerCiphertext; if (arg.issuerEncryptedAmt) jv[sfIssuerEncryptedAmount.jsonName] = strHex(*arg.issuerEncryptedAmt); else - jv[sfIssuerEncryptedAmount.jsonName] = - strHex(encryptAmount(issuer_, *arg.amt)); + { + issuerCiphertext = encryptAmount(issuer_, *arg.amt); + jv[sfIssuerEncryptedAmount.jsonName] = strHex(issuerCiphertext.first); + } if (arg.proof) jv[sfZKProof.jsonName] = *arg.proof; + else + { + uint256 const ctxHash = getConvertContextHash( + arg.account->id(), env_.seq(*arg.account), *id_, *arg.amt); + Buffer proof = getConvertProof( + *arg.account, + *arg.amt, + ctxHash, + holderCiphertext, + issuerCiphertext, + {}); + jv[sfZKProof] = strHex(proof); + } auto const holderAmt = getBalance(*arg.account); auto const prevConfidentialOutstanding = getIssuanceConfidentialBalance(); @@ -965,18 +1076,19 @@ MPTTester::send(MPTConfidentialSend const& arg) jv[sfSenderEncryptedAmount] = strHex(*arg.senderEncryptedAmt); else jv[sfSenderEncryptedAmount] = - strHex(encryptAmount(*arg.account, *arg.amt)); + strHex(encryptAmount(*arg.account, *arg.amt).first); if (arg.destEncryptedAmt) jv[sfDestinationEncryptedAmount] = strHex(*arg.destEncryptedAmt); else jv[sfDestinationEncryptedAmount] = - strHex(encryptAmount(*arg.dest, *arg.amt)); + strHex(encryptAmount(*arg.dest, *arg.amt).first); if (arg.issuerEncryptedAmt) jv[sfIssuerEncryptedAmount] = strHex(*arg.issuerEncryptedAmt); else - jv[sfIssuerEncryptedAmount] = strHex(encryptAmount(issuer_, *arg.amt)); + jv[sfIssuerEncryptedAmount] = + strHex(encryptAmount(issuer_, *arg.amt).first); if (arg.proof) jv[sfZKProof] = *arg.proof; @@ -1179,7 +1291,7 @@ MPTTester::getPrivKey(Account const& account) const Throw("Account does not have private key"); } -Buffer +std::pair MPTTester::encryptAmount(Account const& account, uint64_t amt) const { return ripple::encryptAmount(amt, getPubKey(account)); @@ -1308,13 +1420,13 @@ MPTTester::convertBack(MPTConvertBack const& arg) jv[sfHolderEncryptedAmount.jsonName] = strHex(*arg.holderEncryptedAmt); else jv[sfHolderEncryptedAmount.jsonName] = - strHex(encryptAmount(*arg.account, *arg.amt)); + strHex(encryptAmount(*arg.account, *arg.amt).first); if (arg.issuerEncryptedAmt) jv[sfIssuerEncryptedAmount.jsonName] = strHex(*arg.issuerEncryptedAmt); else jv[sfIssuerEncryptedAmount.jsonName] = - strHex(encryptAmount(issuer_, *arg.amt)); + strHex(encryptAmount(issuer_, *arg.amt).first); if (arg.proof) jv[sfZKProof.jsonName] = *arg.proof; diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index 93ecee0d03..f46198f0c1 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -175,6 +175,7 @@ struct MPTConvert std::optional holderPubKey = std::nullopt; std::optional holderEncryptedAmt = std::nullopt; std::optional issuerEncryptedAmt = std::nullopt; + std::optional auditorEncryptedAmt = std::nullopt; std::optional ownerCount = std::nullopt; std::optional holderCount = std::nullopt; std::optional flags = std::nullopt; @@ -400,7 +401,7 @@ public: Buffer getPrivKey(Account const& account) const; - Buffer + std::pair encryptAmount(Account const& account, uint64_t amt) const; uint64_t @@ -421,6 +422,15 @@ public: Buffer const& privateKey, uint256 const& txHash) const; + Buffer + getConvertProof( + Account const& holder, + std::uint64_t amount, + uint256 const& ctxHash, + std::pair holderCiphertext, + std::pair issuerCiphertext, + std::optional> auditorCiphertext) const; + private: using SLEP = SLE::const_pointer; bool diff --git a/src/xrpld/app/tx/detail/ConfidentialClawback.cpp b/src/xrpld/app/tx/detail/ConfidentialClawback.cpp index 9a32a5d037..f32b14a7e9 100644 --- a/src/xrpld/app/tx/detail/ConfidentialClawback.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialClawback.cpp @@ -89,7 +89,7 @@ ConfidentialClawback::preclaim(PreclaimContext const& ctx) auto const contextHash = getClawbackContextHash( account, ctx.tx[sfSequence], mptIssuanceID, amount, holder); - return verifyEqualityProof( + return verifyClawbackEqualityProof( amount, ctx.tx[sfZKProof], pubKeySlice, ciphertext, contextHash); } diff --git a/src/xrpld/app/tx/detail/ConfidentialConvert.cpp b/src/xrpld/app/tx/detail/ConfidentialConvert.cpp index 4656c2a13c..d7369e2176 100644 --- a/src/xrpld/app/tx/detail/ConfidentialConvert.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialConvert.cpp @@ -37,8 +37,10 @@ ConfidentialConvert::preflight(PreflightContext const& ctx) ctx.tx[sfHolderElGamalPublicKey].length() != ecPubKeyLength) return temMALFORMED; - // if (ctx.tx[sfZKProof].length() != ecEqualityProofLength) - // return temMALFORMED; + auto const expectedCount = + ctx.tx.isFieldPresent(sfAuditorEncryptedAmount) ? 3 : 2; + if (ctx.tx[sfZKProof].size() != expectedCount * ecEqualityProofLength) + return temMALFORMED; return tesSUCCESS; } @@ -94,35 +96,56 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx) ctx.tx.isFieldPresent(sfHolderElGamalPublicKey)) return tecDUPLICATE; - // auto const holderPubKey = ctx.tx.isFieldPresent(sfHolderElGamalPublicKey) - // ? ctx.tx[sfHolderElGamalPublicKey] - // : (*sleMptoken)[sfHolderElGamalPublicKey]; + auto const holderPubKey = ctx.tx.isFieldPresent(sfHolderElGamalPublicKey) + ? ctx.tx[sfHolderElGamalPublicKey] + : (*sleMptoken)[sfHolderElGamalPublicKey]; - // auto const contextHash = getContextHash( - // ctx.tx[sfMPTokenIssuanceID], - // ctx.tx[sfMPTAmount], - // ctx.tx[sfAccount], - // ctx.tx.getTxnType()); + auto const contextHash = getConvertContextHash( + ctx.tx[sfAccount], + ctx.tx[sfSequence], + ctx.tx[sfMPTokenIssuanceID], + ctx.tx[sfMPTAmount]); - // // check equality proof - // auto checkEqualityProof = [&](auto const& encryptedAmount, - // auto const& pubKey) -> TER { - // return verifyEqualityProof( - // ctx.tx[sfMPTAmount], - // ctx.tx[sfZKProof], - // pubKey, - // encryptedAmount, - // contextHash); - // }; + bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount); - // if (!isTesSuccess(checkEqualityProof( - // ctx.tx[sfHolderEncryptedAmount], holderPubKey)) || - // !isTesSuccess(checkEqualityProof( - // ctx.tx[sfIssuerEncryptedAmount], - // (*sleIssuance)[sfIssuerElGamalPublicKey]))) - // { - // return tecBAD_PROOF; - // } + std::vector const zkps = getEqualityProofs(ctx.tx[sfZKProof]); + + auto const& amount = ctx.tx[sfMPTAmount]; + + // we already checked proof size in preflight, still do sanity check here + // since we are going to access individual vector entries + auto const expectedCount = ctx.tx[sfZKProof].size() / ecEqualityProofLength; + if (zkps.size() != expectedCount) + return tecINTERNAL; // LCOV_EXCL_LINE + + // check equality proof + if (!isTesSuccess(verifyEqualityProof( + amount, + zkps[0], + holderPubKey, + ctx.tx[sfHolderEncryptedAmount], + contextHash)) || + !isTesSuccess(verifyEqualityProof( + amount, + zkps[1], + (*sleIssuance)[sfIssuerElGamalPublicKey], + ctx.tx[sfIssuerEncryptedAmount], + contextHash))) + { + return tecBAD_PROOF; + } + + // Verify Auditor proof if present + if (hasAuditor && + !isTesSuccess(verifyEqualityProof( + amount, + zkps[2], + (*sleIssuance)[sfAuditorElGamalPublicKey], + ctx.tx[sfAuditorEncryptedAmount], + contextHash))) + { + return tecBAD_PROOF; + } return tesSUCCESS; } @@ -196,19 +219,20 @@ ConfidentialConvert::doApply() { // encrypt sfConfidentialBalanceSpending with zero balance Buffer out; - out = encryptAmount(0, (*sleMptoken)[sfHolderElGamalPublicKey]); + out = + encryptAmount(0, (*sleMptoken)[sfHolderElGamalPublicKey]).first; (*sleMptoken)[sfConfidentialBalanceSpending] = out; } catch (std::exception const& e) { - return tecINTERNAL; + return tecINTERNAL; // LCOV_EXCL_LINE } } else { // both sfIssuerEncryptedBalance and sfConfidentialBalanceInbox should // exist together - return tecINTERNAL; + return tecINTERNAL; // LCOV_EXCL_LINE } view().update(sleIssuance);