From cad129921ddc76e1c8afb7f91017fbd2e621ce44 Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Mon, 4 May 2026 14:32:23 -0400 Subject: [PATCH] add IssuerEncryptedBalance check in ConvertBack (#7060) Co-authored-by: Peter Chen <34582813+PeterChen13579@users.noreply.github.com> --- .../token/ConfidentialMPTConvertBack.cpp | 10 +++- src/test/app/ConfidentialTransfer_test.cpp | 52 +++++++++++++------ 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/libxrpl/tx/transactors/token/ConfidentialMPTConvertBack.cpp b/src/libxrpl/tx/transactors/token/ConfidentialMPTConvertBack.cpp index c28b70078f..188e80ff41 100644 --- a/src/libxrpl/tx/transactors/token/ConfidentialMPTConvertBack.cpp +++ b/src/libxrpl/tx/transactors/token/ConfidentialMPTConvertBack.cpp @@ -165,12 +165,18 @@ ConfidentialMPTConvertBack::preclaim(PreclaimContext const& ctx) if (!sleMptoken) return tecOBJECT_NOT_FOUND; - if (!sleMptoken->isFieldPresent(sfConfidentialBalanceSpending) || - !sleMptoken->isFieldPresent(sfHolderEncryptionKey)) + if (!sleMptoken->isFieldPresent(sfHolderEncryptionKey) || + !sleMptoken->isFieldPresent(sfConfidentialBalanceSpending) || + !sleMptoken->isFieldPresent(sfIssuerEncryptedBalance)) { return tecNO_PERMISSION; } + // Sanity check: holder's MPToken must have auditor balance field if auditing + // is enabled + if (requiresAuditor && !sleMptoken->isFieldPresent(sfAuditorEncryptedBalance)) + return tefINTERNAL; // LCOV_EXCL_LINE + // if the total circulating confidential balance is smaller than what the // holder is trying to convert back, we know for sure this txn should // fail diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index b51c5e995b..f9cb62b74d 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -242,11 +242,14 @@ class ConfidentialTransfer_test : public beast::unit_test::suite *mpt.getEncryptedBalance(sender, test::jtx::MPTTester::HOLDER_ENCRYPTED_SPENDING)) , balanceCommitment(mpt.getPedersenCommitment(prevSpending, balanceBlindingFactor)) { - recipients.push_back({Slice(senderPubKey), senderAmt}); - recipients.push_back({Slice(destPubKey), destAmt}); - recipients.push_back({Slice(issuerPubKey), issuerAmt}); + recipients.push_back({.publicKey = Slice(senderPubKey), .encryptedAmount = senderAmt}); + recipients.push_back({.publicKey = Slice(destPubKey), .encryptedAmount = destAmt}); + recipients.push_back({.publicKey = Slice(issuerPubKey), .encryptedAmount = issuerAmt}); if (auditor) - recipients.push_back({Slice(*auditorPubKey), *auditorAmt}); + { + recipients.push_back( + {.publicKey = Slice(*auditorPubKey), .encryptedAmount = *auditorAmt}); + } } // Generate proof with current account sequence @@ -3727,7 +3730,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite }); } - // bob doesn't have encrypted balances + // mptoken exists but lacks confidential fields { Env env{*this, features}; Account const alice("alice"); @@ -3736,19 +3739,24 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create({ .ownerCount = 1, - .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanConfidentialAmount, + .flags = tfMPTCanTransfer | tfMPTCanConfidentialAmount, }); mptAlice.authorize({ .account = bob, }); + mptAlice.pay(alice, bob, 100); - mptAlice.generateKeyPair(alice); - + mptAlice.generateKeyPair(bob); mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)}); - mptAlice.generateKeyPair(bob); + // Bob's MPToken lacks the confidential fields + auto const sleBobMpt = env.le(keylet::mptoken(mptAlice.issuanceID(), bob.id())); + BEAST_EXPECT(sleBobMpt); + BEAST_EXPECT(!sleBobMpt->isFieldPresent(sfHolderEncryptionKey)); + BEAST_EXPECT(!sleBobMpt->isFieldPresent(sfConfidentialBalanceSpending)); + BEAST_EXPECT(!sleBobMpt->isFieldPresent(sfIssuerEncryptedBalance)); mptAlice.convertBack({ .account = bob, @@ -5702,7 +5710,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite Env env{*this, features}; Account const alice("alice"), bob("bob"); - ConfidentialEnv confEnv{env, alice, {{bob, 100, 40}}}; + ConfidentialEnv confEnv{ + env, alice, {{.account = bob, .payAmount = 100, .convertAmount = 40}}}; auto& mptAlice = confEnv.mpt; uint64_t const amt = 10; @@ -8341,7 +8350,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite using namespace test::jtx; Env env{*this, features}; Account const alice("alice"), bob("bob"), carol("carol"); - ConfidentialEnv confEnv{env, alice, {{bob}, {carol, 1000, 50}}}; + ConfidentialEnv confEnv{ + env, + alice, + {{.account = bob}, {.account = carol, .payAmount = 1000, .convertAmount = 50}}}; auto& mptAlice = confEnv.mpt; ConfidentialSendSetup setup(mptAlice, bob, carol, alice, 10); @@ -8426,7 +8438,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite using namespace test::jtx; Env env{*this, features}; Account const alice("alice"), bob("bob"), carol("carol"); - ConfidentialEnv confEnv{env, alice, {{bob}, {carol, 1000, 50}}}; + ConfidentialEnv confEnv{ + env, + alice, + {{.account = bob}, {.account = carol, .payAmount = 1000, .convertAmount = 50}}}; auto& mptAlice = confEnv.mpt; uint64_t const badAmount = std::numeric_limits::max(); @@ -9335,7 +9350,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite ConfidentialEnv confEnv{ env, alice, - {{bob, 1000, 200}, {carol, 1000, 100}}, + {{.account = bob, .payAmount = 1000, .convertAmount = 200}, + {.account = carol, .payAmount = 1000, .convertAmount = 100}}, tfMPTCanTransfer | tfMPTCanConfidentialAmount}; auto& mptAlice = confEnv.mpt; @@ -9470,7 +9486,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite Env env{*this, features}; Account const alice("alice"), bob("bob"), carol("carol"); ConfidentialEnv confEnv{ - env, alice, {{bob}, {carol, 1000, 50}}, tfMPTCanTransfer | tfMPTCanConfidentialAmount}; + env, + alice, + {{.account = bob}, {.account = carol, .payAmount = 1000, .convertAmount = 50}}, + tfMPTCanTransfer | tfMPTCanConfidentialAmount}; auto& mptAlice = confEnv.mpt; uint64_t const sendAmount = 10; @@ -9563,7 +9582,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite Env env{*this, features}; Account const alice("alice"), bob("bob"), carol("carol"); ConfidentialEnv confEnv{ - env, alice, {{bob}, {carol, 1000, 50}}, tfMPTCanTransfer | tfMPTCanConfidentialAmount}; + env, + alice, + {{.account = bob}, {.account = carol, .payAmount = 1000, .convertAmount = 50}}, + tfMPTCanTransfer | tfMPTCanConfidentialAmount}; auto& mptAlice = confEnv.mpt; uint64_t const sendAmount = 10;