@sphereon/ssi-sdk.vc-status-list 0.32.1-next.13 → 0.32.1-next.145

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.
Files changed (51) hide show
  1. package/dist/functions.d.ts +13 -13
  2. package/dist/functions.d.ts.map +1 -1
  3. package/dist/functions.js +53 -109
  4. package/dist/functions.js.map +1 -1
  5. package/dist/impl/IStatusList.d.ts +26 -0
  6. package/dist/impl/IStatusList.d.ts.map +1 -0
  7. package/dist/impl/IStatusList.js +3 -0
  8. package/dist/impl/IStatusList.js.map +1 -0
  9. package/dist/impl/OAuthStatusList.d.ts +20 -0
  10. package/dist/impl/OAuthStatusList.d.ts.map +1 -0
  11. package/dist/impl/OAuthStatusList.js +147 -0
  12. package/dist/impl/OAuthStatusList.js.map +1 -0
  13. package/dist/impl/StatusList2021.d.ts +15 -0
  14. package/dist/impl/StatusList2021.d.ts.map +1 -0
  15. package/dist/impl/StatusList2021.js +170 -0
  16. package/dist/impl/StatusList2021.js.map +1 -0
  17. package/dist/impl/StatusListFactory.d.ts +11 -0
  18. package/dist/impl/StatusListFactory.d.ts.map +1 -0
  19. package/dist/impl/StatusListFactory.js +32 -0
  20. package/dist/impl/StatusListFactory.js.map +1 -0
  21. package/dist/impl/encoding/cbor.d.ts +6 -0
  22. package/dist/impl/encoding/cbor.d.ts.map +1 -0
  23. package/dist/impl/encoding/cbor.js +140 -0
  24. package/dist/impl/encoding/cbor.js.map +1 -0
  25. package/dist/impl/encoding/common.d.ts +12 -0
  26. package/dist/impl/encoding/common.d.ts.map +1 -0
  27. package/dist/impl/encoding/common.js +26 -0
  28. package/dist/impl/encoding/common.js.map +1 -0
  29. package/dist/impl/encoding/jwt.d.ts +9 -0
  30. package/dist/impl/encoding/jwt.d.ts.map +1 -0
  31. package/dist/impl/encoding/jwt.js +74 -0
  32. package/dist/impl/encoding/jwt.js.map +1 -0
  33. package/dist/types/index.d.ts +115 -30
  34. package/dist/types/index.d.ts.map +1 -1
  35. package/dist/types/index.js +12 -0
  36. package/dist/types/index.js.map +1 -1
  37. package/dist/utils.d.ts +17 -0
  38. package/dist/utils.d.ts.map +1 -0
  39. package/dist/utils.js +88 -0
  40. package/dist/utils.js.map +1 -0
  41. package/package.json +11 -3
  42. package/src/functions.ts +73 -159
  43. package/src/impl/IStatusList.ts +42 -0
  44. package/src/impl/OAuthStatusList.ts +196 -0
  45. package/src/impl/StatusList2021.ts +223 -0
  46. package/src/impl/StatusListFactory.ts +34 -0
  47. package/src/impl/encoding/cbor.ts +171 -0
  48. package/src/impl/encoding/common.ts +25 -0
  49. package/src/impl/encoding/jwt.ts +80 -0
  50. package/src/types/index.ts +132 -34
  51. package/src/utils.ts +95 -0
@@ -0,0 +1,223 @@
1
+ import { IAgentContext, ICredentialPlugin, ProofFormat as VeramoProofFormat } from '@veramo/core'
2
+ import { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
3
+ import { CredentialMapper, DocumentFormat, IIssuer, ProofFormat, StatusListCredential, StatusListType } from '@sphereon/ssi-types'
4
+
5
+ import { StatusList } from '@sphereon/vc-status-list'
6
+ import { IStatusList } from './IStatusList'
7
+ import {
8
+ CheckStatusIndexArgs,
9
+ CreateStatusListArgs,
10
+ Status2021,
11
+ StatusListResult,
12
+ ToStatusListDetailsArgs,
13
+ UpdateStatusListFromEncodedListArgs,
14
+ UpdateStatusListIndexArgs,
15
+ } from '../types'
16
+ import { assertValidProofType, getAssertedProperty, getAssertedValue, getAssertedValues } from '../utils'
17
+
18
+ export const DEFAULT_LIST_LENGTH = 250000
19
+ export const DEFAULT_PROOF_FORMAT = 'lds' as VeramoProofFormat
20
+
21
+ export class StatusList2021Implementation implements IStatusList {
22
+ async createNewStatusList(
23
+ args: CreateStatusListArgs,
24
+ context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
25
+ ): Promise<StatusListResult> {
26
+ const length = args?.length ?? DEFAULT_LIST_LENGTH
27
+ const proofFormat: ProofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
28
+ assertValidProofType(StatusListType.StatusList2021, proofFormat)
29
+ const veramoProofFormat: VeramoProofFormat = proofFormat as VeramoProofFormat
30
+
31
+ const { issuer, id } = args
32
+ const correlationId = getAssertedValue('correlationId', args.correlationId)
33
+
34
+ const list = new StatusList({ length })
35
+ const encodedList = await list.encode()
36
+ const statusPurpose = 'revocation'
37
+
38
+ const statusListCredential = await this.createVerifiableCredential(
39
+ {
40
+ ...args,
41
+ encodedList,
42
+ proofFormat: veramoProofFormat,
43
+ },
44
+ context,
45
+ )
46
+
47
+ return {
48
+ encodedList,
49
+ statusListCredential: statusListCredential,
50
+ statusList2021: {
51
+ statusPurpose,
52
+ indexingDirection: 'rightToLeft',
53
+ },
54
+ length,
55
+ type: StatusListType.StatusList2021,
56
+ proofFormat,
57
+ id,
58
+ correlationId,
59
+ issuer,
60
+ }
61
+ }
62
+
63
+ async updateStatusListIndex(
64
+ args: UpdateStatusListIndexArgs,
65
+ context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
66
+ ): Promise<StatusListResult> {
67
+ const credential = args.statusListCredential
68
+ const uniform = CredentialMapper.toUniformCredential(credential)
69
+ const { issuer, credentialSubject } = uniform
70
+ const id = getAssertedValue('id', uniform.id)
71
+ const origEncodedList = getAssertedProperty('encodedList', credentialSubject)
72
+
73
+ const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
74
+ const statusList = await StatusList.decode({ encodedList: origEncodedList })
75
+ statusList.setStatus(index, args.value != 0)
76
+ const encodedList = await statusList.encode()
77
+
78
+ const updatedCredential = await this.createVerifiableCredential(
79
+ {
80
+ ...args,
81
+ id,
82
+ issuer,
83
+ encodedList,
84
+ proofFormat: CredentialMapper.detectDocumentType(credential) === DocumentFormat.JWT ? 'jwt' : 'lds',
85
+ },
86
+ context,
87
+ )
88
+
89
+ return {
90
+ statusListCredential: updatedCredential,
91
+ encodedList,
92
+ statusList2021: {
93
+ ...('statusPurpose' in credentialSubject ? { statusPurpose: credentialSubject.statusPurpose } : {}),
94
+ indexingDirection: 'rightToLeft',
95
+ },
96
+ length: statusList.length - 1,
97
+ type: StatusListType.StatusList2021,
98
+ proofFormat: CredentialMapper.detectDocumentType(credential) === DocumentFormat.JWT ? 'jwt' : 'lds',
99
+ id,
100
+ issuer,
101
+ }
102
+ }
103
+
104
+ async updateStatusListFromEncodedList(
105
+ args: UpdateStatusListFromEncodedListArgs,
106
+ context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
107
+ ): Promise<StatusListResult> {
108
+ if (!args.statusList2021) {
109
+ throw new Error('statusList2021 options required for type StatusList2021')
110
+ }
111
+ const proofFormat: ProofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
112
+ assertValidProofType(StatusListType.StatusList2021, proofFormat)
113
+ const veramoProofFormat: VeramoProofFormat = proofFormat as VeramoProofFormat
114
+
115
+ const { issuer, id } = getAssertedValues(args)
116
+ const statusList = await StatusList.decode({ encodedList: args.encodedList })
117
+ const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
118
+ statusList.setStatus(index, args.value)
119
+
120
+ const newEncodedList = await statusList.encode()
121
+ const credential = await this.createVerifiableCredential(
122
+ {
123
+ id,
124
+ issuer,
125
+ encodedList: newEncodedList,
126
+ proofFormat: veramoProofFormat,
127
+ keyRef: args.keyRef,
128
+ },
129
+ context,
130
+ )
131
+
132
+ return {
133
+ type: StatusListType.StatusList2021,
134
+ statusListCredential: credential,
135
+ encodedList: newEncodedList,
136
+ statusList2021: {
137
+ statusPurpose: args.statusList2021.statusPurpose,
138
+ indexingDirection: 'rightToLeft',
139
+ },
140
+ length: statusList.length,
141
+ proofFormat: args.proofFormat ?? 'lds',
142
+ id: id,
143
+ issuer: issuer,
144
+ }
145
+ }
146
+
147
+ async checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | Status2021> {
148
+ const uniform = CredentialMapper.toUniformCredential(args.statusListCredential)
149
+ const { credentialSubject } = uniform
150
+ const encodedList = getAssertedProperty('encodedList', credentialSubject)
151
+
152
+ const statusList = await StatusList.decode({ encodedList })
153
+ const status = statusList.getStatus(typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex))
154
+ return status ? Status2021.Invalid : Status2021.Valid
155
+ }
156
+
157
+ async toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult> {
158
+ const { statusListPayload } = args
159
+ const uniform = CredentialMapper.toUniformCredential(statusListPayload)
160
+ const { issuer, credentialSubject } = uniform
161
+ const id = getAssertedValue('id', uniform.id)
162
+ const encodedList = getAssertedProperty('encodedList', credentialSubject)
163
+ const proofFormat: ProofFormat = CredentialMapper.detectDocumentType(statusListPayload) === DocumentFormat.JWT ? 'jwt' : 'lds'
164
+
165
+ const statusPurpose = getAssertedProperty('statusPurpose', credentialSubject)
166
+ const list = await StatusList.decode({ encodedList })
167
+
168
+ return {
169
+ id,
170
+ encodedList,
171
+ issuer,
172
+ type: StatusListType.StatusList2021,
173
+ proofFormat,
174
+ length: list.length,
175
+ statusListCredential: statusListPayload,
176
+ statusList2021: {
177
+ indexingDirection: 'rightToLeft',
178
+ statusPurpose,
179
+ },
180
+ ...(args.correlationId && { correlationId: args.correlationId }),
181
+ ...(args.driverType && { driverType: args.driverType }),
182
+ }
183
+ }
184
+
185
+ private async createVerifiableCredential(
186
+ args: {
187
+ id: string
188
+ issuer: string | IIssuer
189
+ encodedList: string
190
+ proofFormat: VeramoProofFormat
191
+ keyRef?: string
192
+ },
193
+ context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
194
+ ): Promise<StatusListCredential> {
195
+ const identifier = await context.agent.identifierManagedGet({
196
+ identifier: typeof args.issuer === 'string' ? args.issuer : args.issuer.id,
197
+ vmRelationship: 'assertionMethod',
198
+ offlineWhenNoDIDRegistered: true,
199
+ })
200
+
201
+ const credential = {
202
+ '@context': ['https://www.w3.org/2018/credentials/v1', 'https://w3id.org/vc/status-list/2021/v1'],
203
+ id: args.id,
204
+ issuer: args.issuer,
205
+ type: ['VerifiableCredential', 'StatusList2021Credential'],
206
+ credentialSubject: {
207
+ id: args.id,
208
+ type: 'StatusList2021',
209
+ statusPurpose: 'revocation',
210
+ encodedList: args.encodedList,
211
+ },
212
+ }
213
+
214
+ const verifiableCredential = await context.agent.createVerifiableCredential({
215
+ credential,
216
+ keyRef: args.keyRef ?? identifier.kmsKeyRef,
217
+ proofFormat: args.proofFormat,
218
+ fetchRemoteContexts: true,
219
+ })
220
+
221
+ return CredentialMapper.toWrappedVerifiableCredential(verifiableCredential as StatusListCredential).original as StatusListCredential
222
+ }
223
+ }
@@ -0,0 +1,34 @@
1
+ import { IStatusList } from './IStatusList'
2
+ import { StatusList2021Implementation } from './StatusList2021'
3
+ import { OAuthStatusListImplementation } from './OAuthStatusList'
4
+ import { StatusListType } from '@sphereon/ssi-types'
5
+
6
+ export class StatusListFactory {
7
+ private static instance: StatusListFactory
8
+ private implementations: Map<StatusListType, IStatusList>
9
+
10
+ private constructor() {
11
+ this.implementations = new Map()
12
+ this.implementations.set(StatusListType.StatusList2021, new StatusList2021Implementation())
13
+ this.implementations.set(StatusListType.OAuthStatusList, new OAuthStatusListImplementation())
14
+ }
15
+
16
+ public static getInstance(): StatusListFactory {
17
+ if (!StatusListFactory.instance) {
18
+ StatusListFactory.instance = new StatusListFactory()
19
+ }
20
+ return StatusListFactory.instance
21
+ }
22
+
23
+ public getByType(type: StatusListType): IStatusList {
24
+ const statusList = this.implementations.get(type)
25
+ if (!statusList) {
26
+ throw new Error(`No implementation found for status list type: ${type}`)
27
+ }
28
+ return statusList
29
+ }
30
+ }
31
+
32
+ export function getStatusListImplementation(type: StatusListType): IStatusList {
33
+ return StatusListFactory.getInstance().getByType(type)
34
+ }
@@ -0,0 +1,171 @@
1
+ import { StatusList } from '@sd-jwt/jwt-status-list'
2
+ import { deflate, inflate } from 'pako'
3
+ import { com, kotlin } from '@sphereon/kmp-cbor'
4
+ import base64url from 'base64url'
5
+ import { IRequiredContext, SignedStatusListData } from '../../types'
6
+ import { DecodedStatusListPayload, resolveIdentifier } from './common'
7
+ import { BitsPerStatus } from '@sd-jwt/jwt-status-list/dist'
8
+
9
+ const cbor = com.sphereon.cbor
10
+ const kmp = com.sphereon.kmp
11
+ const decompressRawStatusList = (StatusList as any).decodeStatusList.bind(StatusList)
12
+
13
+ const CWT_CLAIMS = {
14
+ SUBJECT: 2,
15
+ ISSUER: 1,
16
+ ISSUED_AT: 6,
17
+ EXPIRATION: 4,
18
+ TIME_TO_LIVE: 65534,
19
+ STATUS_LIST: 65533,
20
+ } as const
21
+
22
+ export const createSignedCbor = async (
23
+ context: IRequiredContext,
24
+ statusList: StatusList,
25
+ issuerString: string,
26
+ id: string,
27
+ expiresAt?: Date,
28
+ keyRef?: string,
29
+ ): Promise<SignedStatusListData> => {
30
+ const identifier = await resolveIdentifier(context, issuerString, keyRef)
31
+
32
+ const encodeStatusList = statusList.encodeStatusList()
33
+ const compressedList = deflate(encodeStatusList, { level: 9 })
34
+ const compressedBytes = new Int8Array(compressedList)
35
+
36
+ const statusListMap = new cbor.CborMap(
37
+ kotlin.collections.KtMutableMap.fromJsMap(
38
+ new Map<com.sphereon.cbor.CborString, com.sphereon.cbor.CborItem<any>>([
39
+ [new cbor.CborString('bits'), new cbor.CborUInt(kmp.LongKMP.fromNumber(statusList.getBitsPerStatus()))],
40
+ [new cbor.CborString('lst'), new cbor.CborByteString(compressedBytes)],
41
+ ]),
42
+ ),
43
+ )
44
+
45
+ const protectedHeader = new cbor.CborMap(
46
+ kotlin.collections.KtMutableMap.fromJsMap(
47
+ new Map([[new cbor.CborUInt(kmp.LongKMP.fromNumber(16)), new cbor.CborString('statuslist+cwt')]]), // "type"
48
+ ),
49
+ )
50
+ const protectedHeaderEncoded = cbor.Cbor.encode(protectedHeader)
51
+ const claimsMap = buildClaimsMap(id, issuerString, statusListMap, expiresAt)
52
+ const claimsEncoded: Int8Array = cbor.Cbor.encode(claimsMap)
53
+
54
+ const signedCWT: string = await context.agent.keyManagerSign({
55
+ keyRef: identifier.kmsKeyRef,
56
+ data: base64url.encode(Buffer.from(claimsEncoded)), // TODO test on RN
57
+ encoding: undefined,
58
+ })
59
+
60
+ const protectedHeaderEncodedInt8 = new Int8Array(protectedHeaderEncoded)
61
+ const claimsEncodedInt8 = new Int8Array(claimsEncoded)
62
+ const signatureBytes = base64url.decode(signedCWT)
63
+ const signatureInt8 = new Int8Array(Buffer.from(signatureBytes))
64
+
65
+ const cwtArrayElements: Array<com.sphereon.cbor.CborItem<any>> = [
66
+ new cbor.CborByteString(protectedHeaderEncodedInt8),
67
+ new cbor.CborByteString(claimsEncodedInt8),
68
+ new cbor.CborByteString(signatureInt8),
69
+ ]
70
+ const cwtArray = new cbor.CborArray(kotlin.collections.KtMutableList.fromJsArray(cwtArrayElements))
71
+ const cwtEncoded = cbor.Cbor.encode(cwtArray)
72
+ const cwtBuffer = Buffer.from(cwtEncoded)
73
+ return {
74
+ statusListCredential: base64url.encode(cwtBuffer),
75
+ encodedList: base64url.encode(compressedList as Buffer), // JS in @sd-jwt/jwt-status-list drops it in like this, so keep the same method
76
+ }
77
+ }
78
+
79
+ function buildClaimsMap(
80
+ id: string,
81
+ issuerString: string,
82
+ statusListMap: com.sphereon.cbor.CborMap<com.sphereon.cbor.CborString, com.sphereon.cbor.CborItem<any>>,
83
+ expiresAt?: Date,
84
+ ) {
85
+ const ttl = 65535 // FIXME figure out what value should be / come from and what the difference is with exp
86
+ const claimsEntries: Array<[com.sphereon.cbor.CborUInt, com.sphereon.cbor.CborItem<any>]> = [
87
+ [new cbor.CborUInt(kmp.LongKMP.fromNumber(CWT_CLAIMS.SUBJECT)), new cbor.CborString(id)], // "sub"
88
+ [new cbor.CborUInt(kmp.LongKMP.fromNumber(CWT_CLAIMS.ISSUER)), new cbor.CborString(issuerString)], // "iss"
89
+ [
90
+ new cbor.CborUInt(kmp.LongKMP.fromNumber(CWT_CLAIMS.ISSUED_AT)),
91
+ new cbor.CborUInt(kmp.LongKMP.fromNumber(Math.floor(Date.now() / 1000))), // "iat"
92
+ ],
93
+ ]
94
+
95
+ if (expiresAt) {
96
+ claimsEntries.push([
97
+ new cbor.CborUInt(kmp.LongKMP.fromNumber(CWT_CLAIMS.EXPIRATION)),
98
+ new cbor.CborUInt(kmp.LongKMP.fromNumber(Math.floor(expiresAt.getTime() / 1000))), // "exp"
99
+ ])
100
+ }
101
+
102
+ if (ttl) {
103
+ claimsEntries.push([
104
+ new cbor.CborUInt(kmp.LongKMP.fromNumber(CWT_CLAIMS.TIME_TO_LIVE)),
105
+ new cbor.CborUInt(kmp.LongKMP.fromNumber(ttl)), // "time to live"
106
+ ])
107
+ }
108
+
109
+ claimsEntries.push([new cbor.CborUInt(kmp.LongKMP.fromNumber(CWT_CLAIMS.STATUS_LIST)), statusListMap])
110
+
111
+ const claimsMap = new cbor.CborMap(kotlin.collections.KtMutableMap.fromJsMap(new Map(claimsEntries)))
112
+ return claimsMap
113
+ }
114
+
115
+ const getCborValueFromMap = <T>(map: Map<com.sphereon.cbor.CborItem<any>, com.sphereon.cbor.CborItem<any>>, key: number): T => {
116
+ const value = getCborOptionalValueFromMap<T>(map, key)
117
+ if (value === undefined) {
118
+ throw new Error(`Required claim ${key} not found`)
119
+ }
120
+ return value
121
+ }
122
+
123
+ const getCborOptionalValueFromMap = <T>(
124
+ map: Map<com.sphereon.cbor.CborItem<any>, com.sphereon.cbor.CborItem<any>>,
125
+ key: number,
126
+ ): T | undefined | never => {
127
+ const value = map.get(new com.sphereon.cbor.CborUInt(kmp.LongKMP.fromNumber(key)))
128
+ if (!value) {
129
+ return undefined
130
+ }
131
+ return value.value as T
132
+ }
133
+
134
+ export const decodeStatusListCWT = (cwt: string): DecodedStatusListPayload => {
135
+ const encodedCbor = base64url.toBuffer(cwt)
136
+ const encodedCborArray = new Int8Array(encodedCbor)
137
+ const decodedCbor = com.sphereon.cbor.Cbor.decode(encodedCborArray)
138
+
139
+ if (!(decodedCbor instanceof com.sphereon.cbor.CborArray)) {
140
+ throw new Error('Invalid CWT format: Expected a CBOR array')
141
+ }
142
+
143
+ const [, payload] = decodedCbor.value.asJsArrayView()
144
+ if (!(payload instanceof com.sphereon.cbor.CborByteString)) {
145
+ throw new Error('Invalid payload format: Expected a CBOR ByteString')
146
+ }
147
+
148
+ const claims = com.sphereon.cbor.Cbor.decode(payload.value)
149
+ if (!(claims instanceof com.sphereon.cbor.CborMap)) {
150
+ throw new Error('Invalid claims format: Expected a CBOR map')
151
+ }
152
+
153
+ const claimsMap = claims.value.asJsMapView()
154
+
155
+ const statusListMap = claimsMap.get(new com.sphereon.cbor.CborUInt(kmp.LongKMP.fromNumber(65533))).value.asJsMapView()
156
+
157
+ const bits = Number(statusListMap.get(new com.sphereon.cbor.CborString('bits')).value) as BitsPerStatus
158
+ const decoded = new Uint8Array(statusListMap.get(new com.sphereon.cbor.CborString('lst')).value)
159
+ const uint8Array = inflate(decoded)
160
+ const rawStatusList = decompressRawStatusList(uint8Array, bits)
161
+ const statusList = new StatusList(rawStatusList, bits)
162
+
163
+ return {
164
+ issuer: getCborValueFromMap<string>(claimsMap, CWT_CLAIMS.ISSUER),
165
+ id: getCborValueFromMap<string>(claimsMap, CWT_CLAIMS.SUBJECT),
166
+ statusList,
167
+ iat: Number(getCborValueFromMap<number>(claimsMap, CWT_CLAIMS.ISSUED_AT)),
168
+ exp: getCborOptionalValueFromMap<number>(claimsMap, CWT_CLAIMS.EXPIRATION),
169
+ ttl: getCborOptionalValueFromMap<number>(claimsMap, CWT_CLAIMS.TIME_TO_LIVE),
170
+ }
171
+ }
@@ -0,0 +1,25 @@
1
+ import { IRequiredContext } from '../../types'
2
+ import { StatusList } from '@sd-jwt/jwt-status-list'
3
+
4
+ export interface DecodedStatusListPayload {
5
+ issuer: string
6
+ id: string
7
+ statusList: StatusList
8
+ exp?: number
9
+ ttl?: number
10
+ iat: number
11
+ }
12
+
13
+ export const resolveIdentifier = async (context: IRequiredContext, issuer: string, keyRef?: string) => {
14
+ if (keyRef) {
15
+ return await context.agent.identifierManagedGetByKid({
16
+ identifier: keyRef,
17
+ })
18
+ }
19
+
20
+ return await context.agent.identifierManagedGet({
21
+ identifier: issuer,
22
+ vmRelationship: 'assertionMethod',
23
+ offlineWhenNoDIDRegistered: true,
24
+ })
25
+ }
@@ -0,0 +1,80 @@
1
+ import { CompactJWT, JoseSignatureAlgorithm } from '@sphereon/ssi-types'
2
+ import { createHeaderAndPayload, StatusList, StatusListJWTHeaderParameters, StatusListJWTPayload } from '@sd-jwt/jwt-status-list'
3
+ import base64url from 'base64url'
4
+ import { JWTPayload } from 'did-jwt'
5
+ import { IRequiredContext, SignedStatusListData } from '../../types'
6
+ import { DecodedStatusListPayload, resolveIdentifier } from './common'
7
+ import { TKeyType } from '@veramo/core'
8
+ import { ensureManagedIdentifierResult } from '@sphereon/ssi-sdk-ext.identifier-resolution'
9
+
10
+ const STATUS_LIST_JWT_TYP = 'statuslist+jwt'
11
+
12
+ export const createSignedJwt = async (
13
+ context: IRequiredContext,
14
+ statusList: StatusList,
15
+ issuerString: string,
16
+ id: string,
17
+ expiresAt?: Date,
18
+ keyRef?: string,
19
+ ): Promise<SignedStatusListData> => {
20
+ const identifier = await resolveIdentifier(context, issuerString, keyRef)
21
+ const resolution = await ensureManagedIdentifierResult(identifier, context)
22
+
23
+ const payload: JWTPayload = {
24
+ iss: issuerString,
25
+ sub: id,
26
+ iat: Math.floor(Date.now() / 1000),
27
+ ...(expiresAt && { exp: Math.floor(expiresAt.getTime() / 1000) }),
28
+ }
29
+
30
+ const header: StatusListJWTHeaderParameters = {
31
+ alg: getSigningAlgo(resolution.key.type),
32
+ typ: STATUS_LIST_JWT_TYP,
33
+ }
34
+ const values = createHeaderAndPayload(statusList, payload, header)
35
+ const signedJwt = await context.agent.jwtCreateJwsCompactSignature({
36
+ issuer: { ...identifier, noIssPayloadUpdate: false },
37
+ protectedHeader: values.header,
38
+ payload: values.payload,
39
+ })
40
+
41
+ return {
42
+ statusListCredential: signedJwt.jwt,
43
+ encodedList: (values.payload as StatusListJWTPayload).status_list.lst,
44
+ }
45
+ }
46
+
47
+ export const decodeStatusListJWT = (jwt: CompactJWT): DecodedStatusListPayload => {
48
+ const [, payloadBase64] = jwt.split('.')
49
+ const payload = JSON.parse(base64url.decode(payloadBase64))
50
+
51
+ if (!payload.iss || !payload.sub || !payload.status_list) {
52
+ throw new Error('Missing required fields in JWT payload')
53
+ }
54
+
55
+ const statusList = StatusList.decompressStatusList(payload.status_list.lst, payload.status_list.bits)
56
+
57
+ return {
58
+ issuer: payload.iss,
59
+ id: payload.sub,
60
+ statusList,
61
+ exp: payload.exp,
62
+ ttl: payload.ttl,
63
+ iat: payload.iat,
64
+ }
65
+ }
66
+
67
+ export const getSigningAlgo = (type: TKeyType): JoseSignatureAlgorithm => {
68
+ switch (type) {
69
+ case 'Ed25519':
70
+ return JoseSignatureAlgorithm.EdDSA
71
+ case 'Secp256k1':
72
+ return JoseSignatureAlgorithm.ES256K
73
+ case 'Secp256r1':
74
+ return JoseSignatureAlgorithm.ES256
75
+ case 'RSA':
76
+ return JoseSignatureAlgorithm.RS256
77
+ default:
78
+ throw Error('Key type not yet supported')
79
+ }
80
+ }