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/protocol/AccountID.h>
8#include <xrpl/protocol/Protocol.h>
9#include <xrpl/protocol/SField.h>
10#include <xrpl/protocol/STObject.h>
11#include <xrpl/protocol/TER.h>
12#include <xrpl/protocol/UintTypes.h>
13
14#include <openssl/rand.h>
15#include <utility/mpt_utility.h>
16
17#include <mpt_protocol.h>
18#include <secp256k1.h>
19#include <secp256k1_mpt.h>
20
21#include <cstddef>
22#include <cstdint>
23#include <cstring>
24#include <optional>
25#include <stdexcept>
26#include <vector>
27
28namespace xrpl {
29namespace {
30
31account_id
32toAccountId(AccountID const& account)
33{
34 account_id res;
35 std::memcpy(res.bytes, account.data(), kMPT_ACCOUNT_ID_SIZE);
36 return res;
37}
38
39mpt_issuance_id
40toIssuanceId(uint192 const& issuance)
41{
42 mpt_issuance_id res;
43 std::memcpy(res.bytes, issuance.data(), kMPT_ISSUANCE_ID_SIZE);
44 return res;
45}
46
53mpt_confidential_participant
54toParticipant(ConfidentialRecipient const& r)
55{
56 mpt_confidential_participant p{};
57 std::memcpy(p.pubkey, r.publicKey.data(), kEcPubKeyLength);
58 std::memcpy(p.ciphertext, r.encryptedAmount.data(), kEcGamalEncryptedTotalLength);
59 return p;
60}
61
62} // namespace
63
66 AccountID const& account,
67 uint192 const& issuanceID,
68 std::uint32_t sequence,
69 AccountID const& destination,
70 std::uint32_t version)
71{
72 uint256 result;
73 mpt_get_send_context_hash(
74 toAccountId(account),
75 toIssuanceId(issuanceID),
76 sequence,
77 toAccountId(destination),
78 version,
79 result.data());
80 return result;
81}
82
85 AccountID const& account,
86 uint192 const& issuanceID,
87 std::uint32_t sequence,
88 AccountID const& holder)
89{
90 uint256 result;
91 mpt_get_clawback_context_hash(
92 toAccountId(account),
93 toIssuanceId(issuanceID),
94 sequence,
95 toAccountId(holder),
96 result.data());
97 return result;
98}
99
101getConvertContextHash(AccountID const& account, uint192 const& issuanceID, std::uint32_t sequence)
102{
103 uint256 result;
104 mpt_get_convert_context_hash(
105 toAccountId(account), toIssuanceId(issuanceID), sequence, result.data());
106 return result;
107}
108
111 AccountID const& account,
112 uint192 const& issuanceID,
113 std::uint32_t sequence,
114 std::uint32_t version)
115{
116 uint256 result;
117 mpt_get_convert_back_context_hash(
118 toAccountId(account), toIssuanceId(issuanceID), sequence, version, result.data());
119 return result;
120}
121
123makeEcPair(Slice const& buffer)
124{
125 if (buffer.length() != 2 * kEcCiphertextComponentLength)
126 return std::nullopt; // LCOV_EXCL_LINE
127
128 auto parsePubKey = [](Slice const& slice, secp256k1_pubkey& out) {
129 return secp256k1_ec_pubkey_parse(secp256k1Context(), &out, slice.data(), slice.length());
130 };
131
132 Slice const s1{buffer.data(), kEcCiphertextComponentLength};
134
135 EcPair pair{};
136 if (parsePubKey(s1, pair.c1) != 1 || parsePubKey(s2, pair.c2) != 1)
137 return std::nullopt;
138
139 return pair;
140}
141
144{
145 auto serializePubKey = [](secp256k1_pubkey const& pub, unsigned char* out) {
146 size_t outLen = kEcCiphertextComponentLength; // 33 bytes
147 auto const ret = secp256k1_ec_pubkey_serialize(
148 secp256k1Context(), out, &outLen, &pub, SECP256K1_EC_COMPRESSED);
149 return ret == 1 && outLen == kEcCiphertextComponentLength;
150 };
151
153 auto const ptr = buffer.data();
154 bool const res1 = serializePubKey(pair.c1, ptr);
155 bool const res2 = serializePubKey(pair.c2, ptr + kEcCiphertextComponentLength);
156
157 if (!res1 || !res2)
158 return std::nullopt;
159
160 return buffer;
161}
162
163bool
165{
166 return makeEcPair(buffer).has_value();
167}
168
169bool
171{
172 if (buffer.size() != kCompressedEcPointLength)
173 return false;
174
175 // Compressed EC points must start with 0x02 or 0x03
176 if (buffer[0] != kEcCompressedPrefixEvenY && buffer[0] != kEcCompressedPrefixOddY)
177 return false;
178
179 secp256k1_pubkey point;
180 return secp256k1_ec_pubkey_parse(secp256k1Context(), &point, buffer.data(), buffer.size()) == 1;
181}
182
184homomorphicAdd(Slice const& a, Slice const& b)
185{
187 return std::nullopt;
188
189 auto const pairA = makeEcPair(a);
190 auto const pairB = makeEcPair(b);
191
192 if (!pairA || !pairB)
193 return std::nullopt;
194
195 EcPair sum{};
196 if (auto res = secp256k1_elgamal_add(
197 secp256k1Context(), &sum.c1, &sum.c2, &pairA->c1, &pairA->c2, &pairB->c1, &pairB->c2);
198 res != 1)
199 {
200 return std::nullopt;
201 }
202
203 return serializeEcPair(sum);
204}
205
207homomorphicSubtract(Slice const& a, Slice const& b)
208{
210 return std::nullopt;
211
212 auto const pairA = makeEcPair(a);
213 auto const pairB = makeEcPair(b);
214
215 if (!pairA || !pairB)
216 return std::nullopt;
217
218 EcPair diff{};
219 if (auto const res = secp256k1_elgamal_subtract(
220 secp256k1Context(), &diff.c1, &diff.c2, &pairA->c1, &pairA->c2, &pairB->c1, &pairB->c2);
221 res != 1)
222 {
223 return std::nullopt;
224 }
225
226 return serializeEcPair(diff);
227}
228
230rerandomizeCiphertext(Slice const& ciphertext, Slice const& pubKeySlice, Slice const& randomness)
231{
232 auto zero = encryptAmount(0, pubKeySlice, randomness);
233 if (!zero)
234 return std::nullopt;
235
236 return homomorphicAdd(ciphertext, *zero);
237}
238
239Buffer
241{
242 unsigned char blindingFactor[kEcBlindingFactorLength];
243
244 // todo: might need to be updated using another RNG
245 if (RAND_bytes(blindingFactor, kEcBlindingFactorLength) != 1)
246 Throw<std::runtime_error>("Failed to generate random number");
247
248 return Buffer(blindingFactor, kEcBlindingFactorLength);
249}
250
252encryptAmount(uint64_t const amt, Slice const& pubKeySlice, Slice const& blindingFactor)
253{
254 if (blindingFactor.size() != kEcBlindingFactorLength || pubKeySlice.size() != kEcPubKeyLength)
255 return std::nullopt;
256
258 if (mpt_encrypt_amount(amt, pubKeySlice.data(), blindingFactor.data(), out.data()) != 0)
259 return std::nullopt;
260
261 return out;
262}
263
265encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, MPTID const& mptId)
266{
267 if (pubKeySlice.size() != kEcPubKeyLength)
268 return std::nullopt; // LCOV_EXCL_LINE
269
270 EcPair pair{};
271 secp256k1_pubkey pubKey;
272 if (auto res = secp256k1_ec_pubkey_parse(
273 secp256k1Context(), &pubKey, pubKeySlice.data(), kEcPubKeyLength);
274 res != 1)
275 {
276 return std::nullopt; // LCOV_EXCL_LINE
277 }
278
279 if (auto res = generate_canonical_encrypted_zero(
280 secp256k1Context(), &pair.c1, &pair.c2, &pubKey, account.data(), mptId.data());
281 res != 1)
282 {
283 return std::nullopt; // LCOV_EXCL_LINE
284 }
285
286 return serializeEcPair(pair);
287}
288
289TER
291 uint64_t const amount,
292 Slice const& blindingFactor,
293 ConfidentialRecipient const& holder,
294 ConfidentialRecipient const& issuer,
296{
297 if (blindingFactor.size() != kEcBlindingFactorLength ||
298 holder.publicKey.size() != kEcPubKeyLength ||
300 issuer.publicKey.size() != kEcPubKeyLength ||
302 {
303 return tecINTERNAL; // LCOV_EXCL_LINE
304 }
305
306 auto const holderP = toParticipant(holder);
307 auto const issuerP = toParticipant(issuer);
308 mpt_confidential_participant auditorP{};
309 mpt_confidential_participant const* auditorPtr = nullptr;
310 if (auditor)
311 {
312 if (auditor->publicKey.size() != kEcPubKeyLength ||
313 auditor->encryptedAmount.size() != kEcGamalEncryptedTotalLength)
314 {
315 return tecINTERNAL; // LCOV_EXCL_LINE
316 }
317 auditorP = toParticipant(*auditor);
318 auditorPtr = &auditorP;
319 }
320
321 if (mpt_verify_revealed_amount(amount, blindingFactor.data(), &holderP, &issuerP, auditorPtr) !=
322 0)
323 {
324 return tecBAD_PROOF;
325 }
326
327 return tesSUCCESS;
328}
329
330NotTEC
332{
333 // Current usage of this function is only for ConfidentialMPTConvert and
334 // ConfidentialMPTConvertBack transactions, which already enforce that these fields
335 // are present.
336 if (!object.isFieldPresent(sfHolderEncryptedAmount) ||
337 !object.isFieldPresent(sfIssuerEncryptedAmount))
338 {
339 return temMALFORMED; // LCOV_EXCL_LINE
340 }
341
342 if (object[sfHolderEncryptedAmount].length() != kEcGamalEncryptedTotalLength ||
343 object[sfIssuerEncryptedAmount].length() != kEcGamalEncryptedTotalLength)
344 {
345 return temBAD_CIPHERTEXT;
346 }
347
348 bool const hasAuditor = object.isFieldPresent(sfAuditorEncryptedAmount);
349 if (hasAuditor && object[sfAuditorEncryptedAmount].length() != kEcGamalEncryptedTotalLength)
350 return temBAD_CIPHERTEXT;
351
352 if (!isValidCiphertext(object[sfHolderEncryptedAmount]) ||
353 !isValidCiphertext(object[sfIssuerEncryptedAmount]))
354 {
355 return temBAD_CIPHERTEXT;
356 }
357
358 if (hasAuditor && !isValidCiphertext(object[sfAuditorEncryptedAmount]))
359 return temBAD_CIPHERTEXT;
360
361 return tesSUCCESS;
362}
363
364TER
365verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 const& contextHash)
366{
367 if (proofSlice.size() != kEcSchnorrProofLength || pubKeySlice.size() != kEcPubKeyLength)
368 return tecINTERNAL; // LCOV_EXCL_LINE
369
370 if (mpt_verify_convert_proof(proofSlice.data(), pubKeySlice.data(), contextHash.data()) != 0)
371 return tecBAD_PROOF;
372
373 return tesSUCCESS;
374}
375
376TER
378 uint64_t const amount,
379 Slice const& proof,
380 Slice const& pubKeySlice,
381 Slice const& ciphertext,
382 uint256 const& contextHash)
383{
384 if (ciphertext.size() != kEcGamalEncryptedTotalLength ||
385 pubKeySlice.size() != kEcPubKeyLength || proof.size() != kEcClawbackProofLength)
386 {
387 return tecINTERNAL; // LCOV_EXCL_LINE
388 }
389
390 if (mpt_verify_clawback_proof(
391 proof.data(), amount, pubKeySlice.data(), ciphertext.data(), contextHash.data()) != 0)
392 {
393 return tecBAD_PROOF;
394 }
395
396 return tesSUCCESS;
397}
398
399TER
401 Slice const& proof,
402 ConfidentialRecipient const& sender,
403 ConfidentialRecipient const& destination,
404 ConfidentialRecipient const& issuer,
406 Slice const& spendingBalance,
407 Slice const& amountCommitment,
408 Slice const& balanceCommitment,
409 uint256 const& contextHash)
410{
411 auto const recipientCount = getConfidentialRecipientCount(auditor.has_value());
412 if (proof.size() != kEcSendProofLength || sender.publicKey.size() != kEcPubKeyLength ||
414 destination.publicKey.size() != kEcPubKeyLength ||
416 issuer.publicKey.size() != kEcPubKeyLength ||
418 spendingBalance.size() != kEcGamalEncryptedTotalLength ||
419 amountCommitment.size() != kEcPedersenCommitmentLength ||
420 balanceCommitment.size() != kEcPedersenCommitmentLength)
421 {
422 return tecINTERNAL; // LCOV_EXCL_LINE
423 }
424
426 participants.reserve(recipientCount);
427 participants.push_back(toParticipant(sender));
428 participants.push_back(toParticipant(destination));
429 participants.push_back(toParticipant(issuer));
430 if (auditor)
431 {
432 if (auditor->publicKey.size() != kEcPubKeyLength ||
433 auditor->encryptedAmount.size() != kEcGamalEncryptedTotalLength)
434 {
435 return tecINTERNAL; // LCOV_EXCL_LINE
436 }
437 participants.push_back(toParticipant(*auditor));
438 }
439 if (participants.size() != recipientCount)
440 return tecINTERNAL; // LCOV_EXCL_LINE
441
442 if (mpt_verify_send_proof(
443 proof.data(),
444 participants.data(),
445 recipientCount,
446 spendingBalance.data(),
447 amountCommitment.data(),
448 balanceCommitment.data(),
449 contextHash.data()) != 0)
450 {
451 return tecBAD_PROOF;
452 }
453
454 return tesSUCCESS;
455}
456
457TER
459 Slice const& proof,
460 Slice const& pubKeySlice,
461 Slice const& spendingBalance,
462 Slice const& balanceCommitment,
463 uint64_t amount,
464 uint256 const& contextHash)
465{
466 if (proof.size() != kEcConvertBackProofLength || pubKeySlice.size() != kEcPubKeyLength ||
467 spendingBalance.size() != kEcGamalEncryptedTotalLength ||
468 balanceCommitment.size() != kEcPedersenCommitmentLength)
469 {
470 return tecINTERNAL; // LCOV_EXCL_LINE
471 }
472
473 if (mpt_verify_convert_back_proof(
474 proof.data(),
475 pubKeySlice.data(),
476 spendingBalance.data(),
477 balanceCommitment.data(),
478 amount,
479 contextHash.data()) != 0)
480 {
481 return tecBAD_PROOF;
482 }
483
484 return tesSUCCESS;
485}
486
487} // namespace xrpl
pointer data()
Definition base_uint.h:106
Like std::vector<char> but better.
Definition Buffer.h:16
std::uint8_t const * data() const noexcept
Return a pointer to beginning of the storage.
Definition Buffer.h:129
An immutable linear range of bytes.
Definition Slice.h:26
std::size_t length() const noexcept
Definition Slice.h:67
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
T data(T... args)
T memcpy(T... args)
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
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:327
constexpr std::uint8_t kEcCompressedPrefixEvenY
Compressed EC point prefix for even y-coordinate.
Definition Protocol.h:367
BaseUInt< 192 > uint192
Definition base_uint.h:563
NotTEC checkEncryptedAmountFormat(STObject const &object)
Validates the format of encrypted amount fields in a transaction.
static auto sum(TCollection const &col)
Definition BookStep.cpp:993
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:333
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:318
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.
constexpr std::uint8_t kEcCompressedPrefixOddY
Compressed EC point prefix for odd y-coordinate.
Definition Protocol.h:370
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:336
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:324
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:357
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:594
constexpr std::size_t kEcPedersenCommitmentLength
Length of Pedersen Commitment (compressed).
Definition Protocol.h:339
constexpr std::size_t kEcCiphertextComponentLength
Length of one compressed EC point component in an EC ElGamal ciphertext.
Definition Protocol.h:321
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: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).
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
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:351
@ temBAD_CIPHERTEXT
Definition TER.h:131
@ temMALFORMED
Definition TER.h:73
TERSubset< CanCvtToTER > TER
Definition TER.h:634
@ tecINTERNAL
Definition TER.h:308
@ tecBAD_PROOF
Definition TER.h:366
BaseUInt< 256 > uint256
Definition base_uint.h:562
std::optional< Buffer > homomorphicAdd(Slice const &a, Slice const &b)
Homomorphically adds two ElGamal ciphertexts.
@ tesSUCCESS
Definition TER.h:240
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:49
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.