Add auditing feature across confidential transfer transactions (#6200)

This commit is contained in:
Shawn Xie
2026-01-14 11:18:06 -05:00
committed by GitHub
parent 6c38086f17
commit fa055c2bd5
12 changed files with 803 additions and 146 deletions

View File

@@ -17,6 +17,12 @@
namespace ripple {
struct CiphertextComponents
{
Buffer ciphertext;
Buffer randomness;
};
void
addCommonZKPFields(
Serializer& s,
@@ -236,8 +242,8 @@ proveEquality(
std::uint32_t const spendVersion);
// returns ciphertext and the blinding factor used
std::pair<Buffer, Buffer>
encryptAmount(uint64_t amt, Slice const& pubKeySlice);
CiphertextComponents
encryptAmount(uint64_t const amt, Slice const& pubKeySlice);
Buffer
encryptCanonicalZeroAmount(
@@ -277,6 +283,9 @@ verifyClawbackEqualityProof(
std::vector<Buffer>
getEqualityProofs(Slice const& zkp);
NotTEC
checkEncryptedAmountFormat(STObject const& object);
} // namespace ripple
#endif

View File

@@ -418,6 +418,7 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
{sfConfidentialBalanceSpending, soeOPTIONAL},
{sfConfidentialBalanceVersion, soeDEFAULT},
{sfIssuerEncryptedBalance, soeOPTIONAL},
{sfAuditorEncryptedBalance, soeOPTIONAL},
{sfHolderElGamalPublicKey, soeOPTIONAL},
}))

View File

@@ -723,6 +723,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet,
{sfTransferFee, soeOPTIONAL},
{sfMutableFlags, soeOPTIONAL},
{sfIssuerElGamalPublicKey, soeOPTIONAL},
{sfAuditorElGamalPublicKey, soeOPTIONAL},
}))
/** This transaction type authorizes a MPToken instance */
@@ -1073,6 +1074,7 @@ TRANSACTION(ttCONFIDENTIAL_CONVERT, 85, ConfidentialConvert,
{sfHolderElGamalPublicKey, soeOPTIONAL},
{sfHolderEncryptedAmount, soeREQUIRED},
{sfIssuerEncryptedAmount, soeREQUIRED},
{sfAuditorEncryptedAmount, soeOPTIONAL},
{sfZKProof, soeREQUIRED},
}))
@@ -1101,6 +1103,7 @@ TRANSACTION(ttCONFIDENTIAL_CONVERT_BACK, 87, ConfidentialConvertBack,
{sfMPTAmount, soeREQUIRED},
{sfHolderEncryptedAmount, soeREQUIRED},
{sfIssuerEncryptedAmount, soeREQUIRED},
{sfAuditorEncryptedAmount, soeOPTIONAL},
{sfZKProof, soeREQUIRED},
}))
@@ -1119,6 +1122,7 @@ TRANSACTION(ttCONFIDENTIAL_SEND, 88, ConfidentialSend,
{sfIssuerEncryptedAmount, soeREQUIRED},
{sfZKProof, soeREQUIRED},
{sfCredentialIDs, soeOPTIONAL},
{sfAuditorEncryptedAmount, soeOPTIONAL},
}))
#if TRANSACTION_INCLUDE

View File

@@ -856,8 +856,8 @@ proveEquality(
return tesSUCCESS;
}
std::pair<Buffer, Buffer>
encryptAmount(uint64_t amt, Slice const& pubKeySlice)
CiphertextComponents
encryptAmount(uint64_t const amt, Slice const& pubKeySlice)
{
Buffer buf(ecGamalEncryptedTotalLength);
@@ -884,7 +884,7 @@ encryptAmount(uint64_t amt, Slice const& pubKeySlice)
Throw<std::runtime_error>(
"Failed to serialize into 66 byte compressed format");
return std::make_pair(buf, Buffer(blindingFactor, 32));
return {std::move(buf), Buffer(blindingFactor, 32)};
}
Buffer
@@ -1057,4 +1057,28 @@ getEqualityProofs(Slice const& zkp)
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;
}
} // namespace ripple

View File

@@ -23,6 +23,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{
testcase("Convert");
using namespace test::jtx;
Env env{*this, features};
Account const alice("alice");
Account const bob("bob");
@@ -40,7 +41,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set({.account = alice, .pubKey = mptAlice.getPubKey(alice)});
mptAlice.set(
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
@@ -66,6 +68,55 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
});
}
void
testConvertWithAuditor(FeatureBitset features)
{
testcase("Convert with auditor");
using namespace test::jtx;
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,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(auditor);
mptAlice.set(
{.account = alice,
.issuerPubKey = mptAlice.getPubKey(alice),
.auditorPubKey = mptAlice.getPubKey(auditor)});
mptAlice.generateKeyPair(bob);
mptAlice.convert({
.account = bob,
.amt = 0,
.holderPubKey = mptAlice.getPubKey(bob),
});
mptAlice.convert({
.account = bob,
.amt = 20,
});
mptAlice.convert({
.account = bob,
.amt = 30,
});
}
void
testConvertPreflight(FeatureBitset features)
{
@@ -93,7 +144,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.set(
{.account = alice,
.pubKey = mptAlice.getPubKey(alice),
.issuerPubKey = mptAlice.getPubKey(alice),
.err = temDISABLED});
mptAlice.convert(
@@ -208,7 +259,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.set(
{.account = alice,
.pubKey = mptAlice.getPubKey(alice),
.issuerPubKey = mptAlice.getPubKey(alice),
.err = temDISABLED});
}
@@ -233,7 +284,9 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(bob);
mptAlice.set(
{.account = alice, .pubKey = Buffer{}, .err = temMALFORMED});
{.account = alice,
.issuerPubKey = Buffer{},
.err = temMALFORMED});
}
// issuance has disabled confidential transfer
@@ -259,7 +312,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.set(
{.account = alice,
.pubKey = mptAlice.getPubKey(alice),
.issuerPubKey = mptAlice.getPubKey(alice),
.err = tecNO_PERMISSION});
}
}
@@ -340,7 +393,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.destroy();
mptAlice.generateKeyPair(bob);
@@ -368,7 +421,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(bob);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.convert(
{.account = bob,
@@ -397,7 +450,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
@@ -428,7 +481,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
@@ -465,7 +518,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
@@ -509,7 +562,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
@@ -561,7 +614,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set({.account = alice, .pubKey = mptAlice.getPubKey(alice)});
mptAlice.set(
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
@@ -598,7 +652,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set({.account = alice, .pubKey = mptAlice.getPubKey(alice)});
mptAlice.set(
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
@@ -638,7 +693,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.destroy();
mptAlice.generateKeyPair(bob);
@@ -684,7 +739,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.mergeInbox({.account = bob, .err = tecOBJECT_NOT_FOUND});
}
@@ -709,7 +764,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
@@ -743,7 +798,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(bob);
mptAlice.generateKeyPair(carol);
mptAlice.set({.account = alice, .pubKey = mptAlice.getPubKey(alice)});
mptAlice.set(
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
// Convert 60 out of 100
mptAlice.convert(
@@ -819,6 +875,120 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.err = tesSUCCESS});
}
void
testSendWithAuditor(FeatureBitset features)
{
testcase("test confidential send with auditor");
using namespace test::jtx;
Env env{*this, features};
Account const alice("alice");
Account const bob("bob");
Account const carol("carol");
Account const auditor("auditor");
MPTTester mptAlice(
env, alice, {.holders = {bob, carol}, .auditor = auditor});
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
mptAlice.authorize({.account = carol});
mptAlice.pay(alice, bob, 100);
mptAlice.pay(alice, carol, 50);
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
mptAlice.generateKeyPair(carol);
mptAlice.generateKeyPair(auditor);
mptAlice.set(
{.account = alice,
.issuerPubKey = mptAlice.getPubKey(alice),
.auditorPubKey = mptAlice.getPubKey(auditor)});
// Convert 60 out of 100
mptAlice.convert(
{.account = bob,
.amt = 60,
.holderPubKey = mptAlice.getPubKey(bob),
.err = tesSUCCESS});
BEAST_EXPECT(mptAlice.getBalance(bob) == 40);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
bob, MPTTester::HOLDER_ENCRYPTED_INBOX) == 60);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
bob, MPTTester::HOLDER_ENCRYPTED_SPENDING) == 0);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
bob, MPTTester::ISSUER_ENCRYPTED_BALANCE) == 60);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
bob, MPTTester::AUDITOR_ENCRYPTED_BALANCE) == 60);
// bob merge inbox
mptAlice.mergeInbox({
.account = bob,
});
mptAlice.convert(
{.account = carol,
.amt = 20,
.holderPubKey = mptAlice.getPubKey(carol),
.err = tesSUCCESS});
BEAST_EXPECT(mptAlice.getBalance(carol) == 30);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
carol, MPTTester::HOLDER_ENCRYPTED_INBOX) == 20);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
carol, MPTTester::HOLDER_ENCRYPTED_SPENDING) == 0);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
carol, MPTTester::ISSUER_ENCRYPTED_BALANCE) == 20);
BEAST_EXPECT(
mptAlice.getDecryptedBalance(
carol, MPTTester::AUDITOR_ENCRYPTED_BALANCE) == 20);
// carol merge inbox
mptAlice.mergeInbox({
.account = carol,
});
// bob sends 10 to carol
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 10, // will be encrypted internally
.proof = "123",
.err = tesSUCCESS});
// bob sends 1 to carol again
mptAlice.send(
{.account = bob,
.dest = carol,
.amt = 1,
.proof = "123",
.err = tesSUCCESS});
mptAlice.mergeInbox({
.account = carol,
});
// carol sends 15 backto bob
mptAlice.send(
{.account = carol,
.dest = bob,
.amt = 15,
.proof = "123",
.err = tesSUCCESS});
}
void
testSendPreflight(FeatureBitset features)
{
@@ -871,7 +1041,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(bob);
mptAlice.generateKeyPair(carol);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.pay(alice, bob, 100);
mptAlice.pay(alice, carol, 50);
env.close();
@@ -1015,7 +1185,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(bob);
mptAlice.generateKeyPair(carol);
mptAlice.generateKeyPair(dave);
mptAlice.set({.account = alice, .pubKey = mptAlice.getPubKey(alice)});
mptAlice.set(
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
env.close();
// bob and carol convert some funds to confidential
@@ -1053,7 +1224,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.authorize({.account = carol});
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
// destroy the issuance
mptAlice.destroy();
@@ -1234,7 +1405,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(carol);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
// Convert 60 out of 100
mptAlice.convert(
@@ -1295,7 +1466,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
@@ -1333,7 +1504,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
mptAlice.generateKeyPair(carol);
@@ -1376,7 +1547,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
@@ -1392,8 +1563,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
});
}
// can delete mptoken if issuance has been destroyed and has encrypted
// zero balance
// can delete mptoken if issuance has been destroyed and has
// encrypted zero balance
{
Env env{*this, features};
Account const alice("alice");
@@ -1412,7 +1583,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
@@ -1437,6 +1608,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{
testcase("Convert back");
using namespace test::jtx;
Env env{*this, features};
Account const alice("alice");
Account const bob("bob");
@@ -1454,7 +1626,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set({.account = alice, .pubKey = mptAlice.getPubKey(alice)});
mptAlice.set(
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
@@ -1473,12 +1646,55 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.amt = 30,
.proof = "123",
});
}
// mptAlice.convertBack({
// .account = bob,
// .amt = 10,
// .proof = "123",
// });
void
testConvertBackWithAuditor(FeatureBitset features)
{
testcase("Convert back with auditor");
using namespace test::jtx;
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,
.holderCount = 0,
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
env.close();
mptAlice.pay(alice, bob, 100);
env.close();
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(auditor);
mptAlice.set(
{.account = alice,
.issuerPubKey = mptAlice.getPubKey(alice),
.auditorPubKey = mptAlice.getPubKey(auditor)});
mptAlice.generateKeyPair(bob);
mptAlice.convert({
.account = bob,
.amt = 40,
.holderPubKey = mptAlice.getPubKey(bob),
});
mptAlice.mergeInbox({
.account = bob,
});
mptAlice.convertBack({
.account = bob,
.amt = 30,
.proof = "123",
});
}
void
@@ -1532,7 +1748,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
@@ -1618,7 +1834,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.destroy();
mptAlice.generateKeyPair(bob);
@@ -1673,7 +1889,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(bob);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.convertBack(
{.account = bob,
@@ -1702,7 +1918,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
@@ -1736,7 +1952,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
mptAlice.generateKeyPair(carol);
@@ -1786,7 +2002,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.generateKeyPair(bob);
@@ -1859,7 +2075,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(bob);
mptAlice.generateKeyPair(carol);
mptAlice.set({.account = alice, .pubKey = mptAlice.getPubKey(alice)});
mptAlice.set(
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
// Bob require preauthorization
env(fset(bob, asfDepositAuth));
@@ -1986,7 +2203,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(bob);
mptAlice.generateKeyPair(carol);
mptAlice.generateKeyPair(dave);
mptAlice.set({.account = alice, .pubKey = mptAlice.getPubKey(alice)});
mptAlice.set(
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
// setup bob.
// after setup, bob's spending balance is 60, inbox balance is 0.
@@ -2027,15 +2245,114 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
.holderPubKey = mptAlice.getPubKey(dave)});
// setup: carol confidential send 50 to bob.
// after send, bob's inbox balance is 50, spending balance remains 60.
// carol's inbox balance remains 0, spending balance drops to 70.
// after send, bob's inbox balance is 50, spending balance
// remains 60. carol's inbox balance remains 0, spending balance
// drops to 70.
mptAlice.send(
{.account = carol, .dest = bob, .amt = 50, .proof = "123"});
// alice clawback all confidential balance from bob, 110 in total.
// bob has balance in both inbox and spending. These balances should
// become zero after clawback, which is verified in the confidentialClaw
// function.
// become zero after clawback, which is verified in the
// confidentialClaw function.
mptAlice.confidentialClaw(
{.account = alice, .holder = bob, .amt = 110});
// alice clawback all confidential balance from carol, which is 70.
// carol only has balance in spending.
mptAlice.confidentialClaw(
{.account = alice, .holder = carol, .amt = 70});
// alice clawback all confidential balance from dave, which is 200.
// dave only has balance in inbox.
mptAlice.confidentialClaw(
{.account = alice, .holder = dave, .amt = 200});
}
void
testClawbackWithAuditor(FeatureBitset features)
{
testcase("test ConfidentialClawback with auditor");
using namespace test::jtx;
Env env{*this, features};
Account const alice("alice");
Account const bob("bob");
Account const carol("carol");
Account const dave("dave");
Account const auditor("auditor");
MPTTester mptAlice(
env, alice, {.holders = {bob, carol, dave}, .auditor = auditor});
mptAlice.create(
{.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanClawback |
tfMPTCanPrivacy});
mptAlice.authorize({.account = bob});
mptAlice.pay(alice, bob, 100);
mptAlice.authorize({.account = carol});
mptAlice.pay(alice, carol, 200);
mptAlice.authorize({.account = dave});
mptAlice.pay(alice, dave, 300);
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
mptAlice.generateKeyPair(carol);
mptAlice.generateKeyPair(dave);
mptAlice.generateKeyPair(auditor);
mptAlice.set(
{.account = alice,
.issuerPubKey = mptAlice.getPubKey(alice),
.auditorPubKey = mptAlice.getPubKey(auditor)});
// setup bob.
// after setup, bob's spending balance is 60, inbox balance is 0.
{
// bob converts 60 to confidential
mptAlice.convert(
{.account = bob,
.amt = 60,
.holderPubKey = mptAlice.getPubKey(bob)});
// bob merge inbox
mptAlice.mergeInbox({
.account = bob,
});
}
// setup carol.
// after setup, carol's spending balance is 120, inbox balance is 0.
{
// carol converts 120 to confidential
mptAlice.convert(
{.account = carol,
.amt = 120,
.holderPubKey = mptAlice.getPubKey(carol)});
// carol merge inbox
mptAlice.mergeInbox({
.account = carol,
});
}
// setup dave.
// dave will not merge inbox.
// after setup, dave's inbox balance is 200, spending balance is 0.
mptAlice.convert(
{.account = dave,
.amt = 200,
.holderPubKey = mptAlice.getPubKey(dave)});
// setup: carol confidential send 50 to bob.
// after send, bob's inbox balance is 50, spending balance
// remains 60. carol's inbox balance remains 0, spending balance
// drops to 70.
mptAlice.send(
{.account = carol, .dest = bob, .amt = 50, .proof = "123"});
// alice clawback all confidential balance from bob, 110 in total.
// bob has balance in both inbox and spending. These balances should
// become zero after clawback, which is verified in the
// confidentialClaw function.
mptAlice.confidentialClaw(
{.account = alice, .holder = bob, .amt = 110});
@@ -2095,7 +2412,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(bob);
mptAlice.generateKeyPair(carol);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.pay(alice, bob, 100);
mptAlice.pay(alice, carol, 50);
env.close();
@@ -2158,8 +2475,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
{
// set up, alice is the issuer, bob and carol are authorized
// holders. dave is not authorized. bob has confidential balance,
// carol does not.
// holders. dave is not authorized. bob has confidential
// balance, carol does not.
Env env{*this, features};
Account const alice("alice");
Account const bob("bob");
@@ -2181,7 +2498,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(bob);
mptAlice.generateKeyPair(carol);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.convert({
.account = bob,
@@ -2232,7 +2549,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.authorize({.account = bob});
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
env.close();
mptAlice.confidentialClaw(
@@ -2270,7 +2587,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.authorize({.account = bob});
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
// destroy the issuance
mptAlice.destroy();
@@ -2304,7 +2621,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
mptAlice.convert(
{.account = bob,
.amt = 60,
@@ -2383,8 +2700,9 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
Account const bob("bob");
Account const carol("carol");
// lambda function to set up MPT with alice as issuer, bob and carol as
// authorized holders, and fund 1000 mpt to bob and 2000 mpt to carol.
// lambda function to set up MPT with alice as issuer, bob and carol
// as authorized holders, and fund 1000 mpt to bob and 2000 mpt to
// carol.
auto setupEnv = [&](Env& env) -> MPTTester {
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
@@ -2402,7 +2720,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
return mptAlice;
};
@@ -2493,8 +2811,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
// bob: 100 in inbox, 200 in spending
checkBadProofs(mptAlice, bob, {1, 10, 50, 100, 200, 299, 301, 400});
// proof failure for incorrect amount when clawbacking from carol
// carol: 100 in inbox, 300 in spending
// proof failure for incorrect amount when clawbacking from
// carol carol: 100 in inbox, 300 in spending
checkBadProofs(
mptAlice, carol, {1, 10, 50, 100, 300, 399, 401, 501});
@@ -2582,7 +2900,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
auto holderPubKeySet = false;
auto verifyToggle = [&](TER expectedResult, uint64_t amt) {
@@ -2613,8 +2931,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
}
};
// set lsfMPTCanPrivacy, but no effect because lsfMPTCanPrivacy was
// already set
// set lsfMPTCanPrivacy, but no effect because lsfMPTCanPrivacy
// was already set
mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetPrivacy});
verifyToggle(tesSUCCESS, 10);
@@ -2654,7 +2972,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
mptAlice.generateKeyPair(alice);
mptAlice.generateKeyPair(bob);
mptAlice.set(
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
{.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
// bob convert 50 to confidential
mptAlice.convert(
@@ -2710,6 +3028,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
testConvert(features);
testConvertPreflight(features);
testConvertPreclaim(features);
testConvertWithAuditor(features);
testMergeInbox(features);
testMergeInboxPreflight(features);
@@ -2722,18 +3041,21 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
testSendPreflight(features);
testSendPreclaim(features);
testSendDepositPreauth(features);
testSendWithAuditor(features);
// ConfidentialClawback
testClawback(features);
testClawbackPreflight(features);
testClawbackPreclaim(features);
testClawbackProof(features);
testClawbackWithAuditor(features);
testDelete(features);
testConvertBack(features);
testConvertBackPreflight(features);
testConvertBackPreclaim(features);
testConvertBackWithAuditor(features);
testMutatePrivacy(features);
}

View File

@@ -64,6 +64,7 @@ MPTTester::MPTTester(Env& env, Account const& issuer, MPTInit const& arg)
: env_(env)
, issuer_(issuer)
, holders_(makeHolders(arg.holders))
, auditor_(arg.auditor)
, close_(arg.close)
{
if (arg.fund)
@@ -71,6 +72,9 @@ MPTTester::MPTTester(Env& env, Account const& issuer, MPTInit const& arg)
env_.fund(arg.xrp, issuer_);
for (auto it : holders_)
env_.fund(arg.xrpHolders, it.second);
if (arg.auditor)
env_.fund(arg.xrp, *arg.auditor);
}
if (close_)
env.close();
@@ -83,6 +87,9 @@ MPTTester::MPTTester(Env& env, Account const& issuer, MPTInit const& arg)
Throw<std::runtime_error>("Issuer can't be holder");
env_.require(owners(it.second, 0));
}
if (arg.auditor)
env_.require(owners(*arg.auditor, 0));
}
if (arg.create)
create(*arg.create);
@@ -125,9 +132,11 @@ MPTTester::MPTTester(MPTInitDef const& arg)
arg.env,
arg.issuer,
MPTInit{
.auditor = arg.auditor,
.fund = arg.fund,
.close = arg.close,
.create = makeMPTCreate(arg)}}
.create = makeMPTCreate(arg),
}}
{
}
@@ -374,8 +383,10 @@ MPTTester::setjv(MPTSet const& arg)
jv[sfTransferFee] = *arg.transferFee;
if (arg.metadata)
jv[sfMPTokenMetadata] = strHex(*arg.metadata);
if (arg.pubKey)
jv[sfIssuerElGamalPublicKey] = strHex(*arg.pubKey);
if (arg.issuerPubKey)
jv[sfIssuerElGamalPublicKey] = strHex(*arg.issuerPubKey);
if (arg.auditorPubKey)
jv[sfAuditorElGamalPublicKey] = strHex(*arg.auditorPubKey);
jv[sfTransactionType] = jss::MPTokenIssuanceSet;
return jv;
@@ -395,7 +406,8 @@ MPTTester::set(MPTSet const& arg)
.metadata = arg.metadata,
.delegate = arg.delegate,
.domainID = arg.domainID,
.pubKey = arg.pubKey});
.issuerPubKey = arg.issuerPubKey,
.auditorPubKey = arg.auditorPubKey});
if (submit(arg, jv) == tesSUCCESS)
{
if ((arg.flags.value_or(0) || arg.mutableFlags))
@@ -461,7 +473,7 @@ MPTTester::set(MPTSet const& arg)
require(*account, false);
}
if (arg.pubKey)
if (arg.issuerPubKey)
{
env_.require(requireAny([&]() -> bool {
return forObject([&](SLEP const& sle) -> bool {
@@ -474,6 +486,23 @@ MPTTester::set(MPTSet const& arg)
});
}));
}
if (arg.auditorPubKey)
{
env_.require(requireAny([&]() -> bool {
return forObject([&](SLEP const& sle) -> bool {
if (sle)
{
if (!auditor_)
Throw<std::runtime_error>(
"MPTTester::set: auditor is not set");
return strHex((*sle)[sfAuditorElGamalPublicKey]) ==
strHex(getPubKey(*auditor_));
}
return false;
});
}));
}
}
}
@@ -780,9 +809,9 @@ MPTTester::getConvertProof(
Account const& holder,
std::uint64_t amount,
uint256 const& ctxHash,
std::pair<Buffer, Buffer> holderCiphertext,
std::pair<Buffer, Buffer> issuerCiphertext,
std::optional<std::pair<Buffer, Buffer>> auditorCiphertext) const
CiphertextComponents holderCiphertext,
CiphertextComponents issuerCiphertext,
std::optional<CiphertextComponents> auditorCiphertext) const
{
if (!id_)
Throw<std::runtime_error>("MPT has not been created");
@@ -793,8 +822,8 @@ MPTTester::getConvertProof(
size_t const zkpSize = auditorCiphertext ? 3 : 2;
size_t const zkpByteLength = zkpSize * ecEqualityProofLength;
if (!sleHolder || !sleIssuance || holderCiphertext.first.size() == 0 ||
issuerCiphertext.first.size() == 0)
if (!sleHolder || !sleIssuance || holderCiphertext.ciphertext.size() == 0 ||
issuerCiphertext.ciphertext.size() == 0)
return Buffer(zkpByteLength);
auto const generateProof = [amount, ctxHash](
@@ -836,22 +865,14 @@ MPTTester::getConvertProof(
Buffer zkp(zkpByteLength);
Buffer holderZkp = generateProof(
holderCiphertext.first, getPubKey(holder), holderCiphertext.second);
holderCiphertext.ciphertext,
getPubKey(holder),
holderCiphertext.randomness);
Buffer issuerZkp = generateProof(
issuerCiphertext.first, getPubKey(issuer_), issuerCiphertext.second);
// std::optional<Slice> 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();
// }
issuerCiphertext.ciphertext,
getPubKey(issuer_),
issuerCiphertext.randomness);
// Pointer arithmetic to copy data into place
std::uint8_t* ptr = zkp.data();
@@ -864,6 +885,26 @@ MPTTester::getConvertProof(
std::memcpy(ptr, issuerZkp.data(), issuerZkp.size());
ptr += issuerZkp.size();
if (auditorCiphertext)
{
Buffer auditorZkp(ecEqualityProofLength);
if (sleIssuance->isFieldPresent(sfAuditorElGamalPublicKey))
{
Buffer const auditorPubKey(
sleIssuance->getFieldVL(sfAuditorElGamalPublicKey).data(),
sleIssuance->getFieldVL(sfAuditorElGamalPublicKey).size());
auditorZkp = generateProof(
auditorCiphertext->ciphertext,
auditorPubKey,
auditorCiphertext->randomness);
}
// Copy auditor
std::memcpy(ptr, auditorZkp.data(), auditorZkp.size());
ptr += auditorZkp.size();
}
return zkp;
}
@@ -892,6 +933,11 @@ MPTTester::getEncryptedBalance(
return Buffer(
(*sle)[sfIssuerEncryptedBalance].data(),
(*sle)[sfIssuerEncryptedBalance].size());
if (option == AUDITOR_ENCRYPTED_BALANCE &&
sle->isFieldPresent(sfAuditorEncryptedBalance))
return Buffer(
(*sle)[sfAuditorEncryptedBalance].data(),
(*sle)[sfAuditorEncryptedBalance].size());
}
return {};
@@ -947,28 +993,43 @@ MPTTester::convert(MPTConvert const& arg)
if (arg.holderPubKey)
jv[sfHolderElGamalPublicKey.jsonName] = strHex(*arg.holderPubKey);
std::pair<Buffer, Buffer> holderCiphertext;
CiphertextComponents holderCiphertext;
if (arg.holderEncryptedAmt)
jv[sfHolderEncryptedAmount.jsonName] = strHex(*arg.holderEncryptedAmt);
else
{
holderCiphertext = encryptAmount(*arg.account, *arg.amt);
jv[sfHolderEncryptedAmount.jsonName] = strHex(holderCiphertext.first);
jv[sfHolderEncryptedAmount.jsonName] =
strHex(holderCiphertext.ciphertext);
}
std::pair<Buffer, Buffer> issuerCiphertext;
CiphertextComponents issuerCiphertext;
if (arg.issuerEncryptedAmt)
jv[sfIssuerEncryptedAmount.jsonName] = strHex(*arg.issuerEncryptedAmt);
else
{
issuerCiphertext = encryptAmount(issuer_, *arg.amt);
jv[sfIssuerEncryptedAmount.jsonName] = strHex(issuerCiphertext.first);
jv[sfIssuerEncryptedAmount.jsonName] =
strHex(issuerCiphertext.ciphertext);
}
std::optional<CiphertextComponents> auditorCiphertext;
if (arg.auditorEncryptedAmt)
jv[sfAuditorEncryptedAmount.jsonName] =
strHex(*arg.auditorEncryptedAmt);
else if (auditor())
{
auditorCiphertext = encryptAmount(*auditor(), *arg.amt);
jv[sfAuditorEncryptedAmount.jsonName] =
strHex(auditorCiphertext->ciphertext);
}
if (arg.proof)
jv[sfZKProof.jsonName] = *arg.proof;
else
{
// if the caller generated ciphertexts themselves, they should also
// generate the proof themselves from the randomness factor
uint256 const ctxHash = getConvertContextHash(
arg.account->id(), env_.seq(*arg.account), *id_, *arg.amt);
Buffer proof = getConvertProof(
@@ -977,7 +1038,7 @@ MPTTester::convert(MPTConvert const& arg)
ctxHash,
holderCiphertext,
issuerCiphertext,
{});
auditorCiphertext);
jv[sfZKProof] = strHex(proof);
}
@@ -990,6 +1051,8 @@ MPTTester::convert(MPTConvert const& arg)
getDecryptedBalance(*arg.account, HOLDER_ENCRYPTED_SPENDING);
uint64_t prevIssuerBalance =
getDecryptedBalance(*arg.account, ISSUER_ENCRYPTED_BALANCE);
[[maybe_unused]] uint64_t prevAuditorBalance =
getDecryptedBalance(*arg.account, AUDITOR_ENCRYPTED_BALANCE);
if (submit(arg, jv) == tesSUCCESS)
{
@@ -1008,6 +1071,15 @@ MPTTester::convert(MPTConvert const& arg)
uint64_t postSpendingBalance =
getDecryptedBalance(*arg.account, HOLDER_ENCRYPTED_SPENDING);
if (arg.auditorEncryptedAmt || auditor_)
{
uint64_t postAuditorBalance =
getDecryptedBalance(*arg.account, AUDITOR_ENCRYPTED_BALANCE);
// auditor's encrypted balance is updated correctly
env_.require(requireAny([&]() -> bool {
return prevAuditorBalance + *arg.amt == postAuditorBalance;
}));
}
// spending balance should not change
env_.require(requireAny([&]() -> bool {
return postSpendingBalance == prevSpendingBalance;
@@ -1076,19 +1148,25 @@ MPTTester::send(MPTConfidentialSend const& arg)
jv[sfSenderEncryptedAmount] = strHex(*arg.senderEncryptedAmt);
else
jv[sfSenderEncryptedAmount] =
strHex(encryptAmount(*arg.account, *arg.amt).first);
strHex(encryptAmount(*arg.account, *arg.amt).ciphertext);
if (arg.destEncryptedAmt)
jv[sfDestinationEncryptedAmount] = strHex(*arg.destEncryptedAmt);
else
jv[sfDestinationEncryptedAmount] =
strHex(encryptAmount(*arg.dest, *arg.amt).first);
strHex(encryptAmount(*arg.dest, *arg.amt).ciphertext);
if (arg.issuerEncryptedAmt)
jv[sfIssuerEncryptedAmount] = strHex(*arg.issuerEncryptedAmt);
else
jv[sfIssuerEncryptedAmount] =
strHex(encryptAmount(issuer_, *arg.amt).first);
strHex(encryptAmount(issuer_, *arg.amt).ciphertext);
if (arg.auditorEncryptedAmt)
jv[sfAuditorEncryptedAmount] = strHex(*arg.auditorEncryptedAmt);
else if (auditor())
jv[sfAuditorEncryptedAmount] =
strHex(encryptAmount(*auditor(), *arg.amt).ciphertext);
if (arg.proof)
jv[sfZKProof] = *arg.proof;
@@ -1112,6 +1190,8 @@ MPTTester::send(MPTConfidentialSend const& arg)
getDecryptedBalance(*arg.account, HOLDER_ENCRYPTED_SPENDING);
uint64_t prevSenderIssuer =
getDecryptedBalance(*arg.account, ISSUER_ENCRYPTED_BALANCE);
[[maybe_unused]] uint64_t prevSenderAuditor =
getDecryptedBalance(*arg.account, AUDITOR_ENCRYPTED_BALANCE);
// Destination's previous confidential state
uint64_t prevDestInbox =
@@ -1120,6 +1200,8 @@ MPTTester::send(MPTConfidentialSend const& arg)
getDecryptedBalance(*arg.dest, HOLDER_ENCRYPTED_SPENDING);
uint64_t prevDestIssuer =
getDecryptedBalance(*arg.dest, ISSUER_ENCRYPTED_BALANCE);
[[maybe_unused]] uint64_t prevDestAuditor =
getDecryptedBalance(*arg.dest, AUDITOR_ENCRYPTED_BALANCE);
if (submit(arg, jv) == tesSUCCESS)
{
@@ -1179,6 +1261,30 @@ MPTTester::send(MPTConfidentialSend const& arg)
env_.require(requireAny([&]() -> bool {
return postDestInbox + postDestSpending == postDestIssuer;
}));
if (arg.auditorEncryptedAmt || auditor_)
{
uint64_t postSenderAuditor =
getDecryptedBalance(*arg.account, AUDITOR_ENCRYPTED_BALANCE);
uint64_t postDestAuditor =
getDecryptedBalance(*arg.dest, AUDITOR_ENCRYPTED_BALANCE);
env_.require(requireAny([&]() -> bool {
return postSenderAuditor == postSenderIssuer &&
postDestAuditor == postDestIssuer;
}));
// verify sender
env_.require(requireAny([&]() -> bool {
return prevSenderAuditor >= *arg.amt &&
postSenderAuditor == prevSenderAuditor - *arg.amt;
}));
// verify dest
env_.require(requireAny([&]() -> bool {
return postDestAuditor == prevDestAuditor + *arg.amt;
}));
}
}
}
@@ -1251,6 +1357,10 @@ MPTTester::confidentialClaw(MPTConfidentialClawback const& arg)
return getDecryptedBalance(*arg.holder, ISSUER_ENCRYPTED_BALANCE) ==
0;
}));
env_.require(requireAny([&]() -> bool {
return getDecryptedBalance(
*arg.holder, AUDITOR_ENCRYPTED_BALANCE) == 0;
}));
}
}
@@ -1291,8 +1401,8 @@ MPTTester::getPrivKey(Account const& account) const
Throw<std::runtime_error>("Account does not have private key");
}
std::pair<Buffer, Buffer>
MPTTester::encryptAmount(Account const& account, uint64_t amt) const
CiphertextComponents
MPTTester::encryptAmount(Account const& account, uint64_t const amt) const
{
return ripple::encryptAmount(amt, getPubKey(account));
}
@@ -1327,8 +1437,17 @@ MPTTester::getDecryptedBalance(
{
auto maybeEncrypted = getEncryptedBalance(account, balanceType);
auto accountToDecrypt =
balanceType == ISSUER_ENCRYPTED_BALANCE ? issuer_ : account;
Account accountToDecrypt = account;
if (balanceType == ISSUER_ENCRYPTED_BALANCE)
accountToDecrypt = issuer_;
else if (balanceType == AUDITOR_ENCRYPTED_BALANCE)
{
if (!auditor_)
return 0;
accountToDecrypt = *auditor_;
}
return maybeEncrypted ? decryptAmount(accountToDecrypt, *maybeEncrypted)
: 0;
};
@@ -1420,13 +1539,24 @@ MPTTester::convertBack(MPTConvertBack const& arg)
jv[sfHolderEncryptedAmount.jsonName] = strHex(*arg.holderEncryptedAmt);
else
jv[sfHolderEncryptedAmount.jsonName] =
strHex(encryptAmount(*arg.account, *arg.amt).first);
strHex(encryptAmount(*arg.account, *arg.amt).ciphertext);
if (arg.issuerEncryptedAmt)
jv[sfIssuerEncryptedAmount.jsonName] = strHex(*arg.issuerEncryptedAmt);
else
jv[sfIssuerEncryptedAmount.jsonName] =
strHex(encryptAmount(issuer_, *arg.amt).first);
strHex(encryptAmount(issuer_, *arg.amt).ciphertext);
std::optional<CiphertextComponents> auditorCiphertext;
if (arg.auditorEncryptedAmt)
jv[sfAuditorEncryptedAmount.jsonName] =
strHex(*arg.auditorEncryptedAmt);
else if (auditor())
{
auditorCiphertext = encryptAmount(*auditor(), *arg.amt);
jv[sfAuditorEncryptedAmount.jsonName] =
strHex(auditorCiphertext->ciphertext);
}
if (arg.proof)
jv[sfZKProof.jsonName] = *arg.proof;
@@ -1440,6 +1570,8 @@ MPTTester::convertBack(MPTConvertBack const& arg)
getDecryptedBalance(*arg.account, HOLDER_ENCRYPTED_SPENDING);
uint64_t prevIssuerBalance =
getDecryptedBalance(*arg.account, ISSUER_ENCRYPTED_BALANCE);
[[maybe_unused]] uint64_t prevAuditorBalance =
getDecryptedBalance(*arg.account, AUDITOR_ENCRYPTED_BALANCE);
if (submit(arg, jv) == tesSUCCESS)
{
@@ -1458,6 +1590,16 @@ MPTTester::convertBack(MPTConvertBack const& arg)
uint64_t postSpendingBalance =
getDecryptedBalance(*arg.account, HOLDER_ENCRYPTED_SPENDING);
if (arg.auditorEncryptedAmt || auditor_)
{
uint64_t postAuditorBalance =
getDecryptedBalance(*arg.account, AUDITOR_ENCRYPTED_BALANCE);
// auditor's encrypted balance is updated correctly
env_.require(requireAny([&]() -> bool {
return prevAuditorBalance - *arg.amt == postAuditorBalance;
}));
}
// inbox balance should not change
env_.require(requireAny(
[&]() -> bool { return postInboxBalance == prevInboxBalance; }));

View File

@@ -6,6 +6,7 @@
#include <test/jtx/ter.h>
#include <test/jtx/txflags.h>
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/UintTypes.h>
#include <cstdint>
@@ -104,6 +105,7 @@ struct MPTCreate
struct MPTInit
{
Holders holders = {};
std::optional<Account> auditor = std::nullopt;
PrettyAmount const xrp = XRP(10'000);
PrettyAmount const xrpHolders = XRP(10'000);
bool fund = true;
@@ -118,6 +120,7 @@ struct MPTInitDef
Env& env;
Account issuer;
Holders holders = {};
std::optional<Account> auditor = std::nullopt;
std::uint16_t transferFee = 0;
std::optional<std::uint64_t> pay = std::nullopt;
std::uint32_t flags = MPTDEXFlags;
@@ -162,7 +165,8 @@ struct MPTSet
std::optional<std::string> metadata = std::nullopt;
std::optional<Account> delegate = std::nullopt;
std::optional<uint256> domainID = std::nullopt;
std::optional<Buffer> pubKey = std::nullopt;
std::optional<Buffer> issuerPubKey = std::nullopt;
std::optional<Buffer> auditorPubKey = std::nullopt;
std::optional<TER> err = std::nullopt;
};
@@ -203,6 +207,7 @@ struct MPTConfidentialSend
std::optional<Buffer> senderEncryptedAmt = std::nullopt;
std::optional<Buffer> destEncryptedAmt = std::nullopt;
std::optional<Buffer> issuerEncryptedAmt = std::nullopt;
std::optional<Buffer> auditorEncryptedAmt = std::nullopt;
std::optional<std::vector<std::string>> credentials = std::nullopt;
std::optional<std::uint32_t> ownerCount = std::nullopt;
std::optional<std::uint32_t> holderCount = std::nullopt;
@@ -218,6 +223,7 @@ struct MPTConvertBack
std::optional<std::string> proof = std::nullopt;
std::optional<Buffer> holderEncryptedAmt = std::nullopt;
std::optional<Buffer> issuerEncryptedAmt = std::nullopt;
std::optional<Buffer> auditorEncryptedAmt = std::nullopt;
std::optional<std::uint32_t> ownerCount = std::nullopt;
std::optional<std::uint32_t> holderCount = std::nullopt;
std::optional<std::uint32_t> flags = std::nullopt;
@@ -242,6 +248,7 @@ class MPTTester
Env& env_;
Account const issuer_;
std::unordered_map<std::string, Account> const holders_;
std::optional<Account> const auditor_;
std::optional<MPTID> id_;
bool close_;
std::unordered_map<AccountID, Buffer> pubKeys;
@@ -252,6 +259,7 @@ public:
ISSUER_ENCRYPTED_BALANCE,
HOLDER_ENCRYPTED_INBOX,
HOLDER_ENCRYPTED_SPENDING,
AUDITOR_ENCRYPTED_BALANCE,
};
MPTTester(Env& env, Account const& issuer, MPTInit const& constr = {});
@@ -342,6 +350,13 @@ public:
{
return issuer_;
}
std::optional<Account> const&
auditor() const
{
return auditor_;
}
Account const&
holder(std::string const& h) const;
@@ -401,8 +416,8 @@ public:
Buffer
getPrivKey(Account const& account) const;
std::pair<Buffer, Buffer>
encryptAmount(Account const& account, uint64_t amt) const;
CiphertextComponents
encryptAmount(Account const& account, uint64_t const amt) const;
uint64_t
decryptAmount(Account const& account, Buffer const& amt) const;
@@ -427,9 +442,9 @@ public:
Account const& holder,
std::uint64_t amount,
uint256 const& ctxHash,
std::pair<Buffer, Buffer> holderCiphertext,
std::pair<Buffer, Buffer> issuerCiphertext,
std::optional<std::pair<Buffer, Buffer>> auditorCiphertext) const;
CiphertextComponents holderCiphertext,
CiphertextComponents issuerCiphertext,
std::optional<CiphertextComponents> auditorCiphertext) const;
private:
using SLEP = SLE::const_pointer;

View File

@@ -126,7 +126,7 @@ ConfidentialClawback::doApply()
JLOG(ctx_.journal.error())
<< "ConfidentialClawback: Failed to generate canonical zero: "
<< e.what();
return tecINTERNAL;
return tecINTERNAL; // LCOV_EXCL_LINE
}
// Set holder's confidential balances to encrypted zero
@@ -135,6 +135,30 @@ ConfidentialClawback::doApply()
(*sleHolderMPToken)[sfIssuerEncryptedBalance] = encZeroForIssuer;
(*sleHolderMPToken)[sfConfidentialBalanceVersion] = 0;
if (sleHolderMPToken->isFieldPresent(sfAuditorEncryptedBalance))
{
// check that issuance has auditor's pubkey before accessing it, if the
// field is absent, something has gone bad
if (!sleIssuance->isFieldPresent(sfAuditorElGamalPublicKey))
return tecINTERNAL; // LCOV_EXCL_LINE
Slice const auditorPubKey = (*sleIssuance)[sfAuditorElGamalPublicKey];
try
{
Buffer const encZeroForAuditor = encryptCanonicalZeroAmount(
auditorPubKey, holder, mptIssuanceID);
(*sleHolderMPToken)[sfAuditorEncryptedBalance] = encZeroForAuditor;
}
catch (std::exception const& e)
{
JLOG(ctx_.journal.error())
<< "ConfidentialClawback: Failed to generate canonical zero: "
<< e.what();
return tecINTERNAL; // LCOV_EXCL_LINE
}
}
// Decrease Global Confidential Outstanding Amount
auto const oldCOA = (*sleIssuance)[sfConfidentialOutstandingAmount];
(*sleIssuance)[sfConfidentialOutstandingAmount] = oldCOA - clawAmount;

View File

@@ -21,18 +21,12 @@ ConfidentialConvert::preflight(PreflightContext const& ctx)
if (MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer() == ctx.tx[sfAccount])
return temMALFORMED;
if (ctx.tx[sfHolderEncryptedAmount].length() !=
ecGamalEncryptedTotalLength ||
ctx.tx[sfIssuerEncryptedAmount].length() != ecGamalEncryptedTotalLength)
return temBAD_CIPHERTEXT;
if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res))
return res;
if (ctx.tx[sfMPTAmount] > maxMPTokenAmount)
return temBAD_AMOUNT;
if (!isValidCiphertext(ctx.tx[sfHolderEncryptedAmount]) ||
!isValidCiphertext(ctx.tx[sfIssuerEncryptedAmount]))
return temBAD_CIPHERTEXT;
if (ctx.tx.isFieldPresent(sfHolderElGamalPublicKey) &&
ctx.tx[sfHolderElGamalPublicKey].length() != ecPubKeyLength)
return temMALFORMED;
@@ -66,6 +60,12 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx)
if (!sleIssuance->isFieldPresent(sfIssuerElGamalPublicKey))
return tecNO_PERMISSION;
bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
// tx must include auditor ciphertext if the issuance has enabled auditing
if (sleIssuance->isFieldPresent(sfAuditorElGamalPublicKey) && !hasAuditor)
return tecNO_PERMISSION;
auto const sleMptoken = ctx.view.read(
keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], ctx.tx[sfAccount]));
if (!sleMptoken)
@@ -106,8 +106,6 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx)
ctx.tx[sfMPTokenIssuanceID],
ctx.tx[sfMPTAmount]);
bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
std::vector<Buffer> const zkps = getEqualityProofs(ctx.tx[sfZKProof]);
auto const& amount = ctx.tx[sfMPTAmount];
@@ -178,8 +176,10 @@ ConfidentialConvert::doApply()
Slice const holderEc = ctx_.tx[sfHolderEncryptedAmount];
Slice const issuerEc = ctx_.tx[sfIssuerEncryptedAmount];
// todo: we should check sfConfidentialBalanceSpending depending on if we
// encrypt zero amount
std::optional<Slice> const auditorEc = ctx_.tx[~sfAuditorEncryptedAmount];
// todo: we should check sfConfidentialBalanceSpending depending on
// if we encrypt zero amount
if (sleMptoken->isFieldPresent(sfIssuerEncryptedBalance) &&
sleMptoken->isFieldPresent(sfConfidentialBalanceInbox) &&
sleMptoken->isFieldPresent(sfConfidentialBalanceSpending))
@@ -205,6 +205,18 @@ ConfidentialConvert::doApply()
(*sleMptoken)[sfIssuerEncryptedBalance] = sum;
}
// homomorphically add auditor's encrypted balance
if (auditorEc)
{
Buffer sum(ecGamalEncryptedTotalLength);
if (TER const ter = homomorphicAdd(
*auditorEc, (*sleMptoken)[sfAuditorEncryptedBalance], sum);
!isTesSuccess(ter))
return tecINTERNAL;
(*sleMptoken)[sfAuditorEncryptedBalance] = sum;
}
}
else if (
!sleMptoken->isFieldPresent(sfIssuerEncryptedBalance) &&
@@ -215,12 +227,15 @@ ConfidentialConvert::doApply()
(*sleMptoken)[sfIssuerEncryptedBalance] = issuerEc;
(*sleMptoken)[sfConfidentialBalanceVersion] = 0;
if (auditorEc)
(*sleMptoken)[sfAuditorEncryptedBalance] = *auditorEc;
try
{
// encrypt sfConfidentialBalanceSpending with zero balance
Buffer out;
out =
encryptAmount(0, (*sleMptoken)[sfHolderElGamalPublicKey]).first;
out = encryptAmount(0, (*sleMptoken)[sfHolderElGamalPublicKey])
.ciphertext;
(*sleMptoken)[sfConfidentialBalanceSpending] = out;
}
catch (std::exception const& e)

View File

@@ -20,18 +20,12 @@ ConfidentialConvertBack::preflight(PreflightContext const& ctx)
if (MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer() == ctx.tx[sfAccount])
return temMALFORMED;
if (ctx.tx[sfHolderEncryptedAmount].length() !=
ecGamalEncryptedTotalLength ||
ctx.tx[sfIssuerEncryptedAmount].length() != ecGamalEncryptedTotalLength)
return temBAD_CIPHERTEXT;
if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res))
return res;
if (ctx.tx[sfMPTAmount] == 0 || ctx.tx[sfMPTAmount] > maxMPTokenAmount)
return temBAD_AMOUNT;
if (!isValidCiphertext(ctx.tx[sfHolderEncryptedAmount]) ||
!isValidCiphertext(ctx.tx[sfIssuerEncryptedAmount]))
return temBAD_CIPHERTEXT;
// todo: update with correct size of proof since it might also contain range
// proof
// if (ctx.tx[sfZKProof].length() != ecEqualityProofLength)
@@ -52,8 +46,13 @@ ConfidentialConvertBack::preclaim(PreclaimContext const& ctx)
if (!sleIssuance->isFlag(lsfMPTCanPrivacy))
return tecNO_PERMISSION;
// already checked in preflight, but should also check that issuer on the
// issuance isn't the account either
// tx must include auditor ciphertext if the issuance has enabled auditing
if (sleIssuance->isFieldPresent(sfAuditorElGamalPublicKey) &&
!ctx.tx.isFieldPresent(sfAuditorEncryptedAmount))
return tecNO_PERMISSION;
// 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])
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -144,6 +143,8 @@ ConfidentialConvertBack::doApply()
(*sleMptoken)[sfConfidentialBalanceVersion] =
(*sleMptoken)[~sfConfidentialBalanceVersion].value_or(0u) + 1u;
std::optional<Slice> const auditorEc = ctx_.tx[~sfAuditorEncryptedAmount];
// homomorphically subtract holder's encrypted balance
{
Buffer res(ecGamalEncryptedTotalLength);
@@ -170,6 +171,19 @@ ConfidentialConvertBack::doApply()
(*sleMptoken)[sfIssuerEncryptedBalance] = res;
}
if (auditorEc)
{
Buffer res(ecGamalEncryptedTotalLength);
if (TER const ter = homomorphicSubtract(
(*sleMptoken)[sfAuditorEncryptedBalance],
ctx_.tx[sfAuditorEncryptedAmount],
res);
!isTesSuccess(ter))
return tecINTERNAL;
(*sleMptoken)[sfAuditorEncryptedBalance] = res;
}
view().update(sleIssuance);
view().update(sleMptoken);
return tesSUCCESS;

View File

@@ -37,11 +37,20 @@ ConfidentialSend::preflight(PreflightContext const& ctx)
ctx.tx[sfIssuerEncryptedAmount].length() != ecGamalEncryptedTotalLength)
return temBAD_CIPHERTEXT;
bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
if (hasAuditor &&
ctx.tx[sfAuditorEncryptedAmount].length() !=
ecGamalEncryptedTotalLength)
return temBAD_CIPHERTEXT;
if (!isValidCiphertext(ctx.tx[sfSenderEncryptedAmount]) ||
!isValidCiphertext(ctx.tx[sfDestinationEncryptedAmount]) ||
!isValidCiphertext(ctx.tx[sfIssuerEncryptedAmount]))
return temBAD_CIPHERTEXT;
if (hasAuditor && !isValidCiphertext(ctx.tx[sfAuditorEncryptedAmount]))
return temBAD_CIPHERTEXT;
// if (ctx.tx[sfZKProof].length() != ecEqualityProofLength)
// return temMALFORMED;
@@ -79,6 +88,11 @@ ConfidentialSend::preclaim(PreclaimContext const& ctx)
if (!sleIssuance->isFieldPresent(sfIssuerElGamalPublicKey))
return tecNO_PERMISSION;
// tx must include auditor ciphertext if the issuance has enabled auditing
if (sleIssuance->isFieldPresent(sfAuditorElGamalPublicKey) &&
!ctx.tx.isFieldPresent(sfAuditorEncryptedAmount))
return tecNO_PERMISSION;
// 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])
@@ -176,6 +190,8 @@ ConfidentialSend::doApply()
Slice const destEc = ctx_.tx[sfDestinationEncryptedAmount];
Slice const issuerEc = ctx_.tx[sfIssuerEncryptedAmount];
std::optional<Slice> const auditorEc = ctx_.tx[~sfAuditorEncryptedAmount];
// Subtract from sender's spending balance
{
Slice const curSpending = (*sleSender)[sfConfidentialBalanceSpending];
@@ -202,6 +218,20 @@ ConfidentialSend::doApply()
(*sleSender)[sfIssuerEncryptedBalance] = newIssuerEnc;
}
// Subtract from auditor's balance if present
if (auditorEc)
{
Slice const curAuditorEnc = (*sleSender)[sfAuditorEncryptedBalance];
Buffer newAuditorEnc(ecGamalEncryptedTotalLength);
if (TER const ter =
homomorphicSubtract(curAuditorEnc, *auditorEc, newAuditorEnc);
!isTesSuccess(ter))
return tecINTERNAL;
(*sleSender)[sfAuditorEncryptedBalance] = newAuditorEnc;
}
// Increment version
(*sleSender)[sfConfidentialBalanceVersion] =
(*sleSender)[~sfConfidentialBalanceVersion].value_or(0u) + 1u;
@@ -231,6 +261,21 @@ ConfidentialSend::doApply()
(*sleDestination)[sfIssuerEncryptedBalance] = newIssuerEnc;
}
// Add to auditor's balance if present
if (auditorEc)
{
Slice const curAuditorEnc =
(*sleDestination)[sfAuditorEncryptedBalance];
Buffer newAuditorEnc(ecGamalEncryptedTotalLength);
if (TER const ter =
homomorphicAdd(curAuditorEnc, *auditorEc, newAuditorEnc);
!isTesSuccess(ter))
return tecINTERNAL;
(*sleDestination)[sfAuditorEncryptedBalance] = newAuditorEnc;
}
view().update(sleSender);
view().update(sleDestination);
return tesSUCCESS;

View File

@@ -71,7 +71,10 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
auto const metadata = ctx.tx[~sfMPTokenMetadata];
auto const transferFee = ctx.tx[~sfTransferFee];
auto const isMutate = mutableFlags || metadata || transferFee;
auto const hasElGamalKey = ctx.tx.isFieldPresent(sfIssuerElGamalPublicKey);
auto const hasIssuerElGamalKey =
ctx.tx.isFieldPresent(sfIssuerElGamalPublicKey);
auto const hasAuditorElGamalKey =
ctx.tx.isFieldPresent(sfAuditorElGamalPublicKey);
auto const txFlags = ctx.tx.getFlags();
auto const mutatePrivacy = mutableFlags &&
@@ -83,7 +86,7 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
if (isMutate && !ctx.rules.enabled(featureDynamicMPT))
return temDISABLED;
if ((hasElGamalKey || mutatePrivacy) &&
if ((hasIssuerElGamalKey || hasAuditorElGamalKey || mutatePrivacy) &&
!ctx.rules.enabled(featureConfidentialTransfer))
return temDISABLED;
@@ -93,13 +96,6 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
if (mutatePrivacy && hasHolder)
return temMALFORMED;
if (hasElGamalKey && hasHolder)
return temMALFORMED;
if (hasElGamalKey &&
ctx.tx[sfIssuerElGamalPublicKey].length() != ecPubKeyLength)
return temMALFORMED;
// fails if both flags are set
if ((txFlags & tfMPTLock) && (txFlags & tfMPTUnlock))
return temINVALID_FLAG;
@@ -114,7 +110,8 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
ctx.rules.enabled(featureConfidentialTransfer))
{
// Is this transaction actually changing anything ?
if (txFlags == 0 && !hasDomain && !hasElGamalKey && !isMutate)
if (txFlags == 0 && !hasDomain && !hasIssuerElGamalKey &&
!hasAuditorElGamalKey && !isMutate)
return temMALFORMED;
}
@@ -158,6 +155,20 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
}
}
if (hasHolder && (hasIssuerElGamalKey || hasAuditorElGamalKey))
return temMALFORMED;
if (hasAuditorElGamalKey && !hasIssuerElGamalKey)
return temMALFORMED;
if (hasIssuerElGamalKey &&
ctx.tx[sfIssuerElGamalPublicKey].length() != ecPubKeyLength)
return temMALFORMED;
if (hasAuditorElGamalKey &&
ctx.tx[sfAuditorElGamalPublicKey].length() != ecPubKeyLength)
return temMALFORMED;
return tesSUCCESS;
}
@@ -301,19 +312,40 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
return tecNO_PERMISSION;
}
// cannot update public key
// cannot update issuer public key
if (ctx.tx.isFieldPresent(sfIssuerElGamalPublicKey) &&
sleMptIssuance->isFieldPresent(sfIssuerElGamalPublicKey))
{
return tecNO_PERMISSION;
}
// cannot update auditor public key
if (ctx.tx.isFieldPresent(sfAuditorElGamalPublicKey) &&
sleMptIssuance->isFieldPresent(sfAuditorElGamalPublicKey))
{
return tecNO_PERMISSION;
}
if (ctx.tx.isFieldPresent(sfIssuerElGamalPublicKey) &&
!sleMptIssuance->isFlag(lsfMPTCanPrivacy))
{
return tecNO_PERMISSION;
}
if (ctx.tx.isFieldPresent(sfAuditorElGamalPublicKey) &&
!sleMptIssuance->isFlag(lsfMPTCanPrivacy))
{
return tecNO_PERMISSION;
}
// cannot upload key if there's circulating supply of COA
if ((ctx.tx.isFieldPresent(sfIssuerElGamalPublicKey) ||
ctx.tx.isFieldPresent(sfAuditorElGamalPublicKey)) &&
sleMptIssuance->isFieldPresent(sfConfidentialOutstandingAmount))
{
return tecNO_PERMISSION; // LCOV_EXCL_LINE
}
return tesSUCCESS;
}
@@ -411,6 +443,16 @@ MPTokenIssuanceSet::doApply()
sle->setFieldVL(sfIssuerElGamalPublicKey, *pubKey);
}
if (auto const pubKey = ctx_.tx[~sfAuditorElGamalPublicKey])
{
// This is enforced in preflight.
XRPL_ASSERT(
sle->getType() == ltMPTOKEN_ISSUANCE,
"MPTokenIssuanceSet::doApply : modifying MPTokenIssuance");
sle->setFieldVL(sfAuditorElGamalPublicKey, *pubKey);
}
view().update(sle);
return tesSUCCESS;