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