mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-02 16:26:48 +00:00
Add auditing feature across confidential transfer transactions (#6200)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -418,6 +418,7 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
|
||||
{sfConfidentialBalanceSpending, soeOPTIONAL},
|
||||
{sfConfidentialBalanceVersion, soeDEFAULT},
|
||||
{sfIssuerEncryptedBalance, soeOPTIONAL},
|
||||
{sfAuditorEncryptedBalance, soeOPTIONAL},
|
||||
{sfHolderElGamalPublicKey, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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; }));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user