xrpld
Loading...
Searching...
No Matches
mpt.cpp
1
2#include <test/jtx/mpt.h>
3
4#include <test/jtx/Account.h>
5#include <test/jtx/Env.h>
6#include <test/jtx/amount.h>
7#include <test/jtx/credentials.h>
8#include <test/jtx/owners.h>
9#include <test/jtx/pay.h>
10#include <test/jtx/ter.h>
11#include <test/jtx/trust.h>
12
13#include <xrpl/basics/Slice.h>
14#include <xrpl/basics/StringUtilities.h>
15#include <xrpl/basics/base_uint.h>
16#include <xrpl/basics/contract.h>
17#include <xrpl/basics/strHex.h>
18#include <xrpl/beast/unit_test/suite.h>
19#include <xrpl/json/json_value.h>
20#include <xrpl/ledger/helpers/MPTokenHelpers.h>
21#include <xrpl/protocol/AccountID.h>
22#include <xrpl/protocol/Asset.h>
23#include <xrpl/protocol/ConfidentialTransfer.h>
24#include <xrpl/protocol/Indexes.h>
25#include <xrpl/protocol/LedgerFormats.h>
26#include <xrpl/protocol/Protocol.h>
27#include <xrpl/protocol/Rate.h>
28#include <xrpl/protocol/SField.h>
29#include <xrpl/protocol/TER.h>
30#include <xrpl/protocol/TxFlags.h>
31#include <xrpl/protocol/UintTypes.h>
32#include <xrpl/protocol/jss.h>
33#include <xrpl/tx/transactors/token/MPTokenIssuanceSet.h>
34
35#include <utility/mpt_utility.h>
36
37#include <mpt_protocol.h>
38#include <secp256k1.h>
39#include <secp256k1_mpt.h>
40
41#include <algorithm>
42#include <cstddef>
43#include <cstdint>
44#include <cstring>
45#include <filesystem>
46#include <functional>
47#include <optional>
48#include <source_location>
49#include <stdexcept>
50#include <string>
51#include <unordered_map>
52#include <utility>
53#include <variant>
54#include <vector>
55
56namespace xrpl::test::jtx {
57namespace {
58
59constexpr std::uint64_t kElGamalDecryptRangeLow = 0;
60constexpr std::uint64_t kElGamalDecryptRangeHigh = 3000;
61
72template <class T>
73[[nodiscard]] T const&
74requireValue(
75 std::optional<T> const& opt,
76 char const* what,
77 std::source_location const& loc = std::source_location::current())
78{
79 if (!opt)
80 {
82 std::string(what) + " must be present (called from " +
83 std::string(loc.function_name()) + ")");
84 }
85 return *opt;
86}
87
94mpt_pedersen_proof_params
95makePedersenParams(PedersenProofParams const& params)
96{
97 mpt_pedersen_proof_params res{};
99 res.pedersen_commitment, params.pedersenCommitment.data(), kMPT_PEDERSEN_COMMIT_SIZE);
100 res.amount = params.amt;
101 std::memcpy(res.ciphertext, params.encryptedAmt.data(), kMPT_ELGAMAL_TOTAL_SIZE);
102 std::memcpy(res.blinding_factor, params.blindingFactor.data(), kMPT_BLINDING_FACTOR_SIZE);
103 return res;
104}
105
113Account const&
114setAccountField(json::Value& jv, std::optional<Account> const& account)
115{
116 Account const& act = requireValue(account, "account");
117 jv[sfAccount] = act.human();
118 return act;
119}
120
128Account const&
129setDestinationField(json::Value& jv, std::optional<Account> const& dest)
130{
131 Account const& act = requireValue(dest, "dest");
132 jv[sfDestination] = act.human();
133 return act;
134}
135
144void
145setProofOrDummy(json::Value& jv, std::optional<Buffer> const& proof, std::size_t dummyLen)
146{
147 jv[sfZKProof.jsonName] = strHex(proof ? *proof : gMakeZeroBuffer(dummyLen));
148}
149
158[[nodiscard]] std::optional<Buffer>
159keyAtEpoch(
160 std::unordered_map<AccountID, std::vector<Buffer>> const& keys,
161 AccountID const& account,
162 std::optional<std::uint32_t> epoch)
163{
164 auto const it = keys.find(account);
165 if (it == keys.end() || it->second.empty())
166 return std::nullopt;
167
168 if (!epoch)
169 return it->second.back();
170
171 if (*epoch >= it->second.size())
172 return std::nullopt;
173
174 return it->second[*epoch];
175}
176
177} // namespace
178
179namespace {
180// Renders the check's own construction site (usually in mpt.cpp) so it can be
181// embedded as the "reason" alongside the test call site reported as file/line.
182std::string
183checkSiteReason(std::source_location const& checkLoc)
184{
185 return std::string("check ") + std::filesystem::path{checkLoc.file_name()}.filename().string() +
186 "(" + std::to_string(checkLoc.line()) + ")";
187}
188} // namespace
189
190void
192{
193 env.test.expect(
194 tester_.checkFlags(flags_, holder_),
195 checkSiteReason(checkLoc_),
196 testLoc_.file_name(),
197 testLoc_.line());
198}
199
200void
202{
203 env.test.expect(
204 amount_ == tester_.getBalance(account_),
205 checkSiteReason(checkLoc_),
206 testLoc_.file_name(),
207 testLoc_.line());
208}
209
210void
212{
213 env.test.expect(cb_(), checkSiteReason(checkLoc_), testLoc_.file_name(), testLoc_.line());
214}
215
218{
220 for (auto const& h : holders)
221 {
222 if (accounts.find(h.human()) != accounts.cend())
223 Throw<std::runtime_error>("Duplicate holder");
224 accounts.emplace(h.human(), h);
225 }
226 return accounts;
227}
228
230 : env_(env)
231 , issuer_(std::move(issuer))
232 , holders_(makeHolders(arg.holders))
233 , auditor_(arg.auditor)
234 , close_(arg.close)
235{
236 if (arg.fund)
237 {
238 env_.fund(arg.xrp, issuer_);
239 for (auto const& it : holders_)
240 env_.fund(arg.xrpHolders, it.second);
241
242 if (arg.auditor)
243 env_.fund(arg.xrp, *arg.auditor);
244 }
245 if (close_)
246 env.close();
247 if (arg.fund)
248 {
249 env_.require(Owners(issuer_, 0));
250 for (auto const& it : holders_)
251 {
252 if (issuer_.id() == it.second.id())
253 Throw<std::runtime_error>("Issuer can't be holder");
254 env_.require(Owners(it.second, 0));
255 }
256
257 if (arg.auditor)
258 env_.require(Owners(*arg.auditor, 0));
259 }
260 if (arg.create)
261 create(*arg.create, loc);
262}
263
265 Env& env,
267 MPTID const& id,
268 std::vector<Account> const& holders,
269 bool close)
270 : env_(env), issuer_(std::move(issuer)), holders_(makeHolders(holders)), id_(id), close_(close)
271{
272}
273
274static MPTCreate
276{
277 if (arg.pay)
278 {
279 return {
280 .maxAmt = arg.maxAmt,
281 .transferFee = arg.transferFee,
282 .pay = {{arg.holders, *arg.pay}},
283 .flags = arg.flags,
284 .immutableFlags = arg.immutableFlags,
285 .authHolder = arg.authHolder};
286 }
287 return {
288 .maxAmt = arg.maxAmt,
289 .transferFee = arg.transferFee,
290 .authorize = arg.holders,
291 .flags = arg.flags,
292 .immutableFlags = arg.immutableFlags,
293 .authHolder = arg.authHolder};
294}
295
297 : MPTTester{
298 arg.env,
299 arg.issuer,
300 MPTInit{
301 .auditor = arg.auditor,
302 .fund = arg.fund,
303 .close = arg.close,
304 .create = makeMPTCreate(arg),
305 },
306 loc}
307{
308}
309
310MPTTester::
311operator MPT() const
312{
313 if (!id_)
314 Throw<std::runtime_error>("MPT has not been created");
315 return MPT("", *id_);
316}
317
320{
321 if (!arg.issuer)
322 Throw<std::runtime_error>("MPTTester::createJV: issuer is not set");
323 json::Value jv;
324 jv[sfAccount] = arg.issuer->human();
325 if (arg.assetScale)
326 jv[sfAssetScale] = *arg.assetScale;
327 if (arg.transferFee)
328 jv[sfTransferFee] = *arg.transferFee;
329 if (arg.metadata)
330 jv[sfMPTokenMetadata] = strHex(*arg.metadata);
331 if (arg.maxAmt)
332 jv[sfMaximumAmount] = std::to_string(*arg.maxAmt);
333 if (arg.domainID)
334 jv[sfDomainID] = to_string(*arg.domainID);
335 if (arg.immutableFlags)
336 jv[sfImmutableFlags] = *arg.immutableFlags;
337 jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
338
339 return jv;
340}
341
342void
344{
345 if (id_)
346 Throw<std::runtime_error>("MPT can't be reused");
348 json::Value const jv = createJV(
349 {.issuer = issuer_,
350 .maxAmt = arg.maxAmt,
351 .assetScale = arg.assetScale,
352 .transferFee = arg.transferFee,
353 .metadata = arg.metadata,
354 .immutableFlags = arg.immutableFlags,
355 .domainID = arg.domainID});
356 if (!isTesSuccess(submit(arg, {jv, loc})))
357 {
358 // Verify issuance doesn't exist
359 env_.require(RequireAny(
360 [&]() -> bool { return env_.le(keylet::mptokenIssuance(*id_)) == nullptr; }, loc));
361
362 id_.reset();
363 }
364 else
365 {
366 env_.require(MptFlags(*this, arg.flags.value_or(0), std::nullopt, loc));
367 auto authAndPay = [&](auto const& accts, auto const&& getAcct) {
368 for (auto const& it : accts)
369 {
370 authorize({.account = getAcct(it)}, loc);
371 if ((arg.flags.value_or(0) & tfMPTRequireAuth) && arg.authHolder)
372 authorize({.account = issuer_, .holder = getAcct(it)}, loc);
373 if (arg.pay && arg.pay->first.empty())
374 pay(issuer_, getAcct(it), arg.pay->second, std::nullopt, std::nullopt, loc);
375 }
376 if (arg.pay)
377 {
378 for (auto const& p : arg.pay->first)
379 pay(issuer_, p, arg.pay->second, std::nullopt, std::nullopt, loc);
380 }
381 };
382 if (arg.authorize)
383 {
384 if (arg.authorize->empty())
385 {
386 authAndPay(holders_, [](auto const& it) { return it.second; });
387 }
388 else
389 {
390 authAndPay(*arg.authorize, [](auto const& it) { return it; });
391 }
392 }
393 else if (arg.pay)
394 {
395 if (arg.pay->first.empty())
396 {
397 authAndPay(holders_, [](auto const& it) { return it.second; });
398 }
399 else
400 {
401 authAndPay(arg.pay->first, [](auto const& it) { return it; });
402 }
403 }
404 }
405}
406
409{
410 json::Value jv;
411 if (!arg.issuer || !arg.id)
412 Throw<std::runtime_error>("MPTTester::destroyJV: issuer/id is not set");
413 jv[sfAccount] = arg.issuer->human();
414 jv[sfMPTokenIssuanceID] = to_string(*arg.id);
415 jv[sfTransactionType] = jss::MPTokenIssuanceDestroy;
416
417 return jv;
418}
419
420void
422{
423 if (!arg.id && !id_)
424 Throw<std::runtime_error>("MPT has not been created");
425 json::Value const jv =
426 destroyJV({.issuer = arg.issuer ? arg.issuer : issuer_, .id = arg.id ? arg.id : id_});
427 submit(arg, {jv, loc});
428}
429
430Account const&
432{
433 auto const& it = holders_.find(holder);
434 if (it == holders_.cend())
435 Throw<std::runtime_error>("Holder is not found");
436 return it->second;
437}
438
441{
442 json::Value jv;
443 if (!arg.account || !arg.id)
444 Throw<std::runtime_error>("MPTTester::authorizeJV: account/id is not set");
445 jv[sfAccount] = arg.account->human();
446 jv[sfMPTokenIssuanceID] = to_string(*arg.id);
447 if (arg.holder)
448 jv[sfHolder] = arg.holder->human();
449 jv[sfTransactionType] = jss::MPTokenAuthorize;
450
451 return jv;
452}
453
454void
456{
457 if (!arg.id && !id_)
458 Throw<std::runtime_error>("MPT has not been created");
459 json::Value const jv = authorizeJV({
460 .account = arg.account ? arg.account : issuer_,
461 .holder = arg.holder,
462 .id = arg.id ? arg.id : id_,
463 });
464 if (auto const result = submit(arg, {jv, loc}); isTesSuccess(result))
465 {
466 // Issuer authorizes
467 if (!arg.account || *arg.account == issuer_)
468 {
469 auto const flags = getFlags(arg.holder);
470 // issuer un-authorizes the holder
471 if (arg.flags.value_or(0) == tfMPTUnauthorize)
472 {
473 env_.require(MptFlags(*this, flags, arg.holder, loc));
474 // issuer authorizes the holder
475 }
476 else
477 {
478 env_.require(MptFlags(*this, flags | lsfMPTAuthorized, arg.holder, loc));
479 }
480 }
481 // Holder authorizes
482 else if (arg.flags.value_or(0) != tfMPTUnauthorize)
483 {
484 auto const flags = getFlags(arg.account);
485 // holder creates a token
486 env_.require(MptFlags(*this, flags, arg.account, loc));
487 env_.require(MptBalance(*this, *arg.account, 0, loc));
488 }
489 else
490 {
491 // Verify that the MPToken doesn't exist.
492 forObject(
493 [&](SLEP const& sle) {
494 return env_.test.expect(!sle, "", loc.file_name(), loc.line());
495 },
496 arg.account);
497 }
498 }
499 else if (
500 arg.account && *arg.account != issuer_ && arg.flags.value_or(0) != tfMPTUnauthorize && id_)
501 {
502 if (result == tecDUPLICATE)
503 {
504 // Verify that MPToken already exists
505 env_.require(RequireAny(
506 [&]() -> bool {
507 return env_.le(keylet::mptoken(*id_, arg.account->id())) != nullptr;
508 },
509 loc));
510 }
511 else
512 {
513 // Verify MPToken doesn't exist if holder failed authorizing(unless
514 // it already exists)
515 env_.require(RequireAny(
516 [&]() -> bool {
517 return env_.le(keylet::mptoken(*id_, arg.account->id())) == nullptr;
518 },
519 loc));
520 }
521 }
522}
523
524void
526{
527 for (auto const& holder : holders)
528 {
529 authorize({.account = holder}, loc);
530 }
531}
532
535{
536 json::Value jv;
537 if (!arg.account || !arg.id)
538 Throw<std::runtime_error>("MPTTester::setJV: account and/or id is not set");
539 jv[sfAccount] = arg.account->human();
540 jv[sfMPTokenIssuanceID] = to_string(*arg.id);
541 if (arg.holder)
542 {
544 [&jv]<typename T>(T const& holder) {
545 if constexpr (std::is_same_v<T, Account>)
546 {
547 jv[sfHolder] = holder.human();
548 }
549 else if constexpr (std::is_same_v<T, AccountID>)
550 {
551 jv[sfHolder] = toBase58(holder);
552 }
553 },
554 *arg.holder);
555 }
556
557 if (arg.delegate)
558 jv[sfDelegate] = arg.delegate->human();
559 if (arg.domainID)
560 jv[sfDomainID] = to_string(*arg.domainID);
561 if (arg.immutableFlags)
562 jv[sfImmutableFlags] = *arg.immutableFlags;
563 if (arg.transferFee)
564 jv[sfTransferFee] = *arg.transferFee;
565 if (arg.metadata)
566 jv[sfMPTokenMetadata] = strHex(*arg.metadata);
567 if (arg.issuerPubKey)
568 jv[sfIssuerEncryptionKey] = strHex(*arg.issuerPubKey);
569 if (arg.auditorPubKey)
570 jv[sfAuditorEncryptionKey] = strHex(*arg.auditorPubKey);
571 jv[sfTransactionType] = jss::MPTokenIssuanceSet;
572
573 return jv;
574}
575
576void
578{
579 if (!arg.id && !id_)
580 Throw<std::runtime_error>("MPT has not been created");
581 json::Value const jv = setJV(
582 {.account = arg.account ? arg.account : issuer_,
583 .holder = arg.holder,
584 .id = arg.id ? arg.id : id_,
585 .immutableFlags = arg.immutableFlags,
586 .transferFee = arg.transferFee,
587 .metadata = arg.metadata,
588 .delegate = arg.delegate,
589 .domainID = arg.domainID,
590 .issuerPubKey = arg.issuerPubKey,
591 .auditorPubKey = arg.auditorPubKey});
592 if (submit(arg, {jv, loc}) == tesSUCCESS && arg.flags.value_or(0) != 0u)
593 {
594 auto require = [&](std::optional<Account> const& holder, bool unchanged) {
595 auto flags = getFlags(holder);
596 if (!unchanged)
597 {
598 if (arg.flags)
599 {
600 if (*arg.flags & tfMPTLock)
601 {
602 flags |= lsfMPTLocked;
603 }
604 else if (*arg.flags & tfMPTUnlock)
605 {
606 flags &= ~lsfMPTLocked;
607 }
608
609 for (auto const& f : MPTokenIssuanceSet::flagMapping)
610 {
611 if ((*arg.flags & f.setFlag) != 0u)
612 {
613 flags |= f.ledgerFlag;
614 }
615 }
616 }
617 }
618 env_.require(MptFlags(*this, flags, holder, loc));
619 };
620 if (arg.account)
621 require(std::nullopt, arg.holder.has_value());
622 if (auto const account = (arg.holder ? std::get_if<Account>(&(*arg.holder)) : nullptr))
623 require(*account, false);
624
625 if (arg.issuerPubKey)
626 {
627 env_.require(RequireAny(
628 [&]() -> bool {
629 return forObject([&](SLEP const& sle) -> bool {
630 if (sle)
631 {
632 auto const issuerPubKey = getPubKey(issuer_);
633 if (!issuerPubKey)
634 {
636 "MPTTester::set: issuer's pubkey is not set");
637 }
638
639 return strHex((*sle)[sfIssuerEncryptionKey]) == strHex(*issuerPubKey);
640 }
641 return false;
642 });
643 },
644 loc));
645 }
646 if (arg.auditorPubKey)
647 {
648 env_.require(RequireAny(
649 [&]() -> bool {
650 return forObject([&](SLEP const& sle) -> bool {
651 if (sle)
652 {
653 if (!auditor_.has_value())
654 Throw<std::runtime_error>("MPTTester::set: auditor is not set");
655
656 auto const auditorPubKey = getPubKey(*auditor_);
657 if (!auditorPubKey)
658 {
660 "MPTTester::set: auditor's pubkey is not set");
661 }
662
663 return strHex((*sle)[sfAuditorEncryptionKey]) == strHex(*auditorPubKey);
664 }
665 return false;
666 });
667 },
668 loc));
669 }
670 }
671}
672
673bool
675 std::function<bool(SLEP const& sle)> const& cb,
676 std::optional<Account> const& holder) const
677{
678 if (!id_)
679 Throw<std::runtime_error>("MPT has not been created");
680 auto const key = holder ? keylet::mptoken(*id_, holder->id()) : keylet::mptokenIssuance(*id_);
681 if (auto const sle = env_.le(key))
682 return cb(sle);
683 return false;
684}
685
686[[nodiscard]] bool
688{
689 return forObject([&](SLEP const& sle) -> bool {
690 if (sle->isFieldPresent(sfDomainID))
691 return expected == sle->getFieldH256(sfDomainID);
692 return (!expected.has_value());
693 });
694}
695
696[[nodiscard]] bool
698{
699 return forObject(
700 [&](SLEP const& sle) { return expectedAmount == (*sle)[sfMPTAmount]; }, holder);
701}
702
703[[nodiscard]] bool
705{
706 return forObject(
707 [&](SLEP const& sle) { return expectedAmount == (*sle)[sfOutstandingAmount]; });
708}
709
710[[nodiscard]] bool
712{
713 return forObject([&](SLEP const& sle) {
714 return expectedAmount == (*sle)[~sfConfidentialOutstandingAmount].value_or(0);
715 });
716}
717
718[[nodiscard]] bool
719MPTTester::checkFlags(uint32_t const expectedFlags, std::optional<Account> const& holder) const
720{
721 return expectedFlags == getFlags(holder);
722}
723
724[[nodiscard]] bool
726{
727 return forObject([&](SLEP const& sle) -> bool {
728 if (sle->isFieldPresent(sfMPTokenMetadata))
729 return strHex(sle->getFieldVL(sfMPTokenMetadata)) == strHex(metadata);
730 return false;
731 });
732}
733
734[[nodiscard]] bool
736{
737 return forObject(
738 [&](SLEP const& sle) -> bool { return sle->isFieldPresent(sfMPTokenMetadata); });
739}
740
741[[nodiscard]] bool
743{
744 return forObject([&](SLEP const& sle) -> bool {
745 if (sle->isFieldPresent(sfTransferFee))
746 return sle->getFieldU16(sfTransferFee) == transferFee;
747 return false;
748 });
749}
750
751[[nodiscard]] bool
753{
754 return forObject([&](SLEP const& sle) -> bool { return sle->isFieldPresent(sfTransferFee); });
755}
756
757[[nodiscard]] bool
759{
760 // sfImmutableFlags is soeDEFAULT, defaulting to 0 if not present.
761 return forObject([&](SLEP const& sle) -> bool {
762 return sle->getFieldU32(sfImmutableFlags) == expectedFlags;
763 });
764}
765
766[[nodiscard]] bool
768 std::optional<std::uint32_t> issuerKeyEpoch,
769 std::optional<std::uint32_t> auditorKeyEpoch) const
770{
771 return forObject([&](SLEP const& sle) -> bool {
772 return (*sle)[~sfIssuerKeyEpoch] == issuerKeyEpoch &&
773 (*sle)[~sfAuditorKeyEpoch] == auditorKeyEpoch;
774 });
775}
776
777[[nodiscard]] bool
779 Account const& holder,
780 std::optional<std::uint32_t> issuerKeyMirrorEpoch,
781 std::optional<std::uint32_t> auditorKeyMirrorEpoch) const
782{
783 return forObject(
784 [&](SLEP const& sle) -> bool {
785 return (*sle)[~sfIssuerKeyMirrorEpoch] == issuerKeyMirrorEpoch &&
786 (*sle)[~sfAuditorKeyMirrorEpoch] == auditorKeyMirrorEpoch;
787 },
788 holder);
789}
790
793{
795 forObject(
796 [&](SLEP const& sle) -> bool {
797 epoch = (*sle)[~field];
798 return true;
799 },
800 holder);
801 return epoch;
802}
803
804[[nodiscard]] bool
806 std::optional<Account> const& issuerKeyOwner,
807 std::optional<Account> const& auditorKeyOwner) const
808{
809 auto const matches =
810 [this](SLEP const& sle, SF_VL const& field, std::optional<Account> const& owner) {
811 if (!owner)
812 return !sle->isFieldPresent(field);
813
814 auto const expected = getPubKey(*owner);
815 return expected && sle->isFieldPresent(field) &&
816 strHex((*sle)[field]) == strHex(*expected);
817 };
818
819 return forObject([&](SLEP const& sle) -> bool {
820 return matches(sle, sfIssuerEncryptionKey, issuerKeyOwner) &&
821 matches(sle, sfAuditorEncryptionKey, auditorKeyOwner);
822 });
823}
824
825void
827 Account const& src,
828 Account const& dest,
829 std::int64_t amount,
832 std::source_location const& loc)
833{
834 if (!id_)
835 Throw<std::runtime_error>("MPT has not been created");
836 auto const srcAmt = getBalance(src);
837 auto const destAmt = getBalance(dest);
838 auto const outstandingAmt = getBalance(issuer_);
839
840 if (credentials)
841 {
842 env_(
843 WithSourceLocation<json::Value>{jtx::pay(src, dest, mpt(amount)), loc},
844 Ter(err.value_or(tesSUCCESS)),
846 }
847 else
848 {
849 env_(
850 WithSourceLocation<json::Value>{jtx::pay(src, dest, mpt(amount)), loc},
851 Ter(err.value_or(tesSUCCESS)));
852 }
853
854 if (!isTesSuccess(env_.ter()))
855 amount = 0;
856 if (close_)
857 env_.close();
858 if (src == issuer_)
859 {
860 env_.require(MptBalance(*this, src, srcAmt + amount, loc));
861 env_.require(MptBalance(*this, dest, destAmt + amount, loc));
862 }
863 else if (dest == issuer_)
864 {
865 env_.require(MptBalance(*this, src, srcAmt - amount, loc));
866 env_.require(MptBalance(*this, dest, destAmt - amount, loc));
867 }
868 else
869 {
870 STAmount const saAmount = {*id_, amount};
871 auto const actual = multiply(saAmount, transferRate(*env_.current(), *id_)).mpt().value();
872 // Sender pays the transfer fee if any
873 env_.require(MptBalance(*this, src, srcAmt - actual, loc));
874 env_.require(MptBalance(*this, dest, destAmt + amount, loc));
875 // Outstanding amount is reduced by the transfer fee if any
876 env_.require(MptBalance(*this, issuer_, outstandingAmt - (actual - amount), loc));
877 }
878}
879
880void
882 Account const& issuer,
883 Account const& holder,
884 std::int64_t amount,
886 std::source_location const& loc)
887{
888 if (!id_)
889 Throw<std::runtime_error>("MPT has not been created");
890 auto const issuerAmt = getBalance(issuer);
891 auto const holderAmt = getBalance(holder);
892 env_(
894 Ter(err.value_or(tesSUCCESS)));
895 if (!isTesSuccess(env_.ter()))
896 amount = 0;
897 if (close_)
898 env_.close();
899
900 env_.require(MptBalance(*this, issuer, issuerAmt - std::min(holderAmt, amount), loc));
901 env_.require(MptBalance(*this, holder, holderAmt - std::min(holderAmt, amount), loc));
902}
903
906{
907 if (!id_)
908 Throw<std::runtime_error>("MPT has not been created");
909 return xrpl::test::jtx::MPT(issuer_.name(), *id_)(amount);
910}
911
912MPTTester::
913operator Asset() const
914{
915 if (!id_)
916 Throw<std::runtime_error>("MPT has not been created");
917 return Asset(*id_);
918}
919
921MPTTester::getBalance(Account const& account) const
922{
923 if (!id_)
924 Throw<std::runtime_error>("MPT has not been created");
925 if (account == issuer_)
926 {
927 if (auto const sle = env_.le(keylet::mptokenIssuance(*id_)))
928 return sle->getFieldU64(sfOutstandingAmount);
929 }
930 else
931 {
932 if (auto const sle = env_.le(keylet::mptoken(*id_, account.id())))
933 return sle->getFieldU64(sfMPTAmount);
934 }
935 return 0;
936}
937
940{
941 if (!id_)
942 Throw<std::runtime_error>("MPT has not been created");
943
944 if (auto const sle = env_.le(keylet::mptokenIssuance(*id_)))
945 return (*sle)[~sfConfidentialOutstandingAmount].value_or(0);
946
947 return 0;
948}
949
952 Account const& holder,
953 std::uint64_t amount,
954 Buffer const& privateKey,
955 UInt256 const& contextHash) const
956{
957 if (!id_)
958 Throw<std::runtime_error>("MPT has not been created");
959
960 auto const sleHolder = env_.le(keylet::mptoken(*id_, holder.id()));
961 auto const sleIssuance = env_.le(keylet::mptokenIssuance(*id_));
962
963 if (!sleHolder || !sleIssuance)
964 return std::nullopt;
965
966 auto const ciphertextBlob = sleHolder->getFieldVL(sfIssuerEncryptedBalance);
967 if (ciphertextBlob.size() != kEcGamalEncryptedTotalLength)
968 return std::nullopt;
969
970 auto const pubKeyBlob = sleIssuance->getFieldVL(sfIssuerEncryptionKey);
971 if (pubKeyBlob.size() != kEcPubKeyLength)
972 return std::nullopt;
973
975
976 if (mpt_get_clawback_proof(
977 privateKey.data(),
978 pubKeyBlob.data(),
979 contextHash.data(),
980 amount,
981 ciphertextBlob.data(),
982 proof.data()) != 0)
983 {
984 return std::nullopt;
985 }
986
987 return proof;
988}
989
991MPTTester::getSchnorrProof(Account const& account, UInt256 const& ctxHash) const
992{
993 auto const pubKey = getPubKey(account);
994 if (!pubKey || pubKey->size() != kEcPubKeyLength)
995 return std::nullopt;
996
997 auto const privKey = getPrivKey(account);
998 if (requireValue(privKey, "privKey").size() != kEcPrivKeyLength)
999 return std::nullopt;
1000
1002
1003 if (mpt_get_convert_proof(
1004 requireValue(pubKey, "pubKey").data(),
1005 requireValue(privKey, "privKey").data(),
1006 ctxHash.data(),
1007 proof.data()) != 0)
1008 return std::nullopt;
1009
1010 return proof;
1011}
1012
1015 Account const& sender,
1016 std::uint64_t const amount,
1017 std::vector<ConfidentialRecipient> const& recipients,
1018 Slice const& blindingFactor,
1019 UInt256 const& contextHash,
1020 PedersenProofParams const& amountParams,
1021 PedersenProofParams const& balanceParams) const
1022{
1023 auto const pedersenBalanceParams = makePedersenParams(balanceParams);
1024
1025 if (blindingFactor.size() != kEcBlindingFactorLength)
1026 return std::nullopt;
1027
1028 auto const senderPrivKey = getPrivKey(sender);
1029 if (!senderPrivKey)
1030 return std::nullopt;
1031
1032 auto const senderPubKey = getPubKey(sender);
1033 if (!senderPubKey || senderPubKey->size() != kEcPubKeyLength)
1034 return std::nullopt;
1035
1037 return std::nullopt;
1038
1039 // Build mpt_confidential_participant array
1040 std::vector<mpt_confidential_participant> participants(recipients.size());
1041 for (size_t i = 0; i < recipients.size(); ++i)
1042 {
1043 auto const& r = recipients[i];
1044 if (r.encryptedAmount.size() != kEcGamalEncryptedTotalLength ||
1045 r.publicKey.size() != kEcPubKeyLength)
1046 {
1047 return std::nullopt;
1048 }
1049 std::memcpy(participants[i].pubkey, r.publicKey.data(), kEcPubKeyLength);
1051 participants[i].ciphertext, r.encryptedAmount.data(), kEcGamalEncryptedTotalLength);
1052 }
1053
1054 size_t proofLen = kEcSendProofLength;
1055 Buffer proof(proofLen);
1056
1057 if (mpt_get_confidential_send_proof(
1058 senderPrivKey->data(),
1059 senderPubKey->data(),
1060 amount,
1061 participants.data(),
1062 recipients.size(),
1063 blindingFactor.data(),
1064 contextHash.data(),
1065 amountParams.pedersenCommitment.data(),
1066 &pedersenBalanceParams,
1067 proof.data(),
1068 &proofLen) != 0)
1069 return std::nullopt;
1070
1071 return proof;
1072}
1073
1074Buffer
1075MPTTester::getPedersenCommitment(std::uint64_t const amount, Buffer const& pedersenBlindingFactor)
1076{
1077 // Blinding factor (rho) must be a 32-byte scalar
1078 if (pedersenBlindingFactor.size() != kEcBlindingFactorLength)
1079 Throw<std::runtime_error>("Invalid blinding factor size");
1080
1081 // secp256k1_mpt_pedersen_commit doesn't handle amount 0, return a trivial
1082 // valid commitment for test purposes
1083 if (amount == 0)
1084 {
1087 buf.data()[0] = kEcCompressedPrefixEvenY;
1088 buf.data()[kEcPedersenCommitmentLength - 1] = 0x01;
1089 return buf;
1090 }
1091
1093
1094 if (mpt_get_pedersen_commitment(amount, pedersenBlindingFactor.data(), buf.data()) != 0)
1095 Throw<std::runtime_error>("Pedersen commitment generation failed");
1096
1097 return buf;
1098}
1099
1102 Account const& holder,
1103 std::uint64_t const amount,
1104 UInt256 const& contextHash,
1105 PedersenProofParams const& pcParams) const
1106{
1107 // Expected total proof length: compact sigma proof (128 bytes) + single bulletproof (688 bytes)
1108 std::size_t constexpr kExpectedProofLength = kEcConvertBackProofLength;
1109
1110 auto const sleMptoken = env_.le(keylet::mptoken(issuanceID(), holder.id()));
1111 if (!sleMptoken || !sleMptoken->isFieldPresent(sfConfidentialBalanceSpending))
1112 return std::nullopt;
1113
1114 auto const holderPubKey = getPubKey(holder);
1115 auto const holderPrivKey = getPrivKey(holder);
1116
1117 if (!holderPubKey || !holderPrivKey)
1118 return std::nullopt;
1119
1120 auto const pedersenParams = makePedersenParams(pcParams);
1121 Buffer proof(kExpectedProofLength);
1122
1123 if (mpt_get_convert_back_proof(
1124 holderPrivKey->data(),
1125 holderPubKey->data(),
1126 contextHash.data(),
1127 amount,
1128 &pedersenParams,
1129 proof.data()) != 0)
1130 return std::nullopt;
1131
1132 return proof;
1133}
1134
1137{
1138 if (!id_)
1139 Throw<std::runtime_error>("MPT has not been created");
1140
1141 auto const sle = env_.le(keylet::mptoken(*id_, account.id()));
1142 if (!sle)
1143 return {};
1144
1145 SField const* field = nullptr;
1146 switch (option)
1147 {
1149 field = &sfConfidentialBalanceInbox;
1150 break;
1152 field = &sfConfidentialBalanceSpending;
1153 break;
1155 field = &sfIssuerEncryptedBalance;
1156 break;
1158 field = &sfAuditorEncryptedBalance;
1159 break;
1160 }
1161
1162 if (field == nullptr || !sle->isFieldPresent(*field))
1163 return {};
1164
1165 auto const blob = sle->getFieldVL(*field);
1166 return Buffer(blob.data(), blob.size());
1167}
1168
1171{
1172 std::uint32_t flags = 0;
1173 if (!forObject(
1174 [&](SLEP const& sle) {
1175 flags = sle->getFlags();
1176 return true;
1177 },
1178 holder))
1179 Throw<std::runtime_error>("Failed to get the flags");
1180 return flags;
1181}
1182
1183void
1185{
1186 if (id)
1187 {
1188 jv[sfMPTokenIssuanceID] = to_string(*id);
1189 }
1190 else if (id_)
1191 {
1192 jv[sfMPTokenIssuanceID] = to_string(*id_);
1193 }
1194 else
1195 {
1196 Throw<std::runtime_error>("MPT has not been created");
1197 }
1198}
1199
1202 std::optional<std::uint32_t> const& ticketSeq,
1203 std::optional<Account> const& account) const
1204{
1205 if (ticketSeq)
1206 return *ticketSeq;
1207 return env_.seq(requireValue(account, "account"));
1208}
1209
1210MPT
1212{
1213 return MPT(name, issuanceID());
1214}
1215
1218{
1219 return MPT("", issuanceID())(amount);
1220}
1221
1222template <typename T>
1223void
1225 T const& arg,
1226 json::Value& jv,
1227 Account const& account,
1228 std::uint64_t const amount) const
1229{
1230 Buffer const blindingFactor =
1231 arg.blindingFactor ? *arg.blindingFactor : generateBlindingFactor();
1232 jv[sfBlindingFactor.jsonName] = strHex(blindingFactor);
1233
1234 // Handle Holder
1235 Buffer const holderCiphertext = arg.holderEncryptedAmt
1236 ? *arg.holderEncryptedAmt
1237 : encryptAmount(account, amount, blindingFactor);
1238
1239 jv[sfHolderEncryptedAmount.jsonName] = strHex(holderCiphertext);
1240
1241 // Handle Issuer
1242 Buffer const issuerCiphertext = arg.issuerEncryptedAmt
1243 ? *arg.issuerEncryptedAmt
1244 : encryptAmount(issuer_, amount, blindingFactor);
1245
1246 jv[sfIssuerEncryptedAmount.jsonName] = strHex(issuerCiphertext);
1247
1248 // Handle Auditor
1249 std::optional<Buffer> auditorCiphertext;
1250 if (arg.auditorEncryptedAmt)
1251 {
1252 auditorCiphertext = *arg.auditorEncryptedAmt;
1253 }
1254 else if (auditor_.has_value() && arg.fillAuditorEncryptedAmt.value_or(false))
1255 {
1256 auditorCiphertext =
1257 encryptAmount(requireValue(auditor_, "auditor"), amount, blindingFactor);
1258 }
1259
1260 // Update auditor JSON only if ciphertext exists
1261 if (auditorCiphertext)
1262 jv[sfAuditorEncryptedAmount.jsonName] = strHex(*auditorCiphertext);
1263}
1264
1265void
1267{
1268 json::Value const jv = convertJV(arg, ticketOrSeq(arg.ticketSeq, arg.account));
1269
1270 Account const& account = requireValue(arg.account, "account");
1271 auto const amt = requireValue(arg.amt, "amt");
1272
1273 auto const holderAmt = getBalance(account);
1274 auto const prevConfidentialOutstanding = getIssuanceConfidentialBalance();
1275
1276 auto const prevInboxBalance = getDecryptedBalance(account, holderEncryptedInbox);
1277 auto const prevSpendingBalance = getDecryptedBalance(account, holderEncryptedSpending);
1278 auto const prevIssuerBalance = getDecryptedBalance(account, issuerEncryptedBalance);
1279
1280 if (!prevInboxBalance || !prevSpendingBalance || !prevIssuerBalance)
1281 Throw<std::runtime_error>("Failed to get Pre-convert balance");
1282
1283 // The auditor mirror is only touched if the transaction carries an auditor
1284 // ciphertext, which mirrors the condition convertJV fills it under.
1285 bool const hasAuditorAmt =
1287
1288 std::optional<uint64_t> prevAuditorBalance;
1289 if (hasAuditorAmt)
1290 {
1291 prevAuditorBalance = getDecryptedBalance(account, auditorEncryptedBalance);
1292 if (!prevAuditorBalance)
1293 Throw<std::runtime_error>("Failed to get Pre-convert balance");
1294 }
1295
1296 auto const prevOutstanding = getIssuanceOutstandingBalance();
1297
1298 if (submit(arg, {jv, loc}) == tesSUCCESS)
1299 {
1300 auto const postConfidentialOutstanding = getIssuanceConfidentialBalance();
1301 auto const postOutstanding = getIssuanceOutstandingBalance();
1302 env_.require(MptBalance(*this, account, holderAmt - amt, loc));
1303 env_.require(RequireAny(
1304 [&]() -> bool {
1305 return prevOutstanding && postOutstanding && *prevOutstanding == *postOutstanding;
1306 },
1307 loc));
1308 env_.require(RequireAny(
1309 [&]() -> bool {
1310 return prevConfidentialOutstanding + amt == postConfidentialOutstanding;
1311 },
1312 loc));
1313
1314 env_.require(RequireAny(
1315 [&]() -> bool {
1316 return getEncryptedBalance(account, holderEncryptedInbox).has_value();
1317 },
1318 loc));
1319 env_.require(RequireAny(
1320 [&]() -> bool {
1321 return getEncryptedBalance(account, holderEncryptedSpending).has_value();
1322 },
1323 loc));
1324 env_.require(RequireAny(
1325 [&]() -> bool {
1326 return getEncryptedBalance(account, issuerEncryptedBalance).has_value();
1327 },
1328 loc));
1329
1330 auto const postInboxBalance = getDecryptedBalance(account, holderEncryptedInbox);
1331 auto const postIssuerBalance = getDecryptedBalance(account, issuerEncryptedBalance);
1332 auto const postSpendingBalance = getDecryptedBalance(account, holderEncryptedSpending);
1333
1334 if (!postInboxBalance || !postIssuerBalance || !postSpendingBalance)
1335 Throw<std::runtime_error>("Failed to get post-convert balance");
1336
1337 if (hasAuditorAmt)
1338 {
1339 auto const postAuditorBalance = getDecryptedBalance(account, auditorEncryptedBalance);
1340
1341 if (!postAuditorBalance)
1342 Throw<std::runtime_error>("Failed to get post-convert auditor balance");
1343
1344 env_.require(RequireAny(
1345 [&]() -> bool {
1346 return getEncryptedBalance(account, auditorEncryptedBalance).has_value();
1347 },
1348 loc));
1349
1350 // auditor's encrypted balance is updated correctly
1351 env_.require(RequireAny(
1352 [&]() -> bool { return *prevAuditorBalance + amt == *postAuditorBalance; }, loc));
1353 }
1354 // spending balance should not change
1355 env_.require(RequireAny(
1356 [&]() -> bool { return *postSpendingBalance == *prevSpendingBalance; }, loc));
1357
1358 // issuer's encrypted balance is updated correctly
1359 env_.require(RequireAny(
1360 [&]() -> bool { return *prevIssuerBalance + amt == *postIssuerBalance; }, loc));
1361
1362 // holder's inbox balance is updated correctly
1363 env_.require(RequireAny(
1364 [&]() -> bool { return *prevInboxBalance + amt == *postInboxBalance; }, loc));
1365
1366 // sum of holder's inbox and spending balance should equal to issuer's
1367 // encrypted balance
1368 env_.require(RequireAny(
1369 [&]() -> bool {
1370 return *postInboxBalance + *postSpendingBalance == *postIssuerBalance;
1371 },
1372 loc));
1373
1374 if (arg.holderPubKey)
1375 {
1376 env_.require(RequireAny(
1377 [&]() -> bool {
1378 return forObject(
1379 [&](SLEP const& sle) -> bool {
1380 if (sle)
1381 {
1382 auto const holderPubKey = getPubKey(account);
1383 if (!holderPubKey)
1384 {
1386 "MPTTester::convert: holder's pubkey is "
1387 "not set");
1388 }
1389
1390 return strHex((*sle)[sfHolderEncryptionKey]) ==
1391 strHex(*holderPubKey);
1392 }
1393 return false;
1394 },
1395 account);
1396 },
1397 loc));
1398 }
1399 }
1400}
1401
1404{
1405 json::Value jv;
1406 Account const& account = setAccountField(jv, arg.account);
1407
1408 jv[jss::TransactionType] = jss::ConfidentialMPTConvert;
1409 setIssuanceIdField(jv, arg.id);
1410
1411 auto const amt = requireValue(arg.amt, "amt");
1412 jv[sfMPTAmount.jsonName] = std::to_string(amt);
1413 if (arg.holderPubKey)
1414 jv[sfHolderEncryptionKey.jsonName] = strHex(*arg.holderPubKey);
1415
1416 fillConversionCiphertexts(arg, jv, account, amt);
1417
1418 if (arg.proof)
1419 {
1420 jv[sfZKProof.jsonName] = *arg.proof;
1421 }
1422 else if (arg.fillSchnorrProof.value_or(arg.holderPubKey.has_value()))
1423 {
1424 auto const contextHash = getConvertContextHash(account.id(), issuanceID(), seq);
1425 setProofOrDummy(jv, getSchnorrProof(account, contextHash), kEcSchnorrProofLength);
1426 }
1427
1428 return jv;
1429}
1430
1431void
1433{
1434 json::Value const jv = sendJV(arg, ticketOrSeq(arg.ticketSeq, arg.account));
1435
1436 Account const& account = requireValue(arg.account, "account");
1437 Account const& dest = requireValue(arg.dest, "dest");
1438 auto const amt = requireValue(arg.amt, "amt");
1439
1440 // Version counters before send
1441 auto const prevSenderVersion = getMPTokenVersion(account);
1442 auto const prevDestVersion = getMPTokenVersion(dest);
1443
1444 // Sender's previous confidential state
1445 auto const prevSenderInbox = getDecryptedBalance(account, holderEncryptedInbox);
1446 auto const prevSenderSpending = getDecryptedBalance(account, holderEncryptedSpending);
1447 auto const prevSenderIssuer = getDecryptedBalance(account, issuerEncryptedBalance);
1448 if (!prevSenderInbox || !prevSenderSpending || !prevSenderIssuer)
1449 Throw<std::runtime_error>("Failed to get Pre-send balance");
1450
1451 std::optional<uint64_t> prevSenderAuditor;
1452 if (arg.auditorEncryptedAmt || auditor_)
1453 {
1454 prevSenderAuditor = getDecryptedBalance(account, auditorEncryptedBalance);
1455 if (!prevSenderAuditor)
1456 Throw<std::runtime_error>("Failed to get Pre-send balance");
1457 }
1458
1459 // Destination's previous confidential state
1460 auto const prevDestInbox = getDecryptedBalance(dest, holderEncryptedInbox);
1461 auto const prevDestSpending = getDecryptedBalance(dest, holderEncryptedSpending);
1462 auto const prevDestIssuer = getDecryptedBalance(dest, issuerEncryptedBalance);
1463 if (!prevDestInbox || !prevDestSpending || !prevDestIssuer)
1464 Throw<std::runtime_error>("Failed to get Pre-send balance");
1465
1466 std::optional<uint64_t> prevDestAuditor;
1467 if (arg.auditorEncryptedAmt || auditor_)
1468 {
1469 prevDestAuditor = getDecryptedBalance(dest, auditorEncryptedBalance);
1470 if (!prevDestAuditor)
1471 Throw<std::runtime_error>("Failed to get Pre-send balance");
1472 }
1473
1474 auto const senderPubAmt = getBalance(account);
1475 auto const destPubAmt = getBalance(dest);
1476 auto const prevCOA = getIssuanceConfidentialBalance();
1477 auto const prevOA = getIssuanceOutstandingBalance();
1478
1479 if (submit(arg, {jv, loc}) == tesSUCCESS)
1480 {
1481 auto const postCOA = getIssuanceConfidentialBalance();
1482 auto const postOA = getIssuanceOutstandingBalance();
1483
1484 // Sender's post confidential state
1485 auto const postSenderInbox = getDecryptedBalance(account, holderEncryptedInbox);
1486 auto const postSenderSpending = getDecryptedBalance(account, holderEncryptedSpending);
1487 auto const postSenderIssuer = getDecryptedBalance(account, issuerEncryptedBalance);
1488
1489 if (!postSenderInbox || !postSenderSpending || !postSenderIssuer)
1490 Throw<std::runtime_error>("Failed to get Post-send balance");
1491
1492 // Destination's post confidential state
1493 auto const postDestInbox = getDecryptedBalance(dest, holderEncryptedInbox);
1494 auto const postDestSpending = getDecryptedBalance(dest, holderEncryptedSpending);
1495 auto const postDestIssuer = getDecryptedBalance(dest, issuerEncryptedBalance);
1496
1497 if (!postDestInbox || !postDestSpending || !postDestIssuer)
1498 Throw<std::runtime_error>("Failed to get Post-send balance");
1499
1500 // Public balances unchanged
1501 env_.require(MptBalance(*this, account, senderPubAmt, loc));
1502 env_.require(MptBalance(*this, dest, destPubAmt, loc));
1503
1504 // OA and COA unchanged
1505 env_.require(
1506 RequireAny([&]() -> bool { return prevOA && postOA && *prevOA == *postOA; }, loc));
1507 env_.require(RequireAny([&]() -> bool { return prevCOA == postCOA; }, loc));
1508
1509 // Verify sender changes
1510 env_.require(RequireAny(
1511 [&]() -> bool {
1512 return *prevSenderSpending >= amt &&
1513 *postSenderSpending == *prevSenderSpending - amt;
1514 },
1515 loc));
1516 env_.require(RequireAny([&]() -> bool { return postSenderInbox == prevSenderInbox; }, loc));
1517 env_.require(RequireAny(
1518 [&]() -> bool {
1519 return *prevSenderIssuer >= amt && *postSenderIssuer == *prevSenderIssuer - amt;
1520 },
1521 loc));
1522
1523 // Verify destination changes
1524 env_.require(
1525 RequireAny([&]() -> bool { return *postDestInbox == *prevDestInbox + amt; }, loc));
1526 env_.require(
1527 RequireAny([&]() -> bool { return *postDestSpending == *prevDestSpending; }, loc));
1528 env_.require(
1529 RequireAny([&]() -> bool { return *postDestIssuer == *prevDestIssuer + amt; }, loc));
1530
1531 // Cross checks
1532 env_.require(RequireAny(
1533 [&]() -> bool { return *postSenderInbox + *postSenderSpending == *postSenderIssuer; },
1534 loc));
1535 env_.require(RequireAny(
1536 [&]() -> bool { return *postDestInbox + *postDestSpending == *postDestIssuer; }, loc));
1537
1538 // Version: sender increments by 1; receiver version is unchanged by incoming sends
1539 env_.require(RequireAny(
1540 [&]() -> bool { return getMPTokenVersion(account) == prevSenderVersion + 1; }, loc));
1541 env_.require(
1542 RequireAny([&]() -> bool { return getMPTokenVersion(dest) == prevDestVersion; }, loc));
1543
1544 if (arg.auditorEncryptedAmt || auditor_)
1545 {
1546 auto const postSenderAuditor = getDecryptedBalance(account, auditorEncryptedBalance);
1547 auto const postDestAuditor = getDecryptedBalance(dest, auditorEncryptedBalance);
1548 if (!postSenderAuditor || !postDestAuditor)
1549 Throw<std::runtime_error>("Failed to get Post-send balance");
1550
1551 env_.require(RequireAny(
1552 [&]() -> bool {
1553 return *postSenderAuditor == *postSenderIssuer &&
1554 *postDestAuditor == *postDestIssuer;
1555 },
1556 loc));
1557
1558 // verify sender
1559 env_.require(RequireAny(
1560 [&]() -> bool {
1561 return *prevSenderAuditor >= amt &&
1562 *postSenderAuditor == *prevSenderAuditor - amt;
1563 },
1564 loc));
1565
1566 // verify dest
1567 env_.require(RequireAny(
1568 [&]() -> bool { return *postDestAuditor == *prevDestAuditor + amt; }, loc));
1569 }
1570 }
1571}
1572
1575 MPTConfidentialSend const& arg,
1576 std::uint32_t seq,
1578{
1579 json::Value jv;
1580 jv[jss::TransactionType] = jss::ConfidentialMPTSend;
1581
1582 Account const& account = setAccountField(jv, arg.account);
1583 Account const& dest = setDestinationField(jv, arg.dest);
1584 auto const amt = requireValue(arg.amt, "amt");
1585
1586 setIssuanceIdField(jv, arg.id);
1587
1588 Buffer const blindingFactor =
1590
1591 auto const senderAmt = arg.senderEncryptedAmt ? *arg.senderEncryptedAmt
1592 : encryptAmount(account, amt, blindingFactor);
1593 auto const destAmt =
1594 arg.destEncryptedAmt ? *arg.destEncryptedAmt : encryptAmount(dest, amt, blindingFactor);
1595 auto const issuerAmt = arg.issuerEncryptedAmt ? *arg.issuerEncryptedAmt
1596 : encryptAmount(issuer_, amt, blindingFactor);
1597
1598 std::optional<Buffer> auditorAmt;
1599 if (arg.auditorEncryptedAmt)
1600 {
1601 auditorAmt = arg.auditorEncryptedAmt;
1602 }
1603 else if (auditor_.has_value() && arg.fillAuditorEncryptedAmt.value_or(false))
1604 {
1605 auditorAmt = encryptAmount(requireValue(auditor_, "auditor"), amt, blindingFactor);
1606 }
1607
1608 jv[sfSenderEncryptedAmount] = strHex(senderAmt);
1609 jv[sfDestinationEncryptedAmount] = strHex(destAmt);
1610 jv[sfIssuerEncryptedAmount] = strHex(issuerAmt);
1611 if (auditorAmt)
1612 jv[sfAuditorEncryptedAmount] = strHex(*auditorAmt);
1613
1614 if (arg.credentials)
1615 {
1616 auto& arr(jv[sfCredentialIDs.jsonName] = json::ValueType::Array);
1617 for (auto const& hash : *arg.credentials)
1618 arr.append(hash);
1619 }
1620
1621 std::uint64_t prevSenderSpending = 0;
1622 std::optional<Buffer> prevEncryptedSenderSpending;
1623 std::uint32_t version = 0;
1624 if (chain)
1625 {
1626 prevSenderSpending = chain->spending;
1627 prevEncryptedSenderSpending = chain->encSpending;
1628 version = chain->version;
1629 }
1630 else
1631 {
1632 auto const ledgerSpending = getDecryptedBalance(account, holderEncryptedSpending);
1633 if (!ledgerSpending)
1634 Throw<std::runtime_error>("Failed to get sender spending balance");
1635 prevSenderSpending = *ledgerSpending;
1636 prevEncryptedSenderSpending = getEncryptedBalance(account, holderEncryptedSpending);
1637 version = getMPTokenVersion(account);
1638 }
1639
1640 // The amount commitment must use the same blinding factor as the tx ElGamal
1641 // encryption blinding factor.
1642 Buffer amountCommitment, balanceCommitment;
1643 if (arg.amountCommitment)
1644 {
1645 amountCommitment = *arg.amountCommitment;
1646 }
1647 else
1648 {
1649 amountCommitment = getPedersenCommitment(amt, blindingFactor);
1650 }
1651
1652 jv[sfAmountCommitment] = strHex(amountCommitment);
1653
1654 auto const balanceBlindingFactor = generateBlindingFactor();
1655 if (arg.balanceCommitment)
1656 {
1657 balanceCommitment = *arg.balanceCommitment;
1658 }
1659 else
1660 {
1661 balanceCommitment = getPedersenCommitment(prevSenderSpending, balanceBlindingFactor);
1662 }
1663
1664 jv[sfBalanceCommitment] = strHex(balanceCommitment);
1665
1666 if (arg.proof)
1667 {
1668 jv[sfZKProof.jsonName] = *arg.proof;
1669 }
1670 else
1671 {
1672 auto const ctxHash =
1673 getSendContextHash(account.id(), issuanceID(), seq, dest.id(), version);
1674
1676
1677 auto const senderPubKey = getPubKey(account);
1678 auto const destPubKey = getPubKey(dest);
1679 auto const issuerPubKey = getPubKey(issuer_);
1680
1681 if (senderPubKey)
1682 {
1683 recipients.push_back({
1684 .publicKey = Slice(*senderPubKey),
1685 .encryptedAmount = senderAmt,
1686 });
1687 }
1688 if (destPubKey)
1689 {
1690 recipients.push_back({
1691 .publicKey = Slice(*destPubKey),
1692 .encryptedAmount = destAmt,
1693 });
1694 }
1695 if (issuerPubKey)
1696 {
1697 recipients.push_back({
1698 .publicKey = Slice(*issuerPubKey),
1699 .encryptedAmount = issuerAmt,
1700 });
1701 }
1702
1703 std::optional<Buffer> auditorPubKey;
1704 if (auditorAmt)
1705 {
1706 if (!auditor_)
1707 Throw<std::runtime_error>("Auditor not registered");
1708 auditorPubKey = getPubKey(*auditor_);
1709 if (auditorPubKey)
1710 {
1711 recipients.push_back({
1712 .publicKey = Slice(*auditorPubKey),
1713 .encryptedAmount = *auditorAmt,
1714 });
1715 }
1716 }
1717
1719
1720 // Skip proof generation when spending balance is 0
1721 if (prevEncryptedSenderSpending && prevSenderSpending > 0)
1722 {
1724 account,
1725 amt,
1726 recipients,
1727 blindingFactor,
1728 ctxHash,
1729 {
1730 .pedersenCommitment = amountCommitment,
1731 .amt = amt,
1732 .encryptedAmt = senderAmt,
1733 .blindingFactor = blindingFactor,
1734 },
1735 {
1736 .pedersenCommitment = balanceCommitment,
1737 .amt = prevSenderSpending,
1738 .encryptedAmt = *prevEncryptedSenderSpending,
1739 .blindingFactor = balanceBlindingFactor,
1740 });
1741 }
1742
1743 setProofOrDummy(jv, proof, kEcSendProofLength);
1744 }
1745
1746 return jv;
1747}
1748
1749static Buffer
1751{
1752 auto const hexStr = jv[sfSenderEncryptedAmount.jsonName].asString();
1753 auto const bytes = strUnHex(hexStr);
1754 if (!bytes)
1755 Throw<std::runtime_error>("chainAfterSend: invalid hex in sfSenderEncryptedAmount");
1756 return Buffer(bytes->data(), bytes->size());
1757}
1758
1760MPTTester::chainAfterSend(Account const& sender, std::uint64_t sendAmt, json::Value const& jv) const
1761{
1762 auto const prevSpending = getDecryptedBalance(sender, holderEncryptedSpending);
1763 auto const prevEncSpending = getEncryptedBalance(sender, holderEncryptedSpending);
1764 auto const prevVersion = getMPTokenVersion(sender);
1765
1766 if (!prevSpending || !prevEncSpending)
1767 Throw<std::runtime_error>("chainAfterSend: failed to read sender state from ledger");
1768
1769 Buffer const senderEncAmt = parseSenderEncAmt(jv);
1770 auto chain = computeNextSendChainState(
1771 *prevSpending, Slice(*prevEncSpending), prevVersion, sendAmt, Slice(senderEncAmt));
1772 if (!chain)
1773 Throw<std::runtime_error>("chainAfterSend: computeNextSendChainState failed");
1774 return std::move(*chain);
1775}
1776
1779 std::uint64_t currentSpending,
1780 Slice const& currentEncSpending,
1781 std::uint32_t currentVersion,
1782 std::uint64_t sendAmt,
1783 Slice const& senderEncAmt)
1784{
1785 if (sendAmt > currentSpending)
1786 return std::nullopt; // LCOV_EXCL_LINE
1787
1788 auto newEncSpending = homomorphicSubtract(currentEncSpending, senderEncAmt);
1789 if (!newEncSpending)
1790 return std::nullopt; // LCOV_EXCL_LINE
1791
1793 .spending = currentSpending - sendAmt,
1794 .encSpending = std::move(*newEncSpending),
1795 .version = currentVersion + 1,
1796 };
1797}
1798
1799void
1801{
1802 json::Value jv;
1803 auto const account = arg.account ? *arg.account : issuer_;
1804 jv[sfAccount] = account.human();
1805
1806 Account const& holder = requireValue(arg.holder, "holder");
1807 jv[sfHolder] = holder.human();
1808
1809 jv[jss::TransactionType] = jss::ConfidentialMPTClawback;
1810 setIssuanceIdField(jv, arg.id);
1811
1812 auto const amt = requireValue(arg.amt, "amt");
1813 jv[sfMPTAmount] = std::to_string(amt);
1814
1815 if (arg.proof)
1816 {
1817 jv[sfZKProof] = *arg.proof;
1818 }
1819 else
1820 {
1821 auto const seq = arg.ticketSeq ? *arg.ticketSeq : env_.seq(account);
1822 auto const contextHash =
1823 getClawbackContextHash(account.id(), issuanceID(), seq, holder.id());
1824
1825 auto const privKey = getPrivKey(account);
1826 if (!privKey || privKey->size() != kEcPrivKeyLength)
1827 Throw<std::runtime_error>("Failed to get clawback private key");
1828
1829 auto const proof =
1830 getClawbackProof(holder, amt, requireValue(privKey, "privKey"), contextHash);
1831
1832 setProofOrDummy(jv, proof, kEcClawbackProofLength);
1833 }
1834
1835 auto const holderPubAmt = getBalance(holder);
1836 auto const prevCOA = getIssuanceConfidentialBalance();
1837 auto const prevOA = getIssuanceOutstandingBalance();
1838 auto const prevVersion = getMPTokenVersion(holder);
1839
1840 if (submit(arg, {jv, loc}) == tesSUCCESS)
1841 {
1842 auto const postCOA = getIssuanceConfidentialBalance();
1843 auto const postOA = getIssuanceOutstandingBalance();
1844 auto const postVersion = getMPTokenVersion(holder);
1845
1846 // Verify holder's public balance is unchanged
1847 env_.require(MptBalance(*this, holder, holderPubAmt, loc));
1848
1849 // Verify COA and OA are reduced correctly
1850 env_.require(
1851 RequireAny([&]() -> bool { return prevCOA >= amt && postCOA == prevCOA - amt; }, loc));
1852 env_.require(RequireAny(
1853 [&]() -> bool {
1854 return prevOA && postOA && *prevOA >= amt && *postOA == *prevOA - amt;
1855 },
1856 loc));
1857
1858 // Verify holder's confidential balances are zeroed out
1859 env_.require(RequireAny(
1860 [&]() -> bool { return getDecryptedBalance(holder, holderEncryptedInbox) == 0; }, loc));
1861 env_.require(RequireAny(
1862 [&]() -> bool { return getDecryptedBalance(holder, holderEncryptedSpending) == 0; },
1863 loc));
1864 env_.require(RequireAny(
1865 [&]() -> bool { return getDecryptedBalance(holder, issuerEncryptedBalance) == 0; },
1866 loc));
1867 env_.require(RequireAny(
1868 [&]() -> bool { return getDecryptedBalance(holder, auditorEncryptedBalance) == 0; },
1869 loc));
1870
1871 // Verify version is incremented
1872 env_.require(RequireAny([&]() -> bool { return postVersion == prevVersion + 1; }, loc));
1873 }
1874}
1875
1878{
1879 unsigned char privKey[kEcPrivKeyLength];
1880 secp256k1_pubkey pubKey;
1881 if (secp256k1_elgamal_generate_keypair(secp256k1Context(), privKey, &pubKey) == 0)
1882 Throw<std::runtime_error>("failed to generate key pair");
1883
1884 // Serialize public key to compressed format (33 bytes)
1885 unsigned char compressedPubKey[kEcPubKeyLength];
1886 size_t outLen = kEcPubKeyLength;
1887 if (secp256k1_ec_pubkey_serialize(
1888 secp256k1Context(), compressedPubKey, &outLen, &pubKey, SECP256K1_EC_COMPRESSED) != 1 ||
1889 outLen != kEcPubKeyLength)
1890 {
1891 Throw<std::runtime_error>("failed to serialize public key");
1892 }
1893
1894 auto& pubKeyEpochs = pubKeys_[account.id()];
1895 pubKeyEpochs.emplace_back(compressedPubKey, kEcPubKeyLength);
1896 privKeys_[account.id()].emplace_back(privKey, kEcPrivKeyLength);
1897
1898 return static_cast<std::uint32_t>(pubKeyEpochs.size() - 1);
1899}
1900
1903{
1904 return keyAtEpoch(pubKeys_, account.id(), epoch);
1905}
1906
1909{
1910 return keyAtEpoch(privKeys_, account.id(), epoch);
1911}
1912
1913Buffer
1914MPTTester::encryptAmount(Account const& account, uint64_t const amt, Buffer const& blindingFactor)
1915 const
1916{
1917 if (auto const pubKey = getPubKey(account))
1918 {
1919 if (auto const result = xrpl::encryptAmount(amt, *pubKey, blindingFactor))
1920 return *result;
1921 }
1922
1923 // Return a dummy buffer on failure to allow testing of
1924 // failures that occur prior to encryption.
1926}
1927
1930 Account const& account,
1931 Buffer const& amt,
1932 std::optional<std::uint32_t> epoch) const
1933{
1935 return std::nullopt;
1936
1937 auto const pair = makeEcPair(amt);
1938 if (!pair)
1939 return std::nullopt;
1940
1941 auto const privKey = getPrivKey(account, epoch);
1942 if (!privKey || privKey->size() != kEcPrivKeyLength)
1943 return std::nullopt;
1944
1945 uint64_t decryptedAmt = 0;
1946 if (secp256k1_elgamal_decrypt(
1948 &decryptedAmt,
1949 &pair->c1,
1950 &pair->c2,
1951 privKey->data(),
1952 kElGamalDecryptRangeLow,
1953 kElGamalDecryptRangeHigh) == 0)
1954 {
1955 return std::nullopt;
1956 }
1957
1958 return decryptedAmt;
1959}
1960
1963{
1964 auto const encryptedAmt = getEncryptedBalance(account, balanceType);
1965
1966 // Return zero to test cases like Feature Disabled, where the ledger object
1967 // does not exist.
1968 if (!encryptedAmt)
1969 return 0;
1970
1971 Account decryptor = account;
1972
1973 // A mirror stays encrypted under the key it was written with, so a rotation
1974 // leaves it readable only by that generation of the key, not the latest one.
1976
1977 if (balanceType == issuerEncryptedBalance)
1978 {
1979 decryptor = issuer_;
1980 epoch = getMirrorEpoch(account, sfIssuerKeyMirrorEpoch).value_or(0);
1981 }
1982 else if (balanceType == auditorEncryptedBalance)
1983 {
1984 if (!auditor_)
1985 return std::nullopt;
1986 decryptor = *auditor_;
1987 epoch = getMirrorEpoch(account, sfAuditorKeyMirrorEpoch).value_or(0);
1988 }
1989
1990 return decryptAmount(decryptor, *encryptedAmt, epoch);
1991}
1992
1995{
1996 json::Value jv;
1997 setAccountField(jv, arg.account);
1998 setIssuanceIdField(jv, arg.id);
1999 jv[sfTransactionType] = jss::ConfidentialMPTMergeInbox;
2000 return jv;
2001}
2002
2003void
2005{
2006 json::Value const jv = mergeInboxJV(arg);
2007 Account const& account = requireValue(arg.account, "account");
2008
2009 auto const holderPubAmt = getBalance(account);
2010 auto const prevCOA = getIssuanceConfidentialBalance();
2011 auto const prevOA = getIssuanceOutstandingBalance();
2012 auto const prevInboxBalance = getDecryptedBalance(account, holderEncryptedInbox);
2013 auto const prevSpendingBalance = getDecryptedBalance(account, holderEncryptedSpending);
2014 auto const prevIssuerBalance = getDecryptedBalance(account, issuerEncryptedBalance);
2015 auto const prevIssuerEncrypted = getEncryptedBalance(account, issuerEncryptedBalance);
2016 auto const prevAuditorEncrypted = getEncryptedBalance(account, auditorEncryptedBalance);
2017 auto const prevVersion = getMPTokenVersion(account);
2018
2019 if (!prevInboxBalance || !prevSpendingBalance || !prevIssuerBalance)
2020 Throw<std::runtime_error>("Failed to get pre-mergeInbox balances");
2021
2022 if (submit(arg, {jv, loc}) == tesSUCCESS)
2023 {
2024 auto const postCOA = getIssuanceConfidentialBalance();
2025 auto const postOA = getIssuanceOutstandingBalance();
2026 auto const postInboxBalance = getDecryptedBalance(account, holderEncryptedInbox);
2027 auto const postSpendingBalance = getDecryptedBalance(account, holderEncryptedSpending);
2028 auto const postIssuerBalance = getDecryptedBalance(account, issuerEncryptedBalance);
2029 auto const postInboxEncrypted = getEncryptedBalance(account, holderEncryptedInbox);
2030 auto const postIssuerEncrypted = getEncryptedBalance(account, issuerEncryptedBalance);
2031 auto const postAuditorEncrypted = getEncryptedBalance(account, auditorEncryptedBalance);
2032 auto const postVersion = getMPTokenVersion(account);
2033
2034 if (!postInboxBalance || !postSpendingBalance || !postIssuerBalance ||
2035 !prevIssuerEncrypted || !postInboxEncrypted || !postIssuerEncrypted)
2036 Throw<std::runtime_error>("Failed to get post-mergeInbox balances");
2037
2038 env_.require(MptBalance(*this, account, holderPubAmt, loc));
2039 env_.require(
2040 RequireAny([&]() -> bool { return prevOA && postOA && *prevOA == *postOA; }, loc));
2041 env_.require(RequireAny([&]() -> bool { return prevCOA == postCOA; }, loc));
2042
2043 env_.require(RequireAny(
2044 [&]() -> bool {
2045 return *postSpendingBalance == *prevInboxBalance + *prevSpendingBalance &&
2046 *postInboxBalance == 0;
2047 },
2048 loc));
2049
2050 env_.require(
2051 RequireAny([&]() -> bool { return *prevIssuerBalance == *postIssuerBalance; }, loc));
2052
2053 auto const holderPubKey = getPubKey(account);
2054 if (!holderPubKey)
2055 Throw<std::runtime_error>("Failed to get holder public key");
2056
2057 auto const expectedInbox = encryptCanonicalZeroAmount(
2058 requireValue(holderPubKey, "holderPubKey"), account.id(), issuanceID());
2059 if (!expectedInbox)
2060 Throw<std::runtime_error>("Failed to get canonical zero encryption");
2061
2062 env_.require(
2063 RequireAny([&]() -> bool { return *postInboxEncrypted == *expectedInbox; }, loc));
2064 env_.require(RequireAny(
2065 [&]() -> bool { return *postIssuerEncrypted == *prevIssuerEncrypted; }, loc));
2066 env_.require(RequireAny(
2067 [&]() -> bool {
2068 return postAuditorEncrypted.has_value() == prevAuditorEncrypted.has_value() &&
2069 (!postAuditorEncrypted || *postAuditorEncrypted == *prevAuditorEncrypted);
2070 },
2071 loc));
2072 env_.require(RequireAny([&]() -> bool { return postVersion == prevVersion + 1; }, loc));
2073
2074 env_.require(RequireAny(
2075 [&]() -> bool {
2076 return *postSpendingBalance + *postInboxBalance == *postIssuerBalance;
2077 },
2078 loc));
2079 }
2080}
2081
2084{
2085 if (!id_)
2086 return std::nullopt;
2087
2088 auto const sle = env_.current()->read(keylet::mptokenIssuance(*id_));
2089
2090 if (!sle)
2091 return std::nullopt;
2092
2093 return (*sle)[sfOutstandingAmount];
2094}
2095
2098{
2099 if (!id_)
2100 Throw<std::runtime_error>("Issuance ID does not exist");
2101
2102 auto const sle = env_.current()->read(keylet::mptoken(*id_, account));
2103
2104 // return 0 here instead of throwing an exception since tests for
2105 // preclaim will check if the MPToken exists
2106 if (!sle)
2107 return 0;
2108
2109 return (*sle)[~sfConfidentialBalanceVersion].value_or(0);
2110}
2111
2112void
2114{
2115 json::Value const jv = convertBackJV(arg, ticketOrSeq(arg.ticketSeq, arg.account));
2116
2117 Account const& account = requireValue(arg.account, "account");
2118 auto const amt = requireValue(arg.amt, "amt");
2119
2120 auto const prevInboxBalance = getDecryptedBalance(account, holderEncryptedInbox);
2121 auto const prevSpendingBalance = getDecryptedBalance(account, holderEncryptedSpending);
2122 auto const prevIssuerBalance = getDecryptedBalance(account, issuerEncryptedBalance);
2123
2124 if (!prevInboxBalance || !prevSpendingBalance || !prevIssuerBalance)
2125 Throw<std::runtime_error>("Failed to get Pre-convertBack balance");
2126
2127 auto const holderAmt = getBalance(account);
2128 auto const prevConfidentialOutstanding = getIssuanceConfidentialBalance();
2129
2130 std::optional<uint64_t> prevAuditorBalance;
2131 if (arg.auditorEncryptedAmt || auditor_)
2132 {
2133 prevAuditorBalance = getDecryptedBalance(account, auditorEncryptedBalance);
2134 if (!prevAuditorBalance)
2135 Throw<std::runtime_error>("Failed to get Pre-convertBack balance");
2136 }
2137
2138 auto const prevOutstanding = getIssuanceOutstandingBalance();
2139 auto const prevVersion = getMPTokenVersion(account);
2140
2141 if (submit(arg, {jv, loc}) == tesSUCCESS)
2142 {
2143 auto const postConfidentialOutstanding = getIssuanceConfidentialBalance();
2144 auto const postOutstanding = getIssuanceOutstandingBalance();
2145 auto const postVersion = getMPTokenVersion(account);
2146 env_.require(MptBalance(*this, account, holderAmt + amt, loc));
2147 env_.require(RequireAny(
2148 [&]() -> bool {
2149 return prevOutstanding && postOutstanding && *prevOutstanding == *postOutstanding;
2150 },
2151 loc));
2152 env_.require(RequireAny(
2153 [&]() -> bool {
2154 return prevConfidentialOutstanding - amt == postConfidentialOutstanding;
2155 },
2156 loc));
2157
2158 auto const postInboxBalance = getDecryptedBalance(account, holderEncryptedInbox);
2159 auto const postIssuerBalance = getDecryptedBalance(account, issuerEncryptedBalance);
2160 auto const postSpendingBalance = getDecryptedBalance(account, holderEncryptedSpending);
2161
2162 if (!postInboxBalance || !postIssuerBalance || !postSpendingBalance)
2163 Throw<std::runtime_error>("Failed to get post-convertBack balance");
2164
2165 if (arg.auditorEncryptedAmt || auditor_)
2166 {
2167 auto const postAuditorBalance = getDecryptedBalance(account, auditorEncryptedBalance);
2168
2169 if (!postAuditorBalance)
2170 Throw<std::runtime_error>("Failed to get post-convertBack balance");
2171
2172 // auditor's encrypted balance is updated correctly
2173 env_.require(RequireAny(
2174 [&]() -> bool { return *prevAuditorBalance - amt == *postAuditorBalance; }, loc));
2175 }
2176
2177 // inbox balance should not change
2178 env_.require(
2179 RequireAny([&]() -> bool { return *postInboxBalance == *prevInboxBalance; }, loc));
2180
2181 // issuer's encrypted balance is updated correctly
2182 env_.require(RequireAny(
2183 [&]() -> bool { return *prevIssuerBalance - amt == *postIssuerBalance; }, loc));
2184
2185 // holder's spending balance is updated correctly
2186 env_.require(RequireAny(
2187 [&]() -> bool { return *prevSpendingBalance - amt == *postSpendingBalance; }, loc));
2188
2189 // holder's confidential balance version is updated correctly
2190 env_.require(RequireAny([&]() -> bool { return postVersion == prevVersion + 1; }, loc));
2191
2192 // sum of holder's inbox and spending balance should equal to issuer's
2193 // encrypted balance
2194 env_.require(RequireAny(
2195 [&]() -> bool {
2196 return *postInboxBalance + *postSpendingBalance == *postIssuerBalance;
2197 },
2198 loc));
2199 }
2200}
2201
2204{
2205 json::Value jv;
2206 Account const& account = setAccountField(jv, arg.account);
2207
2208 jv[jss::TransactionType] = jss::ConfidentialMPTConvertBack;
2209 setIssuanceIdField(jv, arg.id);
2210
2211 auto const amt = requireValue(arg.amt, "amt");
2212 jv[sfMPTAmount.jsonName] = std::to_string(amt);
2213
2214 fillConversionCiphertexts(arg, jv, account, amt);
2215
2216 auto const prevSpendingBalance = getDecryptedBalance(account, holderEncryptedSpending);
2217 if (!prevSpendingBalance)
2218 Throw<std::runtime_error>("convertBackJV: failed to read spending balance from ledger");
2219
2220 Buffer pedersenCommitment;
2221 Buffer const pcBlindingFactor = generateBlindingFactor();
2222 if (arg.pedersenCommitment)
2223 {
2224 pedersenCommitment = *arg.pedersenCommitment;
2225 }
2226 else
2227 {
2228 pedersenCommitment = getPedersenCommitment(*prevSpendingBalance, pcBlindingFactor);
2229 }
2230
2231 jv[sfBalanceCommitment] = strHex(pedersenCommitment);
2232
2233 if (arg.proof)
2234 {
2235 jv[sfZKProof.jsonName] = strHex(*arg.proof);
2236 }
2237 else
2238 {
2239 auto const version = getMPTokenVersion(account);
2240 auto const prevEncSpending = getEncryptedBalance(account, holderEncryptedSpending);
2241 auto const contextHash =
2242 getConvertBackContextHash(account.id(), issuanceID(), seq, version);
2243
2245 if (prevEncSpending)
2246 {
2247 proof = getConvertBackProof(
2248 account,
2249 amt,
2250 contextHash,
2251 {
2252 .pedersenCommitment = pedersenCommitment,
2253 .amt = *prevSpendingBalance,
2254 .encryptedAmt = *prevEncSpending,
2255 .blindingFactor = pcBlindingFactor,
2256 });
2257 }
2258
2259 setProofOrDummy(jv, proof, kEcConvertBackProofLength);
2260 }
2261
2262 return jv;
2263}
2264
2265void
2267{
2268 json::Value jv;
2269 jv[jss::TransactionType] = jss::ConfidentialMPTMirrorUpdate;
2270
2271 setAccountField(jv, arg.account);
2272 setIssuanceIdField(jv, arg.id);
2273
2274 if (arg.holder)
2275 jv[sfHolder] = arg.holder->human();
2276 if (arg.issuerEncryptedAmount)
2277 jv[sfIssuerEncryptedAmount] = strHex(*arg.issuerEncryptedAmount);
2278 if (arg.auditorEncryptedAmount)
2279 jv[sfAuditorEncryptedAmount] = strHex(*arg.auditorEncryptedAmount);
2280
2281 // Placeholder for proof, the logic will be added in the future
2282 if (arg.zkProof)
2283 {
2284 jv[sfZKProof] = strHex(*arg.zkProof);
2285 }
2286 else
2287 {
2289 }
2290
2291 submit(arg, {jv, loc});
2292}
2293
2294void
2296{
2297 json::Value jv;
2298 jv[jss::TransactionType] = jss::ConfidentialMPTHolderKeyUpdate;
2299
2300 setAccountField(jv, arg.account);
2301 setIssuanceIdField(jv, arg.id);
2302
2303 if (arg.holderPubKey)
2304 jv[sfHolderEncryptionKey.jsonName] = strHex(*arg.holderPubKey);
2305 if (arg.spendingCiphertext)
2306 jv[sfConfidentialBalanceSpending.jsonName] = strHex(*arg.spendingCiphertext);
2307 if (arg.inboxCiphertext)
2308 jv[sfConfidentialBalanceInbox.jsonName] = strHex(*arg.inboxCiphertext);
2309 if (arg.proof)
2310 jv[sfZKProof.jsonName] = strHex(*arg.proof);
2311
2312 submit(arg, {jv, loc});
2313}
2314
2315} // namespace xrpl::test::jtx
bool expect(Condition const &shouldBeTrue)
Evaluate a test condition.
Definition suite.h:235
Represents a JSON value.
Definition json_value.h:117
std::string asString() const
Returns the unquoted string value.
pointer data()
Definition base_uint.h:117
Like std::vector<char> but better.
Definition Buffer.h:19
std::size_t size() const noexcept
Returns the number of bytes in the buffer.
Definition Buffer.h:123
std::uint8_t const * data() const noexcept
Return a pointer to beginning of the storage.
Definition Buffer.h:148
constexpr value_type value() const
Returns the underlying value.
Definition MPTAmount.h:135
static constexpr std::array< FlagMapping, 7 > flagMapping
Identifies fields.
Definition SField.h:132
MPTAmount mpt() const
Definition STAmount.cpp:302
An immutable linear range of bytes.
Definition Slice.h:28
std::uint8_t const * data() const noexcept
Return a pointer to beginning of the storage.
Definition Slice.h:88
std::size_t size() const noexcept
Returns the number of bytes in the storage.
Definition Slice.h:70
Immutable cryptographic account descriptor.
Definition jtx/Account.h:21
AccountID id() const
Returns the Account ID.
A transaction testing environment.
Definition Env.h:161
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
beast::unit_test::Suite & test
Definition Env.h:163
std::unordered_map< AccountID, std::vector< Buffer > > pubKeys_
Definition mpt.h:521
static constexpr auto holderEncryptedInbox
Definition mpt.h:533
void mirrorUpdate(MPTMirrorUpdate const &arg=MPTMirrorUpdate{}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:2266
std::optional< std::uint32_t > getMirrorEpoch(Account const &holder, SF_UINT32 const &field) const
Definition mpt.cpp:792
std::unordered_map< std::string, Account > const holders_
Definition mpt.h:515
Buffer encryptAmount(Account const &account, uint64_t const amt, Buffer const &blindingFactor) const
Definition mpt.cpp:1914
bool forObject(std::function< bool(SLEP const &sle)> const &cb, std::optional< Account > const &holder=std::nullopt) const
Definition mpt.cpp:674
void mergeInbox(MPTMergeInbox const &arg=MPTMergeInbox{}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:2004
bool checkMPTokenAmount(Account const &holder, std::int64_t expectedAmount) const
Definition mpt.cpp:697
std::optional< MPTID > id_
Definition mpt.h:517
json::Value convertJV(MPTConvert const &arg, std::uint32_t seq)
Build a confidential convert JV without submitting it.
Definition mpt.cpp:1403
static Buffer getPedersenCommitment(std::uint64_t const amount, Buffer const &pedersenBlindingFactor)
Definition mpt.cpp:1075
static json::Value destroyJV(MPTDestroy const &arg=MPTDestroy{})
Definition mpt.cpp:408
void setIssuanceIdField(json::Value &jv, std::optional< MPTID > const &id) const
Sets sfMPTokenIssuanceID on jv, falling back to id_ if arg's id is not set.
Definition mpt.cpp:1184
std::optional< Buffer > getEncryptedBalance(Account const &account, EncryptedBalanceType option=holderEncryptedInbox) const
Definition mpt.cpp:1136
void convertBack(MPTConvertBack const &arg=MPTConvertBack{}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:2113
std::optional< uint64_t > getDecryptedBalance(Account const &account, EncryptedBalanceType balanceType) const
Definition mpt.cpp:1962
bool checkMirrorEpochs(Account const &holder, std::optional< std::uint32_t > issuerKeyMirrorEpoch, std::optional< std::uint32_t > auditorKeyMirrorEpoch) const
Definition mpt.cpp:778
Account const & holder(std::string const &h) const
Definition mpt.cpp:431
static json::Value authorizeJV(MPTAuthorize const &arg=MPTAuthorize{})
Definition mpt.cpp:440
bool isTransferFeePresent() const
Definition mpt.cpp:752
std::optional< Buffer > getClawbackProof(Account const &holder, std::uint64_t amount, Buffer const &privateKey, UInt256 const &txHash) const
Definition mpt.cpp:951
bool checkMetadata(std::string const &metadata) const
Definition mpt.cpp:725
std::uint32_t generateKeyPair(Account const &account)
Definition mpt.cpp:1877
bool checkFlags(uint32_t const expectedFlags, std::optional< Account > const &holder=std::nullopt) const
Definition mpt.cpp:719
bool isMetadataPresent() const
Definition mpt.cpp:735
MPT operator[](std::string const &name) const
Definition mpt.cpp:1211
void holderKeyUpdate(MPTHolderKeyUpdate const &arg=MPTHolderKeyUpdate{}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:2295
std::optional< Buffer > getSchnorrProof(Account const &account, UInt256 const &ctxHash) const
Definition mpt.cpp:991
static constexpr auto issuerEncryptedBalance
Definition mpt.h:532
bool checkDomainID(std::optional< UInt256 > expected) const
Definition mpt.cpp:687
std::uint32_t getMPTokenVersion(Account const account) const
Definition mpt.cpp:2097
Account const & issuer() const
Definition mpt.h:741
std::optional< std::int64_t > getIssuanceOutstandingBalance() const
Definition mpt.cpp:2083
static json::Value createJV(MPTCreate const &arg=MPTCreate{})
Definition mpt.cpp:319
MPTTester(Env &env, Account issuer, MPTInit const &constr={}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:229
json::Value sendJV(MPTConfidentialSend const &arg, std::uint32_t seq, std::optional< ConfidentialSendChainState > chain=std::nullopt)
Build a confidential send JV.
Definition mpt.cpp:1574
ConfidentialSendChainState chainAfterSend(Account const &sender, std::uint64_t sendAmt, json::Value const &jv) const
Compute the projected sender state after a confidential send in a batch.
Definition mpt.cpp:1760
static json::Value setJV(MPTSet const &set={})
Definition mpt.cpp:534
MPTID const & issuanceID() const
Definition mpt.h:768
static constexpr auto holderEncryptedSpending
Definition mpt.h:534
std::optional< uint64_t > decryptAmount(Account const &account, Buffer const &amt, std::optional< std::uint32_t > epoch=std::nullopt) const
Definition mpt.cpp:1929
void send(MPTConfidentialSend const &arg=MPTConfidentialSend{}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:1432
bool checkEncryptionKeys(std::optional< Account > const &issuerKeyOwner, std::optional< Account > const &auditorKeyOwner) const
Definition mpt.cpp:805
bool checkImmutableFlags(std::uint32_t expectedFlags) const
Definition mpt.cpp:758
std::int64_t getBalance(Account const &account) const
Definition mpt.cpp:921
TER submit(A const &arg, WithSourceLocation< json::Value > jv)
Definition mpt.h:877
void fillConversionCiphertexts(T const &arg, json::Value &jv, Account const &account, std::uint64_t const amount) const
Definition mpt.cpp:1224
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:1014
std::optional< Buffer > getPubKey(Account const &account, std::optional< std::uint32_t > epoch=std::nullopt) const
Definition mpt.cpp:1902
std::optional< Account > const auditor_
Definition mpt.h:516
Account const issuer_
Definition mpt.h:514
json::Value mergeInboxJV(MPTMergeInbox const &arg=MPTMergeInbox{}) const
Definition mpt.cpp:1994
void authorizeHolders(Holders const &holders, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:525
SLE::const_pointer SLEP
Definition mpt.h:865
static constexpr auto auditorEncryptedBalance
Definition mpt.h:535
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, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:826
std::unordered_map< AccountID, std::vector< Buffer > > privKeys_
Definition mpt.h:522
void claw(Account const &issuer, Account const &holder, std::int64_t amount, std::optional< TER > err=std::nullopt, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:881
void confidentialClaw(MPTConfidentialClawback const &arg=MPTConfidentialClawback{}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:1800
PrettyAmount mpt(std::int64_t amount) const
Definition mpt.cpp:905
void create(MPTCreate const &arg=MPTCreate{}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:343
void set(MPTSet const &set={}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:577
std::int64_t getIssuanceConfidentialBalance() const
Definition mpt.cpp:939
std::uint32_t ticketOrSeq(std::optional< std::uint32_t > const &ticketSeq, std::optional< Account > const &account) const
Definition mpt.cpp:1201
bool checkTransferFee(std::uint16_t transferFee) const
Definition mpt.cpp:742
bool checkIssuanceConfidentialBalance(std::int64_t expectedAmount) const
Definition mpt.cpp:711
void authorize(MPTAuthorize const &arg=MPTAuthorize{}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:455
PrettyAmount operator()(std::int64_t amount) const
Definition mpt.cpp:1217
std::uint32_t getFlags(std::optional< Account > const &holder) const
Definition mpt.cpp:1170
bool checkKeyEpochs(std::optional< std::uint32_t > issuerKeyEpoch, std::optional< std::uint32_t > auditorKeyEpoch) const
Definition mpt.cpp:767
static std::unordered_map< std::string, Account > makeHolders(std::vector< Account > const &holders)
Definition mpt.cpp:217
bool checkMPTokenOutstandingAmount(std::int64_t expectedAmount) const
Definition mpt.cpp:704
void convert(MPTConvert const &arg=MPTConvert{}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:1266
std::optional< Buffer > getPrivKey(Account const &account, std::optional< std::uint32_t > epoch=std::nullopt) const
Definition mpt.cpp:1908
void destroy(MPTDestroy const &arg=MPTDestroy{}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:421
std::optional< Buffer > getConvertBackProof(Account const &holder, std::uint64_t const amount, UInt256 const &contextHash, PedersenProofParams const &pcParams) const
Definition mpt.cpp:1101
json::Value convertBackJV(MPTConvertBack const &arg, std::uint32_t seq)
Build a confidential convertBack JV without submitting it.
Definition mpt.cpp:2203
Converts to MPT Issue or STAmount.
Test helper that checks MPT issuance or holder balances.
Definition mpt.h:103
std::source_location testLoc_
Definition mpt.h:109
MPTTester const & tester_
Definition mpt.h:105
std::int64_t const amount_
Definition mpt.h:107
Account const & account_
Definition mpt.h:106
void operator()(Env &env) const
Definition mpt.cpp:201
std::source_location checkLoc_
Definition mpt.h:111
Test helper that checks MPT flag settings after creation.
Definition mpt.h:74
std::uint32_t flags_
Definition mpt.h:77
std::source_location checkLoc_
Definition mpt.h:82
void operator()(Env &env) const
Definition mpt.cpp:191
std::optional< Account > holder_
Definition mpt.h:78
MPTTester & tester_
Definition mpt.h:76
std::source_location testLoc_
Definition mpt.h:80
Match the number of items in the account's owner directory.
Definition owners.h:55
Test helper that accepts any condition supplied by a callback.
Definition mpt.h:136
std::function< bool()> cb_
Definition mpt.h:138
std::source_location testLoc_
Definition mpt.h:140
void operator()(Env &env) const
Definition mpt.cpp:211
std::source_location checkLoc_
Definition mpt.h:142
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:18
T current(T... args)
T data(T... args)
T emplace(T... args)
T cend(T... args)
T file_name(T... args)
T find(T... args)
T get_if(T... args)
T is_same_v
T memcpy(T... args)
T memset(T... args)
T min(T... args)
@ Array
array value (ordered list)
Definition json_value.h:28
STL namespace.
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:573
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:567
json::Value create(AccountID const &account, AccountID const &to, STAmount const &amount, NetClock::duration const &settleDelay, PublicKey const &pk, std::optional< NetClock::time_point > const &cancelAfter, std::optional< std::uint32_t > const &dstTag)
json::Value claw(Account const &account, STAmount const &amount, std::optional< Account > const &mptHolder)
Definition trust.cpp:51
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
std::vector< STAmount > fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition AMMTest.cpp:34
static MPTCreate makeMPTCreate(MPTInitDef const &arg)
Definition mpt.cpp:275
Buffer gMakeZeroBuffer(std::size_t size)
Create a zero-initialized buffer for malformed cryptography test inputs.
Definition mpt.h:62
std::vector< Account > Holders
Definition mpt.h:157
std::optional< ConfidentialSendChainState > computeNextSendChainState(std::uint64_t currentSpending, Slice const &currentEncSpending, std::uint32_t currentVersion, std::uint64_t sendAmt, Slice const &senderEncAmt)
Use this when building a second (or later) confidential send from the same account in the same batch.
Definition mpt.cpp:1778
static Buffer parseSenderEncAmt(json::Value const &jv)
Definition mpt.cpp:1750
constexpr std::size_t kEcPubKeyLength
Length of EC public key (compressed).
Definition Protocol.h:485
constexpr std::uint8_t kEcCompressedPrefixEvenY
Compressed EC point prefix for even y-coordinate.
Definition Protocol.h:561
constexpr std::size_t kEcBlindingFactorLength
Length of the EC blinding factor in bytes.
Definition Protocol.h:495
std::optional< Buffer > encryptCanonicalZeroAmount(Slice const &pubKeySlice, AccountID const &account, MPTID const &mptId)
Generates the canonical zero encryption for a specific MPToken.
UInt256 getConvertContextHash(AccountID const &account, UInt192 const &issuanceID, std::uint32_t sequence)
Generates the context hash for ConfidentialMPTConvert transactions.
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:13
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.
UInt256 getConvertBackContextHash(AccountID const &account, UInt192 const &issuanceID, std::uint32_t sequence, std::uint32_t version)
Generates the context hash for ConfidentialMPTConvertBack transactions.
constexpr std::size_t kEcClawbackProofLength
Length of the ZKProof for ConfidentialMPTClawback.
Definition Protocol.h:541
std::optional< Buffer > encryptAmount(uint64_t const amt, Slice const &pubKeySlice, Slice const &blindingFactor)
Encrypts an amount using ElGamal encryption.
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
constexpr std::size_t kEcSchnorrProofLength
Length of Schnorr ZKProof for public key registration (compact form) in bytes.
Definition Protocol.h:500
std::optional< EcPair > makeEcPair(Slice const &buffer)
Parses an ElGamal ciphertext into two secp256k1 public key components.
bool matches(char const *string, char const *regex)
Return true if the string loosely matches the regex.
Definition STTx_test.cpp:49
constexpr std::size_t kEcGamalEncryptedTotalLength
EC ElGamal ciphertext length: two compressed EC points concatenated.
Definition Protocol.h:480
TypedField< STInteger< std::uint32_t > > SF_UINT32
Definition SField.h:344
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
constexpr std::size_t kEcConvertBackProofLength
128 bytes compact sigma proof + 688 bytes single bulletproof.
Definition Protocol.h:535
BaseUInt< 256 > UInt256
Definition base_uint.h:580
constexpr std::size_t kEcPedersenCommitmentLength
Length of Pedersen Commitment (compressed).
Definition Protocol.h:505
std::optional< Buffer > homomorphicSubtract(Slice const &a, Slice const &b)
Homomorphically subtracts two ElGamal ciphertexts.
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
BaseUInt< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
Definition UintTypes.h:54
TypedField< STBlob > SF_VL
Definition SField.h:362
secp256k1_context const * secp256k1Context()
Definition secp256k1.h:9
constexpr std::size_t kEcEqualityProofLength
Length of compact equality proof.
Definition Protocol.h:546
UInt256 getClawbackContextHash(AccountID const &account, UInt192 const &issuanceID, std::uint32_t sequence, AccountID const &holder)
Generates the context hash for ConfidentialMPTClawback transactions.
Buffer generateBlindingFactor()
Generates a cryptographically secure blinding factor (size=xrpl::kEcBlindingFactorLength).
MPTID makeMptID(std::uint32_t const sequence, AccountID const &account)
Definition Indexes.cpp:206
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
constexpr std::size_t kEcSendProofLength
192 bytes compact sigma proof + 754 bytes double bulletproof.
Definition Protocol.h:525
bool isTesSuccess(TER x) noexcept
Definition TER.h:683
@ tecDUPLICATE
Definition TER.h:323
STAmount multiply(STAmount const &amount, Number const &frac, Number::RoundingMode rm)
constexpr std::size_t kEcPrivKeyLength
Length of EC private key in bytes.
Definition Protocol.h:490
@ tesSUCCESS
Definition TER.h:250
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
T has_value(T... args)
T push_back(T... args)
T size(T... args)
When building multiple confidential sends from the same account inside a single batch transaction,...
Definition mpt.h:468
Arguments for building an MPTokenAuthorize test transaction.
Definition mpt.h:240
std::optional< Account > holder
Definition mpt.h:242
std::optional< Account > account
Definition mpt.h:241
std::optional< MPTID > id
Definition mpt.h:243
std::optional< std::uint32_t > flags
Definition mpt.h:246
Arguments for building a ConfidentialMPTClawback test transaction.
Definition mpt.h:378
std::optional< std::uint32_t > ticketSeq
Definition mpt.h:385
std::optional< std::string > proof
Definition mpt.h:383
std::optional< std::uint64_t > amt
Definition mpt.h:382
std::optional< MPTID > id
Definition mpt.h:381
std::optional< Account > account
Definition mpt.h:379
std::optional< Account > holder
Definition mpt.h:380
Arguments for building a ConfidentialMPTSend test transaction.
Definition mpt.h:322
std::optional< std::vector< std::string > > credentials
Definition mpt.h:334
std::optional< Buffer > balanceCommitment
Definition mpt.h:338
std::optional< MPTID > id
Definition mpt.h:325
std::optional< std::uint32_t > ticketSeq
Definition mpt.h:341
std::optional< std::uint64_t > amt
Definition mpt.h:327
std::optional< bool > fillAuditorEncryptedAmt
Definition mpt.h:333
std::optional< Account > account
Definition mpt.h:323
std::optional< Buffer > auditorEncryptedAmt
Definition mpt.h:332
std::optional< Account > dest
Definition mpt.h:324
std::optional< Buffer > blindingFactor
Definition mpt.h:336
std::optional< Buffer > amountCommitment
Definition mpt.h:337
std::optional< std::string > proof
Definition mpt.h:328
std::optional< Buffer > senderEncryptedAmt
Definition mpt.h:329
std::optional< Buffer > destEncryptedAmt
Definition mpt.h:330
std::optional< Buffer > issuerEncryptedAmt
Definition mpt.h:331
Arguments for building a ConfidentialMPTConvertBack test transaction.
Definition mpt.h:353
std::optional< Buffer > auditorEncryptedAmt
Definition mpt.h:360
std::optional< MPTID > id
Definition mpt.h:355
std::optional< std::uint64_t > amt
Definition mpt.h:356
std::optional< Account > account
Definition mpt.h:354
std::optional< std::uint32_t > ticketSeq
Definition mpt.h:366
std::optional< Buffer > proof
Definition mpt.h:357
std::optional< Buffer > pedersenCommitment
Definition mpt.h:364
Arguments for building a ConfidentialMPTConvert test transaction.
Definition mpt.h:276
std::optional< bool > fillAuditorEncryptedAmt
Definition mpt.h:281
std::optional< MPTID > id
Definition mpt.h:278
std::optional< std::uint64_t > amt
Definition mpt.h:279
std::optional< Buffer > auditorEncryptedAmt
Definition mpt.h:290
std::optional< bool > fillSchnorrProof
Definition mpt.h:286
std::optional< Buffer > holderPubKey
Definition mpt.h:287
std::optional< Account > account
Definition mpt.h:277
std::optional< std::string > proof
Definition mpt.h:280
std::optional< std::uint32_t > ticketSeq
Definition mpt.h:294
Arguments for building an MPTokenIssuanceCreate test transaction.
Definition mpt.h:163
std::optional< std::uint8_t > assetScale
Definition mpt.h:167
std::optional< std::uint32_t > immutableFlags
Definition mpt.h:179
std::optional< std::string > metadata
Definition mpt.h:169
std::optional< std::uint32_t > flags
Definition mpt.h:178
std::optional< std::uint64_t > maxAmt
Definition mpt.h:166
std::optional< UInt256 > domainID
Definition mpt.h:181
std::optional< Account > issuer
Definition mpt.h:165
std::optional< std::pair< std::vector< Account >, std::uint64_t > > pay
Definition mpt.h:177
std::optional< std::uint16_t > transferFee
Definition mpt.h:168
std::optional< std::vector< Account > > authorize
Definition mpt.h:174
Arguments for building an MPTokenIssuanceDestroy test transaction.
Definition mpt.h:227
std::optional< Account > issuer
Definition mpt.h:228
std::optional< MPTID > id
Definition mpt.h:229
Arguments for building a ConfidentialMPTHolderKeyUpdate test transaction.
Definition mpt.h:415
std::optional< Account > account
Definition mpt.h:416
std::optional< Buffer > inboxCiphertext
Definition mpt.h:420
std::optional< Buffer > proof
Definition mpt.h:421
std::optional< MPTID > id
Definition mpt.h:417
std::optional< Buffer > spendingCiphertext
Definition mpt.h:419
std::optional< Buffer > holderPubKey
Definition mpt.h:418
Full constructor arguments for MPTTester initialization.
Definition mpt.h:207
std::uint16_t transferFee
Definition mpt.h:212
std::uint32_t flags
Definition mpt.h:214
std::optional< std::uint64_t > maxAmt
Definition mpt.h:219
std::optional< std::uint32_t > immutableFlags
Definition mpt.h:215
std::optional< std::uint64_t > pay
Definition mpt.h:213
Arguments for initializing funded MPT test accounts and issuance.
Definition mpt.h:189
std::optional< Account > auditor
Definition mpt.h:193
Arguments for building a ConfidentialMPTMergeInbox test transaction.
Definition mpt.h:306
std::optional< MPTID > id
Definition mpt.h:308
std::optional< Account > account
Definition mpt.h:307
Arguments for building a ConfidentialMPTMirrorUpdate test transaction.
Definition mpt.h:397
std::optional< Buffer > auditorEncryptedAmount
Definition mpt.h:402
std::optional< Account > holder
Definition mpt.h:399
std::optional< Account > account
Definition mpt.h:398
std::optional< Buffer > zkProof
Definition mpt.h:403
std::optional< MPTID > id
Definition mpt.h:400
std::optional< Buffer > issuerEncryptedAmount
Definition mpt.h:401
Arguments for building an MPTokenIssuanceSet test transaction.
Definition mpt.h:254
std::optional< Account > account
Definition mpt.h:255
std::optional< Buffer > auditorPubKey
Definition mpt.h:267
std::optional< MPTID > id
Definition mpt.h:257
std::optional< std::uint32_t > immutableFlags
Definition mpt.h:261
std::optional< std::uint16_t > transferFee
Definition mpt.h:262
std::optional< std::uint32_t > flags
Definition mpt.h:260
std::optional< std::variant< Account, AccountID > > holder
Definition mpt.h:256
std::optional< std::string > metadata
Definition mpt.h:263
std::optional< Buffer > issuerPubKey
Definition mpt.h:266
std::optional< UInt256 > domainID
Definition mpt.h:265
std::optional< Account > delegate
Definition mpt.h:264
Stores the parameters that are exclusively used to generate a Pedersen linkage proof.
Definition mpt.h:434
Buffer const pedersenCommitment
The Pedersen commitment used by the proof.
Definition mpt.h:438
Represents an XRP, IOU, or MPT quantity This customizes the string conversion and supports XRP conver...
Wrapper that captures std::source_location when implicitly constructed.
Definition Env.h:69
T to_string(T... args)
T value_or(T... args)
T visit(T... args)