xrpld
Loading...
Searching...
No Matches
ConfidentialMPTMirrorUpdate.cpp
1#include <xrpl/tx/transactors/token/ConfidentialMPTMirrorUpdate.h>
2
3#include <xrpl/basics/Slice.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/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/MPTIssue.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
20namespace xrpl {
21
22bool
24{
25 // Key rotation makes sense only when featureConfidentialTransfer is enabled.
26 return ctx.rules.enabled(featureConfidentialTransfer);
27}
28
31{
32 auto const account = ctx.tx[sfAccount];
33 auto const issuer = MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer();
34 auto const holder = ctx.tx[~sfHolder];
35 bool const hasHolder = holder.has_value();
36
37 // The rotation mode is determined by the presence of the
38 // Holder field: Holder present is issuer mode, Holder absent is
39 // holder self-migration.
40 if (hasHolder)
41 {
42 // Issuer mode: account must be the issuer
43 if (account != issuer)
44 return temMALFORMED;
45
46 if (account == *holder)
47 return temMALFORMED;
48 }
49 else
50 {
51 // Holder self-migration: the submitter is the holder, account must not be the issuer.
52 if (account == issuer)
53 return temMALFORMED;
54 }
55
56 // At least one ciphertext will be updated.
57 bool const hasIssuerAmount = ctx.tx.isFieldPresent(sfIssuerEncryptedAmount);
58 bool const hasAuditorAmount = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
59 if (!hasIssuerAmount && !hasAuditorAmount)
60 return temMALFORMED;
61
62 // Check the length of the encrypted amounts. Length check is cheaper than format check so put
63 // it before the format check.
64 if (hasIssuerAmount && ctx.tx[sfIssuerEncryptedAmount].length() != kEcGamalEncryptedTotalLength)
65 return temBAD_CIPHERTEXT;
66
67 if (hasAuditorAmount &&
68 ctx.tx[sfAuditorEncryptedAmount].length() != kEcGamalEncryptedTotalLength)
69 return temBAD_CIPHERTEXT;
70
71 // Check proof length.
72 if (ctx.tx[sfZKProof].length() != kEcEqualityProofLength)
73 return temMALFORMED;
74
75 // Check the encrypted amount formats. It is more expensive so put it at the end of preflight.
76 if (hasIssuerAmount && !isValidCiphertext(ctx.tx[sfIssuerEncryptedAmount]))
77 return temBAD_CIPHERTEXT;
78
79 if (hasAuditorAmount && !isValidCiphertext(ctx.tx[sfAuditorEncryptedAmount]))
80 return temBAD_CIPHERTEXT;
81
82 return tesSUCCESS;
83}
84
90
91TER
93{
94 // Check if account exists
95 auto const account = ctx.tx[sfAccount];
96 if (!ctx.view.exists(keylet::account(account)))
97 return terNO_ACCOUNT; // LCOV_EXCL_LINE
98
99 // The issuance must exist and have confidential balances enabled with a
100 // registered issuer encryption key; otherwise there is no mirror to update.
101 auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID];
102 auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
103 if (!sleIssuance)
104 return tecOBJECT_NOT_FOUND;
105
106 // The issuance must have confidential balances enabled with a registered issuer encryption key.
107 if (!sleIssuance->isFlag(lsfMPTCanHoldConfidentialBalance) ||
108 !sleIssuance->isFieldPresent(sfIssuerEncryptionKey))
109 return tecNO_PERMISSION;
110
111 // Sanity check: preflight already enforced the issuer holder combination
112 // under different rotation modes.
113 auto const holder = ctx.tx[~sfHolder];
114 bool const hasHolder = holder.has_value();
115 auto const issuer = sleIssuance->getAccountID(sfIssuer);
116 if (hasHolder ? (issuer != account) : (issuer == account))
117 {
118 // LCOV_EXCL_START
119 UNREACHABLE(
120 "xrpl::ConfidentialMPTMirrorUpdate::preclaim : invalid issuer holder combination");
121 return tefINTERNAL;
122 // LCOV_EXCL_STOP
123 }
124
125 // The holder is sfHolder in issuer mode and is sfAccount in holder mode.
126 auto const holderID = hasHolder ? *holder : account;
127
128 // In issuer mode, the holder must exist. In holder mode, the account existence was checked
129 // already.
130 if (hasHolder && !ctx.view.exists(keylet::account(holderID)))
131 return tecNO_TARGET;
132
133 // In either issuer or holder mode, check the existence of the MPToken object.
134 auto const sleMptoken = ctx.view.read(keylet::mptoken(mptIssuanceID, holderID));
135 if (!sleMptoken)
136 return tecOBJECT_NOT_FOUND;
137
138 // The holder must already hold an issuer confidential balance.
139 if (!sleMptoken->isFieldPresent(sfIssuerEncryptedBalance))
140 return tecNO_PERMISSION;
141
142 bool const hasIssuerAmount = ctx.tx.isFieldPresent(sfIssuerEncryptedAmount);
143 bool const hasAuditorAmount = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
144
145 // Migrating the auditor mirror requires the issuance to have a registered
146 // auditor encryption key.
147 if (hasAuditorAmount && !sleIssuance->isFieldPresent(sfAuditorEncryptionKey))
148 return tecNO_PERMISSION;
149
150 // An issuer mirror may only be re-encrypted while it is stale, reject if it is already current.
151 if (hasIssuerAmount && isIssuerMirrorCurrent(*sleIssuance, *sleMptoken))
152 return tecNO_PERMISSION;
153
154 if (hasAuditorAmount)
155 {
156 // An issuer-mode auditor-only migration: the issuer mirror must already be up to date.
157 if (hasHolder && !hasIssuerAmount && !isIssuerMirrorCurrent(*sleIssuance, *sleMptoken))
158 return tecNO_PERMISSION;
159
160 // An auditor mirror may only be re-encrypted while it is stale, reject if it is already
161 // current. isAuditorMirrorCurrent reports an absent auditor mirror as stale, which is what
162 // allows an auditor-only migration to create one for the first time.
163 if (isAuditorMirrorCurrent(*sleIssuance, *sleMptoken))
164 return tecNO_PERMISSION;
165 }
166
167 // Holder self-migration re-encrypts the mirror from the holder's own
168 // spending balance, which reflects the holder's full balance only once the
169 // inbox has been merged into it. Require the inbox to be canonical zero,
170 // i.e. ConfidentialMPTMergeInbox has already been applied.
171 if (!hasHolder)
172 {
173 // Sanity check: a holder that already carries an issuer mirror
174 // necessarily has a holder encryption key and a spending balance
175 if (!sleMptoken->isFieldPresent(sfHolderEncryptionKey) ||
176 !sleMptoken->isFieldPresent(sfConfidentialBalanceSpending))
177 {
178 // LCOV_EXCL_START
179 UNREACHABLE(
180 "xrpl::ConfidentialMPTMirrorUpdate::preclaim : an issuer mirror implies a holder "
181 "key and spending balance");
182 return tefINTERNAL;
183 // LCOV_EXCL_STOP
184 }
185
186 auto const expectedZeroInbox = encryptCanonicalZeroAmount(
187 (*sleMptoken)[sfHolderEncryptionKey], holderID, mptIssuanceID);
188 if (!expectedZeroInbox)
189 {
190 // LCOV_EXCL_START
191 UNREACHABLE(
192 "xrpl::ConfidentialMPTMirrorUpdate::preclaim : canonical zero encryption cannot "
193 "fail for an already-valid holder public key");
194 return tefINTERNAL;
195 // LCOV_EXCL_STOP
196 }
197
198 bool const inboxIsCanonicalZero = sleMptoken->isFieldPresent(sfConfidentialBalanceInbox) &&
199 Slice((*sleMptoken)[sfConfidentialBalanceInbox]) == Slice(*expectedZeroInbox);
200 if (!inboxIsCanonicalZero)
201 return tecNO_PERMISSION;
202 }
203
204 return tesSUCCESS;
205}
206
207TER
209{
210 auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
211
212 auto const sleIssuance = view().read(keylet::mptokenIssuance(mptIssuanceID));
213 if (!sleIssuance)
214 {
215 // LCOV_EXCL_START
216 UNREACHABLE(
217 "xrpl::ConfidentialMPTMirrorUpdate::doApply : preclaim already validated the "
218 "issuance exists");
219 return tecINTERNAL;
220 // LCOV_EXCL_STOP
221 }
222
223 // The holderID is sfHolder in issuer mode and sfAccount in holder mode.
224 auto const holder = ctx_.tx[~sfHolder];
225 auto const holderID = holder.value_or(accountID_);
226
227 auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, holderID));
228 if (!sleMptoken)
229 {
230 // LCOV_EXCL_START
231 UNREACHABLE(
232 "xrpl::ConfidentialMPTMirrorUpdate::doApply : preclaim already validated the "
233 "MPToken exists");
234 return tecINTERNAL;
235 // LCOV_EXCL_STOP
236 }
237
238 // Re-encrypt the requested mirror(s) and advance the corresponding mirror
239 // epoch to match the issuance key epoch. Each mirror is stamped separately
240 // because this transaction may migrate either one or both.
241 if (ctx_.tx.isFieldPresent(sfIssuerEncryptedAmount))
242 {
243 (*sleMptoken)[sfIssuerEncryptedBalance] = ctx_.tx[sfIssuerEncryptedAmount];
244 setIssuerMirrorEpoch(*sleIssuance, *sleMptoken);
245 }
246
247 if (ctx_.tx.isFieldPresent(sfAuditorEncryptedAmount))
248 {
249 (*sleMptoken)[sfAuditorEncryptedBalance] = ctx_.tx[sfAuditorEncryptedAmount];
250 setAuditorMirrorEpoch(*sleIssuance, *sleMptoken);
251 }
252
253 view().update(sleMptoken);
254 return tesSUCCESS;
255}
256
257void
261
262bool
264 STTx const&,
265 TER,
266 XRPAmount,
267 ReadView const&,
268 beast::Journal const&)
269{
270 return true;
271}
272
273} // 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.
static bool checkExtraFeatures(PreflightContext const &ctx)
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 TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
void visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override
Inspect a single ledger entry modified by this transaction.
AccountID const & getIssuer() const
Definition MPTIssue.cpp:29
A view into a ledger.
Definition ReadView.h:41
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
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
std::shared_ptr< STLedgerEntry const > const & ConstRef
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:464
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:643
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
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:573
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
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
@ terNO_ACCOUNT
Definition TER.h:218
std::optional< Buffer > encryptCanonicalZeroAmount(Slice const &pubKeySlice, AccountID const &account, MPTID const &mptId)
Generates the canonical zero encryption for a specific MPToken.
bool isIssuerMirrorCurrent(SLE const &issuance, SLE const &mptoken)
Checks whether a holder's issuer mirror is encrypted under the issuance's currently registered issuer...
constexpr std::uint32_t kConfidentialFeeMultiplier
Extra base fee multiplier charged to confidential MPT transactions.
Definition Protocol.h:551
@ tefINTERNAL
Definition TER.h:168
bool isAuditorMirrorCurrent(SLE const &issuance, SLE const &mptoken)
Checks whether a holder's auditor mirror is encrypted under the issuance's currently registered audit...
constexpr std::size_t kEcGamalEncryptedTotalLength
EC ElGamal ciphertext length: two compressed EC points concatenated.
Definition Protocol.h:480
bool isValidCiphertext(Slice const &buffer)
Verifies that a buffer contains two valid, parsable EC public keys.
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:614
void setAuditorMirrorEpoch(SLE const &issuance, SLE &mptoken)
Set the holder's auditor mirror epoch to match the issuance's current auditor key epoch.
void setIssuerMirrorEpoch(SLE const &issuance, SLE &mptoken)
Set the holder's issuer mirror epoch to match the issuance's current issuer key epoch.
constexpr std::size_t kEcEqualityProofLength
Length of compact equality proof.
Definition Protocol.h:546
@ temBAD_CIPHERTEXT
Definition TER.h:134
@ temMALFORMED
Definition TER.h:75
TERSubset< CanCvtToTER > TER
Definition TER.h:654
@ tecNO_TARGET
Definition TER.h:312
@ tecOBJECT_NOT_FOUND
Definition TER.h:334
@ tecINTERNAL
Definition TER.h:318
@ tecNO_PERMISSION
Definition TER.h:313
@ tesSUCCESS
Definition TER.h:250
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