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 xrpl {
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()) << "Malformed transaction: invalid size of CredentialType.";
62 return temMALFORMED;
63 }
64
65 return tesSUCCESS;
66}
67
68TER
70{
71 auto const credType(ctx.tx[sfCredentialType]);
72 auto const subject = ctx.tx[sfSubject];
73
74 if (!ctx.view.exists(keylet::account(subject)))
75 {
76 JLOG(ctx.j.trace()) << "Subject doesn't exist.";
77 return tecNO_TARGET;
78 }
79
80 if (ctx.view.exists(keylet::credential(subject, ctx.tx[sfAccount], credType)))
81 {
82 JLOG(ctx.j.trace()) << "Credential already exists.";
83 return tecDUPLICATE;
84 }
85
86 return tesSUCCESS;
87}
88
89TER
91{
92 auto const subject = ctx_.tx[sfSubject];
93 auto const credType(ctx_.tx[sfCredentialType]);
94 Keylet const credentialKey = keylet::credential(subject, account_, credType);
95
96 auto const sleCred = std::make_shared<SLE>(credentialKey);
97 if (!sleCred)
98 return tefINTERNAL; // LCOV_EXCL_LINE
99
100 auto const optExp = ctx_.tx[~sfExpiration];
101 if (optExp)
102 {
103 std::uint32_t const closeTime = ctx_.view().header().parentCloseTime.time_since_epoch().count();
104
105 if (closeTime > *optExp)
106 {
107 JLOG(j_.trace()) << "Malformed transaction: "
108 "Expiration time is in the past.";
109 return tecEXPIRED;
110 }
111
112 sleCred->setFieldU32(sfExpiration, ctx_.tx.getFieldU32(sfExpiration));
113 }
114
115 auto const sleIssuer = view().peek(keylet::account(account_));
116 if (!sleIssuer)
117 return tefINTERNAL; // LCOV_EXCL_LINE
118
119 {
120 STAmount const reserve{view().fees().accountReserve(sleIssuer->getFieldU32(sfOwnerCount) + 1)};
121 if (mPriorBalance < reserve)
123 }
124
125 sleCred->setAccountID(sfSubject, subject);
126 sleCred->setAccountID(sfIssuer, account_);
127 sleCred->setFieldVL(sfCredentialType, credType);
128
129 if (ctx_.tx.isFieldPresent(sfURI))
130 sleCred->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI));
131
132 {
133 auto const page = view().dirInsert(keylet::ownerDir(account_), credentialKey, describeOwnerDir(account_));
134 JLOG(j_.trace()) << "Adding Credential to owner directory " << to_string(credentialKey.key) << ": "
135 << (page ? "success" : "failure");
136 if (!page)
137 return tecDIR_FULL;
138 sleCred->setFieldU64(sfIssuerNode, *page);
139
140 adjustOwnerCount(view(), sleIssuer, 1, j_);
141 }
142
143 if (subject == account_)
144 {
145 sleCred->setFieldU32(sfFlags, lsfAccepted);
146 }
147 else
148 {
149 auto const page = view().dirInsert(keylet::ownerDir(subject), credentialKey, describeOwnerDir(subject));
150 JLOG(j_.trace()) << "Adding Credential to owner directory " << to_string(credentialKey.key) << ": "
151 << (page ? "success" : "failure");
152 if (!page)
153 return tecDIR_FULL;
154 sleCred->setFieldU64(sfSubjectNode, *page);
155 view().update(view().peek(keylet::account(subject)));
156 }
157
158 view().insert(sleCred);
159
160 return tesSUCCESS;
161}
162
163// ------- DELETE --------------------------
164
167{
168 // 0 means "Allow any flags"
169 return ctx.rules.enabled(fixInvalidTxFlags) ? tfUniversalMask : 0;
170}
171
172NotTEC
174{
175 auto const subject = ctx.tx[~sfSubject];
176 auto const issuer = ctx.tx[~sfIssuer];
177
178 if (!subject && !issuer)
179 {
180 // Neither field is present, the transaction is malformed.
181 JLOG(ctx.j.trace()) << "Malformed transaction: "
182 "No Subject or Issuer fields.";
183 return temMALFORMED;
184 }
185
186 // Make sure that the passed account is valid.
187 if ((subject && subject->isZero()) || (issuer && issuer->isZero()))
188 {
189 JLOG(ctx.j.trace()) << "Malformed transaction: Subject or Issuer "
190 "field zeroed.";
192 }
193
194 auto const credType = ctx.tx[sfCredentialType];
195 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
196 {
197 JLOG(ctx.j.trace()) << "Malformed transaction: invalid size of CredentialType.";
198 return temMALFORMED;
199 }
200
201 return tesSUCCESS;
202}
203
204TER
206{
207 AccountID const account{ctx.tx[sfAccount]};
208 auto const subject = ctx.tx[~sfSubject].value_or(account);
209 auto const issuer = ctx.tx[~sfIssuer].value_or(account);
210 auto const credType(ctx.tx[sfCredentialType]);
211
212 if (!ctx.view.exists(keylet::credential(subject, issuer, credType)))
213 return tecNO_ENTRY;
214
215 return tesSUCCESS;
216}
217
218TER
220{
221 auto const subject = ctx_.tx[~sfSubject].value_or(account_);
222 auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
223
224 auto const credType(ctx_.tx[sfCredentialType]);
225 auto const sleCred = view().peek(keylet::credential(subject, issuer, credType));
226 if (!sleCred)
227 return tefINTERNAL; // LCOV_EXCL_LINE
228
229 if ((subject != account_) && (issuer != account_) && !checkExpired(sleCred, ctx_.view().header().parentCloseTime))
230 {
231 JLOG(j_.trace()) << "Can't delete non-expired credential.";
232 return tecNO_PERMISSION;
233 }
234
235 return deleteSLE(view(), sleCred, j_);
236}
237
238// ------- APPLY --------------------------
239
242{
243 // 0 means "Allow any flags"
244 return ctx.rules.enabled(fixInvalidTxFlags) ? tfUniversalMask : 0;
245}
246
247NotTEC
249{
250 if (!ctx.tx[sfIssuer])
251 {
252 JLOG(ctx.j.trace()) << "Malformed transaction: Issuer field zeroed.";
254 }
255
256 auto const credType = ctx.tx[sfCredentialType];
257 if (credType.empty() || (credType.size() > maxCredentialTypeLength))
258 {
259 JLOG(ctx.j.trace()) << "Malformed transaction: invalid size of CredentialType.";
260 return temMALFORMED;
261 }
262
263 return tesSUCCESS;
264}
265
266TER
268{
269 AccountID const subject = ctx.tx[sfAccount];
270 AccountID const issuer = ctx.tx[sfIssuer];
271 auto const credType(ctx.tx[sfCredentialType]);
272
273 if (!ctx.view.exists(keylet::account(issuer)))
274 {
275 JLOG(ctx.j.warn()) << "No issuer: " << to_string(issuer);
276 return tecNO_ISSUER;
277 }
278
279 auto const sleCred = ctx.view.read(keylet::credential(subject, issuer, credType));
280 if (!sleCred)
281 {
282 JLOG(ctx.j.warn()) << "No credential: " << to_string(subject) << ", " << to_string(issuer) << ", " << credType;
283 return tecNO_ENTRY;
284 }
285
286 if (sleCred->getFieldU32(sfFlags) & lsfAccepted)
287 {
288 JLOG(ctx.j.warn()) << "Credential already accepted: " << to_string(subject) << ", " << to_string(issuer) << ", "
289 << credType;
290 return tecDUPLICATE;
291 }
292
293 return tesSUCCESS;
294}
295
296TER
298{
299 AccountID const issuer{ctx_.tx[sfIssuer]};
300
301 // Both exist as credential object exist itself (checked in preclaim)
302 auto const sleSubject = view().peek(keylet::account(account_));
303 auto const sleIssuer = view().peek(keylet::account(issuer));
304
305 if (!sleSubject || !sleIssuer)
306 return tefINTERNAL; // LCOV_EXCL_LINE
307
308 {
309 STAmount const reserve{view().fees().accountReserve(sleSubject->getFieldU32(sfOwnerCount) + 1)};
310 if (mPriorBalance < reserve)
312 }
313
314 auto const credType(ctx_.tx[sfCredentialType]);
315 Keylet const credentialKey = keylet::credential(account_, issuer, credType);
316 auto const sleCred = view().peek(credentialKey); // Checked in preclaim()
317
318 if (checkExpired(sleCred, view().header().parentCloseTime))
319 {
320 JLOG(j_.trace()) << "Credential is expired: " << sleCred->getText();
321 // delete expired credentials even if the transaction failed
322 auto const err = credentials::deleteSLE(view(), sleCred, j_);
323 return isTesSuccess(err) ? tecEXPIRED : err;
324 }
325
326 sleCred->setFieldU32(sfFlags, lsfAccepted);
327 view().update(sleCred);
328
329 adjustOwnerCount(view(), sleIssuer, -1, j_);
330 adjustOwnerCount(view(), sleSubject, 1, j_);
331
332 return tesSUCCESS;
333}
334
335} // namespace xrpl
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
Stream warn() const
Definition Journal.h:313
STTx const & tx
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:284
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static TER preclaim(PreclaimContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
TER doApply() override
static TER preclaim(PreclaimContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
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 LedgerHeader const & header() const =0
Returns information about the ledger.
virtual std::shared_ptr< SLE const > 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:118
Blob getFieldVL(SField const &field) const
Definition STObject.cpp:624
std::uint32_t getFieldU32(SField const &field) const
Definition STObject.cpp:576
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:439
AccountID const account_
Definition Transactor.h:113
beast::Journal const j_
Definition Transactor.h:111
ApplyView & view()
Definition Transactor.h:129
XRPAmount mPriorBalance
Definition Transactor.h:114
ApplyContext & ctx_
Definition Transactor.h:109
T is_same_v
bool checkExpired(std::shared_ptr< SLE const > const &sleCredential, NetClock::time_point const &closed)
TER deleteSLE(ApplyView &view, std::shared_ptr< SLE > const &sleCredential, beast::Journal j)
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:325
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:160
Keylet credential(AccountID const &subject, AccountID const &issuer, Slice const &credType) noexcept
Definition Indexes.cpp:486
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:218
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:598
@ tefINTERNAL
Definition TER.h:154
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:941
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:955
@ temMALFORMED
Definition TER.h:68
@ temINVALID_ACCOUNT_ID
Definition TER.h:100
bool isTesSuccess(TER x) noexcept
Definition TER.h:650
@ tecDIR_FULL
Definition TER.h:269
@ tecNO_ENTRY
Definition TER.h:288
@ tecNO_TARGET
Definition TER.h:286
@ tecEXPIRED
Definition TER.h:296
@ tecINSUFFICIENT_RESERVE
Definition TER.h:289
@ tecNO_PERMISSION
Definition TER.h:287
@ tecNO_ISSUER
Definition TER.h:281
@ tecDUPLICATE
Definition TER.h:297
@ lsfAccepted
@ tesSUCCESS
Definition TER.h:226
constexpr std::uint32_t tfUniversalMask
Definition TxFlags.h:44
std::size_t constexpr maxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition Protocol.h:221
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:54
ReadView const & view
Definition Transactor.h:57
beast::Journal const j
Definition Transactor.h:62
State information when preflighting a tx.
Definition Transactor.h:16
beast::Journal const j
Definition Transactor.h:23
T time_since_epoch(T... args)