@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.
- package/dist/mapper/credential-mapper.d.ts +70 -7
- package/dist/mapper/credential-mapper.d.ts.map +1 -1
- package/dist/mapper/credential-mapper.js +168 -8
- package/dist/mapper/credential-mapper.js.map +1 -1
- package/dist/types/generic.d.ts +1 -0
- package/dist/types/generic.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/sd-jwt-vc.d.ts +171 -0
- package/dist/types/sd-jwt-vc.d.ts.map +1 -0
- package/dist/types/sd-jwt-vc.js +87 -0
- package/dist/types/sd-jwt-vc.js.map +1 -0
- package/dist/types/vc.d.ts +13 -250
- package/dist/types/vc.d.ts.map +1 -1
- package/dist/types/vc.js +10 -20
- package/dist/types/vc.js.map +1 -1
- package/dist/types/w3c-vc.d.ts +248 -0
- package/dist/types/w3c-vc.d.ts.map +1 -0
- package/dist/types/w3c-vc.js +30 -0
- package/dist/types/w3c-vc.js.map +1 -0
- package/dist/utils/object.d.ts +1 -1
- package/dist/utils/object.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/mapper/credential-mapper.ts +214 -24
- package/src/types/generic.ts +2 -0
- package/src/types/index.ts +2 -0
- package/src/types/sd-jwt-vc.ts +248 -0
- package/src/types/vc.ts +44 -293
- package/src/types/w3c-vc.ts +300 -0
- package/src/utils/object.ts +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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) &&
|
|
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
|
-
|
|
159
|
-
return ObjectUtils.isString(original) && (
|
|
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
|
|
163
|
-
return
|
|
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
|
|
167
|
-
|
|
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:
|
|
642
|
+
document:
|
|
643
|
+
| W3CVerifiableCredential
|
|
644
|
+
| W3CVerifiablePresentation
|
|
645
|
+
| JwtDecodedVerifiableCredential
|
|
646
|
+
| JwtDecodedVerifiablePresentation
|
|
647
|
+
| SdJwtDecodedVerifiableCredential
|
|
463
648
|
): DocumentFormat {
|
|
464
|
-
if (
|
|
465
|
-
return
|
|
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
|
|
package/src/types/generic.ts
CHANGED
package/src/types/index.ts
CHANGED
|
@@ -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
|
+
}
|