xrpld
Loading...
Searching...
No Matches
ConfidentialTransfer.cpp
1#include <xrpl/protocol/ConfidentialTransfer.h>
2
3#include <xrpl/basics/Buffer.h>
4#include <xrpl/basics/Slice.h>
5#include <xrpl/basics/base_uint.h>
6#include <xrpl/basics/contract.h>
7#include <xrpl/beast/utility/instrumentation.h>
8#include <xrpl/protocol/AccountID.h>
9#include <xrpl/protocol/Protocol.h>
10#include <xrpl/protocol/SField.h>
11#include <xrpl/protocol/STBlob.h>
12#include <xrpl/protocol/STObject.h>
13#include <xrpl/protocol/TER.h>
14#include <xrpl/protocol/UintTypes.h>
15
16#include <openssl/rand.h>
17#include <utility/mpt_utility.h>
18
19#include <mpt_protocol.h>
20#include <secp256k1.h>
21#include <secp256k1_mpt.h>
22
23#include <cstddef>
24#include <cstdint>
25#include <cstring>
26#include <optional>
27#include <stdexcept>
28#include <vector>
29
30namespace xrpl {
31namespace {
32
33account_id
34toAccountId(AccountID const& account)
35{
36 account_id res;
37 std::memcpy(res.bytes, account.data(), kMPT_ACCOUNT_ID_SIZE);
38 return res;
39}
40
41mpt_issuance_id
42toIssuanceId(uint192 const& issuance)
43{
44 mpt_issuance_id res;
45 std::memcpy(res.bytes, issuance.data(), kMPT_ISSUANCE_ID_SIZE);
46 return res;
47}
48
55mpt_confidential_participant
56toParticipant(ConfidentialRecipient const& r)
57{
58 mpt_confidential_participant p{};
59 std::memcpy(p.pubkey, r.publicKey.data(), kEcPubKeyLength);
60 std::memcpy(p.ciphertext, r.encryptedAmount.data(), kEcGamalEncryptedTotalLength);
61 return p;
62}
63
64} // namespace
65
68 AccountID const& account,
69 uint192 const& issuanceID,
70 std::uint32_t sequence,
71 AccountID const& destination,
72 std::uint32_t version)
73{
74 uint256 result;
75 mpt_get_send_context_hash(
76 toAccountId(account),
77 toIssuanceId(issuanceID),
78 sequence,
79 toAccountId(destination),
80 version,
81 result.data());
82 return result;
83}
84
87 AccountID const& account,
88 uint192 const& issuanceID,
89 std::uint32_t sequence,
90 AccountID const& holder)
91{
92 uint256 result;
93 mpt_get_clawback_context_hash(
94 toAccountId(account),
95 toIssuanceId(issuanceID),
96 sequence,
97 toAccountId(holder),
98 result.data());
99 return result;
100}
101
103getConvertContextHash(AccountID const& account, uint192 const& issuanceID, std::uint32_t sequence)
104{
105 uint256 result;
106 mpt_get_convert_context_hash(
107 toAccountId(account), toIssuanceId(issuanceID), sequence, result.data());
108 return result;
109}
110
113 AccountID const& account,
114 uint192 const& issuanceID,
115 std::uint32_t sequence,
116 std::uint32_t version)
117{
118 uint256 result;
119 mpt_get_convert_back_context_hash(
120 toAccountId(account), toIssuanceId(issuanceID), sequence, version, result.data());
121 return result;
122}
123
125makeEcPair(Slice const& buffer)
126{
127 if (buffer.length() != 2 * kEcCiphertextComponentLength)
128 {
129 // LCOV_EXCL_START
130 UNREACHABLE("xrpl::makeEcPair : callers must pre-validate ciphertext length");
131 return std::nullopt;
132 // LCOV_EXCL_STOP
133 }
134
135 auto parsePubKey = [](Slice const& slice, secp256k1_pubkey& out) {
136 return secp256k1_ec_pubkey_parse(secp256k1Context(), &out, slice.data(), slice.length());
137 };
138
139 Slice const s1{buffer.data(), kEcCiphertextComponentLength};
141
142 EcPair pair{};
143 if (parsePubKey(s1, pair.c1) != 1 || parsePubKey(s2, pair.c2) != 1)
144 return std::nullopt;
145
146 return pair;
147}
148
151{
152 auto serializePubKey = [](secp256k1_pubkey const& pub, unsigned char* out) {
153 size_t outLen = kEcCiphertextComponentLength; // 33 bytes
154 auto const ret = secp256k1_ec_pubkey_serialize(
155 secp256k1Context(), out, &outLen, &pub, SECP256K1_EC_COMPRESSED);
156 return ret == 1 && outLen == kEcCiphertextComponentLength;
157 };
158
160 auto const ptr = buffer.data();
161 bool const res1 = serializePubKey(pair.c1, ptr);
162 bool const res2 = serializePubKey(pair.c2, ptr + kEcCiphertextComponentLength);
163
164 if (!res1 || !res2)
165 return std::nullopt;
166
167 return buffer;
168}
169
170bool
172{
173 return makeEcPair(buffer).has_value();
174}
175
176bool
178{
179 if (buffer.size() != kCompressedEcPointLength)
180 return false;
181
182 // Compressed EC points must start with 0x02 or 0x03
183 if (buffer[0] != kEcCompressedPrefixEvenY && buffer[0] != kEcCompressedPrefixOddY)
184 return false;
185
186 secp256k1_pubkey point;
187 return secp256k1_ec_pubkey_parse(secp256k1Context(), &point, buffer.data(), buffer.size()) == 1;
188}
189
191homomorphicAdd(Slice const& a, Slice const& b)
192{
194 return std::nullopt;
195
196 auto const pairA = makeEcPair(a);
197 auto const pairB = makeEcPair(b);
198
199 if (!pairA || !pairB)
200 return std::nullopt;
201
202 EcPair sum{};
203 if (auto res = secp256k1_elgamal_add(
204 secp256k1Context(), &sum.c1, &sum.c2, &pairA->c1, &pairA->c2, &pairB->c1, &pairB->c2);
205 res != 1)
206 {
207 return std::nullopt;
208 }
209
210 return serializeEcPair(sum);
211}
212
214homomorphicSubtract(Slice const& a, Slice const& b)
215{
217 return std::nullopt;
218
219 auto const pairA = makeEcPair(a);
220 auto const pairB = makeEcPair(b);
221
222 if (!pairA || !pairB)
223 return std::nullopt;
224
225 EcPair diff{};
226 if (auto const res = secp256k1_elgamal_subtract(
227 secp256k1Context(), &diff.c1, &diff.c2, &pairA->c1, &pairA->c2, &pairB->c1, &pairB->c2);
228 res != 1)
229 {
230 return std::nullopt;
231 }
232
233 return serializeEcPair(diff);
234}
235
237rerandomizeCiphertext(Slice const& ciphertext, Slice const& pubKeySlice, Slice const& randomness)
238{
239 auto zero = encryptAmount(0, pubKeySlice, randomness);
240 if (!zero)
241 return std::nullopt;
242
243 return homomorphicAdd(ciphertext, *zero);
244}
245
246Buffer
248{
249 unsigned char blindingFactor[kEcBlindingFactorLength];
250
251 // todo: might need to be updated using another RNG
252 if (RAND_bytes(blindingFactor, kEcBlindingFactorLength) != 1)
253 Throw<std::runtime_error>("Failed to generate random number");
254
255 return Buffer(blindingFactor, kEcBlindingFactorLength);
256}
257
259encryptAmount(uint64_t const amt, Slice const& pubKeySlice, Slice const& blindingFactor)
260{
261 if (blindingFactor.size() != kEcBlindingFactorLength || pubKeySlice.size() != kEcPubKeyLength)
262 return std::nullopt;
263
265 if (mpt_encrypt_amount(amt, pubKeySlice.data(), blindingFactor.data(), out.data()) != 0)
266 return std::nullopt;
267
268 return out;
269}
270
272encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, MPTID const& mptId)
273{
274 if (pubKeySlice.size() != kEcPubKeyLength)
275 {
276 // LCOV_EXCL_START
277 UNREACHABLE(
278 "xrpl::encryptCanonicalZeroAmount : callers must pre-validate public key length");
279 return std::nullopt;
280 // LCOV_EXCL_STOP
281 }
282
283 EcPair pair{};
284 secp256k1_pubkey pubKey;
285 if (auto res = secp256k1_ec_pubkey_parse(
286 secp256k1Context(), &pubKey, pubKeySlice.data(), kEcPubKeyLength);
287 res != 1)
288 {
289 // LCOV_EXCL_START
290 UNREACHABLE(
291 "xrpl::encryptCanonicalZeroAmount : public key read from the ledger must already be "
292 "valid");
293 return std::nullopt;
294 // LCOV_EXCL_STOP
295 }
296
297 if (auto res = generate_canonical_encrypted_zero(
298 secp256k1Context(), &pair.c1, &pair.c2, &pubKey, account.data(), mptId.data());
299 res != 1)
300 {
301 // LCOV_EXCL_START
302 UNREACHABLE(
303 "xrpl::encryptCanonicalZeroAmount : canonical zero generation cannot fail for a "
304 "valid public key");
305 return std::nullopt;
306 // LCOV_EXCL_STOP
307 }
308
309 return serializeEcPair(pair);
310}
311
312TER
314 uint64_t const amount,
315 Slice const& blindingFactor,
316 ConfidentialRecipient const& holder,
317 ConfidentialRecipient const& issuer,
319{
320 if (blindingFactor.size() != kEcBlindingFactorLength ||
321 holder.publicKey.size() != kEcPubKeyLength ||
323 issuer.publicKey.size() != kEcPubKeyLength ||
325 {
326 // LCOV_EXCL_START
327 UNREACHABLE(
328 "xrpl::verifyRevealedAmount : callers must pre-validate holder/issuer field lengths");
329 return tecINTERNAL;
330 // LCOV_EXCL_STOP
331 }
332
333 auto const holderP = toParticipant(holder);
334 auto const issuerP = toParticipant(issuer);
335 mpt_confidential_participant auditorP{};
336 mpt_confidential_participant const* auditorPtr = nullptr;
337 if (auditor)
338 {
339 if (auditor->publicKey.size() != kEcPubKeyLength ||
340 auditor->encryptedAmount.size() != kEcGamalEncryptedTotalLength)
341 {
342 // LCOV_EXCL_START
343 UNREACHABLE(
344 "xrpl::verifyRevealedAmount : callers must pre-validate auditor field lengths");
345 return tecINTERNAL;
346 // LCOV_EXCL_STOP
347 }
348 auditorP = toParticipant(*auditor);
349 auditorPtr = &auditorP;
350 }
351
352 if (mpt_verify_revealed_amount(amount, blindingFactor.data(), &holderP, &issuerP, auditorPtr) !=
353 0)
354 {
355 return tecBAD_PROOF;
356 }
357
358 return tesSUCCESS;
359}
360
361NotTEC
363{
364 // Current usage of this function is only for ConfidentialMPTConvert and
365 // ConfidentialMPTConvertBack transactions, which already enforce that these fields
366 // are present.
367 if (!object.isFieldPresent(sfHolderEncryptedAmount) ||
368 !object.isFieldPresent(sfIssuerEncryptedAmount))
369 {
370 // LCOV_EXCL_START
371 UNREACHABLE(
372 "xrpl::checkEncryptedAmountFormat : callers already enforce that these fields are "
373 "present");
374 return temMALFORMED;
375 // LCOV_EXCL_STOP
376 }
377
378 if (object[sfHolderEncryptedAmount].length() != kEcGamalEncryptedTotalLength ||
379 object[sfIssuerEncryptedAmount].length() != kEcGamalEncryptedTotalLength)
380 {
381 return temBAD_CIPHERTEXT;
382 }
383
384 bool const hasAuditor = object.isFieldPresent(sfAuditorEncryptedAmount);
385 if (hasAuditor && object[sfAuditorEncryptedAmount].length() != kEcGamalEncryptedTotalLength)
386 return temBAD_CIPHERTEXT;
387
388 if (!isValidCiphertext(object[sfHolderEncryptedAmount]) ||
389 !isValidCiphertext(object[sfIssuerEncryptedAmount]))
390 {
391 return temBAD_CIPHERTEXT;
392 }
393
394 if (hasAuditor && !isValidCiphertext(object[sfAuditorEncryptedAmount]))
395 return temBAD_CIPHERTEXT;
396
397 return tesSUCCESS;
398}
399
400TER
401verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 const& contextHash)
402{
403 if (proofSlice.size() != kEcSchnorrProofLength || pubKeySlice.size() != kEcPubKeyLength)
404 {
405 // LCOV_EXCL_START
406 UNREACHABLE("xrpl::verifySchnorrProof : callers must pre-validate proof/public key length");
407 return tecINTERNAL;
408 // LCOV_EXCL_STOP
409 }
410
411 if (mpt_verify_convert_proof(proofSlice.data(), pubKeySlice.data(), contextHash.data()) != 0)
412 return tecBAD_PROOF;
413
414 return tesSUCCESS;
415}
416
417TER
419 uint64_t const amount,
420 Slice const& proof,
421 Slice const& pubKeySlice,
422 Slice const& ciphertext,
423 uint256 const& contextHash)
424{
425 if (ciphertext.size() != kEcGamalEncryptedTotalLength ||
426 pubKeySlice.size() != kEcPubKeyLength || proof.size() != kEcClawbackProofLength)
427 {
428 // LCOV_EXCL_START
429 UNREACHABLE(
430 "xrpl::verifyClawbackProof : callers must pre-validate ciphertext/public "
431 "key/proof length");
432 return tecINTERNAL;
433 // LCOV_EXCL_STOP
434 }
435
436 if (mpt_verify_clawback_proof(
437 proof.data(), amount, pubKeySlice.data(), ciphertext.data(), contextHash.data()) != 0)
438 {
439 return tecBAD_PROOF;
440 }
441
442 return tesSUCCESS;
443}
444
445TER
447 Slice const& proof,
448 ConfidentialRecipient const& sender,
449 ConfidentialRecipient const& destination,
450 ConfidentialRecipient const& issuer,
452 Slice const& spendingBalance,
453 Slice const& amountCommitment,
454 Slice const& balanceCommitment,
455 uint256 const& contextHash)
456{
457 auto const recipientCount = getConfidentialRecipientCount(auditor.has_value());
458 if (proof.size() != kEcSendProofLength || sender.publicKey.size() != kEcPubKeyLength ||
460 destination.publicKey.size() != kEcPubKeyLength ||
462 issuer.publicKey.size() != kEcPubKeyLength ||
464 spendingBalance.size() != kEcGamalEncryptedTotalLength ||
465 amountCommitment.size() != kEcPedersenCommitmentLength ||
466 balanceCommitment.size() != kEcPedersenCommitmentLength)
467 {
468 // LCOV_EXCL_START
469 UNREACHABLE(
470 "xrpl::verifySendProof : callers must pre-validate proof/participant/commitment "
471 "lengths");
472 return tecINTERNAL;
473 // LCOV_EXCL_STOP
474 }
475
477 participants.reserve(recipientCount);
478 participants.push_back(toParticipant(sender));
479 participants.push_back(toParticipant(destination));
480 participants.push_back(toParticipant(issuer));
481 if (auditor)
482 {
483 if (auditor->publicKey.size() != kEcPubKeyLength ||
484 auditor->encryptedAmount.size() != kEcGamalEncryptedTotalLength)
485 {
486 // LCOV_EXCL_START
487 UNREACHABLE("xrpl::verifySendProof : callers must pre-validate auditor field lengths");
488 return tecINTERNAL;
489 // LCOV_EXCL_STOP
490 }
491 participants.push_back(toParticipant(*auditor));
492 }
493 if (participants.size() != recipientCount)
494 {
495 // LCOV_EXCL_START
496 UNREACHABLE(
497 "xrpl::verifySendProof : participant count must match the requested recipient "
498 "count");
499 return tecINTERNAL;
500 // LCOV_EXCL_STOP
501 }
502
503 if (mpt_verify_send_proof(
504 proof.data(),
505 participants.data(),
506 recipientCount,
507 spendingBalance.data(),
508 amountCommitment.data(),
509 balanceCommitment.data(),
510 contextHash.data()) != 0)
511 {
512 return tecBAD_PROOF;
513 }
514
515 return tesSUCCESS;
516}
517
518TER
520 Slice const& proof,
521 Slice const& pubKeySlice,
522 Slice const& spendingBalance,
523 Slice const& balanceCommitment,
524 uint64_t amount,
525 uint256 const& contextHash)
526{
527 if (proof.size() != kEcConvertBackProofLength || pubKeySlice.size() != kEcPubKeyLength ||
528 spendingBalance.size() != kEcGamalEncryptedTotalLength ||
529 balanceCommitment.size() != kEcPedersenCommitmentLength)
530 {
531 // LCOV_EXCL_START
532 UNREACHABLE(
533 "xrpl::verifyConvertBackProof : callers must pre-validate proof/public "
534 "key/balance/commitment lengths");
535 return tecINTERNAL;
536 // LCOV_EXCL_STOP
537 }
538
539 if (mpt_verify_convert_back_proof(
540 proof.data(),
541 pubKeySlice.data(),
542 spendingBalance.data(),
543 balanceCommitment.data(),
544 amount,
545 contextHash.data()) != 0)
546 {
547 return tecBAD_PROOF;
548 }
549
550 return tesSUCCESS;
551}
552
553} // namespace xrpl
pointer data()
Definition base_uint.h:117
Like std::vector<char> but better.
Definition Buffer.h:19
std::uint8_t const * data() const noexcept
Return a pointer to beginning of the storage.
Definition Buffer.h:148
An immutable linear range of bytes.
Definition Slice.h:28
std::size_t length() const noexcept
Definition Slice.h:76
std::uint8_t const * data() const noexcept
Return a pointer to beginning of the storage.
Definition Slice.h:88
std::size_t size() const noexcept
Returns the number of bytes in the storage.
Definition Slice.h:70
T data(T... args)
T memcpy(T... args)
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:198
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr std::size_t kEcPubKeyLength
Length of EC public key (compressed).
Definition Protocol.h:473
constexpr std::uint8_t kEcCompressedPrefixEvenY
Compressed EC point prefix for even y-coordinate.
Definition Protocol.h:539
BaseUInt< 192 > uint192
Definition base_uint.h:581
NotTEC checkEncryptedAmountFormat(STObject const &object)
Validates the format of encrypted amount fields in a transaction.
static auto sum(TCollection const &col)
std::optional< Buffer > rerandomizeCiphertext(Slice const &ciphertext, Slice const &pubKeySlice, Slice const &randomness)
Re-randomizes an ElGamal ciphertext without changing its plaintext.
TER verifySchnorrProof(Slice const &pubKeySlice, Slice const &proofSlice, uint256 const &contextHash)
Verifies a Schnorr proof of knowledge of an ElGamal private key.
constexpr std::size_t kEcBlindingFactorLength
Length of the EC blinding factor in bytes.
Definition Protocol.h:483
std::optional< Buffer > encryptCanonicalZeroAmount(Slice const &pubKeySlice, AccountID const &account, MPTID const &mptId)
Generates the canonical zero encryption for a specific MPToken.
constexpr std::size_t kCompressedEcPointLength
Length of EC point (compressed).
Definition Protocol.h:458
constexpr std::size_t kEcClawbackProofLength
Length of the ZKProof for ConfidentialMPTClawback.
Definition Protocol.h:529
std::optional< Buffer > encryptAmount(uint64_t const amt, Slice const &pubKeySlice, Slice const &blindingFactor)
Encrypts an amount using ElGamal encryption.
constexpr std::uint8_t kEcCompressedPrefixOddY
Compressed EC point prefix for odd y-coordinate.
Definition Protocol.h:544
bool isValidCompressedECPoint(Slice const &buffer)
Verifies that a buffer contains a valid, parsable compressed EC point.
constexpr std::size_t kEcSchnorrProofLength
Length of Schnorr ZKProof for public key registration (compact form) in bytes.
Definition Protocol.h:488
constexpr uint8_t getConfidentialRecipientCount(bool hasAuditor)
Returns the number of recipients in a confidential transfer.
std::optional< EcPair > makeEcPair(Slice const &buffer)
Parses an ElGamal ciphertext into two secp256k1 public key components.
constexpr std::size_t kEcGamalEncryptedTotalLength
EC ElGamal ciphertext length: two compressed EC points concatenated.
Definition Protocol.h:468
std::optional< Buffer > serializeEcPair(EcPair const &pair)
Serializes an EcPair into compressed form.
TER verifyRevealedAmount(uint64_t const amount, Slice const &blindingFactor, ConfidentialRecipient const &holder, ConfidentialRecipient const &issuer, std::optional< ConfidentialRecipient > const &auditor)
Verifies revealed amount encryptions for all recipients.
constexpr std::size_t kEcConvertBackProofLength
128 bytes compact sigma proof + 688 bytes single bulletproof.
Definition Protocol.h:523
uint256 getConvertBackContextHash(AccountID const &account, uint192 const &issuanceID, std::uint32_t sequence, std::uint32_t version)
Generates the context hash for ConfidentialMPTConvertBack transactions.
bool isValidCiphertext(Slice const &buffer)
Verifies that a buffer contains two valid, parsable EC public keys.
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:607
constexpr std::size_t kEcPedersenCommitmentLength
Length of Pedersen Commitment (compressed).
Definition Protocol.h:493
constexpr std::size_t kEcCiphertextComponentLength
Length of one compressed EC point component in an EC ElGamal ciphertext.
Definition Protocol.h:463
std::optional< Buffer > homomorphicSubtract(Slice const &a, Slice const &b)
Homomorphically subtracts two ElGamal ciphertexts.
BaseUInt< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
Definition UintTypes.h:54
uint256 getConvertContextHash(AccountID const &account, uint192 const &issuanceID, std::uint32_t sequence)
Generates the context hash for ConfidentialMPTConvert transactions.
secp256k1_context const * secp256k1Context()
Definition secp256k1.h:9
uint256 getClawbackContextHash(AccountID const &account, uint192 const &issuanceID, std::uint32_t sequence, AccountID const &holder)
Generates the context hash for ConfidentialMPTClawback transactions.
Buffer generateBlindingFactor()
Generates a cryptographically secure blinding factor (size=xrpl::kEcBlindingFactorLength).
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
TER verifyConvertBackProof(Slice const &proof, Slice const &pubKeySlice, Slice const &spendingBalance, Slice const &balanceCommitment, uint64_t amount, uint256 const &contextHash)
Verifies all zero-knowledge proofs for a ConfidentialMPTConvertBack transaction.
uint256 getSendContextHash(AccountID const &account, uint192 const &issuanceID, std::uint32_t sequence, AccountID const &destination, std::uint32_t version)
Generates the context hash for ConfidentialMPTSend transactions.
constexpr std::size_t kEcSendProofLength
192 bytes compact sigma proof + 754 bytes double bulletproof.
Definition Protocol.h:513
@ temBAD_CIPHERTEXT
Definition TER.h:133
@ temMALFORMED
Definition TER.h:75
TERSubset< CanCvtToTER > TER
Definition TER.h:647
@ tecINTERNAL
Definition TER.h:313
@ tecBAD_PROOF
Definition TER.h:371
BaseUInt< 256 > uint256
Definition base_uint.h:580
std::optional< Buffer > homomorphicAdd(Slice const &a, Slice const &b)
Homomorphically adds two ElGamal ciphertexts.
@ tesSUCCESS
Definition TER.h:245
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
TER verifySendProof(Slice const &proof, ConfidentialRecipient const &sender, ConfidentialRecipient const &destination, ConfidentialRecipient const &issuer, std::optional< ConfidentialRecipient > const &auditor, Slice const &spendingBalance, Slice const &amountCommitment, Slice const &balanceCommitment, uint256 const &contextHash)
Verifies all zero-knowledge proofs for a ConfidentialMPTSend transaction.
TER verifyClawbackProof(uint64_t const amount, Slice const &proof, Slice const &pubKeySlice, Slice const &ciphertext, uint256 const &contextHash)
Verifies a compact sigma clawback proof.
T has_value(T... args)
T push_back(T... args)
T reserve(T... args)
T size(T... args)
Bundles an ElGamal public key with its associated encrypted amount.
Slice encryptedAmount
The encrypted amount ciphertext (size=xrpl::kEcGamalEncryptedTotalLength).
Slice publicKey
The recipient's ElGamal public key (size=xrpl::kEcPubKeyLength).
Holds two secp256k1 public key components representing an ElGamal ciphertext (C1, C2).
secp256k1_pubkey c2
Second ElGamal ciphertext component.
secp256k1_pubkey c1
First ElGamal ciphertext component.