@sphereon/ssi-sdk.vc-status-list 0.34.1-feature.SSISDK.17.bitstring.sl.13 → 0.34.1-feature.SSISDK.17.bitstring.sl.14

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sphereon/ssi-sdk.vc-status-list",
3
3
  "description": "Sphereon SSI-SDK plugin for Status List management, like StatusList2021.",
4
- "version": "0.34.1-feature.SSISDK.17.bitstring.sl.13+61bcf6d3",
4
+ "version": "0.34.1-feature.SSISDK.17.bitstring.sl.14+35c8ca99",
5
5
  "source": "src/index.ts",
6
6
  "type": "module",
7
7
  "main": "./dist/index.cjs",
@@ -31,9 +31,9 @@
31
31
  "@sphereon/ssi-sdk-ext.did-utils": "0.29.0",
32
32
  "@sphereon/ssi-sdk-ext.identifier-resolution": "0.29.0",
33
33
  "@sphereon/ssi-sdk-ext.jwt-service": "0.29.0",
34
- "@sphereon/ssi-sdk.credential-vcdm": "0.34.1-feature.SSISDK.17.bitstring.sl.13+61bcf6d3",
35
- "@sphereon/ssi-sdk.data-store": "0.34.1-feature.SSISDK.17.bitstring.sl.13+61bcf6d3",
36
- "@sphereon/ssi-types": "0.34.1-feature.SSISDK.17.bitstring.sl.13+61bcf6d3",
34
+ "@sphereon/ssi-sdk.credential-vcdm": "0.34.1-feature.SSISDK.17.bitstring.sl.14+35c8ca99",
35
+ "@sphereon/ssi-sdk.data-store": "0.34.1-feature.SSISDK.17.bitstring.sl.14+35c8ca99",
36
+ "@sphereon/ssi-types": "0.34.1-feature.SSISDK.17.bitstring.sl.14+35c8ca99",
37
37
  "@sphereon/vc-status-list": "7.0.0-next.0",
38
38
  "@veramo/core": "4.2.0",
39
39
  "@veramo/credential-status": "4.2.0",
@@ -72,5 +72,5 @@
72
72
  "SSI",
73
73
  "StatusList2021"
74
74
  ],
75
- "gitHead": "61bcf6d37bcb3d77036b5415d72853c3ba3f9273"
75
+ "gitHead": "35c8ca99b499927dfffd929ae709750a7337b017"
76
76
  }
package/src/functions.ts CHANGED
@@ -1,24 +1,22 @@
1
1
  import type { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
2
- import {
3
- CredentialMapper,
4
- type CredentialProofFormat,
5
- type StatusListCredential,
6
- StatusListDriverType,
7
- StatusListType,
8
- type StatusPurpose2021,
9
- } from '@sphereon/ssi-types'
2
+ import { CredentialMapper, type CredentialProofFormat, type StatusListCredential, StatusListType, type StatusPurpose2021 } from '@sphereon/ssi-types'
10
3
  import type { CredentialStatus, DIDDocument, IAgentContext, ProofFormat as VeramoProofFormat } from '@veramo/core'
11
4
 
12
- import { IAddStatusListArgs, IBitstringStatusListEntryEntity, IStatusListEntryEntity, StatusListEntity } from '@sphereon/ssi-sdk.data-store'
5
+ import {
6
+ BitstringStatusListEntryCredentialStatus,
7
+ IBitstringStatusListEntryEntity,
8
+ IStatusListEntryEntity,
9
+ StatusListEntity,
10
+ } from '@sphereon/ssi-sdk.data-store'
13
11
 
14
12
  import { checkStatus } from '@sphereon/vc-status-list'
15
13
 
16
14
  // @ts-ignore
17
15
  import { CredentialJwtOrJSON, StatusMethod } from 'credential-status'
18
16
  import {
19
- BitstringStatus,
20
- BitstringStatusListEntryCredentialStatus,
21
17
  CreateNewStatusListFuncArgs,
18
+ IMergeDetailsWithEntityArgs,
19
+ IToDetailsFromCredentialArgs,
22
20
  Status2021,
23
21
  StatusList2021EntryCredentialStatus,
24
22
  StatusList2021ToVerifiableCredentialArgs,
@@ -31,6 +29,12 @@ import {
31
29
  import { assertValidProofType, determineStatusListType, getAssertedValue, getAssertedValues } from './utils'
32
30
  import { getStatusListImplementation } from './impl/StatusListFactory'
33
31
  import { IVcdmCredentialPlugin } from '@sphereon/ssi-sdk.credential-vcdm'
32
+ import {
33
+ IBitstringStatusListImplementationResult,
34
+ IExtractedCredentialDetails,
35
+ IOAuthStatusListImplementationResult,
36
+ IStatusList2021ImplementationResult,
37
+ } from './impl/IStatusList'
34
38
 
35
39
  export async function fetchStatusListCredential(args: { statusListCredential: string }): Promise<StatusListCredential> {
36
40
  const url = getAssertedValue('statusListCredential', args.statusListCredential)
@@ -142,7 +146,7 @@ export async function simpleCheckStatusFromStatusListUrl(args: {
142
146
  type?: StatusListType | 'StatusList2021Entry'
143
147
  id?: string
144
148
  statusListIndex: string
145
- }): Promise<number | Status2021 | StatusOAuth | BitstringStatus> {
149
+ }): Promise<number | Status2021 | StatusOAuth> {
146
150
  return checkStatusIndexFromStatusListCredential({
147
151
  ...args,
148
152
  statusListCredential: await fetchStatusListCredential(args),
@@ -156,7 +160,7 @@ export async function checkStatusIndexFromStatusListCredential(args: {
156
160
  id?: string
157
161
  statusListIndex: string | number
158
162
  bitsPerStatus?: number
159
- }): Promise<number | Status2021 | StatusOAuth | BitstringStatus> {
163
+ }): Promise<number | Status2021 | StatusOAuth> {
160
164
  const statusListType: StatusListType = determineStatusListType(args.statusListCredential)
161
165
  const implementation = getStatusListImplementation(statusListType)
162
166
  return implementation.checkStatusIndex(args)
@@ -181,31 +185,36 @@ export async function updateStatusIndexFromStatusListCredential(
181
185
  return implementation.updateStatusListIndex(args, context)
182
186
  }
183
187
 
184
- export async function statusListCredentialToDetails({
185
- statusListType,
186
- correlationId,
187
- driverType,
188
- statusListCredential,
189
- bitsPerStatus,
190
- }: {
191
- statusListType: StatusListType
192
- statusListCredential: StatusListCredential
193
- correlationId?: string
194
- driverType?: StatusListDriverType
195
- bitsPerStatus?: number
196
- }): Promise<StatusListResult & Partial<IAddStatusListArgs>> {
197
- const credential = getAssertedValue('statusListCredential', statusListCredential)
188
+ export async function extractCredentialDetails(statusListCredential: StatusListCredential): Promise<IExtractedCredentialDetails> {
189
+ const statusListType = determineStatusListType(statusListCredential)
198
190
  const implementation = getStatusListImplementation(statusListType)
191
+ return implementation.extractCredentialDetails(statusListCredential)
192
+ }
199
193
 
200
- // The implementation should now return all the type-specific fields needed for the entity
201
- const result = await implementation.toStatusListDetails({
202
- statusListPayload: credential,
203
- correlationId: correlationId,
204
- driverType: driverType,
205
- bitsPerStatus: bitsPerStatus,
206
- })
194
+ export async function toStatusListDetails(
195
+ args: IToDetailsFromCredentialArgs,
196
+ ): Promise<StatusListResult & (IStatusList2021ImplementationResult | IOAuthStatusListImplementationResult | IBitstringStatusListImplementationResult)>
207
197
 
208
- return result
198
+ export async function toStatusListDetails(
199
+ args: IMergeDetailsWithEntityArgs,
200
+ ): Promise<StatusListResult & (IStatusList2021ImplementationResult | IOAuthStatusListImplementationResult | IBitstringStatusListImplementationResult)>
201
+
202
+ export async function toStatusListDetails(
203
+ args: IToDetailsFromCredentialArgs | IMergeDetailsWithEntityArgs,
204
+ ): Promise<
205
+ StatusListResult & (IStatusList2021ImplementationResult | IOAuthStatusListImplementationResult | IBitstringStatusListImplementationResult)
206
+ > {
207
+ if ('statusListCredential' in args) {
208
+ // CREATE/READ context
209
+ const statusListType = args.statusListType
210
+ const implementation = getStatusListImplementation(statusListType)
211
+ return implementation.toStatusListDetails(args)
212
+ } else {
213
+ // UPDATE context
214
+ const statusListType = args.statusListEntity.type
215
+ const implementation = getStatusListImplementation(statusListType)
216
+ return implementation.toStatusListDetails(args)
217
+ }
209
218
  }
210
219
 
211
220
  export async function createCredentialStatusFromStatusList(args: {
@@ -28,14 +28,13 @@ import {
28
28
  StatusListType,
29
29
  } from '@sphereon/ssi-types'
30
30
 
31
- import type { IBitstringStatusListImplementationResult, IStatusList } from './IStatusList'
31
+ import type { IBitstringStatusListImplementationResult, IExtractedCredentialDetails, IStatusList } from './IStatusList'
32
32
  import {
33
- BitstringStatus,
34
- BitstringStatusListEntryCredentialStatus,
35
33
  CheckStatusIndexArgs,
36
34
  CreateStatusListArgs,
35
+ IMergeDetailsWithEntityArgs,
36
+ IToDetailsFromCredentialArgs,
37
37
  StatusListResult,
38
- ToStatusListDetailsArgs,
39
38
  UpdateStatusListFromEncodedListArgs,
40
39
  UpdateStatusListIndexArgs,
41
40
  } from '../types'
@@ -48,11 +47,17 @@ import {
48
47
  BitstringStatusPurpose,
49
48
  createStatusListCredential,
50
49
  } from '@4sure-tech/vc-bitstring-status-lists'
51
- import { BitstringStatusListEntity, IBitstringStatusListEntryEntity, IStatusListEntryEntity, StatusListEntity } from '@sphereon/ssi-sdk.data-store'
50
+ import {
51
+ BitstringStatusListEntity,
52
+ BitstringStatusListEntryCredentialStatus,
53
+ IBitstringStatusListEntryEntity,
54
+ IStatusListEntryEntity,
55
+ StatusListEntity,
56
+ } from '@sphereon/ssi-sdk.data-store'
52
57
  import { IVcdmCredentialPlugin } from '@sphereon/ssi-sdk.credential-vcdm'
53
58
 
54
59
  export const DEFAULT_LIST_LENGTH = 131072 // W3C spec minimum
55
- export const DEFAULT_PROOF_FORMAT = 'lds' as CredentialProofFormat
60
+ export const DEFAULT_PROOF_FORMAT = 'vc+jwt' as CredentialProofFormat
56
61
  export const DEFAULT_STATUS_PURPOSE: BitstringStatusPurpose = 'revocation'
57
62
 
58
63
  /**
@@ -148,9 +153,10 @@ export class BitstringStatusListImplementation implements IStatusList {
148
153
 
149
154
  const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
150
155
  const statusList: BitstreamStatusList = await BitstreamStatusList.decode({ encodedList: origEncodedList, statusSize: args.bitsPerStatus })
151
- statusList.setStatus(index, args.value)
156
+ const bitstringStatusId = args.value as number
157
+ statusList.setStatus(index, bitstringStatusId)
152
158
 
153
- const proofFormat = CredentialMapper.detectDocumentType(credential) === DocumentFormat.JWT ? 'jwt' : 'lds'
159
+ const proofFormat = CredentialMapper.detectDocumentType(credential) === DocumentFormat.JWT ? 'vc+jwt' : 'lds'
154
160
 
155
161
  const credSubject = Array.isArray(credentialSubject) ? credentialSubject[0] : credentialSubject
156
162
 
@@ -275,7 +281,7 @@ export class BitstringStatusListImplementation implements IStatusList {
275
281
  * @param args - Check parameters including the status list credential and index
276
282
  * @returns Promise resolving to the status value at the specified index
277
283
  */
278
- async checkStatusIndex(args: CheckStatusIndexArgs): Promise<BitstringStatus> {
284
+ async checkStatusIndex(args: CheckStatusIndexArgs): Promise<number> {
279
285
  if (!args.bitsPerStatus || args.bitsPerStatus < 1) {
280
286
  return Promise.reject(Error('bitsPerStatus must be set for bitstring status lists and must be 1 or higher. (checkStatusIndex)'))
281
287
  }
@@ -292,62 +298,111 @@ export class BitstringStatusListImplementation implements IStatusList {
292
298
  return statusList.getStatus(numIndex)
293
299
  }
294
300
 
301
+ async extractCredentialDetails(credential: StatusListCredential): Promise<IExtractedCredentialDetails> {
302
+ const uniform = CredentialMapper.toUniformCredential(credential)
303
+ const { issuer, credentialSubject } = uniform
304
+ const subject = Array.isArray(credentialSubject) ? credentialSubject[0] : credentialSubject
305
+
306
+ return {
307
+ id: getAssertedValue('id', uniform.id),
308
+ issuer,
309
+ encodedList: getAssertedProperty('encodedList', subject),
310
+ }
311
+ }
312
+
295
313
  /**
296
314
  * Converts a status list credential payload to detailed status list information
297
315
  *
298
316
  * @param args - Conversion parameters including the status list payload
299
317
  * @returns Promise resolving to detailed status list information
300
318
  */
301
- async toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult & IBitstringStatusListImplementationResult> {
302
- const { statusListPayload, bitsPerStatus } = args
303
- if (!bitsPerStatus || bitsPerStatus < 1) {
304
- return Promise.reject(Error('bitsPerStatus must be set for bitstring status lists and must be 1 or higher. (toStatusListDetails)'))
305
- }
306
-
307
- const uniform = CredentialMapper.toUniformCredential(statusListPayload)
308
- const { issuer, credentialSubject } = uniform
309
- const id = getAssertedValue('id', uniform.id)
310
- const encodedList = getAssertedProperty('encodedList', credentialSubject)
311
- const proofFormat: CredentialProofFormat = CredentialMapper.detectDocumentType(statusListPayload) === DocumentFormat.JWT ? 'vc+jwt' : 'lds'
312
- const credSubject = Array.isArray(credentialSubject) ? credentialSubject[0] : credentialSubject
313
- const statusPurpose = getAssertedProperty('statusPurpose', credSubject)
314
- const validFrom = uniform.validFrom ? new Date(uniform.validFrom) : undefined
315
- const validUntil = uniform.validUntil ? new Date(uniform.validUntil) : undefined
316
- const ttl = credSubject.ttl
317
- const statuslistLength: number = BitstreamStatusList.getStatusListLength(encodedList, bitsPerStatus)
318
-
319
- return {
320
- // Base implementation fields
321
- id,
322
- encodedList,
323
- issuer,
324
- type: StatusListType.BitstringStatusList,
325
- proofFormat,
326
- length: statuslistLength,
327
- statusListCredential: statusListPayload,
328
- statuslistContentType: this.buildContentType(proofFormat),
329
- correlationId: args.correlationId, // FIXME these do not need to be inside the impl
330
- driverType: args.driverType, // FIXME these do not need to be inside the impl
331
-
332
- // Flattened Bitstring-specific fields
333
- statusPurpose,
334
- bitsPerStatus,
335
- ...(validFrom && { validFrom }),
336
- ...(validUntil && { validUntil }),
337
- ...(ttl && { ttl }),
338
-
339
- // Legacy nested structure for backward compatibility
340
- bitstringStatusList: {
319
+ // For CREATE and READ contexts
320
+ async toStatusListDetails(args: IToDetailsFromCredentialArgs): Promise<StatusListResult & IBitstringStatusListImplementationResult>
321
+ // For UPDATE contexts
322
+ async toStatusListDetails(args: IMergeDetailsWithEntityArgs): Promise<StatusListResult & IBitstringStatusListImplementationResult>
323
+ async toStatusListDetails(
324
+ args: IToDetailsFromCredentialArgs | IMergeDetailsWithEntityArgs,
325
+ ): Promise<StatusListResult & IBitstringStatusListImplementationResult> {
326
+ if ('statusListCredential' in args) {
327
+ // CREATE/READ context
328
+ const { statusListCredential, bitsPerStatus, correlationId, driverType } = args
329
+ if (!bitsPerStatus || bitsPerStatus < 1) {
330
+ return Promise.reject(Error('bitsPerStatus must be set for bitstring status lists and must be 1 or higher'))
331
+ }
332
+
333
+ const uniform = CredentialMapper.toUniformCredential(statusListCredential)
334
+ const { issuer, credentialSubject } = uniform
335
+ const subject = Array.isArray(credentialSubject) ? credentialSubject[0] : credentialSubject
336
+
337
+ const id = getAssertedValue('id', uniform.id)
338
+ const encodedList = getAssertedProperty('encodedList', subject)
339
+ const statusPurpose = getAssertedProperty('statusPurpose', subject)
340
+ const validFrom = uniform.validFrom ? new Date(uniform.validFrom) : undefined
341
+ const validUntil = uniform.validUntil ? new Date(uniform.validUntil) : undefined
342
+ const ttl = subject.ttl
343
+ const proofFormat: CredentialProofFormat = CredentialMapper.detectDocumentType(statusListCredential) === DocumentFormat.JWT ? 'vc+jwt' : 'lds'
344
+ const statuslistLength = BitstreamStatusList.getStatusListLength(encodedList, bitsPerStatus)
345
+
346
+ return {
347
+ id,
348
+ encodedList,
349
+ issuer,
350
+ type: StatusListType.BitstringStatusList,
351
+ proofFormat,
352
+ length: statuslistLength,
353
+ statusListCredential,
354
+ statuslistContentType: this.buildContentType(proofFormat),
355
+ correlationId,
356
+ driverType,
341
357
  statusPurpose,
342
358
  bitsPerStatus,
343
359
  ...(validFrom && { validFrom }),
344
360
  ...(validUntil && { validUntil }),
345
361
  ...(ttl && { ttl }),
346
- },
347
-
348
- // Optional fields from args
349
- ...(args.correlationId && { correlationId: args.correlationId }),
350
- ...(args.driverType && { driverType: args.driverType }),
362
+ bitstringStatusList: {
363
+ statusPurpose,
364
+ bitsPerStatus,
365
+ ...(validFrom && { validFrom }),
366
+ ...(validUntil && { validUntil }),
367
+ ...(ttl && { ttl }),
368
+ },
369
+ }
370
+ } else {
371
+ // UPDATE context
372
+ const { extractedDetails, statusListEntity } = args
373
+ const bitstringEntity = statusListEntity as BitstringStatusListEntity
374
+ if (!bitstringEntity.bitsPerStatus) {
375
+ return Promise.reject(Error('bitsPerStatus must be present for a bitstring status list'))
376
+ }
377
+
378
+ const proofFormat: CredentialProofFormat =
379
+ CredentialMapper.detectDocumentType(statusListEntity.statusListCredential!) === DocumentFormat.JWT ? 'vc+jwt' : 'lds'
380
+ const statuslistLength = BitstreamStatusList.getStatusListLength(extractedDetails.encodedList, bitstringEntity.bitsPerStatus)
381
+
382
+ return {
383
+ id: extractedDetails.id,
384
+ encodedList: extractedDetails.encodedList,
385
+ issuer: extractedDetails.issuer,
386
+ type: StatusListType.BitstringStatusList,
387
+ proofFormat,
388
+ length: statuslistLength,
389
+ statusListCredential: statusListEntity.statusListCredential!,
390
+ statuslistContentType: this.buildContentType(proofFormat),
391
+ correlationId: statusListEntity.correlationId,
392
+ driverType: statusListEntity.driverType,
393
+ statusPurpose: bitstringEntity.statusPurpose,
394
+ bitsPerStatus: bitstringEntity.bitsPerStatus,
395
+ ...(bitstringEntity.validFrom && { validFrom: bitstringEntity.validFrom }),
396
+ ...(bitstringEntity.validUntil && { validUntil: bitstringEntity.validUntil }),
397
+ ...(bitstringEntity.ttl && { ttl: bitstringEntity.ttl }),
398
+ bitstringStatusList: {
399
+ statusPurpose: bitstringEntity.statusPurpose,
400
+ bitsPerStatus: bitstringEntity.bitsPerStatus,
401
+ ...(bitstringEntity.validFrom && { validFrom: bitstringEntity.validFrom }),
402
+ ...(bitstringEntity.validUntil && { validUntil: bitstringEntity.validUntil }),
403
+ ...(bitstringEntity.ttl && { ttl: bitstringEntity.ttl }),
404
+ },
405
+ }
351
406
  }
352
407
  }
353
408
 
@@ -364,25 +419,17 @@ export class BitstringStatusListImplementation implements IStatusList {
364
419
  }): Promise<BitstringStatusListEntryCredentialStatus> {
365
420
  const { statusList, statusListEntry, statusListIndex } = args
366
421
 
367
- // Type guard to ensure we have a bitstring entry
368
- const isBitstringEntry = (entry: IStatusListEntryEntity | IBitstringStatusListEntryEntity): entry is IBitstringStatusListEntryEntity => {
369
- return 'statusPurpose' in entry
370
- }
371
-
372
- if (!isBitstringEntry(statusListEntry)) {
373
- throw new Error('Expected bitstring status list entry for bitstring status list')
374
- }
375
-
376
- // Cast to BitstringStatusListEntity to access specific properties
377
422
  const bitstringStatusList = statusList as BitstringStatusListEntity
378
-
423
+ const bitstringStatusListEntry = statusListEntry as IBitstringStatusListEntryEntity
379
424
  return {
380
425
  id: `${statusList.id}#${statusListIndex}`,
381
426
  type: 'BitstringStatusListEntry',
382
- statusPurpose: statusListEntry.statusPurpose,
427
+ statusPurpose: bitstringStatusListEntry.statusPurpose,
383
428
  statusListIndex: '' + statusListIndex,
384
429
  statusListCredential: statusList.id,
385
430
  bitsPerStatus: bitstringStatusList.bitsPerStatus,
431
+ statusMessage: bitstringStatusListEntry.statusMessage,
432
+ statusReference: bitstringStatusListEntry.statusReference,
386
433
  } satisfies BitstringStatusListEntryCredentialStatus
387
434
  }
388
435
 
@@ -1,16 +1,15 @@
1
1
  import type { IAgentContext } from '@veramo/core'
2
2
  import type { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
3
3
  import {
4
- BitstringStatus,
5
- BitstringStatusListEntryCredentialStatus,
6
4
  CheckStatusIndexArgs,
7
5
  CreateStatusListArgs,
6
+ IMergeDetailsWithEntityArgs,
7
+ IToDetailsFromCredentialArgs,
8
8
  Status2021,
9
9
  StatusList2021EntryCredentialStatus,
10
10
  StatusListOAuthEntryCredentialStatus,
11
11
  StatusListResult,
12
12
  StatusOAuth,
13
- ToStatusListDetailsArgs,
14
13
  UpdateStatusListFromEncodedListArgs,
15
14
  UpdateStatusListIndexArgs,
16
15
  } from '../types'
@@ -24,8 +23,21 @@ import {
24
23
  StatusListType,
25
24
  StatusPurpose2021,
26
25
  } from '@sphereon/ssi-types'
27
- import { IBitstringStatusListEntryEntity, IStatusListEntryEntity, StatusListEntity } from '@sphereon/ssi-sdk.data-store'
26
+ import {
27
+ BitstringStatusListEntryCredentialStatus,
28
+ IBitstringStatusListEntryEntity,
29
+ IStatusListEntryEntity,
30
+ StatusListEntity,
31
+ } from '@sphereon/ssi-sdk.data-store'
28
32
  import { IVcdmCredentialPlugin } from '@sphereon/ssi-sdk.credential-vcdm'
33
+ import { DecodedStatusListPayload } from './encoding/common'
34
+
35
+ export interface IExtractedCredentialDetails {
36
+ id: string
37
+ issuer: string | IIssuer
38
+ encodedList: string
39
+ decodedPayload?: DecodedStatusListPayload
40
+ }
29
41
 
30
42
  export interface IStatusList {
31
43
  /**
@@ -52,13 +64,29 @@ export interface IStatusList {
52
64
  /**
53
65
  * Checks the status at a given index in the status list
54
66
  */
55
- checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | Status2021 | StatusOAuth | BitstringStatus>
67
+ checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | Status2021 | StatusOAuth>
68
+
69
+ /**
70
+ * Performs the initial parsing of a StatusListCredential.
71
+ * This method handles expensive operations like JWT/CWT decoding once.
72
+ * It extracts all details available from the credential payload itself.
73
+ */
74
+ extractCredentialDetails(credential: StatusListCredential): Promise<IExtractedCredentialDetails>
75
+
76
+ /**
77
+ * Converts a credential and its known metadata into a full StatusListResult.
78
+ */
79
+ toStatusListDetails(
80
+ args: IToDetailsFromCredentialArgs,
81
+ ): Promise<
82
+ StatusListResult & (IStatusList2021ImplementationResult | IOAuthStatusListImplementationResult | IBitstringStatusListImplementationResult)
83
+ >
56
84
 
57
85
  /**
58
- * Collects the status list details - returns flattened entity data ready for storage
86
+ * Merges pre-parsed details from a new credential with an existing database entity.
59
87
  */
60
88
  toStatusListDetails(
61
- args: ToStatusListDetailsArgs,
89
+ args: IMergeDetailsWithEntityArgs,
62
90
  ): Promise<
63
91
  StatusListResult & (IStatusList2021ImplementationResult | IOAuthStatusListImplementationResult | IBitstringStatusListImplementationResult)
64
92
  >
@@ -3,16 +3,17 @@ import { type CompactJWT, type CredentialProofFormat, type CWT, StatusListType }
3
3
  import type {
4
4
  CheckStatusIndexArgs,
5
5
  CreateStatusListArgs,
6
+ IMergeDetailsWithEntityArgs,
7
+ IToDetailsFromCredentialArgs,
6
8
  SignedStatusListData,
7
9
  StatusListOAuthEntryCredentialStatus,
8
10
  StatusListResult,
9
11
  StatusOAuth,
10
- ToStatusListDetailsArgs,
11
12
  UpdateStatusListFromEncodedListArgs,
12
13
  UpdateStatusListIndexArgs,
13
14
  } from '../types'
14
15
  import { determineProofFormat, ensureDate, getAssertedValue, getAssertedValues } from '../utils'
15
- import type { IOAuthStatusListImplementationResult, IStatusList } from './IStatusList'
16
+ import type { IExtractedCredentialDetails, IOAuthStatusListImplementationResult, IStatusList } from './IStatusList'
16
17
  import { StatusList } from '@sd-jwt/jwt-status-list'
17
18
  import type { IJwtService } from '@sphereon/ssi-sdk-ext.jwt-service'
18
19
  import type { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
@@ -164,41 +165,87 @@ export class OAuthStatusListImplementation implements IStatusList {
164
165
  return statusList.getStatus(index)
165
166
  }
166
167
 
167
- async toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult & IOAuthStatusListImplementationResult> {
168
- const { statusListPayload } = args as { statusListPayload: CompactJWT | CWT }
169
- const proofFormat = determineProofFormat(statusListPayload)
170
- const decoded = proofFormat === 'jwt' ? decodeStatusListJWT(statusListPayload) : decodeStatusListCWT(statusListPayload)
171
- const { statusList, issuer, id, exp } = decoded
168
+ async extractCredentialDetails(credential: CompactJWT | CWT): Promise<IExtractedCredentialDetails> {
169
+ if (typeof credential !== 'string') {
170
+ return Promise.reject('statusListCredential must be a JWT or CWT string')
171
+ }
172
172
 
173
- const bitsPerStatus = statusList.getBitsPerStatus()
174
- const expiresAt = exp ? new Date(exp * 1000) : undefined
173
+ const proofFormat = determineProofFormat(credential)
174
+ const decoded = proofFormat === 'jwt' ? decodeStatusListJWT(credential) : decodeStatusListCWT(credential)
175
175
 
176
176
  return {
177
- // Base implementation fields
178
- id,
179
- encodedList: statusList.compressStatusList(),
180
- issuer,
181
- type: StatusListType.OAuthStatusList,
182
- proofFormat,
183
- length: statusList.statusList.length,
184
- statusListCredential: statusListPayload,
185
- statuslistContentType: this.buildContentType(proofFormat),
186
- correlationId: args.correlationId, // FIXME these do not need to be inside the impl
187
- driverType: args.driverType, // FIXME these do not need to be inside the impl
177
+ id: decoded.id,
178
+ issuer: decoded.issuer,
179
+ encodedList: decoded.statusList.compressStatusList(),
180
+ decodedPayload: decoded,
181
+ }
182
+ }
188
183
 
189
- // Flattened OAuth-specific fields
190
- bitsPerStatus,
191
- ...(expiresAt && { expiresAt }),
184
+ // For CREATE and READ contexts
185
+ async toStatusListDetails(args: IToDetailsFromCredentialArgs): Promise<StatusListResult & IOAuthStatusListImplementationResult>
186
+ // For UPDATE contexts
187
+ async toStatusListDetails(args: IMergeDetailsWithEntityArgs): Promise<StatusListResult & IOAuthStatusListImplementationResult>
188
+ async toStatusListDetails(
189
+ args: IToDetailsFromCredentialArgs | IMergeDetailsWithEntityArgs,
190
+ ): Promise<StatusListResult & IOAuthStatusListImplementationResult> {
191
+ if ('statusListCredential' in args) {
192
+ // CREATE/READ context
193
+ const { statusListCredential, bitsPerStatus, correlationId, driverType } = args
194
+ if (!bitsPerStatus || bitsPerStatus < 1) {
195
+ return Promise.reject(Error('bitsPerStatus must be set for OAuth status lists and must be 1 or higher'))
196
+ }
192
197
 
193
- // Legacy nested structure for backward compatibility
194
- oauthStatusList: {
198
+ const proofFormat = determineProofFormat(statusListCredential as string)
199
+ const decoded =
200
+ proofFormat === 'jwt' ? decodeStatusListJWT(statusListCredential as string) : decodeStatusListCWT(statusListCredential as string)
201
+ const { statusList, issuer, id, exp } = decoded
202
+ const expiresAt = exp ? new Date(exp * 1000) : undefined
203
+
204
+ return {
205
+ id,
206
+ encodedList: statusList.compressStatusList(),
207
+ issuer,
208
+ type: StatusListType.OAuthStatusList,
209
+ proofFormat,
210
+ length: statusList.statusList.length,
211
+ statusListCredential: statusListCredential as CompactJWT | CWT,
212
+ statuslistContentType: this.buildContentType(proofFormat),
213
+ correlationId,
214
+ driverType,
195
215
  bitsPerStatus,
196
216
  ...(expiresAt && { expiresAt }),
197
- },
198
-
199
- // Optional fields from args
200
- ...(args.correlationId && { correlationId: args.correlationId }),
201
- ...(args.driverType && { driverType: args.driverType }),
217
+ oauthStatusList: {
218
+ bitsPerStatus,
219
+ ...(expiresAt && { expiresAt }),
220
+ },
221
+ }
222
+ } else {
223
+ // UPDATE context
224
+ const { extractedDetails, statusListEntity } = args
225
+ const oauthEntity = statusListEntity as OAuthStatusListEntity
226
+ const decoded = extractedDetails.decodedPayload as { statusList: StatusList; exp?: number }
227
+
228
+ const proofFormat = determineProofFormat(statusListEntity.statusListCredential as string)
229
+ const expiresAt = decoded.exp ? new Date(decoded.exp * 1000) : undefined
230
+
231
+ return {
232
+ id: extractedDetails.id,
233
+ encodedList: extractedDetails.encodedList,
234
+ issuer: extractedDetails.issuer,
235
+ type: StatusListType.OAuthStatusList,
236
+ proofFormat,
237
+ length: decoded.statusList.statusList.length,
238
+ statusListCredential: statusListEntity.statusListCredential!,
239
+ statuslistContentType: this.buildContentType(proofFormat),
240
+ correlationId: statusListEntity.correlationId,
241
+ driverType: statusListEntity.driverType,
242
+ bitsPerStatus: oauthEntity.bitsPerStatus,
243
+ ...(expiresAt && { expiresAt }),
244
+ oauthStatusList: {
245
+ bitsPerStatus: oauthEntity.bitsPerStatus,
246
+ ...(expiresAt && { expiresAt }),
247
+ },
248
+ }
202
249
  }
203
250
  }
204
251