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
23#include <xrpld/core/TimeKeeper.h>
24#include <xrpld/ledger/ApplyView.h>
25#include <xrpld/ledger/View.h>
26#include <xrpl/basics/Log.h>
27#include <xrpl/protocol/Feature.h>
28#include <xrpl/protocol/Indexes.h>
29#include <xrpl/protocol/TxFlags.h>
30#include <xrpl/protocol/st.h>
31
32#include <chrono>
33
34namespace ripple {
35
36/*
37 Credentials
38 ======
39
40 A verifiable credentials (VC
41 https://en.wikipedia.org/wiki/Verifiable_credentials), as defined by the W3C
42 specification (https://www.w3.org/TR/vc-data-model-2.0/), is a
43 secure and tamper-evident way to represent information about a subject, such
44 as an individual, organization, or even an IoT device. These credentials are
45 issued by a trusted entity and can be verified by third parties without
46 directly involving the issuer at all.
47*/
48
49using namespace credentials;
50
51// ------- CREATE --------------------------
52
55{
56 if (!ctx.rules.enabled(featureCredentials))
57 {
58 JLOG(ctx.j.trace()) << "featureCredentials is disabled.";
59 return temDISABLED;
60 }
61
62 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
63 return ret;
64
65 auto const& tx = ctx.tx;
66 auto& j = ctx.j;
67
68 if (!tx[sfSubject])
69 {
70 JLOG(j.trace()) << "Malformed transaction: Invalid Subject";
71 return temMALFORMED;
72 }
73
74 auto const uri = tx[~sfURI];
75 if (uri && (uri->empty() || (uri->size() > maxCredentialURILength)))
76 {
77 JLOG(j.trace()) << "Malformed transaction: invalid size of URI.";
78 return temMALFORMED;
79 }
80
81 auto const credType = tx[sfCredentialType];
82 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
83 {
84 JLOG(j.trace())
85 << "Malformed transaction: invalid size of CredentialType.";
86 return temMALFORMED;
87 }
88
89 return preflight2(ctx);
90}
91
92TER
94{
95 auto const credType(ctx.tx[sfCredentialType]);
96 auto const subject = ctx.tx[sfSubject];
97
98 if (!ctx.view.exists(keylet::account(subject)))
99 {
100 JLOG(ctx.j.trace()) << "Subject doesn't exist.";
101 return tecNO_TARGET;
102 }
103
104 if (ctx.view.exists(
105 keylet::credential(subject, ctx.tx[sfAccount], credType)))
106 {
107 JLOG(ctx.j.trace()) << "Credential already exists.";
108 return tecDUPLICATE;
109 }
110
111 return tesSUCCESS;
112}
113
114TER
116{
117 auto const subject = ctx_.tx[sfSubject];
118 auto const credType(ctx_.tx[sfCredentialType]);
119 Keylet const credentialKey =
120 keylet::credential(subject, account_, credType);
121
122 auto const sleCred = std::make_shared<SLE>(credentialKey);
123 if (!sleCred)
124 return tefINTERNAL;
125
126 auto const optExp = ctx_.tx[~sfExpiration];
127 if (optExp)
128 {
129 std::uint32_t const closeTime =
131
132 if (closeTime > *optExp)
133 {
134 JLOG(j_.trace()) << "Malformed transaction: "
135 "Expiration time is in the past.";
136 return tecEXPIRED;
137 }
138
139 sleCred->setFieldU32(sfExpiration, ctx_.tx.getFieldU32(sfExpiration));
140 }
141
142 auto const sleIssuer = view().peek(keylet::account(account_));
143 if (!sleIssuer)
144 return tefINTERNAL;
145
146 {
147 STAmount const reserve{view().fees().accountReserve(
148 sleIssuer->getFieldU32(sfOwnerCount) + 1)};
149 if (mPriorBalance < reserve)
151 }
152
153 sleCred->setAccountID(sfSubject, subject);
154 sleCred->setAccountID(sfIssuer, account_);
155 sleCred->setFieldVL(sfCredentialType, credType);
156
157 if (ctx_.tx.isFieldPresent(sfURI))
158 sleCred->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI));
159
160 {
161 auto const page = view().dirInsert(
163 credentialKey,
165 JLOG(j_.trace()) << "Adding Credential to owner directory "
166 << to_string(credentialKey.key) << ": "
167 << (page ? "success" : "failure");
168 if (!page)
169 return tecDIR_FULL;
170 sleCred->setFieldU64(sfIssuerNode, *page);
171
172 adjustOwnerCount(view(), sleIssuer, 1, j_);
173 }
174
175 if (subject == account_)
176 {
177 sleCred->setFieldU32(sfFlags, lsfAccepted);
178 }
179 else
180 {
181 auto const page = view().dirInsert(
182 keylet::ownerDir(subject),
183 credentialKey,
184 describeOwnerDir(subject));
185 JLOG(j_.trace()) << "Adding Credential to owner directory "
186 << to_string(credentialKey.key) << ": "
187 << (page ? "success" : "failure");
188 if (!page)
189 return tecDIR_FULL;
190 sleCred->setFieldU64(sfSubjectNode, *page);
191 view().update(view().peek(keylet::account(subject)));
192 }
193
194 view().insert(sleCred);
195
196 return tesSUCCESS;
197}
198
199// ------- DELETE --------------------------
200NotTEC
202{
203 if (!ctx.rules.enabled(featureCredentials))
204 {
205 JLOG(ctx.j.trace()) << "featureCredentials is disabled.";
206 return temDISABLED;
207 }
208
209 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
210 return ret;
211
212 auto const subject = ctx.tx[~sfSubject];
213 auto const issuer = ctx.tx[~sfIssuer];
214
215 if (!subject && !issuer)
216 {
217 // Neither field is present, the transaction is malformed.
218 JLOG(ctx.j.trace()) << "Malformed transaction: "
219 "No Subject or Issuer fields.";
220 return temMALFORMED;
221 }
222
223 // Make sure that the passed account is valid.
224 if ((subject && subject->isZero()) || (issuer && issuer->isZero()))
225 {
226 JLOG(ctx.j.trace()) << "Malformed transaction: Subject or Issuer "
227 "field zeroed.";
229 }
230
231 auto const credType = ctx.tx[sfCredentialType];
232 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
233 {
234 JLOG(ctx.j.trace())
235 << "Malformed transaction: invalid size of CredentialType.";
236 return temMALFORMED;
237 }
238
239 return preflight2(ctx);
240}
241
242TER
244{
245 AccountID const account{ctx.tx[sfAccount]};
246 auto const subject = ctx.tx[~sfSubject].value_or(account);
247 auto const issuer = ctx.tx[~sfIssuer].value_or(account);
248 auto const credType(ctx.tx[sfCredentialType]);
249
250 if (!ctx.view.exists(keylet::credential(subject, issuer, credType)))
251 return tecNO_ENTRY;
252
253 return tesSUCCESS;
254}
255
256TER
258{
259 auto const subject = ctx_.tx[~sfSubject].value_or(account_);
260 auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
261
262 auto const credType(ctx_.tx[sfCredentialType]);
263 auto const sleCred =
264 view().peek(keylet::credential(subject, issuer, credType));
265 if (!sleCred)
266 return tefINTERNAL;
267
268 if ((subject != account_) && (issuer != account_) &&
270 {
271 JLOG(j_.trace()) << "Can't delete non-expired credential.";
272 return tecNO_PERMISSION;
273 }
274
275 return deleteSLE(view(), sleCred, j_);
276}
277
278// ------- APPLY --------------------------
279
280NotTEC
282{
283 if (!ctx.rules.enabled(featureCredentials))
284 {
285 JLOG(ctx.j.trace()) << "featureCredentials is disabled.";
286 return temDISABLED;
287 }
288
289 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
290 return ret;
291
292 if (!ctx.tx[sfIssuer])
293 {
294 JLOG(ctx.j.trace()) << "Malformed transaction: Issuer field zeroed.";
296 }
297
298 auto const credType = ctx.tx[sfCredentialType];
299 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
300 {
301 JLOG(ctx.j.trace())
302 << "Malformed transaction: invalid size of CredentialType.";
303 return temMALFORMED;
304 }
305
306 return preflight2(ctx);
307}
308
309TER
311{
312 AccountID const subject = ctx.tx[sfAccount];
313 AccountID const issuer = ctx.tx[sfIssuer];
314 auto const credType(ctx.tx[sfCredentialType]);
315
316 if (!ctx.view.exists(keylet::account(issuer)))
317 {
318 JLOG(ctx.j.warn()) << "No issuer: " << to_string(issuer);
319 return tecNO_ISSUER;
320 }
321
322 auto const sleCred =
323 ctx.view.read(keylet::credential(subject, issuer, credType));
324 if (!sleCred)
325 {
326 JLOG(ctx.j.warn()) << "No credential: " << to_string(subject) << ", "
327 << to_string(issuer) << ", " << credType;
328 return tecNO_ENTRY;
329 }
330
331 if (sleCred->getFieldU32(sfFlags) & lsfAccepted)
332 {
333 JLOG(ctx.j.warn()) << "Credential already accepted: "
334 << to_string(subject) << ", " << to_string(issuer)
335 << ", " << credType;
336 return tecDUPLICATE;
337 }
338
339 return tesSUCCESS;
340}
341
342TER
344{
345 AccountID const issuer{ctx_.tx[sfIssuer]};
346
347 // Both exist as credential object exist itself (checked in preclaim)
348 auto const sleSubject = view().peek(keylet::account(account_));
349 auto const sleIssuer = view().peek(keylet::account(issuer));
350
351 if (!sleSubject || !sleIssuer)
352 return tefINTERNAL;
353
354 {
355 STAmount const reserve{view().fees().accountReserve(
356 sleSubject->getFieldU32(sfOwnerCount) + 1)};
357 if (mPriorBalance < reserve)
359 }
360
361 auto const credType(ctx_.tx[sfCredentialType]);
362 Keylet const credentialKey = keylet::credential(account_, issuer, credType);
363 auto const sleCred = view().peek(credentialKey); // Checked in preclaim()
364
365 if (checkExpired(sleCred, view().info().parentCloseTime))
366 {
367 JLOG(j_.trace()) << "Credential is expired: " << sleCred->getText();
368 // delete expired credentials even if the transaction failed
369 auto const err = credentials::deleteSLE(view(), sleCred, j_);
370 return isTesSuccess(err) ? tecEXPIRED : err;
371 }
372
373 sleCred->setFieldU32(sfFlags, lsfAccepted);
374 view().update(sleCred);
375
376 adjustOwnerCount(view(), sleIssuer, -1, j_);
377 adjustOwnerCount(view(), sleSubject, 1, j_);
378
379 return tesSUCCESS;
380}
381
382} // namespace ripple
Stream trace() const
Severity stream access functions.
Definition: Journal.h:311
Stream warn() const
Definition: Journal.h:329
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:93
static NotTEC preflight(PreflightContext const &ctx)
Definition: Credentials.cpp:54
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:122
Blob getFieldVL(SField const &field) const
Definition: STObject.cpp:627
std::uint32_t getFieldU32(SField const &field) const
Definition: STObject.cpp:585
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:454
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:521
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:160
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:350
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:100
bool isTesSuccess(TER x)
Definition: TER.h:656
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition: View.cpp:925
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:82
@ tefINTERNAL
Definition: TER.h:173
static bool adjustOwnerCount(ApplyContext &ctx, int count)
Definition: SetOracle.cpp:186
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:134
std::size_t constexpr maxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition: Protocol.h:103
@ 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:629
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:587
@ temMALFORMED
Definition: TER.h:87
@ 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)