From 99e66c9037af843ce92d869244f08c08063ed5d2 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 21 Jan 2026 17:44:59 -0500 Subject: [PATCH 1/8] add more auditor tests --- src/test/app/ConfidentialTransfer_test.cpp | 283 ++++++++++++++++++--- src/test/jtx/impl/mpt.cpp | 28 +- src/test/jtx/mpt.h | 11 +- 3 files changed, 264 insertions(+), 58 deletions(-) diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index 22b971596f0..cc904efced1 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -48,6 +48,16 @@ class ConfidentialTransfer_test : public beast::unit_test::suite return trivialCiphertext; } + // a valid hardcoded ciphertext for testing + constexpr static unsigned char + validCiphertext[ecGamalEncryptedTotalLength] = { + 0x02, 0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB, 0xAC, 0x55, 0xA0, + 0x62, 0x95, 0xCE, 0x87, 0x0B, 0x07, 0x02, 0x9B, 0xFC, 0xDB, 0x2D, + 0xCE, 0x28, 0xD9, 0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17, 0x98, + 0x02, 0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB, 0xAC, 0x55, 0xA0, + 0x62, 0x95, 0xCE, 0x87, 0x0B, 0x07, 0x02, 0x9B, 0xFC, 0xDB, 0x2D, + 0xCE, 0x28, 0xD9, 0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17, 0x98}; + void testConvert(FeatureBitset features) { @@ -61,7 +71,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -110,7 +119,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -149,6 +157,23 @@ class ConfidentialTransfer_test : public beast::unit_test::suite testcase("Convert preflight"); using namespace test::jtx; + // Alice (issuer) tries to convert her own tokens - should fail + { + Env env{*this, features}; + Account const alice("alice"); + MPTTester mptAlice(env, alice); + + mptAlice.create( + {.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); + mptAlice.generateKeyPair(alice); + + mptAlice.convert( + {.account = alice, + .amt = 10, + .holderPubKey = mptAlice.getPubKey(alice), + .err = temMALFORMED}); + } + { Env env{*this, features - featureConfidentialTransfer}; Account const alice("alice"); @@ -156,9 +181,7 @@ 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}); mptAlice.pay(alice, bob, 100); @@ -185,9 +208,7 @@ 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}); mptAlice.pay(alice, bob, 100); @@ -209,6 +230,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .blindingFactor = Buffer(10), .err = temMALFORMED}); + // Holder encrypted amount is empty (length 0) mptAlice.convert( {.account = bob, .amt = 10, @@ -216,6 +238,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .holderEncryptedAmt = Buffer{}, .err = temBAD_CIPHERTEXT}); + // Issuer encrypted amount is empty (length 0) mptAlice.convert( {.account = bob, .amt = 10, @@ -223,12 +246,31 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .issuerEncryptedAmt = Buffer{}, .err = temBAD_CIPHERTEXT}); + // Auditor encrypted amount has invalid length (must be 66 bytes) + mptAlice.convert( + {.account = bob, + .amt = 10, + .holderPubKey = mptAlice.getPubKey(bob), + .auditorEncryptedAmt = Buffer(10), + .err = temBAD_CIPHERTEXT}); + + // Auditor encrypted amount has correct length but invalid data + mptAlice.convert( + {.account = bob, + .amt = 10, + .holderPubKey = mptAlice.getPubKey(bob), + .auditorEncryptedAmt = + Buffer{badCiphertext, ecGamalEncryptedTotalLength}, + .err = temBAD_CIPHERTEXT}); + + // Amount exceeds maximum allowed MPT amount mptAlice.convert( {.account = bob, .amt = maxMPTokenAmount + 1, .holderPubKey = mptAlice.getPubKey(bob), .err = temBAD_AMOUNT}); + // Holder encrypted amount has correct length but invalid data mptAlice.convert( {.account = bob, .amt = 1, @@ -236,6 +278,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .holderEncryptedAmt = getBadCiphertext(), .err = temBAD_CIPHERTEXT}); + // Issuer encrypted amount has correct length but invalid data (not + // a valid EC point) mptAlice.convert( {.account = bob, .amt = 1, @@ -243,7 +287,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .issuerEncryptedAmt = getBadCiphertext(), .err = temBAD_CIPHERTEXT}); - // invalid pub key + // Holder public key is invalid (empty buffer) mptAlice.convert( {.account = bob, .amt = 10, @@ -261,7 +305,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -306,7 +349,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -407,6 +449,42 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .issuerPubKey = mptAlice.getPubKey(alice), .err = tecNO_PERMISSION}); } + + // auditor key is invalid length + { + Env env{*this, features}; + Account const alice("alice"); + MPTTester mptAlice(env, alice); + + mptAlice.create( + {.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanPrivacy}); + + mptAlice.generateKeyPair(alice); + + mptAlice.set( + {.account = alice, + .issuerPubKey = mptAlice.getPubKey(alice), + .auditorPubKey = Buffer(10), // Invalid length + .err = temMALFORMED}); + } + + // cannot set auditor key without issuer key + { + Env env{*this, features}; + Account const alice("alice"); + MPTTester mptAlice(env, alice); + + mptAlice.create( + {.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanPrivacy}); + + mptAlice.generateKeyPair(alice); + + // Providing only auditor key should fail + mptAlice.set( + {.account = alice, + .auditorPubKey = mptAlice.getPubKey(alice), + .err = temMALFORMED}); + } } void @@ -660,6 +738,70 @@ class ConfidentialTransfer_test : public beast::unit_test::suite }); } + // cannot convert if auditor key is set, but auditor amount is not + // provided + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + Account const auditor("auditor"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); + + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 100); + + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); + mptAlice.generateKeyPair(auditor); + + mptAlice.set( + {.account = alice, + .issuerPubKey = mptAlice.getPubKey(alice), + .auditorPubKey = mptAlice.getPubKey(auditor)}); + + // no auditor encrypted amt provided + mptAlice.convert( + {.account = bob, + .amt = 10, + .holderPubKey = mptAlice.getPubKey(bob), + .err = tecNO_PERMISSION}); + } + + // cannot convert if tx include auditor ciphertext, but does not have + // auditing enabled + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); + + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 100); + + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); + + // there is no auditor key set + mptAlice.set( + {.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)}); + + mptAlice.convert( + {.account = bob, + .amt = 10, + .holderPubKey = mptAlice.getPubKey(bob), + .auditorEncryptedAmt = + Buffer{validCiphertext, ecGamalEncryptedTotalLength}, + .err = tecNO_PERMISSION}); + } + // invalid proof when registering holder pub key { Env env{*this, features}; @@ -669,7 +811,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -778,7 +919,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -801,9 +941,7 @@ 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}); mptAlice.pay(alice, bob, 100); @@ -823,7 +961,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.generateKeyPair(alice); @@ -843,7 +980,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -1105,6 +1241,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .issuerEncryptedAmt = Buffer(ripple::ecGamalEncryptedTotalLength), .err = temBAD_CIPHERTEXT}); + // dest encrypted amount wrong length mptAlice.send( {.account = bob, @@ -1116,6 +1253,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .issuerEncryptedAmt = Buffer(ripple::ecGamalEncryptedTotalLength), .err = temBAD_CIPHERTEXT}); + // issuer encrypted amount wrong length mptAlice.send( {.account = bob, @@ -1138,6 +1276,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .senderEncryptedAmt = Buffer(ripple::ecGamalEncryptedTotalLength), .err = temBAD_CIPHERTEXT}); + // dest encrypted amount malformed mptAlice.send( {.account = bob, @@ -1146,6 +1285,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .destEncryptedAmt = Buffer(ripple::ecGamalEncryptedTotalLength), .err = temBAD_CIPHERTEXT}); + // issuer encrypted amount malformed mptAlice.send( {.account = bob, @@ -1529,7 +1669,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -1572,7 +1711,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -1644,7 +1782,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -1687,7 +1824,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -1732,9 +1868,7 @@ 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}); mptAlice.pay(alice, bob, 100); @@ -1754,7 +1888,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -1818,6 +1951,19 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .amt = 30, .issuerEncryptedAmt = getBadCiphertext(), .err = temBAD_CIPHERTEXT}); + + mptAlice.convertBack( + {.account = bob, + .amt = 30, + .auditorEncryptedAmt = Buffer(10), + .err = temBAD_CIPHERTEXT}); + + mptAlice.convertBack( + {.account = bob, + .amt = 30, + .auditorEncryptedAmt = + Buffer{badCiphertext, ecGamalEncryptedTotalLength}, + .err = temBAD_CIPHERTEXT}); } } @@ -1836,7 +1982,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -1860,9 +2005,7 @@ 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}); mptAlice.pay(alice, bob, 100); @@ -1883,7 +2026,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.generateKeyPair(alice); @@ -1905,7 +2047,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -1932,7 +2073,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -1977,7 +2117,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTRequireAuth | tfMPTCanPrivacy}); @@ -2026,6 +2165,82 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .amt = 10, }); } + + // Alice has NOT set an auditor key, but Bob provides + // auditorEncryptedAmt + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .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)}); + + // Bob converts funds to confidential so he has something to convert + // back + mptAlice.convert( + {.account = bob, + .amt = 50, + .holderPubKey = mptAlice.getPubKey(bob)}); + mptAlice.mergeInbox({.account = bob}); + + mptAlice.convertBack( + {.account = bob, + .amt = 10, + // Provide valid ciphertext to pass preflight + .auditorEncryptedAmt = + Buffer(validCiphertext, ecGamalEncryptedTotalLength), + .err = tecNO_PERMISSION}); + } + + // we set the auditor key, but convertBack omits auditorEncryptedAmt + { + Env env2{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + Account const auditor("auditor"); + MPTTester mpt2(env2, alice, {.holders = {bob}, .auditor = auditor}); + + mpt2.create( + {.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); + mpt2.authorize({.account = bob}); + mpt2.pay(alice, bob, 100); + + mpt2.generateKeyPair(alice); + mpt2.generateKeyPair(bob); + mpt2.generateKeyPair(auditor); + + mpt2.set( + {.account = alice, + .issuerPubKey = mpt2.getPubKey(alice), + .auditorPubKey = mpt2.getPubKey(auditor)}); + + // Convert funds so Bob has a balance + mpt2.convert({ + .account = bob, + .amt = 50, + .holderPubKey = mpt2.getPubKey(bob), + }); + mpt2.mergeInbox({.account = bob}); + + // ConvertBack WITHOUT auditorEncryptedAmt + mpt2.convertBack({ + .account = bob, + .amt = 10, + // we omit .auditorEncryptedAmt. + .fillAuditorEncryptedAmt = false, + .err = tecNO_PERMISSION, + }); + } } void diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 267d71e3f33..e4890f842bc 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -482,17 +482,8 @@ MPTTester::set(MPTSet const& arg) return forObject([&](SLEP const& sle) -> bool { if (sle) { - if (!auditor_) - Throw( - "MPTTester::set: auditor is not set"); - - auto const auditorPubKey = getPubKey(*auditor_); - if (!auditorPubKey) - Throw( - "MPTTester::set: auditor's pubkey is not set"); - return strHex((*sle)[sfAuditorElGamalPublicKey]) == - strHex(*auditorPubKey); + strHex(*arg.auditorPubKey); } return false; }); @@ -1020,7 +1011,8 @@ MPTTester::fillConversionCiphertexts( Buffer& holderCiphertext, Buffer& issuerCiphertext, std::optional& auditorCiphertext, - Buffer& blindingFactor) const + Buffer& blindingFactor, + bool fillAuditorEncryptedAmt) const { blindingFactor = arg.blindingFactor ? *arg.blindingFactor : generateBlindingFactor(); @@ -1045,8 +1037,8 @@ MPTTester::fillConversionCiphertexts( // Handle Auditor if (arg.auditorEncryptedAmt) auditorCiphertext = *arg.auditorEncryptedAmt; - else if (auditor()) - auditorCiphertext = encryptAmount(*auditor(), *arg.amt, blindingFactor); + else if (auditor_.has_value() && fillAuditorEncryptedAmt) + auditorCiphertext = encryptAmount(*auditor_, *arg.amt, blindingFactor); // Update auditor JSON only if ciphertext exists if (auditorCiphertext) @@ -1254,9 +1246,10 @@ MPTTester::send(MPTConfidentialSend const& arg) std::optional auditorAmt; if (arg.auditorEncryptedAmt) - auditorAmt = arg.auditorEncryptedAmt; - else if (auditor_) - auditorAmt = encryptAmount(*auditor_, *arg.amt, blindingFactor); + jv[sfAuditorEncryptedAmount] = strHex(*arg.auditorEncryptedAmt); + else if (auditor_.has_value()) + jv[sfAuditorEncryptedAmount] = + strHex(encryptAmount(*auditor_, *arg.amt, blindingFactor)); jv[sfSenderEncryptedAmount] = strHex(senderAmt); jv[sfDestinationEncryptedAmount] = strHex(destAmt); @@ -1775,7 +1768,8 @@ MPTTester::convertBack(MPTConvertBack const& arg) holderCiphertext, issuerCiphertext, auditorCiphertext, - blindingFactor); + blindingFactor, + *arg.fillAuditorEncryptedAmt); jv[sfBlindingFactor] = strHex(blindingFactor); diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index 0b2d34221dd..0d4cce5b3d6 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -230,6 +230,8 @@ struct MPTConvertBack std::optional holderEncryptedAmt = std::nullopt; std::optional issuerEncryptedAmt = std::nullopt; std::optional auditorEncryptedAmt = std::nullopt; + std::optional fillAuditorEncryptedAmt = true; + // not an txn param, only used for autofilling std::optional blindingFactor = std::nullopt; std::optional pedersenCommitment = std::nullopt; std::optional ownerCount = std::nullopt; @@ -371,12 +373,6 @@ class MPTTester return issuer_; } - std::optional const& - auditor() const - { - return auditor_; - } - Account const& holder(std::string const& h) const; @@ -539,7 +535,8 @@ class MPTTester Buffer& holderCiphertext, Buffer& issuerCiphertext, std::optional& auditorCiphertext, - Buffer& blindingFactor) const; + Buffer& blindingFactor, + bool fillAuditorEncryptedAmt = true) const; }; } // namespace jtx From 3a86ae6a3cc1932a6fb45970d8f4453fb94b7803 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Tue, 27 Jan 2026 13:50:54 -0500 Subject: [PATCH 2/8] fix a couple comments --- src/test/app/ConfidentialTransfer_test.cpp | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index cc904efced1..7086596214e 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -48,16 +48,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite return trivialCiphertext; } - // a valid hardcoded ciphertext for testing - constexpr static unsigned char - validCiphertext[ecGamalEncryptedTotalLength] = { - 0x02, 0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB, 0xAC, 0x55, 0xA0, - 0x62, 0x95, 0xCE, 0x87, 0x0B, 0x07, 0x02, 0x9B, 0xFC, 0xDB, 0x2D, - 0xCE, 0x28, 0xD9, 0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17, 0x98, - 0x02, 0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB, 0xAC, 0x55, 0xA0, - 0x62, 0x95, 0xCE, 0x87, 0x0B, 0x07, 0x02, 0x9B, 0xFC, 0xDB, 0x2D, - 0xCE, 0x28, 0xD9, 0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17, 0x98}; - void testConvert(FeatureBitset features) { @@ -259,8 +249,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite {.account = bob, .amt = 10, .holderPubKey = mptAlice.getPubKey(bob), - .auditorEncryptedAmt = - Buffer{badCiphertext, ecGamalEncryptedTotalLength}, + .auditorEncryptedAmt = getBadCiphertext(), .err = temBAD_CIPHERTEXT}); // Amount exceeds maximum allowed MPT amount @@ -797,8 +786,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite {.account = bob, .amt = 10, .holderPubKey = mptAlice.getPubKey(bob), - .auditorEncryptedAmt = - Buffer{validCiphertext, ecGamalEncryptedTotalLength}, + .auditorEncryptedAmt = getTrivialCiphertext(), .err = tecNO_PERMISSION}); } @@ -1961,8 +1949,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convertBack( {.account = bob, .amt = 30, - .auditorEncryptedAmt = - Buffer{badCiphertext, ecGamalEncryptedTotalLength}, + .auditorEncryptedAmt = getBadCiphertext(), .err = temBAD_CIPHERTEXT}); } } @@ -2197,8 +2184,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite {.account = bob, .amt = 10, // Provide valid ciphertext to pass preflight - .auditorEncryptedAmt = - Buffer(validCiphertext, ecGamalEncryptedTotalLength), + .auditorEncryptedAmt = getTrivialCiphertext(), .err = tecNO_PERMISSION}); } From 103ca45fa8aa56fe2ff9059a6dc565d47772d128 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2026 11:22:26 -0500 Subject: [PATCH 3/8] fix comments --- src/test/app/ConfidentialTransfer_test.cpp | 61 +++++++++------------- src/test/jtx/impl/mpt.cpp | 8 +-- src/test/jtx/mpt.h | 2 +- 3 files changed, 29 insertions(+), 42 deletions(-) diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index 7086596214e..61faef9618d 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -410,10 +410,31 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); + // Pub key is invalid mptAlice.set( {.account = alice, .issuerPubKey = Buffer{}, .err = temMALFORMED}); + + // Auditor key is invalid length + mptAlice.set( + {.account = alice, + .issuerPubKey = mptAlice.getPubKey(alice), + .auditorPubKey = Buffer(10), + .err = temMALFORMED}); + + // Cannot set auditor key without issuer key + mptAlice.set( + {.account = alice, + .auditorPubKey = mptAlice.getPubKey(alice), + .err = temMALFORMED}); + + // Cannot set Holder and Keys in the same transaction + mptAlice.set( + {.account = alice, + .holder = bob, + .issuerPubKey = mptAlice.getPubKey(alice), + .err = temMALFORMED}); } // issuance has disabled confidential transfer @@ -438,42 +459,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .issuerPubKey = mptAlice.getPubKey(alice), .err = tecNO_PERMISSION}); } - - // auditor key is invalid length - { - Env env{*this, features}; - Account const alice("alice"); - MPTTester mptAlice(env, alice); - - mptAlice.create( - {.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanPrivacy}); - - mptAlice.generateKeyPair(alice); - - mptAlice.set( - {.account = alice, - .issuerPubKey = mptAlice.getPubKey(alice), - .auditorPubKey = Buffer(10), // Invalid length - .err = temMALFORMED}); - } - - // cannot set auditor key without issuer key - { - Env env{*this, features}; - Account const alice("alice"); - MPTTester mptAlice(env, alice); - - mptAlice.create( - {.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanPrivacy}); - - mptAlice.generateKeyPair(alice); - - // Providing only auditor key should fail - mptAlice.set( - {.account = alice, - .auditorPubKey = mptAlice.getPubKey(alice), - .err = temMALFORMED}); - } } void @@ -734,7 +719,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite Account const alice("alice"); Account const bob("bob"); Account const auditor("auditor"); - MPTTester mptAlice(env, alice, {.holders = {bob}}); + MPTTester mptAlice( + env, alice, {.holders = {bob}, .auditor = auditor}); mptAlice.create( {.ownerCount = 1, @@ -756,6 +742,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.convert( {.account = bob, .amt = 10, + .fillAuditorEncryptedAmt = false, .holderPubKey = mptAlice.getPubKey(bob), .err = tecNO_PERMISSION}); } diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index e4890f842bc..c1ce84cb129 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -1080,7 +1080,8 @@ MPTTester::convert(MPTConvert const& arg) holderCiphertext, issuerCiphertext, auditorCiphertext, - blindingFactor); + blindingFactor, + *arg.fillAuditorEncryptedAmt); jv[sfBlindingFactor.jsonName] = strHex(blindingFactor); if (arg.proof) @@ -1246,10 +1247,9 @@ MPTTester::send(MPTConfidentialSend const& arg) std::optional auditorAmt; if (arg.auditorEncryptedAmt) - jv[sfAuditorEncryptedAmount] = strHex(*arg.auditorEncryptedAmt); + auditorAmt = arg.auditorEncryptedAmt; else if (auditor_.has_value()) - jv[sfAuditorEncryptedAmount] = - strHex(encryptAmount(*auditor_, *arg.amt, blindingFactor)); + auditorAmt = encryptAmount(*auditor_, *arg.amt, blindingFactor); jv[sfSenderEncryptedAmount] = strHex(senderAmt); jv[sfDestinationEncryptedAmount] = strHex(destAmt); diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index 0d4cce5b3d6..68cf954f7de 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -172,7 +172,7 @@ struct MPTConvert std::optional id = std::nullopt; std::optional amt = std::nullopt; std::optional proof = std::nullopt; - + std::optional fillAuditorEncryptedAmt = true; // indicates whether to autofill schnorr proof. // default : auto generate proof if holderPubKey is present. // true: force proof generation. From 24c15937c6d02dfaf8d4842e690c0967d14eb354 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2026 11:25:45 -0500 Subject: [PATCH 4/8] fix comments --- src/test/app/ConfidentialTransfer_test.cpp | 31 +++++++++++----------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index 61faef9618d..c5b94351b11 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -2177,36 +2177,35 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // we set the auditor key, but convertBack omits auditorEncryptedAmt { - Env env2{*this, features}; + Env env{*this, features}; Account const alice("alice"); Account const bob("bob"); Account const auditor("auditor"); - MPTTester mpt2(env2, alice, {.holders = {bob}, .auditor = auditor}); + MPTTester mpt(env, alice, {.holders = {bob}, .auditor = auditor}); - mpt2.create( + mpt.create( {.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); - mpt2.authorize({.account = bob}); - mpt2.pay(alice, bob, 100); - - mpt2.generateKeyPair(alice); - mpt2.generateKeyPair(bob); - mpt2.generateKeyPair(auditor); + mpt.authorize({.account = bob}); + mpt.pay(alice, bob, 100); - mpt2.set( + mpt.generateKeyPair(alice); + mpt.generateKeyPair(bob); + mpt.generateKeyPair(auditor); + mpt.set( {.account = alice, - .issuerPubKey = mpt2.getPubKey(alice), - .auditorPubKey = mpt2.getPubKey(auditor)}); + .issuerPubKey = mpt.getPubKey(alice), + .auditorPubKey = mpt.getPubKey(auditor)}); // Convert funds so Bob has a balance - mpt2.convert({ + mpt.convert({ .account = bob, .amt = 50, - .holderPubKey = mpt2.getPubKey(bob), + .holderPubKey = mpt.getPubKey(bob), }); - mpt2.mergeInbox({.account = bob}); + mpt.mergeInbox({.account = bob}); // ConvertBack WITHOUT auditorEncryptedAmt - mpt2.convertBack({ + mpt.convertBack({ .account = bob, .amt = 10, // we omit .auditorEncryptedAmt. From 98a207ff9ecbfa8118eddf0e91693d8fcfc119e0 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2026 13:09:09 -0500 Subject: [PATCH 5/8] add unit test --- src/test/app/ConfidentialTransfer_test.cpp | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index c5b94351b11..9cfd681ece0 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -459,6 +459,36 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .issuerPubKey = mptAlice.getPubKey(alice), .err = tecNO_PERMISSION}); } + + // issuer sets public key and then sets auditor public key + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + Account const auditor("auditor"); + MPTTester mptAlice( + env, alice, {.holders = {bob}, .auditor = auditor}); + + mptAlice.create( + {.ownerCount = 1, + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); + + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 100); + + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); + mptAlice.generateKeyPair(auditor); + + mptAlice.set( + {.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)}); + + mptAlice.set( + {.account = alice, + .issuerPubKey = mptAlice.getPubKey(alice), + .auditorPubKey = mptAlice.getPubKey(auditor), + .err = tecNO_PERMISSION}); + } } void From d296f980903b846cd725267d313720aca66460b7 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2026 17:48:32 -0500 Subject: [PATCH 6/8] fix comments --- src/test/app/ConfidentialTransfer_test.cpp | 86 ++++++++----------- src/test/jtx/impl/mpt.cpp | 22 +++-- src/test/jtx/mpt.h | 3 +- .../app/tx/detail/MPTokenIssuanceSet.cpp | 2 +- 4 files changed, 52 insertions(+), 61 deletions(-) diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index 9cfd681ece0..fba5c2c7e0b 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -429,64 +429,55 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .auditorPubKey = mptAlice.getPubKey(alice), .err = temMALFORMED}); - // Cannot set Holder and Keys in the same transaction + // Cannot set Holder and issuer Keys in the same transaction mptAlice.set( {.account = alice, .holder = bob, .issuerPubKey = mptAlice.getPubKey(alice), .err = temMALFORMED}); - } - - // issuance has disabled confidential transfer - { - Env env{*this, features}; - Account const alice("alice"); - Account const bob("bob"); - MPTTester mptAlice(env, alice, {.holders = {bob}}); - // no tfMPTCanPrivacy flag enabled - mptAlice.create( - {.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanLock}); - - mptAlice.authorize({.account = bob}); - mptAlice.pay(alice, bob, 100); + // Cannot set Holder and auditor Keys in the same transaction + mptAlice.set( + {.account = alice, + .holder = bob, + .auditorPubKey = mptAlice.getPubKey(alice), + .err = temMALFORMED}); - mptAlice.generateKeyPair(alice); - mptAlice.generateKeyPair(bob); + // Cannot set self as holder + mptAlice.set( + {.account = alice, .holder = alice, .err = temMALFORMED}); + // Cannot Lock and Unlock simultaneously mptAlice.set( {.account = alice, - .issuerPubKey = mptAlice.getPubKey(alice), - .err = tecNO_PERMISSION}); + .holder = bob, + .flags = tfMPTLock | tfMPTUnlock, + .err = temINVALID_FLAG}); + + // Transaction does nothing + mptAlice.set({.account = alice, .err = temMALFORMED}); } - // issuer sets public key and then sets auditor public key + // issuance has disabled confidential transfer { Env env{*this, features}; Account const alice("alice"); Account const bob("bob"); - Account const auditor("auditor"); - MPTTester mptAlice( - env, alice, {.holders = {bob}, .auditor = auditor}); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + // no tfMPTCanPrivacy flag enabled mptAlice.create( - {.ownerCount = 1, - .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); + {.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanLock}); mptAlice.authorize({.account = bob}); mptAlice.pay(alice, bob, 100); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); - mptAlice.generateKeyPair(auditor); - - mptAlice.set( - {.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)}); mptAlice.set( {.account = alice, .issuerPubKey = mptAlice.getPubKey(alice), - .auditorPubKey = mptAlice.getPubKey(auditor), .err = tecNO_PERMISSION}); } } @@ -1246,7 +1237,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .issuerEncryptedAmt = Buffer(ripple::ecGamalEncryptedTotalLength), .err = temBAD_CIPHERTEXT}); - // dest encrypted amount wrong length mptAlice.send( {.account = bob, @@ -1258,7 +1248,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .issuerEncryptedAmt = Buffer(ripple::ecGamalEncryptedTotalLength), .err = temBAD_CIPHERTEXT}); - // issuer encrypted amount wrong length mptAlice.send( {.account = bob, @@ -1272,7 +1261,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .err = temBAD_CIPHERTEXT}); // auto const ciphertextHex = generatePlaceholderCiphertext(); - // sender encrypted amount malformed mptAlice.send( {.account = bob, @@ -1281,7 +1269,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .senderEncryptedAmt = Buffer(ripple::ecGamalEncryptedTotalLength), .err = temBAD_CIPHERTEXT}); - // dest encrypted amount malformed mptAlice.send( {.account = bob, @@ -1290,7 +1277,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .destEncryptedAmt = Buffer(ripple::ecGamalEncryptedTotalLength), .err = temBAD_CIPHERTEXT}); - // issuer encrypted amount malformed mptAlice.send( {.account = bob, @@ -2211,34 +2197,34 @@ class ConfidentialTransfer_test : public beast::unit_test::suite Account const alice("alice"); Account const bob("bob"); Account const auditor("auditor"); - MPTTester mpt(env, alice, {.holders = {bob}, .auditor = auditor}); + MPTTester mptAlice( + env, alice, {.holders = {bob}, .auditor = auditor}); - mpt.create( + mptAlice.create( {.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); - mpt.authorize({.account = bob}); - mpt.pay(alice, bob, 100); + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 100); - mpt.generateKeyPair(alice); - mpt.generateKeyPair(bob); - mpt.generateKeyPair(auditor); - mpt.set( + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); + mptAlice.generateKeyPair(auditor); + mptAlice.set( {.account = alice, - .issuerPubKey = mpt.getPubKey(alice), - .auditorPubKey = mpt.getPubKey(auditor)}); + .issuerPubKey = mptAlice.getPubKey(alice), + .auditorPubKey = mptAlice.getPubKey(auditor)}); // Convert funds so Bob has a balance - mpt.convert({ + mptAlice.convert({ .account = bob, .amt = 50, - .holderPubKey = mpt.getPubKey(bob), + .holderPubKey = mptAlice.getPubKey(bob), }); - mpt.mergeInbox({.account = bob}); + mptAlice.mergeInbox({.account = bob}); // ConvertBack WITHOUT auditorEncryptedAmt - mpt.convertBack({ + mptAlice.convertBack({ .account = bob, .amt = 10, - // we omit .auditorEncryptedAmt. .fillAuditorEncryptedAmt = false, .err = tecNO_PERMISSION, }); diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index c1ce84cb129..660420877cc 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -482,8 +482,17 @@ MPTTester::set(MPTSet const& arg) return forObject([&](SLEP const& sle) -> bool { if (sle) { + if (!auditor_.has_value()) + Throw( + "MPTTester::set: auditor is not set"); + + auto const auditorPubKey = getPubKey(*auditor_); + if (!auditorPubKey) + Throw( + "MPTTester::set: auditor's pubkey is not set"); + return strHex((*sle)[sfAuditorElGamalPublicKey]) == - strHex(*arg.auditorPubKey); + strHex(*auditorPubKey); } return false; }); @@ -1011,8 +1020,7 @@ MPTTester::fillConversionCiphertexts( Buffer& holderCiphertext, Buffer& issuerCiphertext, std::optional& auditorCiphertext, - Buffer& blindingFactor, - bool fillAuditorEncryptedAmt) const + Buffer& blindingFactor) const { blindingFactor = arg.blindingFactor ? *arg.blindingFactor : generateBlindingFactor(); @@ -1037,7 +1045,7 @@ MPTTester::fillConversionCiphertexts( // Handle Auditor if (arg.auditorEncryptedAmt) auditorCiphertext = *arg.auditorEncryptedAmt; - else if (auditor_.has_value() && fillAuditorEncryptedAmt) + else if (auditor_.has_value() && *arg.fillAuditorEncryptedAmt) auditorCiphertext = encryptAmount(*auditor_, *arg.amt, blindingFactor); // Update auditor JSON only if ciphertext exists @@ -1080,8 +1088,7 @@ MPTTester::convert(MPTConvert const& arg) holderCiphertext, issuerCiphertext, auditorCiphertext, - blindingFactor, - *arg.fillAuditorEncryptedAmt); + blindingFactor); jv[sfBlindingFactor.jsonName] = strHex(blindingFactor); if (arg.proof) @@ -1768,8 +1775,7 @@ MPTTester::convertBack(MPTConvertBack const& arg) holderCiphertext, issuerCiphertext, auditorCiphertext, - blindingFactor, - *arg.fillAuditorEncryptedAmt); + blindingFactor); jv[sfBlindingFactor] = strHex(blindingFactor); diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index 68cf954f7de..baf547df798 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -535,8 +535,7 @@ class MPTTester Buffer& holderCiphertext, Buffer& issuerCiphertext, std::optional& auditorCiphertext, - Buffer& blindingFactor, - bool fillAuditorEncryptedAmt = true) const; + Buffer& blindingFactor) const; }; } // namespace jtx diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp index e33a59a26f8..8f9e93c11db 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp @@ -323,7 +323,7 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) if (ctx.tx.isFieldPresent(sfAuditorElGamalPublicKey) && sleMptIssuance->isFieldPresent(sfAuditorElGamalPublicKey)) { - return tecNO_PERMISSION; + return tecNO_PERMISSION; // LCOV_EXCL_LINE } if (ctx.tx.isFieldPresent(sfIssuerElGamalPublicKey) && From 1601943d5950b7bd6e41b8e05afa471f74d0ad33 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Thu, 29 Jan 2026 11:30:17 -0500 Subject: [PATCH 7/8] fix comments --- src/test/app/ConfidentialTransfer_test.cpp | 47 +++++++++++++++------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index fba5c2c7e0b..5fb541e4b4b 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -442,20 +442,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .holder = bob, .auditorPubKey = mptAlice.getPubKey(alice), .err = temMALFORMED}); - - // Cannot set self as holder - mptAlice.set( - {.account = alice, .holder = alice, .err = temMALFORMED}); - - // Cannot Lock and Unlock simultaneously - mptAlice.set( - {.account = alice, - .holder = bob, - .flags = tfMPTLock | tfMPTUnlock, - .err = temINVALID_FLAG}); - - // Transaction does nothing - mptAlice.set({.account = alice, .err = temMALFORMED}); } // issuance has disabled confidential transfer @@ -798,6 +784,39 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .err = tecNO_PERMISSION}); } + // Auditor key set successfully, auditor ciphertext mathematically + // correct, but contains invalid data (mismatching amount). + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + Account const auditor("auditor"); + MPTTester mptAlice( + env, alice, {.holders = {bob}, .auditor = auditor}); + + mptAlice.create( + {.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); + + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 100); + + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); + mptAlice.generateKeyPair(auditor); + + mptAlice.set( + {.account = alice, + .issuerPubKey = mptAlice.getPubKey(alice), + .auditorPubKey = mptAlice.getPubKey(auditor)}); + + mptAlice.convert( + {.account = bob, + .amt = 10, + .holderPubKey = mptAlice.getPubKey(bob), + .auditorEncryptedAmt = getTrivialCiphertext(), + .err = tecBAD_PROOF}); + } + // invalid proof when registering holder pub key { Env env{*this, features}; From 6b3ffc39e455405c5056eafbb53304c7a1a4b334 Mon Sep 17 00:00:00 2001 From: Peter Chen <34582813+PeterChen13579@users.noreply.github.com> Date: Thu, 29 Jan 2026 09:07:48 -0800 Subject: [PATCH 8/8] add convert back test --- src/test/app/ConfidentialTransfer_test.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index ce601fb7798..0532f991869 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -2280,6 +2280,14 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .fillAuditorEncryptedAmt = false, .err = tecNO_PERMISSION, }); + + // ConvertBack where auditor ciphertext mathematically + // correct, but contains invalid data (mismatching amount). + mptAlice.convertBack( + {.account = bob, + .amt = 10, + .auditorEncryptedAmt = getTrivialCiphertext(), + .err = tecBAD_PROOF}); } }