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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,43 +1,136 @@
1
- import type { IAgentContext, ICredentialPlugin } from '@veramo/core'
1
+ import type { IAgentContext } from '@veramo/core'
2
2
  import type { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
3
3
  import {
4
- BitstringStatusResult,
5
4
  CheckStatusIndexArgs,
6
5
  CreateStatusListArgs,
6
+ IMergeDetailsWithEntityArgs,
7
+ IToDetailsFromCredentialArgs,
7
8
  Status2021,
9
+ StatusList2021EntryCredentialStatus,
10
+ StatusListOAuthEntryCredentialStatus,
8
11
  StatusListResult,
9
12
  StatusOAuth,
10
- ToStatusListDetailsArgs,
11
13
  UpdateStatusListFromEncodedListArgs,
12
14
  UpdateStatusListIndexArgs,
13
15
  } from '../types'
16
+ import { BitstringStatusPurpose } from '@4sure-tech/vc-bitstring-status-lists'
17
+ import {
18
+ CredentialProofFormat,
19
+ IIssuer,
20
+ StatusListCredential,
21
+ StatusListDriverType,
22
+ StatusListIndexingDirection,
23
+ StatusListType,
24
+ StatusPurpose2021,
25
+ } from '@sphereon/ssi-types'
26
+ import {
27
+ BitstringStatusListEntryCredentialStatus,
28
+ IBitstringStatusListEntryEntity,
29
+ IStatusListEntryEntity,
30
+ StatusListEntity,
31
+ } from '@sphereon/ssi-sdk.data-store'
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
+ }
14
41
 
15
42
  export interface IStatusList {
16
43
  /**
17
44
  * Creates a new status list of the specific type
18
45
  */
19
- createNewStatusList(args: CreateStatusListArgs, context: IAgentContext<ICredentialPlugin & IIdentifierResolution>): Promise<StatusListResult>
46
+ createNewStatusList(args: CreateStatusListArgs, context: IAgentContext<IVcdmCredentialPlugin & IIdentifierResolution>): Promise<StatusListResult>
20
47
 
21
48
  /**
22
49
  * Updates a status at the given index in the status list
23
50
  */
24
- updateStatusListIndex(args: UpdateStatusListIndexArgs, context: IAgentContext<ICredentialPlugin & IIdentifierResolution>): Promise<StatusListResult>
51
+ updateStatusListIndex(
52
+ args: UpdateStatusListIndexArgs,
53
+ context: IAgentContext<IVcdmCredentialPlugin & IIdentifierResolution>,
54
+ ): Promise<StatusListResult>
25
55
 
26
56
  /**
27
57
  * Updates a status list using a base64 encoded list of statuses
28
58
  */
29
59
  updateStatusListFromEncodedList(
30
60
  args: UpdateStatusListFromEncodedListArgs,
31
- context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
61
+ context: IAgentContext<IVcdmCredentialPlugin & IIdentifierResolution>,
32
62
  ): Promise<StatusListResult>
33
63
 
34
64
  /**
35
65
  * Checks the status at a given index in the status list
36
66
  */
37
- checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | Status2021 | StatusOAuth | BitstringStatusResult>
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
+ >
38
84
 
39
85
  /**
40
- * Collects the status list details
86
+ * Merges pre-parsed details from a new credential with an existing database entity.
41
87
  */
42
- toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult>
88
+ toStatusListDetails(
89
+ args: IMergeDetailsWithEntityArgs,
90
+ ): Promise<
91
+ StatusListResult & (IStatusList2021ImplementationResult | IOAuthStatusListImplementationResult | IBitstringStatusListImplementationResult)
92
+ >
93
+
94
+ /**
95
+ * Creates a credential status object from a status list and entry
96
+ */
97
+ createCredentialStatus(args: {
98
+ statusList: StatusListEntity
99
+ statusListEntry: IStatusListEntryEntity | IBitstringStatusListEntryEntity
100
+ statusListIndex: number
101
+ }): Promise<StatusList2021EntryCredentialStatus | StatusListOAuthEntryCredentialStatus | BitstringStatusListEntryCredentialStatus>
102
+ }
103
+
104
+ export interface IStatusListImplementationResult {
105
+ id: string
106
+ encodedList: string
107
+ issuer: string | IIssuer
108
+ type: StatusListType
109
+ proofFormat: CredentialProofFormat
110
+ length: number
111
+ statusListCredential: StatusListCredential
112
+ statuslistContentType: string
113
+ correlationId?: string
114
+ driverType?: StatusListDriverType
115
+ }
116
+
117
+ export interface IStatusList2021ImplementationResult extends IStatusListImplementationResult {
118
+ type: StatusListType.StatusList2021
119
+ indexingDirection: StatusListIndexingDirection
120
+ statusPurpose: StatusPurpose2021
121
+ }
122
+
123
+ export interface IOAuthStatusListImplementationResult extends IStatusListImplementationResult {
124
+ type: StatusListType.OAuthStatusList
125
+ bitsPerStatus: number
126
+ expiresAt?: Date
127
+ }
128
+
129
+ export interface IBitstringStatusListImplementationResult extends IStatusListImplementationResult {
130
+ type: StatusListType.BitstringStatusList
131
+ statusPurpose: BitstringStatusPurpose | BitstringStatusPurpose[]
132
+ bitsPerStatus?: number
133
+ validFrom?: Date
134
+ validUntil?: Date
135
+ ttl?: number
43
136
  }
@@ -1,24 +1,28 @@
1
- import type { IAgentContext, ICredentialPlugin, IKeyManager } from '@veramo/core'
2
- import { type CompactJWT, type CWT, type CredentialProofFormat, StatusListType } from '@sphereon/ssi-types'
1
+ import type { IAgentContext, IKeyManager } from '@veramo/core'
2
+ import { type CompactJWT, type CredentialProofFormat, type CWT, StatusListType } from '@sphereon/ssi-types'
3
3
  import type {
4
4
  CheckStatusIndexArgs,
5
5
  CreateStatusListArgs,
6
+ IMergeDetailsWithEntityArgs,
7
+ IToDetailsFromCredentialArgs,
6
8
  SignedStatusListData,
9
+ StatusListOAuthEntryCredentialStatus,
7
10
  StatusListResult,
8
11
  StatusOAuth,
9
- ToStatusListDetailsArgs,
10
12
  UpdateStatusListFromEncodedListArgs,
11
13
  UpdateStatusListIndexArgs,
12
14
  } from '../types'
13
- import { determineProofFormat, getAssertedValue, getAssertedValues } from '../utils'
14
- import type { IStatusList } from './IStatusList'
15
+ import { determineProofFormat, ensureDate, getAssertedValue, getAssertedValues } from '../utils'
16
+ import type { IExtractedCredentialDetails, IOAuthStatusListImplementationResult, IStatusList } from './IStatusList'
15
17
  import { StatusList } from '@sd-jwt/jwt-status-list'
16
18
  import type { IJwtService } from '@sphereon/ssi-sdk-ext.jwt-service'
17
19
  import type { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
18
20
  import { createSignedJwt, decodeStatusListJWT } from './encoding/jwt'
19
21
  import { createSignedCbor, decodeStatusListCWT } from './encoding/cbor'
22
+ import { IBitstringStatusListEntryEntity, IStatusListEntryEntity, OAuthStatusListEntity, StatusListEntity } from '@sphereon/ssi-sdk.data-store'
23
+ import { IVcdmCredentialPlugin } from '@sphereon/ssi-sdk.credential-vcdm'
20
24
 
21
- type IRequiredContext = IAgentContext<ICredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>
25
+ type IRequiredContext = IAgentContext<IVcdmCredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>
22
26
 
23
27
  export const DEFAULT_BITS_PER_STATUS = 1 // 1 bit is sufficient for 0x00 - "VALID" 0x01 - "INVALID" saving space in the process
24
28
  export const DEFAULT_LIST_LENGTH = 250000
@@ -32,7 +36,8 @@ export class OAuthStatusListImplementation implements IStatusList {
32
36
 
33
37
  const proofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
34
38
  const { issuer, id, oauthStatusList, keyRef } = args
35
- const { bitsPerStatus, expiresAt } = oauthStatusList
39
+ const { bitsPerStatus } = oauthStatusList
40
+ const expiresAt = ensureDate(oauthStatusList.expiresAt)
36
41
  const length = args.length ?? DEFAULT_LIST_LENGTH
37
42
  const issuerString = typeof issuer === 'string' ? issuer : issuer.id
38
43
  const correlationId = getAssertedValue('correlationId', args.correlationId)
@@ -56,7 +61,8 @@ export class OAuthStatusListImplementation implements IStatusList {
56
61
  }
57
62
 
58
63
  async updateStatusListIndex(args: UpdateStatusListIndexArgs, context: IRequiredContext): Promise<StatusListResult> {
59
- const { statusListCredential, value, expiresAt, keyRef } = args
64
+ const { statusListCredential, value, keyRef } = args
65
+ const expiresAt = ensureDate(args.expiresAt)
60
66
  if (typeof statusListCredential !== 'string') {
61
67
  return Promise.reject('statusListCredential in neither JWT nor CWT')
62
68
  }
@@ -106,15 +112,15 @@ export class OAuthStatusListImplementation implements IStatusList {
106
112
  throw new Error('OAuthStatusList options are required for type OAuthStatusList')
107
113
  }
108
114
  const { proofFormat, oauthStatusList, keyRef } = args
109
- const { bitsPerStatus, expiresAt } = oauthStatusList
115
+ const { bitsPerStatus } = oauthStatusList
116
+ const expiresAt = ensureDate(oauthStatusList.expiresAt)
110
117
 
111
118
  const { issuer, id } = getAssertedValues(args)
112
119
  const issuerString = typeof issuer === 'string' ? issuer : issuer.id
113
120
 
114
121
  const listToUpdate = StatusList.decompressStatusList(args.encodedList, bitsPerStatus ?? DEFAULT_BITS_PER_STATUS)
115
122
  const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
116
- // FIXME: See above.
117
- listToUpdate.setStatus(index, args.value ? 1 : 0)
123
+ listToUpdate.setStatus(index, args.value)
118
124
 
119
125
  const { statusListCredential, encodedList } = await this.createSignedStatusList(
120
126
  proofFormat ?? DEFAULT_PROOF_FORMAT,
@@ -142,10 +148,6 @@ export class OAuthStatusListImplementation implements IStatusList {
142
148
  }
143
149
  }
144
150
 
145
- private buildContentType(proofFormat: CredentialProofFormat | undefined) {
146
- return `application/statuslist+${proofFormat === 'cbor' ? 'cwt' : 'jwt'}`
147
- }
148
-
149
151
  async checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | StatusOAuth> {
150
152
  const { statusListCredential, statusListIndex } = args
151
153
  if (typeof statusListCredential !== 'string') {
@@ -163,33 +165,122 @@ export class OAuthStatusListImplementation implements IStatusList {
163
165
  return statusList.getStatus(index)
164
166
  }
165
167
 
166
- async toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult> {
167
- const { statusListPayload } = args as { statusListPayload: CompactJWT | CWT }
168
- const proofFormat = determineProofFormat(statusListPayload)
169
- const decoded = proofFormat === 'jwt' ? decodeStatusListJWT(statusListPayload) : decodeStatusListCWT(statusListPayload)
170
- const { statusList, issuer, id, exp } = decoded
168
+ /**
169
+ * Performs the initial parsing of a StatusListCredential.
170
+ * This method handles expensive operations like JWT/CWT decoding once.
171
+ * It extracts all details available from the credential payload itself.
172
+ */
173
+ async extractCredentialDetails(credential: CompactJWT | CWT): Promise<IExtractedCredentialDetails> {
174
+ if (typeof credential !== 'string') {
175
+ return Promise.reject('statusListCredential must be a JWT or CWT string')
176
+ }
177
+
178
+ const proofFormat = determineProofFormat(credential)
179
+ const decoded = proofFormat === 'jwt' ? decodeStatusListJWT(credential) : decodeStatusListCWT(credential)
171
180
 
172
181
  return {
173
- id,
174
- encodedList: statusList.compressStatusList(),
175
- issuer,
176
- type: StatusListType.OAuthStatusList,
177
- proofFormat,
178
- length: statusList.statusList.length,
179
- statusListCredential: statusListPayload,
180
- statuslistContentType: this.buildContentType(proofFormat),
181
- oauthStatusList: {
182
- bitsPerStatus: statusList.getBitsPerStatus(),
183
- ...(exp && { expiresAt: new Date(exp * 1000) }),
184
- },
185
- ...(args.correlationId && { correlationId: args.correlationId }),
186
- ...(args.driverType && { driverType: args.driverType }),
182
+ id: decoded.id,
183
+ issuer: decoded.issuer,
184
+ encodedList: decoded.statusList.compressStatusList(),
185
+ decodedPayload: decoded,
186
+ }
187
+ }
188
+
189
+ // For CREATE and READ contexts
190
+ async toStatusListDetails(args: IToDetailsFromCredentialArgs): Promise<StatusListResult & IOAuthStatusListImplementationResult>
191
+ // For UPDATE contexts
192
+ async toStatusListDetails(args: IMergeDetailsWithEntityArgs): Promise<StatusListResult & IOAuthStatusListImplementationResult>
193
+ async toStatusListDetails(
194
+ args: IToDetailsFromCredentialArgs | IMergeDetailsWithEntityArgs,
195
+ ): Promise<StatusListResult & IOAuthStatusListImplementationResult> {
196
+ if ('statusListCredential' in args) {
197
+ // CREATE/READ context
198
+ const { statusListCredential, bitsPerStatus, correlationId, driverType } = args
199
+ if (!bitsPerStatus || bitsPerStatus < 1) {
200
+ return Promise.reject(Error('bitsPerStatus must be set for OAuth status lists and must be 1 or higher'))
201
+ }
202
+
203
+ const proofFormat = determineProofFormat(statusListCredential as string)
204
+ const decoded =
205
+ proofFormat === 'jwt' ? decodeStatusListJWT(statusListCredential as string) : decodeStatusListCWT(statusListCredential as string)
206
+ const { statusList, issuer, id, exp } = decoded
207
+ const expiresAt = exp ? new Date(exp * 1000) : undefined
208
+
209
+ return {
210
+ id,
211
+ encodedList: statusList.compressStatusList(),
212
+ issuer,
213
+ type: StatusListType.OAuthStatusList,
214
+ proofFormat,
215
+ length: statusList.statusList.length,
216
+ statusListCredential: statusListCredential as CompactJWT | CWT,
217
+ statuslistContentType: this.buildContentType(proofFormat),
218
+ correlationId,
219
+ driverType,
220
+ bitsPerStatus,
221
+ ...(expiresAt && { expiresAt }),
222
+ oauthStatusList: {
223
+ bitsPerStatus,
224
+ ...(expiresAt && { expiresAt }),
225
+ },
226
+ }
227
+ } else {
228
+ // UPDATE context
229
+ const { extractedDetails, statusListEntity } = args
230
+ const oauthEntity = statusListEntity as OAuthStatusListEntity
231
+ const decoded = extractedDetails.decodedPayload as { statusList: StatusList; exp?: number }
232
+
233
+ const proofFormat = determineProofFormat(statusListEntity.statusListCredential as string)
234
+ const expiresAt = decoded.exp ? new Date(decoded.exp * 1000) : undefined
235
+
236
+ return {
237
+ id: extractedDetails.id,
238
+ encodedList: extractedDetails.encodedList,
239
+ issuer: extractedDetails.issuer,
240
+ type: StatusListType.OAuthStatusList,
241
+ proofFormat,
242
+ length: decoded.statusList.statusList.length,
243
+ statusListCredential: statusListEntity.statusListCredential!,
244
+ statuslistContentType: this.buildContentType(proofFormat),
245
+ correlationId: statusListEntity.correlationId,
246
+ driverType: statusListEntity.driverType,
247
+ bitsPerStatus: oauthEntity.bitsPerStatus,
248
+ ...(expiresAt && { expiresAt }),
249
+ oauthStatusList: {
250
+ bitsPerStatus: oauthEntity.bitsPerStatus,
251
+ ...(expiresAt && { expiresAt }),
252
+ },
253
+ }
254
+ }
255
+ }
256
+
257
+ async createCredentialStatus(args: {
258
+ statusList: StatusListEntity
259
+ statusListEntry: IStatusListEntryEntity | IBitstringStatusListEntryEntity
260
+ statusListIndex: number
261
+ }): Promise<StatusListOAuthEntryCredentialStatus> {
262
+ const { statusList, statusListIndex } = args
263
+
264
+ // Cast to OAuthStatusListEntity to access specific properties
265
+ const oauthStatusList = statusList as OAuthStatusListEntity
266
+
267
+ return {
268
+ id: `${statusList.id}#${statusListIndex}`,
269
+ type: 'OAuthStatusListEntry',
270
+ bitsPerStatus: oauthStatusList.bitsPerStatus,
271
+ statusListIndex: '' + statusListIndex,
272
+ statusListCredential: statusList.id,
273
+ expiresAt: oauthStatusList.expiresAt,
187
274
  }
188
275
  }
189
276
 
277
+ private buildContentType(proofFormat: CredentialProofFormat | undefined) {
278
+ return `application/statuslist+${proofFormat === 'cbor' ? 'cwt' : 'jwt'}`
279
+ }
280
+
190
281
  private async createSignedStatusList(
191
282
  proofFormat: CredentialProofFormat,
192
- context: IAgentContext<ICredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>,
283
+ context: IAgentContext<IVcdmCredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>,
193
284
  statusList: StatusList,
194
285
  issuerString: string,
195
286
  id: string,
@@ -1,27 +1,29 @@
1
- import type { IAgentContext, ICredentialPlugin, ProofFormat as VeramoProofFormat } from '@veramo/core'
1
+ import type { IAgentContext, ProofFormat as VeramoProofFormat } from '@veramo/core'
2
2
  import type { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
3
3
  import {
4
4
  CredentialMapper,
5
+ type CredentialProofFormat,
5
6
  DocumentFormat,
6
7
  type IIssuer,
7
- type CredentialProofFormat,
8
8
  type StatusListCredential,
9
9
  StatusListType,
10
10
  } from '@sphereon/ssi-types'
11
11
 
12
12
  import { StatusList } from '@sphereon/vc-status-list'
13
- import type { IStatusList } from './IStatusList'
13
+ import type { IExtractedCredentialDetails, IStatusList, IStatusList2021ImplementationResult } from './IStatusList'
14
14
  import type {
15
15
  CheckStatusIndexArgs,
16
16
  CreateStatusListArgs,
17
+ IMergeDetailsWithEntityArgs,
18
+ IToDetailsFromCredentialArgs,
17
19
  StatusListResult,
18
- ToStatusListDetailsArgs,
19
20
  UpdateStatusListFromEncodedListArgs,
20
21
  UpdateStatusListIndexArgs,
21
22
  } from '../types'
22
-
23
- import { Status2021 } from '../types'
23
+ import { Status2021, StatusList2021EntryCredentialStatus } from '../types'
24
24
  import { assertValidProofType, getAssertedProperty, getAssertedValue, getAssertedValues } from '../utils'
25
+ import { IBitstringStatusListEntryEntity, IStatusListEntryEntity, StatusList2021Entity, StatusListEntity } from '@sphereon/ssi-sdk.data-store'
26
+ import { IVcdmCredentialPlugin } from '@sphereon/ssi-sdk.credential-vcdm'
25
27
 
26
28
  export const DEFAULT_LIST_LENGTH = 250000
27
29
  export const DEFAULT_PROOF_FORMAT = 'lds' as CredentialProofFormat
@@ -29,7 +31,7 @@ export const DEFAULT_PROOF_FORMAT = 'lds' as CredentialProofFormat
29
31
  export class StatusList2021Implementation implements IStatusList {
30
32
  async createNewStatusList(
31
33
  args: CreateStatusListArgs,
32
- context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
34
+ context: IAgentContext<IVcdmCredentialPlugin & IIdentifierResolution>,
33
35
  ): Promise<StatusListResult> {
34
36
  const length = args?.length ?? DEFAULT_LIST_LENGTH
35
37
  const proofFormat: CredentialProofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
@@ -71,7 +73,7 @@ export class StatusList2021Implementation implements IStatusList {
71
73
 
72
74
  async updateStatusListIndex(
73
75
  args: UpdateStatusListIndexArgs,
74
- context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
76
+ context: IAgentContext<IVcdmCredentialPlugin & IIdentifierResolution>,
75
77
  ): Promise<StatusListResult> {
76
78
  const credential = args.statusListCredential
77
79
  const uniform = CredentialMapper.toUniformCredential(credential)
@@ -96,11 +98,15 @@ export class StatusList2021Implementation implements IStatusList {
96
98
  context,
97
99
  )
98
100
 
101
+ if (!('statusPurpose' in credentialSubject)) {
102
+ return Promise.reject(Error('statusPurpose is required in credentialSubject for StatusList2021'))
103
+ }
104
+
99
105
  return {
100
106
  statusListCredential: updatedCredential,
101
107
  encodedList,
102
108
  statusList2021: {
103
- ...('statusPurpose' in credentialSubject ? { statusPurpose: credentialSubject.statusPurpose } : {}),
109
+ statusPurpose: credentialSubject.statusPurpose,
104
110
  indexingDirection: 'rightToLeft',
105
111
  },
106
112
  length: statusList.length - 1,
@@ -114,7 +120,7 @@ export class StatusList2021Implementation implements IStatusList {
114
120
 
115
121
  async updateStatusListFromEncodedList(
116
122
  args: UpdateStatusListFromEncodedListArgs,
117
- context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
123
+ context: IAgentContext<IVcdmCredentialPlugin & IIdentifierResolution>,
118
124
  ): Promise<StatusListResult> {
119
125
  if (!args.statusList2021) {
120
126
  throw new Error('statusList2021 options required for type StatusList2021')
@@ -126,7 +132,7 @@ export class StatusList2021Implementation implements IStatusList {
126
132
  const { issuer, id } = getAssertedValues(args)
127
133
  const statusList = await StatusList.decode({ encodedList: args.encodedList })
128
134
  const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
129
- statusList.setStatus(index, args.value)
135
+ statusList.setStatus(index, args.value !== 0)
130
136
 
131
137
  const newEncodedList = await statusList.encode()
132
138
  const credential = await this.createVerifiableCredential(
@@ -166,32 +172,106 @@ export class StatusList2021Implementation implements IStatusList {
166
172
  return status ? Status2021.Invalid : Status2021.Valid
167
173
  }
168
174
 
169
- async toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult> {
170
- const { statusListPayload } = args
171
- const uniform = CredentialMapper.toUniformCredential(statusListPayload)
175
+ /**
176
+ * Performs the initial parsing of a StatusListCredential.
177
+ * This method handles expensive operations like JWT/CWT decoding once.
178
+ * It extracts all details available from the credential payload itself.
179
+ */
180
+ async extractCredentialDetails(credential: StatusListCredential): Promise<IExtractedCredentialDetails> {
181
+ const uniform = CredentialMapper.toUniformCredential(credential)
172
182
  const { issuer, credentialSubject } = uniform
173
- const id = getAssertedValue('id', uniform.id)
174
- const encodedList = getAssertedProperty('encodedList', credentialSubject)
175
- const proofFormat: CredentialProofFormat = CredentialMapper.detectDocumentType(statusListPayload) === DocumentFormat.JWT ? 'jwt' : 'lds'
176
-
177
- const statusPurpose = getAssertedProperty('statusPurpose', credentialSubject)
178
- const list = await StatusList.decode({ encodedList })
183
+ const subject = Array.isArray(credentialSubject) ? credentialSubject[0] : credentialSubject
179
184
 
180
185
  return {
181
- id,
182
- encodedList,
186
+ id: getAssertedValue('id', uniform.id),
183
187
  issuer,
184
- type: StatusListType.StatusList2021,
185
- proofFormat,
186
- length: list.length,
187
- statusListCredential: statusListPayload,
188
- statuslistContentType: this.buildContentType(proofFormat),
189
- statusList2021: {
188
+ encodedList: getAssertedProperty('encodedList', subject),
189
+ }
190
+ }
191
+
192
+ async toStatusListDetails(args: IToDetailsFromCredentialArgs): Promise<StatusListResult & IStatusList2021ImplementationResult>
193
+ // For UPDATE contexts
194
+ async toStatusListDetails(args: IMergeDetailsWithEntityArgs): Promise<StatusListResult & IStatusList2021ImplementationResult>
195
+ async toStatusListDetails(
196
+ args: IToDetailsFromCredentialArgs | IMergeDetailsWithEntityArgs,
197
+ ): Promise<StatusListResult & IStatusList2021ImplementationResult> {
198
+ if ('statusListCredential' in args) {
199
+ // CREATE/READ context
200
+ const { statusListCredential, correlationId, driverType } = args
201
+ const uniform = CredentialMapper.toUniformCredential(statusListCredential)
202
+ const { issuer, credentialSubject } = uniform
203
+ const subject = Array.isArray(credentialSubject) ? credentialSubject[0] : credentialSubject
204
+
205
+ const id = getAssertedValue('id', uniform.id)
206
+ const encodedList = getAssertedProperty('encodedList', subject)
207
+ const statusPurpose = getAssertedProperty('statusPurpose', subject)
208
+ const proofFormat: CredentialProofFormat = CredentialMapper.detectDocumentType(statusListCredential) === DocumentFormat.JWT ? 'jwt' : 'lds'
209
+ const list = await StatusList.decode({ encodedList })
210
+
211
+ return {
212
+ id,
213
+ encodedList,
214
+ issuer,
215
+ type: StatusListType.StatusList2021,
216
+ proofFormat,
217
+ length: list.length,
218
+ statusListCredential,
219
+ statuslistContentType: this.buildContentType(proofFormat),
220
+ correlationId,
221
+ driverType,
190
222
  indexingDirection: 'rightToLeft',
191
223
  statusPurpose,
192
- },
193
- ...(args.correlationId && { correlationId: args.correlationId }),
194
- ...(args.driverType && { driverType: args.driverType }),
224
+ statusList2021: {
225
+ indexingDirection: 'rightToLeft',
226
+ statusPurpose,
227
+ },
228
+ }
229
+ } else {
230
+ // UPDATE context
231
+ const { extractedDetails, statusListEntity } = args
232
+ const statusList2021Entity = statusListEntity as StatusList2021Entity
233
+
234
+ const proofFormat: CredentialProofFormat =
235
+ CredentialMapper.detectDocumentType(statusListEntity.statusListCredential!) === DocumentFormat.JWT ? 'jwt' : 'lds'
236
+ const list = await StatusList.decode({ encodedList: extractedDetails.encodedList })
237
+
238
+ return {
239
+ id: extractedDetails.id,
240
+ encodedList: extractedDetails.encodedList,
241
+ issuer: extractedDetails.issuer,
242
+ type: StatusListType.StatusList2021,
243
+ proofFormat,
244
+ length: list.length,
245
+ statusListCredential: statusListEntity.statusListCredential!,
246
+ statuslistContentType: this.buildContentType(proofFormat),
247
+ correlationId: statusListEntity.correlationId,
248
+ driverType: statusListEntity.driverType,
249
+ indexingDirection: statusList2021Entity.indexingDirection,
250
+ statusPurpose: statusList2021Entity.statusPurpose,
251
+ statusList2021: {
252
+ indexingDirection: statusList2021Entity.indexingDirection,
253
+ statusPurpose: statusList2021Entity.statusPurpose,
254
+ },
255
+ }
256
+ }
257
+ }
258
+
259
+ async createCredentialStatus(args: {
260
+ statusList: StatusListEntity
261
+ statusListEntry: IStatusListEntryEntity | IBitstringStatusListEntryEntity
262
+ statusListIndex: number
263
+ }): Promise<StatusList2021EntryCredentialStatus> {
264
+ const { statusList, statusListIndex } = args
265
+
266
+ // Cast to StatusList2021Entity to access specific properties
267
+ const statusList2021 = statusList as StatusList2021Entity
268
+
269
+ return {
270
+ id: `${statusList.id}#${statusListIndex}`,
271
+ type: 'StatusList2021Entry',
272
+ statusPurpose: statusList2021.statusPurpose ?? 'revocation',
273
+ statusListIndex: '' + statusListIndex,
274
+ statusListCredential: statusList.id,
195
275
  }
196
276
  }
197
277
 
@@ -203,7 +283,7 @@ export class StatusList2021Implementation implements IStatusList {
203
283
  proofFormat: VeramoProofFormat
204
284
  keyRef?: string
205
285
  },
206
- context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
286
+ context: IAgentContext<IVcdmCredentialPlugin & IIdentifierResolution>,
207
287
  ): Promise<StatusListCredential> {
208
288
  const identifier = await context.agent.identifierManagedGet({
209
289
  identifier: typeof args.issuer === 'string' ? args.issuer : args.issuer.id,
package/src/index.ts CHANGED
@@ -3,3 +3,4 @@
3
3
 
4
4
  export * from './types'
5
5
  export * from './functions'
6
+ export { determineStatusListType } from './utils'
@@ -1,42 +1,4 @@
1
- export type BitstringConstructorOptions = {
2
- length?: number
3
- buffer?: Uint8Array
4
- leftToRightIndexing?: boolean
5
- littleEndianBits?: boolean // deprecated
6
- }
1
+ import { StatusListCredential } from '@sphereon/ssi-types'
2
+ import { BitstringStatusListCredentialUnsigned } from '../../../../../vc-bitstring-status-lists'
7
3
 
8
- export type IBitstring = {
9
- bits: Uint8Array
10
- length: number
11
- leftToRightIndexing: boolean
12
-
13
- set(position: number, on: boolean): void
14
- get(position: number): boolean
15
- encodeBits(): Promise<string>
16
- compressBits(): Promise<Uint8Array>
17
- }
18
-
19
- export type BitstringStatic = {
20
- new (options?: BitstringConstructorOptions): IBitstring
21
- decodeBits(options: { encoded: string }): Promise<Uint8Array>
22
- uncompressBits(options: { compressed: Uint8Array }): Promise<Uint8Array>
23
- }
24
-
25
- export type BitstringStatusListConstructorOptions = {
26
- length?: number
27
- buffer?: Uint8Array
28
- }
29
-
30
- export type IBitstringStatusList = {
31
- bitstring: IBitstring
32
- length: number
33
-
34
- setStatus(index: number, status: boolean): void
35
- getStatus(index: number): boolean
36
- encode(): Promise<string>
37
- }
38
-
39
- export type BitstringStatusListStatic = {
40
- new (options?: BitstringStatusListConstructorOptions): IBitstringStatusList
41
- decode(options: { encodedList: string }): Promise<IBitstringStatusList>
42
- }
4
+ export type BitstringStatusListCredential = BitstringStatusListCredentialUnsigned & StatusListCredential