dvlprsh-dcql-test 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/dist/index.d.mts +135 -0
- package/dist/index.d.ts +135 -0
- package/dist/index.js +341 -0
- package/dist/index.mjs +314 -0
- package/package.json +59 -0
- package/src/credentials/credential.ts +12 -0
- package/src/credentials/sdjwtvc.credential.ts +208 -0
- package/src/dcql.ts +170 -0
- package/src/index.ts +4 -0
- package/src/match.ts +88 -0
- package/src/test/dcql.spec.ts +295 -0
- package/src/test/index.spec.ts +7 -0
- package/src/test/pathMatch.spec.ts +163 -0
- package/src/test/sdjwtvc-credentials.spec.ts +259 -0
- package/src/type.ts +65 -0
- package/tsconfig.json +9 -0
- package/vitest.config.mts +4 -0
package/dist/index.mjs
ADDED
@@ -0,0 +1,314 @@
|
|
1
|
+
var __defProp = Object.defineProperty;
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
3
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
4
|
+
var __publicField = (obj, key, value) => {
|
5
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
6
|
+
return value;
|
7
|
+
};
|
8
|
+
|
9
|
+
// src/match.ts
|
10
|
+
var pathMatch = /* @__PURE__ */ __name((path, data) => {
|
11
|
+
let selectedElements = [
|
12
|
+
data
|
13
|
+
];
|
14
|
+
for (const component of path) {
|
15
|
+
const nextSelectedElements = [];
|
16
|
+
for (const element of selectedElements) {
|
17
|
+
if (typeof component === "string") {
|
18
|
+
if (element === null || typeof element !== "object" || Array.isArray(element)) {
|
19
|
+
throw new Error("Path component requires object but found non-object element");
|
20
|
+
}
|
21
|
+
if (component in element) {
|
22
|
+
nextSelectedElements.push(element[component]);
|
23
|
+
}
|
24
|
+
} else if (component === null) {
|
25
|
+
if (element === null || !Array.isArray(element)) {
|
26
|
+
throw new Error("Null path component requires array but found non-array element");
|
27
|
+
}
|
28
|
+
nextSelectedElements.push(...element);
|
29
|
+
} else if (typeof component === "number" && component >= 0 && Number.isInteger(component)) {
|
30
|
+
if (element === null || !Array.isArray(element)) {
|
31
|
+
throw new Error("Numeric path component requires array but found non-array element");
|
32
|
+
}
|
33
|
+
if (component < element.length) {
|
34
|
+
nextSelectedElements.push(element[component]);
|
35
|
+
}
|
36
|
+
} else {
|
37
|
+
throw new Error(`Invalid path component: ${component}`);
|
38
|
+
}
|
39
|
+
}
|
40
|
+
if (nextSelectedElements.length === 0) {
|
41
|
+
throw new Error("No elements selected after processing path component");
|
42
|
+
}
|
43
|
+
selectedElements = nextSelectedElements;
|
44
|
+
}
|
45
|
+
return selectedElements;
|
46
|
+
}, "pathMatch");
|
47
|
+
|
48
|
+
// src/credentials/sdjwtvc.credential.ts
|
49
|
+
var _SdJwtVcCredential = class _SdJwtVcCredential {
|
50
|
+
constructor(id, vct_values, options) {
|
51
|
+
__publicField(this, "id");
|
52
|
+
__publicField(this, "vct_values");
|
53
|
+
__publicField(this, "_multiple");
|
54
|
+
__publicField(this, "_trusted_authorities");
|
55
|
+
__publicField(this, "_require_cryptographic_holder_binding");
|
56
|
+
__publicField(this, "_claims");
|
57
|
+
__publicField(this, "_claim_sets");
|
58
|
+
this.id = id;
|
59
|
+
this.vct_values = vct_values;
|
60
|
+
if (options) {
|
61
|
+
this._multiple = options.multiple;
|
62
|
+
this._trusted_authorities = options.trusted_authorities;
|
63
|
+
this._require_cryptographic_holder_binding = options.require_cryptographic_holder_binding;
|
64
|
+
this._claims = options.claims;
|
65
|
+
this._claim_sets = options.claim_sets;
|
66
|
+
}
|
67
|
+
}
|
68
|
+
setMultiple(multiple) {
|
69
|
+
this._multiple = multiple;
|
70
|
+
return this;
|
71
|
+
}
|
72
|
+
setTrustedAuthorities(trusted_authorities) {
|
73
|
+
this._trusted_authorities = trusted_authorities;
|
74
|
+
return this;
|
75
|
+
}
|
76
|
+
setRequireCryptographicHolderBinding(require_cryptographic_holder_binding) {
|
77
|
+
this._require_cryptographic_holder_binding = require_cryptographic_holder_binding;
|
78
|
+
return this;
|
79
|
+
}
|
80
|
+
setClaims(claims) {
|
81
|
+
this._claims = claims;
|
82
|
+
return this;
|
83
|
+
}
|
84
|
+
setClaimSets(claim_sets) {
|
85
|
+
if (!this._claims || this._claims.length === 0) {
|
86
|
+
throw new Error("Claims must be set before claim sets");
|
87
|
+
}
|
88
|
+
if (!this._claims.every((c) => c.id !== void 0)) {
|
89
|
+
throw new Error("Claims must not have an id before claim sets");
|
90
|
+
}
|
91
|
+
this._claim_sets = claim_sets;
|
92
|
+
return this;
|
93
|
+
}
|
94
|
+
serialize() {
|
95
|
+
return {
|
96
|
+
id: this.id,
|
97
|
+
format: "dc+sd-jwt",
|
98
|
+
meta: {
|
99
|
+
vct_values: this.vct_values
|
100
|
+
},
|
101
|
+
multiple: this._multiple,
|
102
|
+
trusted_authorities: this._trusted_authorities,
|
103
|
+
require_cryptographic_holder_binding: this._require_cryptographic_holder_binding,
|
104
|
+
claims: this._claims,
|
105
|
+
claim_sets: this._claim_sets
|
106
|
+
};
|
107
|
+
}
|
108
|
+
match(data) {
|
109
|
+
if (data["vct"] !== void 0 && !this.vct_values.includes(data["vct"])) {
|
110
|
+
return {
|
111
|
+
match: false
|
112
|
+
};
|
113
|
+
}
|
114
|
+
if (!this._claims || this._claims.length === 0) {
|
115
|
+
return {
|
116
|
+
match: true,
|
117
|
+
matchedClaims: []
|
118
|
+
};
|
119
|
+
}
|
120
|
+
if (this._claim_sets && this._claim_sets.length > 0) {
|
121
|
+
for (const claimSet of this._claim_sets) {
|
122
|
+
const claimsInSet = this._claims.filter((claim) => claim.id !== void 0 && claimSet.includes(claim.id));
|
123
|
+
const allClaimsMatch = claimsInSet.every((claim) => this.matchClaim(claim, data));
|
124
|
+
if (allClaimsMatch && claimsInSet.length > 0) {
|
125
|
+
return {
|
126
|
+
match: true,
|
127
|
+
matchedClaims: claimsInSet
|
128
|
+
};
|
129
|
+
}
|
130
|
+
}
|
131
|
+
return {
|
132
|
+
match: false
|
133
|
+
};
|
134
|
+
} else {
|
135
|
+
const satisfiableClaims = this._claims.every((claim) => this.matchClaim(claim, data));
|
136
|
+
if (satisfiableClaims) {
|
137
|
+
return {
|
138
|
+
match: true,
|
139
|
+
matchedClaims: this._claims
|
140
|
+
};
|
141
|
+
}
|
142
|
+
}
|
143
|
+
return {
|
144
|
+
match: false
|
145
|
+
};
|
146
|
+
}
|
147
|
+
/**
|
148
|
+
* Helper method to check if a specific claim matches against the data
|
149
|
+
* Implements semantics for JSON-based credentials as specified in section 7.1
|
150
|
+
*/
|
151
|
+
matchClaim(claim, data) {
|
152
|
+
try {
|
153
|
+
const selectedElements = pathMatch(claim.path, data);
|
154
|
+
if (selectedElements.length === 0) {
|
155
|
+
return false;
|
156
|
+
}
|
157
|
+
if (!claim.value || claim.value.length === 0) {
|
158
|
+
return true;
|
159
|
+
}
|
160
|
+
return selectedElements.some((element) => claim.value.some((val) => val === element));
|
161
|
+
} catch (error) {
|
162
|
+
return false;
|
163
|
+
}
|
164
|
+
}
|
165
|
+
static parseSdJwtCredential(c) {
|
166
|
+
if (!this.validateCredential(c)) {
|
167
|
+
throw new Error("Invalid credential");
|
168
|
+
}
|
169
|
+
const sdJwtVcCredential = new _SdJwtVcCredential(c.id, c.meta.vct_values, {
|
170
|
+
multiple: c.multiple,
|
171
|
+
trusted_authorities: c.trusted_authorities,
|
172
|
+
require_cryptographic_holder_binding: c.require_cryptographic_holder_binding,
|
173
|
+
claims: c.claims,
|
174
|
+
claim_sets: c.claim_sets
|
175
|
+
});
|
176
|
+
return sdJwtVcCredential;
|
177
|
+
}
|
178
|
+
static validateCredential(c) {
|
179
|
+
if (c.format !== "dc+sd-jwt") {
|
180
|
+
throw new Error("Invalid credential format");
|
181
|
+
}
|
182
|
+
if (!c.meta || !("vct_values" in c.meta)) {
|
183
|
+
throw new Error("Invalid credential meta");
|
184
|
+
}
|
185
|
+
return true;
|
186
|
+
}
|
187
|
+
};
|
188
|
+
__name(_SdJwtVcCredential, "SdJwtVcCredential");
|
189
|
+
var SdJwtVcCredential = _SdJwtVcCredential;
|
190
|
+
|
191
|
+
// src/dcql.ts
|
192
|
+
var _DCQL = class _DCQL {
|
193
|
+
constructor({ credentials, credential_sets }) {
|
194
|
+
__publicField(this, "_credentials", []);
|
195
|
+
__publicField(this, "_credential_sets");
|
196
|
+
this._credentials = credentials ?? [];
|
197
|
+
this._credential_sets = credential_sets;
|
198
|
+
}
|
199
|
+
addCredential(credential) {
|
200
|
+
this._credentials.push(credential);
|
201
|
+
return this;
|
202
|
+
}
|
203
|
+
addCredentialSet(credential_set) {
|
204
|
+
if (!this._credential_sets) {
|
205
|
+
this._credential_sets = [];
|
206
|
+
}
|
207
|
+
this._credential_sets.push(credential_set);
|
208
|
+
return this;
|
209
|
+
}
|
210
|
+
serialize() {
|
211
|
+
return {
|
212
|
+
credentials: this._credentials.map((c) => c.serialize()),
|
213
|
+
credential_sets: this._credential_sets
|
214
|
+
};
|
215
|
+
}
|
216
|
+
static parse(raw) {
|
217
|
+
const credentials = raw.credentials.map((c) => {
|
218
|
+
if (c.format === "dc+sd-jwt") {
|
219
|
+
return SdJwtVcCredential.parseSdJwtCredential(c);
|
220
|
+
}
|
221
|
+
throw new Error("Invalid credential format");
|
222
|
+
});
|
223
|
+
return new _DCQL({
|
224
|
+
credentials,
|
225
|
+
credential_sets: raw.credential_sets
|
226
|
+
});
|
227
|
+
}
|
228
|
+
/**
|
229
|
+
* Match credentials against an array of data records according to section 6.4.2 rules.
|
230
|
+
*
|
231
|
+
* If credential_sets is not provided, all credentials in credentials are requested.
|
232
|
+
* Otherwise, the Verifier requests credentials satisfying:
|
233
|
+
* - All required credential sets (where required is true or omitted)
|
234
|
+
* - Optionally, any other credential sets
|
235
|
+
*
|
236
|
+
* @param dataRecords Array of data records to match against
|
237
|
+
* @returns Object containing match result and matched credentials with their claims
|
238
|
+
*/
|
239
|
+
match(dataRecords) {
|
240
|
+
if (this._credentials.length === 0) {
|
241
|
+
return {
|
242
|
+
match: false
|
243
|
+
};
|
244
|
+
}
|
245
|
+
const allMatches = [];
|
246
|
+
this._credentials.forEach((credential) => {
|
247
|
+
dataRecords.forEach((data, index) => {
|
248
|
+
const result = credential.match(data);
|
249
|
+
if (result.match) {
|
250
|
+
allMatches.push({
|
251
|
+
credential: data,
|
252
|
+
dcqlCredential: credential,
|
253
|
+
matchedClaims: result.matchedClaims,
|
254
|
+
dataIndex: index
|
255
|
+
});
|
256
|
+
}
|
257
|
+
});
|
258
|
+
});
|
259
|
+
if (allMatches.length === 0) {
|
260
|
+
return {
|
261
|
+
match: false
|
262
|
+
};
|
263
|
+
}
|
264
|
+
if (!this._credential_sets || this._credential_sets.length === 0) {
|
265
|
+
return {
|
266
|
+
match: true,
|
267
|
+
matchedCredentials: allMatches
|
268
|
+
};
|
269
|
+
}
|
270
|
+
const matchedIds = new Set(allMatches.map((match) => {
|
271
|
+
const serialized = match.dcqlCredential.serialize();
|
272
|
+
return serialized.id;
|
273
|
+
}));
|
274
|
+
const requiredSets = this._credential_sets.filter((set) => set.required === void 0 || set.required === true);
|
275
|
+
const satisfiedRequiredSets = requiredSets.every((set) => this.isCredentialSetSatisfied(set, matchedIds));
|
276
|
+
if (!satisfiedRequiredSets) {
|
277
|
+
return {
|
278
|
+
match: false
|
279
|
+
};
|
280
|
+
}
|
281
|
+
return {
|
282
|
+
match: true,
|
283
|
+
matchedCredentials: allMatches.map((matches) => {
|
284
|
+
return {
|
285
|
+
credential: matches.credential,
|
286
|
+
matchedClaims: matches.matchedClaims,
|
287
|
+
dataIndex: matches.dataIndex
|
288
|
+
};
|
289
|
+
})
|
290
|
+
};
|
291
|
+
}
|
292
|
+
/**
|
293
|
+
* Check if a credential set is satisfied by the matched credential IDs
|
294
|
+
* A credential set is satisfied if at least one of its options is satisfied
|
295
|
+
*/
|
296
|
+
isCredentialSetSatisfied(set, matchedIds) {
|
297
|
+
return set.options.some((option) => {
|
298
|
+
return option.every((credentialId) => matchedIds.has(credentialId));
|
299
|
+
});
|
300
|
+
}
|
301
|
+
};
|
302
|
+
__name(_DCQL, "DCQL");
|
303
|
+
var DCQL = _DCQL;
|
304
|
+
|
305
|
+
// src/credentials/credential.ts
|
306
|
+
var _CredentialBase = class _CredentialBase {
|
307
|
+
};
|
308
|
+
__name(_CredentialBase, "CredentialBase");
|
309
|
+
var CredentialBase = _CredentialBase;
|
310
|
+
export {
|
311
|
+
CredentialBase,
|
312
|
+
DCQL,
|
313
|
+
SdJwtVcCredential
|
314
|
+
};
|
package/package.json
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
{
|
2
|
+
"name": "dvlprsh-dcql-test",
|
3
|
+
"version": "0.1.8",
|
4
|
+
"description": "DCQL implementation in typescript",
|
5
|
+
"main": "dist/index.js",
|
6
|
+
"module": "dist/index.mjs",
|
7
|
+
"types": "dist/index.d.ts",
|
8
|
+
"exports": {
|
9
|
+
".": {
|
10
|
+
"import": "./dist/index.mjs",
|
11
|
+
"require": "./dist/index.js"
|
12
|
+
}
|
13
|
+
},
|
14
|
+
"repository": {
|
15
|
+
"type": "git",
|
16
|
+
"url": "https://github.com/hopae-official/Verifiable-Digital-Credentials.git"
|
17
|
+
},
|
18
|
+
"bugs": {
|
19
|
+
"url": "https://github.com/hopae-official/Verifiable-Digital-Credentials/issues"
|
20
|
+
},
|
21
|
+
"homepage": "https://vdcs.js.org",
|
22
|
+
"scripts": {
|
23
|
+
"build": "rm -rf **/dist && tsup",
|
24
|
+
"lint": "biome lint ./src",
|
25
|
+
"test": "pnpm run test:node && pnpm run test:browser && pnpm run test:cov",
|
26
|
+
"test:node": "vitest run ./src/test/*.spec.ts",
|
27
|
+
"test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom",
|
28
|
+
"test:cov": "vitest run --coverage"
|
29
|
+
},
|
30
|
+
"keywords": [
|
31
|
+
"openid",
|
32
|
+
"openid4vp",
|
33
|
+
"vcdm",
|
34
|
+
"jwt",
|
35
|
+
"sd-jwt",
|
36
|
+
"mdl"
|
37
|
+
],
|
38
|
+
"engines": {
|
39
|
+
"node": ">=16"
|
40
|
+
},
|
41
|
+
"author": "Lukas.J.Han <lukas.j.han@gmail.com>",
|
42
|
+
"license": "Apache-2.0",
|
43
|
+
"publishConfig": {
|
44
|
+
"access": "public"
|
45
|
+
},
|
46
|
+
"tsup": {
|
47
|
+
"entry": [
|
48
|
+
"./src/index.ts"
|
49
|
+
],
|
50
|
+
"sourceMap": true,
|
51
|
+
"splitting": false,
|
52
|
+
"clean": true,
|
53
|
+
"dts": true,
|
54
|
+
"format": [
|
55
|
+
"cjs",
|
56
|
+
"esm"
|
57
|
+
]
|
58
|
+
}
|
59
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { Credential, MatchResult } from '../type';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* This class represent a credential
|
5
|
+
*
|
6
|
+
*
|
7
|
+
*/
|
8
|
+
export abstract class CredentialBase {
|
9
|
+
abstract serialize(): Credential;
|
10
|
+
|
11
|
+
abstract match(data: Record<string, unknown>): MatchResult;
|
12
|
+
}
|
@@ -0,0 +1,208 @@
|
|
1
|
+
import { pathMatch } from '../match';
|
2
|
+
import {
|
3
|
+
Claims,
|
4
|
+
ClaimSet,
|
5
|
+
Credential,
|
6
|
+
MatchResult,
|
7
|
+
SdJwtVcCredentialQuery,
|
8
|
+
TrustedAuthority,
|
9
|
+
} from '../type';
|
10
|
+
import { CredentialBase } from './credential';
|
11
|
+
|
12
|
+
export class SdJwtVcCredential implements CredentialBase {
|
13
|
+
private _multiple?: boolean;
|
14
|
+
private _trusted_authorities?: TrustedAuthority[];
|
15
|
+
private _require_cryptographic_holder_binding?: boolean;
|
16
|
+
private _claims?: Claims[];
|
17
|
+
private _claim_sets?: ClaimSet[];
|
18
|
+
|
19
|
+
constructor(
|
20
|
+
public readonly id: string,
|
21
|
+
public readonly vct_values: string[],
|
22
|
+
options?: {
|
23
|
+
multiple?: boolean;
|
24
|
+
trusted_authorities?: TrustedAuthority[];
|
25
|
+
require_cryptographic_holder_binding?: boolean;
|
26
|
+
claims?: Claims[];
|
27
|
+
claim_sets?: ClaimSet[];
|
28
|
+
},
|
29
|
+
) {
|
30
|
+
if (options) {
|
31
|
+
this._multiple = options.multiple;
|
32
|
+
this._trusted_authorities = options.trusted_authorities;
|
33
|
+
this._require_cryptographic_holder_binding =
|
34
|
+
options.require_cryptographic_holder_binding;
|
35
|
+
this._claims = options.claims;
|
36
|
+
this._claim_sets = options.claim_sets;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
setMultiple(multiple: boolean) {
|
41
|
+
this._multiple = multiple;
|
42
|
+
return this;
|
43
|
+
}
|
44
|
+
|
45
|
+
setTrustedAuthorities(trusted_authorities: TrustedAuthority[]) {
|
46
|
+
this._trusted_authorities = trusted_authorities;
|
47
|
+
return this;
|
48
|
+
}
|
49
|
+
|
50
|
+
setRequireCryptographicHolderBinding(
|
51
|
+
require_cryptographic_holder_binding: boolean,
|
52
|
+
) {
|
53
|
+
this._require_cryptographic_holder_binding =
|
54
|
+
require_cryptographic_holder_binding;
|
55
|
+
return this;
|
56
|
+
}
|
57
|
+
|
58
|
+
setClaims(claims: Claims[]) {
|
59
|
+
this._claims = claims;
|
60
|
+
return this;
|
61
|
+
}
|
62
|
+
|
63
|
+
setClaimSets(claim_sets: ClaimSet[]) {
|
64
|
+
/**
|
65
|
+
* Check if claims are set properly
|
66
|
+
*/
|
67
|
+
if (!this._claims || this._claims.length === 0) {
|
68
|
+
throw new Error('Claims must be set before claim sets');
|
69
|
+
}
|
70
|
+
|
71
|
+
if (!this._claims.every((c) => c.id !== undefined)) {
|
72
|
+
throw new Error('Claims must not have an id before claim sets');
|
73
|
+
}
|
74
|
+
|
75
|
+
this._claim_sets = claim_sets;
|
76
|
+
return this;
|
77
|
+
}
|
78
|
+
|
79
|
+
serialize(): Credential {
|
80
|
+
return {
|
81
|
+
id: this.id,
|
82
|
+
format: 'dc+sd-jwt',
|
83
|
+
meta: { vct_values: this.vct_values },
|
84
|
+
multiple: this._multiple,
|
85
|
+
trusted_authorities: this._trusted_authorities,
|
86
|
+
require_cryptographic_holder_binding:
|
87
|
+
this._require_cryptographic_holder_binding,
|
88
|
+
claims: this._claims,
|
89
|
+
claim_sets: this._claim_sets,
|
90
|
+
};
|
91
|
+
}
|
92
|
+
|
93
|
+
match(data: Record<string, unknown>): MatchResult {
|
94
|
+
// First check if the credential type matches
|
95
|
+
if (
|
96
|
+
data['vct'] !== undefined &&
|
97
|
+
!this.vct_values.includes(data['vct'] as string)
|
98
|
+
) {
|
99
|
+
return { match: false };
|
100
|
+
}
|
101
|
+
|
102
|
+
// If claims is absent, the Verifier is requesting no claims that are selectively disclosable
|
103
|
+
// Return only the mandatory claims
|
104
|
+
if (!this._claims || this._claims.length === 0) {
|
105
|
+
return { match: true, matchedClaims: [] };
|
106
|
+
}
|
107
|
+
|
108
|
+
// If claim_sets is present, the Verifier requests one combination of the claims listed in claim_sets
|
109
|
+
if (this._claim_sets && this._claim_sets.length > 0) {
|
110
|
+
// The order of options in claim_sets expresses the Verifier's preference
|
111
|
+
// Try to match the first option that can be satisfied
|
112
|
+
for (const claimSet of this._claim_sets) {
|
113
|
+
const claimsInSet = this._claims.filter(
|
114
|
+
(claim) => claim.id !== undefined && claimSet.includes(claim.id),
|
115
|
+
);
|
116
|
+
|
117
|
+
// Check if all claims in this set can be satisfied by the data
|
118
|
+
const allClaimsMatch = claimsInSet.every((claim) =>
|
119
|
+
this.matchClaim(claim, data),
|
120
|
+
);
|
121
|
+
|
122
|
+
// If all claims in this set can be satisfied, return the earliest set
|
123
|
+
if (allClaimsMatch && claimsInSet.length > 0) {
|
124
|
+
return {
|
125
|
+
match: true,
|
126
|
+
matchedClaims: claimsInSet,
|
127
|
+
};
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
// If we can't satisfy any of the claim sets, don't return any claims
|
132
|
+
return { match: false };
|
133
|
+
}
|
134
|
+
// If claims is present but claim_sets is absent, the Verifier requests all claims listed in claims
|
135
|
+
else {
|
136
|
+
const satisfiableClaims = this._claims.every((claim) =>
|
137
|
+
this.matchClaim(claim, data),
|
138
|
+
);
|
139
|
+
|
140
|
+
// Only match if we can satisfy all claims
|
141
|
+
if (satisfiableClaims) {
|
142
|
+
return { match: true, matchedClaims: this._claims };
|
143
|
+
}
|
144
|
+
}
|
145
|
+
|
146
|
+
return { match: false };
|
147
|
+
}
|
148
|
+
|
149
|
+
/**
|
150
|
+
* Helper method to check if a specific claim matches against the data
|
151
|
+
* Implements semantics for JSON-based credentials as specified in section 7.1
|
152
|
+
*/
|
153
|
+
private matchClaim(claim: Claims, data: Record<string, unknown>): boolean {
|
154
|
+
try {
|
155
|
+
// Start with the root element (top-level JSON object)
|
156
|
+
const selectedElements = pathMatch(claim.path, data);
|
157
|
+
|
158
|
+
// If no elements were selected, the claim can't be matched
|
159
|
+
if (selectedElements.length === 0) {
|
160
|
+
return false;
|
161
|
+
}
|
162
|
+
|
163
|
+
// If the claim doesn't have a value restriction, any selected value is acceptable
|
164
|
+
if (!claim.value || claim.value.length === 0) {
|
165
|
+
return true;
|
166
|
+
}
|
167
|
+
|
168
|
+
// Check if any of the selected elements match any of the allowed values
|
169
|
+
return selectedElements.some((element) =>
|
170
|
+
claim.value!.some((val) => val === element),
|
171
|
+
);
|
172
|
+
} catch (error) {
|
173
|
+
// If there was an error processing the path, the claim can't be matched
|
174
|
+
return false;
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
static parseSdJwtCredential(c: Credential): CredentialBase {
|
179
|
+
if (!this.validateCredential(c)) {
|
180
|
+
throw new Error('Invalid credential');
|
181
|
+
}
|
182
|
+
|
183
|
+
const sdJwtVcCredential = new SdJwtVcCredential(c.id, c.meta.vct_values, {
|
184
|
+
multiple: c.multiple,
|
185
|
+
trusted_authorities: c.trusted_authorities,
|
186
|
+
require_cryptographic_holder_binding:
|
187
|
+
c.require_cryptographic_holder_binding,
|
188
|
+
claims: c.claims,
|
189
|
+
claim_sets: c.claim_sets,
|
190
|
+
});
|
191
|
+
|
192
|
+
return sdJwtVcCredential;
|
193
|
+
}
|
194
|
+
|
195
|
+
private static validateCredential(
|
196
|
+
c: Credential,
|
197
|
+
): c is SdJwtVcCredentialQuery {
|
198
|
+
if (c.format !== 'dc+sd-jwt') {
|
199
|
+
throw new Error('Invalid credential format');
|
200
|
+
}
|
201
|
+
|
202
|
+
if (!c.meta || !('vct_values' in c.meta)) {
|
203
|
+
throw new Error('Invalid credential meta');
|
204
|
+
}
|
205
|
+
|
206
|
+
return true;
|
207
|
+
}
|
208
|
+
}
|