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/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
+ }