xrpld
Loading...
Searching...
No Matches
src/test/jtx/ConfidentialTransfer.h
1#pragma once
2
3#include <test/jtx/AMM.h>
4#include <test/jtx/Account.h>
5#include <test/jtx/Env.h>
6#include <test/jtx/TestHelpers.h>
7#include <test/jtx/amount.h>
8#include <test/jtx/batch.h>
9#include <test/jtx/credentials.h>
10#include <test/jtx/delegate.h>
11#include <test/jtx/deposit.h>
12#include <test/jtx/flags.h>
13#include <test/jtx/mpt.h>
14#include <test/jtx/pay.h>
15#include <test/jtx/ter.h>
16#include <test/jtx/ticket.h>
17#include <test/jtx/vault.h>
18
19#include <xrpl/basics/Buffer.h>
20#include <xrpl/basics/Slice.h>
21#include <xrpl/basics/base_uint.h>
22#include <xrpl/basics/contract.h>
23#include <xrpl/basics/strHex.h>
24#include <xrpl/beast/unit_test/suite.h>
25#include <xrpl/beast/utility/Journal.h>
26#include <xrpl/core/ServiceRegistry.h>
27#include <xrpl/json/json_value.h>
28#include <xrpl/ledger/OpenView.h>
29#include <xrpl/protocol/ConfidentialTransfer.h>
30#include <xrpl/protocol/Feature.h>
31#include <xrpl/protocol/Indexes.h>
32#include <xrpl/protocol/LedgerFormats.h>
33#include <xrpl/protocol/Protocol.h>
34#include <xrpl/protocol/SField.h>
35#include <xrpl/protocol/STObject.h>
36#include <xrpl/protocol/Serializer.h>
37#include <xrpl/protocol/TER.h>
38#include <xrpl/protocol/TxFlags.h>
39#include <xrpl/protocol/jss.h>
40
41#include <utility/mpt_utility.h>
42
43#include <secp256k1.h>
44#include <secp256k1_mpt.h>
45
46#include <array>
47#include <cstddef>
48#include <cstdint>
49#include <cstring>
50#include <functional>
51#include <optional>
52#include <stdexcept>
53#include <string>
54#include <vector>
55
56namespace xrpl {
57
59{
60protected:
61 template <class T>
62 static T
63 requireOptional(std::optional<T> value, char const* message)
64 {
65 if (!value)
67 return std::move(*value);
68 }
69
70 template <class T>
71 static T const&
72 requireOptionalRef(std::optional<T> const& value, char const* message)
73 {
74 if (!value)
76 return *value;
77 }
78
79 // Offset where the bulletproof begins in a send proof blob.
80 // Proof layout: [compact_sigma | bulletproof]
82
83 // Generate a forged aggregated bulletproof (double bulletproof) for
84 // the given values and blinding factors. Used to test that splicing
85 // a bulletproof claiming a different remaining balance is rejected.
86 // secp256k1 convention: returns 1 on success, 0 on failure.
87 static Buffer
89 std::array<uint64_t, 2> const& values,
90 std::array<Buffer, 2> const& blindingFactors,
91 uint256 const& contextHash)
92 {
93 auto* const ctx = mpt_secp256k1_context();
94
95 secp256k1_pubkey h;
96 secp256k1_mpt_get_h_generator(ctx, &h);
97
99 size_t proofLen = kEcDoubleBulletproofLength;
100
101 unsigned char blindings[64];
102 std::memcpy(blindings, blindingFactors[0].data(), 32);
103 std::memcpy(blindings + 32, blindingFactors[1].data(), 32);
104
105 if (secp256k1_bulletproof_prove_agg(
106 ctx,
107 proof.data(),
108 &proofLen,
109 values.data(),
110 blindings,
111 2,
112 &h,
113 contextHash.data()) == 0)
114 Throw<std::runtime_error>("Failed to generate forged bulletproof");
115
116 return proof;
117 }
118
119 // Get a bad ciphertext with valid structure but cryptographic invalid for
120 // testing purposes. For preflight test purposes.
121 static Buffer const&
123 {
124 static Buffer const kBadCiphertext = []() {
127
130 return buf;
131 }();
132
133 return kBadCiphertext;
134 }
135
136 // Get a trivial buffer that is structurally and mathematically valid, but
137 // contains invalid data that does not match the ledger state. For preclaim
138 // test purposes.
139 static Buffer const&
141 {
142 static Buffer const kTrivialCiphertext = []() {
145
148
149 buf.data()[kEcCiphertextComponentLength - 1] = 0x01;
150 buf.data()[kEcGamalEncryptedTotalLength - 1] = 0x01;
151
152 return buf;
153 }();
154
155 return kTrivialCiphertext;
156 }
157
158 // Returns a valid compressed EC point (33 bytes) that can pass preflight
159 // validation but contains invalid data for preclaim test purposes.
160 static Buffer const&
162 {
163 static Buffer const kTrivialCommitment = []() {
166
168 // Set last byte to make it a valid x-coordinate on the curve
169 buf.data()[kEcPedersenCommitmentLength - 1] = 0x01;
170
171 return buf;
172 }();
173
174 return kTrivialCommitment;
175 }
176
177 static std::string
179 {
182
184 {
187 buf.data()[i + kEcCiphertextComponentLength - 1] = 0x01;
188 }
189
190 return strHex(buf);
191 }
192
193 // Helper struct to encapsulate common setup for integration tests.
195 {
196 // Constants
197 uint64_t sendAmount;
199 uint32_t version;
200
201 // Blinding factors
205
206 // Encrypted amounts
211
212 // Commitments
214
215 // Long-lived pub key buffers (to avoid dangling Slice)
220
221 // Balance data
222 uint64_t prevSpending;
224
225 // Balance commitment (declared after prevSpending for init order)
227
228 // Recipients vector
230
231 // Constructor that performs all common setup
234 test::jtx::Account const& sender,
235 test::jtx::Account const& dest,
236 test::jtx::Account const& issuer,
237 uint64_t amount,
239 : sendAmount(amount)
240 , nRecipients(auditor ? 4 : 3)
241 , version(mpt.getMPTokenVersion(sender))
245 , senderAmt(mpt.encryptAmount(sender, amount, blindingFactor))
246 , destAmt(mpt.encryptAmount(dest, amount, blindingFactor))
247 , issuerAmt(mpt.encryptAmount(issuer, amount, blindingFactor))
248 , auditorAmt(
249 auditor ? std::optional<Buffer>(
250 mpt.encryptAmount(auditor->get(), amount, blindingFactor))
251 : std::nullopt)
252 , amountCommitment(mpt.getPedersenCommitment(amount, amountBlindingFactor))
253 , senderPubKey(requireOptional(mpt.getPubKey(sender), "Missing sender public key"))
254 , destPubKey(requireOptional(mpt.getPubKey(dest), "Missing destination public key"))
255 , issuerPubKey(requireOptional(mpt.getPubKey(issuer), "Missing issuer public key"))
256 , auditorPubKey(auditor ? mpt.getPubKey(auditor->get()) : std::nullopt)
258 mpt.getDecryptedBalance(sender, test::jtx::MPTTester::holderEncryptedSpending),
259 "Missing sender spending balance"))
261 mpt.getEncryptedBalance(sender, test::jtx::MPTTester::holderEncryptedSpending),
262 "Missing sender encrypted spending balance"))
263 , balanceCommitment(mpt.getPedersenCommitment(prevSpending, balanceBlindingFactor))
264 {
265 recipients.push_back({
266 .publicKey = Slice(senderPubKey),
267 .encryptedAmount = senderAmt,
268 });
269 recipients.push_back({
270 .publicKey = Slice(destPubKey),
271 .encryptedAmount = destAmt,
272 });
273 recipients.push_back({
274 .publicKey = Slice(issuerPubKey),
275 .encryptedAmount = issuerAmt,
276 });
277 if (auditor)
278 {
279 recipients.push_back({
280 .publicKey =
281 Slice(requireOptionalRef(auditorPubKey, "Missing auditor public key")),
282 .encryptedAmount =
283 requireOptionalRef(auditorAmt, "Missing auditor encrypted amount"),
284 });
285 }
286 }
287
288 // Generate proof with current account sequence
292 test::jtx::Env& env,
293 test::jtx::Account const& sender,
294 test::jtx::Account const& dest) const
295 {
296 auto const ctxHash = getSendContextHash(
297 sender.id(), mpt.issuanceID(), env.seq(sender), dest.id(), version);
298
299 return mpt.getConfidentialSendProof(
300 sender,
304 ctxHash,
305 {
306 .pedersenCommitment = amountCommitment,
307 .amt = sendAmount,
308 .encryptedAmt = senderAmt,
309 .blindingFactor = amountBlindingFactor,
310 },
311 {
312 .pedersenCommitment = balanceCommitment,
313 .amt = prevSpending,
314 .encryptedAmt = prevEncryptedSpending,
315 .blindingFactor = balanceBlindingFactor,
316 });
317 }
318
321 test::jtx::Account const& sender,
322 test::jtx::Account const& dest,
323 Buffer const& proof,
324 std::optional<TER> err = std::nullopt) const
325 {
326 return {
327 .account = sender,
328 .dest = dest,
329 .amt = sendAmount,
330 .proof = strHex(proof),
331 .senderEncryptedAmt = senderAmt,
332 .destEncryptedAmt = destAmt,
333 .issuerEncryptedAmt = issuerAmt,
334 .auditorEncryptedAmt = auditorAmt,
335 .amountCommitment = amountCommitment,
336 .balanceCommitment = balanceCommitment,
337 .err = err,
338 };
339 }
340 };
341
342 // Helper that wraps the boilerplate setup: Env + MPT creation, funding, key
343 // generation, and seeding each holder with a confidential balance.
344 // The caller supplies the issuer and any number of holders.
346 {
347 // Per-holder configuration: the account, how much MPT to fund it
348 // with, and how much of that to convert to a confidential balance.
355
357
359 test::jtx::Env& env,
360 test::jtx::Account const& issuer,
361 std::vector<HolderInit> const& holders,
362 std::uint32_t flags = tfMPTCanLock | tfMPTCanHoldConfidentialBalance | tfMPTCanTransfer,
363 std::optional<test::jtx::Account> auditor = std::nullopt)
364 : mpt{env, issuer, {.holders = extractAccounts(holders), .auditor = auditor}}
365 {
366 mpt.create({.ownerCount = 1, .flags = flags});
367
368 for (auto const& h : holders)
369 {
370 mpt.authorize({.account = h.account});
371 if ((flags & tfMPTRequireAuth) != 0)
372 mpt.authorize({.account = issuer, .holder = h.account});
373 mpt.pay(issuer, h.account, h.payAmount);
374 }
375
376 mpt.generateKeyPair(issuer);
377 for (auto const& h : holders)
378 mpt.generateKeyPair(h.account);
379 if (auditor)
380 mpt.generateKeyPair(requireOptionalRef(auditor, "Missing auditor"));
381
382 mpt.set({
383 .account = issuer,
384 .issuerPubKey = mpt.getPubKey(issuer),
385 .auditorPubKey = auditor
386 ? mpt.getPubKey(requireOptionalRef(auditor, "Missing auditor"))
387 : std::optional<Buffer>{},
388 });
389
390 for (auto const& h : holders)
391 {
392 mpt.convert({
393 .account = h.account,
394 .amt = h.convertAmount,
395 .holderPubKey = mpt.getPubKey(h.account),
396 });
397 mpt.mergeInbox({.account = h.account});
398 }
399 }
400
401 private:
402 static std::vector<test::jtx::Account>
404 {
406 accounts.reserve(holders.size());
407 for (auto const& h : holders)
408 accounts.push_back(h.account);
409 return accounts;
410 }
411 };
412
413 // Set up an MPT environment suitable for batch testing.
414 // alice is issuer; bob has 'bobAmt' in confidential spending; carol has
415 // 'carolAmt' in confidential spending; dave is initialised with pubkey but
416 // zero spending/inbox.
417 static void
420 test::jtx::Account const& alice,
421 test::jtx::Account const& bob,
422 test::jtx::Account const& carol,
423 test::jtx::Account const& dave,
424 std::uint64_t bobAmt,
425 std::uint64_t carolAmt)
426 {
427 using namespace test::jtx;
428 mpt.create({
429 .ownerCount = 1,
430 .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanHoldConfidentialBalance,
431 });
432 mpt.authorize({.account = bob});
433 mpt.authorize({.account = carol});
434 mpt.authorize({.account = dave});
435
436 if (bobAmt > 0)
437 mpt.pay(alice, bob, bobAmt);
438 if (carolAmt > 0)
439 mpt.pay(alice, carol, carolAmt);
440
441 mpt.generateKeyPair(alice);
442 mpt.generateKeyPair(bob);
443 mpt.generateKeyPair(carol);
444 mpt.generateKeyPair(dave);
445
446 mpt.set({
447 .account = alice,
448 .issuerPubKey = mpt.getPubKey(alice),
449 });
450
451 if (bobAmt > 0)
452 {
453 mpt.convert({
454 .account = bob,
455 .amt = bobAmt,
456 .holderPubKey = mpt.getPubKey(bob),
457 });
458 mpt.mergeInbox({.account = bob});
459 }
460 else
461 {
462 mpt.convert({
463 .account = bob,
464 .amt = 0,
465 .holderPubKey = mpt.getPubKey(bob),
466 });
467 }
468
469 if (carolAmt > 0)
470 {
471 mpt.convert({
472 .account = carol,
473 .amt = carolAmt,
474 .holderPubKey = mpt.getPubKey(carol),
475 });
476 mpt.mergeInbox({.account = carol});
477 }
478 else
479 {
480 mpt.convert({
481 .account = carol,
482 .amt = 0,
483 .holderPubKey = mpt.getPubKey(carol),
484 });
485 }
486
487 // dave: register pubkey only (0 spending/inbox)
488 mpt.convert({
489 .account = dave,
490 .amt = 0,
491 .holderPubKey = mpt.getPubKey(dave),
492 });
493 }
494};
495
496} // namespace xrpl
A testsuite class.
Definition suite.h:50
pointer data()
Definition base_uint.h:106
Like std::vector<char> but better.
Definition Buffer.h:16
std::uint8_t const * data() const noexcept
Return a pointer to beginning of the storage.
Definition Buffer.h:129
static T requireOptional(std::optional< T > value, char const *message)
static T const & requireOptionalRef(std::optional< T > const &value, char const *message)
static void setupBatchEnv(test::jtx::MPTTester &mpt, test::jtx::Account const &alice, test::jtx::Account const &bob, test::jtx::Account const &carol, test::jtx::Account const &dave, std::uint64_t bobAmt, std::uint64_t carolAmt)
static Buffer getForgedBulletproof(std::array< uint64_t, 2 > const &values, std::array< Buffer, 2 > const &blindingFactors, uint256 const &contextHash)
An immutable linear range of bytes.
Definition Slice.h:26
Immutable cryptographic account descriptor.
Definition jtx/Account.h:17
AccountID id() const
Returns the Account ID.
Definition jtx/Account.h:85
A transaction testing environment.
Definition Env.h:143
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:275
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
Definition mpt.h:385
void set(MPTSet const &set={})
Definition mpt.cpp:482
void pay(Account const &src, Account const &dest, std::int64_t amount, std::optional< TER > err=std::nullopt, std::optional< std::vector< std::string > > credentials=std::nullopt)
Definition mpt.cpp:668
void create(MPTCreate const &arg=MPTCreate{})
Definition mpt.cpp:256
void authorize(MPTAuthorize const &arg=MPTAuthorize{})
Definition mpt.cpp:368
MPTID const & issuanceID() const
Definition mpt.h:576
std::optional< Buffer > getConfidentialSendProof(Account const &sender, std::uint64_t const amount, std::vector< ConfidentialRecipient > const &recipients, Slice const &blindingFactor, uint256 const &contextHash, PedersenProofParams const &amountParams, PedersenProofParams const &balanceParams) const
Definition mpt.cpp:850
void generateKeyPair(Account const &account)
Definition mpt.cpp:2027
void mergeInbox(MPTMergeInbox const &arg=MPTMergeInbox{})
Definition mpt.cpp:2164
void convert(MPTConvert const &arg=MPTConvert{})
Definition mpt.cpp:1086
std::optional< Buffer > getPubKey(Account const &account) const
Definition mpt.cpp:2049
T data(T... args)
T memcpy(T... args)
T memset(T... args)
STL namespace.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr std::uint8_t kEcCompressedPrefixEvenY
Compressed EC point prefix for even y-coordinate.
Definition Protocol.h:367
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
std::optional< Buffer > encryptAmount(uint64_t const amt, Slice const &pubKeySlice, Slice const &blindingFactor)
Encrypts an amount using ElGamal encryption.
constexpr std::size_t kEcGamalEncryptedTotalLength
EC ElGamal ciphertext length: two compressed EC points concatenated.
Definition Protocol.h:324
constexpr std::size_t kEcPedersenCommitmentLength
Length of Pedersen Commitment (compressed).
Definition Protocol.h:339
constexpr std::size_t kEcCiphertextComponentLength
Length of one compressed EC point component in an EC ElGamal ciphertext.
Definition Protocol.h:321
constexpr std::size_t kEcDoubleBulletproofLength
Length of double bulletproof (range proof for 2 commitments) in bytes.
Definition Protocol.h:345
Buffer generateBlindingFactor()
Generates a cryptographically secure blinding factor (size=xrpl::kEcBlindingFactorLength).
uint256 getSendContextHash(AccountID const &account, uint192 const &issuanceID, std::uint32_t sequence, AccountID const &destination, std::uint32_t version)
Generates the context hash for ConfidentialMPTSend transactions.
constexpr std::size_t kEcSendProofLength
192 bytes compact sigma proof + 754 bytes double bulletproof.
Definition Protocol.h:351
BaseUInt< 256 > uint256
Definition base_uint.h:562
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:49
T push_back(T... args)
T reserve(T... args)
T size(T... args)
static std::vector< test::jtx::Account > extractAccounts(std::vector< HolderInit > const &holders)
ConfidentialEnv(test::jtx::Env &env, test::jtx::Account const &issuer, std::vector< HolderInit > const &holders, std::uint32_t flags=tfMPTCanLock|tfMPTCanHoldConfidentialBalance|tfMPTCanTransfer, std::optional< test::jtx::Account > auditor=std::nullopt)
std::optional< Buffer > generateProof(test::jtx::MPTTester &mpt, test::jtx::Env &env, test::jtx::Account const &sender, test::jtx::Account const &dest) const
test::jtx::MPTConfidentialSend sendArgs(test::jtx::Account const &sender, test::jtx::Account const &dest, Buffer const &proof, std::optional< TER > err=std::nullopt) const
ConfidentialSendSetup(test::jtx::MPTTester &mpt, test::jtx::Account const &sender, test::jtx::Account const &dest, test::jtx::Account const &issuer, uint64_t amount, std::optional< std::reference_wrapper< test::jtx::Account const > > auditor=std::nullopt)
Arguments for building a ConfidentialMPTSend test transaction.
Definition mpt.h:249