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/Feature.h>
11#include <xrpl/protocol/Indexes.h>
12#include <xrpl/protocol/LedgerFormats.h>
13#include <xrpl/protocol/Protocol.h>
14#include <xrpl/protocol/SField.h>
15#include <xrpl/protocol/STTx.h>
16#include <xrpl/protocol/TER.h>
17#include <xrpl/protocol/XRPAmount.h>
18#include <xrpl/tx/Transactor.h>
19
20#include <memory>
21#include <optional>
22#include <utility>
23
24namespace xrpl {
25
28{
29 // issuer cannot convert back
30 if (MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer() == ctx.tx[sfAccount])
31 return temMALFORMED;
32
33 if (ctx.tx[sfMPTAmount] == 0 || ctx.tx[sfMPTAmount] > kMaxMpTokenAmount)
34 return temBAD_AMOUNT;
35
36 if (!isValidCompressedECPoint(ctx.tx[sfBalanceCommitment]))
37 return temMALFORMED;
38
39 // check encrypted amount format after the above basic checks
40 // this check is more expensive so put it at the end
41 if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res))
42 return res;
43
44 // ConvertBack proof = compact sigma proof (128 bytes) + single bulletproof (688 bytes)
45 if (ctx.tx[sfZKProof].size() != kEcConvertBackProofLength)
46 return temMALFORMED;
47
48 return tesSUCCESS;
49}
50
56
70static TER
72 STTx const& tx,
73 std::shared_ptr<SLE const> const& issuance,
74 std::shared_ptr<SLE const> const& mptoken)
75{
76 if (!mptoken->isFieldPresent(sfHolderEncryptionKey))
77 {
78 // LCOV_EXCL_START
79 UNREACHABLE(
80 "xrpl::verifyProofs : preclaim already validated the holder encryption key is "
81 "present");
82 return tecINTERNAL;
83 // LCOV_EXCL_STOP
84 }
85
86 auto const mptIssuanceID = tx[sfMPTokenIssuanceID];
87 auto const account = tx[sfAccount];
88 auto const amount = tx[sfMPTAmount];
89 auto const blindingFactor = tx[sfBlindingFactor];
90 auto const holderPubKey = (*mptoken)[sfHolderEncryptionKey];
91
92 auto const contextHash = getConvertBackContextHash(
93 account,
94 mptIssuanceID,
95 tx.getSeqProxy().value(),
96 (*mptoken)[~sfConfidentialBalanceVersion].value_or(0));
97
98 // Prepare Auditor Info
100 bool const hasAuditor = issuance->isFieldPresent(sfAuditorEncryptionKey);
101 if (hasAuditor)
102 {
103 auditor.emplace(
105 .publicKey = (*issuance)[sfAuditorEncryptionKey],
106 .encryptedAmount = tx[sfAuditorEncryptedAmount],
107 });
108 }
109
110 // Run all verifications before returning any error to prevent timing attacks
111 // that could reveal which proof failed.
112 bool valid = true;
113
114 if (auto const ter = verifyRevealedAmount(
115 amount,
116 Slice(blindingFactor.data(), blindingFactor.size()),
117 {
118 .publicKey = holderPubKey,
119 .encryptedAmount = tx[sfHolderEncryptedAmount],
120 },
121 {
122 .publicKey = (*issuance)[sfIssuerEncryptionKey],
123 .encryptedAmount = tx[sfIssuerEncryptedAmount],
124 },
125 auditor);
126 !isTesSuccess(ter))
127 {
128 valid = false;
129 }
130
131 if (auto const ter = verifyConvertBackProof(
132 tx[sfZKProof],
133 holderPubKey,
134 (*mptoken)[sfConfidentialBalanceSpending],
135 tx[sfBalanceCommitment],
136 amount,
137 contextHash);
138 !isTesSuccess(ter))
139 {
140 valid = false;
141 }
142
143 if (!valid)
144 return tecBAD_PROOF;
145
146 return tesSUCCESS;
147}
148
149TER
151{
152 auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID];
153 auto const account = ctx.tx[sfAccount];
154 auto const amount = ctx.tx[sfMPTAmount];
155
156 // ensure that issuance exists
157 auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
158 if (!sleIssuance)
159 return tecOBJECT_NOT_FOUND;
160
161 if (!sleIssuance->isFlag(lsfMPTCanHoldConfidentialBalance) ||
162 !sleIssuance->isFieldPresent(sfIssuerEncryptionKey))
163 return tecNO_PERMISSION;
164
165 bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
166 bool const requiresAuditor = sleIssuance->isFieldPresent(sfAuditorEncryptionKey);
167
168 // tx must include auditor ciphertext if the issuance has enabled
169 // auditing
170 if (requiresAuditor && !hasAuditor)
171 return tecNO_PERMISSION;
172
173 // if auditing is not supported then user should not upload auditor
174 // ciphertext
175 if (!requiresAuditor && hasAuditor)
176 return tecNO_PERMISSION;
177
178 // already checked in preflight, but should also check that issuer on
179 // the issuance isn't the account either
180 if (sleIssuance->getAccountID(sfIssuer) == account)
181 {
182 // LCOV_EXCL_START
183 UNREACHABLE(
184 "xrpl::ConfidentialMPTConvertBack::preclaim : issuer derived from the MPT ID must "
185 "match the ledger's stored issuer");
186 return tefINTERNAL;
187 // LCOV_EXCL_STOP
188 }
189
190 auto const sleMptoken = ctx.view.read(keylet::mptoken(mptIssuanceID, account));
191 if (!sleMptoken)
192 return tecOBJECT_NOT_FOUND;
193
194 if (!sleMptoken->isFieldPresent(sfHolderEncryptionKey) ||
195 !sleMptoken->isFieldPresent(sfConfidentialBalanceSpending) ||
196 !sleMptoken->isFieldPresent(sfIssuerEncryptedBalance))
197 {
198 return tecNO_PERMISSION;
199 }
200
201 // Converting back homomorphically subtracts from the holder's mirrors, so
202 // those mirrors must be current.
203 if (ctx.view.rules().enabled(featureConfidentialMPTKeyRotation) &&
204 !areMirrorsCurrent(*sleIssuance, *sleMptoken))
205 {
206 return tecNO_PERMISSION;
207 }
208
209 // Sanity check: holder's MPToken must have auditor balance field if auditing
210 // is enabled
211 if (requiresAuditor && !sleMptoken->isFieldPresent(sfAuditorEncryptedBalance))
212 {
213 // LCOV_EXCL_START
214 UNREACHABLE(
215 "xrpl::ConfidentialMPTConvertBack::preclaim : issuance-level auditing implies the "
216 "MPToken already carries an auditor balance");
217 return tefINTERNAL;
218 // LCOV_EXCL_STOP
219 }
220
221 // if the total circulating confidential balance is smaller than what the
222 // holder is trying to convert back, we know for sure this txn should
223 // fail
224 if ((*sleIssuance)[~sfConfidentialOutstandingAmount].value_or(0) < amount)
226
227 // Check lock
228 MPTIssue const mptIssue(mptIssuanceID);
229 if (auto const ter = checkFrozen(ctx.view, account, mptIssue); !isTesSuccess(ter))
230 return ter;
231
232 // Check auth
233 if (auto const ter = requireAuth(ctx.view, mptIssue, account); !isTesSuccess(ter))
234 return ter;
235
236 if (auto const res = verifyProofs(ctx.tx, sleIssuance, sleMptoken); !isTesSuccess(res))
237 return res;
238
239 return tesSUCCESS;
240}
241
242TER
244{
245 auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
246
247 auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, accountID_));
248 if (!sleMptoken)
249 {
250 // LCOV_EXCL_START
251 UNREACHABLE(
252 "xrpl::ConfidentialMPTConvertBack::doApply : preclaim already validated the "
253 "MPToken exists");
254 return tecINTERNAL;
255 // LCOV_EXCL_STOP
256 }
257
258 auto sleIssuance = view().peek(keylet::mptokenIssuance(mptIssuanceID));
259 if (!sleIssuance)
260 {
261 // LCOV_EXCL_START
262 UNREACHABLE(
263 "xrpl::ConfidentialMPTConvertBack::doApply : preclaim already validated the "
264 "issuance exists");
265 return tecINTERNAL;
266 // LCOV_EXCL_STOP
267 }
268
269 auto const amtToConvertBack = ctx_.tx[sfMPTAmount];
270 auto const amt = (*sleMptoken)[~sfMPTAmount].valueOr(0);
271
272 // Converting back increases regular balance and decreases confidential
273 // outstanding. This is the inverse of Convert.
274 if (amt > kMaxMpTokenAmount - amtToConvertBack)
275 return tecINTERNAL; // LCOV_EXCL_LINE
276 (*sleMptoken)[sfMPTAmount] = amt + amtToConvertBack;
277
278 auto const coa = (*sleIssuance)[~sfConfidentialOutstandingAmount].valueOr(0);
279 if (coa < amtToConvertBack)
280 return tecINTERNAL; // LCOV_EXCL_LINE
281 (*sleIssuance)[sfConfidentialOutstandingAmount] = coa - amtToConvertBack;
282
283 std::optional<Slice> const auditorEc = ctx_.tx[~sfAuditorEncryptedAmount];
284
285 // homomorphically subtract holder's encrypted balance
286 {
287 auto res = homomorphicSubtract(
288 (*sleMptoken)[sfConfidentialBalanceSpending], ctx_.tx[sfHolderEncryptedAmount]);
289 if (!res)
290 {
291 // LCOV_EXCL_START
292 JLOG(ctx_.journal.error())
293 << "ConfidentialMPTConvertBack failed homomorphic subtract for holder spending "
294 "balance.";
295 return tecINTERNAL;
296 // LCOV_EXCL_STOP
297 }
298
299 (*sleMptoken)[sfConfidentialBalanceSpending] = std::move(*res);
300 }
301
302 // homomorphically subtract issuer's encrypted balance
303 {
304 auto res = homomorphicSubtract(
305 (*sleMptoken)[sfIssuerEncryptedBalance], ctx_.tx[sfIssuerEncryptedAmount]);
306 if (!res)
307 {
308 // LCOV_EXCL_START
309 JLOG(ctx_.journal.error())
310 << "ConfidentialMPTConvertBack failed homomorphic subtract for issuer balance.";
311 return tecINTERNAL;
312 // LCOV_EXCL_STOP
313 }
314
315 (*sleMptoken)[sfIssuerEncryptedBalance] = std::move(*res);
316 }
317
318 if (auditorEc)
319 {
320 auto res = homomorphicSubtract(
321 (*sleMptoken)[sfAuditorEncryptedBalance], ctx_.tx[sfAuditorEncryptedAmount]);
322 if (!res)
323 {
324 // LCOV_EXCL_START
325 JLOG(ctx_.journal.error())
326 << "ConfidentialMPTConvertBack failed homomorphic subtract for auditor balance.";
327 return tecINTERNAL;
328 // LCOV_EXCL_STOP
329 }
330
331 (*sleMptoken)[sfAuditorEncryptedBalance] = std::move(*res);
332 }
333
334 incrementConfidentialVersion(*sleMptoken);
335
336 view().update(sleIssuance);
337 view().update(sleMptoken);
338 return tesSUCCESS;
339}
340
341void
348
349bool
351 STTx const&,
352 TER,
353 XRPAmount,
354 ReadView const&,
355 beast::Journal const&)
356{
357 return true;
358}
359
360} // 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 Rules const & rules() const =0
Returns the tx processing rules.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(UInt256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:182
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:464
SeqProxy getSeqProxy() const
Definition STTx.cpp:198
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:184
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
AccountID const accountID_
Definition Transactor.h:166
ApplyContext & ctx_
Definition Transactor.h:162
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:573
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:567
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
bool areMirrorsCurrent(SLE const &issuance, SLE const &mptoken)
Checks whether each mirror a holder is required to have is encrypted under the issuance's currently r...
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:551
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
@ tefINTERNAL
Definition TER.h:168
UInt256 getConvertBackContextHash(AccountID const &account, UInt192 const &issuanceID, std::uint32_t sequence, std::uint32_t version)
Generates the context hash for ConfidentialMPTConvertBack transactions.
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:535
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.
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:614
std::optional< Buffer > homomorphicSubtract(Slice const &a, Slice const &b)
Homomorphically subtracts two ElGamal ciphertexts.
@ temMALFORMED
Definition TER.h:75
@ temBAD_AMOUNT
Definition TER.h:77
bool isTesSuccess(TER x) noexcept
Definition TER.h:683
TERSubset< CanCvtToTER > TER
Definition TER.h:654
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:334
@ tecINTERNAL
Definition TER.h:318
@ tecINSUFFICIENT_FUNDS
Definition TER.h:333
@ tecBAD_PROOF
Definition TER.h:376
@ tecNO_PERMISSION
Definition TER.h:313
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:297
@ tesSUCCESS
Definition TER.h:250
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:92
ReadView const & view
Definition Transactor.h:95
State information when preflighting a tx.
Definition Transactor.h:39