@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.
@@ -10,12 +10,13 @@ import {
10
10
  } from '@sphereon/ssi-types'
11
11
 
12
12
  import { StatusList } from '@sphereon/vc-status-list'
13
- import type { IStatusList, IStatusList2021ImplementationResult } 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'
@@ -171,44 +172,82 @@ export class StatusList2021Implementation implements IStatusList {
171
172
  return status ? Status2021.Invalid : Status2021.Valid
172
173
  }
173
174
 
174
- async toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult & IStatusList2021ImplementationResult> {
175
- const { statusListPayload } = args
176
- const uniform = CredentialMapper.toUniformCredential(statusListPayload)
175
+ async extractCredentialDetails(credential: StatusListCredential): Promise<IExtractedCredentialDetails> {
176
+ const uniform = CredentialMapper.toUniformCredential(credential)
177
177
  const { issuer, credentialSubject } = uniform
178
- const id = getAssertedValue('id', uniform.id)
179
- const encodedList = getAssertedProperty('encodedList', credentialSubject)
180
- const proofFormat: CredentialProofFormat = CredentialMapper.detectDocumentType(statusListPayload) === DocumentFormat.JWT ? 'jwt' : 'lds'
181
-
182
- const statusPurpose = getAssertedProperty('statusPurpose', credentialSubject)
183
- const indexingDirection = 'rightToLeft'
184
- const list = await StatusList.decode({ encodedList })
178
+ const subject = Array.isArray(credentialSubject) ? credentialSubject[0] : credentialSubject
185
179
 
186
180
  return {
187
- // Base implementation fields
188
- id,
189
- encodedList,
181
+ id: getAssertedValue('id', uniform.id),
190
182
  issuer,
191
- type: StatusListType.StatusList2021,
192
- proofFormat,
193
- length: list.length,
194
- statusListCredential: statusListPayload,
195
- statuslistContentType: this.buildContentType(proofFormat),
196
- correlationId: args.correlationId, // FIXME these do not need to be inside the impl
197
- driverType: args.driverType, // FIXME these do not need to be inside the impl
198
-
199
- // Flattened StatusList2021-specific fields
200
- indexingDirection,
201
- statusPurpose,
183
+ encodedList: getAssertedProperty('encodedList', subject),
184
+ }
185
+ }
202
186
 
203
- // Legacy nested structure for backward compatibility
204
- statusList2021: {
205
- indexingDirection,
187
+ async toStatusListDetails(args: IToDetailsFromCredentialArgs): Promise<StatusListResult & IStatusList2021ImplementationResult>
188
+ // For UPDATE contexts
189
+ async toStatusListDetails(args: IMergeDetailsWithEntityArgs): Promise<StatusListResult & IStatusList2021ImplementationResult>
190
+ async toStatusListDetails(
191
+ args: IToDetailsFromCredentialArgs | IMergeDetailsWithEntityArgs,
192
+ ): Promise<StatusListResult & IStatusList2021ImplementationResult> {
193
+ if ('statusListCredential' in args) {
194
+ // CREATE/READ context
195
+ const { statusListCredential, correlationId, driverType } = args
196
+ const uniform = CredentialMapper.toUniformCredential(statusListCredential)
197
+ const { issuer, credentialSubject } = uniform
198
+ const subject = Array.isArray(credentialSubject) ? credentialSubject[0] : credentialSubject
199
+
200
+ const id = getAssertedValue('id', uniform.id)
201
+ const encodedList = getAssertedProperty('encodedList', subject)
202
+ const statusPurpose = getAssertedProperty('statusPurpose', subject)
203
+ const proofFormat: CredentialProofFormat = CredentialMapper.detectDocumentType(statusListCredential) === DocumentFormat.JWT ? 'jwt' : 'lds'
204
+ const list = await StatusList.decode({ encodedList })
205
+
206
+ return {
207
+ id,
208
+ encodedList,
209
+ issuer,
210
+ type: StatusListType.StatusList2021,
211
+ proofFormat,
212
+ length: list.length,
213
+ statusListCredential,
214
+ statuslistContentType: this.buildContentType(proofFormat),
215
+ correlationId,
216
+ driverType,
217
+ indexingDirection: 'rightToLeft',
206
218
  statusPurpose,
207
-
208
- // Optional fields from args
209
- ...(args.correlationId && { correlationId: args.correlationId }),
210
- ...(args.driverType && { driverType: args.driverType }),
211
- },
219
+ statusList2021: {
220
+ indexingDirection: 'rightToLeft',
221
+ statusPurpose,
222
+ },
223
+ }
224
+ } else {
225
+ // UPDATE context
226
+ const { extractedDetails, statusListEntity } = args
227
+ const statusList2021Entity = statusListEntity as StatusList2021Entity
228
+
229
+ const proofFormat: CredentialProofFormat =
230
+ CredentialMapper.detectDocumentType(statusListEntity.statusListCredential!) === DocumentFormat.JWT ? 'jwt' : 'lds'
231
+ const list = await StatusList.decode({ encodedList: extractedDetails.encodedList })
232
+
233
+ return {
234
+ id: extractedDetails.id,
235
+ encodedList: extractedDetails.encodedList,
236
+ issuer: extractedDetails.issuer,
237
+ type: StatusListType.StatusList2021,
238
+ proofFormat,
239
+ length: list.length,
240
+ statusListCredential: statusListEntity.statusListCredential!,
241
+ statuslistContentType: this.buildContentType(proofFormat),
242
+ correlationId: statusListEntity.correlationId,
243
+ driverType: statusListEntity.driverType,
244
+ indexingDirection: statusList2021Entity.indexingDirection,
245
+ statusPurpose: statusList2021Entity.statusPurpose,
246
+ statusList2021: {
247
+ indexingDirection: statusList2021Entity.indexingDirection,
248
+ statusPurpose: statusList2021Entity.statusPurpose,
249
+ },
250
+ }
212
251
  }
213
252
  }
214
253
 
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'
@@ -19,6 +19,8 @@ import type { SdJwtVcPayload } from '@sd-jwt/sd-jwt-vc'
19
19
  import type { StatusListOpts } from '@sphereon/oid4vci-common'
20
20
  import { BitstringStatusPurpose } from '@4sure-tech/vc-bitstring-status-lists'
21
21
  import { IVcdmCredentialPlugin } from '@sphereon/ssi-sdk.credential-vcdm'
22
+ import { IExtractedCredentialDetails } from '../impl/IStatusList'
23
+ import { BitstringStatusListArgs, IStatusListEntity } from '@sphereon/ssi-sdk.data-store'
22
24
 
23
25
  export enum StatusOAuth {
24
26
  Valid = 0,
@@ -31,8 +33,6 @@ export enum Status2021 {
31
33
  Invalid = 1,
32
34
  }
33
35
 
34
- export type BitstringStatus = number
35
-
36
36
  export type StatusList2021Args = {
37
37
  indexingDirection: StatusListIndexingDirection
38
38
  statusPurpose?: StatusPurpose2021
@@ -44,14 +44,6 @@ export type OAuthStatusListArgs = {
44
44
  expiresAt?: Date
45
45
  }
46
46
 
47
- export type BitstringStatusListArgs = {
48
- statusPurpose: BitstringStatusPurpose
49
- bitsPerStatus: number
50
- ttl?: number
51
- validFrom?: Date
52
- validUntil?: Date
53
- }
54
-
55
47
  export type BaseCreateNewStatusListArgs = {
56
48
  type: StatusListType
57
49
  id: string
@@ -102,7 +94,7 @@ export interface UpdateStatusListFromStatusListCredentialArgs {
102
94
  statusListCredential: StatusListCredential // | CompactJWT
103
95
  keyRef?: string
104
96
  statusListIndex: number | string
105
- value: number | Status2021 | StatusOAuth | BitstringStatus
97
+ value: number | Status2021 | StatusOAuth
106
98
  }
107
99
 
108
100
  export interface StatusListResult {
@@ -163,16 +155,6 @@ export interface StatusListOAuthEntryCredentialStatus extends ICredentialStatus
163
155
  expiresAt?: Date
164
156
  }
165
157
 
166
- export interface BitstringStatusListEntryCredentialStatus extends ICredentialStatus {
167
- type: 'BitstringStatusListEntry'
168
- statusPurpose: BitstringStatusPurpose | BitstringStatusPurpose[]
169
- statusListIndex: string
170
- statusListCredential: string
171
- bitsPerStatus?: number
172
- statusMessage?: Array<BitstringStatus>
173
- statusReference?: string | string[]
174
- }
175
-
176
158
  export interface StatusList2021ToVerifiableCredentialArgs {
177
159
  issuer: string | IIssuer
178
160
  id: string
@@ -198,7 +180,7 @@ export interface CreateStatusListArgs {
198
180
  export interface UpdateStatusListIndexArgs {
199
181
  statusListCredential: StatusListCredential // | CompactJWT
200
182
  statusListIndex: number | string
201
- value: number | Status2021 | StatusOAuth | BitstringStatus
183
+ value: number | Status2021 | StatusOAuth
202
184
  bitsPerStatus?: number
203
185
  keyRef?: string
204
186
  expiresAt?: Date
@@ -210,11 +192,22 @@ export interface CheckStatusIndexArgs {
210
192
  bitsPerStatus?: number
211
193
  }
212
194
 
213
- export interface ToStatusListDetailsArgs {
214
- statusListPayload: StatusListCredential
195
+ // For the CREATE and READ contexts
196
+ export interface IToDetailsFromCredentialArgs {
197
+ // The source credential we are converting
198
+ statusListCredential: StatusListCredential
199
+
200
+ // The required metadata that is NOT in the credential itself
201
+ statusListType: StatusListType
202
+ bitsPerStatus?: number
215
203
  correlationId?: string
216
204
  driverType?: StatusListDriverType
217
- bitsPerStatus?: number
205
+ }
206
+
207
+ // For the UPDATE context
208
+ export interface IMergeDetailsWithEntityArgs {
209
+ extractedDetails: IExtractedCredentialDetails
210
+ statusListEntity: IStatusListEntity
218
211
  }
219
212
 
220
213
  /**
package/src/utils.ts CHANGED
@@ -53,31 +53,60 @@ export function assertValidProofType(type: StatusListType, proofFormat: Credenti
53
53
 
54
54
  export function determineStatusListType(credential: StatusListCredential): StatusListType {
55
55
  const proofFormat = determineProofFormat(credential)
56
+
56
57
  switch (proofFormat) {
57
58
  case 'jwt':
58
- const payload: StatusListCredential = jwtDecode(credential as string)
59
- const keys = Object.keys(payload)
60
- if (keys.includes('status_list')) {
61
- return StatusListType.OAuthStatusList
62
- } else if (keys.includes('vc')) {
63
- return StatusListType.StatusList2021
64
- }
65
- break
59
+ return determineJwtStatusListType(credential as string)
66
60
  case 'lds':
67
- const uniform = CredentialMapper.toUniformCredential(credential)
68
- const type = uniform.type.find((t) => {
69
- return Object.values(StatusListType).some((statusType) => t.includes(statusType))
70
- })
71
- if (!type) {
72
- throw new Error('Invalid status list credential type')
73
- }
74
- return type.replace('Credential', '') as StatusListType
75
-
61
+ return determineLdsStatusListType(credential)
76
62
  case 'cbor':
77
63
  return StatusListType.OAuthStatusList
64
+ default:
65
+ throw new Error('Cannot determine status list type from credential payload')
66
+ }
67
+ }
68
+
69
+ function determineJwtStatusListType(credential: string): StatusListType {
70
+ const payload: any = jwtDecode(credential)
71
+
72
+ // OAuth status list format
73
+ if ('status_list' in payload) {
74
+ return StatusListType.OAuthStatusList
75
+ }
76
+
77
+ // Direct credential subject
78
+ if ('credentialSubject' in payload) {
79
+ return getStatusListTypeFromSubject(payload.credentialSubject)
80
+ }
81
+
82
+ // Wrapped VC format
83
+ if ('vc' in payload && 'credentialSubject' in payload.vc) {
84
+ return getStatusListTypeFromSubject(payload.vc.credentialSubject)
85
+ }
86
+
87
+ throw new Error('Invalid status list credential: credentialSubject not found')
88
+ }
89
+
90
+ function determineLdsStatusListType(credential: StatusListCredential): StatusListType {
91
+ const uniform = CredentialMapper.toUniformCredential(credential)
92
+ const statusListType = uniform.type.find((type) => Object.values(StatusListType).some((statusType) => type.includes(statusType)))
93
+
94
+ if (!statusListType) {
95
+ throw new Error('Invalid status list credential type')
78
96
  }
79
97
 
80
- throw new Error('Cannot determine status list type from credential payload')
98
+ return statusListType.replace('Credential', '') as StatusListType
99
+ }
100
+
101
+ function getStatusListTypeFromSubject(credentialSubject: any): StatusListType {
102
+ switch (credentialSubject.type) {
103
+ case 'StatusList2021':
104
+ return StatusListType.StatusList2021
105
+ case 'BitstringStatusList':
106
+ return StatusListType.BitstringStatusList
107
+ default:
108
+ throw new Error(`Unknown credential subject type: ${credentialSubject.type}`)
109
+ }
81
110
  }
82
111
 
83
112
  export function determineProofFormat(credential: StatusListCredential): CredentialProofFormat {