xrpld
Loading...
Searching...
No Matches
ConfidentialMPTConvertBack.cpp
1#include <xrpl/tx/transactors/token/ConfidentialMPTConvertBack.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/utility/Journal.h>
5#include <xrpl/beast/utility/instrumentation.h>
6#include <xrpl/core/ServiceRegistry.h>
7#include <xrpl/ledger/ReadView.h>
8#include <xrpl/ledger/helpers/TokenHelpers.h>
9#include <xrpl/protocol/ConfidentialTransfer.h>
10#include <xrpl/protocol/Indexes.h>
11#include <xrpl/protocol/LedgerFormats.h>
12#include <xrpl/protocol/Protocol.h>
13#include <xrpl/protocol/SField.h>
14#include <xrpl/protocol/STTx.h>
15#include <xrpl/protocol/TER.h>
16#include <xrpl/protocol/XRPAmount.h>
17#include <xrpl/tx/Transactor.h>
18
19#include <memory>
20#include <optional>
21#include <utility>
22
23namespace xrpl {
24
27{
28 // issuer cannot convert back
29 if (MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer() == ctx.tx[sfAccount])
30 return temMALFORMED;
31
32 if (ctx.tx[sfMPTAmount] == 0 || ctx.tx[sfMPTAmount] > kMaxMpTokenAmount)
33 return temBAD_AMOUNT;
34
35 if (!isValidCompressedECPoint(ctx.tx[sfBalanceCommitment]))
36 return temMALFORMED;
37
38 // check encrypted amount format after the above basic checks
39 // this check is more expensive so put it at the end
40 if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res))
41 return res;
42
43 // ConvertBack proof = compact sigma proof (128 bytes) + single bulletproof (688 bytes)
44 if (ctx.tx[sfZKProof].size() != kEcConvertBackProofLength)
45 return temMALFORMED;
46
47 return tesSUCCESS;
48}
49
55
69static TER
71 STTx const& tx,
72 std::shared_ptr<SLE const> const& issuance,
73 std::shared_ptr<SLE const> const& mptoken)
74{
75 if (!mptoken->isFieldPresent(sfHolderEncryptionKey))
76 {
77 // LCOV_EXCL_START
78 UNREACHABLE(
79 "xrpl::verifyProofs : preclaim already validated the holder encryption key is "
80 "present");
81 return tecINTERNAL;
82 // LCOV_EXCL_STOP
83 }
84
85 auto const mptIssuanceID = tx[sfMPTokenIssuanceID];
86 auto const account = tx[sfAccount];
87 auto const amount = tx[sfMPTAmount];
88 auto const blindingFactor = tx[sfBlindingFactor];
89 auto const holderPubKey = (*mptoken)[sfHolderEncryptionKey];
90
91 auto const contextHash = getConvertBackContextHash(
92 account,
93 mptIssuanceID,
94 tx.getSeqProxy().value(),
95 (*mptoken)[~sfConfidentialBalanceVersion].value_or(0));
96
97 // Prepare Auditor Info
99 bool const hasAuditor = issuance->isFieldPresent(sfAuditorEncryptionKey);
100 if (hasAuditor)
101 {
102 auditor.emplace(
104 .publicKey = (*issuance)[sfAuditorEncryptionKey],
105 .encryptedAmount = tx[sfAuditorEncryptedAmount],
106 });
107 }
108
109 // Run all verifications before returning any error to prevent timing attacks
110 // that could reveal which proof failed.
111 bool valid = true;
112
113 if (auto const ter = verifyRevealedAmount(
114 amount,
115 Slice(blindingFactor.data(), blindingFactor.size()),
116 {
117 .publicKey = holderPubKey,
118 .encryptedAmount = tx[sfHolderEncryptedAmount],
119 },
120 {
121 .publicKey = (*issuance)[sfIssuerEncryptionKey],
122 .encryptedAmount = tx[sfIssuerEncryptedAmount],
123 },
124 auditor);
125 !isTesSuccess(ter))
126 {
127 valid = false;
128 }
129
130 if (auto const ter = verifyConvertBackProof(
131 tx[sfZKProof],
132 holderPubKey,
133 (*mptoken)[sfConfidentialBalanceSpending],
134 tx[sfBalanceCommitment],
135 amount,
136 contextHash);
137 !isTesSuccess(ter))
138 {
139 valid = false;
140 }
141
142 if (!valid)
143 return tecBAD_PROOF;
144
145 return tesSUCCESS;
146}
147
148TER
150{
151 auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID];
152 auto const account = ctx.tx[sfAccount];
153 auto const amount = ctx.tx[sfMPTAmount];
154
155 // ensure that issuance exists
156 auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
157 if (!sleIssuance)
158 return tecOBJECT_NOT_FOUND;
159
160 if (!sleIssuance->isFlag(lsfMPTCanHoldConfidentialBalance) ||
161 !sleIssuance->isFieldPresent(sfIssuerEncryptionKey))
162 return tecNO_PERMISSION;
163
164 bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
165 bool const requiresAuditor = sleIssuance->isFieldPresent(sfAuditorEncryptionKey);
166
167 // tx must include auditor ciphertext if the issuance has enabled
168 // auditing
169 if (requiresAuditor && !hasAuditor)
170 return tecNO_PERMISSION;
171
172 // if auditing is not supported then user should not upload auditor
173 // ciphertext
174 if (!requiresAuditor && hasAuditor)
175 return tecNO_PERMISSION;
176
177 // already checked in preflight, but should also check that issuer on
178 // the issuance isn't the account either
179 if (sleIssuance->getAccountID(sfIssuer) == account)
180 {
181 // LCOV_EXCL_START
182 UNREACHABLE(
183 "xrpl::ConfidentialMPTConvertBack::preclaim : issuer derived from the MPT ID must "
184 "match the ledger's stored issuer");
185 return tefINTERNAL;
186 // LCOV_EXCL_STOP
187 }
188
189 auto const sleMptoken = ctx.view.read(keylet::mptoken(mptIssuanceID, account));
190 if (!sleMptoken)
191 return tecOBJECT_NOT_FOUND;
192
193 if (!sleMptoken->isFieldPresent(sfHolderEncryptionKey) ||
194 !sleMptoken->isFieldPresent(sfConfidentialBalanceSpending) ||
195 !sleMptoken->isFieldPresent(sfIssuerEncryptedBalance))
196 {
197 return tecNO_PERMISSION;
198 }
199
200 // Sanity check: holder's MPToken must have auditor balance field if auditing
201 // is enabled
202 if (requiresAuditor && !sleMptoken->isFieldPresent(sfAuditorEncryptedBalance))
203 {
204 // LCOV_EXCL_START
205 UNREACHABLE(
206 "xrpl::ConfidentialMPTConvertBack::preclaim : issuance-level auditing implies the "
207 "MPToken already carries an auditor balance");
208 return tefINTERNAL;
209 // LCOV_EXCL_STOP
210 }
211
212 // if the total circulating confidential balance is smaller than what the
213 // holder is trying to convert back, we know for sure this txn should
214 // fail
215 if ((*sleIssuance)[~sfConfidentialOutstandingAmount].value_or(0) < amount)
217
218 // Check lock
219 MPTIssue const mptIssue(mptIssuanceID);
220 if (auto const ter = checkFrozen(ctx.view, account, mptIssue); !isTesSuccess(ter))
221 return ter;
222
223 // Check auth
224 if (auto const ter = requireAuth(ctx.view, mptIssue, account); !isTesSuccess(ter))
225 return ter;
226
227 if (auto const res = verifyProofs(ctx.tx, sleIssuance, sleMptoken); !isTesSuccess(res))
228 return res;
229
230 return tesSUCCESS;
231}
232
233TER
235{
236 auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
237
238 auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, accountID_));
239 if (!sleMptoken)
240 {
241 // LCOV_EXCL_START
242 UNREACHABLE(
243 "xrpl::ConfidentialMPTConvertBack::doApply : preclaim already validated the "
244 "MPToken exists");
245 return tecINTERNAL;
246 // LCOV_EXCL_STOP
247 }
248
249 auto sleIssuance = view().peek(keylet::mptokenIssuance(mptIssuanceID));
250 if (!sleIssuance)
251 {
252 // LCOV_EXCL_START
253 UNREACHABLE(
254 "xrpl::ConfidentialMPTConvertBack::doApply : preclaim already validated the "
255 "issuance exists");
256 return tecINTERNAL;
257 // LCOV_EXCL_STOP
258 }
259
260 auto const amtToConvertBack = ctx_.tx[sfMPTAmount];
261 auto const amt = (*sleMptoken)[~sfMPTAmount].valueOr(0);
262
263 // Converting back increases regular balance and decreases confidential
264 // outstanding. This is the inverse of Convert.
265 if (amt > kMaxMpTokenAmount - amtToConvertBack)
266 return tecINTERNAL; // LCOV_EXCL_LINE
267 (*sleMptoken)[sfMPTAmount] = amt + amtToConvertBack;
268
269 auto const coa = (*sleIssuance)[~sfConfidentialOutstandingAmount].valueOr(0);
270 if (coa < amtToConvertBack)
271 return tecINTERNAL; // LCOV_EXCL_LINE
272 (*sleIssuance)[sfConfidentialOutstandingAmount] = coa - amtToConvertBack;
273
274 std::optional<Slice> const auditorEc = ctx_.tx[~sfAuditorEncryptedAmount];
275
276 // homomorphically subtract holder's encrypted balance
277 {
278 auto res = homomorphicSubtract(
279 (*sleMptoken)[sfConfidentialBalanceSpending], ctx_.tx[sfHolderEncryptedAmount]);
280 if (!res)
281 {
282 // LCOV_EXCL_START
283 JLOG(ctx_.journal.error())
284 << "ConfidentialMPTConvertBack failed homomorphic subtract for holder spending "
285 "balance.";
286 return tecINTERNAL;
287 // LCOV_EXCL_STOP
288 }
289
290 (*sleMptoken)[sfConfidentialBalanceSpending] = std::move(*res);
291 }
292
293 // homomorphically subtract issuer's encrypted balance
294 {
295 auto res = homomorphicSubtract(
296 (*sleMptoken)[sfIssuerEncryptedBalance], ctx_.tx[sfIssuerEncryptedAmount]);
297 if (!res)
298 {
299 // LCOV_EXCL_START
300 JLOG(ctx_.journal.error())
301 << "ConfidentialMPTConvertBack failed homomorphic subtract for issuer balance.";
302 return tecINTERNAL;
303 // LCOV_EXCL_STOP
304 }
305
306 (*sleMptoken)[sfIssuerEncryptedBalance] = std::move(*res);
307 }
308
309 if (auditorEc)
310 {
311 auto res = homomorphicSubtract(
312 (*sleMptoken)[sfAuditorEncryptedBalance], ctx_.tx[sfAuditorEncryptedAmount]);
313 if (!res)
314 {
315 // LCOV_EXCL_START
316 JLOG(ctx_.journal.error())
317 << "ConfidentialMPTConvertBack failed homomorphic subtract for auditor balance.";
318 return tecINTERNAL;
319 // LCOV_EXCL_STOP
320 }
321
322 (*sleMptoken)[sfAuditorEncryptedBalance] = std::move(*res);
323 }
324
325 incrementConfidentialVersion(*sleMptoken);
326
327 view().update(sleIssuance);
328 view().update(sleMptoken);
329 return tesSUCCESS;
330}
331
332void
339
340bool
342 STTx const&,
343 TER,
344 XRPAmount,
345 ReadView const&,
346 beast::Journal const&)
347{
348 return true;
349}
350
351} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void update(SLE::ref sle)=0
Indicate changes to a peeked SLE.
void visitInvariantEntry(bool isDelete, std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after) override
static TER preclaim(PreclaimContext const &ctx)
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
static NotTEC preflight(PreflightContext const &ctx)
AccountID const & getIssuer() const
Definition MPTIssue.cpp:29
A view into a ledger.
Definition ReadView.h:41
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:464
SeqProxy getSeqProxy() const
Definition STTx.cpp:199
constexpr std::uint32_t value() const
Definition SeqProxy.h:80
An immutable linear range of bytes.
Definition Slice.h:28
ApplyView & view()
Definition Transactor.h:175
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
AccountID const accountID_
Definition Transactor.h:157
ApplyContext & ctx_
Definition Transactor.h:153
T emplace(T... args)
TER valid(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:543
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:537
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
NotTEC checkEncryptedAmountFormat(STObject const &object)
Validates the format of encrypted amount fields in a transaction.
constexpr std::uint32_t kConfidentialFeeMultiplier
Extra base fee multiplier charged to confidential MPT transactions.
Definition Protocol.h:534
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
@ tefINTERNAL
Definition TER.h:165
bool isValidCompressedECPoint(Slice const &buffer)
Verifies that a buffer contains a valid, parsable compressed EC point.
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.
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:607
std::optional< Buffer > homomorphicSubtract(Slice const &a, Slice const &b)
Homomorphically subtracts two ElGamal ciphertexts.
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.
@ temMALFORMED
Definition TER.h:75
@ temBAD_AMOUNT
Definition TER.h:77
bool isTesSuccess(TER x) noexcept
Definition TER.h:676
TERSubset< CanCvtToTER > TER
Definition TER.h:647
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, std::uint8_t depth=0)
Check if the account lacks required authorization for MPT.
@ tecOBJECT_NOT_FOUND
Definition TER.h:329
@ tecINTERNAL
Definition TER.h:313
@ tecINSUFFICIENT_FUNDS
Definition TER.h:328
@ tecBAD_PROOF
Definition TER.h:371
@ tecNO_PERMISSION
Definition TER.h:308
void incrementConfidentialVersion(STObject &mptoken)
Increments the confidential balance version counter on an MPToken.
constexpr std::uint64_t kMaxMpTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:296
@ tesSUCCESS
Definition TER.h:245
static TER verifyProofs(STTx const &tx, std::shared_ptr< SLE const > const &issuance, std::shared_ptr< SLE const > const &mptoken)
Verifies the cryptographic proofs for a ConvertBack transaction.
Bundles an ElGamal public key with its associated encrypted amount.
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:83
ReadView const & view
Definition Transactor.h:86
State information when preflighting a tx.
Definition Transactor.h:38