@sphereon/ssi-sdk.vc-status-list 0.32.1-next.18 → 0.32.1-next.287

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 +21 -0
  10. package/dist/impl/OAuthStatusList.d.ts.map +1 -0
  11. package/dist/impl/OAuthStatusList.js +155 -0
  12. package/dist/impl/OAuthStatusList.js.map +1 -0
  13. package/dist/impl/StatusList2021.d.ts +16 -0
  14. package/dist/impl/StatusList2021.d.ts.map +1 -0
  15. package/dist/impl/StatusList2021.js +186 -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 +17 -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 +131 -33
  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 +17 -6
  42. package/src/functions.ts +73 -159
  43. package/src/impl/IStatusList.ts +42 -0
  44. package/src/impl/OAuthStatusList.ts +206 -0
  45. package/src/impl/StatusList2021.ts +241 -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 +20 -0
  49. package/src/impl/encoding/jwt.ts +80 -0
  50. package/src/types/index.ts +151 -37
  51. package/src/utils.ts +95 -0
package/src/functions.ts CHANGED
@@ -2,40 +2,42 @@ import { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resoluti
2
2
  import {
3
3
  CredentialMapper,
4
4
  DocumentFormat,
5
- IIssuer,
6
- OriginalVerifiableCredential,
5
+ ProofFormat,
6
+ StatusListCredential,
7
7
  StatusListDriverType,
8
8
  StatusListType,
9
9
  StatusPurpose2021,
10
10
  } from '@sphereon/ssi-types'
11
+ import { CredentialStatus, DIDDocument, IAgentContext, ICredentialPlugin, ProofFormat as VeramoProofFormat } from '@veramo/core'
11
12
 
12
- import { checkStatus, StatusList } from '@sphereon/vc-status-list'
13
- import { CredentialStatus, DIDDocument, IAgentContext, ICredentialPlugin, ProofFormat } from '@veramo/core'
13
+ import { checkStatus } from '@sphereon/vc-status-list'
14
14
  import { CredentialJwtOrJSON, StatusMethod } from 'credential-status'
15
15
  import {
16
16
  CreateNewStatusListFuncArgs,
17
+ Status2021,
17
18
  StatusList2021ToVerifiableCredentialArgs,
18
- StatusListDetails,
19
19
  StatusListResult,
20
+ StatusOAuth,
20
21
  UpdateStatusListFromEncodedListArgs,
21
- UpdateStatusListFromStatusListCredentialArgs,
22
+ UpdateStatusListIndexArgs,
22
23
  } from './types'
24
+ import { assertValidProofType, determineStatusListType, getAssertedValue, getAssertedValues } from './utils'
25
+ import { getStatusListImplementation } from './impl/StatusListFactory'
23
26
 
24
- export async function fetchStatusListCredential(args: { statusListCredential: string }): Promise<OriginalVerifiableCredential> {
27
+ export async function fetchStatusListCredential(args: { statusListCredential: string }): Promise<StatusListCredential> {
25
28
  const url = getAssertedValue('statusListCredential', args.statusListCredential)
26
29
  try {
27
30
  const response = await fetch(url)
28
31
  if (!response.ok) {
29
- const error = `Fetching status list ${url} resulted in an error: ${response.status} : ${response.statusText}`
30
- throw Error(error)
32
+ throw Error(`Fetching status list ${url} resulted in an error: ${response.status} : ${response.statusText}`)
31
33
  }
32
34
  const responseAsText = await response.text()
33
35
  if (responseAsText.trim().startsWith('{')) {
34
- return JSON.parse(responseAsText) as OriginalVerifiableCredential
36
+ return JSON.parse(responseAsText) as StatusListCredential
35
37
  }
36
- return responseAsText as OriginalVerifiableCredential
38
+ return responseAsText as StatusListCredential
37
39
  } catch (error) {
38
- console.log(`Fetching status list ${url} resulted in an unexpected error: ${error instanceof Error ? error.message : JSON.stringify(error)}`)
40
+ console.error(`Fetching status list ${url} resulted in an unexpected error: ${error instanceof Error ? error.message : JSON.stringify(error)}`)
39
41
  throw error
40
42
  }
41
43
  }
@@ -52,7 +54,7 @@ export function statusPluginStatusFunction(args: {
52
54
  const result = await checkStatusForCredential({
53
55
  ...args,
54
56
  documentLoader: args.documentLoader,
55
- credential: credential as OriginalVerifiableCredential,
57
+ credential: credential as StatusListCredential,
56
58
  errorUnknownListType: args.errorUnknownListType,
57
59
  })
58
60
 
@@ -75,7 +77,7 @@ export function vcLibCheckStatusFunction(args: {
75
77
  }) {
76
78
  const { mandatoryCredentialStatus, verifyStatusListCredential, verifyMatchingIssuers, errorUnknownListType } = args
77
79
  return (args: {
78
- credential: OriginalVerifiableCredential
80
+ credential: StatusListCredential
79
81
  documentLoader: any
80
82
  suite: any
81
83
  }): Promise<{
@@ -93,7 +95,7 @@ export function vcLibCheckStatusFunction(args: {
93
95
  }
94
96
 
95
97
  export async function checkStatusForCredential(args: {
96
- credential: OriginalVerifiableCredential
98
+ credential: StatusListCredential
97
99
  documentLoader: any
98
100
  suite: any
99
101
  mandatoryCredentialStatus?: boolean
@@ -132,7 +134,7 @@ export async function simpleCheckStatusFromStatusListUrl(args: {
132
134
  type?: StatusListType | 'StatusList2021Entry'
133
135
  id?: string
134
136
  statusListIndex: string
135
- }): Promise<boolean> {
137
+ }): Promise<number | Status2021 | StatusOAuth> {
136
138
  return checkStatusIndexFromStatusListCredential({
137
139
  ...args,
138
140
  statusListCredential: await fetchStatusListCredential(args),
@@ -140,163 +142,97 @@ export async function simpleCheckStatusFromStatusListUrl(args: {
140
142
  }
141
143
 
142
144
  export async function checkStatusIndexFromStatusListCredential(args: {
143
- statusListCredential: OriginalVerifiableCredential
145
+ statusListCredential: StatusListCredential
144
146
  statusPurpose?: StatusPurpose2021
145
147
  type?: StatusListType | 'StatusList2021Entry'
146
148
  id?: string
147
149
  statusListIndex: string | number
148
- }): Promise<boolean> {
149
- const requestedType = getAssertedStatusListType(args.type?.replace('Entry', '') as StatusListType)
150
- const uniform = CredentialMapper.toUniformCredential(args.statusListCredential)
151
- const { issuer, type, credentialSubject, id } = uniform
152
- getAssertedValue('issuer', issuer) // We are only checking the value here
153
- getAssertedValue('credentialSubject', credentialSubject)
154
- if (args.statusPurpose && 'statusPurpose' in credentialSubject) {
155
- if (args.statusPurpose !== credentialSubject.statusPurpose) {
156
- throw Error(
157
- `Status purpose in StatusList credential with id ${id} and value ${credentialSubject.statusPurpose} does not match supplied purpose: ${args.statusPurpose}`,
158
- )
159
- }
160
- } else if (args.id && args.id !== id) {
161
- throw Error(`Status list id ${id} did not match required supplied id: ${args.id}`)
162
- }
163
- if (!type || !(type.includes(requestedType) || type.includes(requestedType + 'Credential'))) {
164
- throw Error(`Credential type ${JSON.stringify(type)} does not contain requested type ${requestedType}`)
165
- }
166
- // @ts-ignore
167
- const encodedList = getAssertedValue('encodedList', credentialSubject['encodedList'])
168
-
169
- const statusList = await StatusList.decode({ encodedList })
170
- const status = statusList.getStatus(typeof args.statusListIndex === 'number' ? args.statusListIndex : Number.parseInt(args.statusListIndex))
171
- return status
150
+ }): Promise<number | Status2021 | StatusOAuth> {
151
+ const statusListType: StatusListType = determineStatusListType(args.statusListCredential)
152
+ const implementation = getStatusListImplementation(statusListType)
153
+ return implementation.checkStatusIndex(args)
172
154
  }
173
155
 
174
156
  export async function createNewStatusList(
175
157
  args: CreateNewStatusListFuncArgs,
176
158
  context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
177
159
  ): Promise<StatusListResult> {
178
- const length = args?.length ?? 250000
179
- const proofFormat = args?.proofFormat ?? 'lds'
180
- const { issuer, type, id } = getAssertedValues(args)
181
- const correlationId = getAssertedValue('correlationId', args.correlationId)
182
-
183
- const list = new StatusList({ length })
184
- const encodedList = await list.encode()
185
- const statusPurpose = args.statusPurpose ?? 'revocation'
186
- const statusListCredential = await statusList2021ToVerifiableCredential(
187
- {
188
- ...args,
189
- type,
190
- proofFormat,
191
- encodedList,
192
- },
193
- context,
194
- )
195
-
196
- return {
197
- encodedList,
198
- statusListCredential,
199
- length,
200
- type,
201
- proofFormat,
202
- id,
203
- correlationId,
204
- issuer,
205
- statusPurpose,
206
- indexingDirection: 'rightToLeft',
207
- } as StatusListResult
160
+ const { type } = getAssertedValues(args)
161
+ const implementation = getStatusListImplementation(type)
162
+ return implementation.createNewStatusList(args, context)
208
163
  }
209
164
 
210
165
  export async function updateStatusIndexFromStatusListCredential(
211
- args: UpdateStatusListFromStatusListCredentialArgs,
166
+ args: UpdateStatusListIndexArgs,
212
167
  context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
213
- ): Promise<StatusListDetails> {
214
- return updateStatusListIndexFromEncodedList(
215
- {
216
- ...(await statusListCredentialToDetails(args)),
217
- statusListIndex: args.statusListIndex,
218
- value: args.value,
219
- },
220
- context,
221
- )
168
+ ): Promise<StatusListResult> {
169
+ const credential = getAssertedValue('statusListCredential', args.statusListCredential)
170
+ const statusListType: StatusListType = determineStatusListType(credential)
171
+ const implementation = getStatusListImplementation(statusListType)
172
+ return implementation.updateStatusListIndex(args, context)
222
173
  }
223
174
 
175
+ // Keeping helper function for backward compatibility
224
176
  export async function statusListCredentialToDetails(args: {
225
- statusListCredential: OriginalVerifiableCredential
177
+ statusListCredential: StatusListCredential
226
178
  correlationId?: string
227
179
  driverType?: StatusListDriverType
228
- }): Promise<StatusListDetails> {
180
+ }): Promise<StatusListResult> {
229
181
  const credential = getAssertedValue('statusListCredential', args.statusListCredential)
230
- const uniform = CredentialMapper.toUniformCredential(credential)
231
- const { issuer, type, credentialSubject } = uniform
232
- if (!type.includes('StatusList2021Credential')) {
233
- throw Error('StatusList2021Credential type should be present in the Verifiable Credential')
182
+
183
+ let statusListType: StatusListType | undefined
184
+ const documentFormat = CredentialMapper.detectDocumentType(credential)
185
+ if (documentFormat === DocumentFormat.JWT) {
186
+ const [header] = credential.split('.')
187
+ const decodedHeader = JSON.parse(Buffer.from(header, 'base64').toString())
188
+
189
+ if (decodedHeader.typ === 'statuslist+jwt') {
190
+ statusListType = StatusListType.OAuthStatusList
191
+ }
192
+ } else if (documentFormat === DocumentFormat.MSO_MDOC) {
193
+ statusListType = StatusListType.OAuthStatusList
194
+ // TODO check CBOR content?
234
195
  }
235
- const id = getAssertedValue('id', uniform.id)
236
- // @ts-ignore
237
- const { encodedList, statusPurpose } = credentialSubject
238
- const proofFormat: ProofFormat = CredentialMapper.detectDocumentType(credential) === DocumentFormat.JWT ? 'jwt' : 'lds'
239
- return {
240
- id,
241
- encodedList,
242
- issuer,
243
- type: StatusListType.StatusList2021,
244
- proofFormat,
245
- indexingDirection: 'rightToLeft',
246
- length: (await StatusList.decode({ encodedList })).length,
247
- statusPurpose,
248
- statusListCredential: credential,
249
- ...(args.correlationId && { correlationId: args.correlationId }),
250
- ...(args.driverType && { driverType: args.driverType }),
196
+ if (!statusListType) {
197
+ const uniform = CredentialMapper.toUniformCredential(credential)
198
+ const type = uniform.type.find((t) => t.includes('StatusList2021') || t.includes('OAuth2StatusList'))
199
+ if (!type) {
200
+ throw new Error('Invalid status list credential type')
201
+ }
202
+ statusListType = type.replace('Credential', '') as StatusListType
251
203
  }
204
+
205
+ const implementation = getStatusListImplementation(statusListType)
206
+ return await implementation.toStatusListDetails({
207
+ statusListPayload: credential,
208
+ correlationId: args.correlationId,
209
+ driverType: args.driverType,
210
+ })
252
211
  }
253
212
 
254
213
  export async function updateStatusListIndexFromEncodedList(
255
214
  args: UpdateStatusListFromEncodedListArgs,
256
215
  context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
257
- ): Promise<StatusListDetails> {
258
- const { issuer, type, id } = getAssertedValues(args)
259
- const proofFormat = args?.proofFormat ?? 'lds'
260
- const origEncodedList = getAssertedValue('encodedList', args.encodedList)
261
- const index = getAssertedValue('index', typeof args.statusListIndex === 'number' ? args.statusListIndex : Number.parseInt(args.statusListIndex))
262
- const value = getAssertedValue('value', args.value)
263
- const statusPurpose = getAssertedValue('statusPurpose', args.statusPurpose)
264
-
265
- const statusList = await StatusList.decode({ encodedList: origEncodedList })
266
- statusList.setStatus(index, value)
267
- const encodedList = await statusList.encode()
268
- const statusListCredential = await statusList2021ToVerifiableCredential(
269
- {
270
- ...args,
271
- type,
272
- proofFormat,
273
- encodedList,
274
- },
275
- context,
276
- )
277
- return {
278
- encodedList,
279
- statusListCredential,
280
- length: statusList.length - 1,
281
- type,
282
- proofFormat,
283
- id,
284
- issuer,
285
- statusPurpose,
286
- indexingDirection: 'rightToLeft',
287
- }
216
+ ): Promise<StatusListResult> {
217
+ const { type } = getAssertedValue('type', args)
218
+ const implementation = getStatusListImplementation(type!)
219
+ return implementation.updateStatusListFromEncodedList(args, context)
288
220
  }
289
221
 
290
222
  export async function statusList2021ToVerifiableCredential(
291
223
  args: StatusList2021ToVerifiableCredentialArgs,
292
224
  context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
293
- ): Promise<OriginalVerifiableCredential> {
225
+ ): Promise<StatusListCredential> {
294
226
  const { issuer, id, type } = getAssertedValues(args)
295
227
  const identifier = await context.agent.identifierManagedGet({
296
228
  identifier: typeof issuer === 'string' ? issuer : issuer.id,
297
229
  vmRelationship: 'assertionMethod',
298
230
  offlineWhenNoDIDRegistered: true, // FIXME Fix identifier resolution for EBSI
299
231
  })
232
+ const proofFormat: ProofFormat = args?.proofFormat ?? 'lds'
233
+ assertValidProofType(StatusListType.StatusList2021, proofFormat)
234
+ const veramoProofFormat: VeramoProofFormat = proofFormat as VeramoProofFormat
235
+
300
236
  const encodedList = getAssertedValue('encodedList', args.encodedList)
301
237
  const statusPurpose = getAssertedValue('statusPurpose', args.statusPurpose)
302
238
  const credential = {
@@ -316,31 +252,9 @@ export async function statusList2021ToVerifiableCredential(
316
252
  const verifiableCredential = await context.agent.createVerifiableCredential({
317
253
  credential,
318
254
  keyRef: identifier.kmsKeyRef,
319
- proofFormat: args.proofFormat ?? 'lds',
255
+ proofFormat: veramoProofFormat,
320
256
  fetchRemoteContexts: true,
321
257
  })
322
258
 
323
- return CredentialMapper.toWrappedVerifiableCredential(verifiableCredential as OriginalVerifiableCredential).original
324
- }
325
-
326
- function getAssertedStatusListType(type?: StatusListType) {
327
- const assertedType = type ?? StatusListType.StatusList2021
328
- if (assertedType !== StatusListType.StatusList2021) {
329
- throw Error(`StatusList type ${assertedType} is not supported (yet)`)
330
- }
331
- return assertedType
332
- }
333
-
334
- function getAssertedValue<T>(name: string, value: T): NonNullable<T> {
335
- if (value === undefined || value === null) {
336
- throw Error(`Missing required ${name} value`)
337
- }
338
- return value
339
- }
340
-
341
- function getAssertedValues(args: { issuer: string | IIssuer; id: string; type?: StatusListType }) {
342
- const type = getAssertedStatusListType(args?.type)
343
- const id = getAssertedValue('id', args.id)
344
- const issuer = getAssertedValue('issuer', args.issuer)
345
- return { id, issuer, type }
259
+ return CredentialMapper.toWrappedVerifiableCredential(verifiableCredential as StatusListCredential).original as StatusListCredential
346
260
  }
@@ -0,0 +1,42 @@
1
+ import { IAgentContext, ICredentialPlugin } from '@veramo/core'
2
+ import { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
3
+ import {
4
+ CheckStatusIndexArgs,
5
+ CreateStatusListArgs,
6
+ Status2021,
7
+ StatusListResult,
8
+ StatusOAuth,
9
+ ToStatusListDetailsArgs,
10
+ UpdateStatusListFromEncodedListArgs,
11
+ UpdateStatusListIndexArgs,
12
+ } from '../types'
13
+
14
+ export interface IStatusList {
15
+ /**
16
+ * Creates a new status list of the specific type
17
+ */
18
+ createNewStatusList(args: CreateStatusListArgs, context: IAgentContext<ICredentialPlugin & IIdentifierResolution>): Promise<StatusListResult>
19
+
20
+ /**
21
+ * Updates a status at the given index in the status list
22
+ */
23
+ updateStatusListIndex(args: UpdateStatusListIndexArgs, context: IAgentContext<ICredentialPlugin & IIdentifierResolution>): Promise<StatusListResult>
24
+
25
+ /**
26
+ * Updates a status list using a base64 encoded list of statuses
27
+ */
28
+ updateStatusListFromEncodedList(
29
+ args: UpdateStatusListFromEncodedListArgs,
30
+ context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
31
+ ): Promise<StatusListResult>
32
+
33
+ /**
34
+ * Checks the status at a given index in the status list
35
+ */
36
+ checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | Status2021 | StatusOAuth>
37
+
38
+ /**
39
+ * Collects the status list details
40
+ */
41
+ toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult>
42
+ }
@@ -0,0 +1,206 @@
1
+ import { IAgentContext, ICredentialPlugin, IKeyManager } from '@veramo/core'
2
+ import { CompactJWT, CWT, ProofFormat, StatusListType } from '@sphereon/ssi-types'
3
+ import {
4
+ CheckStatusIndexArgs,
5
+ CreateStatusListArgs,
6
+ SignedStatusListData,
7
+ StatusListResult,
8
+ StatusOAuth,
9
+ ToStatusListDetailsArgs,
10
+ UpdateStatusListFromEncodedListArgs,
11
+ UpdateStatusListIndexArgs,
12
+ } from '../types'
13
+ import { determineProofFormat, getAssertedValue, getAssertedValues } from '../utils'
14
+ import { IStatusList } from './IStatusList'
15
+ import { StatusList } from '@sd-jwt/jwt-status-list'
16
+ import { IJwtService } from '@sphereon/ssi-sdk-ext.jwt-service'
17
+ import { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
18
+ import { createSignedJwt, decodeStatusListJWT } from './encoding/jwt'
19
+ import { createSignedCbor, decodeStatusListCWT } from './encoding/cbor'
20
+
21
+ type IRequiredContext = IAgentContext<ICredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>
22
+
23
+ export const DEFAULT_BITS_PER_STATUS = 1 // 1 bit is sufficient for 0x00 - "VALID" 0x01 - "INVALID" saving space in the process
24
+ export const DEFAULT_LIST_LENGTH = 250000
25
+ export const DEFAULT_PROOF_FORMAT = 'jwt' as ProofFormat
26
+
27
+ export class OAuthStatusListImplementation implements IStatusList {
28
+ async createNewStatusList(args: CreateStatusListArgs, context: IRequiredContext): Promise<StatusListResult> {
29
+ if (!args.oauthStatusList) {
30
+ throw new Error('OAuthStatusList options are required for type OAuthStatusList')
31
+ }
32
+
33
+ const proofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
34
+ const { issuer, id, oauthStatusList, keyRef } = args
35
+ const { bitsPerStatus, expiresAt } = oauthStatusList
36
+ const length = args.length ?? DEFAULT_LIST_LENGTH
37
+ const issuerString = typeof issuer === 'string' ? issuer : issuer.id
38
+ const correlationId = getAssertedValue('correlationId', args.correlationId)
39
+
40
+ const statusList = new StatusList(new Array(length).fill(0), bitsPerStatus ?? DEFAULT_BITS_PER_STATUS)
41
+ const encodedList = statusList.compressStatusList()
42
+ const { statusListCredential } = await this.createSignedStatusList(proofFormat, context, statusList, issuerString, id, expiresAt, keyRef)
43
+
44
+ return {
45
+ encodedList,
46
+ statusListCredential,
47
+ oauthStatusList: { bitsPerStatus },
48
+ length,
49
+ type: StatusListType.OAuthStatusList,
50
+ proofFormat,
51
+ id,
52
+ correlationId,
53
+ issuer,
54
+ statuslistContentType: this.buildContentType(proofFormat),
55
+ }
56
+ }
57
+
58
+ async updateStatusListIndex(args: UpdateStatusListIndexArgs, context: IRequiredContext): Promise<StatusListResult> {
59
+ const { statusListCredential, value, expiresAt, keyRef } = args
60
+ if (typeof statusListCredential !== 'string') {
61
+ return Promise.reject('statusListCredential in neither JWT nor CWT')
62
+ }
63
+
64
+ const proofFormat = determineProofFormat(statusListCredential)
65
+ const decoded = proofFormat === 'jwt' ? decodeStatusListJWT(statusListCredential) : decodeStatusListCWT(statusListCredential)
66
+ const { statusList, issuer, id } = decoded
67
+
68
+ const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
69
+ if (index < 0 || index >= statusList.statusList.length) {
70
+ throw new Error('Status list index out of bounds')
71
+ }
72
+
73
+ statusList.setStatus(index, value)
74
+ const { statusListCredential: signedCredential, encodedList } = await this.createSignedStatusList(
75
+ proofFormat,
76
+ context,
77
+ statusList,
78
+ issuer,
79
+ id,
80
+ expiresAt,
81
+ keyRef,
82
+ )
83
+
84
+ return {
85
+ statusListCredential: signedCredential,
86
+ encodedList,
87
+ oauthStatusList: {
88
+ bitsPerStatus: statusList.getBitsPerStatus(),
89
+ },
90
+ length: statusList.statusList.length,
91
+ type: StatusListType.OAuthStatusList,
92
+ proofFormat,
93
+ id,
94
+ issuer,
95
+ statuslistContentType: this.buildContentType(proofFormat),
96
+ }
97
+ }
98
+
99
+ // FIXME: This still assumes only two values (boolean), whilst this list supports 8 bits max
100
+ async updateStatusListFromEncodedList(args: UpdateStatusListFromEncodedListArgs, context: IRequiredContext): Promise<StatusListResult> {
101
+ if (!args.oauthStatusList) {
102
+ throw new Error('OAuthStatusList options are required for type OAuthStatusList')
103
+ }
104
+ const { proofFormat, oauthStatusList, keyRef } = args
105
+ const { bitsPerStatus, expiresAt } = oauthStatusList
106
+
107
+ const { issuer, id } = getAssertedValues(args)
108
+ const issuerString = typeof issuer === 'string' ? issuer : issuer.id
109
+
110
+ const listToUpdate = StatusList.decompressStatusList(args.encodedList, bitsPerStatus ?? DEFAULT_BITS_PER_STATUS)
111
+ const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
112
+ // FIXME: See above.
113
+ listToUpdate.setStatus(index, args.value ? 1 : 0)
114
+
115
+ const { statusListCredential, encodedList } = await this.createSignedStatusList(
116
+ proofFormat ?? DEFAULT_PROOF_FORMAT,
117
+ context,
118
+ listToUpdate,
119
+ issuerString,
120
+ id,
121
+ expiresAt,
122
+ keyRef,
123
+ )
124
+
125
+ return {
126
+ encodedList,
127
+ statusListCredential,
128
+ oauthStatusList: {
129
+ bitsPerStatus,
130
+ expiresAt,
131
+ },
132
+ length: listToUpdate.statusList.length,
133
+ type: StatusListType.OAuthStatusList,
134
+ proofFormat: proofFormat ?? DEFAULT_PROOF_FORMAT,
135
+ id,
136
+ issuer,
137
+ statuslistContentType: this.buildContentType(proofFormat),
138
+ }
139
+ }
140
+
141
+ private buildContentType(proofFormat: 'jwt' | 'lds' | 'EthereumEip712Signature2021' | 'cbor' | undefined) {
142
+ return `application/statuslist+${proofFormat === 'cbor' ? 'cwt' : 'jwt'}`
143
+ }
144
+
145
+ async checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | StatusOAuth> {
146
+ const { statusListCredential, statusListIndex } = args
147
+ if (typeof statusListCredential !== 'string') {
148
+ return Promise.reject('statusListCredential in neither JWT nor CWT')
149
+ }
150
+
151
+ const proofFormat = determineProofFormat(statusListCredential)
152
+ const { statusList } = proofFormat === 'jwt' ? decodeStatusListJWT(statusListCredential) : decodeStatusListCWT(statusListCredential)
153
+
154
+ const index = typeof statusListIndex === 'number' ? statusListIndex : parseInt(statusListIndex)
155
+ if (index < 0 || index >= statusList.statusList.length) {
156
+ throw new Error('Status list index out of bounds')
157
+ }
158
+
159
+ return statusList.getStatus(index)
160
+ }
161
+
162
+ async toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult> {
163
+ const { statusListPayload } = args as { statusListPayload: CompactJWT | CWT }
164
+ const proofFormat = determineProofFormat(statusListPayload)
165
+ const decoded = proofFormat === 'jwt' ? decodeStatusListJWT(statusListPayload) : decodeStatusListCWT(statusListPayload)
166
+ const { statusList, issuer, id, exp } = decoded
167
+
168
+ return {
169
+ id,
170
+ encodedList: statusList.compressStatusList(),
171
+ issuer,
172
+ type: StatusListType.OAuthStatusList,
173
+ proofFormat,
174
+ length: statusList.statusList.length,
175
+ statusListCredential: statusListPayload,
176
+ statuslistContentType: this.buildContentType(proofFormat),
177
+ oauthStatusList: {
178
+ bitsPerStatus: statusList.getBitsPerStatus(),
179
+ ...(exp && { expiresAt: new Date(exp * 1000) }),
180
+ },
181
+ ...(args.correlationId && { correlationId: args.correlationId }),
182
+ ...(args.driverType && { driverType: args.driverType }),
183
+ }
184
+ }
185
+
186
+ private async createSignedStatusList(
187
+ proofFormat: 'jwt' | 'lds' | 'EthereumEip712Signature2021' | 'cbor',
188
+ context: IAgentContext<ICredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>,
189
+ statusList: StatusList,
190
+ issuerString: string,
191
+ id: string,
192
+ expiresAt?: Date,
193
+ keyRef?: string,
194
+ ): Promise<SignedStatusListData> {
195
+ switch (proofFormat) {
196
+ case 'jwt': {
197
+ return await createSignedJwt(context, statusList, issuerString, id, expiresAt, keyRef)
198
+ }
199
+ case 'cbor': {
200
+ return await createSignedCbor(context, statusList, issuerString, id, expiresAt, keyRef)
201
+ }
202
+ default:
203
+ throw new Error(`Invalid proof format '${proofFormat}' for OAuthStatusList`)
204
+ }
205
+ }
206
+ }