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/core/ServiceRegistry.h>
6#include <xrpl/ledger/ReadView.h>
7#include <xrpl/ledger/helpers/TokenHelpers.h>
8#include <xrpl/protocol/ConfidentialTransfer.h>
9#include <xrpl/protocol/Feature.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 if (!ctx.rules.enabled(featureConfidentialTransfer))
29 return temDISABLED;
30
31 // issuer cannot convert back
32 if (MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer() == ctx.tx[sfAccount])
33 return temMALFORMED;
34
35 if (ctx.tx[sfMPTAmount] == 0 || ctx.tx[sfMPTAmount] > kMaxMpTokenAmount)
36 return temBAD_AMOUNT;
37
38 if (!isValidCompressedECPoint(ctx.tx[sfBalanceCommitment]))
39 return temMALFORMED;
40
41 // check encrypted amount format after the above basic checks
42 // this check is more expensive so put it at the end
43 if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res))
44 return res;
45
46 // ConvertBack proof = compact sigma proof (128 bytes) + single bulletproof (688 bytes)
47 if (ctx.tx[sfZKProof].size() != kEcConvertBackProofLength)
48 return temMALFORMED;
49
50 return tesSUCCESS;
51}
52
58
72static TER
74 STTx const& tx,
75 std::shared_ptr<SLE const> const& issuance,
76 std::shared_ptr<SLE const> const& mptoken)
77{
78 if (!mptoken->isFieldPresent(sfHolderEncryptionKey))
79 return tecINTERNAL; // LCOV_EXCL_LINE
80
81 auto const mptIssuanceID = tx[sfMPTokenIssuanceID];
82 auto const account = tx[sfAccount];
83 auto const amount = tx[sfMPTAmount];
84 auto const blindingFactor = tx[sfBlindingFactor];
85 auto const holderPubKey = (*mptoken)[sfHolderEncryptionKey];
86
87 auto const contextHash = getConvertBackContextHash(
88 account,
89 mptIssuanceID,
90 tx.getSeqProxy().value(),
91 (*mptoken)[~sfConfidentialBalanceVersion].value_or(0));
92
93 // Prepare Auditor Info
95 bool const hasAuditor = issuance->isFieldPresent(sfAuditorEncryptionKey);
96 if (hasAuditor)
97 {
98 auditor.emplace(
100 .publicKey = (*issuance)[sfAuditorEncryptionKey],
101 .encryptedAmount = tx[sfAuditorEncryptedAmount],
102 });
103 }
104
105 // Run all verifications before returning any error to prevent timing attacks
106 // that could reveal which proof failed.
107 bool valid = true;
108
109 if (auto const ter = verifyRevealedAmount(
110 amount,
111 Slice(blindingFactor.data(), blindingFactor.size()),
112 {
113 .publicKey = holderPubKey,
114 .encryptedAmount = tx[sfHolderEncryptedAmount],
115 },
116 {
117 .publicKey = (*issuance)[sfIssuerEncryptionKey],
118 .encryptedAmount = tx[sfIssuerEncryptedAmount],
119 },
120 auditor);
121 !isTesSuccess(ter))
122 {
123 valid = false;
124 }
125
126 if (auto const ter = verifyConvertBackProof(
127 tx[sfZKProof],
128 holderPubKey,
129 (*mptoken)[sfConfidentialBalanceSpending],
130 tx[sfBalanceCommitment],
131 amount,
132 contextHash);
133 !isTesSuccess(ter))
134 {
135 valid = false;
136 }
137
138 if (!valid)
139 return tecBAD_PROOF;
140
141 return tesSUCCESS;
142}
143
144TER
146{
147 auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID];
148 auto const account = ctx.tx[sfAccount];
149 auto const amount = ctx.tx[sfMPTAmount];
150
151 // ensure that issuance exists
152 auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
153 if (!sleIssuance)
154 return tecOBJECT_NOT_FOUND;
155
156 if (!sleIssuance->isFlag(lsfMPTCanHoldConfidentialBalance) ||
157 !sleIssuance->isFieldPresent(sfIssuerEncryptionKey))
158 return tecNO_PERMISSION;
159
160 bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
161 bool const requiresAuditor = sleIssuance->isFieldPresent(sfAuditorEncryptionKey);
162
163 // tx must include auditor ciphertext if the issuance has enabled
164 // auditing
165 if (requiresAuditor && !hasAuditor)
166 return tecNO_PERMISSION;
167
168 // if auditing is not supported then user should not upload auditor
169 // ciphertext
170 if (!requiresAuditor && hasAuditor)
171 return tecNO_PERMISSION;
172
173 // already checked in preflight, but should also check that issuer on
174 // the issuance isn't the account either
175 if (sleIssuance->getAccountID(sfIssuer) == account)
176 return tefINTERNAL; // LCOV_EXCL_LINE
177
178 auto const sleMptoken = ctx.view.read(keylet::mptoken(mptIssuanceID, account));
179 if (!sleMptoken)
180 return tecOBJECT_NOT_FOUND;
181
182 if (!sleMptoken->isFieldPresent(sfHolderEncryptionKey) ||
183 !sleMptoken->isFieldPresent(sfConfidentialBalanceSpending) ||
184 !sleMptoken->isFieldPresent(sfIssuerEncryptedBalance))
185 {
186 return tecNO_PERMISSION;
187 }
188
189 // Sanity check: holder's MPToken must have auditor balance field if auditing
190 // is enabled
191 if (requiresAuditor && !sleMptoken->isFieldPresent(sfAuditorEncryptedBalance))
192 return tefINTERNAL; // LCOV_EXCL_LINE
193
194 // if the total circulating confidential balance is smaller than what the
195 // holder is trying to convert back, we know for sure this txn should
196 // fail
197 if ((*sleIssuance)[~sfConfidentialOutstandingAmount].value_or(0) < amount)
199
200 // Check lock
201 MPTIssue const mptIssue(mptIssuanceID);
202 if (auto const ter = checkFrozen(ctx.view, account, mptIssue); !isTesSuccess(ter))
203 return ter;
204
205 // Check auth
206 if (auto const ter = requireAuth(ctx.view, mptIssue, account); !isTesSuccess(ter))
207 return ter;
208
209 if (auto const res = verifyProofs(ctx.tx, sleIssuance, sleMptoken); !isTesSuccess(res))
210 return res;
211
212 return tesSUCCESS;
213}
214
215TER
217{
218 auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
219
220 auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, accountID_));
221 if (!sleMptoken)
222 return tecINTERNAL; // LCOV_EXCL_LINE
223
224 auto sleIssuance = view().peek(keylet::mptokenIssuance(mptIssuanceID));
225 if (!sleIssuance)
226 return tecINTERNAL; // LCOV_EXCL_LINE
227
228 auto const amtToConvertBack = ctx_.tx[sfMPTAmount];
229 auto const amt = (*sleMptoken)[~sfMPTAmount].valueOr(0);
230
231 // Converting back increases regular balance and decreases confidential
232 // outstanding. This is the inverse of Convert.
233 if (amt > kMaxMpTokenAmount - amtToConvertBack)
234 return tecINTERNAL; // LCOV_EXCL_LINE
235 (*sleMptoken)[sfMPTAmount] = amt + amtToConvertBack;
236
237 auto const coa = (*sleIssuance)[~sfConfidentialOutstandingAmount].valueOr(0);
238 if (coa < amtToConvertBack)
239 return tecINTERNAL; // LCOV_EXCL_LINE
240 (*sleIssuance)[sfConfidentialOutstandingAmount] = coa - amtToConvertBack;
241
242 std::optional<Slice> const auditorEc = ctx_.tx[~sfAuditorEncryptedAmount];
243
244 // homomorphically subtract holder's encrypted balance
245 {
246 auto res = homomorphicSubtract(
247 (*sleMptoken)[sfConfidentialBalanceSpending], ctx_.tx[sfHolderEncryptedAmount]);
248 if (!res)
249 {
250 // LCOV_EXCL_START
251 JLOG(ctx_.journal.error())
252 << "ConfidentialMPTConvertBack failed homomorphic subtract for holder spending "
253 "balance.";
254 return tecINTERNAL;
255 // LCOV_EXCL_STOP
256 }
257
258 (*sleMptoken)[sfConfidentialBalanceSpending] = std::move(*res);
259 }
260
261 // homomorphically subtract issuer's encrypted balance
262 {
263 auto res = homomorphicSubtract(
264 (*sleMptoken)[sfIssuerEncryptedBalance], ctx_.tx[sfIssuerEncryptedAmount]);
265 if (!res)
266 {
267 // LCOV_EXCL_START
268 JLOG(ctx_.journal.error())
269 << "ConfidentialMPTConvertBack failed homomorphic subtract for issuer balance.";
270 return tecINTERNAL;
271 // LCOV_EXCL_STOP
272 }
273
274 (*sleMptoken)[sfIssuerEncryptedBalance] = std::move(*res);
275 }
276
277 if (auditorEc)
278 {
279 auto res = homomorphicSubtract(
280 (*sleMptoken)[sfAuditorEncryptedBalance], ctx_.tx[sfAuditorEncryptedAmount]);
281 if (!res)
282 {
283 // LCOV_EXCL_START
284 JLOG(ctx_.journal.error())
285 << "ConfidentialMPTConvertBack failed homomorphic subtract for auditor balance.";
286 return tecINTERNAL;
287 // LCOV_EXCL_STOP
288 }
289
290 (*sleMptoken)[sfAuditorEncryptedBalance] = std::move(*res);
291 }
292
293 incrementConfidentialVersion(*sleMptoken);
294
295 view().update(sleIssuance);
296 view().update(sleMptoken);
297 return tesSUCCESS;
298}
299
300void
307
308bool
310 STTx const&,
311 TER,
312 XRPAmount,
313 ReadView const&,
314 beast::Journal const&)
315{
316 return true;
317}
318
319} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
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:31
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:171
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:454
SeqProxy getSeqProxy() const
Definition STTx.cpp:193
constexpr std::uint32_t value() const
Definition SeqProxy.h:62
An immutable linear range of bytes.
Definition Slice.h:26
ApplyView & view()
Definition Transactor.h:136
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
AccountID const accountID_
Definition Transactor.h:120
ApplyContext & ctx_
Definition Transactor.h:116
T emplace(T... args)
TER valid(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
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
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:364
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
@ tefINTERNAL
Definition TER.h:163
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:357
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:594
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:73
@ temDISABLED
Definition TER.h:100
@ temBAD_AMOUNT
Definition TER.h:75
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
TERSubset< CanCvtToTER > TER
Definition TER.h:634
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:324
@ tecINTERNAL
Definition TER.h:308
@ tecINSUFFICIENT_FUNDS
Definition TER.h:323
@ tecBAD_PROOF
Definition TER.h:366
@ tecNO_PERMISSION
Definition TER.h:303
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:238
@ tesSUCCESS
Definition TER.h:240
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:61
ReadView const & view
Definition Transactor.h:64
State information when preflighting a tx.
Definition Transactor.h:18