From f24d584f29f5fe120d64b88bb8647228b95c441a Mon Sep 17 00:00:00 2001 From: Shawn Xie <35279399+shawnxie999@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:39:16 -0400 Subject: [PATCH] ConfidentialConvert tests (#5911) --- src/test/app/ConfidentialTransfer_test.cpp | 270 ++++++++++++++++-- .../app/tx/detail/ConfidentialConvert.cpp | 12 +- 2 files changed, 257 insertions(+), 25 deletions(-) diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index f187f8a47c..d4b5de2553 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -31,7 +31,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite void testConvert(FeatureBitset features) { - testcase("test convert"); + testcase("Convert"); using namespace test::jtx; Env env{*this, features}; Account const alice("alice"); @@ -85,7 +85,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite void testConvertPreflight(FeatureBitset features) { - testcase("test convert"); + testcase("Convert preflight"); using namespace test::jtx; Env env{*this, features - featureConfidentialTransfer}; @@ -146,6 +146,13 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .issuerEncryptedAmt = Buffer{}, .err = temMALFORMED}); + mptAlice.convert( + {.account = bob, + .amt = maxMPTokenAmount + 1, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob), + .err = temMALFORMED}); + // todo: change to to check proof size // mptAlice.convert( // {.account = bob, @@ -161,28 +168,249 @@ class ConfidentialTransfer_test : public beast::unit_test::suite testcase("Set 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}); + } + + // issuance has disabled confidential transfer + { + 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 | + tfMPTNoConfidentialTransfer}); + + mptAlice.authorize({.account = bob}); + env.close(); + mptAlice.pay(alice, bob, 100); + env.close(); + + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); + + mptAlice.set( + {.account = alice, + .pubKey = mptAlice.getPubKey(alice), + .err = tecNO_PERMISSION}); + } + } + + void + testConvertPreclaim(FeatureBitset features) + { + testcase("Convert preclaim"); + using namespace test::jtx; + + // tfMPTNoConfidentialTransfer is set on issuance + { + 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 | + tfMPTNoConfidentialTransfer}); + + mptAlice.authorize({.account = bob}); + env.close(); + mptAlice.pay(alice, bob, 100); + env.close(); + + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); + + mptAlice.convert( + {.account = bob, + .amt = 10, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob), + .err = tecNO_PERMISSION}); + } + + // issuer has not uploaded their sfIssuerElGamalPublicKey + { + 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}); + + mptAlice.authorize({.account = bob}); + env.close(); + mptAlice.pay(alice, bob, 100); + env.close(); + + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); + + mptAlice.convert( + {.account = bob, + .amt = 10, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob), + .err = tecNO_PERMISSION}); + } + + // issuance does not exist + { + 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}); + + mptAlice.authorize({.account = bob}); + mptAlice.generateKeyPair(alice); + + mptAlice.set( + {.account = alice, .pubKey = mptAlice.getPubKey(alice)}); + + mptAlice.destroy(); + mptAlice.generateKeyPair(bob); + + mptAlice.convert( + {.account = bob, + .amt = 10, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob), + .err = tecOBJECT_NOT_FOUND}); + } + + // bob has not created MPToken + { + 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}); + + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); + + mptAlice.set( + {.account = alice, .pubKey = mptAlice.getPubKey(alice)}); + + mptAlice.convert( + {.account = bob, + .amt = 10, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob), + .err = tecOBJECT_NOT_FOUND}); + } + + // trying to convert more than what bob has + { + 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}); + + mptAlice.authorize({.account = bob}); + env.close(); + mptAlice.pay(alice, bob, 100); + env.close(); + + mptAlice.generateKeyPair(alice); + + mptAlice.set( + {.account = alice, .pubKey = mptAlice.getPubKey(alice)}); + + mptAlice.generateKeyPair(bob); + + mptAlice.convert( + {.account = bob, + .amt = 200, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob), + .err = tecINSUFFICIENT_FUNDS}); + } + + // holder cannot upload pk again + { + 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}); + + mptAlice.authorize({.account = bob}); + env.close(); + mptAlice.pay(alice, bob, 100); + env.close(); + + mptAlice.generateKeyPair(alice); + + mptAlice.set( + {.account = alice, .pubKey = mptAlice.getPubKey(alice)}); + + mptAlice.generateKeyPair(bob); + + 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}); + } + + // todo: test well formed proof } void @@ -190,6 +418,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite { testConvert(features); testConvertPreflight(features); + testConvertPreclaim(features); + testSetPreflight(features); } diff --git a/src/xrpld/app/tx/detail/ConfidentialConvert.cpp b/src/xrpld/app/tx/detail/ConfidentialConvert.cpp index ee773a8296..18df8df596 100644 --- a/src/xrpld/app/tx/detail/ConfidentialConvert.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialConvert.cpp @@ -44,6 +44,9 @@ ConfidentialConvert::preflight(PreflightContext const& ctx) ctx.tx[sfIssuerEncryptedAmount].length() != ecGamalEncryptedTotalLength) return temMALFORMED; + if (ctx.tx[sfMPTAmount] > maxMPTokenAmount) + return temMALFORMED; + // if (ctx.tx[sfZKProof].length() != ecEqualityProofLength) // return temMALFORMED; @@ -83,7 +86,7 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx) // can't update if there's already a pk if (sleMptoken->isFieldPresent(sfHolderElGamalPublicKey) && ctx.tx.isFieldPresent(sfHolderElGamalPublicKey)) - return tecNO_PERMISSION; + return tecDUPLICATE; auto const holderPubKey = ctx.tx.isFieldPresent(sfHolderElGamalPublicKey) ? ctx.tx[sfHolderElGamalPublicKey] @@ -180,18 +183,17 @@ ConfidentialConvert::doApply() (*sleMptoken)[sfIssuerEncryptedBalance] = issuerEc; (*sleMptoken)[sfConfidentialBalanceVersion] = 0; - // encrypt sfConfidentialBalanceSpending with zero balance - Buffer out; try { + // encrypt sfConfidentialBalanceSpending with zero balance + Buffer out; out = encryptAmount(0, (*sleMptoken)[sfHolderElGamalPublicKey]); + (*sleMptoken)[sfConfidentialBalanceSpending] = out; } catch (std::exception const& e) { return tecINTERNAL; } - - (*sleMptoken)[sfConfidentialBalanceSpending] = out; } else {