rippled
Loading...
Searching...
No Matches
Credentials.cpp
1#include <xrpld/app/tx/detail/Credentials.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/ledger/ApplyView.h>
5#include <xrpl/ledger/CredentialHelpers.h>
6#include <xrpl/ledger/View.h>
7#include <xrpl/protocol/Feature.h>
8#include <xrpl/protocol/Indexes.h>
9#include <xrpl/protocol/TxFlags.h>
10
11#include <chrono>
12
13namespace ripple {
14
15/*
16 Credentials
17 ======
18
19 A verifiable credentials (VC
20 https://en.wikipedia.org/wiki/Verifiable_credentials), as defined by the W3C
21 specification (https://www.w3.org/TR/vc-data-model-2.0/), is a
22 secure and tamper-evident way to represent information about a subject, such
23 as an individual, organization, or even an IoT device. These credentials are
24 issued by a trusted entity and can be verified by third parties without
25 directly involving the issuer at all.
26*/
27
28using namespace credentials;
29
30// ------- CREATE --------------------------
31
34{
35 // 0 means "Allow any flags"
36 return ctx.rules.enabled(fixInvalidTxFlags) ? tfUniversalMask : 0;
37}
38
41{
42 auto const& tx = ctx.tx;
43 auto& j = ctx.j;
44
45 if (!tx[sfSubject])
46 {
47 JLOG(j.trace()) << "Malformed transaction: Invalid Subject";
48 return temMALFORMED;
49 }
50
51 auto const uri = tx[~sfURI];
52 if (uri && (uri->empty() || (uri->size() > maxCredentialURILength)))
53 {
54 JLOG(j.trace()) << "Malformed transaction: invalid size of URI.";
55 return temMALFORMED;
56 }
57
58 auto const credType = tx[sfCredentialType];
59 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
60 {
61 JLOG(j.trace())
62 << "Malformed transaction: invalid size of CredentialType.";
63 return temMALFORMED;
64 }
65
66 return tesSUCCESS;
67}
68
69TER
71{
72 auto const credType(ctx.tx[sfCredentialType]);
73 auto const subject = ctx.tx[sfSubject];
74
75 if (!ctx.view.exists(keylet::account(subject)))
76 {
77 JLOG(ctx.j.trace()) << "Subject doesn't exist.";
78 return tecNO_TARGET;
79 }
80
81 if (ctx.view.exists(
82 keylet::credential(subject, ctx.tx[sfAccount], credType)))
83 {
84 JLOG(ctx.j.trace()) << "Credential already exists.";
85 return tecDUPLICATE;
86 }
87
88 return tesSUCCESS;
89}
90
91TER
93{
94 auto const subject = ctx_.tx[sfSubject];
95 auto const credType(ctx_.tx[sfCredentialType]);
96 Keylet const credentialKey =
97 keylet::credential(subject, account_, credType);
98
99 auto const sleCred = std::make_shared<SLE>(credentialKey);
100 if (!sleCred)
101 return tefINTERNAL; // LCOV_EXCL_LINE
102
103 auto const optExp = ctx_.tx[~sfExpiration];
104 if (optExp)
105 {
106 std::uint32_t const closeTime =
108
109 if (closeTime > *optExp)
110 {
111 JLOG(j_.trace()) << "Malformed transaction: "
112 "Expiration time is in the past.";
113 return tecEXPIRED;
114 }
115
116 sleCred->setFieldU32(sfExpiration, ctx_.tx.getFieldU32(sfExpiration));
117 }
118
119 auto const sleIssuer = view().peek(keylet::account(account_));
120 if (!sleIssuer)
121 return tefINTERNAL; // LCOV_EXCL_LINE
122
123 {
124 STAmount const reserve{view().fees().accountReserve(
125 sleIssuer->getFieldU32(sfOwnerCount) + 1)};
126 if (mPriorBalance < reserve)
128 }
129
130 sleCred->setAccountID(sfSubject, subject);
131 sleCred->setAccountID(sfIssuer, account_);
132 sleCred->setFieldVL(sfCredentialType, credType);
133
134 if (ctx_.tx.isFieldPresent(sfURI))
135 sleCred->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI));
136
137 {
138 auto const page = view().dirInsert(
140 credentialKey,
142 JLOG(j_.trace()) << "Adding Credential to owner directory "
143 << to_string(credentialKey.key) << ": "
144 << (page ? "success" : "failure");
145 if (!page)
146 return tecDIR_FULL;
147 sleCred->setFieldU64(sfIssuerNode, *page);
148
149 adjustOwnerCount(view(), sleIssuer, 1, j_);
150 }
151
152 if (subject == account_)
153 {
154 sleCred->setFieldU32(sfFlags, lsfAccepted);
155 }
156 else
157 {
158 auto const page = view().dirInsert(
159 keylet::ownerDir(subject),
160 credentialKey,
161 describeOwnerDir(subject));
162 JLOG(j_.trace()) << "Adding Credential to owner directory "
163 << to_string(credentialKey.key) << ": "
164 << (page ? "success" : "failure");
165 if (!page)
166 return tecDIR_FULL;
167 sleCred->setFieldU64(sfSubjectNode, *page);
168 view().update(view().peek(keylet::account(subject)));
169 }
170
171 view().insert(sleCred);
172
173 return tesSUCCESS;
174}
175
176// ------- DELETE --------------------------
177
180{
181 // 0 means "Allow any flags"
182 return ctx.rules.enabled(fixInvalidTxFlags) ? tfUniversalMask : 0;
183}
184
185NotTEC
187{
188 auto const subject = ctx.tx[~sfSubject];
189 auto const issuer = ctx.tx[~sfIssuer];
190
191 if (!subject && !issuer)
192 {
193 // Neither field is present, the transaction is malformed.
194 JLOG(ctx.j.trace()) << "Malformed transaction: "
195 "No Subject or Issuer fields.";
196 return temMALFORMED;
197 }
198
199 // Make sure that the passed account is valid.
200 if ((subject && subject->isZero()) || (issuer && issuer->isZero()))
201 {
202 JLOG(ctx.j.trace()) << "Malformed transaction: Subject or Issuer "
203 "field zeroed.";
205 }
206
207 auto const credType = ctx.tx[sfCredentialType];
208 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
209 {
210 JLOG(ctx.j.trace())
211 << "Malformed transaction: invalid size of CredentialType.";
212 return temMALFORMED;
213 }
214
215 return tesSUCCESS;
216}
217
218TER
220{
221 AccountID const account{ctx.tx[sfAccount]};
222 auto const subject = ctx.tx[~sfSubject].value_or(account);
223 auto const issuer = ctx.tx[~sfIssuer].value_or(account);
224 auto const credType(ctx.tx[sfCredentialType]);
225
226 if (!ctx.view.exists(keylet::credential(subject, issuer, credType)))
227 return tecNO_ENTRY;
228
229 return tesSUCCESS;
230}
231
232TER
234{
235 auto const subject = ctx_.tx[~sfSubject].value_or(account_);
236 auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
237
238 auto const credType(ctx_.tx[sfCredentialType]);
239 auto const sleCred =
240 view().peek(keylet::credential(subject, issuer, credType));
241 if (!sleCred)
242 return tefINTERNAL; // LCOV_EXCL_LINE
243
244 if ((subject != account_) && (issuer != account_) &&
246 {
247 JLOG(j_.trace()) << "Can't delete non-expired credential.";
248 return tecNO_PERMISSION;
249 }
250
251 return deleteSLE(view(), sleCred, j_);
252}
253
254// ------- APPLY --------------------------
255
258{
259 // 0 means "Allow any flags"
260 return ctx.rules.enabled(fixInvalidTxFlags) ? tfUniversalMask : 0;
261}
262
263NotTEC
265{
266 if (!ctx.tx[sfIssuer])
267 {
268 JLOG(ctx.j.trace()) << "Malformed transaction: Issuer field zeroed.";
270 }
271
272 auto const credType = ctx.tx[sfCredentialType];
273 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
274 {
275 JLOG(ctx.j.trace())
276 << "Malformed transaction: invalid size of CredentialType.";
277 return temMALFORMED;
278 }
279
280 return tesSUCCESS;
281}
282
283TER
285{
286 AccountID const subject = ctx.tx[sfAccount];
287 AccountID const issuer = ctx.tx[sfIssuer];
288 auto const credType(ctx.tx[sfCredentialType]);
289
290 if (!ctx.view.exists(keylet::account(issuer)))
291 {
292 JLOG(ctx.j.warn()) << "No issuer: " << to_string(issuer);
293 return tecNO_ISSUER;
294 }
295
296 auto const sleCred =
297 ctx.view.read(keylet::credential(subject, issuer, credType));
298 if (!sleCred)
299 {
300 JLOG(ctx.j.warn()) << "No credential: " << to_string(subject) << ", "
301 << to_string(issuer) << ", " << credType;
302 return tecNO_ENTRY;
303 }
304
305 if (sleCred->getFieldU32(sfFlags) & lsfAccepted)
306 {
307 JLOG(ctx.j.warn()) << "Credential already accepted: "
308 << to_string(subject) << ", " << to_string(issuer)
309 << ", " << credType;
310 return tecDUPLICATE;
311 }
312
313 return tesSUCCESS;
314}
315
316TER
318{
319 AccountID const issuer{ctx_.tx[sfIssuer]};
320
321 // Both exist as credential object exist itself (checked in preclaim)
322 auto const sleSubject = view().peek(keylet::account(account_));
323 auto const sleIssuer = view().peek(keylet::account(issuer));
324
325 if (!sleSubject || !sleIssuer)
326 return tefINTERNAL; // LCOV_EXCL_LINE
327
328 {
329 STAmount const reserve{view().fees().accountReserve(
330 sleSubject->getFieldU32(sfOwnerCount) + 1)};
331 if (mPriorBalance < reserve)
333 }
334
335 auto const credType(ctx_.tx[sfCredentialType]);
336 Keylet const credentialKey = keylet::credential(account_, issuer, credType);
337 auto const sleCred = view().peek(credentialKey); // Checked in preclaim()
338
339 if (checkExpired(sleCred, view().info().parentCloseTime))
340 {
341 JLOG(j_.trace()) << "Credential is expired: " << sleCred->getText();
342 // delete expired credentials even if the transaction failed
343 auto const err = credentials::deleteSLE(view(), sleCred, j_);
344 return isTesSuccess(err) ? tecEXPIRED : err;
345 }
346
347 sleCred->setFieldU32(sfFlags, lsfAccepted);
348 view().update(sleCred);
349
350 adjustOwnerCount(view(), sleIssuer, -1, j_);
351 adjustOwnerCount(view(), sleSubject, 1, j_);
352
353 return tesSUCCESS;
354}
355
356} // namespace ripple
Stream trace() const
Severity stream access functions.
Definition Journal.h:303
Stream warn() const
Definition Journal.h:321
ApplyView & view()
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:300
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 std::uint32_t getFlagsMask(PreflightContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
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:111
Blob getFieldVL(SField const &field) const
Definition STObject.cpp:644
std::uint32_t getFieldU32(SField const &field) const
Definition STObject.cpp:596
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:465
AccountID const account_
Definition Transactor.h:128
ApplyView & view()
Definition Transactor.h:144
beast::Journal const j_
Definition Transactor.h:126
XRPAmount mPriorBalance
Definition Transactor.h:129
ApplyContext & ctx_
Definition Transactor.h:124
T is_same_v
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:534
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:355
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
std::size_t constexpr maxCredentialURILength
The maximum length of a URI inside a Credential.
Definition Protocol.h:84
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1013
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1031
@ tefINTERNAL
Definition TER.h:154
std::size_t constexpr maxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition Protocol.h:87
@ tecNO_ENTRY
Definition TER.h:288
@ tecNO_ISSUER
Definition TER.h:281
@ tecNO_TARGET
Definition TER.h:286
@ tecDIR_FULL
Definition TER.h:269
@ tecDUPLICATE
Definition TER.h:297
@ tecNO_PERMISSION
Definition TER.h:287
@ tecINSUFFICIENT_RESERVE
Definition TER.h:289
@ tecEXPIRED
Definition TER.h:296
@ tesSUCCESS
Definition TER.h:226
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
constexpr std::uint32_t tfUniversalMask
Definition TxFlags.h:44
@ temMALFORMED
Definition TER.h:68
@ temINVALID_ACCOUNT_ID
Definition TER.h:100
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
NetClock::time_point parentCloseTime
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
beast::Journal const j
Definition Transactor.h:69
State information when preflighting a tx.
Definition Transactor.h:16
beast::Journal const j
Definition Transactor.h:23
T time_since_epoch(T... args)