@sphereon/ssi-types 0.17.6-unstable.8 → 0.18.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.
@@ -12,17 +12,49 @@ import {
12
12
  OriginalType,
13
13
  OriginalVerifiableCredential,
14
14
  OriginalVerifiablePresentation,
15
- PresentationFormat,
16
15
  UniformVerifiablePresentation,
17
16
  W3CVerifiableCredential,
18
17
  W3CVerifiablePresentation,
19
18
  WrappedVerifiableCredential,
20
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,
21
33
  } from '../types'
22
34
  import { ObjectUtils } from '../utils'
23
35
 
24
36
  export class CredentialMapper {
25
- static decodeVerifiablePresentation(presentation: OriginalVerifiablePresentation): JwtDecodedVerifiablePresentation | IVerifiablePresentation {
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 {
26
58
  if (CredentialMapper.isJwtEncoded(presentation)) {
27
59
  const payload = jwt_decode(presentation as string) as JwtDecodedVerifiablePresentation
28
60
  const header = jwt_decode(presentation as string, { header: true }) as Record<string, any>
@@ -37,6 +69,13 @@ export class CredentialMapper {
37
69
  return payload
38
70
  } else if (CredentialMapper.isJwtDecodedPresentation(presentation)) {
39
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
40
79
  } else if (CredentialMapper.isJsonLdAsString(presentation)) {
41
80
  return JSON.parse(presentation as string) as IVerifiablePresentation
42
81
  } else {
@@ -44,7 +83,19 @@ export class CredentialMapper {
44
83
  }
45
84
  }
46
85
 
47
- static decodeVerifiableCredential(credential: OriginalVerifiableCredential): JwtDecodedVerifiableCredential | IVerifiableCredential {
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 {
48
99
  if (CredentialMapper.isJwtEncoded(credential)) {
49
100
  const payload = jwt_decode(credential as string) as JwtDecodedVerifiableCredential
50
101
  const header = jwt_decode(credential as string, { header: true }) as Record<string, any>
@@ -57,18 +108,57 @@ export class CredentialMapper {
57
108
  }
58
109
  return payload
59
110
  } else if (CredentialMapper.isJwtDecodedCredential(credential)) {
60
- return credential as JwtDecodedVerifiableCredential
111
+ return credential
61
112
  } else if (CredentialMapper.isJsonLdAsString(credential)) {
62
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
63
121
  } else {
64
122
  return credential as IVerifiableCredential
65
123
  }
66
124
  }
67
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
+ */
68
135
  static toWrappedVerifiablePresentation(
69
136
  originalPresentation: OriginalVerifiablePresentation,
70
- opts?: { maxTimeSkewInMS?: number }
137
+ opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }
71
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
72
162
  const proof = CredentialMapper.getFirstProof(originalPresentation)
73
163
  const original =
74
164
  typeof originalPresentation !== 'string' && CredentialMapper.hasJWTProofType(originalPresentation) ? proof?.jwt : originalPresentation
@@ -77,12 +167,12 @@ export class CredentialMapper {
77
167
  'Could not determine original presentation, probably it was a converted JWT presentation, that is now missing the JWT value in the proof'
78
168
  )
79
169
  }
80
- const decoded = CredentialMapper.decodeVerifiablePresentation(original)
170
+ const decoded = CredentialMapper.decodeVerifiablePresentation(original) as IVerifiablePresentation | JwtDecodedVerifiablePresentation
81
171
  const isJwtEncoded: boolean = CredentialMapper.isJwtEncoded(original)
82
172
  const isJwtDecoded: boolean = CredentialMapper.isJwtDecodedPresentation(original)
83
173
 
84
174
  const type = isJwtEncoded ? OriginalType.JWT_ENCODED : isJwtDecoded ? OriginalType.JWT_DECODED : OriginalType.JSONLD
85
- const format: PresentationFormat = isJwtDecoded || isJwtEncoded ? 'jwt_vp' : 'ldp_vp'
175
+ const format = isJwtDecoded || isJwtEncoded ? 'jwt_vp' : ('ldp_vp' as const)
86
176
 
87
177
  let vp: OriginalVerifiablePresentation
88
178
  if (isJwtEncoded || isJwtDecoded) {
@@ -93,10 +183,10 @@ export class CredentialMapper {
93
183
  if (!vp || !('verifiableCredential' in vp) || !vp.verifiableCredential || vp.verifiableCredential.length === 0) {
94
184
  throw Error(`VP needs to have at least one verifiable credential at this point`)
95
185
  }
96
- const vcs: WrappedVerifiableCredential[] = CredentialMapper.toWrappedVerifiableCredentials(
186
+ const vcs = CredentialMapper.toWrappedVerifiableCredentials(
97
187
  vp.verifiableCredential /*.map(value => value.original)*/,
98
188
  opts
99
- )
189
+ ) as WrappedW3CVerifiableCredential[]
100
190
 
101
191
  const presentation = {
102
192
  ...vp,
@@ -112,17 +202,57 @@ export class CredentialMapper {
112
202
  }
113
203
  }
114
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
+ */
115
214
  static toWrappedVerifiableCredentials(
116
215
  verifiableCredentials: OriginalVerifiableCredential[],
117
- opts?: { maxTimeSkewInMS?: number }
216
+ opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }
118
217
  ): WrappedVerifiableCredential[] {
119
218
  return verifiableCredentials.map((vc) => CredentialMapper.toWrappedVerifiableCredential(vc, opts))
120
219
  }
121
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
+ */
122
230
  static toWrappedVerifiableCredential(
123
231
  verifiableCredential: OriginalVerifiableCredential,
124
- opts?: { maxTimeSkewInMS?: number }
232
+ opts?: { maxTimeSkewInMS?: number; hasher?: Hasher }
125
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
126
256
  const proof = CredentialMapper.getFirstProof(verifiableCredential)
127
257
  const original = CredentialMapper.hasJWTProofType(verifiableCredential) && proof ? proof.jwt ?? verifiableCredential : verifiableCredential
128
258
  if (!original) {
@@ -130,7 +260,7 @@ export class CredentialMapper {
130
260
  'Could not determine original credential, probably it was a converted JWT credential, that is now missing the JWT value in the proof'
131
261
  )
132
262
  }
133
- const decoded = CredentialMapper.decodeVerifiableCredential(original)
263
+ const decoded = CredentialMapper.decodeVerifiableCredential(original) as JwtDecodedVerifiableCredential | IVerifiableCredential
134
264
 
135
265
  const isJwtEncoded = CredentialMapper.isJwtEncoded(original)
136
266
  const isJwtDecoded = CredentialMapper.isJwtDecodedCredential(original)
@@ -141,7 +271,7 @@ export class CredentialMapper {
141
271
  ? CredentialMapper.jwtDecodedCredentialToUniformCredential(decoded as JwtDecodedVerifiableCredential, opts)
142
272
  : (decoded as IVerifiableCredential)
143
273
 
144
- const format = isJwtEncoded || isJwtDecoded ? 'jwt_vc' : 'ldp_vc'
274
+ const format = isJwtEncoded || isJwtDecoded ? ('jwt_vc' as const) : ('ldp_vc' as const)
145
275
  return {
146
276
  original,
147
277
  decoded,
@@ -151,22 +281,65 @@ export class CredentialMapper {
151
281
  }
152
282
  }
153
283
 
154
- public static isJwtEncoded(original: OriginalVerifiableCredential | OriginalVerifiablePresentation) {
155
- return ObjectUtils.isString(original) && (original as string).startsWith('ey')
284
+ public static isJwtEncoded(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
285
+ return ObjectUtils.isString(original) && original.startsWith('ey') && !original.includes('~')
156
286
  }
157
287
 
158
- private static isJsonLdAsString(original: OriginalVerifiableCredential | OriginalVerifiablePresentation) {
159
- return ObjectUtils.isString(original) && (original as string).includes('@context')
288
+ public static isSdJwtEncoded(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
289
+ return ObjectUtils.isString(original) && original.startsWith('ey') && original.includes('~')
160
290
  }
161
291
 
162
- public static isJwtDecodedCredential(original: OriginalVerifiableCredential): boolean {
163
- return (<JwtDecodedVerifiableCredential>original)['vc'] !== undefined && (<JwtDecodedVerifiableCredential>original)['iss'] !== undefined
292
+ public static isW3cCredential(credential: ICredential | SdJwtDecodedVerifiableCredential): credential is ICredential {
293
+ return '@context' in credential && ((credential as ICredential).type?.includes('VerifiableCredential') || false)
164
294
  }
165
295
 
166
- public static isJwtDecodedPresentation(original: OriginalVerifiablePresentation): boolean {
167
- return (<JwtDecodedVerifiablePresentation>original)['vp'] !== undefined && (<JwtDecodedVerifiablePresentation>original)['iss'] !== undefined
296
+ public static isW3cPresentation(
297
+ presentation: UniformVerifiablePresentation | IPresentation | SdJwtDecodedVerifiableCredential
298
+ ): presentation is IPresentation {
299
+ return '@context' in presentation && ((presentation as IPresentation).type?.includes('VerifiablePresentation') || false)
168
300
  }
169
301
 
302
+ public static isSdJwtDecodedCredentialPayload(
303
+ credential: ICredential | SdJwtDecodedVerifiableCredentialPayload
304
+ ): credential is SdJwtDecodedVerifiableCredentialPayload {
305
+ return 'vct' in credential
306
+ }
307
+
308
+ public static areOriginalVerifiableCredentialsEqual(firstOriginal: OriginalVerifiableCredential, secondOriginal: OriginalVerifiableCredential) {
309
+ // String (e.g. encoded jwt or SD-JWT)
310
+ if (typeof firstOriginal === 'string' || typeof secondOriginal === 'string') {
311
+ return firstOriginal === secondOriginal
312
+ } else if (CredentialMapper.isSdJwtDecodedCredential(firstOriginal) || CredentialMapper.isSdJwtDecodedCredential(secondOriginal)) {
313
+ return firstOriginal.compactSdJwtVc === secondOriginal.compactSdJwtVc
314
+ } else {
315
+ // JSON-LD or decoded JWT. (should we compare the signatures instead?)
316
+ return JSON.stringify(secondOriginal.proof) === JSON.stringify(firstOriginal.proof)
317
+ }
318
+ }
319
+
320
+ private static isJsonLdAsString(original: OriginalVerifiableCredential | OriginalVerifiablePresentation): original is string {
321
+ return ObjectUtils.isString(original) && original.includes('@context')
322
+ }
323
+
324
+ public static isSdJwtDecodedCredential(
325
+ original: OriginalVerifiableCredential | OriginalVerifiablePresentation | ICredential | IPresentation
326
+ ): original is SdJwtDecodedVerifiableCredential {
327
+ return (<SdJwtDecodedVerifiableCredential>original).compactSdJwtVc !== undefined
328
+ }
329
+
330
+ public static isJwtDecodedCredential(original: OriginalVerifiableCredential): original is JwtDecodedVerifiableCredential {
331
+ return (<JwtDecodedVerifiableCredential>original).vc !== undefined && (<JwtDecodedVerifiableCredential>original).iss !== undefined
332
+ }
333
+
334
+ public static isJwtDecodedPresentation(original: OriginalVerifiablePresentation): original is JwtDecodedVerifiablePresentation {
335
+ return (<JwtDecodedVerifiablePresentation>original).vp !== undefined && (<JwtDecodedVerifiablePresentation>original).iss !== undefined
336
+ }
337
+
338
+ public static isWrappedSdJwtVerifiableCredential = isWrappedSdJwtVerifiableCredential
339
+ public static isWrappedSdJwtVerifiablePresentation = isWrappedSdJwtVerifiablePresentation
340
+ public static isWrappedW3CVerifiableCredential = isWrappedW3CVerifiableCredential
341
+ public static isWrappedW3CVerifiablePresentation = isWrappedW3CVerifiablePresentation
342
+
170
343
  static jwtEncodedPresentationToUniformPresentation(
171
344
  jwt: string,
172
345
  makeCredentialsUniform: boolean = true,
@@ -226,6 +399,9 @@ export class CredentialMapper {
226
399
  maxTimeSkewInMS?: number
227
400
  }
228
401
  ): IVerifiableCredential {
402
+ if (CredentialMapper.isSdJwtDecodedCredential(verifiableCredential)) {
403
+ throw new Error('Converting SD-JWT VC to uniform VC is not supported.')
404
+ }
229
405
  const original =
230
406
  typeof verifiableCredential !== 'string' && CredentialMapper.hasJWTProofType(verifiableCredential)
231
407
  ? CredentialMapper.getFirstProof(verifiableCredential)?.jwt
@@ -251,6 +427,10 @@ export class CredentialMapper {
251
427
  presentation: OriginalVerifiablePresentation,
252
428
  opts?: { maxTimeSkewInMS?: number; addContextIfMissing?: boolean }
253
429
  ): IVerifiablePresentation {
430
+ if (CredentialMapper.isSdJwtDecodedCredential(presentation)) {
431
+ throw new Error('Converting SD-JWT VC to uniform VP is not supported.')
432
+ }
433
+
254
434
  const proof = CredentialMapper.getFirstProof(presentation)
255
435
  const original = typeof presentation !== 'string' && CredentialMapper.hasJWTProofType(presentation) ? proof?.jwt : presentation
256
436
  if (!original) {
@@ -459,11 +639,21 @@ export class CredentialMapper {
459
639
  }
460
640
 
461
641
  static detectDocumentType(
462
- document: W3CVerifiableCredential | W3CVerifiablePresentation | JwtDecodedVerifiableCredential | JwtDecodedVerifiablePresentation
642
+ document:
643
+ | W3CVerifiableCredential
644
+ | W3CVerifiablePresentation
645
+ | JwtDecodedVerifiableCredential
646
+ | JwtDecodedVerifiablePresentation
647
+ | SdJwtDecodedVerifiableCredential
463
648
  ): DocumentFormat {
464
- if (typeof document === 'string') {
465
- return this.isJsonLdAsString(document) ? DocumentFormat.JSONLD : DocumentFormat.JWT
649
+ if (this.isJsonLdAsString(document)) {
650
+ return DocumentFormat.JSONLD
651
+ } else if (this.isJwtEncoded(document)) {
652
+ return DocumentFormat.JWT
653
+ } else if (this.isSdJwtEncoded(document) || this.isSdJwtDecodedCredential(document as any)) {
654
+ return DocumentFormat.SD_JWT_VC
466
655
  }
656
+
467
657
  const proofs = 'vc' in document ? document.vc.proof : 'vp' in document ? document.vp.proof : (<IVerifiableCredential>document).proof
468
658
  const proof: IProof = Array.isArray(proofs) ? proofs[0] : proofs
469
659
 
@@ -4,3 +4,5 @@
4
4
  * @internal
5
5
  */
6
6
  export type OrPromise<T> = T | Promise<T>
7
+
8
+ export type BearerTokenArg = (() => Promise<string>) | string
@@ -2,3 +2,5 @@ export * from './did'
2
2
  export * from './pex'
3
3
  export * from './vc'
4
4
  export * from './generic'
5
+ export * from './sd-jwt-vc'
6
+ export * from './w3c-vc'
@@ -0,0 +1,248 @@
1
+ import { OriginalType, WrappedVerifiableCredential, WrappedVerifiablePresentation } from './vc'
2
+ import { SdJwtVc } from '@sd-jwt/core'
3
+ import { swapClaims } from '@sd-jwt/core/build/sdJwt/swapClaim'
4
+ type JsonValue = string | number | boolean | { [x: string]: JsonValue | undefined } | Array<JsonValue>
5
+
6
+ type SdJwtJsonValue =
7
+ | string
8
+ | number
9
+ | boolean
10
+ | {
11
+ [x: string]: SdJwtJsonValue | undefined
12
+ _sd?: string[]
13
+ }
14
+ | Array<SdJwtJsonValue | { '...': string }>
15
+
16
+ /**
17
+ * Decoded 'pretty' SD JWT Verifiable Credential. This representation has all the `_sd` properties
18
+ * removed, and includes the disclosures directly within the payload.
19
+ */
20
+ export interface SdJwtDecodedVerifiableCredentialPayload {
21
+ vct: string
22
+ iss: string
23
+ iat: number
24
+ nbf?: number
25
+ exp?: number
26
+ cnf?: {
27
+ jwk?: any
28
+ kid?: string
29
+ }
30
+ status?: {
31
+ idx: number
32
+ uri: string
33
+ }
34
+ sub?: string
35
+
36
+ [key: string]: JsonValue | undefined
37
+ }
38
+
39
+ /**
40
+ * Represents a selective disclosure JWT vc in compact form.
41
+ */
42
+ export type CompactSdJwtVc = string
43
+
44
+ /**
45
+ * The signed payload of an SD-JWT. Includes fields such as `_sd`, `...` and `_sd_alg`
46
+ */
47
+ interface SdJwtSignedVerifiableCredentialPayload extends SdJwtDecodedVerifiableCredentialPayload {
48
+ // Only present if there are any selectively discloseable claims
49
+ _sd?: string[]
50
+ _sd_alg?: string
51
+
52
+ [x: string]: SdJwtJsonValue | undefined
53
+ }
54
+
55
+ type SdJwtFrameValue = boolean | Array<SdJwtFrameValue> | { [x: string]: SdJwtFrameValue }
56
+ export type SdJwtDisclosureFrame = Record<string, SdJwtFrameValue>
57
+ export type SdJwtPresentationFrame = Record<string, SdJwtFrameValue>
58
+
59
+ /**
60
+ * Input for creating a SD JWT Verifiable Credential. This representation optionally includes the disclosure frame,
61
+ * (as `__disclosureFrame`) to indicate which fields in the signed SD-JWT should be selectively discloseable
62
+ */
63
+ export interface SdJwtCredentialInput extends SdJwtDecodedVerifiableCredentialPayload {
64
+ /**
65
+ * Disclosure frame, indicating which fields in the signed SD-JWT should be selectively discloseable
66
+ * Will be removed from the actual SD-JWT payload before signing
67
+ */
68
+ __disclosureFrame?: SdJwtDisclosureFrame
69
+ }
70
+
71
+ export type SdJwtDecodedDisclosure = [string, string, JsonValue] | [string, JsonValue]
72
+ export interface SdJwtDisclosure {
73
+ // The encoded disclosure
74
+ encoded: string
75
+
76
+ // The decoded disclosure, in format [salt, claim, value] or in case of array entry [salt, value]
77
+ decoded: SdJwtDecodedDisclosure
78
+
79
+ // Digest over disclosure, can be used to match against a value within the SD JWT payload
80
+ digest: string
81
+ }
82
+
83
+ /**
84
+ * The decoded SD JWT Verifiable Credential. This representation includes multiple representations of the
85
+ * same SD-JWT, and allows to fully process an SD-JWT, as well as create a presentation SD-JWT (minus the KB-JWT) by removing
86
+ * certain disclosures from the compact SD-JWT.
87
+ *
88
+ * This representation is useful as it doesn't require a hasher implementation to match the different digests in the signed SD-JWT
89
+ * payload, with the different disclosures.
90
+ */
91
+ export interface SdJwtDecodedVerifiableCredential {
92
+ /**
93
+ * The compact sd jwt is the sd-jwt encoded as string. It is a normal JWT,
94
+ * with the disclosures and kb-jwt appended separated by ~ */
95
+ compactSdJwtVc: string
96
+
97
+ /**
98
+ * The disclosures included within the SD-JWT in both encoded and decoded format.
99
+ * The digests are also included, and allows the disclosures to be linked against
100
+ * the digests in the signed payload.
101
+ */
102
+ disclosures: Array<SdJwtDisclosure>
103
+
104
+ /**
105
+ * The signed payload is the payload of the sd-jwt that is actually signed, and that includes
106
+ * the `_sd` and `...` digests.
107
+ */
108
+ signedPayload: SdJwtSignedVerifiableCredentialPayload
109
+
110
+ /**
111
+ * The decoded payload is the payload when all `_sd` and `...` digests have been replaced
112
+ * by the actual values from the disclosures. This format could also be seen as the 'pretty`
113
+ * version of the SD JWT payload.
114
+ *
115
+ * This is useful for displaying the contents of the SD JWT VC to the user, or for example
116
+ * for querying the contents of the SD JWT VC using a PEX presentation definition path.
117
+ */
118
+ decodedPayload: SdJwtDecodedVerifiableCredentialPayload
119
+ }
120
+
121
+ export interface WrappedSdJwtVerifiableCredential {
122
+ /**
123
+ * Original VC that we've received. Can be either the encoded or decoded variant.
124
+ */
125
+ original: SdJwtDecodedVerifiableCredential | CompactSdJwtVc
126
+ /**
127
+ * Decoded version of the SD-JWT payload. This is the decoded payload, rather than the whole SD-JWT as the `decoded` property
128
+ * is used in e.g. PEX to check for path filters from fields. The full decoded credential can be found in the `credential` field.
129
+ */
130
+ decoded: SdJwtDecodedVerifiableCredentialPayload
131
+ /**
132
+ * Type of this credential.
133
+ */
134
+ type: OriginalType.SD_JWT_VC_DECODED | OriginalType.SD_JWT_VC_ENCODED
135
+ /**
136
+ * The claim format, typically used during exchange transport protocols
137
+ */
138
+ format: 'vc+sd-jwt'
139
+ /**
140
+ * Internal stable representation of a Credential
141
+ */
142
+ credential: SdJwtDecodedVerifiableCredential
143
+ }
144
+
145
+ export interface WrappedSdJwtVerifiablePresentation {
146
+ /**
147
+ * Original VP that we've received. Can be either the encoded or decoded variant.
148
+ */
149
+ original: SdJwtDecodedVerifiableCredential | CompactSdJwtVc
150
+ /**
151
+ * Decoded version of the SD-JWT payload. This is the decoded payload, rather than the whole SD-JWT.
152
+ */
153
+ decoded: SdJwtDecodedVerifiableCredentialPayload
154
+ /**
155
+ * Type of this Presentation.
156
+ */
157
+ type: OriginalType.SD_JWT_VC_DECODED | OriginalType.SD_JWT_VC_ENCODED
158
+ /**
159
+ * The claim format, typically used during exchange transport protocols
160
+ */
161
+ format: 'vc+sd-jwt'
162
+ /**
163
+ * Internal stable representation of a Presentation
164
+ */
165
+ presentation: SdJwtDecodedVerifiableCredential
166
+ /**
167
+ * Wrapped Verifiable Credentials belonging to the Presentation. Will always be an array
168
+ * with a single SdJwtVerifiableCredential entry.
169
+ */
170
+ vcs: [WrappedSdJwtVerifiableCredential]
171
+ }
172
+
173
+ export function isWrappedSdJwtVerifiableCredential(vc: WrappedVerifiableCredential): vc is WrappedSdJwtVerifiableCredential {
174
+ return vc.format === 'vc+sd-jwt'
175
+ }
176
+
177
+ export function isWrappedSdJwtVerifiablePresentation(vp: WrappedVerifiablePresentation): vp is WrappedSdJwtVerifiablePresentation {
178
+ return vp.format === 'vc+sd-jwt'
179
+ }
180
+
181
+ export type Hasher = (data: string, alg: string) => Uint8Array
182
+ export type AsyncHasher = (data: string, alg: string) => Promise<Uint8Array>
183
+
184
+ /**
185
+ * Decode an SD-JWT vc from its compact format (string) to an object containing the disclosures,
186
+ * signed payload, decoded payload and the compact SD-JWT vc.
187
+ *
188
+ * Both the input and output interfaces of this method are defined in `@sphereon/ssi-types`, so
189
+ * this method hides the actual implementation of SD-JWT (which is currently based on @sd-jwt/core)
190
+ */
191
+ export function decodeSdJwtVc(compactSdJwtVc: CompactSdJwtVc, hasher: Hasher): SdJwtDecodedVerifiableCredential {
192
+ const sdJwtVc = SdJwtVc.fromCompact(compactSdJwtVc)
193
+
194
+ // Default (should be handled by the sd-jwt library)
195
+ let sdAlg = 'sha-256'
196
+ try {
197
+ sdAlg = sdJwtVc.getClaimInPayload('_sd_alg')
198
+ } catch {
199
+ /* no-op */
200
+ }
201
+
202
+ const disclosuresWithDigests = sdJwtVc.disclosures?.map((d) => d.withCalculateDigest((data: string) => hasher(data, sdAlg))) ?? []
203
+
204
+ return {
205
+ compactSdJwtVc: compactSdJwtVc,
206
+ decodedPayload: swapClaims(sdJwtVc.payload, disclosuresWithDigests) as SdJwtDecodedVerifiableCredentialPayload,
207
+ disclosures: disclosuresWithDigests.map((d) => ({
208
+ decoded: d.decoded as SdJwtDecodedDisclosure,
209
+ digest: d.digest,
210
+ encoded: d.encoded,
211
+ })),
212
+ signedPayload: sdJwtVc.payload as SdJwtDecodedVerifiableCredentialPayload,
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Decode an SD-JWT vc from its compact format (string) to an object containing the disclosures,
218
+ * signed payload, decoded payload and the compact SD-JWT vc.
219
+ *
220
+ * Both the input and output interfaces of this method are defined in `@sphereon/ssi-types`, so
221
+ * this method hides the actual implementation of SD-JWT (which is currently based on @sd-jwt/core)
222
+ */
223
+ export async function decodeSdJwtVcAsync(compactSdJwtVc: CompactSdJwtVc, hasher: AsyncHasher): Promise<SdJwtDecodedVerifiableCredential> {
224
+ const sdJwtVc = SdJwtVc.fromCompact(compactSdJwtVc)
225
+
226
+ // Default (should be handled by the sd-jwt library)
227
+ let sdAlg = 'sha-256'
228
+ try {
229
+ sdAlg = sdJwtVc.getClaimInPayload('_sd_alg')
230
+ } catch {
231
+ /* no-op */
232
+ }
233
+
234
+ const disclosuresWithDigests = await Promise.all(
235
+ sdJwtVc.disclosures?.map((d) => d.withCalculateDigest((data: string) => hasher(data, sdAlg))) ?? []
236
+ )
237
+
238
+ return {
239
+ compactSdJwtVc: compactSdJwtVc,
240
+ decodedPayload: swapClaims(sdJwtVc.payload, disclosuresWithDigests) as SdJwtDecodedVerifiableCredentialPayload,
241
+ disclosures: disclosuresWithDigests.map((d) => ({
242
+ decoded: d.decoded as SdJwtDecodedDisclosure,
243
+ digest: d.digest,
244
+ encoded: d.encoded,
245
+ })),
246
+ signedPayload: sdJwtVc.payload as SdJwtDecodedVerifiableCredentialPayload,
247
+ }
248
+ }