mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
Add tests for confidential delegation with tickets (#6808)
This commit is contained in:
@@ -7630,6 +7630,264 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
// Test invalid scenarios for delegation with tickets.
|
||||
void
|
||||
testInvalidDelegationWithTickets(FeatureBitset features)
|
||||
{
|
||||
testcase("Invalid cases for delegation with tickets");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
Account const carol("carol");
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||
env.fund(XRP(10000), carol);
|
||||
env.close();
|
||||
|
||||
mptAlice.create({
|
||||
.ownerCount = 1,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanConfidentialAmount | tfMPTCanClawback,
|
||||
});
|
||||
mptAlice.authorize({.account = bob});
|
||||
mptAlice.pay(alice, bob, 200);
|
||||
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.generateKeyPair(bob);
|
||||
mptAlice.set({.issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
// Bob grants carol permissions.
|
||||
env(delegate::set(bob, carol, {"ConfidentialMPTConvert"}));
|
||||
env.close();
|
||||
|
||||
uint64_t const amt = 10;
|
||||
auto const bf = generateBlindingFactor();
|
||||
auto const holderCt = mptAlice.encryptAmount(bob, amt, bf);
|
||||
auto const issuerCt = mptAlice.encryptAmount(alice, amt, bf);
|
||||
|
||||
// Invalid: proof built with wrong ticket sequence (ticketSeq + 1).
|
||||
{
|
||||
auto const ticketSeq = env.seq(bob) + 1;
|
||||
env(ticket::create(bob, 1));
|
||||
|
||||
auto const badCtxHash =
|
||||
getConvertContextHash(bob, mptAlice.issuanceID(), ticketSeq + 1);
|
||||
auto const badProof = mptAlice.getSchnorrProof(bob, badCtxHash);
|
||||
BEAST_EXPECT(badProof.has_value());
|
||||
|
||||
mptAlice.convert(
|
||||
{.account = bob,
|
||||
.amt = amt,
|
||||
.proof = strHex(*badProof),
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
.holderEncryptedAmt = holderCt,
|
||||
.issuerEncryptedAmt = issuerCt,
|
||||
.blindingFactor = bf,
|
||||
.delegate = carol,
|
||||
.ticketSeq = ticketSeq,
|
||||
.err = tecBAD_PROOF});
|
||||
}
|
||||
|
||||
// Invalid: proof built with account sequence instead of ticket sequence.
|
||||
{
|
||||
auto const ticketSeq = env.seq(bob) + 1;
|
||||
env(ticket::create(bob, 1));
|
||||
auto const badCtxHash = getConvertContextHash(bob, mptAlice.issuanceID(), env.seq(bob));
|
||||
auto const badProof = mptAlice.getSchnorrProof(bob, badCtxHash);
|
||||
BEAST_EXPECT(badProof.has_value());
|
||||
|
||||
mptAlice.convert(
|
||||
{.account = bob,
|
||||
.amt = amt,
|
||||
.proof = strHex(*badProof),
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
.holderEncryptedAmt = holderCt,
|
||||
.issuerEncryptedAmt = issuerCt,
|
||||
.blindingFactor = bf,
|
||||
.delegate = carol,
|
||||
.ticketSeq = ticketSeq,
|
||||
.err = tecBAD_PROOF});
|
||||
}
|
||||
|
||||
// Invalid: ticket sequence is far in the future and hasn't been created yet.
|
||||
{
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = amt,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
.holderEncryptedAmt = holderCt,
|
||||
.issuerEncryptedAmt = issuerCt,
|
||||
.blindingFactor = bf,
|
||||
.delegate = carol,
|
||||
.ticketSeq = env.seq(bob) + 100,
|
||||
.err = terPRE_TICKET,
|
||||
});
|
||||
}
|
||||
|
||||
// Invalid: ticket sequence is in the past but was never created.
|
||||
{
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = amt,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
.holderEncryptedAmt = holderCt,
|
||||
.issuerEncryptedAmt = issuerCt,
|
||||
.blindingFactor = bf,
|
||||
.delegate = carol,
|
||||
.ticketSeq = 1,
|
||||
.err = tefNO_TICKET,
|
||||
});
|
||||
}
|
||||
|
||||
// Invalid: the delegated account, carol, creates a ticket and uses it.
|
||||
{
|
||||
auto const carolTicketSeq = env.seq(carol) + 1;
|
||||
env(ticket::create(carol, 1));
|
||||
|
||||
mptAlice.convert(
|
||||
{.account = bob,
|
||||
.amt = amt,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
.holderEncryptedAmt = holderCt,
|
||||
.issuerEncryptedAmt = issuerCt,
|
||||
.blindingFactor = bf,
|
||||
.delegate = carol,
|
||||
.ticketSeq = carolTicketSeq,
|
||||
.err = tefNO_TICKET});
|
||||
}
|
||||
|
||||
// Invalid: proof bound to a ticket sequence but submitted without a ticket,
|
||||
// using account sequence.
|
||||
{
|
||||
auto const ticketSeq = env.seq(bob) + 1;
|
||||
env(ticket::create(bob, 1));
|
||||
|
||||
// Build proof using ticketSeq.
|
||||
auto const ctxHashForTicket =
|
||||
getConvertContextHash(bob, mptAlice.issuanceID(), ticketSeq);
|
||||
auto const proof = mptAlice.getSchnorrProof(bob, ctxHashForTicket);
|
||||
BEAST_EXPECT(proof.has_value());
|
||||
|
||||
// Submit without ticket.
|
||||
mptAlice.convert(
|
||||
{.account = bob,
|
||||
.amt = amt,
|
||||
.proof = strHex(*proof),
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
.holderEncryptedAmt = holderCt,
|
||||
.issuerEncryptedAmt = issuerCt,
|
||||
.blindingFactor = bf,
|
||||
.delegate = carol,
|
||||
.err = tecBAD_PROOF});
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that delegation works correctly when the delegating account uses
|
||||
// tickets instead of regular sequence numbers. The proof must bind to the
|
||||
// ticket sequence, not the account sequence.
|
||||
void
|
||||
testDelegationWithTickets(FeatureBitset features)
|
||||
{
|
||||
testcase("Confidential delegation with tickets");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
Account const carol("carol");
|
||||
Account const dave("dave");
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
|
||||
env.fund(XRP(10000), dave);
|
||||
env.close();
|
||||
|
||||
mptAlice.create({
|
||||
.ownerCount = 1,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanConfidentialAmount | tfMPTCanClawback,
|
||||
});
|
||||
mptAlice.authorize({.account = bob});
|
||||
mptAlice.authorize({.account = carol});
|
||||
mptAlice.pay(alice, bob, 200);
|
||||
mptAlice.pay(alice, carol, 100);
|
||||
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.generateKeyPair(bob);
|
||||
mptAlice.generateKeyPair(carol);
|
||||
mptAlice.set({.issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
// Bob grants dave permissions.
|
||||
env(delegate::set(
|
||||
bob,
|
||||
dave,
|
||||
{"ConfidentialMPTConvert",
|
||||
"ConfidentialMPTMergeInbox",
|
||||
"ConfidentialMPTSend",
|
||||
"ConfidentialMPTConvertBack"}));
|
||||
// Alice grants dave permission to clawback on her behalf.
|
||||
env(delegate::set(alice, dave, {"ConfidentialMPTClawback"}));
|
||||
env.close();
|
||||
|
||||
// Dave executes Convert on behalf of bob using ticket.
|
||||
auto ticketSeq = env.seq(bob) + 1;
|
||||
env(ticket::create(bob, 1));
|
||||
BEAST_EXPECT(env.seq(bob) != ticketSeq);
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 100,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
.delegate = dave,
|
||||
.ticketSeq = ticketSeq,
|
||||
});
|
||||
env.require(mptbalance(mptAlice, bob, 100));
|
||||
|
||||
// MergeInbox using ticket with delegation.
|
||||
ticketSeq = env.seq(bob) + 1;
|
||||
env(ticket::create(bob, 1));
|
||||
BEAST_EXPECT(env.seq(bob) != ticketSeq);
|
||||
mptAlice.mergeInbox({.account = bob, .delegate = dave, .ticketSeq = ticketSeq});
|
||||
|
||||
// Carol converts and merges inbox to receive from bob.
|
||||
mptAlice.convert({
|
||||
.account = carol,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(carol),
|
||||
});
|
||||
mptAlice.mergeInbox({.account = carol});
|
||||
|
||||
// Send using ticket with delegation.
|
||||
ticketSeq = env.seq(bob) + 1;
|
||||
env(ticket::create(bob, 1));
|
||||
BEAST_EXPECT(env.seq(bob) != ticketSeq);
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = 20,
|
||||
.delegate = dave,
|
||||
.ticketSeq = ticketSeq,
|
||||
});
|
||||
|
||||
// ConvertBack using ticket with delegation.
|
||||
ticketSeq = env.seq(bob) + 1;
|
||||
env(ticket::create(bob, 1));
|
||||
BEAST_EXPECT(env.seq(bob) != ticketSeq);
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
.amt = 10,
|
||||
.delegate = dave,
|
||||
.ticketSeq = ticketSeq,
|
||||
});
|
||||
|
||||
// Clawback using ticket with delegation.
|
||||
ticketSeq = env.seq(alice) + 1;
|
||||
env(ticket::create(alice, 1));
|
||||
BEAST_EXPECT(env.seq(alice) != ticketSeq);
|
||||
mptAlice.confidentialClaw({
|
||||
.holder = bob,
|
||||
.amt = 70,
|
||||
.delegate = dave,
|
||||
.ticketSeq = ticketSeq,
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -7702,6 +7960,10 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
||||
testDelegationRevocation(features);
|
||||
testDelegationWithAuditor(features);
|
||||
testDelegationClawbackIssuerOnly(features);
|
||||
|
||||
// Delegation with Tickets Tests
|
||||
testInvalidDelegationWithTickets(features);
|
||||
testDelegationWithTickets(features);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -1479,9 +1479,8 @@ MPTTester::confidentialClaw(MPTConfidentialClawback const& arg)
|
||||
jv[sfZKProof] = *arg.proof;
|
||||
else
|
||||
{
|
||||
std::uint32_t const seq = env_.seq(account);
|
||||
uint256 const contextHash =
|
||||
getClawbackContextHash(account.id(), *id_, seq, arg.holder->id());
|
||||
auto const seq = arg.ticketSeq ? *arg.ticketSeq : env_.seq(account);
|
||||
auto const contextHash = getClawbackContextHash(account.id(), *id_, seq, arg.holder->id());
|
||||
|
||||
auto const privKey = getPrivKey(account);
|
||||
if (!privKey || privKey->size() != ecPrivKeyLength)
|
||||
|
||||
@@ -274,6 +274,7 @@ struct MPTConfidentialClawback
|
||||
std::optional<std::uint64_t> amt = std::nullopt;
|
||||
std::optional<std::string> proof = std::nullopt;
|
||||
std::optional<Account> delegate = std::nullopt;
|
||||
std::optional<std::uint32_t> ticketSeq = 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;
|
||||
|
||||
Reference in New Issue
Block a user