@sphereon/ssi-types 0.30.1-unstable.4 → 0.30.1

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.
@@ -1,919 +1,919 @@
1
- import { IssuerType } from '@veramo/core'
2
- import jwt_decode from 'jwt-decode'
3
- import {
4
- AsyncHasher,
5
- decodeMdocDeviceResponse,
6
- decodeMdocIssuerSigned,
7
- decodeSdJwtVc,
8
- decodeSdJwtVcAsync,
9
- DocumentFormat,
10
- Hasher,
11
- ICredential,
12
- IPresentation,
13
- IProof,
14
- IProofPurpose,
15
- IProofType,
16
- isWrappedMdocCredential,
17
- isWrappedMdocPresentation,
18
- isWrappedSdJwtVerifiableCredential,
19
- isWrappedSdJwtVerifiablePresentation,
20
- isWrappedW3CVerifiableCredential,
21
- isWrappedW3CVerifiablePresentation,
22
- IVerifiableCredential,
23
- IVerifiablePresentation,
24
- JwtDecodedVerifiableCredential,
25
- JwtDecodedVerifiablePresentation,
26
- mdocDecodedCredentialToUniformCredential,
27
- MdocDeviceResponse,
28
- MdocOid4vpMdocVpToken,
29
- OriginalType,
30
- OriginalVerifiableCredential,
31
- OriginalVerifiablePresentation,
32
- sdJwtDecodedCredentialToUniformCredential,
33
- SdJwtDecodedVerifiableCredential,
34
- SdJwtDecodedVerifiableCredentialPayload,
35
- UniformVerifiablePresentation,
36
- W3CVerifiableCredential,
37
- W3CVerifiablePresentation,
38
- WrappedMdocCredential,
39
- WrappedSdJwtVerifiableCredential,
40
- WrappedVerifiableCredential,
41
- WrappedVerifiablePresentation,
42
- WrappedW3CVerifiableCredential,
43
- } from '../types'
44
- import { MdocDocument } from '../types/mso_mdoc'
45
- import { ObjectUtils } from '../utils'
46
- import { com } from '@sphereon/kmp-mdl-mdoc'
47
- import DeviceResponseCbor = com.sphereon.mdoc.data.device.DeviceResponseCbor
48
-
49
- export class CredentialMapper {
50
- /**
51
- * Decodes a compact SD-JWT vc to it's decoded variant. This method can be used when the hasher implementation used is Async, and therefore not suitable for usage
52
- * with the other decode methods.
53
- */
54
- static decodeSdJwtVcAsync(compactSdJwtVc: string, hasher: AsyncHasher) {
55
- return decodeSdJwtVcAsync(compactSdJwtVc, hasher)
56
- }
57
-
58
- /**
59
- * Decodes a Verifiable Presentation to a uniform format.
60
- *
61
- * When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
62
- * an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
63
- * instead of the compact SD-JWT.
64
- *
65
- * @param hasher Hasher implementation to use for SD-JWT decoding.
66
- */
67
- static decodeVerifiablePresentation(
68
- presentation: OriginalVerifiablePresentation,
69
- hasher?: Hasher
70
- ): JwtDecodedVerifiablePresentation | IVerifiablePresentation | SdJwtDecodedVerifiableCredential | MdocOid4vpMdocVpToken | MdocDeviceResponse {
71
- if (CredentialMapper.isJwtEncoded(presentation)) {
72
- const payload = jwt_decode(presentation as string) as JwtDecodedVerifiablePresentation
73
- const header = jwt_decode(presentation as string, { header: true }) as Record<string, any>
74
-
75
- payload.vp.proof = {
76
- type: IProofType.JwtProof2020,
77
- created: payload.nbf,
78
- proofPurpose: IProofPurpose.authentication,
79
- verificationMethod: header['kid'] ?? payload.iss,
80
- jwt: presentation as string,
81
- }
82
- return payload
83
- } else if (CredentialMapper.isJwtDecodedPresentation(presentation)) {
84
- return presentation as JwtDecodedVerifiablePresentation
85
- } else if (CredentialMapper.isSdJwtEncoded(presentation)) {
86
- if (!hasher) {
87
- throw new Error('Hasher implementation is required to decode SD-JWT')
88
- }
89
- return decodeSdJwtVc(presentation, hasher)
90
- } else if (CredentialMapper.isSdJwtDecodedCredential(presentation)) {
91
- return presentation as SdJwtDecodedVerifiableCredential
92
- } else if (CredentialMapper.isMsoMdocOid4VPEncoded(presentation)) {
93
- return presentation as MdocOid4vpMdocVpToken
94
- } else if (CredentialMapper.isMsoMdocDecodedPresentation(presentation)) {
95
- return presentation as MdocDeviceResponse
96
- } else if (CredentialMapper.isJsonLdAsString(presentation)) {
97
- return JSON.parse(presentation as string) as IVerifiablePresentation
98
- } else {
99
- return presentation as IVerifiablePresentation
100
- }
101
- }
102
-
103
- /**
104
- * Decodes a Verifiable Credential to a uniform format.
105
- *
106
- * When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
107
- * an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
108
- * instead of the compact SD-JWT.
109
- *
110
- * @param hasher Hasher implementation to use for SD-JWT decoding
111
- */
112
- static decodeVerifiableCredential(
113
- credential: OriginalVerifiableCredential,
114
- hasher?: Hasher
115
- ): JwtDecodedVerifiableCredential | IVerifiableCredential | SdJwtDecodedVerifiableCredential {
116
- if (CredentialMapper.isJwtEncoded(credential)) {
117
- const payload = jwt_decode(credential as string) as JwtDecodedVerifiableCredential
118
- const header = jwt_decode(credential as string, { header: true }) as Record<string, any>
119
- payload.vc.proof = {
120
- type: IProofType.JwtProof2020,
121
- created: payload.nbf,
122
- proofPurpose: IProofPurpose.authentication,
123
- verificationMethod: header['kid'] ?? payload.iss,
124
- jwt: credential as string,
125
- }
126
- return payload
127
- } else if (CredentialMapper.isJwtDecodedCredential(credential)) {
128
- return credential
129
- } else if (CredentialMapper.isJsonLdAsString(credential)) {
130
- return JSON.parse(credential as string) as IVerifiableCredential
131
- } else if (CredentialMapper.isSdJwtEncoded(credential)) {
132
- if (!hasher) {
133
- throw new Error('Hasher implementation is required to decode SD-JWT')
134
- }
135
- return decodeSdJwtVc(credential, hasher)
136
- } else if (CredentialMapper.isSdJwtDecodedCredential(credential)) {
137
- return credential
138
- } else {
139
- return credential as IVerifiableCredential
140
- }
141
- }
142
-
143
- /**
144
- * Converts a presentation to a wrapped presentation.
145
- *
146
- * When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
147
- * an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
148
- * instead of the compact SD-JWT.
149
- *
150
- * @param hasher Hasher implementation to use for SD-JWT decoding
151
- */
152
- static toWrappedVerifiablePresentation(
153
- originalPresentation: OriginalVerifiablePresentation,
154
- opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }
155
- ): WrappedVerifiablePresentation {
156
- // MSO_MDOC
157
- if (CredentialMapper.isMsoMdocDecodedPresentation(originalPresentation) || CredentialMapper.isMsoMdocOid4VPEncoded(originalPresentation)) {
158
- let deviceResponse: MdocDeviceResponse
159
- if (CredentialMapper.isMsoMdocOid4VPEncoded(originalPresentation)) {
160
- deviceResponse = decodeMdocDeviceResponse(originalPresentation)
161
- } else {
162
- deviceResponse = originalPresentation
163
- }
164
-
165
- const mdocCredential: WrappedMdocCredential | undefined = deviceResponse.documents
166
- ?.map((doc) => CredentialMapper.toWrappedVerifiableCredential(doc, opts) as WrappedMdocCredential)
167
- .find((value) => value !== undefined)
168
- if (mdocCredential === undefined) {
169
- throw Error('mdoc credential data corruption')
170
- }
171
- return {
172
- type: CredentialMapper.isMsoMdocDecodedPresentation(originalPresentation) ? OriginalType.MSO_MDOC_DECODED : OriginalType.MSO_MDOC_ENCODED,
173
- format: 'mso_mdoc',
174
- original: originalPresentation,
175
- presentation: deviceResponse,
176
- decoded: deviceResponse,
177
- vcs: [mdocCredential],
178
- }
179
- }
180
- // SD-JWT
181
- if (CredentialMapper.isSdJwtDecodedCredential(originalPresentation) || CredentialMapper.isSdJwtEncoded(originalPresentation)) {
182
- let decodedPresentation: SdJwtDecodedVerifiableCredential
183
- if (CredentialMapper.isSdJwtEncoded(originalPresentation)) {
184
- if (!opts?.hasher) {
185
- throw new Error('Hasher implementation is required to decode SD-JWT')
186
- }
187
- decodedPresentation = decodeSdJwtVc(originalPresentation, opts.hasher)
188
- } else {
189
- decodedPresentation = originalPresentation
190
- }
191
- return {
192
- type: CredentialMapper.isSdJwtDecodedCredential(originalPresentation) ? OriginalType.SD_JWT_VC_DECODED : OriginalType.SD_JWT_VC_ENCODED,
193
- format: 'vc+sd-jwt',
194
- original: originalPresentation,
195
- presentation: decodedPresentation,
196
- decoded: decodedPresentation.decodedPayload,
197
- // NOTE: we also include the SD-JWT VC as the VC, as the SD-JWT acts as both the VC and the VP
198
- vcs: [CredentialMapper.toWrappedVerifiableCredential(originalPresentation, opts) as WrappedSdJwtVerifiableCredential],
199
- }
200
- }
201
-
202
- // If the VP is not an encoded/decoded SD-JWT, we assume it will be a W3C VC
203
- const proof = CredentialMapper.getFirstProof(originalPresentation)
204
- const original =
205
- typeof originalPresentation !== 'string' && CredentialMapper.hasJWTProofType(originalPresentation) ? proof?.jwt : originalPresentation
206
- if (!original) {
207
- throw Error(
208
- 'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof'
209
- )
210
- }
211
- const decoded = CredentialMapper.decodeVerifiablePresentation(original) as IVerifiablePresentation | JwtDecodedVerifiablePresentation
212
- const isJwtEncoded: boolean = CredentialMapper.isJwtEncoded(original)
213
- const isJwtDecoded: boolean = CredentialMapper.isJwtDecodedPresentation(original)
214
-
215
- const type = isJwtEncoded ? OriginalType.JWT_ENCODED : isJwtDecoded ? OriginalType.JWT_DECODED : OriginalType.JSONLD
216
- const format = isJwtDecoded || isJwtEncoded ? 'jwt_vp' : ('ldp_vp' as const)
217
-
218
- let vp: OriginalVerifiablePresentation
219
- if (isJwtEncoded || isJwtDecoded) {
220
- vp = CredentialMapper.jwtDecodedPresentationToUniformPresentation(decoded as JwtDecodedVerifiablePresentation, false, opts)
221
- } else {
222
- vp = decoded as IVerifiablePresentation
223
- }
224
- if (!vp) {
225
- throw Error(`VP key not found`)
226
- }
227
- const noVCs = !('verifiableCredential' in vp) || !vp.verifiableCredential || vp.verifiableCredential.length === 0
228
- if (noVCs) {
229
- console.warn(`Presentation without verifiable credentials. That is rare! `)
230
- // throw Error(`VP needs to have at least one verifiable credential at this point`)
231
- }
232
- const vcs = noVCs
233
- ? []
234
- : (CredentialMapper.toWrappedVerifiableCredentials(
235
- vp.verifiableCredential ?? [] /*.map(value => value.original)*/,
236
- opts
237
- ) as WrappedW3CVerifiableCredential[])
238
-
239
- const presentation = {
240
- ...vp,
241
- verifiableCredential: vcs, // We overwrite the verifiableCredentials with wrapped versions, making it an InternalVerifiablePresentation. Note: we keep the singular key name of the vc data model
242
- } as UniformVerifiablePresentation
243
- return {
244
- type,
245
- format,
246
- original,
247
- decoded,
248
- presentation,
249
- vcs,
250
- }
251
- }
252
-
253
- /**
254
- * Converts a list of credentials to a list of wrapped credentials.
255
- *
256
- * When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
257
- * an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
258
- * instead of the compact SD-JWT.
259
- *
260
- * @param hasher Hasher implementation to use for SD-JWT decoding
261
- */
262
- static toWrappedVerifiableCredentials(
263
- verifiableCredentials: OriginalVerifiableCredential[],
264
- opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }
265
- ): WrappedVerifiableCredential[] {
266
- return verifiableCredentials.map((vc) => CredentialMapper.toWrappedVerifiableCredential(vc, opts))
267
- }
268
-
269
- /**
270
- * Converts a credential to a wrapped credential.
271
- *
272
- * When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
273
- * an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
274
- * instead of the compact SD-JWT.
275
- *
276
- * @param hasher Hasher implementation to use for SD-JWT decoding
277
- */
278
- static toWrappedVerifiableCredential(
279
- verifiableCredential: OriginalVerifiableCredential,
280
- opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }
281
- ): WrappedVerifiableCredential {
282
- // MSO_MDOC
283
- if (CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential) || CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential)) {
284
- let mdoc: MdocDocument
285
- if (CredentialMapper.isMsoMdocOid4VPEncoded(verifiableCredential)) {
286
- mdoc = decodeMdocIssuerSigned(verifiableCredential)
287
- } else {
288
- mdoc = verifiableCredential
289
- }
290
- return {
291
- type: CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential) ? OriginalType.MSO_MDOC_DECODED : OriginalType.MSO_MDOC_ENCODED,
292
- format: 'mso_mdoc',
293
- original: verifiableCredential,
294
- credential: mdocDecodedCredentialToUniformCredential(mdoc),
295
- decoded: mdoc,
296
- }
297
- }
298
-
299
- // SD-JWT
300
- if (CredentialMapper.isSdJwtDecodedCredential(verifiableCredential) || CredentialMapper.isSdJwtEncoded(verifiableCredential)) {
301
- let decodedCredential: SdJwtDecodedVerifiableCredential
302
- if (CredentialMapper.isSdJwtEncoded(verifiableCredential)) {
303
- if (!opts?.hasher) {
304
- throw new Error('Hasher implementation is required to decode SD-JWT')
305
- }
306
- decodedCredential = decodeSdJwtVc(verifiableCredential, opts.hasher)
307
- } else {
308
- decodedCredential = verifiableCredential
309
- }
310
-
311
- return {
312
- type: CredentialMapper.isSdJwtDecodedCredential(verifiableCredential) ? OriginalType.SD_JWT_VC_DECODED : OriginalType.SD_JWT_VC_ENCODED,
313
- format: 'vc+sd-jwt',
314
- original: verifiableCredential,
315
- credential: decodedCredential,
316
- decoded: decodedCredential.decodedPayload,
317
- }
318
- }
319
-
320
- // If the VC is not an encoded/decoded SD-JWT, we assume it will be a W3C VC
321
- const proof = CredentialMapper.getFirstProof(verifiableCredential)
322
- const original = CredentialMapper.hasJWTProofType(verifiableCredential) && proof ? proof.jwt ?? verifiableCredential : verifiableCredential
323
- if (!original) {
324
- throw Error(
325
- 'Could not determine original credential, probably it was a converted JWT credential, that is now missing the JWT value in the proof'
326
- )
327
- }
328
- const decoded = CredentialMapper.decodeVerifiableCredential(original) as JwtDecodedVerifiableCredential | IVerifiableCredential
329
-
330
- const isJwtEncoded = CredentialMapper.isJwtEncoded(original)
331
- const isJwtDecoded = CredentialMapper.isJwtDecodedCredential(original)
332
- const type = isJwtEncoded ? OriginalType.JWT_ENCODED : isJwtDecoded ? OriginalType.JWT_DECODED : OriginalType.JSONLD
333
-
334
- const credential =
335
- isJwtEncoded || isJwtDecoded
336
- ? CredentialMapper.jwtDecodedCredentialToUniformCredential(decoded as JwtDecodedVerifiableCredential, opts)
337
- : (decoded as IVerifiableCredential)
338
-
339
- const format = isJwtEncoded || isJwtDecoded ? ('jwt_vc' as const) : ('ldp_vc' as const)
340
- return {
341
- original,
342
- decoded,
343
- format,
344
- type,
345
- credential,
346
- }
347
- }
348
-
349
- public static isJwtEncoded(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
350
- return ObjectUtils.isString(original) && original.startsWith('ey') && !original.includes('~')
351
- }
352
-
353
- public static isSdJwtEncoded(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
354
- return ObjectUtils.isString(original) && original.startsWith('ey') && original.includes('~')
355
- }
356
-
357
- public static isMsoMdocOid4VPEncoded(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
358
- return ObjectUtils.isString(original) && !original.startsWith('ey') && ObjectUtils.isBase64(original)
359
- }
360
-
361
- public static isW3cCredential(credential: ICredential | SdJwtDecodedVerifiableCredential): credential is ICredential {
362
- return typeof credential === 'object' && '@context' in credential && ((credential as ICredential).type?.includes('VerifiableCredential') || false)
363
- }
364
-
365
- public static isCredential(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is OriginalVerifiableCredential {
366
- try {
367
- if (CredentialMapper.isJwtEncoded(original)) {
368
- const vc: IVerifiableCredential = CredentialMapper.toUniformCredential(original)
369
- return CredentialMapper.isW3cCredential(vc)
370
- } else if (CredentialMapper.isSdJwtEncoded(original)) {
371
- return true
372
- } else if (CredentialMapper.isMsoMdocDecodedCredential(original)) {
373
- return true
374
- } else if (CredentialMapper.isMsoMdocOid4VPEncoded(original)) {
375
- return true
376
- }
377
- return (
378
- CredentialMapper.isW3cCredential(original as ICredential) ||
379
- CredentialMapper.isSdJwtDecodedCredentialPayload(original as ICredential) ||
380
- CredentialMapper.isJwtDecodedCredential(original as OriginalVerifiableCredential) ||
381
- CredentialMapper.isSdJwtDecodedCredential(original as OriginalVerifiableCredential)
382
- )
383
- } catch (e) {
384
- return false
385
- }
386
- }
387
-
388
- public static isPresentation(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is OriginalVerifiablePresentation {
389
- try {
390
- if (CredentialMapper.isJwtEncoded(original)) {
391
- const vp: IVerifiablePresentation = CredentialMapper.toUniformPresentation(original)
392
- return CredentialMapper.isW3cPresentation(vp)
393
- } else if (CredentialMapper.isSdJwtEncoded(original)) {
394
- return false
395
- // @ts-expect-error
396
- } else if (CredentialMapper.isMsoMdocDecodedPresentation(original)) {
397
- return true
398
- } else if (CredentialMapper.isMsoMdocOid4VPEncoded(original)) {
399
- return true
400
- }
401
- return (
402
- CredentialMapper.isW3cPresentation(original as IPresentation) ||
403
- CredentialMapper.isSdJwtDecodedCredentialPayload(original as ICredential) ||
404
- CredentialMapper.isJwtDecodedPresentation(original as OriginalVerifiablePresentation) ||
405
- CredentialMapper.isSdJwtDecodedCredential(original as OriginalVerifiableCredential)
406
- )
407
- } catch (e) {
408
- return false
409
- }
410
- }
411
-
412
- public static hasProof(original: OriginalVerifiableCredential | OriginalVerifiablePresentation | string): boolean {
413
- try {
414
- if (CredentialMapper.isMsoMdocOid4VPEncoded(original)) {
415
- return false
416
- // @ts-ignore
417
- } else if (CredentialMapper.isMsoMdocDecodedCredential(original) || CredentialMapper.isMsoMdocDecodedPresentation(original)) {
418
- return true
419
- } else if (CredentialMapper.isJwtEncoded(original) || CredentialMapper.isJwtDecodedCredential(original as OriginalVerifiableCredential)) {
420
- return true
421
- } else if (CredentialMapper.isSdJwtEncoded(original) || CredentialMapper.isSdJwtDecodedCredential(original)) {
422
- //todo: we might want to revisit this
423
- return true
424
- }
425
- if (typeof original !== 'object') {
426
- return false
427
- }
428
- if ('vc' in (original as JwtDecodedVerifiableCredential) && (original as JwtDecodedVerifiableCredential).vc.proof) {
429
- return true
430
- }
431
- if ('vp' in (original as JwtDecodedVerifiablePresentation) && (original as JwtDecodedVerifiablePresentation).vp.proof) {
432
- return true
433
- }
434
- return !!(original as IVerifiableCredential | IVerifiablePresentation).proof
435
- } catch (e) {
436
- return false
437
- }
438
- }
439
-
440
- public static isW3cPresentation(
441
- presentation: UniformVerifiablePresentation | IPresentation | SdJwtDecodedVerifiableCredential | DeviceResponseCbor
442
- ): presentation is IPresentation {
443
- return (
444
- typeof presentation === 'object' &&
445
- '@context' in presentation &&
446
- ((presentation as IPresentation).type?.includes('VerifiablePresentation') || false)
447
- )
448
- }
449
-
450
- public static isSdJwtDecodedCredentialPayload(
451
- credential: ICredential | SdJwtDecodedVerifiableCredentialPayload
452
- ): credential is SdJwtDecodedVerifiableCredentialPayload {
453
- return typeof credential === 'object' && 'vct' in credential
454
- }
455
-
456
- public static areOriginalVerifiableCredentialsEqual(firstOriginal: OriginalVerifiableCredential, secondOriginal: OriginalVerifiableCredential) {
457
- // String (e.g. encoded jwt or SD-JWT)
458
- if (typeof firstOriginal === 'string' || typeof secondOriginal === 'string') {
459
- return firstOriginal === secondOriginal
460
- } else if (CredentialMapper.isMsoMdocDecodedCredential(firstOriginal) || CredentialMapper.isMsoMdocDecodedCredential(secondOriginal)) {
461
- if (!CredentialMapper.isMsoMdocDecodedCredential(firstOriginal) || !CredentialMapper.isMsoMdocDecodedCredential(secondOriginal)) {
462
- // We are doing this over here, as the rest of the logic around it would otherwise need to be adjusted substantially
463
- return false
464
- }
465
- return firstOriginal.toJson().toJsonString() === secondOriginal.toJson().toJsonString()
466
- } else if (CredentialMapper.isSdJwtDecodedCredential(firstOriginal) || CredentialMapper.isSdJwtDecodedCredential(secondOriginal)) {
467
- return firstOriginal.compactSdJwtVc === secondOriginal.compactSdJwtVc
468
- } else {
469
- // JSON-LD or decoded JWT. (should we compare the signatures instead?)
470
- return JSON.stringify(secondOriginal.proof) === JSON.stringify(firstOriginal.proof)
471
- }
472
- }
473
-
474
- private static isJsonLdAsString(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
475
- return ObjectUtils.isString(original) && original.includes('@context')
476
- }
477
-
478
- public static isSdJwtDecodedCredential(
479
- original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation
480
- ): original is SdJwtDecodedVerifiableCredential {
481
- return (
482
- typeof original === 'object' &&
483
- ((<SdJwtDecodedVerifiableCredential>original).compactSdJwtVc !== undefined || (<SdJwtDecodedVerifiableCredential>original).kbJwt !== undefined)
484
- )
485
- }
486
-
487
- public static isMsoMdocDecodedCredential(
488
- original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation
489
- ): original is MdocDocument {
490
- return typeof original === 'object' && 'issuerSigned' in original && (<MdocDocument>original).issuerSigned !== undefined
491
- }
492
-
493
- public static isMsoMdocDecodedPresentation(original: OriginalVerifiablePresentation): original is MdocDeviceResponse {
494
- return typeof original === 'object' && 'version' in original && (<MdocDeviceResponse>original).version !== undefined
495
- }
496
-
497
- public static isJwtDecodedCredential(original: OriginalVerifiableCredential): original is JwtDecodedVerifiableCredential {
498
- return (
499
- typeof original === 'object' &&
500
- (<JwtDecodedVerifiableCredential>original).vc !== undefined &&
501
- (<JwtDecodedVerifiableCredential>original).iss !== undefined
502
- )
503
- }
504
-
505
- public static isJwtDecodedPresentation(original: OriginalVerifiablePresentation): original is JwtDecodedVerifiablePresentation {
506
- return (
507
- typeof original === 'object' &&
508
- (<JwtDecodedVerifiablePresentation>original).vp !== undefined &&
509
- (<JwtDecodedVerifiablePresentation>original).iss !== undefined
510
- )
511
- }
512
-
513
- public static isWrappedSdJwtVerifiableCredential = isWrappedSdJwtVerifiableCredential
514
- public static isWrappedSdJwtVerifiablePresentation = isWrappedSdJwtVerifiablePresentation
515
- public static isWrappedW3CVerifiableCredential = isWrappedW3CVerifiableCredential
516
- public static isWrappedW3CVerifiablePresentation = isWrappedW3CVerifiablePresentation
517
- public static isWrappedMdocCredential = isWrappedMdocCredential
518
- public static isWrappedMdocPresentation = isWrappedMdocPresentation
519
-
520
- static jwtEncodedPresentationToUniformPresentation(
521
- jwt: string,
522
- makeCredentialsUniform: boolean = true,
523
- opts?: { maxTimeSkewInMS?: number }
524
- ): IPresentation {
525
- return CredentialMapper.jwtDecodedPresentationToUniformPresentation(jwt_decode(jwt), makeCredentialsUniform, opts)
526
- }
527
-
528
- static jwtDecodedPresentationToUniformPresentation(
529
- decoded: JwtDecodedVerifiablePresentation,
530
- makeCredentialsUniform: boolean = true,
531
- opts?: { maxTimeSkewInMS?: number }
532
- ): IVerifiablePresentation {
533
- const { iss, aud, jti, vp, ...rest } = decoded
534
-
535
- const presentation: IVerifiablePresentation = {
536
- ...rest,
537
- ...vp,
538
- }
539
- if (makeCredentialsUniform) {
540
- if (!vp.verifiableCredential) {
541
- throw Error('Verifiable Presentation should have a verifiable credential at this point')
542
- }
543
- presentation.verifiableCredential = vp.verifiableCredential.map((vc) => CredentialMapper.toUniformCredential(vc, opts))
544
- }
545
- if (iss) {
546
- const holder = presentation.holder
547
- if (holder) {
548
- if (holder !== iss) {
549
- throw new Error(`Inconsistent holders between JWT claim (${iss}) and VC value (${holder})`)
550
- }
551
- }
552
- presentation.holder = iss
553
- }
554
- if (aud) {
555
- const verifier = presentation.verifier
556
- if (verifier) {
557
- if (verifier !== aud) {
558
- throw new Error(`Inconsistent holders between JWT claim (${aud}) and VC value (${verifier})`)
559
- }
560
- }
561
- presentation.verifier = aud
562
- }
563
- if (jti) {
564
- const id = presentation.id
565
- if (id && id !== jti) {
566
- throw new Error(`Inconsistent VP ids between JWT claim (${jti}) and VP value (${id})`)
567
- }
568
- presentation.id = jti
569
- }
570
- return presentation
571
- }
572
-
573
- static toUniformCredential(
574
- verifiableCredential: OriginalVerifiableCredential,
575
- opts?: {
576
- maxTimeSkewInMS?: number
577
- hasher?: Hasher
578
- }
579
- ): IVerifiableCredential {
580
- if (CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential)) {
581
- return mdocDecodedCredentialToUniformCredential(verifiableCredential)
582
- }
583
- if (CredentialMapper.isSdJwtDecodedCredential(verifiableCredential)) {
584
- return sdJwtDecodedCredentialToUniformCredential(verifiableCredential, opts)
585
- }
586
- const original =
587
- typeof verifiableCredential !== 'string' && CredentialMapper.hasJWTProofType(verifiableCredential)
588
- ? CredentialMapper.getFirstProof(verifiableCredential)?.jwt
589
- : verifiableCredential
590
- if (!original) {
591
- throw Error(
592
- 'Could not determine original credential from passed in credential. Probably because a JWT proof type was present, but now is not available anymore'
593
- )
594
- }
595
- const decoded = CredentialMapper.decodeVerifiableCredential(original, opts?.hasher)
596
-
597
- const isJwtEncoded: boolean = CredentialMapper.isJwtEncoded(original)
598
- const isJwtDecoded: boolean = CredentialMapper.isJwtDecodedCredential(original)
599
- const isSdJwtEncoded = CredentialMapper.isSdJwtEncoded(original)
600
- const isMdocEncoded = CredentialMapper.isMsoMdocOid4VPEncoded(original)
601
-
602
- if (isSdJwtEncoded) {
603
- return sdJwtDecodedCredentialToUniformCredential(decoded as SdJwtDecodedVerifiableCredential, opts)
604
- } else if (isMdocEncoded) {
605
- return mdocDecodedCredentialToUniformCredential(decodeMdocIssuerSigned(original))
606
- } else if (isJwtDecoded || isJwtEncoded) {
607
- return CredentialMapper.jwtDecodedCredentialToUniformCredential(decoded as JwtDecodedVerifiableCredential, opts)
608
- } else {
609
- return decoded as IVerifiableCredential
610
- }
611
- }
612
-
613
- static toUniformPresentation(
614
- presentation: OriginalVerifiablePresentation,
615
- opts?: { maxTimeSkewInMS?: number; addContextIfMissing?: boolean }
616
- ): IVerifiablePresentation {
617
- if (CredentialMapper.isSdJwtDecodedCredential(presentation)) {
618
- throw new Error('Converting SD-JWT VC to uniform VP is not supported.')
619
- } else if (CredentialMapper.isMsoMdocDecodedPresentation(presentation)) {
620
- throw new Error('Converting MSO_MDOC to uniform VP is not supported yet.')
621
- }
622
-
623
- const proof = CredentialMapper.getFirstProof(presentation)
624
- const original = typeof presentation !== 'string' && CredentialMapper.hasJWTProofType(presentation) ? proof?.jwt : presentation
625
- if (!original) {
626
- throw Error(
627
- 'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof'
628
- )
629
- }
630
- const decoded = CredentialMapper.decodeVerifiablePresentation(original)
631
- const isJwtEncoded: boolean = CredentialMapper.isJwtEncoded(original)
632
- const isJwtDecoded: boolean = CredentialMapper.isJwtDecodedPresentation(original)
633
- const uniformPresentation =
634
- isJwtEncoded || isJwtDecoded
635
- ? CredentialMapper.jwtDecodedPresentationToUniformPresentation(decoded as JwtDecodedVerifiablePresentation, false)
636
- : (decoded as IVerifiablePresentation)
637
-
638
- // At time of writing Velocity Networks does not conform to specification. Adding bare minimum @context section to stop parsers from crashing and whatnot
639
- if (opts?.addContextIfMissing && !uniformPresentation['@context']) {
640
- uniformPresentation['@context'] = ['https://www.w3.org/2018/credentials/v1']
641
- }
642
-
643
- uniformPresentation.verifiableCredential = uniformPresentation.verifiableCredential?.map((vc) =>
644
- CredentialMapper.toUniformCredential(vc, opts)
645
- ) as IVerifiableCredential[] // We cast it because we IPresentation needs a VC. The internal Credential doesn't have the required Proof anymore (that is intended)
646
- return uniformPresentation
647
- }
648
-
649
- static jwtEncodedCredentialToUniformCredential(
650
- jwt: string,
651
- opts?: {
652
- maxTimeSkewInMS?: number
653
- }
654
- ): IVerifiableCredential {
655
- return CredentialMapper.jwtDecodedCredentialToUniformCredential(jwt_decode(jwt), opts)
656
- }
657
-
658
- static jwtDecodedCredentialToUniformCredential(
659
- decoded: JwtDecodedVerifiableCredential,
660
- opts?: { maxTimeSkewInMS?: number }
661
- ): IVerifiableCredential {
662
- const { exp, nbf, iss, vc, sub, jti, ...rest } = decoded
663
- const credential: IVerifiableCredential = {
664
- ...rest,
665
- ...vc,
666
- }
667
-
668
- const maxSkewInMS = opts?.maxTimeSkewInMS ?? 1500
669
-
670
- if (exp) {
671
- const expDate = credential.expirationDate
672
- const jwtExp = parseInt(exp.toString())
673
- // fix seconds to millisecond for the date
674
- const expDateAsStr = jwtExp < 9999999999 ? new Date(jwtExp * 1000).toISOString().replace(/\.000Z/, 'Z') : new Date(jwtExp).toISOString()
675
- if (expDate && expDate !== expDateAsStr) {
676
- const diff = Math.abs(new Date(expDateAsStr).getTime() - new Date(expDate).getTime())
677
- if (!maxSkewInMS || diff > maxSkewInMS) {
678
- throw new Error(`Inconsistent expiration dates between JWT claim (${expDateAsStr}) and VC value (${expDate})`)
679
- }
680
- }
681
- credential.expirationDate = expDateAsStr
682
- }
683
-
684
- if (nbf) {
685
- const issuanceDate = credential.issuanceDate
686
- const jwtNbf = parseInt(nbf.toString())
687
- // fix seconds to millisecs for the date
688
- const nbfDateAsStr = jwtNbf < 9999999999 ? new Date(jwtNbf * 1000).toISOString().replace(/\.000Z/, 'Z') : new Date(jwtNbf).toISOString()
689
- if (issuanceDate && issuanceDate !== nbfDateAsStr) {
690
- const diff = Math.abs(new Date(nbfDateAsStr).getTime() - new Date(issuanceDate).getTime())
691
- if (!maxSkewInMS || diff > maxSkewInMS) {
692
- throw new Error(`Inconsistent issuance dates between JWT claim (${nbfDateAsStr}) and VC value (${issuanceDate})`)
693
- }
694
- }
695
- credential.issuanceDate = nbfDateAsStr
696
- }
697
-
698
- if (iss) {
699
- const issuer = credential.issuer
700
- if (issuer) {
701
- if (typeof issuer === 'string') {
702
- if (issuer !== iss) {
703
- throw new Error(`Inconsistent issuers between JWT claim (${iss}) and VC value (${issuer})`)
704
- }
705
- } else {
706
- if (!issuer.id && Object.keys(issuer).length > 0) {
707
- // We have an issuer object with more than 1 property but without an issuer id. Set it,
708
- // because the default behaviour of did-jwt-vc is to remove the id value when creating JWTs
709
- issuer.id = iss
710
- }
711
- if (issuer.id !== iss) {
712
- throw new Error(`Inconsistent issuers between JWT claim (${iss}) and VC value (${issuer.id})`)
713
- }
714
- }
715
- } else {
716
- credential.issuer = iss
717
- }
718
- }
719
-
720
- if (sub) {
721
- const subjects = Array.isArray(credential.credentialSubject) ? credential.credentialSubject : [credential.credentialSubject]
722
- for (let i = 0; i < subjects.length; i++) {
723
- const csId = subjects[i].id
724
- if (csId && csId !== sub) {
725
- throw new Error(`Inconsistent credential subject ids between JWT claim (${sub}) and VC value (${csId})`)
726
- }
727
- Array.isArray(credential.credentialSubject) ? (credential.credentialSubject[i].id = sub) : (credential.credentialSubject.id = sub)
728
- }
729
- }
730
- if (jti) {
731
- const id = credential.id
732
- if (id && id !== jti) {
733
- throw new Error(`Inconsistent credential ids between JWT claim (${jti}) and VC value (${id})`)
734
- }
735
- credential.id = jti
736
- }
737
-
738
- return credential
739
- }
740
-
741
- static toExternalVerifiableCredential(verifiableCredential: any): IVerifiableCredential {
742
- let proof
743
- if (verifiableCredential.proof) {
744
- if (!verifiableCredential.proof.type) {
745
- throw new Error('Verifiable credential proof is missing a type')
746
- }
747
-
748
- if (!verifiableCredential.proof.created) {
749
- throw new Error('Verifiable credential proof is missing a created date')
750
- }
751
-
752
- if (!verifiableCredential.proof.proofPurpose) {
753
- throw new Error('Verifiable credential proof is missing a proof purpose')
754
- }
755
-
756
- if (!verifiableCredential.proof.verificationMethod) {
757
- throw new Error('Verifiable credential proof is missing a verification method')
758
- }
759
- proof = {
760
- ...verifiableCredential.proof,
761
- type: verifiableCredential.proof.type,
762
- created: verifiableCredential.proof.created,
763
- proofPurpose: verifiableCredential.proof.proofPurpose,
764
- verificationMethod: verifiableCredential.proof.verificationMethod,
765
- }
766
- }
767
-
768
- return {
769
- ...verifiableCredential,
770
- type: verifiableCredential.type
771
- ? typeof verifiableCredential.type === 'string'
772
- ? [verifiableCredential.type]
773
- : verifiableCredential.type
774
- : ['VerifiableCredential'],
775
- proof,
776
- }
777
- }
778
-
779
- static storedCredentialToOriginalFormat(credential: OriginalVerifiableCredential): W3CVerifiableCredential {
780
- const type: DocumentFormat = CredentialMapper.detectDocumentType(credential)
781
- if (typeof credential === 'string') {
782
- if (type === DocumentFormat.JWT) {
783
- return CredentialMapper.toCompactJWT(credential)
784
- } else if (type === DocumentFormat.JSONLD) {
785
- return JSON.parse(credential)
786
- } else if (type === DocumentFormat.SD_JWT_VC) {
787
- return credential
788
- }
789
- } else if (type === DocumentFormat.JWT && ObjectUtils.isObject(credential) && 'vc' in credential) {
790
- return CredentialMapper.toCompactJWT(credential)
791
- } else if (ObjectUtils.isObject(credential) && 'proof' in credential && credential.proof.type === 'JwtProof2020' && credential.proof.jwt) {
792
- return credential.proof.jwt
793
- } else if (
794
- ObjectUtils.isObject(credential) &&
795
- 'proof' in credential &&
796
- credential.proof.type === IProofType.SdJwtProof2024 &&
797
- credential.proof.jwt
798
- ) {
799
- return credential.proof.jwt
800
- } else if (type === DocumentFormat.SD_JWT_VC && this.isSdJwtDecodedCredential(credential)) {
801
- return credential.compactSdJwtVc
802
- }
803
- return credential as W3CVerifiableCredential
804
- }
805
-
806
- static storedPresentationToOriginalFormat(presentation: OriginalVerifiablePresentation): W3CVerifiablePresentation {
807
- const type: DocumentFormat = CredentialMapper.detectDocumentType(presentation)
808
- if (typeof presentation === 'string') {
809
- if (type === DocumentFormat.JWT) {
810
- return CredentialMapper.toCompactJWT(presentation)
811
- } else if (type === DocumentFormat.JSONLD) {
812
- return JSON.parse(presentation)
813
- }
814
- } else if (type === DocumentFormat.JWT && ObjectUtils.isObject(presentation) && 'vp' in presentation) {
815
- return CredentialMapper.toCompactJWT(presentation)
816
- } else if (
817
- ObjectUtils.isObject(presentation) &&
818
- 'proof' in presentation &&
819
- presentation.proof.type === 'JwtProof2020' &&
820
- presentation.proof.jwt
821
- ) {
822
- return presentation.proof.jwt
823
- }
824
- return presentation as W3CVerifiablePresentation
825
- }
826
-
827
- static toCompactJWT(
828
- jwtDocument: W3CVerifiableCredential | JwtDecodedVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiablePresentation | string
829
- ): string {
830
- if (!jwtDocument || CredentialMapper.detectDocumentType(jwtDocument) !== DocumentFormat.JWT) {
831
- throw Error('Cannot convert non JWT credential to JWT')
832
- }
833
- if (typeof jwtDocument === 'string') {
834
- return jwtDocument
835
- }
836
- let proof: string | undefined
837
- if (ObjectUtils.isObject(jwtDocument) && 'vp' in jwtDocument) {
838
- proof = 'jwt' in jwtDocument.vp.proof ? jwtDocument.vp.proof.jwt : jwtDocument.vp.proof
839
- } else if (ObjectUtils.isObject(jwtDocument) && 'vc' in jwtDocument) {
840
- proof = 'jwt' in jwtDocument.vc.proof ? jwtDocument.vc.proof.jwt : jwtDocument.vc.proof
841
- } else {
842
- proof = Array.isArray(jwtDocument.proof) ? jwtDocument.proof[0].jwt : jwtDocument.proof.jwt
843
- }
844
- if (!proof) {
845
- throw Error(`Could not get JWT from supplied document`)
846
- }
847
- return proof
848
- }
849
-
850
- static detectDocumentType(
851
- document:
852
- | W3CVerifiableCredential
853
- | W3CVerifiablePresentation
854
- | JwtDecodedVerifiableCredential
855
- | JwtDecodedVerifiablePresentation
856
- | SdJwtDecodedVerifiableCredential
857
- | MdocDeviceResponse
858
- | MdocDocument
859
- ): DocumentFormat {
860
- if (this.isMsoMdocOid4VPEncoded(document as any) || this.isMsoMdocDecodedCredential(document as any)) {
861
- return DocumentFormat.MSO_MDOC
862
- } else if (this.isMsoMdocDecodedPresentation(document as any) || this.isMsoMdocDecodedPresentation(document as any)) {
863
- return DocumentFormat.MSO_MDOC
864
- } else if (this.isJsonLdAsString(document)) {
865
- return DocumentFormat.JSONLD
866
- } else if (this.isJwtEncoded(document)) {
867
- return DocumentFormat.JWT
868
- } else if (this.isSdJwtEncoded(document) || this.isSdJwtDecodedCredential(document as any)) {
869
- return DocumentFormat.SD_JWT_VC
870
- }
871
-
872
- const proofs =
873
- typeof document !== 'string' &&
874
- ('vc' in document ? document.vc.proof : 'vp' in document ? document.vp.proof : (<IVerifiableCredential>document).proof)
875
- const proof: IProof = Array.isArray(proofs) ? proofs[0] : proofs
876
-
877
- if (proof?.jwt) {
878
- return DocumentFormat.JWT
879
- } else if (proof?.type === 'EthereumEip712Signature2021') {
880
- return DocumentFormat.EIP712
881
- }
882
- return DocumentFormat.JSONLD
883
- }
884
-
885
- private static hasJWTProofType(
886
- document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation
887
- ): boolean {
888
- if (typeof document === 'string') {
889
- return false
890
- }
891
- return !!CredentialMapper.getFirstProof(document)?.jwt
892
- }
893
-
894
- private static getFirstProof(
895
- document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation
896
- ): IProof | undefined {
897
- if (!document || typeof document === 'string') {
898
- return undefined
899
- }
900
- const proofs = 'vc' in document ? document.vc.proof : 'vp' in document ? document.vp.proof : (<IVerifiableCredential>document).proof
901
- return Array.isArray(proofs) ? proofs[0] : proofs
902
- }
903
-
904
- static issuerCorrelationIdFromIssuerType(issuer: IssuerType): string {
905
- if (issuer === undefined) {
906
- throw new Error('Issuer type us undefined')
907
- } else if (typeof issuer === 'string') {
908
- return issuer
909
- } else if (typeof issuer === 'object') {
910
- if ('id' in issuer) {
911
- return issuer.id
912
- } else {
913
- throw new Error('Encountered an invalid issuer object: missing id property')
914
- }
915
- } else {
916
- throw new Error('Invalid issuer type')
917
- }
918
- }
919
- }
1
+ import { IssuerType } from '@veramo/core'
2
+ import jwt_decode from 'jwt-decode'
3
+ import {
4
+ AsyncHasher,
5
+ decodeMdocDeviceResponse,
6
+ decodeMdocIssuerSigned,
7
+ decodeSdJwtVc,
8
+ decodeSdJwtVcAsync,
9
+ DocumentFormat,
10
+ Hasher,
11
+ ICredential,
12
+ IPresentation,
13
+ IProof,
14
+ IProofPurpose,
15
+ IProofType,
16
+ isWrappedMdocCredential,
17
+ isWrappedMdocPresentation,
18
+ isWrappedSdJwtVerifiableCredential,
19
+ isWrappedSdJwtVerifiablePresentation,
20
+ isWrappedW3CVerifiableCredential,
21
+ isWrappedW3CVerifiablePresentation,
22
+ IVerifiableCredential,
23
+ IVerifiablePresentation,
24
+ JwtDecodedVerifiableCredential,
25
+ JwtDecodedVerifiablePresentation,
26
+ mdocDecodedCredentialToUniformCredential,
27
+ MdocDeviceResponse,
28
+ MdocOid4vpMdocVpToken,
29
+ OriginalType,
30
+ OriginalVerifiableCredential,
31
+ OriginalVerifiablePresentation,
32
+ sdJwtDecodedCredentialToUniformCredential,
33
+ SdJwtDecodedVerifiableCredential,
34
+ SdJwtDecodedVerifiableCredentialPayload,
35
+ UniformVerifiablePresentation,
36
+ W3CVerifiableCredential,
37
+ W3CVerifiablePresentation,
38
+ WrappedMdocCredential,
39
+ WrappedSdJwtVerifiableCredential,
40
+ WrappedVerifiableCredential,
41
+ WrappedVerifiablePresentation,
42
+ WrappedW3CVerifiableCredential,
43
+ } from '../types'
44
+ import { MdocDocument } from '../types/mso_mdoc'
45
+ import { ObjectUtils } from '../utils'
46
+ import { com } from '@sphereon/kmp-mdl-mdoc'
47
+ import DeviceResponseCbor = com.sphereon.mdoc.data.device.DeviceResponseCbor
48
+
49
+ export class CredentialMapper {
50
+ /**
51
+ * Decodes a compact SD-JWT vc to it's decoded variant. This method can be used when the hasher implementation used is Async, and therefore not suitable for usage
52
+ * with the other decode methods.
53
+ */
54
+ static decodeSdJwtVcAsync(compactSdJwtVc: string, hasher: AsyncHasher) {
55
+ return decodeSdJwtVcAsync(compactSdJwtVc, hasher)
56
+ }
57
+
58
+ /**
59
+ * Decodes a Verifiable Presentation to a uniform format.
60
+ *
61
+ * When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
62
+ * an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
63
+ * instead of the compact SD-JWT.
64
+ *
65
+ * @param hasher Hasher implementation to use for SD-JWT decoding.
66
+ */
67
+ static decodeVerifiablePresentation(
68
+ presentation: OriginalVerifiablePresentation,
69
+ hasher?: Hasher
70
+ ): JwtDecodedVerifiablePresentation | IVerifiablePresentation | SdJwtDecodedVerifiableCredential | MdocOid4vpMdocVpToken | MdocDeviceResponse {
71
+ if (CredentialMapper.isJwtEncoded(presentation)) {
72
+ const payload = jwt_decode(presentation as string) as JwtDecodedVerifiablePresentation
73
+ const header = jwt_decode(presentation as string, { header: true }) as Record<string, any>
74
+
75
+ payload.vp.proof = {
76
+ type: IProofType.JwtProof2020,
77
+ created: payload.nbf,
78
+ proofPurpose: IProofPurpose.authentication,
79
+ verificationMethod: header['kid'] ?? payload.iss,
80
+ jwt: presentation as string,
81
+ }
82
+ return payload
83
+ } else if (CredentialMapper.isJwtDecodedPresentation(presentation)) {
84
+ return presentation as JwtDecodedVerifiablePresentation
85
+ } else if (CredentialMapper.isSdJwtEncoded(presentation)) {
86
+ if (!hasher) {
87
+ throw new Error('Hasher implementation is required to decode SD-JWT')
88
+ }
89
+ return decodeSdJwtVc(presentation, hasher)
90
+ } else if (CredentialMapper.isSdJwtDecodedCredential(presentation)) {
91
+ return presentation as SdJwtDecodedVerifiableCredential
92
+ } else if (CredentialMapper.isMsoMdocOid4VPEncoded(presentation)) {
93
+ return presentation as MdocOid4vpMdocVpToken
94
+ } else if (CredentialMapper.isMsoMdocDecodedPresentation(presentation)) {
95
+ return presentation as MdocDeviceResponse
96
+ } else if (CredentialMapper.isJsonLdAsString(presentation)) {
97
+ return JSON.parse(presentation as string) as IVerifiablePresentation
98
+ } else {
99
+ return presentation as IVerifiablePresentation
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Decodes a Verifiable Credential to a uniform format.
105
+ *
106
+ * When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
107
+ * an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
108
+ * instead of the compact SD-JWT.
109
+ *
110
+ * @param hasher Hasher implementation to use for SD-JWT decoding
111
+ */
112
+ static decodeVerifiableCredential(
113
+ credential: OriginalVerifiableCredential,
114
+ hasher?: Hasher
115
+ ): JwtDecodedVerifiableCredential | IVerifiableCredential | SdJwtDecodedVerifiableCredential {
116
+ if (CredentialMapper.isJwtEncoded(credential)) {
117
+ const payload = jwt_decode(credential as string) as JwtDecodedVerifiableCredential
118
+ const header = jwt_decode(credential as string, { header: true }) as Record<string, any>
119
+ payload.vc.proof = {
120
+ type: IProofType.JwtProof2020,
121
+ created: payload.nbf,
122
+ proofPurpose: IProofPurpose.authentication,
123
+ verificationMethod: header['kid'] ?? payload.iss,
124
+ jwt: credential as string,
125
+ }
126
+ return payload
127
+ } else if (CredentialMapper.isJwtDecodedCredential(credential)) {
128
+ return credential
129
+ } else if (CredentialMapper.isJsonLdAsString(credential)) {
130
+ return JSON.parse(credential as string) as IVerifiableCredential
131
+ } else if (CredentialMapper.isSdJwtEncoded(credential)) {
132
+ if (!hasher) {
133
+ throw new Error('Hasher implementation is required to decode SD-JWT')
134
+ }
135
+ return decodeSdJwtVc(credential, hasher)
136
+ } else if (CredentialMapper.isSdJwtDecodedCredential(credential)) {
137
+ return credential
138
+ } else {
139
+ return credential as IVerifiableCredential
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Converts a presentation to a wrapped presentation.
145
+ *
146
+ * When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
147
+ * an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
148
+ * instead of the compact SD-JWT.
149
+ *
150
+ * @param hasher Hasher implementation to use for SD-JWT decoding
151
+ */
152
+ static toWrappedVerifiablePresentation(
153
+ originalPresentation: OriginalVerifiablePresentation,
154
+ opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }
155
+ ): WrappedVerifiablePresentation {
156
+ // MSO_MDOC
157
+ if (CredentialMapper.isMsoMdocDecodedPresentation(originalPresentation) || CredentialMapper.isMsoMdocOid4VPEncoded(originalPresentation)) {
158
+ let deviceResponse: MdocDeviceResponse
159
+ if (CredentialMapper.isMsoMdocOid4VPEncoded(originalPresentation)) {
160
+ deviceResponse = decodeMdocDeviceResponse(originalPresentation)
161
+ } else {
162
+ deviceResponse = originalPresentation
163
+ }
164
+
165
+ const mdocCredential: WrappedMdocCredential | undefined = deviceResponse.documents
166
+ ?.map((doc) => CredentialMapper.toWrappedVerifiableCredential(doc, opts) as WrappedMdocCredential)
167
+ .find((value) => value !== undefined)
168
+ if (mdocCredential === undefined) {
169
+ throw Error('mdoc credential data corruption')
170
+ }
171
+ return {
172
+ type: CredentialMapper.isMsoMdocDecodedPresentation(originalPresentation) ? OriginalType.MSO_MDOC_DECODED : OriginalType.MSO_MDOC_ENCODED,
173
+ format: 'mso_mdoc',
174
+ original: originalPresentation,
175
+ presentation: deviceResponse,
176
+ decoded: deviceResponse,
177
+ vcs: [mdocCredential],
178
+ }
179
+ }
180
+ // SD-JWT
181
+ if (CredentialMapper.isSdJwtDecodedCredential(originalPresentation) || CredentialMapper.isSdJwtEncoded(originalPresentation)) {
182
+ let decodedPresentation: SdJwtDecodedVerifiableCredential
183
+ if (CredentialMapper.isSdJwtEncoded(originalPresentation)) {
184
+ if (!opts?.hasher) {
185
+ throw new Error('Hasher implementation is required to decode SD-JWT')
186
+ }
187
+ decodedPresentation = decodeSdJwtVc(originalPresentation, opts.hasher)
188
+ } else {
189
+ decodedPresentation = originalPresentation
190
+ }
191
+ return {
192
+ type: CredentialMapper.isSdJwtDecodedCredential(originalPresentation) ? OriginalType.SD_JWT_VC_DECODED : OriginalType.SD_JWT_VC_ENCODED,
193
+ format: 'vc+sd-jwt',
194
+ original: originalPresentation,
195
+ presentation: decodedPresentation,
196
+ decoded: decodedPresentation.decodedPayload,
197
+ // NOTE: we also include the SD-JWT VC as the VC, as the SD-JWT acts as both the VC and the VP
198
+ vcs: [CredentialMapper.toWrappedVerifiableCredential(originalPresentation, opts) as WrappedSdJwtVerifiableCredential],
199
+ }
200
+ }
201
+
202
+ // If the VP is not an encoded/decoded SD-JWT, we assume it will be a W3C VC
203
+ const proof = CredentialMapper.getFirstProof(originalPresentation)
204
+ const original =
205
+ typeof originalPresentation !== 'string' && CredentialMapper.hasJWTProofType(originalPresentation) ? proof?.jwt : originalPresentation
206
+ if (!original) {
207
+ throw Error(
208
+ 'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof'
209
+ )
210
+ }
211
+ const decoded = CredentialMapper.decodeVerifiablePresentation(original) as IVerifiablePresentation | JwtDecodedVerifiablePresentation
212
+ const isJwtEncoded: boolean = CredentialMapper.isJwtEncoded(original)
213
+ const isJwtDecoded: boolean = CredentialMapper.isJwtDecodedPresentation(original)
214
+
215
+ const type = isJwtEncoded ? OriginalType.JWT_ENCODED : isJwtDecoded ? OriginalType.JWT_DECODED : OriginalType.JSONLD
216
+ const format = isJwtDecoded || isJwtEncoded ? 'jwt_vp' : ('ldp_vp' as const)
217
+
218
+ let vp: OriginalVerifiablePresentation
219
+ if (isJwtEncoded || isJwtDecoded) {
220
+ vp = CredentialMapper.jwtDecodedPresentationToUniformPresentation(decoded as JwtDecodedVerifiablePresentation, false, opts)
221
+ } else {
222
+ vp = decoded as IVerifiablePresentation
223
+ }
224
+ if (!vp) {
225
+ throw Error(`VP key not found`)
226
+ }
227
+ const noVCs = !('verifiableCredential' in vp) || !vp.verifiableCredential || vp.verifiableCredential.length === 0
228
+ if (noVCs) {
229
+ console.warn(`Presentation without verifiable credentials. That is rare! `)
230
+ // throw Error(`VP needs to have at least one verifiable credential at this point`)
231
+ }
232
+ const vcs = noVCs
233
+ ? []
234
+ : (CredentialMapper.toWrappedVerifiableCredentials(
235
+ vp.verifiableCredential ?? [] /*.map(value => value.original)*/,
236
+ opts
237
+ ) as WrappedW3CVerifiableCredential[])
238
+
239
+ const presentation = {
240
+ ...vp,
241
+ verifiableCredential: vcs, // We overwrite the verifiableCredentials with wrapped versions, making it an InternalVerifiablePresentation. Note: we keep the singular key name of the vc data model
242
+ } as UniformVerifiablePresentation
243
+ return {
244
+ type,
245
+ format,
246
+ original,
247
+ decoded,
248
+ presentation,
249
+ vcs,
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Converts a list of credentials to a list of wrapped credentials.
255
+ *
256
+ * When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
257
+ * an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
258
+ * instead of the compact SD-JWT.
259
+ *
260
+ * @param hasher Hasher implementation to use for SD-JWT decoding
261
+ */
262
+ static toWrappedVerifiableCredentials(
263
+ verifiableCredentials: OriginalVerifiableCredential[],
264
+ opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }
265
+ ): WrappedVerifiableCredential[] {
266
+ return verifiableCredentials.map((vc) => CredentialMapper.toWrappedVerifiableCredential(vc, opts))
267
+ }
268
+
269
+ /**
270
+ * Converts a credential to a wrapped credential.
271
+ *
272
+ * When decoding SD-JWT credentials, a hasher implementation must be provided. The hasher implementation must be sync. When using
273
+ * an async hasher implementation, use the decodeSdJwtVcAsync method instead and you can provide the decoded payload to methods
274
+ * instead of the compact SD-JWT.
275
+ *
276
+ * @param hasher Hasher implementation to use for SD-JWT decoding
277
+ */
278
+ static toWrappedVerifiableCredential(
279
+ verifiableCredential: OriginalVerifiableCredential,
280
+ opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }
281
+ ): WrappedVerifiableCredential {
282
+ // MSO_MDOC
283
+ if (CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential) || CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential)) {
284
+ let mdoc: MdocDocument
285
+ if (CredentialMapper.isMsoMdocOid4VPEncoded(verifiableCredential)) {
286
+ mdoc = decodeMdocIssuerSigned(verifiableCredential)
287
+ } else {
288
+ mdoc = verifiableCredential
289
+ }
290
+ return {
291
+ type: CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential) ? OriginalType.MSO_MDOC_DECODED : OriginalType.MSO_MDOC_ENCODED,
292
+ format: 'mso_mdoc',
293
+ original: verifiableCredential,
294
+ credential: mdocDecodedCredentialToUniformCredential(mdoc),
295
+ decoded: mdoc,
296
+ }
297
+ }
298
+
299
+ // SD-JWT
300
+ if (CredentialMapper.isSdJwtDecodedCredential(verifiableCredential) || CredentialMapper.isSdJwtEncoded(verifiableCredential)) {
301
+ let decodedCredential: SdJwtDecodedVerifiableCredential
302
+ if (CredentialMapper.isSdJwtEncoded(verifiableCredential)) {
303
+ if (!opts?.hasher) {
304
+ throw new Error('Hasher implementation is required to decode SD-JWT')
305
+ }
306
+ decodedCredential = decodeSdJwtVc(verifiableCredential, opts.hasher)
307
+ } else {
308
+ decodedCredential = verifiableCredential
309
+ }
310
+
311
+ return {
312
+ type: CredentialMapper.isSdJwtDecodedCredential(verifiableCredential) ? OriginalType.SD_JWT_VC_DECODED : OriginalType.SD_JWT_VC_ENCODED,
313
+ format: 'vc+sd-jwt',
314
+ original: verifiableCredential,
315
+ credential: decodedCredential,
316
+ decoded: decodedCredential.decodedPayload,
317
+ }
318
+ }
319
+
320
+ // If the VC is not an encoded/decoded SD-JWT, we assume it will be a W3C VC
321
+ const proof = CredentialMapper.getFirstProof(verifiableCredential)
322
+ const original = CredentialMapper.hasJWTProofType(verifiableCredential) && proof ? proof.jwt ?? verifiableCredential : verifiableCredential
323
+ if (!original) {
324
+ throw Error(
325
+ 'Could not determine original credential, probably it was a converted JWT credential, that is now missing the JWT value in the proof'
326
+ )
327
+ }
328
+ const decoded = CredentialMapper.decodeVerifiableCredential(original) as JwtDecodedVerifiableCredential | IVerifiableCredential
329
+
330
+ const isJwtEncoded = CredentialMapper.isJwtEncoded(original)
331
+ const isJwtDecoded = CredentialMapper.isJwtDecodedCredential(original)
332
+ const type = isJwtEncoded ? OriginalType.JWT_ENCODED : isJwtDecoded ? OriginalType.JWT_DECODED : OriginalType.JSONLD
333
+
334
+ const credential =
335
+ isJwtEncoded || isJwtDecoded
336
+ ? CredentialMapper.jwtDecodedCredentialToUniformCredential(decoded as JwtDecodedVerifiableCredential, opts)
337
+ : (decoded as IVerifiableCredential)
338
+
339
+ const format = isJwtEncoded || isJwtDecoded ? ('jwt_vc' as const) : ('ldp_vc' as const)
340
+ return {
341
+ original,
342
+ decoded,
343
+ format,
344
+ type,
345
+ credential,
346
+ }
347
+ }
348
+
349
+ public static isJwtEncoded(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
350
+ return ObjectUtils.isString(original) && original.startsWith('ey') && !original.includes('~')
351
+ }
352
+
353
+ public static isSdJwtEncoded(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
354
+ return ObjectUtils.isString(original) && original.startsWith('ey') && original.includes('~')
355
+ }
356
+
357
+ public static isMsoMdocOid4VPEncoded(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
358
+ return ObjectUtils.isString(original) && !original.startsWith('ey') && ObjectUtils.isBase64(original)
359
+ }
360
+
361
+ public static isW3cCredential(credential: ICredential | SdJwtDecodedVerifiableCredential): credential is ICredential {
362
+ return typeof credential === 'object' && '@context' in credential && ((credential as ICredential).type?.includes('VerifiableCredential') || false)
363
+ }
364
+
365
+ public static isCredential(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is OriginalVerifiableCredential {
366
+ try {
367
+ if (CredentialMapper.isJwtEncoded(original)) {
368
+ const vc: IVerifiableCredential = CredentialMapper.toUniformCredential(original)
369
+ return CredentialMapper.isW3cCredential(vc)
370
+ } else if (CredentialMapper.isSdJwtEncoded(original)) {
371
+ return true
372
+ } else if (CredentialMapper.isMsoMdocDecodedCredential(original)) {
373
+ return true
374
+ } else if (CredentialMapper.isMsoMdocOid4VPEncoded(original)) {
375
+ return true
376
+ }
377
+ return (
378
+ CredentialMapper.isW3cCredential(original as ICredential) ||
379
+ CredentialMapper.isSdJwtDecodedCredentialPayload(original as ICredential) ||
380
+ CredentialMapper.isJwtDecodedCredential(original as OriginalVerifiableCredential) ||
381
+ CredentialMapper.isSdJwtDecodedCredential(original as OriginalVerifiableCredential)
382
+ )
383
+ } catch (e) {
384
+ return false
385
+ }
386
+ }
387
+
388
+ public static isPresentation(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is OriginalVerifiablePresentation {
389
+ try {
390
+ if (CredentialMapper.isJwtEncoded(original)) {
391
+ const vp: IVerifiablePresentation = CredentialMapper.toUniformPresentation(original)
392
+ return CredentialMapper.isW3cPresentation(vp)
393
+ } else if (CredentialMapper.isSdJwtEncoded(original)) {
394
+ return false
395
+ // @ts-expect-error
396
+ } else if (CredentialMapper.isMsoMdocDecodedPresentation(original)) {
397
+ return true
398
+ } else if (CredentialMapper.isMsoMdocOid4VPEncoded(original)) {
399
+ return true
400
+ }
401
+ return (
402
+ CredentialMapper.isW3cPresentation(original as IPresentation) ||
403
+ CredentialMapper.isSdJwtDecodedCredentialPayload(original as ICredential) ||
404
+ CredentialMapper.isJwtDecodedPresentation(original as OriginalVerifiablePresentation) ||
405
+ CredentialMapper.isSdJwtDecodedCredential(original as OriginalVerifiableCredential)
406
+ )
407
+ } catch (e) {
408
+ return false
409
+ }
410
+ }
411
+
412
+ public static hasProof(original: OriginalVerifiableCredential | OriginalVerifiablePresentation | string): boolean {
413
+ try {
414
+ if (CredentialMapper.isMsoMdocOid4VPEncoded(original)) {
415
+ return false
416
+ // @ts-ignore
417
+ } else if (CredentialMapper.isMsoMdocDecodedCredential(original) || CredentialMapper.isMsoMdocDecodedPresentation(original)) {
418
+ return true
419
+ } else if (CredentialMapper.isJwtEncoded(original) || CredentialMapper.isJwtDecodedCredential(original as OriginalVerifiableCredential)) {
420
+ return true
421
+ } else if (CredentialMapper.isSdJwtEncoded(original) || CredentialMapper.isSdJwtDecodedCredential(original)) {
422
+ //todo: we might want to revisit this
423
+ return true
424
+ }
425
+ if (typeof original !== 'object') {
426
+ return false
427
+ }
428
+ if ('vc' in (original as JwtDecodedVerifiableCredential) && (original as JwtDecodedVerifiableCredential).vc.proof) {
429
+ return true
430
+ }
431
+ if ('vp' in (original as JwtDecodedVerifiablePresentation) && (original as JwtDecodedVerifiablePresentation).vp.proof) {
432
+ return true
433
+ }
434
+ return !!(original as IVerifiableCredential | IVerifiablePresentation).proof
435
+ } catch (e) {
436
+ return false
437
+ }
438
+ }
439
+
440
+ public static isW3cPresentation(
441
+ presentation: UniformVerifiablePresentation | IPresentation | SdJwtDecodedVerifiableCredential | DeviceResponseCbor
442
+ ): presentation is IPresentation {
443
+ return (
444
+ typeof presentation === 'object' &&
445
+ '@context' in presentation &&
446
+ ((presentation as IPresentation).type?.includes('VerifiablePresentation') || false)
447
+ )
448
+ }
449
+
450
+ public static isSdJwtDecodedCredentialPayload(
451
+ credential: ICredential | SdJwtDecodedVerifiableCredentialPayload
452
+ ): credential is SdJwtDecodedVerifiableCredentialPayload {
453
+ return typeof credential === 'object' && 'vct' in credential
454
+ }
455
+
456
+ public static areOriginalVerifiableCredentialsEqual(firstOriginal: OriginalVerifiableCredential, secondOriginal: OriginalVerifiableCredential) {
457
+ // String (e.g. encoded jwt or SD-JWT)
458
+ if (typeof firstOriginal === 'string' || typeof secondOriginal === 'string') {
459
+ return firstOriginal === secondOriginal
460
+ } else if (CredentialMapper.isMsoMdocDecodedCredential(firstOriginal) || CredentialMapper.isMsoMdocDecodedCredential(secondOriginal)) {
461
+ if (!CredentialMapper.isMsoMdocDecodedCredential(firstOriginal) || !CredentialMapper.isMsoMdocDecodedCredential(secondOriginal)) {
462
+ // We are doing this over here, as the rest of the logic around it would otherwise need to be adjusted substantially
463
+ return false
464
+ }
465
+ return firstOriginal.toJson().toJsonString() === secondOriginal.toJson().toJsonString()
466
+ } else if (CredentialMapper.isSdJwtDecodedCredential(firstOriginal) || CredentialMapper.isSdJwtDecodedCredential(secondOriginal)) {
467
+ return firstOriginal.compactSdJwtVc === secondOriginal.compactSdJwtVc
468
+ } else {
469
+ // JSON-LD or decoded JWT. (should we compare the signatures instead?)
470
+ return JSON.stringify(secondOriginal.proof) === JSON.stringify(firstOriginal.proof)
471
+ }
472
+ }
473
+
474
+ private static isJsonLdAsString(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
475
+ return ObjectUtils.isString(original) && original.includes('@context')
476
+ }
477
+
478
+ public static isSdJwtDecodedCredential(
479
+ original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation
480
+ ): original is SdJwtDecodedVerifiableCredential {
481
+ return (
482
+ typeof original === 'object' &&
483
+ ((<SdJwtDecodedVerifiableCredential>original).compactSdJwtVc !== undefined || (<SdJwtDecodedVerifiableCredential>original).kbJwt !== undefined)
484
+ )
485
+ }
486
+
487
+ public static isMsoMdocDecodedCredential(
488
+ original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation
489
+ ): original is MdocDocument {
490
+ return typeof original === 'object' && 'issuerSigned' in original && (<MdocDocument>original).issuerSigned !== undefined
491
+ }
492
+
493
+ public static isMsoMdocDecodedPresentation(original: OriginalVerifiablePresentation): original is MdocDeviceResponse {
494
+ return typeof original === 'object' && 'version' in original && (<MdocDeviceResponse>original).version !== undefined
495
+ }
496
+
497
+ public static isJwtDecodedCredential(original: OriginalVerifiableCredential): original is JwtDecodedVerifiableCredential {
498
+ return (
499
+ typeof original === 'object' &&
500
+ (<JwtDecodedVerifiableCredential>original).vc !== undefined &&
501
+ (<JwtDecodedVerifiableCredential>original).iss !== undefined
502
+ )
503
+ }
504
+
505
+ public static isJwtDecodedPresentation(original: OriginalVerifiablePresentation): original is JwtDecodedVerifiablePresentation {
506
+ return (
507
+ typeof original === 'object' &&
508
+ (<JwtDecodedVerifiablePresentation>original).vp !== undefined &&
509
+ (<JwtDecodedVerifiablePresentation>original).iss !== undefined
510
+ )
511
+ }
512
+
513
+ public static isWrappedSdJwtVerifiableCredential = isWrappedSdJwtVerifiableCredential
514
+ public static isWrappedSdJwtVerifiablePresentation = isWrappedSdJwtVerifiablePresentation
515
+ public static isWrappedW3CVerifiableCredential = isWrappedW3CVerifiableCredential
516
+ public static isWrappedW3CVerifiablePresentation = isWrappedW3CVerifiablePresentation
517
+ public static isWrappedMdocCredential = isWrappedMdocCredential
518
+ public static isWrappedMdocPresentation = isWrappedMdocPresentation
519
+
520
+ static jwtEncodedPresentationToUniformPresentation(
521
+ jwt: string,
522
+ makeCredentialsUniform: boolean = true,
523
+ opts?: { maxTimeSkewInMS?: number }
524
+ ): IPresentation {
525
+ return CredentialMapper.jwtDecodedPresentationToUniformPresentation(jwt_decode(jwt), makeCredentialsUniform, opts)
526
+ }
527
+
528
+ static jwtDecodedPresentationToUniformPresentation(
529
+ decoded: JwtDecodedVerifiablePresentation,
530
+ makeCredentialsUniform: boolean = true,
531
+ opts?: { maxTimeSkewInMS?: number }
532
+ ): IVerifiablePresentation {
533
+ const { iss, aud, jti, vp, ...rest } = decoded
534
+
535
+ const presentation: IVerifiablePresentation = {
536
+ ...rest,
537
+ ...vp,
538
+ }
539
+ if (makeCredentialsUniform) {
540
+ if (!vp.verifiableCredential) {
541
+ throw Error('Verifiable Presentation should have a verifiable credential at this point')
542
+ }
543
+ presentation.verifiableCredential = vp.verifiableCredential.map((vc) => CredentialMapper.toUniformCredential(vc, opts))
544
+ }
545
+ if (iss) {
546
+ const holder = presentation.holder
547
+ if (holder) {
548
+ if (holder !== iss) {
549
+ throw new Error(`Inconsistent holders between JWT claim (${iss}) and VC value (${holder})`)
550
+ }
551
+ }
552
+ presentation.holder = iss
553
+ }
554
+ if (aud) {
555
+ const verifier = presentation.verifier
556
+ if (verifier) {
557
+ if (verifier !== aud) {
558
+ throw new Error(`Inconsistent holders between JWT claim (${aud}) and VC value (${verifier})`)
559
+ }
560
+ }
561
+ presentation.verifier = aud
562
+ }
563
+ if (jti) {
564
+ const id = presentation.id
565
+ if (id && id !== jti) {
566
+ throw new Error(`Inconsistent VP ids between JWT claim (${jti}) and VP value (${id})`)
567
+ }
568
+ presentation.id = jti
569
+ }
570
+ return presentation
571
+ }
572
+
573
+ static toUniformCredential(
574
+ verifiableCredential: OriginalVerifiableCredential,
575
+ opts?: {
576
+ maxTimeSkewInMS?: number
577
+ hasher?: Hasher
578
+ }
579
+ ): IVerifiableCredential {
580
+ if (CredentialMapper.isMsoMdocDecodedCredential(verifiableCredential)) {
581
+ return mdocDecodedCredentialToUniformCredential(verifiableCredential)
582
+ }
583
+ if (CredentialMapper.isSdJwtDecodedCredential(verifiableCredential)) {
584
+ return sdJwtDecodedCredentialToUniformCredential(verifiableCredential, opts)
585
+ }
586
+ const original =
587
+ typeof verifiableCredential !== 'string' && CredentialMapper.hasJWTProofType(verifiableCredential)
588
+ ? CredentialMapper.getFirstProof(verifiableCredential)?.jwt
589
+ : verifiableCredential
590
+ if (!original) {
591
+ throw Error(
592
+ 'Could not determine original credential from passed in credential. Probably because a JWT proof type was present, but now is not available anymore'
593
+ )
594
+ }
595
+ const decoded = CredentialMapper.decodeVerifiableCredential(original, opts?.hasher)
596
+
597
+ const isJwtEncoded: boolean = CredentialMapper.isJwtEncoded(original)
598
+ const isJwtDecoded: boolean = CredentialMapper.isJwtDecodedCredential(original)
599
+ const isSdJwtEncoded = CredentialMapper.isSdJwtEncoded(original)
600
+ const isMdocEncoded = CredentialMapper.isMsoMdocOid4VPEncoded(original)
601
+
602
+ if (isSdJwtEncoded) {
603
+ return sdJwtDecodedCredentialToUniformCredential(decoded as SdJwtDecodedVerifiableCredential, opts)
604
+ } else if (isMdocEncoded) {
605
+ return mdocDecodedCredentialToUniformCredential(decodeMdocIssuerSigned(original))
606
+ } else if (isJwtDecoded || isJwtEncoded) {
607
+ return CredentialMapper.jwtDecodedCredentialToUniformCredential(decoded as JwtDecodedVerifiableCredential, opts)
608
+ } else {
609
+ return decoded as IVerifiableCredential
610
+ }
611
+ }
612
+
613
+ static toUniformPresentation(
614
+ presentation: OriginalVerifiablePresentation,
615
+ opts?: { maxTimeSkewInMS?: number; addContextIfMissing?: boolean }
616
+ ): IVerifiablePresentation {
617
+ if (CredentialMapper.isSdJwtDecodedCredential(presentation)) {
618
+ throw new Error('Converting SD-JWT VC to uniform VP is not supported.')
619
+ } else if (CredentialMapper.isMsoMdocDecodedPresentation(presentation)) {
620
+ throw new Error('Converting MSO_MDOC to uniform VP is not supported yet.')
621
+ }
622
+
623
+ const proof = CredentialMapper.getFirstProof(presentation)
624
+ const original = typeof presentation !== 'string' && CredentialMapper.hasJWTProofType(presentation) ? proof?.jwt : presentation
625
+ if (!original) {
626
+ throw Error(
627
+ 'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof'
628
+ )
629
+ }
630
+ const decoded = CredentialMapper.decodeVerifiablePresentation(original)
631
+ const isJwtEncoded: boolean = CredentialMapper.isJwtEncoded(original)
632
+ const isJwtDecoded: boolean = CredentialMapper.isJwtDecodedPresentation(original)
633
+ const uniformPresentation =
634
+ isJwtEncoded || isJwtDecoded
635
+ ? CredentialMapper.jwtDecodedPresentationToUniformPresentation(decoded as JwtDecodedVerifiablePresentation, false)
636
+ : (decoded as IVerifiablePresentation)
637
+
638
+ // At time of writing Velocity Networks does not conform to specification. Adding bare minimum @context section to stop parsers from crashing and whatnot
639
+ if (opts?.addContextIfMissing && !uniformPresentation['@context']) {
640
+ uniformPresentation['@context'] = ['https://www.w3.org/2018/credentials/v1']
641
+ }
642
+
643
+ uniformPresentation.verifiableCredential = uniformPresentation.verifiableCredential?.map((vc) =>
644
+ CredentialMapper.toUniformCredential(vc, opts)
645
+ ) as IVerifiableCredential[] // We cast it because we IPresentation needs a VC. The internal Credential doesn't have the required Proof anymore (that is intended)
646
+ return uniformPresentation
647
+ }
648
+
649
+ static jwtEncodedCredentialToUniformCredential(
650
+ jwt: string,
651
+ opts?: {
652
+ maxTimeSkewInMS?: number
653
+ }
654
+ ): IVerifiableCredential {
655
+ return CredentialMapper.jwtDecodedCredentialToUniformCredential(jwt_decode(jwt), opts)
656
+ }
657
+
658
+ static jwtDecodedCredentialToUniformCredential(
659
+ decoded: JwtDecodedVerifiableCredential,
660
+ opts?: { maxTimeSkewInMS?: number }
661
+ ): IVerifiableCredential {
662
+ const { exp, nbf, iss, vc, sub, jti, ...rest } = decoded
663
+ const credential: IVerifiableCredential = {
664
+ ...rest,
665
+ ...vc,
666
+ }
667
+
668
+ const maxSkewInMS = opts?.maxTimeSkewInMS ?? 1500
669
+
670
+ if (exp) {
671
+ const expDate = credential.expirationDate
672
+ const jwtExp = parseInt(exp.toString())
673
+ // fix seconds to millisecond for the date
674
+ const expDateAsStr = jwtExp < 9999999999 ? new Date(jwtExp * 1000).toISOString().replace(/\.000Z/, 'Z') : new Date(jwtExp).toISOString()
675
+ if (expDate && expDate !== expDateAsStr) {
676
+ const diff = Math.abs(new Date(expDateAsStr).getTime() - new Date(expDate).getTime())
677
+ if (!maxSkewInMS || diff > maxSkewInMS) {
678
+ throw new Error(`Inconsistent expiration dates between JWT claim (${expDateAsStr}) and VC value (${expDate})`)
679
+ }
680
+ }
681
+ credential.expirationDate = expDateAsStr
682
+ }
683
+
684
+ if (nbf) {
685
+ const issuanceDate = credential.issuanceDate
686
+ const jwtNbf = parseInt(nbf.toString())
687
+ // fix seconds to millisecs for the date
688
+ const nbfDateAsStr = jwtNbf < 9999999999 ? new Date(jwtNbf * 1000).toISOString().replace(/\.000Z/, 'Z') : new Date(jwtNbf).toISOString()
689
+ if (issuanceDate && issuanceDate !== nbfDateAsStr) {
690
+ const diff = Math.abs(new Date(nbfDateAsStr).getTime() - new Date(issuanceDate).getTime())
691
+ if (!maxSkewInMS || diff > maxSkewInMS) {
692
+ throw new Error(`Inconsistent issuance dates between JWT claim (${nbfDateAsStr}) and VC value (${issuanceDate})`)
693
+ }
694
+ }
695
+ credential.issuanceDate = nbfDateAsStr
696
+ }
697
+
698
+ if (iss) {
699
+ const issuer = credential.issuer
700
+ if (issuer) {
701
+ if (typeof issuer === 'string') {
702
+ if (issuer !== iss) {
703
+ throw new Error(`Inconsistent issuers between JWT claim (${iss}) and VC value (${issuer})`)
704
+ }
705
+ } else {
706
+ if (!issuer.id && Object.keys(issuer).length > 0) {
707
+ // We have an issuer object with more than 1 property but without an issuer id. Set it,
708
+ // because the default behaviour of did-jwt-vc is to remove the id value when creating JWTs
709
+ issuer.id = iss
710
+ }
711
+ if (issuer.id !== iss) {
712
+ throw new Error(`Inconsistent issuers between JWT claim (${iss}) and VC value (${issuer.id})`)
713
+ }
714
+ }
715
+ } else {
716
+ credential.issuer = iss
717
+ }
718
+ }
719
+
720
+ if (sub) {
721
+ const subjects = Array.isArray(credential.credentialSubject) ? credential.credentialSubject : [credential.credentialSubject]
722
+ for (let i = 0; i < subjects.length; i++) {
723
+ const csId = subjects[i].id
724
+ if (csId && csId !== sub) {
725
+ throw new Error(`Inconsistent credential subject ids between JWT claim (${sub}) and VC value (${csId})`)
726
+ }
727
+ Array.isArray(credential.credentialSubject) ? (credential.credentialSubject[i].id = sub) : (credential.credentialSubject.id = sub)
728
+ }
729
+ }
730
+ if (jti) {
731
+ const id = credential.id
732
+ if (id && id !== jti) {
733
+ throw new Error(`Inconsistent credential ids between JWT claim (${jti}) and VC value (${id})`)
734
+ }
735
+ credential.id = jti
736
+ }
737
+
738
+ return credential
739
+ }
740
+
741
+ static toExternalVerifiableCredential(verifiableCredential: any): IVerifiableCredential {
742
+ let proof
743
+ if (verifiableCredential.proof) {
744
+ if (!verifiableCredential.proof.type) {
745
+ throw new Error('Verifiable credential proof is missing a type')
746
+ }
747
+
748
+ if (!verifiableCredential.proof.created) {
749
+ throw new Error('Verifiable credential proof is missing a created date')
750
+ }
751
+
752
+ if (!verifiableCredential.proof.proofPurpose) {
753
+ throw new Error('Verifiable credential proof is missing a proof purpose')
754
+ }
755
+
756
+ if (!verifiableCredential.proof.verificationMethod) {
757
+ throw new Error('Verifiable credential proof is missing a verification method')
758
+ }
759
+ proof = {
760
+ ...verifiableCredential.proof,
761
+ type: verifiableCredential.proof.type,
762
+ created: verifiableCredential.proof.created,
763
+ proofPurpose: verifiableCredential.proof.proofPurpose,
764
+ verificationMethod: verifiableCredential.proof.verificationMethod,
765
+ }
766
+ }
767
+
768
+ return {
769
+ ...verifiableCredential,
770
+ type: verifiableCredential.type
771
+ ? typeof verifiableCredential.type === 'string'
772
+ ? [verifiableCredential.type]
773
+ : verifiableCredential.type
774
+ : ['VerifiableCredential'],
775
+ proof,
776
+ }
777
+ }
778
+
779
+ static storedCredentialToOriginalFormat(credential: OriginalVerifiableCredential): W3CVerifiableCredential {
780
+ const type: DocumentFormat = CredentialMapper.detectDocumentType(credential)
781
+ if (typeof credential === 'string') {
782
+ if (type === DocumentFormat.JWT) {
783
+ return CredentialMapper.toCompactJWT(credential)
784
+ } else if (type === DocumentFormat.JSONLD) {
785
+ return JSON.parse(credential)
786
+ } else if (type === DocumentFormat.SD_JWT_VC) {
787
+ return credential
788
+ }
789
+ } else if (type === DocumentFormat.JWT && ObjectUtils.isObject(credential) && 'vc' in credential) {
790
+ return CredentialMapper.toCompactJWT(credential)
791
+ } else if (ObjectUtils.isObject(credential) && 'proof' in credential && credential.proof.type === 'JwtProof2020' && credential.proof.jwt) {
792
+ return credential.proof.jwt
793
+ } else if (
794
+ ObjectUtils.isObject(credential) &&
795
+ 'proof' in credential &&
796
+ credential.proof.type === IProofType.SdJwtProof2024 &&
797
+ credential.proof.jwt
798
+ ) {
799
+ return credential.proof.jwt
800
+ } else if (type === DocumentFormat.SD_JWT_VC && this.isSdJwtDecodedCredential(credential)) {
801
+ return credential.compactSdJwtVc
802
+ }
803
+ return credential as W3CVerifiableCredential
804
+ }
805
+
806
+ static storedPresentationToOriginalFormat(presentation: OriginalVerifiablePresentation): W3CVerifiablePresentation {
807
+ const type: DocumentFormat = CredentialMapper.detectDocumentType(presentation)
808
+ if (typeof presentation === 'string') {
809
+ if (type === DocumentFormat.JWT) {
810
+ return CredentialMapper.toCompactJWT(presentation)
811
+ } else if (type === DocumentFormat.JSONLD) {
812
+ return JSON.parse(presentation)
813
+ }
814
+ } else if (type === DocumentFormat.JWT && ObjectUtils.isObject(presentation) && 'vp' in presentation) {
815
+ return CredentialMapper.toCompactJWT(presentation)
816
+ } else if (
817
+ ObjectUtils.isObject(presentation) &&
818
+ 'proof' in presentation &&
819
+ presentation.proof.type === 'JwtProof2020' &&
820
+ presentation.proof.jwt
821
+ ) {
822
+ return presentation.proof.jwt
823
+ }
824
+ return presentation as W3CVerifiablePresentation
825
+ }
826
+
827
+ static toCompactJWT(
828
+ jwtDocument: W3CVerifiableCredential | JwtDecodedVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiablePresentation | string
829
+ ): string {
830
+ if (!jwtDocument || CredentialMapper.detectDocumentType(jwtDocument) !== DocumentFormat.JWT) {
831
+ throw Error('Cannot convert non JWT credential to JWT')
832
+ }
833
+ if (typeof jwtDocument === 'string') {
834
+ return jwtDocument
835
+ }
836
+ let proof: string | undefined
837
+ if (ObjectUtils.isObject(jwtDocument) && 'vp' in jwtDocument) {
838
+ proof = 'jwt' in jwtDocument.vp.proof ? jwtDocument.vp.proof.jwt : jwtDocument.vp.proof
839
+ } else if (ObjectUtils.isObject(jwtDocument) && 'vc' in jwtDocument) {
840
+ proof = 'jwt' in jwtDocument.vc.proof ? jwtDocument.vc.proof.jwt : jwtDocument.vc.proof
841
+ } else {
842
+ proof = Array.isArray(jwtDocument.proof) ? jwtDocument.proof[0].jwt : jwtDocument.proof.jwt
843
+ }
844
+ if (!proof) {
845
+ throw Error(`Could not get JWT from supplied document`)
846
+ }
847
+ return proof
848
+ }
849
+
850
+ static detectDocumentType(
851
+ document:
852
+ | W3CVerifiableCredential
853
+ | W3CVerifiablePresentation
854
+ | JwtDecodedVerifiableCredential
855
+ | JwtDecodedVerifiablePresentation
856
+ | SdJwtDecodedVerifiableCredential
857
+ | MdocDeviceResponse
858
+ | MdocDocument
859
+ ): DocumentFormat {
860
+ if (this.isMsoMdocOid4VPEncoded(document as any) || this.isMsoMdocDecodedCredential(document as any)) {
861
+ return DocumentFormat.MSO_MDOC
862
+ } else if (this.isMsoMdocDecodedPresentation(document as any) || this.isMsoMdocDecodedPresentation(document as any)) {
863
+ return DocumentFormat.MSO_MDOC
864
+ } else if (this.isJsonLdAsString(document)) {
865
+ return DocumentFormat.JSONLD
866
+ } else if (this.isJwtEncoded(document)) {
867
+ return DocumentFormat.JWT
868
+ } else if (this.isSdJwtEncoded(document) || this.isSdJwtDecodedCredential(document as any)) {
869
+ return DocumentFormat.SD_JWT_VC
870
+ }
871
+
872
+ const proofs =
873
+ typeof document !== 'string' &&
874
+ ('vc' in document ? document.vc.proof : 'vp' in document ? document.vp.proof : (<IVerifiableCredential>document).proof)
875
+ const proof: IProof = Array.isArray(proofs) ? proofs[0] : proofs
876
+
877
+ if (proof?.jwt) {
878
+ return DocumentFormat.JWT
879
+ } else if (proof?.type === 'EthereumEip712Signature2021') {
880
+ return DocumentFormat.EIP712
881
+ }
882
+ return DocumentFormat.JSONLD
883
+ }
884
+
885
+ private static hasJWTProofType(
886
+ document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation
887
+ ): boolean {
888
+ if (typeof document === 'string') {
889
+ return false
890
+ }
891
+ return !!CredentialMapper.getFirstProof(document)?.jwt
892
+ }
893
+
894
+ private static getFirstProof(
895
+ document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation
896
+ ): IProof | undefined {
897
+ if (!document || typeof document === 'string') {
898
+ return undefined
899
+ }
900
+ const proofs = 'vc' in document ? document.vc.proof : 'vp' in document ? document.vp.proof : (<IVerifiableCredential>document).proof
901
+ return Array.isArray(proofs) ? proofs[0] : proofs
902
+ }
903
+
904
+ static issuerCorrelationIdFromIssuerType(issuer: IssuerType): string {
905
+ if (issuer === undefined) {
906
+ throw new Error('Issuer type us undefined')
907
+ } else if (typeof issuer === 'string') {
908
+ return issuer
909
+ } else if (typeof issuer === 'object') {
910
+ if ('id' in issuer) {
911
+ return issuer.id
912
+ } else {
913
+ throw new Error('Encountered an invalid issuer object: missing id property')
914
+ }
915
+ } else {
916
+ throw new Error('Invalid issuer type')
917
+ }
918
+ }
919
+ }