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