@sphereon/ssi-sdk.vc-status-list 0.34.1-next.3 → 0.34.1-next.323
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/dist/index.cjs +703 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +164 -37
- package/dist/index.d.ts +164 -37
- package/dist/index.js +707 -129
- package/dist/index.js.map +1 -1
- package/package.json +14 -10
- package/src/functions.ts +126 -47
- package/src/impl/BitstringStatusListImplementation.ts +496 -0
- package/src/impl/IStatusList.ts +102 -8
- package/src/impl/OAuthStatusList.ts +133 -38
- package/src/impl/StatusList2021.ts +120 -34
- package/src/impl/StatusListFactory.ts +2 -0
- package/src/impl/encoding/cbor.ts +14 -12
- package/src/index.ts +1 -0
- package/src/types/BitstringStatusList.ts +4 -0
- package/src/types/index.ts +57 -34
- package/src/utils.ts +82 -20
|
@@ -1,26 +1,30 @@
|
|
|
1
|
-
import type { IAgentContext,
|
|
2
|
-
import { type CompactJWT, type
|
|
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<
|
|
25
|
+
type IRequiredContext = IAgentContext<IVcdmCredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>
|
|
22
26
|
|
|
23
|
-
export const DEFAULT_BITS_PER_STATUS = 1 // 1 bit is sufficient for 0x00 - "VALID"
|
|
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
|
|
25
29
|
export const DEFAULT_PROOF_FORMAT = 'jwt' as CredentialProofFormat
|
|
26
30
|
|
|
@@ -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
|
|
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,
|
|
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
|
}
|
|
@@ -70,6 +76,10 @@ export class OAuthStatusListImplementation implements IStatusList {
|
|
|
70
76
|
throw new Error('Status list index out of bounds')
|
|
71
77
|
}
|
|
72
78
|
|
|
79
|
+
if (typeof value !== 'number') {
|
|
80
|
+
throw new Error('Status list values should be of type number')
|
|
81
|
+
}
|
|
82
|
+
|
|
73
83
|
statusList.setStatus(index, value)
|
|
74
84
|
const { statusListCredential: signedCredential, encodedList } = await this.createSignedStatusList(
|
|
75
85
|
proofFormat,
|
|
@@ -102,15 +112,15 @@ export class OAuthStatusListImplementation implements IStatusList {
|
|
|
102
112
|
throw new Error('OAuthStatusList options are required for type OAuthStatusList')
|
|
103
113
|
}
|
|
104
114
|
const { proofFormat, oauthStatusList, keyRef } = args
|
|
105
|
-
const { bitsPerStatus
|
|
115
|
+
const { bitsPerStatus } = oauthStatusList
|
|
116
|
+
const expiresAt = ensureDate(oauthStatusList.expiresAt)
|
|
106
117
|
|
|
107
118
|
const { issuer, id } = getAssertedValues(args)
|
|
108
119
|
const issuerString = typeof issuer === 'string' ? issuer : issuer.id
|
|
109
120
|
|
|
110
121
|
const listToUpdate = StatusList.decompressStatusList(args.encodedList, bitsPerStatus ?? DEFAULT_BITS_PER_STATUS)
|
|
111
122
|
const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
|
|
112
|
-
|
|
113
|
-
listToUpdate.setStatus(index, args.value ? 1 : 0)
|
|
123
|
+
listToUpdate.setStatus(index, args.value)
|
|
114
124
|
|
|
115
125
|
const { statusListCredential, encodedList } = await this.createSignedStatusList(
|
|
116
126
|
proofFormat ?? DEFAULT_PROOF_FORMAT,
|
|
@@ -138,10 +148,6 @@ export class OAuthStatusListImplementation implements IStatusList {
|
|
|
138
148
|
}
|
|
139
149
|
}
|
|
140
150
|
|
|
141
|
-
private buildContentType(proofFormat: 'jwt' | 'lds' | 'EthereumEip712Signature2021' | 'cbor' | undefined) {
|
|
142
|
-
return `application/statuslist+${proofFormat === 'cbor' ? 'cwt' : 'jwt'}`
|
|
143
|
-
}
|
|
144
|
-
|
|
145
151
|
async checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | StatusOAuth> {
|
|
146
152
|
const { statusListCredential, statusListIndex } = args
|
|
147
153
|
if (typeof statusListCredential !== 'string') {
|
|
@@ -153,39 +159,128 @@ export class OAuthStatusListImplementation implements IStatusList {
|
|
|
153
159
|
|
|
154
160
|
const index = typeof statusListIndex === 'number' ? statusListIndex : parseInt(statusListIndex)
|
|
155
161
|
if (index < 0 || index >= statusList.statusList.length) {
|
|
156
|
-
throw new Error(
|
|
162
|
+
throw new Error(`Status list index out of bounds, has ${statusList.statusList.length} items, requested ${index}`)
|
|
157
163
|
}
|
|
158
164
|
|
|
159
165
|
return statusList.getStatus(index)
|
|
160
166
|
}
|
|
161
167
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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)
|
|
167
180
|
|
|
168
181
|
return {
|
|
169
|
-
id,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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 }),
|
|
182
|
+
id: decoded.id,
|
|
183
|
+
issuer: decoded.issuer,
|
|
184
|
+
encodedList: decoded.statusList.compressStatusList(),
|
|
185
|
+
decodedPayload: decoded,
|
|
183
186
|
}
|
|
184
187
|
}
|
|
185
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,
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private buildContentType(proofFormat: CredentialProofFormat | undefined) {
|
|
278
|
+
return `application/statuslist+${proofFormat === 'cbor' ? 'cwt' : 'jwt'}`
|
|
279
|
+
}
|
|
280
|
+
|
|
186
281
|
private async createSignedStatusList(
|
|
187
|
-
proofFormat:
|
|
188
|
-
context: IAgentContext<
|
|
282
|
+
proofFormat: CredentialProofFormat,
|
|
283
|
+
context: IAgentContext<IVcdmCredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>,
|
|
189
284
|
statusList: StatusList,
|
|
190
285
|
issuerString: string,
|
|
191
286
|
id: string,
|
|
@@ -1,35 +1,38 @@
|
|
|
1
|
-
import type { IAgentContext,
|
|
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
|
+
StatusListCredentialIdMode,
|
|
9
10
|
StatusListType,
|
|
10
11
|
} from '@sphereon/ssi-types'
|
|
11
12
|
|
|
12
13
|
import { StatusList } from '@sphereon/vc-status-list'
|
|
13
|
-
import type { IStatusList } from './IStatusList'
|
|
14
|
+
import type { IExtractedCredentialDetails, IStatusList, IStatusList2021ImplementationResult } from './IStatusList'
|
|
14
15
|
import type {
|
|
15
16
|
CheckStatusIndexArgs,
|
|
16
17
|
CreateStatusListArgs,
|
|
18
|
+
IMergeDetailsWithEntityArgs,
|
|
19
|
+
IToDetailsFromCredentialArgs,
|
|
17
20
|
StatusListResult,
|
|
18
|
-
ToStatusListDetailsArgs,
|
|
19
21
|
UpdateStatusListFromEncodedListArgs,
|
|
20
22
|
UpdateStatusListIndexArgs,
|
|
21
23
|
} from '../types'
|
|
22
|
-
|
|
23
|
-
import { Status2021 } from '../types'
|
|
24
|
+
import { Status2021, StatusList2021EntryCredentialStatus } from '../types'
|
|
24
25
|
import { assertValidProofType, getAssertedProperty, getAssertedValue, getAssertedValues } from '../utils'
|
|
26
|
+
import { IBitstringStatusListEntryEntity, IStatusListEntryEntity, StatusList2021Entity, StatusListEntity } from '@sphereon/ssi-sdk.data-store'
|
|
27
|
+
import { IVcdmCredentialPlugin } from '@sphereon/ssi-sdk.credential-vcdm'
|
|
25
28
|
|
|
26
29
|
export const DEFAULT_LIST_LENGTH = 250000
|
|
27
|
-
export const DEFAULT_PROOF_FORMAT = 'lds' as
|
|
30
|
+
export const DEFAULT_PROOF_FORMAT = 'lds' as CredentialProofFormat
|
|
28
31
|
|
|
29
32
|
export class StatusList2021Implementation implements IStatusList {
|
|
30
33
|
async createNewStatusList(
|
|
31
34
|
args: CreateStatusListArgs,
|
|
32
|
-
context: IAgentContext<
|
|
35
|
+
context: IAgentContext<IVcdmCredentialPlugin & IIdentifierResolution>,
|
|
33
36
|
): Promise<StatusListResult> {
|
|
34
37
|
const length = args?.length ?? DEFAULT_LIST_LENGTH
|
|
35
38
|
const proofFormat: CredentialProofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
|
|
@@ -58,6 +61,7 @@ export class StatusList2021Implementation implements IStatusList {
|
|
|
58
61
|
statusList2021: {
|
|
59
62
|
statusPurpose,
|
|
60
63
|
indexingDirection: 'rightToLeft',
|
|
64
|
+
credentialIdMode: StatusListCredentialIdMode.ISSUANCE,
|
|
61
65
|
},
|
|
62
66
|
length,
|
|
63
67
|
type: StatusListType.StatusList2021,
|
|
@@ -71,7 +75,7 @@ export class StatusList2021Implementation implements IStatusList {
|
|
|
71
75
|
|
|
72
76
|
async updateStatusListIndex(
|
|
73
77
|
args: UpdateStatusListIndexArgs,
|
|
74
|
-
context: IAgentContext<
|
|
78
|
+
context: IAgentContext<IVcdmCredentialPlugin & IIdentifierResolution>,
|
|
75
79
|
): Promise<StatusListResult> {
|
|
76
80
|
const credential = args.statusListCredential
|
|
77
81
|
const uniform = CredentialMapper.toUniformCredential(credential)
|
|
@@ -96,12 +100,17 @@ export class StatusList2021Implementation implements IStatusList {
|
|
|
96
100
|
context,
|
|
97
101
|
)
|
|
98
102
|
|
|
103
|
+
if (!('statusPurpose' in credentialSubject)) {
|
|
104
|
+
return Promise.reject(Error('statusPurpose is required in credentialSubject for StatusList2021'))
|
|
105
|
+
}
|
|
106
|
+
|
|
99
107
|
return {
|
|
100
108
|
statusListCredential: updatedCredential,
|
|
101
109
|
encodedList,
|
|
102
110
|
statusList2021: {
|
|
103
|
-
|
|
111
|
+
statusPurpose: credentialSubject.statusPurpose,
|
|
104
112
|
indexingDirection: 'rightToLeft',
|
|
113
|
+
credentialIdMode: StatusListCredentialIdMode.ISSUANCE,
|
|
105
114
|
},
|
|
106
115
|
length: statusList.length - 1,
|
|
107
116
|
type: StatusListType.StatusList2021,
|
|
@@ -114,7 +123,7 @@ export class StatusList2021Implementation implements IStatusList {
|
|
|
114
123
|
|
|
115
124
|
async updateStatusListFromEncodedList(
|
|
116
125
|
args: UpdateStatusListFromEncodedListArgs,
|
|
117
|
-
context: IAgentContext<
|
|
126
|
+
context: IAgentContext<IVcdmCredentialPlugin & IIdentifierResolution>,
|
|
118
127
|
): Promise<StatusListResult> {
|
|
119
128
|
if (!args.statusList2021) {
|
|
120
129
|
throw new Error('statusList2021 options required for type StatusList2021')
|
|
@@ -126,7 +135,7 @@ export class StatusList2021Implementation implements IStatusList {
|
|
|
126
135
|
const { issuer, id } = getAssertedValues(args)
|
|
127
136
|
const statusList = await StatusList.decode({ encodedList: args.encodedList })
|
|
128
137
|
const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
|
|
129
|
-
statusList.setStatus(index, args.value)
|
|
138
|
+
statusList.setStatus(index, args.value !== 0)
|
|
130
139
|
|
|
131
140
|
const newEncodedList = await statusList.encode()
|
|
132
141
|
const credential = await this.createVerifiableCredential(
|
|
@@ -147,6 +156,7 @@ export class StatusList2021Implementation implements IStatusList {
|
|
|
147
156
|
statusList2021: {
|
|
148
157
|
statusPurpose: args.statusList2021.statusPurpose,
|
|
149
158
|
indexingDirection: 'rightToLeft',
|
|
159
|
+
credentialIdMode: StatusListCredentialIdMode.ISSUANCE,
|
|
150
160
|
},
|
|
151
161
|
length: statusList.length,
|
|
152
162
|
proofFormat: args.proofFormat ?? 'lds',
|
|
@@ -166,32 +176,108 @@ export class StatusList2021Implementation implements IStatusList {
|
|
|
166
176
|
return status ? Status2021.Invalid : Status2021.Valid
|
|
167
177
|
}
|
|
168
178
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Performs the initial parsing of a StatusListCredential.
|
|
181
|
+
* This method handles expensive operations like JWT/CWT decoding once.
|
|
182
|
+
* It extracts all details available from the credential payload itself.
|
|
183
|
+
*/
|
|
184
|
+
async extractCredentialDetails(credential: StatusListCredential): Promise<IExtractedCredentialDetails> {
|
|
185
|
+
const uniform = CredentialMapper.toUniformCredential(credential)
|
|
172
186
|
const { issuer, credentialSubject } = uniform
|
|
173
|
-
const
|
|
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 })
|
|
187
|
+
const subject = Array.isArray(credentialSubject) ? credentialSubject[0] : credentialSubject
|
|
179
188
|
|
|
180
189
|
return {
|
|
181
|
-
id,
|
|
182
|
-
encodedList,
|
|
190
|
+
id: getAssertedValue('id', uniform.id),
|
|
183
191
|
issuer,
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
192
|
+
encodedList: getAssertedProperty('encodedList', subject),
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async toStatusListDetails(args: IToDetailsFromCredentialArgs): Promise<StatusListResult & IStatusList2021ImplementationResult>
|
|
197
|
+
// For UPDATE contexts
|
|
198
|
+
async toStatusListDetails(args: IMergeDetailsWithEntityArgs): Promise<StatusListResult & IStatusList2021ImplementationResult>
|
|
199
|
+
async toStatusListDetails(
|
|
200
|
+
args: IToDetailsFromCredentialArgs | IMergeDetailsWithEntityArgs,
|
|
201
|
+
): Promise<StatusListResult & IStatusList2021ImplementationResult> {
|
|
202
|
+
if ('statusListCredential' in args) {
|
|
203
|
+
// CREATE/READ context
|
|
204
|
+
const { statusListCredential, correlationId, driverType } = args
|
|
205
|
+
const uniform = CredentialMapper.toUniformCredential(statusListCredential)
|
|
206
|
+
const { issuer, credentialSubject } = uniform
|
|
207
|
+
const subject = Array.isArray(credentialSubject) ? credentialSubject[0] : credentialSubject
|
|
208
|
+
|
|
209
|
+
const id = getAssertedValue('id', uniform.id)
|
|
210
|
+
const encodedList = getAssertedProperty('encodedList', subject)
|
|
211
|
+
const statusPurpose = getAssertedProperty('statusPurpose', subject)
|
|
212
|
+
const proofFormat: CredentialProofFormat = CredentialMapper.detectDocumentType(statusListCredential) === DocumentFormat.JWT ? 'jwt' : 'lds'
|
|
213
|
+
const list = await StatusList.decode({ encodedList })
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
id,
|
|
217
|
+
encodedList,
|
|
218
|
+
issuer,
|
|
219
|
+
type: StatusListType.StatusList2021,
|
|
220
|
+
proofFormat,
|
|
221
|
+
length: list.length,
|
|
222
|
+
statusListCredential,
|
|
223
|
+
statuslistContentType: this.buildContentType(proofFormat),
|
|
224
|
+
correlationId,
|
|
225
|
+
driverType,
|
|
190
226
|
indexingDirection: 'rightToLeft',
|
|
191
227
|
statusPurpose,
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
228
|
+
statusList2021: {
|
|
229
|
+
indexingDirection: 'rightToLeft',
|
|
230
|
+
statusPurpose,
|
|
231
|
+
credentialIdMode: StatusListCredentialIdMode.ISSUANCE,
|
|
232
|
+
},
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
// UPDATE context
|
|
236
|
+
const { extractedDetails, statusListEntity } = args
|
|
237
|
+
const statusList2021Entity = statusListEntity as StatusList2021Entity
|
|
238
|
+
|
|
239
|
+
const proofFormat: CredentialProofFormat =
|
|
240
|
+
CredentialMapper.detectDocumentType(statusListEntity.statusListCredential!) === DocumentFormat.JWT ? 'jwt' : 'lds'
|
|
241
|
+
const list = await StatusList.decode({ encodedList: extractedDetails.encodedList })
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
id: extractedDetails.id,
|
|
245
|
+
encodedList: extractedDetails.encodedList,
|
|
246
|
+
issuer: extractedDetails.issuer,
|
|
247
|
+
type: StatusListType.StatusList2021,
|
|
248
|
+
proofFormat,
|
|
249
|
+
length: list.length,
|
|
250
|
+
statusListCredential: statusListEntity.statusListCredential!,
|
|
251
|
+
statuslistContentType: this.buildContentType(proofFormat),
|
|
252
|
+
correlationId: statusListEntity.correlationId,
|
|
253
|
+
driverType: statusListEntity.driverType,
|
|
254
|
+
indexingDirection: statusList2021Entity.indexingDirection,
|
|
255
|
+
statusPurpose: statusList2021Entity.statusPurpose,
|
|
256
|
+
statusList2021: {
|
|
257
|
+
indexingDirection: statusList2021Entity.indexingDirection,
|
|
258
|
+
statusPurpose: statusList2021Entity.statusPurpose,
|
|
259
|
+
credentialIdMode: StatusListCredentialIdMode.ISSUANCE,
|
|
260
|
+
},
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async createCredentialStatus(args: {
|
|
266
|
+
statusList: StatusListEntity
|
|
267
|
+
statusListEntry: IStatusListEntryEntity | IBitstringStatusListEntryEntity
|
|
268
|
+
statusListIndex: number
|
|
269
|
+
}): Promise<StatusList2021EntryCredentialStatus> {
|
|
270
|
+
const { statusList, statusListIndex } = args
|
|
271
|
+
|
|
272
|
+
// Cast to StatusList2021Entity to access specific properties
|
|
273
|
+
const statusList2021 = statusList as StatusList2021Entity
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
id: `${statusList.id}#${statusListIndex}`,
|
|
277
|
+
type: 'StatusList2021Entry',
|
|
278
|
+
statusPurpose: statusList2021.statusPurpose ?? 'revocation',
|
|
279
|
+
statusListIndex: '' + statusListIndex,
|
|
280
|
+
statusListCredential: statusList.id,
|
|
195
281
|
}
|
|
196
282
|
}
|
|
197
283
|
|
|
@@ -203,7 +289,7 @@ export class StatusList2021Implementation implements IStatusList {
|
|
|
203
289
|
proofFormat: VeramoProofFormat
|
|
204
290
|
keyRef?: string
|
|
205
291
|
},
|
|
206
|
-
context: IAgentContext<
|
|
292
|
+
context: IAgentContext<IVcdmCredentialPlugin & IIdentifierResolution>,
|
|
207
293
|
): Promise<StatusListCredential> {
|
|
208
294
|
const identifier = await context.agent.identifierManagedGet({
|
|
209
295
|
identifier: typeof args.issuer === 'string' ? args.issuer : args.issuer.id,
|
|
@@ -234,7 +320,7 @@ export class StatusList2021Implementation implements IStatusList {
|
|
|
234
320
|
return CredentialMapper.toWrappedVerifiableCredential(verifiableCredential as StatusListCredential).original as StatusListCredential
|
|
235
321
|
}
|
|
236
322
|
|
|
237
|
-
private buildContentType(proofFormat:
|
|
323
|
+
private buildContentType(proofFormat: CredentialProofFormat | undefined) {
|
|
238
324
|
switch (proofFormat) {
|
|
239
325
|
case 'jwt':
|
|
240
326
|
return `application/statuslist+jwt`
|
|
@@ -2,6 +2,7 @@ import type { IStatusList } from './IStatusList'
|
|
|
2
2
|
import { StatusList2021Implementation } from './StatusList2021'
|
|
3
3
|
import { OAuthStatusListImplementation } from './OAuthStatusList'
|
|
4
4
|
import { StatusListType } from '@sphereon/ssi-types'
|
|
5
|
+
import { BitstringStatusListImplementation } from './BitstringStatusListImplementation'
|
|
5
6
|
|
|
6
7
|
export class StatusListFactory {
|
|
7
8
|
private static instance: StatusListFactory
|
|
@@ -11,6 +12,7 @@ export class StatusListFactory {
|
|
|
11
12
|
this.implementations = new Map()
|
|
12
13
|
this.implementations.set(StatusListType.StatusList2021, new StatusList2021Implementation())
|
|
13
14
|
this.implementations.set(StatusListType.OAuthStatusList, new OAuthStatusListImplementation())
|
|
15
|
+
this.implementations.set(StatusListType.BitstringStatusList, new BitstringStatusListImplementation())
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
public static getInstance(): StatusListFactory {
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import type { BitsPerStatus } from '@sd-jwt/jwt-status-list'
|
|
2
2
|
import { StatusList } from '@sd-jwt/jwt-status-list'
|
|
3
3
|
import { deflate, inflate } from 'pako'
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
import mdocPkg from '@sphereon/kmp-mdoc-core'
|
|
6
|
+
const { com, kotlin } = mdocPkg
|
|
7
|
+
|
|
6
8
|
import base64url from 'base64url'
|
|
7
9
|
import type { IRequiredContext, SignedStatusListData } from '../../types'
|
|
8
10
|
import { type DecodedStatusListPayload, resolveIdentifier } from './common'
|
|
9
11
|
|
|
10
|
-
export type IKey =
|
|
11
|
-
export type CborItem<T> =
|
|
12
|
-
export const CborByteString = com.sphereon.cbor.CborByteString
|
|
13
|
-
export type CborByteStringType =
|
|
14
|
-
export const CborUInt = com.sphereon.cbor.CborUInt
|
|
15
|
-
export type CborUIntType =
|
|
16
|
-
export const CborString = com.sphereon.cbor.CborString
|
|
17
|
-
export type CborStringType =
|
|
12
|
+
export type IKey = mdocPkg.com.sphereon.crypto.IKey
|
|
13
|
+
export type CborItem<T> = mdocPkg.com.sphereon.cbor.CborItem<T>
|
|
14
|
+
export const CborByteString = mdocPkg.com.sphereon.cbor.CborByteString
|
|
15
|
+
export type CborByteStringType = mdocPkg.com.sphereon.cbor.CborByteString
|
|
16
|
+
export const CborUInt = mdocPkg.com.sphereon.cbor.CborUInt
|
|
17
|
+
export type CborUIntType = mdocPkg.com.sphereon.cbor.CborUInt
|
|
18
|
+
export const CborString = mdocPkg.com.sphereon.cbor.CborString
|
|
19
|
+
export type CborStringType = mdocPkg.com.sphereon.cbor.CborString
|
|
18
20
|
|
|
19
21
|
// const cbor = cborpkg.com.sphereon.cbor
|
|
20
|
-
// const kmp = cborpkg.com.sphereon.kmp
|
|
22
|
+
// const kmp = cborpkg. mdoc.com.sphereon.kmp
|
|
21
23
|
// const kotlin = cborpkg.kotlin
|
|
22
24
|
const decompressRawStatusList = (StatusList as any).decodeStatusList.bind(StatusList)
|
|
23
25
|
|
|
@@ -93,7 +95,7 @@ export const createSignedCbor = async (
|
|
|
93
95
|
function buildClaimsMap(
|
|
94
96
|
id: string,
|
|
95
97
|
issuerString: string,
|
|
96
|
-
statusListMap:
|
|
98
|
+
statusListMap: mdocPkg.com.sphereon.cbor.CborMap<CborStringType, CborItem<any>>,
|
|
97
99
|
expiresAt?: Date,
|
|
98
100
|
) {
|
|
99
101
|
const ttl = 65535 // FIXME figure out what value should be / come from and what the difference is with exp
|
package/src/index.ts
CHANGED