@idos-network/credentials 1.1.0

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.
@@ -0,0 +1,407 @@
1
+ import { Ed25519VerificationKey2020 } from "@digitalbazaar/ed25519-verification-key-2020";
2
+ import crypto from "node:crypto";
3
+ import { describe, expect, it } from "vitest";
4
+ import { ZodError } from "zod";
5
+
6
+ import { buildCredential, buildFaceIdCredential } from ".";
7
+ import { fileToBase85 } from "../utils";
8
+ import { verifyCredential } from "./verifier";
9
+
10
+ describe("verifiableCredentials", () => {
11
+ describe("face-scan-id", () => {
12
+ it("should raise an error if the fields are invalid", async () => {
13
+ expect.assertions(4); // catch
14
+
15
+ const id = "z6MkszZtxCmA2Ce4vUV132PCuLQmwnaDD5mw2L23fGNnsiX3";
16
+ const issuer = "https://vc-issuers.cool.id/idos";
17
+
18
+ const validKey = await Ed25519VerificationKey2020.generate({
19
+ id: `${issuer}/keys/1`,
20
+ controller: `${issuer}/issuers/1`,
21
+ });
22
+
23
+ try {
24
+ await buildFaceIdCredential(
25
+ {
26
+ id: `${issuer}/credentials/${id}`,
27
+ level: "human",
28
+ issued: new Date("2022-01-01"),
29
+ approvedAt: new Date("2022-01-01"),
30
+ expirationDate: new Date("2030-01-01"),
31
+ },
32
+ {
33
+ // @ts-expect-error
34
+ id: `uuid:${id}`,
35
+ },
36
+ validKey,
37
+ );
38
+ } catch (error) {
39
+ expect(error).toBeInstanceOf(ZodError);
40
+ const zodError = error as ZodError;
41
+ expect(zodError.issues).toHaveLength(1);
42
+
43
+ const faceSignUserId = zodError.issues.find((error) => error.path[0] === "faceSignUserId");
44
+ expect(faceSignUserId).toBeDefined();
45
+ expect(faceSignUserId?.message).toContain(
46
+ "Invalid input: expected string, received undefined",
47
+ );
48
+ }
49
+ });
50
+
51
+ it("should create and verify a verifiable vc", async () => {
52
+ const id = "z6MkszZtxCmA2Ce4vUV132PCuLQmwnaDD5mw2L23fGNnsiX3";
53
+ const issuer = "https://vc-issuers.cool.id/idos";
54
+ const issuer2 = "https://vc-issuers.cool.id/idos2";
55
+
56
+ const validKey = await Ed25519VerificationKey2020.generate({
57
+ id: `${issuer}/keys/1`,
58
+ controller: `${issuer}/issuers/1`,
59
+ });
60
+
61
+ const anotherKey = await Ed25519VerificationKey2020.generate({
62
+ id: `${issuer2}/keys/1`,
63
+ controller: `${issuer2}/issuers/1`,
64
+ });
65
+
66
+ const faceSignUserId = crypto.randomUUID();
67
+
68
+ const data = await buildFaceIdCredential(
69
+ {
70
+ id: `${issuer}/credentials/${id}`,
71
+ level: "human",
72
+ issued: new Date("2022-01-01"),
73
+ approvedAt: new Date("2022-01-01"),
74
+ expirationDate: new Date("2030-01-01"),
75
+ },
76
+ {
77
+ faceSignUserId,
78
+ },
79
+ validKey,
80
+ );
81
+
82
+ // Check if residential address is properly prefixed
83
+ expect(data.credentialSubject.faceSignUserId).toBe(faceSignUserId);
84
+
85
+ // Check if proof is properly constructed
86
+ expect(data.proof.proofPurpose).toBe("assertionMethod");
87
+ expect(data.proof.type).toBe("Ed25519Signature2020");
88
+ expect(data.proof.verificationMethod).toBe("https://vc-issuers.cool.id/idos/keys/1");
89
+
90
+ const allowedIssuers = [anotherKey, validKey];
91
+
92
+ const [verified, validResults] = await verifyCredential(data, allowedIssuers);
93
+ expect(verified).toBe(true);
94
+
95
+ const validResultsArray = validResults.values().toArray();
96
+ expect(validResultsArray).toHaveLength(2);
97
+ expect(validResultsArray[0].verified).toBe(false);
98
+ expect(validResultsArray[1].verified).toBe(true);
99
+ });
100
+
101
+ it("tempered VCs will be rejected", async () => {
102
+ const id = "z6MkszZtxCmA2Ce4vUV132PCuLQmwnaDD5mw2L23fGNnsiX3";
103
+ const issuer = "https://vc-issuers.cool.id/idos";
104
+
105
+ const validKey = await Ed25519VerificationKey2020.generate({
106
+ id: `${issuer}/keys/1`,
107
+ controller: `${issuer}/issuers/1`,
108
+ });
109
+
110
+ const faceSignUserId = crypto.randomUUID();
111
+ const faceSignUserId2 = crypto.randomUUID();
112
+
113
+ const data = await buildFaceIdCredential(
114
+ {
115
+ id: `${issuer}/credentials/${id}`,
116
+ level: "human",
117
+ issued: new Date("2022-01-01"),
118
+ approvedAt: new Date("2022-01-01"),
119
+ expirationDate: new Date("2030-01-01"),
120
+ },
121
+ {
122
+ faceSignUserId,
123
+ },
124
+ validKey,
125
+ );
126
+
127
+ // Check validity
128
+ const allowedIssuers = [validKey];
129
+ const [verified] = await verifyCredential(data, allowedIssuers);
130
+ expect(verified).toBe(true);
131
+
132
+ // Modify data
133
+ data.credentialSubject.faceSignUserId = faceSignUserId2;
134
+ const [verified2] = await verifyCredential(data, allowedIssuers);
135
+ expect(verified2).toBe(false);
136
+ });
137
+ });
138
+
139
+ describe("credentials", () => {
140
+ it("should raise an error if the fields are invalid", async () => {
141
+ expect.assertions(12); // catch
142
+
143
+ const id = "z6MkszZtxCmA2Ce4vUV132PCuLQmwnaDD5mw2L23fGNnsiX3";
144
+ const issuer = "https://vc-issuers.cool.id/idos";
145
+
146
+ const validKey = await Ed25519VerificationKey2020.generate({
147
+ id: `${issuer}/keys/1`,
148
+ controller: `${issuer}/issuers/1`,
149
+ });
150
+
151
+ try {
152
+ await buildCredential(
153
+ {
154
+ id: `${issuer}/credentials/${id}`,
155
+ level: "human",
156
+ issued: new Date("2022-01-01"),
157
+ approvedAt: new Date("2022-01-01"),
158
+ expirationDate: new Date("2030-01-01"),
159
+ },
160
+ {
161
+ id: `uuid:${id}`,
162
+ firstName: "John",
163
+ familyName: "Lennon",
164
+ // @ts-expect-error - This is a test, so the error is expected, since data is invalid
165
+ gender: "MALE",
166
+ email: "john.lennon@example.com",
167
+ phoneNumber: "+1234567890",
168
+ ssn: "203-456",
169
+ dateOfBirth: new Date("2025-02-30"),
170
+ placeOfBirth: "New York, NY",
171
+ idDocumentCountry: "DEU",
172
+ idDocumentNumber: "123456789",
173
+ idDocumentType: "PASSPORT",
174
+ idDocumentDateOfIssue: new Date("2022-01-01"),
175
+ idDocumentDateOfExpiry: new Date("2025-01-01"),
176
+ idDocumentFrontFile: Buffer.from("Front of ID document"),
177
+ residentialAddress: {
178
+ street: "Main St",
179
+ houseNumber: "123",
180
+ additionalAddressInfo: "Apt 1",
181
+ city: "New York",
182
+ postalCode: "10001",
183
+ country: "USA",
184
+ proofCategory: "Utility Bill",
185
+ proofDateOfIssue: new Date("2022-01-01"),
186
+ proofFile: Buffer.from("Proof of address"),
187
+ },
188
+ },
189
+ validKey,
190
+ );
191
+ } catch (error) {
192
+ expect(error).toBeInstanceOf(ZodError);
193
+ const zodError = error as ZodError;
194
+ expect(zodError.issues).toHaveLength(5);
195
+
196
+ // idDocumentCountry
197
+ const idDocumentCountryError = zodError.issues.find(
198
+ (error) => error.path[0] === "idDocumentCountry",
199
+ );
200
+ expect(idDocumentCountryError).toBeDefined();
201
+ expect(idDocumentCountryError?.message).toContain(
202
+ "Too big: expected string to have <=2 characters",
203
+ );
204
+
205
+ // selfieFile
206
+ const selfieFileError = zodError.issues.find((error) => error.path[0] === "selfieFile");
207
+ expect(selfieFileError).toBeDefined();
208
+ expect(selfieFileError?.message).toContain("expected Buffer, received undefined");
209
+
210
+ // residentialAddress country
211
+ const residentialAddressCountryError = zodError.issues.find(
212
+ (error) => error.path[0] === "residentialAddress",
213
+ );
214
+ expect(residentialAddressCountryError).toBeDefined();
215
+ expect(residentialAddressCountryError?.message).toContain(
216
+ "Too big: expected string to have <=2 characters",
217
+ );
218
+
219
+ // gender
220
+ const genderError = zodError.issues.find((error) => error.path[0] === "gender");
221
+ expect(genderError).toBeDefined();
222
+ expect(genderError?.message).toContain('Invalid option: expected one of "M"|"F"|"OTHER"');
223
+
224
+ // ssn
225
+ const ssnError = zodError.issues.find((error) => error.path[0] === "ssn");
226
+ expect(ssnError).toBeDefined();
227
+ expect(ssnError?.message).toContain("Too small: expected string to have >=9 characters");
228
+ }
229
+ });
230
+
231
+ it("test refinement (at least one of firstName or familyName must be provided)", async () => {
232
+ const id = "z6MkszZtxCmA2Ce4vUV132PCuLQmwnaDD5mw2L23fGNnsiX3";
233
+ const issuer = "https://vc-issuers.cool.id/idos";
234
+
235
+ const validKey = await Ed25519VerificationKey2020.generate({
236
+ id: `${issuer}/keys/1`,
237
+ controller: `${issuer}/issuers/1`,
238
+ });
239
+
240
+ expect.assertions(3); // catch
241
+
242
+ try {
243
+ await buildCredential(
244
+ {
245
+ id: `${issuer}/credentials/${id}`,
246
+ level: "human",
247
+ issued: new Date("2022-01-01"),
248
+ approvedAt: new Date("2022-01-01"),
249
+ expirationDate: new Date("2030-01-01"),
250
+ },
251
+ {
252
+ id: `uuid:${id}`,
253
+ ssn: "123456789",
254
+ firstName: "",
255
+ familyName: undefined,
256
+ gender: "M",
257
+ nationality: "US",
258
+ email: "john.lennon@example.com",
259
+ phoneNumber: "+1234567890",
260
+ dateOfBirth: new Date("1980-01-01"),
261
+ placeOfBirth: "New York, NY",
262
+ idDocumentCountry: "US",
263
+ idDocumentNumber: "123456789",
264
+ idDocumentType: "PASSPORT",
265
+ idDocumentDateOfIssue: new Date("2022-01-01"),
266
+ idDocumentDateOfExpiry: new Date("2025-01-01"),
267
+ idDocumentFrontFile: Buffer.from("Front of ID document"),
268
+ idDocumentBackFile: Buffer.from("Back of ID document"),
269
+ selfieFile: Buffer.from("Selfie"),
270
+ },
271
+ validKey,
272
+ );
273
+ } catch (error) {
274
+ expect(error).toBeInstanceOf(ZodError);
275
+ const zodError = error as ZodError;
276
+ expect(zodError.issues).toHaveLength(1);
277
+
278
+ const refinementError = zodError.issues.find(
279
+ (error) => error.message === "At least one of firstName or familyName must be provided",
280
+ );
281
+ expect(refinementError).toBeDefined();
282
+ }
283
+ });
284
+
285
+ it("should create and verify a verifiable vc", async () => {
286
+ const id = "z6MkszZtxCmA2Ce4vUV132PCuLQmwnaDD5mw2L23fGNnsiX3";
287
+ const issuer = "https://vc-issuers.cool.id/idos";
288
+ const issuer2 = "https://vc-issuers.cool.id/idos2";
289
+
290
+ const validKey = await Ed25519VerificationKey2020.generate({
291
+ id: `${issuer}/keys/1`,
292
+ controller: `${issuer}/issuers/1`,
293
+ });
294
+
295
+ const anotherKey = await Ed25519VerificationKey2020.generate({
296
+ id: `${issuer2}/keys/1`,
297
+ controller: `${issuer2}/issuers/1`,
298
+ });
299
+
300
+ const data = await buildCredential(
301
+ {
302
+ id: `${issuer}/credentials/${id}`,
303
+ level: "human",
304
+ issued: new Date("2022-01-01"),
305
+ approvedAt: new Date("2022-01-01"),
306
+ expirationDate: new Date("2030-01-01"),
307
+ },
308
+ {
309
+ id: `uuid:${id}`,
310
+ firstName: "John",
311
+ middleName: "Paul",
312
+ familyName: "Lennon",
313
+ ssn: "123456789",
314
+ gender: "M",
315
+ nationality: "US",
316
+ email: "john.lennon@example.com",
317
+ phoneNumber: "+1234567890",
318
+ dateOfBirth: new Date("1980-01-01"),
319
+ placeOfBirth: "New York, NY",
320
+ idDocumentCountry: "US",
321
+ idDocumentNumber: "123456789",
322
+ idDocumentType: "PASSPORT",
323
+ idDocumentDateOfIssue: new Date("2022-01-01"),
324
+ idDocumentDateOfExpiry: new Date("2025-01-01"),
325
+ idDocumentFrontFile: Buffer.from("Front of ID document"),
326
+ idDocumentBackFile: Buffer.from("Back of ID document"),
327
+ selfieFile: Buffer.from("Selfie"),
328
+ residentialAddress: {
329
+ street: "Main St",
330
+ houseNumber: "123",
331
+ additionalAddressInfo: "Apt 1",
332
+ region: "NY",
333
+ city: "New York",
334
+ postalCode: "10001",
335
+ country: "US",
336
+ proofCategory: "Utility Bill",
337
+ proofDateOfIssue: new Date("2022-01-01"),
338
+ proofFile: Buffer.from("Proof of address"),
339
+ },
340
+ },
341
+ validKey,
342
+ );
343
+
344
+ // Check if residential address is properly prefixed
345
+ expect(data.credentialSubject.residentialAddressStreet).toBe("Main St");
346
+ expect(data.credentialSubject.residentialAddressHouseNumber).toBe("123");
347
+ expect(data.credentialSubject.residentialAddressAdditionalAddressInfo).toBe("Apt 1");
348
+ expect(data.credentialSubject.residentialAddressCity).toBe("New York");
349
+ expect(data.credentialSubject.residentialAddressPostalCode).toBe("10001");
350
+ expect(data.credentialSubject.residentialAddressCountry).toBe("US");
351
+ expect(data.credentialSubject.residentialAddressRegion).toBe("NY");
352
+ expect(data.credentialSubject.residentialAddressProofCategory).toBe("Utility Bill");
353
+ expect(data.credentialSubject.residentialAddressProofDateOfIssue).toEqual(
354
+ new Date("2022-01-01").toISOString(),
355
+ );
356
+ expect(data.credentialSubject.residentialAddressProofFile).toEqual(
357
+ fileToBase85(Buffer.from("Proof of address")),
358
+ );
359
+
360
+ expect(data.credentialSubject.email).toBe("john.lennon@example.com");
361
+ expect(data.credentialSubject.phoneNumber).toBe("+1234567890");
362
+
363
+ // Check if proof is properly constructed
364
+ expect(data.proof.proofPurpose).toBe("assertionMethod");
365
+ expect(data.proof.type).toBe("Ed25519Signature2020");
366
+ expect(data.proof.verificationMethod).toBe("https://vc-issuers.cool.id/idos/keys/1");
367
+
368
+ const allowedIssuers = [anotherKey, validKey];
369
+
370
+ const [verified, validResults] = await verifyCredential(data, allowedIssuers);
371
+ expect(verified).toBe(true);
372
+
373
+ const validResultsArray = validResults.values().toArray();
374
+ expect(validResultsArray).toHaveLength(2);
375
+ expect(validResultsArray[0].verified).toBe(false);
376
+ expect(validResultsArray[1].verified).toBe(true);
377
+
378
+ // Invalid issuer
379
+ const invalidIssuers = [
380
+ {
381
+ // Just the wrong issuer
382
+ issuer: issuer2,
383
+ publicKeyMultibase: anotherKey.publicKeyMultibase,
384
+ },
385
+ ];
386
+
387
+ const [invalidVerified, invalidResults] = await verifyCredential(data, invalidIssuers);
388
+ expect(invalidVerified).toBe(false);
389
+
390
+ const invalidResultsArray = invalidResults.values().toArray();
391
+ expect(invalidResultsArray).toHaveLength(1);
392
+ expect(invalidResultsArray[0].verified).toBe(false);
393
+ expect(invalidResultsArray[0].error?.errors?.[0]?.message).toContain("not verify any proofs");
394
+
395
+ // Accepts issuer as issuer object
396
+ const validIssuer = [
397
+ {
398
+ issuer,
399
+ publicKeyMultibase: validKey.publicKeyMultibase,
400
+ },
401
+ ];
402
+
403
+ const [validIssuerVerified] = await verifyCredential(data, validIssuer);
404
+ expect(validIssuerVerified).toBe(true);
405
+ });
406
+ });
407
+ });
@@ -0,0 +1,122 @@
1
+ import { Ed25519Signature2020 } from "@digitalbazaar/ed25519-signature-2020";
2
+ import * as vc from "@digitalbazaar/vc";
3
+
4
+ import type { VerifyCredentialResult } from "./verifier";
5
+
6
+ import {
7
+ type AvailableIssuerType,
8
+ type CredentialFields,
9
+ CredentialFieldsSchema,
10
+ type CredentialSubject,
11
+ type CredentialSubjectFaceId,
12
+ CredentialSubjectFaceIdSchema,
13
+ CredentialSubjectSchema,
14
+ type VerifiableCredential,
15
+ type VerifiableCredentialSubject,
16
+ } from "../types";
17
+ import { convertValues, issuerToKey } from "../utils";
18
+ import {
19
+ CONTEXT_IDOS_CREDENTIAL,
20
+ CONTEXT_IDOS_CREDENTIAL_FACE_ID,
21
+ CONTEXT_IDOS_CREDENTIAL_SUBJECT,
22
+ CONTEXT_IDOS_SIGNATURE,
23
+ CONTEXT_V1,
24
+ defaultDocumentLoader,
25
+ } from "./loader";
26
+ import { verifyCredential } from "./verifier";
27
+
28
+ export type Credential = VerifiableCredential<VerifiableCredentialSubject>;
29
+ export type FaceIdCredential = VerifiableCredential<CredentialSubjectFaceId>;
30
+
31
+ export type CredentialBuilder<TInput, TOutput = TInput> = (
32
+ fields: CredentialFields,
33
+ subject: TInput,
34
+ issuer: AvailableIssuerType,
35
+ validate?: boolean,
36
+ ) => Promise<VerifiableCredential<TOutput>>;
37
+
38
+ export type CredentialConverter<TInput> = (
39
+ subject: TInput,
40
+ validate: boolean,
41
+ // oxlint-disable-next-line typescript/no-explicit-any -- any is needed here
42
+ ) => Record<string, any>;
43
+
44
+ function genericCredentialBuilder<TInput, TOutput = TInput>(
45
+ credentialConverter: CredentialConverter<TInput>,
46
+ ): CredentialBuilder<TInput, TOutput> {
47
+ async function builder(
48
+ fields: CredentialFields,
49
+ subject: TInput,
50
+ issuer: AvailableIssuerType,
51
+ validate = true,
52
+ ) {
53
+ if (validate) {
54
+ // This raises an z.ZodError exception if the fields are invalid
55
+ CredentialFieldsSchema.parse(fields);
56
+ }
57
+
58
+ const credentialSubject = credentialConverter(subject, validate);
59
+
60
+ const key = await issuerToKey(issuer);
61
+
62
+ // Create credentials container
63
+ const credential = {
64
+ "@context": [CONTEXT_V1, CONTEXT_IDOS_CREDENTIAL, CONTEXT_IDOS_SIGNATURE],
65
+ type: ["VerifiableCredential"],
66
+ issuer: key.controller,
67
+ ...convertValues(fields),
68
+ credentialSubject,
69
+ };
70
+
71
+ const suite = new Ed25519Signature2020({ key });
72
+
73
+ return vc.issue<TOutput>({
74
+ credential,
75
+ suite,
76
+ documentLoader: defaultDocumentLoader,
77
+ });
78
+ }
79
+
80
+ return builder;
81
+ }
82
+
83
+ export const credentialSubjectConverter: CredentialConverter<CredentialSubject> = (
84
+ subject,
85
+ validate,
86
+ ) => {
87
+ if (validate) {
88
+ CredentialSubjectSchema.parse(subject);
89
+ }
90
+
91
+ const { residentialAddress, ...subjectData } = subject;
92
+ return {
93
+ "@context": CONTEXT_IDOS_CREDENTIAL_SUBJECT,
94
+ ...convertValues(subjectData),
95
+ ...(residentialAddress ? convertValues(residentialAddress, "residentialAddress") : {}),
96
+ };
97
+ };
98
+
99
+ export const credentialFaceIdSubjectConverter: CredentialConverter<CredentialSubjectFaceId> = (
100
+ subject,
101
+ validate,
102
+ ) => {
103
+ if (validate) {
104
+ CredentialSubjectFaceIdSchema.parse(subject);
105
+ }
106
+
107
+ return {
108
+ "@context": CONTEXT_IDOS_CREDENTIAL_FACE_ID,
109
+ ...convertValues(subject),
110
+ };
111
+ };
112
+
113
+ export const buildCredential: CredentialBuilder<CredentialSubject, VerifiableCredentialSubject> =
114
+ genericCredentialBuilder<CredentialSubject, VerifiableCredentialSubject>(
115
+ credentialSubjectConverter,
116
+ );
117
+
118
+ export const buildFaceIdCredential: CredentialBuilder<CredentialSubjectFaceId> =
119
+ genericCredentialBuilder<CredentialSubjectFaceId>(credentialFaceIdSubjectConverter);
120
+
121
+ export type { VerifyCredentialResult };
122
+ export { verifyCredential };
@@ -0,0 +1,76 @@
1
+ import { type JsonLDDocumentLoaderInstance, JsonLdDocumentLoader } from "jsonld-document-loader";
2
+
3
+ import ed25519Signature2020V1 from "../../assets/ed25519-signature-2020-v1.json";
4
+ import idosCredentialSubjectFaceIdV1 from "../../assets/idos-credential-subject-face-id-v1.json";
5
+ import idosCredentialSubjectV1 from "../../assets/idos-credential-subject-v1.json";
6
+ import idosCredentialSubjectV2 from "../../assets/idos-credential-subject-v2.json";
7
+ import idosCredentialsV1 from "../../assets/idos-credentials-v1.json";
8
+ import v1 from "../../assets/v1.json";
9
+
10
+ export const CONTEXT_V1 = "https://www.w3.org/2018/credentials/v1";
11
+ export const CONTEXT_ED25519_SIGNATURE_2020_V1 = "https://w3id.org/security/suites/ed25519-2020/v1";
12
+
13
+ // Check .github/workflow/pages.yml how those are deployed
14
+ export const CONTEXT_IDOS_CREDENTIAL_V1 =
15
+ "https://idos-network.github.io/idos-sdk-js/credentials/idos-credentials-v1.json";
16
+ export const CONTEXT_IDOS_CREDENTIAL_V1_SUBJECT =
17
+ "https://idos-network.github.io/idos-sdk-js/credentials/idos-credential-subject-v1.json";
18
+ export const CONTEXT_IDOS_CREDENTIAL_V2_SUBJECT =
19
+ "https://idos-network.github.io/idos-sdk-js/credentials/idos-credential-subject-v2.json";
20
+
21
+ export const CONTEXT_IDOS_CREDENTIAL_V1_FACE_ID =
22
+ "https://idos-network.github.io/idos-sdk-js/credentials/idos-credential-subject-face-id-v1.json";
23
+
24
+ // Latest contexts for builder
25
+ export const CONTEXT_IDOS_CREDENTIAL: string = CONTEXT_IDOS_CREDENTIAL_V1;
26
+ export const CONTEXT_IDOS_CREDENTIAL_SUBJECT: string = CONTEXT_IDOS_CREDENTIAL_V2_SUBJECT;
27
+ export const CONTEXT_IDOS_SIGNATURE: string = CONTEXT_ED25519_SIGNATURE_2020_V1;
28
+ export const CONTEXT_IDOS_CREDENTIAL_FACE_ID: string = CONTEXT_IDOS_CREDENTIAL_V1_FACE_ID;
29
+
30
+ export function buildDocumentLoader(): JsonLDDocumentLoaderInstance {
31
+ const loader = new JsonLdDocumentLoader();
32
+
33
+ loader.addStatic(CONTEXT_V1, v1);
34
+ loader.addStatic(CONTEXT_IDOS_CREDENTIAL_V1, idosCredentialsV1);
35
+ loader.addStatic(CONTEXT_ED25519_SIGNATURE_2020_V1, ed25519Signature2020V1);
36
+
37
+ // plus / basic + liveness credentials
38
+ loader.addStatic(CONTEXT_IDOS_CREDENTIAL_V1_SUBJECT, idosCredentialSubjectV1);
39
+ loader.addStatic(CONTEXT_IDOS_CREDENTIAL_V2_SUBJECT, idosCredentialSubjectV2);
40
+
41
+ // PoP
42
+ loader.addStatic(CONTEXT_IDOS_CREDENTIAL_V1_FACE_ID, idosCredentialSubjectFaceIdV1);
43
+
44
+ // DEPRECATED: Those should be removed
45
+ [
46
+ "1356fea6672d22deb07fedc5fa427478b2e1654c",
47
+ "346f14468348e4f3dd00039c89ce9bb49d88777c",
48
+ "1e8421744e037dfd7b0289cb39261c73ab6d643c",
49
+ "0049d2e93f8913c42398372e2bb65d92bed38ac0",
50
+ ].forEach((hash) => {
51
+ loader.addStatic(
52
+ `https://raw.githubusercontent.com/idos-network/idos-sdk-js/${hash}/packages/%40credentials/assets/idos-credentials-v1.json`,
53
+ idosCredentialsV1,
54
+ );
55
+ loader.addStatic(
56
+ `https://raw.githubusercontent.com/idos-network/idos-sdk-js/${hash}/packages/%40credentials/assets/idos-credential-subject-v1.json`,
57
+ idosCredentialSubjectV1,
58
+ );
59
+ });
60
+
61
+ // DEPRECATED: Those should be removed
62
+ ["1bc3503f5302a7e42777076445d5b05fec8db429"].forEach((hash) => {
63
+ loader.addStatic(
64
+ `https://raw.githubusercontent.com/idos-network/idos-sdk-js/${hash}/packages/issuer-sdk-js/assets/idos-credentials-v1.json`,
65
+ idosCredentialsV1,
66
+ );
67
+ loader.addStatic(
68
+ `https://raw.githubusercontent.com/idos-network/idos-sdk-js/${hash}/packages/issuer-sdk-js/assets/idos-credential-subject-v1.json`,
69
+ idosCredentialSubjectV1,
70
+ );
71
+ });
72
+
73
+ return loader.build();
74
+ }
75
+
76
+ export const defaultDocumentLoader: JsonLDDocumentLoaderInstance = buildDocumentLoader();
@@ -0,0 +1,55 @@
1
+ import type { JsonLDDocumentLoaderInstance } from "jsonld-document-loader";
2
+
3
+ import { Ed25519Signature2020 } from "@digitalbazaar/ed25519-signature-2020";
4
+ import * as vc from "@digitalbazaar/vc";
5
+
6
+ import type {
7
+ AvailableIssuerType,
8
+ VerifiableCredential,
9
+ VerifiableCredentialSubject,
10
+ } from "../types";
11
+
12
+ import { issuerToKey } from "../utils";
13
+ import { defaultDocumentLoader } from "./loader";
14
+
15
+ export type VerifyCredentialResult = [boolean, Map<AvailableIssuerType, vc.VerifyCredentialResult>];
16
+
17
+ export async function verifyCredential<K = VerifiableCredentialSubject>(
18
+ credential: VerifiableCredential<K>,
19
+ issuers: AvailableIssuerType[],
20
+ customDocumentLoader?: JsonLDDocumentLoaderInstance,
21
+ ): Promise<VerifyCredentialResult> {
22
+ const resultsByIssuer: Map<AvailableIssuerType, vc.VerifyCredentialResult> = new Map();
23
+
24
+ for (const issuer of issuers) {
25
+ const publicKey = await issuerToKey(issuer);
26
+
27
+ const vcVerifyingSuite = new Ed25519Signature2020({
28
+ key: publicKey,
29
+ verificationMethod: publicKey.id,
30
+ });
31
+
32
+ const controller = {
33
+ "@context": "https://w3id.org/security/v2",
34
+ id: publicKey.controller,
35
+ assertionMethod: [publicKey.id],
36
+ authentication: [publicKey.id],
37
+ };
38
+
39
+ // Verify the signature
40
+ const verifyCredentialResult = await vc.verifyCredential<K>({
41
+ credential: credential,
42
+ suite: vcVerifyingSuite,
43
+ controller,
44
+ documentLoader: customDocumentLoader ?? defaultDocumentLoader,
45
+ });
46
+
47
+ resultsByIssuer.set(issuer, verifyCredentialResult);
48
+
49
+ if (verifyCredentialResult.verified) {
50
+ return [true, resultsByIssuer];
51
+ }
52
+ }
53
+
54
+ return [false, resultsByIssuer];
55
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ /* oxlint-disable */
2
+ // This is just empty placeholder for main export