rippled
Loading...
Searching...
No Matches
Credentials.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2024 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/misc/CredentialHelpers.h>
21#include <xrpld/app/tx/detail/Credentials.h>
22#include <xrpld/ledger/ApplyView.h>
23#include <xrpld/ledger/View.h>
24#include <xrpl/basics/Log.h>
25#include <xrpl/protocol/Feature.h>
26#include <xrpl/protocol/Indexes.h>
27#include <xrpl/protocol/TxFlags.h>
28
29#include <chrono>
30
31namespace ripple {
32
33/*
34 Credentials
35 ======
36
37 A verifiable credentials (VC
38 https://en.wikipedia.org/wiki/Verifiable_credentials), as defined by the W3C
39 specification (https://www.w3.org/TR/vc-data-model-2.0/), is a
40 secure and tamper-evident way to represent information about a subject, such
41 as an individual, organization, or even an IoT device. These credentials are
42 issued by a trusted entity and can be verified by third parties without
43 directly involving the issuer at all.
44*/
45
46using namespace credentials;
47
48// ------- CREATE --------------------------
49
52{
53 if (!ctx.rules.enabled(featureCredentials))
54 {
55 JLOG(ctx.j.trace()) << "featureCredentials is disabled.";
56 return temDISABLED;
57 }
58
59 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
60 return ret;
61
62 auto const& tx = ctx.tx;
63 auto& j = ctx.j;
64
65 if (ctx.rules.enabled(fixInvalidTxFlags) &&
66 (tx.getFlags() & tfUniversalMask))
67 {
68 JLOG(ctx.j.debug()) << "CredentialCreate: invalid flags.";
69 return temINVALID_FLAG;
70 }
71
72 if (!tx[sfSubject])
73 {
74 JLOG(j.trace()) << "Malformed transaction: Invalid Subject";
75 return temMALFORMED;
76 }
77
78 auto const uri = tx[~sfURI];
79 if (uri && (uri->empty() || (uri->size() > maxCredentialURILength)))
80 {
81 JLOG(j.trace()) << "Malformed transaction: invalid size of URI.";
82 return temMALFORMED;
83 }
84
85 auto const credType = tx[sfCredentialType];
86 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
87 {
88 JLOG(j.trace())
89 << "Malformed transaction: invalid size of CredentialType.";
90 return temMALFORMED;
91 }
92
93 return preflight2(ctx);
94}
95
96TER
98{
99 auto const credType(ctx.tx[sfCredentialType]);
100 auto const subject = ctx.tx[sfSubject];
101
102 if (!ctx.view.exists(keylet::account(subject)))
103 {
104 JLOG(ctx.j.trace()) << "Subject doesn't exist.";
105 return tecNO_TARGET;
106 }
107
108 if (ctx.view.exists(
109 keylet::credential(subject, ctx.tx[sfAccount], credType)))
110 {
111 JLOG(ctx.j.trace()) << "Credential already exists.";
112 return tecDUPLICATE;
113 }
114
115 return tesSUCCESS;
116}
117
118TER
120{
121 auto const subject = ctx_.tx[sfSubject];
122 auto const credType(ctx_.tx[sfCredentialType]);
123 Keylet const credentialKey =
124 keylet::credential(subject, account_, credType);
125
126 auto const sleCred = std::make_shared<SLE>(credentialKey);
127 if (!sleCred)
128 return tefINTERNAL;
129
130 auto const optExp = ctx_.tx[~sfExpiration];
131 if (optExp)
132 {
133 std::uint32_t const closeTime =
135
136 if (closeTime > *optExp)
137 {
138 JLOG(j_.trace()) << "Malformed transaction: "
139 "Expiration time is in the past.";
140 return tecEXPIRED;
141 }
142
143 sleCred->setFieldU32(sfExpiration, ctx_.tx.getFieldU32(sfExpiration));
144 }
145
146 auto const sleIssuer = view().peek(keylet::account(account_));
147 if (!sleIssuer)
148 return tefINTERNAL;
149
150 {
151 STAmount const reserve{view().fees().accountReserve(
152 sleIssuer->getFieldU32(sfOwnerCount) + 1)};
153 if (mPriorBalance < reserve)
155 }
156
157 sleCred->setAccountID(sfSubject, subject);
158 sleCred->setAccountID(sfIssuer, account_);
159 sleCred->setFieldVL(sfCredentialType, credType);
160
161 if (ctx_.tx.isFieldPresent(sfURI))
162 sleCred->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI));
163
164 {
165 auto const page = view().dirInsert(
167 credentialKey,
169 JLOG(j_.trace()) << "Adding Credential to owner directory "
170 << to_string(credentialKey.key) << ": "
171 << (page ? "success" : "failure");
172 if (!page)
173 return tecDIR_FULL;
174 sleCred->setFieldU64(sfIssuerNode, *page);
175
176 adjustOwnerCount(view(), sleIssuer, 1, j_);
177 }
178
179 if (subject == account_)
180 {
181 sleCred->setFieldU32(sfFlags, lsfAccepted);
182 }
183 else
184 {
185 auto const page = view().dirInsert(
186 keylet::ownerDir(subject),
187 credentialKey,
188 describeOwnerDir(subject));
189 JLOG(j_.trace()) << "Adding Credential to owner directory "
190 << to_string(credentialKey.key) << ": "
191 << (page ? "success" : "failure");
192 if (!page)
193 return tecDIR_FULL;
194 sleCred->setFieldU64(sfSubjectNode, *page);
195 view().update(view().peek(keylet::account(subject)));
196 }
197
198 view().insert(sleCred);
199
200 return tesSUCCESS;
201}
202
203// ------- DELETE --------------------------
204NotTEC
206{
207 if (!ctx.rules.enabled(featureCredentials))
208 {
209 JLOG(ctx.j.trace()) << "featureCredentials is disabled.";
210 return temDISABLED;
211 }
212
213 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
214 return ret;
215
216 if (ctx.rules.enabled(fixInvalidTxFlags) &&
217 (ctx.tx.getFlags() & tfUniversalMask))
218 {
219 JLOG(ctx.j.debug()) << "CredentialDelete: invalid flags.";
220 return temINVALID_FLAG;
221 }
222
223 auto const subject = ctx.tx[~sfSubject];
224 auto const issuer = ctx.tx[~sfIssuer];
225
226 if (!subject && !issuer)
227 {
228 // Neither field is present, the transaction is malformed.
229 JLOG(ctx.j.trace()) << "Malformed transaction: "
230 "No Subject or Issuer fields.";
231 return temMALFORMED;
232 }
233
234 // Make sure that the passed account is valid.
235 if ((subject && subject->isZero()) || (issuer && issuer->isZero()))
236 {
237 JLOG(ctx.j.trace()) << "Malformed transaction: Subject or Issuer "
238 "field zeroed.";
240 }
241
242 auto const credType = ctx.tx[sfCredentialType];
243 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
244 {
245 JLOG(ctx.j.trace())
246 << "Malformed transaction: invalid size of CredentialType.";
247 return temMALFORMED;
248 }
249
250 return preflight2(ctx);
251}
252
253TER
255{
256 AccountID const account{ctx.tx[sfAccount]};
257 auto const subject = ctx.tx[~sfSubject].value_or(account);
258 auto const issuer = ctx.tx[~sfIssuer].value_or(account);
259 auto const credType(ctx.tx[sfCredentialType]);
260
261 if (!ctx.view.exists(keylet::credential(subject, issuer, credType)))
262 return tecNO_ENTRY;
263
264 return tesSUCCESS;
265}
266
267TER
269{
270 auto const subject = ctx_.tx[~sfSubject].value_or(account_);
271 auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
272
273 auto const credType(ctx_.tx[sfCredentialType]);
274 auto const sleCred =
275 view().peek(keylet::credential(subject, issuer, credType));
276 if (!sleCred)
277 return tefINTERNAL;
278
279 if ((subject != account_) && (issuer != account_) &&
281 {
282 JLOG(j_.trace()) << "Can't delete non-expired credential.";
283 return tecNO_PERMISSION;
284 }
285
286 return deleteSLE(view(), sleCred, j_);
287}
288
289// ------- APPLY --------------------------
290
291NotTEC
293{
294 if (!ctx.rules.enabled(featureCredentials))
295 {
296 JLOG(ctx.j.trace()) << "featureCredentials is disabled.";
297 return temDISABLED;
298 }
299
300 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
301 return ret;
302
303 if (ctx.rules.enabled(fixInvalidTxFlags) &&
304 (ctx.tx.getFlags() & tfUniversalMask))
305 {
306 JLOG(ctx.j.debug()) << "CredentialAccept: invalid flags.";
307 return temINVALID_FLAG;
308 }
309
310 if (!ctx.tx[sfIssuer])
311 {
312 JLOG(ctx.j.trace()) << "Malformed transaction: Issuer field zeroed.";
314 }
315
316 auto const credType = ctx.tx[sfCredentialType];
317 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
318 {
319 JLOG(ctx.j.trace())
320 << "Malformed transaction: invalid size of CredentialType.";
321 return temMALFORMED;
322 }
323
324 return preflight2(ctx);
325}
326
327TER
329{
330 AccountID const subject = ctx.tx[sfAccount];
331 AccountID const issuer = ctx.tx[sfIssuer];
332 auto const credType(ctx.tx[sfCredentialType]);
333
334 if (!ctx.view.exists(keylet::account(issuer)))
335 {
336 JLOG(ctx.j.warn()) << "No issuer: " << to_string(issuer);
337 return tecNO_ISSUER;
338 }
339
340 auto const sleCred =
341 ctx.view.read(keylet::credential(subject, issuer, credType));
342 if (!sleCred)
343 {
344 JLOG(ctx.j.warn()) << "No credential: " << to_string(subject) << ", "
345 << to_string(issuer) << ", " << credType;
346 return tecNO_ENTRY;
347 }
348
349 if (sleCred->getFieldU32(sfFlags) & lsfAccepted)
350 {
351 JLOG(ctx.j.warn()) << "Credential already accepted: "
352 << to_string(subject) << ", " << to_string(issuer)
353 << ", " << credType;
354 return tecDUPLICATE;
355 }
356
357 return tesSUCCESS;
358}
359
360TER
362{
363 AccountID const issuer{ctx_.tx[sfIssuer]};
364
365 // Both exist as credential object exist itself (checked in preclaim)
366 auto const sleSubject = view().peek(keylet::account(account_));
367 auto const sleIssuer = view().peek(keylet::account(issuer));
368
369 if (!sleSubject || !sleIssuer)
370 return tefINTERNAL;
371
372 {
373 STAmount const reserve{view().fees().accountReserve(
374 sleSubject->getFieldU32(sfOwnerCount) + 1)};
375 if (mPriorBalance < reserve)
377 }
378
379 auto const credType(ctx_.tx[sfCredentialType]);
380 Keylet const credentialKey = keylet::credential(account_, issuer, credType);
381 auto const sleCred = view().peek(credentialKey); // Checked in preclaim()
382
383 if (checkExpired(sleCred, view().info().parentCloseTime))
384 {
385 JLOG(j_.trace()) << "Credential is expired: " << sleCred->getText();
386 // delete expired credentials even if the transaction failed
387 auto const err = credentials::deleteSLE(view(), sleCred, j_);
388 return isTesSuccess(err) ? tecEXPIRED : err;
389 }
390
391 sleCred->setFieldU32(sfFlags, lsfAccepted);
392 view().update(sleCred);
393
394 adjustOwnerCount(view(), sleIssuer, -1, j_);
395 adjustOwnerCount(view(), sleSubject, 1, j_);
396
397 return tesSUCCESS;
398}
399
400} // namespace ripple
Stream debug() const
Definition: Journal.h:328
Stream trace() const
Severity stream access functions.
Definition: Journal.h:322
Stream warn() const
Definition: Journal.h:340
ApplyView & view()
Definition: ApplyContext.h:54
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition: ApplyView.h:314
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
Definition: Credentials.cpp:97
static NotTEC preflight(PreflightContext const &ctx)
Definition: Credentials.cpp:51
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
virtual LedgerInfo const & info() const =0
Returns information about the ledger.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
Blob getFieldVL(SField const &field) const
Definition: STObject.cpp:657
std::uint32_t getFieldU32(SField const &field) const
Definition: STObject.cpp:615
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:484
std::uint32_t getFlags() const
Definition: STObject.cpp:537
AccountID const account_
Definition: Transactor.h:91
ApplyView & view()
Definition: Transactor.h:107
beast::Journal const j_
Definition: Transactor.h:89
XRPAmount mPriorBalance
Definition: Transactor.h:92
ApplyContext & ctx_
Definition: Transactor.h:88
TER deleteSLE(ApplyView &view, std::shared_ptr< SLE > const &sleCredential, beast::Journal j)
bool checkExpired(std::shared_ptr< SLE const > const &sleCredential, NetClock::time_point const &closed)
Keylet credential(AccountID const &subject, AccountID const &issuer, Slice const &credType) noexcept
Definition: Indexes.cpp:536
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:175
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:365
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::size_t constexpr maxCredentialURILength
The maximum length of a URI inside a Credential.
Definition: Protocol.h:101
bool isTesSuccess(TER x)
Definition: TER.h:656
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition: View.cpp:924
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:81
@ tefINTERNAL
Definition: TER.h:173
static bool adjustOwnerCount(ApplyContext &ctx, int count)
Definition: SetOracle.cpp:185
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:133
std::size_t constexpr maxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition: Protocol.h:104
@ tecNO_ENTRY
Definition: TER.h:293
@ tecNO_ISSUER
Definition: TER.h:286
@ tecNO_TARGET
Definition: TER.h:291
@ tecDIR_FULL
Definition: TER.h:274
@ tecDUPLICATE
Definition: TER.h:302
@ tecNO_PERMISSION
Definition: TER.h:292
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:294
@ tecEXPIRED
Definition: TER.h:301
@ tesSUCCESS
Definition: TER.h:242
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
constexpr std::uint32_t tfUniversalMask
Definition: TxFlags.h:62
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:587
@ temMALFORMED
Definition: TER.h:87
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
@ temINVALID_ACCOUNT_ID
Definition: TER.h:119
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
Definition: protocol/Fees.h:49
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:39
uint256 key
Definition: Keylet.h:40
NetClock::time_point parentCloseTime
Definition: LedgerHeader.h:42
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:53
ReadView const & view
Definition: Transactor.h:56
beast::Journal const j
Definition: Transactor.h:60
State information when preflighting a tx.
Definition: Transactor.h:32
beast::Journal const j
Definition: Transactor.h:38
T time_since_epoch(T... args)