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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,369 +1,369 @@
1
- import { OriginalType, WrappedVerifiableCredential, WrappedVerifiablePresentation } from './vc'
2
- import { decodeSdJwt, decodeSdJwtSync, getClaims, getClaimsSync } from '@sd-jwt/decode'
3
- import { CompactJWT, IVerifiableCredential } from './w3c-vc'
4
- import { IProofPurpose, IProofType } from './did'
5
-
6
- type JsonValue = string | number | boolean | { [x: string]: JsonValue | undefined } | Array<JsonValue>
7
-
8
- type SdJwtJsonValue =
9
- | string
10
- | number
11
- | boolean
12
- | {
13
- [x: string]: SdJwtJsonValue | undefined
14
- _sd?: string[]
15
- }
16
- | Array<SdJwtJsonValue | { '...': string }>
17
-
18
- /**
19
- * Decoded 'pretty' SD JWT Verifiable Credential. This representation has all the `_sd` properties
20
- * removed, and includes the disclosures directly within the payload.
21
- */
22
- export interface SdJwtDecodedVerifiableCredentialPayload {
23
- vct: string
24
- iss: string
25
- iat: number
26
- nbf?: number
27
- exp?: number
28
- cnf?: {
29
- jwk?: any
30
- kid?: string
31
- }
32
- status?: {
33
- idx: number
34
- uri: string
35
- }
36
- sub?: string
37
-
38
- [key: string]: JsonValue | undefined
39
- }
40
-
41
- /**
42
- * Represents a selective disclosure JWT vc in compact form.
43
- */
44
- export type CompactSdJwtVc = string
45
-
46
- /**
47
- * The signed payload of an SD-JWT. Includes fields such as `_sd`, `...` and `_sd_alg`
48
- */
49
- interface SdJwtSignedVerifiableCredentialPayload extends SdJwtDecodedVerifiableCredentialPayload {
50
- // Only present if there are any selectively discloseable claims
51
- _sd?: string[]
52
- _sd_alg?: string
53
-
54
- [x: string]: SdJwtJsonValue | undefined
55
- }
56
-
57
- type SdJwtFrameValue = boolean | Array<SdJwtFrameValue> | { [x: string]: SdJwtFrameValue }
58
- export type SdJwtDisclosureFrame = Record<string, SdJwtFrameValue>
59
- export type SdJwtPresentationFrame = Record<string, SdJwtFrameValue>
60
-
61
- /**
62
- * Input for creating a SD JWT Verifiable Credential. This representation optionally includes the disclosure frame,
63
- * (as `__disclosureFrame`) to indicate which fields in the signed SD-JWT should be selectively discloseable
64
- */
65
- export interface SdJwtCredentialInput extends SdJwtDecodedVerifiableCredentialPayload {
66
- /**
67
- * Disclosure frame, indicating which fields in the signed SD-JWT should be selectively discloseable
68
- * Will be removed from the actual SD-JWT payload before signing
69
- */
70
- __disclosureFrame?: SdJwtDisclosureFrame
71
- }
72
-
73
- export type SdJwtDecodedDisclosure = [string, string, JsonValue] | [string, JsonValue]
74
- export interface SdJwtDisclosure {
75
- // The encoded disclosure
76
- encoded: string
77
-
78
- // The decoded disclosure, in format [salt, claim, value] or in case of array entry [salt, value]
79
- decoded: SdJwtDecodedDisclosure
80
-
81
- // Digest over disclosure, can be used to match against a value within the SD JWT payload
82
- digest: string
83
- }
84
-
85
- /**
86
- * The decoded SD JWT Verifiable Credential. This representation includes multiple representations of the
87
- * 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
88
- * certain disclosures from the compact SD-JWT.
89
- *
90
- * This representation is useful as it doesn't require a hasher implementation to match the different digests in the signed SD-JWT
91
- * payload, with the different disclosures.
92
- */
93
- export interface SdJwtDecodedVerifiableCredential {
94
- /**
95
- * The compact sd jwt is the sd-jwt encoded as string. It is a normal JWT,
96
- * with the disclosures and kb-jwt appended separated by ~ */
97
- compactSdJwtVc: string
98
-
99
- /**
100
- * The disclosures included within the SD-JWT in both encoded and decoded format.
101
- * The digests are also included, and allows the disclosures to be linked against
102
- * the digests in the signed payload.
103
- */
104
- disclosures: Array<SdJwtDisclosure>
105
-
106
- /**
107
- * The signed payload is the payload of the sd-jwt that is actually signed, and that includes
108
- * the `_sd` and `...` digests.
109
- */
110
- signedPayload: SdJwtSignedVerifiableCredentialPayload
111
-
112
- /**
113
- * The decoded payload is the payload when all `_sd` and `...` digests have been replaced
114
- * by the actual values from the disclosures. This format could also be seen as the 'pretty`
115
- * version of the SD JWT payload.
116
- *
117
- * This is useful for displaying the contents of the SD JWT VC to the user, or for example
118
- * for querying the contents of the SD JWT VC using a PEX presentation definition path.
119
- */
120
- decodedPayload: SdJwtDecodedVerifiableCredentialPayload
121
-
122
- /**
123
- * Key binding JWT
124
- */
125
- kbJwt?: {
126
- header: SdJwtVcKbJwtHeader
127
- payload: SdJwtVcKbJwtPayload
128
- compact?: CompactJWT
129
- }
130
- }
131
-
132
- export interface SdJwtVcKbJwtHeader {
133
- typ: 'kb+jwt'
134
- alg: string
135
- [x: string]: any
136
- }
137
-
138
- export interface SdJwtVcKbJwtPayload {
139
- iat: number
140
- aud: string
141
- nonce: string
142
- sd_hash: string
143
- [key: string]: unknown
144
- }
145
-
146
- export interface WrappedSdJwtVerifiableCredential {
147
- /**
148
- * Original VC that we've received. Can be either the encoded or decoded variant.
149
- */
150
- original: SdJwtDecodedVerifiableCredential | CompactSdJwtVc
151
- /**
152
- * Decoded version of the SD-JWT payload. This is the decoded payload, rather than the whole SD-JWT as the `decoded` property
153
- * is used in e.g. PEX to check for path filters from fields. The full decoded credential can be found in the `credential` field.
154
- */
155
- decoded: SdJwtDecodedVerifiableCredentialPayload
156
- /**
157
- * Type of this credential.
158
- */
159
- type: OriginalType.SD_JWT_VC_DECODED | OriginalType.SD_JWT_VC_ENCODED
160
- /**
161
- * The claim format, typically used during exchange transport protocols
162
- */
163
- format: 'vc+sd-jwt'
164
- /**
165
- * Internal stable representation of a Credential
166
- */
167
- credential: SdJwtDecodedVerifiableCredential
168
- }
169
-
170
- export interface WrappedSdJwtVerifiablePresentation {
171
- /**
172
- * Original VP that we've received. Can be either the encoded or decoded variant.
173
- */
174
- original: SdJwtDecodedVerifiableCredential | CompactSdJwtVc
175
- /**
176
- * Decoded version of the SD-JWT payload. This is the decoded payload, rather than the whole SD-JWT.
177
- */
178
- decoded: SdJwtDecodedVerifiableCredentialPayload
179
- /**
180
- * Type of this Presentation.
181
- */
182
- type: OriginalType.SD_JWT_VC_DECODED | OriginalType.SD_JWT_VC_ENCODED
183
- /**
184
- * The claim format, typically used during exchange transport protocols
185
- */
186
- format: 'vc+sd-jwt'
187
- /**
188
- * Internal stable representation of a Presentation
189
- */
190
- presentation: SdJwtDecodedVerifiableCredential
191
- /**
192
- * Wrapped Verifiable Credentials belonging to the Presentation. Will always be an array
193
- * with a single SdJwtVerifiableCredential entry.
194
- */
195
- vcs: [WrappedSdJwtVerifiableCredential]
196
- }
197
-
198
- export function isWrappedSdJwtVerifiableCredential(vc: WrappedVerifiableCredential): vc is WrappedSdJwtVerifiableCredential {
199
- return vc.format === 'vc+sd-jwt'
200
- }
201
-
202
- export function isWrappedSdJwtVerifiablePresentation(vp: WrappedVerifiablePresentation): vp is WrappedSdJwtVerifiablePresentation {
203
- return vp.format === 'vc+sd-jwt'
204
- }
205
-
206
- export type Hasher = (data: string, alg: string) => Uint8Array
207
- export type AsyncHasher = (data: string, alg: string) => Promise<Uint8Array>
208
-
209
- /**
210
- * Decode an SD-JWT vc from its compact format (string) to an object containing the disclosures,
211
- * signed payload, decoded payload and the compact SD-JWT vc.
212
- *
213
- * Both the input and output interfaces of this method are defined in `@sphereon/ssi-types`, so
214
- * this method hides the actual implementation of SD-JWT (which is currently based on @sd-jwt/core)
215
- */
216
- export function decodeSdJwtVc(compactSdJwtVc: CompactSdJwtVc, hasher: Hasher): SdJwtDecodedVerifiableCredential {
217
- const { jwt, disclosures, kbJwt } = decodeSdJwtSync(compactSdJwtVc, hasher)
218
-
219
- const signedPayload = jwt.payload as SdJwtSignedVerifiableCredentialPayload
220
- const decodedPayload = getClaimsSync(signedPayload, disclosures, hasher)
221
- const compactKeyBindingJwt = kbJwt ? compactSdJwtVc.split('~').pop() : undefined
222
-
223
- return {
224
- compactSdJwtVc,
225
- decodedPayload: decodedPayload as SdJwtDecodedVerifiableCredentialPayload,
226
- disclosures: disclosures.map((d) => {
227
- const decoded = d.key ? [d.salt, d.key, d.value] : [d.salt, d.value]
228
- if (!d._digest) throw new Error('Implementation error: digest not present in disclosure')
229
- return {
230
- decoded: decoded as SdJwtDecodedDisclosure,
231
- digest: d._digest,
232
- encoded: d.encode(),
233
- } satisfies SdJwtDisclosure
234
- }),
235
- signedPayload: signedPayload as SdJwtSignedVerifiableCredentialPayload,
236
- ...(compactKeyBindingJwt &&
237
- kbJwt && {
238
- kbJwt: {
239
- header: kbJwt.header as SdJwtVcKbJwtHeader,
240
- compact: compactKeyBindingJwt,
241
- payload: kbJwt.payload as SdJwtVcKbJwtPayload,
242
- },
243
- }),
244
- }
245
- }
246
-
247
- /**
248
- * Decode an SD-JWT vc from its compact format (string) to an object containing the disclosures,
249
- * signed payload, decoded payload and the compact SD-JWT vc.
250
- *
251
- * Both the input and output interfaces of this method are defined in `@sphereon/ssi-types`, so
252
- * this method hides the actual implementation of SD-JWT (which is currently based on @sd-jwt/core)
253
- */
254
- export async function decodeSdJwtVcAsync(compactSdJwtVc: CompactSdJwtVc, hasher: AsyncHasher): Promise<SdJwtDecodedVerifiableCredential> {
255
- const { jwt, disclosures, kbJwt } = await decodeSdJwt(compactSdJwtVc, hasher)
256
-
257
- const signedPayload = jwt.payload as SdJwtSignedVerifiableCredentialPayload
258
- const decodedPayload = await getClaims(signedPayload, disclosures, hasher)
259
- const compactKeyBindingJwt = kbJwt ? compactSdJwtVc.split('~').pop() : undefined
260
-
261
- return {
262
- compactSdJwtVc,
263
- decodedPayload: decodedPayload as SdJwtDecodedVerifiableCredentialPayload,
264
- disclosures: disclosures.map((d) => {
265
- const decoded = d.key ? [d.salt, d.key, d.value] : [d.salt, d.value]
266
- if (!d._digest) throw new Error('Implementation error: digest not present in disclosure')
267
- return {
268
- decoded: decoded as SdJwtDecodedDisclosure,
269
- digest: d._digest,
270
- encoded: d.encode(),
271
- } satisfies SdJwtDisclosure
272
- }),
273
- signedPayload: signedPayload as SdJwtSignedVerifiableCredentialPayload,
274
- ...(compactKeyBindingJwt &&
275
- kbJwt && {
276
- kbJwt: {
277
- header: kbJwt.header as SdJwtVcKbJwtHeader,
278
- payload: kbJwt.payload as SdJwtVcKbJwtPayload,
279
- compact: compactKeyBindingJwt,
280
- },
281
- }),
282
- }
283
- }
284
-
285
- // TODO naive implementation of mapping a sd-jwt onto a IVerifiableCredential. Needs some fixes and further implementation and needs to be moved out of ssi-types
286
- export const sdJwtDecodedCredentialToUniformCredential = (
287
- decoded: SdJwtDecodedVerifiableCredential,
288
- opts?: { maxTimeSkewInMS?: number }
289
- ): IVerifiableCredential => {
290
- const { decodedPayload } = decoded // fixme: other params and proof
291
- const { exp, nbf, iss, iat, vct, cnf, status, sub, jti } = decodedPayload
292
-
293
- type DisclosuresAccumulator = {
294
- [key: string]: any
295
- }
296
-
297
- const credentialSubject = decoded.disclosures.reduce(
298
- (acc: DisclosuresAccumulator, item: { decoded: Array<any>; digest: string; encoded: string }) => {
299
- const key = item.decoded[1]
300
- acc[key] = item.decoded[2]
301
-
302
- return acc
303
- },
304
- {}
305
- )
306
-
307
- const maxSkewInMS = opts?.maxTimeSkewInMS ?? 1500
308
-
309
- const expirationDate = jwtDateToISOString({ jwtClaim: exp, claimName: 'exp' })
310
- let issuanceDateStr = jwtDateToISOString({ jwtClaim: iat, claimName: 'iat' })
311
-
312
- let nbfDateAsStr: string | undefined
313
- if (nbf) {
314
- nbfDateAsStr = jwtDateToISOString({ jwtClaim: nbf, claimName: 'nbf' })
315
- if (issuanceDateStr && nbfDateAsStr && issuanceDateStr !== nbfDateAsStr) {
316
- const diff = Math.abs(new Date(nbfDateAsStr).getTime() - new Date(iss).getTime())
317
- if (!maxSkewInMS || diff > maxSkewInMS) {
318
- throw Error(`Inconsistent issuance dates between JWT claim (${nbfDateAsStr}) and VC value (${iss})`)
319
- }
320
- }
321
- issuanceDateStr = nbfDateAsStr
322
- }
323
- const issuanceDate = issuanceDateStr
324
- if (!issuanceDate) {
325
- throw Error(`JWT issuance date is required but was not present`)
326
- }
327
-
328
- const credential: Omit<IVerifiableCredential, 'issuer' | 'issuanceDate'> = {
329
- type: [vct], // SDJwt is not a W3C VC, so no VerifiableCredential
330
- '@context': [], // SDJwt has no JSON-LD by default. Certainly not the VC DM1 default context for JSON-LD
331
- credentialSubject: {
332
- ...credentialSubject,
333
- id: credentialSubject.id ?? sub ?? jti,
334
- },
335
- issuanceDate,
336
- expirationDate,
337
- issuer: iss,
338
- ...(cnf && { cnf }),
339
- ...(status && { status }),
340
- proof: {
341
- type: IProofType.SdJwtProof2024,
342
- created: nbfDateAsStr ?? issuanceDate,
343
- proofPurpose: IProofPurpose.authentication,
344
- verificationMethod: iss,
345
- jwt: decoded.compactSdJwtVc,
346
- },
347
- }
348
-
349
- return credential as IVerifiableCredential
350
- }
351
-
352
- const jwtDateToISOString = ({
353
- jwtClaim,
354
- claimName,
355
- isRequired = false,
356
- }: {
357
- jwtClaim?: number
358
- claimName: string
359
- isRequired?: boolean
360
- }): string | undefined => {
361
- if (jwtClaim) {
362
- const claim = parseInt(jwtClaim.toString())
363
- // change JWT seconds to millisecond for the date
364
- return new Date(claim * (claim < 9999999999 ? 1000 : 1)).toISOString().replace(/\.000Z/, 'Z')
365
- } else if (isRequired) {
366
- throw Error(`JWT claim ${claimName} is required but was not present`)
367
- }
368
- return undefined
369
- }
1
+ import { OriginalType, WrappedVerifiableCredential, WrappedVerifiablePresentation } from './vc'
2
+ import { decodeSdJwt, decodeSdJwtSync, getClaims, getClaimsSync } from '@sd-jwt/decode'
3
+ import { CompactJWT, IVerifiableCredential } from './w3c-vc'
4
+ import { IProofPurpose, IProofType } from './did'
5
+
6
+ type JsonValue = string | number | boolean | { [x: string]: JsonValue | undefined } | Array<JsonValue>
7
+
8
+ type SdJwtJsonValue =
9
+ | string
10
+ | number
11
+ | boolean
12
+ | {
13
+ [x: string]: SdJwtJsonValue | undefined
14
+ _sd?: string[]
15
+ }
16
+ | Array<SdJwtJsonValue | { '...': string }>
17
+
18
+ /**
19
+ * Decoded 'pretty' SD JWT Verifiable Credential. This representation has all the `_sd` properties
20
+ * removed, and includes the disclosures directly within the payload.
21
+ */
22
+ export interface SdJwtDecodedVerifiableCredentialPayload {
23
+ vct: string
24
+ iss: string
25
+ iat: number
26
+ nbf?: number
27
+ exp?: number
28
+ cnf?: {
29
+ jwk?: any
30
+ kid?: string
31
+ }
32
+ status?: {
33
+ idx: number
34
+ uri: string
35
+ }
36
+ sub?: string
37
+
38
+ [key: string]: JsonValue | undefined
39
+ }
40
+
41
+ /**
42
+ * Represents a selective disclosure JWT vc in compact form.
43
+ */
44
+ export type CompactSdJwtVc = string
45
+
46
+ /**
47
+ * The signed payload of an SD-JWT. Includes fields such as `_sd`, `...` and `_sd_alg`
48
+ */
49
+ interface SdJwtSignedVerifiableCredentialPayload extends SdJwtDecodedVerifiableCredentialPayload {
50
+ // Only present if there are any selectively discloseable claims
51
+ _sd?: string[]
52
+ _sd_alg?: string
53
+
54
+ [x: string]: SdJwtJsonValue | undefined
55
+ }
56
+
57
+ type SdJwtFrameValue = boolean | Array<SdJwtFrameValue> | { [x: string]: SdJwtFrameValue }
58
+ export type SdJwtDisclosureFrame = Record<string, SdJwtFrameValue>
59
+ export type SdJwtPresentationFrame = Record<string, SdJwtFrameValue>
60
+
61
+ /**
62
+ * Input for creating a SD JWT Verifiable Credential. This representation optionally includes the disclosure frame,
63
+ * (as `__disclosureFrame`) to indicate which fields in the signed SD-JWT should be selectively discloseable
64
+ */
65
+ export interface SdJwtCredentialInput extends SdJwtDecodedVerifiableCredentialPayload {
66
+ /**
67
+ * Disclosure frame, indicating which fields in the signed SD-JWT should be selectively discloseable
68
+ * Will be removed from the actual SD-JWT payload before signing
69
+ */
70
+ __disclosureFrame?: SdJwtDisclosureFrame
71
+ }
72
+
73
+ export type SdJwtDecodedDisclosure = [string, string, JsonValue] | [string, JsonValue]
74
+ export interface SdJwtDisclosure {
75
+ // The encoded disclosure
76
+ encoded: string
77
+
78
+ // The decoded disclosure, in format [salt, claim, value] or in case of array entry [salt, value]
79
+ decoded: SdJwtDecodedDisclosure
80
+
81
+ // Digest over disclosure, can be used to match against a value within the SD JWT payload
82
+ digest: string
83
+ }
84
+
85
+ /**
86
+ * The decoded SD JWT Verifiable Credential. This representation includes multiple representations of the
87
+ * 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
88
+ * certain disclosures from the compact SD-JWT.
89
+ *
90
+ * This representation is useful as it doesn't require a hasher implementation to match the different digests in the signed SD-JWT
91
+ * payload, with the different disclosures.
92
+ */
93
+ export interface SdJwtDecodedVerifiableCredential {
94
+ /**
95
+ * The compact sd jwt is the sd-jwt encoded as string. It is a normal JWT,
96
+ * with the disclosures and kb-jwt appended separated by ~ */
97
+ compactSdJwtVc: string
98
+
99
+ /**
100
+ * The disclosures included within the SD-JWT in both encoded and decoded format.
101
+ * The digests are also included, and allows the disclosures to be linked against
102
+ * the digests in the signed payload.
103
+ */
104
+ disclosures: Array<SdJwtDisclosure>
105
+
106
+ /**
107
+ * The signed payload is the payload of the sd-jwt that is actually signed, and that includes
108
+ * the `_sd` and `...` digests.
109
+ */
110
+ signedPayload: SdJwtSignedVerifiableCredentialPayload
111
+
112
+ /**
113
+ * The decoded payload is the payload when all `_sd` and `...` digests have been replaced
114
+ * by the actual values from the disclosures. This format could also be seen as the 'pretty`
115
+ * version of the SD JWT payload.
116
+ *
117
+ * This is useful for displaying the contents of the SD JWT VC to the user, or for example
118
+ * for querying the contents of the SD JWT VC using a PEX presentation definition path.
119
+ */
120
+ decodedPayload: SdJwtDecodedVerifiableCredentialPayload
121
+
122
+ /**
123
+ * Key binding JWT
124
+ */
125
+ kbJwt?: {
126
+ header: SdJwtVcKbJwtHeader
127
+ payload: SdJwtVcKbJwtPayload
128
+ compact?: CompactJWT
129
+ }
130
+ }
131
+
132
+ export interface SdJwtVcKbJwtHeader {
133
+ typ: 'kb+jwt'
134
+ alg: string
135
+ [x: string]: any
136
+ }
137
+
138
+ export interface SdJwtVcKbJwtPayload {
139
+ iat: number
140
+ aud: string
141
+ nonce: string
142
+ sd_hash: string
143
+ [key: string]: unknown
144
+ }
145
+
146
+ export interface WrappedSdJwtVerifiableCredential {
147
+ /**
148
+ * Original VC that we've received. Can be either the encoded or decoded variant.
149
+ */
150
+ original: SdJwtDecodedVerifiableCredential | CompactSdJwtVc
151
+ /**
152
+ * Decoded version of the SD-JWT payload. This is the decoded payload, rather than the whole SD-JWT as the `decoded` property
153
+ * is used in e.g. PEX to check for path filters from fields. The full decoded credential can be found in the `credential` field.
154
+ */
155
+ decoded: SdJwtDecodedVerifiableCredentialPayload
156
+ /**
157
+ * Type of this credential.
158
+ */
159
+ type: OriginalType.SD_JWT_VC_DECODED | OriginalType.SD_JWT_VC_ENCODED
160
+ /**
161
+ * The claim format, typically used during exchange transport protocols
162
+ */
163
+ format: 'vc+sd-jwt'
164
+ /**
165
+ * Internal stable representation of a Credential
166
+ */
167
+ credential: SdJwtDecodedVerifiableCredential
168
+ }
169
+
170
+ export interface WrappedSdJwtVerifiablePresentation {
171
+ /**
172
+ * Original VP that we've received. Can be either the encoded or decoded variant.
173
+ */
174
+ original: SdJwtDecodedVerifiableCredential | CompactSdJwtVc
175
+ /**
176
+ * Decoded version of the SD-JWT payload. This is the decoded payload, rather than the whole SD-JWT.
177
+ */
178
+ decoded: SdJwtDecodedVerifiableCredentialPayload
179
+ /**
180
+ * Type of this Presentation.
181
+ */
182
+ type: OriginalType.SD_JWT_VC_DECODED | OriginalType.SD_JWT_VC_ENCODED
183
+ /**
184
+ * The claim format, typically used during exchange transport protocols
185
+ */
186
+ format: 'vc+sd-jwt'
187
+ /**
188
+ * Internal stable representation of a Presentation
189
+ */
190
+ presentation: SdJwtDecodedVerifiableCredential
191
+ /**
192
+ * Wrapped Verifiable Credentials belonging to the Presentation. Will always be an array
193
+ * with a single SdJwtVerifiableCredential entry.
194
+ */
195
+ vcs: [WrappedSdJwtVerifiableCredential]
196
+ }
197
+
198
+ export function isWrappedSdJwtVerifiableCredential(vc: WrappedVerifiableCredential): vc is WrappedSdJwtVerifiableCredential {
199
+ return vc.format === 'vc+sd-jwt'
200
+ }
201
+
202
+ export function isWrappedSdJwtVerifiablePresentation(vp: WrappedVerifiablePresentation): vp is WrappedSdJwtVerifiablePresentation {
203
+ return vp.format === 'vc+sd-jwt'
204
+ }
205
+
206
+ export type Hasher = (data: string, alg: string) => Uint8Array
207
+ export type AsyncHasher = (data: string, alg: string) => Promise<Uint8Array>
208
+
209
+ /**
210
+ * Decode an SD-JWT vc from its compact format (string) to an object containing the disclosures,
211
+ * signed payload, decoded payload and the compact SD-JWT vc.
212
+ *
213
+ * Both the input and output interfaces of this method are defined in `@sphereon/ssi-types`, so
214
+ * this method hides the actual implementation of SD-JWT (which is currently based on @sd-jwt/core)
215
+ */
216
+ export function decodeSdJwtVc(compactSdJwtVc: CompactSdJwtVc, hasher: Hasher): SdJwtDecodedVerifiableCredential {
217
+ const { jwt, disclosures, kbJwt } = decodeSdJwtSync(compactSdJwtVc, hasher)
218
+
219
+ const signedPayload = jwt.payload as SdJwtSignedVerifiableCredentialPayload
220
+ const decodedPayload = getClaimsSync(signedPayload, disclosures, hasher)
221
+ const compactKeyBindingJwt = kbJwt ? compactSdJwtVc.split('~').pop() : undefined
222
+
223
+ return {
224
+ compactSdJwtVc,
225
+ decodedPayload: decodedPayload as SdJwtDecodedVerifiableCredentialPayload,
226
+ disclosures: disclosures.map((d) => {
227
+ const decoded = d.key ? [d.salt, d.key, d.value] : [d.salt, d.value]
228
+ if (!d._digest) throw new Error('Implementation error: digest not present in disclosure')
229
+ return {
230
+ decoded: decoded as SdJwtDecodedDisclosure,
231
+ digest: d._digest,
232
+ encoded: d.encode(),
233
+ } satisfies SdJwtDisclosure
234
+ }),
235
+ signedPayload: signedPayload as SdJwtSignedVerifiableCredentialPayload,
236
+ ...(compactKeyBindingJwt &&
237
+ kbJwt && {
238
+ kbJwt: {
239
+ header: kbJwt.header as SdJwtVcKbJwtHeader,
240
+ compact: compactKeyBindingJwt,
241
+ payload: kbJwt.payload as SdJwtVcKbJwtPayload,
242
+ },
243
+ }),
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Decode an SD-JWT vc from its compact format (string) to an object containing the disclosures,
249
+ * signed payload, decoded payload and the compact SD-JWT vc.
250
+ *
251
+ * Both the input and output interfaces of this method are defined in `@sphereon/ssi-types`, so
252
+ * this method hides the actual implementation of SD-JWT (which is currently based on @sd-jwt/core)
253
+ */
254
+ export async function decodeSdJwtVcAsync(compactSdJwtVc: CompactSdJwtVc, hasher: AsyncHasher): Promise<SdJwtDecodedVerifiableCredential> {
255
+ const { jwt, disclosures, kbJwt } = await decodeSdJwt(compactSdJwtVc, hasher)
256
+
257
+ const signedPayload = jwt.payload as SdJwtSignedVerifiableCredentialPayload
258
+ const decodedPayload = await getClaims(signedPayload, disclosures, hasher)
259
+ const compactKeyBindingJwt = kbJwt ? compactSdJwtVc.split('~').pop() : undefined
260
+
261
+ return {
262
+ compactSdJwtVc,
263
+ decodedPayload: decodedPayload as SdJwtDecodedVerifiableCredentialPayload,
264
+ disclosures: disclosures.map((d) => {
265
+ const decoded = d.key ? [d.salt, d.key, d.value] : [d.salt, d.value]
266
+ if (!d._digest) throw new Error('Implementation error: digest not present in disclosure')
267
+ return {
268
+ decoded: decoded as SdJwtDecodedDisclosure,
269
+ digest: d._digest,
270
+ encoded: d.encode(),
271
+ } satisfies SdJwtDisclosure
272
+ }),
273
+ signedPayload: signedPayload as SdJwtSignedVerifiableCredentialPayload,
274
+ ...(compactKeyBindingJwt &&
275
+ kbJwt && {
276
+ kbJwt: {
277
+ header: kbJwt.header as SdJwtVcKbJwtHeader,
278
+ payload: kbJwt.payload as SdJwtVcKbJwtPayload,
279
+ compact: compactKeyBindingJwt,
280
+ },
281
+ }),
282
+ }
283
+ }
284
+
285
+ // TODO naive implementation of mapping a sd-jwt onto a IVerifiableCredential. Needs some fixes and further implementation and needs to be moved out of ssi-types
286
+ export const sdJwtDecodedCredentialToUniformCredential = (
287
+ decoded: SdJwtDecodedVerifiableCredential,
288
+ opts?: { maxTimeSkewInMS?: number }
289
+ ): IVerifiableCredential => {
290
+ const { decodedPayload } = decoded // fixme: other params and proof
291
+ const { exp, nbf, iss, iat, vct, cnf, status, sub, jti } = decodedPayload
292
+
293
+ type DisclosuresAccumulator = {
294
+ [key: string]: any
295
+ }
296
+
297
+ const credentialSubject = decoded.disclosures.reduce(
298
+ (acc: DisclosuresAccumulator, item: { decoded: Array<any>; digest: string; encoded: string }) => {
299
+ const key = item.decoded[1]
300
+ acc[key] = item.decoded[2]
301
+
302
+ return acc
303
+ },
304
+ {}
305
+ )
306
+
307
+ const maxSkewInMS = opts?.maxTimeSkewInMS ?? 1500
308
+
309
+ const expirationDate = jwtDateToISOString({ jwtClaim: exp, claimName: 'exp' })
310
+ let issuanceDateStr = jwtDateToISOString({ jwtClaim: iat, claimName: 'iat' })
311
+
312
+ let nbfDateAsStr: string | undefined
313
+ if (nbf) {
314
+ nbfDateAsStr = jwtDateToISOString({ jwtClaim: nbf, claimName: 'nbf' })
315
+ if (issuanceDateStr && nbfDateAsStr && issuanceDateStr !== nbfDateAsStr) {
316
+ const diff = Math.abs(new Date(nbfDateAsStr).getTime() - new Date(iss).getTime())
317
+ if (!maxSkewInMS || diff > maxSkewInMS) {
318
+ throw Error(`Inconsistent issuance dates between JWT claim (${nbfDateAsStr}) and VC value (${iss})`)
319
+ }
320
+ }
321
+ issuanceDateStr = nbfDateAsStr
322
+ }
323
+ const issuanceDate = issuanceDateStr
324
+ if (!issuanceDate) {
325
+ throw Error(`JWT issuance date is required but was not present`)
326
+ }
327
+
328
+ const credential: Omit<IVerifiableCredential, 'issuer' | 'issuanceDate'> = {
329
+ type: [vct], // SDJwt is not a W3C VC, so no VerifiableCredential
330
+ '@context': [], // SDJwt has no JSON-LD by default. Certainly not the VC DM1 default context for JSON-LD
331
+ credentialSubject: {
332
+ ...credentialSubject,
333
+ id: credentialSubject.id ?? sub ?? jti,
334
+ },
335
+ issuanceDate,
336
+ expirationDate,
337
+ issuer: iss,
338
+ ...(cnf && { cnf }),
339
+ ...(status && { status }),
340
+ proof: {
341
+ type: IProofType.SdJwtProof2024,
342
+ created: nbfDateAsStr ?? issuanceDate,
343
+ proofPurpose: IProofPurpose.authentication,
344
+ verificationMethod: iss,
345
+ jwt: decoded.compactSdJwtVc,
346
+ },
347
+ }
348
+
349
+ return credential as IVerifiableCredential
350
+ }
351
+
352
+ const jwtDateToISOString = ({
353
+ jwtClaim,
354
+ claimName,
355
+ isRequired = false,
356
+ }: {
357
+ jwtClaim?: number
358
+ claimName: string
359
+ isRequired?: boolean
360
+ }): string | undefined => {
361
+ if (jwtClaim) {
362
+ const claim = parseInt(jwtClaim.toString())
363
+ // change JWT seconds to millisecond for the date
364
+ return new Date(claim * (claim < 9999999999 ? 1000 : 1)).toISOString().replace(/\.000Z/, 'Z')
365
+ } else if (isRequired) {
366
+ throw Error(`JWT claim ${claimName} is required but was not present`)
367
+ }
368
+ return undefined
369
+ }