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