@sphereon/ssi-sdk.vc-status-list 0.33.1-next.73 → 0.34.1-feature.SSISDK.17.bitstring.sl.10
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 +307 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -8
- package/dist/index.d.ts +49 -8
- package/dist/index.js +308 -28
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
- package/src/functions.ts +19 -10
- package/src/impl/BitstringStatusListImplementation.ts +301 -0
- package/src/impl/IStatusList.ts +3 -2
- package/src/impl/OAuthStatusList.ts +17 -11
- package/src/impl/StatusList2021.ts +3 -3
- package/src/impl/StatusListFactory.ts +2 -0
- package/src/types/BitstringStatusList.ts +4 -0
- package/src/types/index.ts +48 -4
- package/src/utils.ts +35 -2
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import type { IAgentContext, ICredentialPlugin, ProofFormat as VeramoProofFormat } from '@veramo/core'
|
|
2
|
+
import type { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
3
|
+
import {
|
|
4
|
+
CredentialMapper,
|
|
5
|
+
type CredentialProofFormat,
|
|
6
|
+
DocumentFormat,
|
|
7
|
+
type IIssuer,
|
|
8
|
+
type StatusListCredential,
|
|
9
|
+
StatusListType,
|
|
10
|
+
} from '@sphereon/ssi-types'
|
|
11
|
+
|
|
12
|
+
import type { IStatusList } from './IStatusList'
|
|
13
|
+
import {
|
|
14
|
+
BitstringStatus,
|
|
15
|
+
CheckStatusIndexArgs,
|
|
16
|
+
CreateStatusListArgs,
|
|
17
|
+
StatusListResult,
|
|
18
|
+
ToStatusListDetailsArgs,
|
|
19
|
+
UpdateStatusListFromEncodedListArgs,
|
|
20
|
+
UpdateStatusListIndexArgs,
|
|
21
|
+
} from '../types'
|
|
22
|
+
|
|
23
|
+
import { assertValidProofType, ensureDate, getAssertedProperty, getAssertedValue, getAssertedValues } from '../utils'
|
|
24
|
+
import { BitstringStatusListCredential } from '../types/BitstringStatusList'
|
|
25
|
+
import { BitstreamStatusList, BitstringStatusPurpose, createStatusListCredential } from '@4sure-tech/vc-bitstring-status-lists'
|
|
26
|
+
|
|
27
|
+
export const DEFAULT_LIST_LENGTH = 131072 // W3C spec minimum
|
|
28
|
+
export const DEFAULT_PROOF_FORMAT = 'lds' as CredentialProofFormat
|
|
29
|
+
export const DEFAULT_STATUS_PURPOSE: BitstringStatusPurpose = 'revocation'
|
|
30
|
+
|
|
31
|
+
export class BitstringStatusListImplementation implements IStatusList {
|
|
32
|
+
async createNewStatusList(
|
|
33
|
+
args: CreateStatusListArgs,
|
|
34
|
+
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
35
|
+
): Promise<StatusListResult> {
|
|
36
|
+
if (!args.bitstringStatusList) {
|
|
37
|
+
throw new Error('BitstringStatusList options are required for type BitstringStatusList')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const length = args?.length ?? DEFAULT_LIST_LENGTH
|
|
41
|
+
const proofFormat: CredentialProofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
|
|
42
|
+
assertValidProofType(StatusListType.BitstringStatusList, proofFormat)
|
|
43
|
+
const veramoProofFormat: VeramoProofFormat = proofFormat as VeramoProofFormat
|
|
44
|
+
|
|
45
|
+
const { issuer, id } = args
|
|
46
|
+
const correlationId = getAssertedValue('correlationId', args.correlationId)
|
|
47
|
+
const { statusPurpose, bitsPerStatus, validFrom, validUntil, ttl } = args.bitstringStatusList
|
|
48
|
+
const statusListCredential = await this.createVerifiableCredential(
|
|
49
|
+
{
|
|
50
|
+
...args,
|
|
51
|
+
proofFormat: veramoProofFormat,
|
|
52
|
+
statusPurpose: statusPurpose ?? DEFAULT_STATUS_PURPOSE,
|
|
53
|
+
validFrom: ensureDate(validFrom),
|
|
54
|
+
validUntil: ensureDate(validUntil),
|
|
55
|
+
ttl,
|
|
56
|
+
},
|
|
57
|
+
context,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
encodedList: statusListCredential.credentialSubject.encodedList,
|
|
62
|
+
statusListCredential: statusListCredential,
|
|
63
|
+
bitstringStatusList: {
|
|
64
|
+
statusPurpose: statusPurpose ?? DEFAULT_STATUS_PURPOSE,
|
|
65
|
+
...(statusListCredential.validFrom && { validFrom: new Date(statusListCredential.validFrom) }),
|
|
66
|
+
...(statusListCredential.validUntil && { validUntil: new Date(statusListCredential.validUntil) }),
|
|
67
|
+
ttl,
|
|
68
|
+
bitsPerStatus,
|
|
69
|
+
},
|
|
70
|
+
length,
|
|
71
|
+
type: StatusListType.BitstringStatusList,
|
|
72
|
+
proofFormat,
|
|
73
|
+
id,
|
|
74
|
+
correlationId,
|
|
75
|
+
issuer,
|
|
76
|
+
statuslistContentType: this.buildContentType(proofFormat),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async updateStatusListIndex(
|
|
81
|
+
args: UpdateStatusListIndexArgs,
|
|
82
|
+
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
83
|
+
): Promise<StatusListResult> {
|
|
84
|
+
if (!args.bitsPerStatus || args.bitsPerStatus < 1) {
|
|
85
|
+
return Promise.reject('bitsPerStatus must be set for bitstring status lists and must be 1 or higher. (updateStatusListIndex)')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const credential = args.statusListCredential
|
|
89
|
+
const uniform = CredentialMapper.toUniformCredential(credential)
|
|
90
|
+
const { issuer, credentialSubject } = uniform
|
|
91
|
+
const id = getAssertedValue('id', uniform.id)
|
|
92
|
+
const origEncodedList = getAssertedProperty('encodedList', credentialSubject)
|
|
93
|
+
|
|
94
|
+
const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
|
|
95
|
+
const statusList: BitstreamStatusList = await BitstreamStatusList.decode({ encodedList: origEncodedList, statusSize: args.bitsPerStatus })
|
|
96
|
+
statusList.setStatus(index, args.value)
|
|
97
|
+
|
|
98
|
+
const proofFormat = CredentialMapper.detectDocumentType(credential) === DocumentFormat.JWT ? 'jwt' : 'lds'
|
|
99
|
+
|
|
100
|
+
const credSubject = Array.isArray(credentialSubject) ? credentialSubject[0] : credentialSubject
|
|
101
|
+
|
|
102
|
+
const statusPurpose = getAssertedProperty('statusPurpose', credSubject)
|
|
103
|
+
|
|
104
|
+
const validFrom = uniform.validFrom ? new Date(uniform.validFrom) : undefined
|
|
105
|
+
const validUntil = uniform.validUntil ? new Date(uniform.validUntil) : undefined
|
|
106
|
+
const ttl = credSubject.ttl
|
|
107
|
+
|
|
108
|
+
const updatedCredential = await this.createVerifiableCredential(
|
|
109
|
+
{
|
|
110
|
+
...args,
|
|
111
|
+
id,
|
|
112
|
+
issuer,
|
|
113
|
+
statusList,
|
|
114
|
+
proofFormat: proofFormat,
|
|
115
|
+
statusPurpose,
|
|
116
|
+
ttl,
|
|
117
|
+
validFrom,
|
|
118
|
+
validUntil,
|
|
119
|
+
},
|
|
120
|
+
context,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
statusListCredential: updatedCredential,
|
|
125
|
+
encodedList: updatedCredential.credentialSubject.encodedList,
|
|
126
|
+
bitstringStatusList: {
|
|
127
|
+
statusPurpose,
|
|
128
|
+
...(updatedCredential.validFrom && { validFrom: new Date(updatedCredential.validFrom) }),
|
|
129
|
+
...(updatedCredential.validUntil && { validUntil: new Date(updatedCredential.validUntil) }),
|
|
130
|
+
bitsPerStatus: args.bitsPerStatus,
|
|
131
|
+
ttl,
|
|
132
|
+
},
|
|
133
|
+
length: statusList.getLength(),
|
|
134
|
+
type: StatusListType.BitstringStatusList,
|
|
135
|
+
proofFormat: proofFormat,
|
|
136
|
+
id,
|
|
137
|
+
issuer,
|
|
138
|
+
statuslistContentType: this.buildContentType(proofFormat),
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async updateStatusListFromEncodedList(
|
|
143
|
+
args: UpdateStatusListFromEncodedListArgs,
|
|
144
|
+
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
145
|
+
): Promise<StatusListResult> {
|
|
146
|
+
if (!args.bitstringStatusList) {
|
|
147
|
+
throw new Error('bitstringStatusList options required for type BitstringStatusList')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (args.bitstringStatusList.bitsPerStatus < 1) {
|
|
151
|
+
return Promise.reject('bitsPerStatus must be set for bitstring status lists and must be 1 or higher. (updateStatusListFromEncodedList)')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const { statusPurpose, bitsPerStatus, ttl, validFrom, validUntil } = args.bitstringStatusList
|
|
155
|
+
|
|
156
|
+
const proofFormat: CredentialProofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
|
|
157
|
+
assertValidProofType(StatusListType.BitstringStatusList, proofFormat)
|
|
158
|
+
const veramoProofFormat: VeramoProofFormat = proofFormat as VeramoProofFormat
|
|
159
|
+
|
|
160
|
+
const { issuer, id } = getAssertedValues(args)
|
|
161
|
+
const statusList: BitstreamStatusList = await BitstreamStatusList.decode({ encodedList: args.encodedList, statusSize: bitsPerStatus })
|
|
162
|
+
const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
|
|
163
|
+
statusList.setStatus(index, args.value)
|
|
164
|
+
|
|
165
|
+
const credential = await this.createVerifiableCredential(
|
|
166
|
+
{
|
|
167
|
+
id,
|
|
168
|
+
issuer,
|
|
169
|
+
statusList,
|
|
170
|
+
proofFormat: veramoProofFormat,
|
|
171
|
+
keyRef: args.keyRef,
|
|
172
|
+
statusPurpose,
|
|
173
|
+
validFrom: ensureDate(validFrom),
|
|
174
|
+
validUntil: ensureDate(validUntil),
|
|
175
|
+
ttl,
|
|
176
|
+
},
|
|
177
|
+
context,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
type: StatusListType.BitstringStatusList,
|
|
182
|
+
statusListCredential: credential,
|
|
183
|
+
encodedList: credential.credentialSubject.encodedList,
|
|
184
|
+
bitstringStatusList: {
|
|
185
|
+
statusPurpose,
|
|
186
|
+
bitsPerStatus,
|
|
187
|
+
...(credential.validFrom && { validFrom: new Date(credential.validFrom) }),
|
|
188
|
+
...(credential.validUntil && { validUntil: new Date(credential.validUntil) }),
|
|
189
|
+
ttl,
|
|
190
|
+
},
|
|
191
|
+
length: statusList.getLength(),
|
|
192
|
+
proofFormat: args.proofFormat ?? 'lds',
|
|
193
|
+
id: id,
|
|
194
|
+
issuer: issuer,
|
|
195
|
+
statuslistContentType: this.buildContentType(proofFormat),
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async checkStatusIndex(args: CheckStatusIndexArgs): Promise<BitstringStatus> {
|
|
200
|
+
if (!args.bitsPerStatus || args.bitsPerStatus < 1) {
|
|
201
|
+
return Promise.reject('bitsPerStatus must be set for bitstring status lists and must be 1 or higher. (checkStatusIndex)')
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const uniform = CredentialMapper.toUniformCredential(args.statusListCredential)
|
|
205
|
+
const { credentialSubject } = uniform
|
|
206
|
+
const encodedList = getAssertedProperty('encodedList', credentialSubject)
|
|
207
|
+
|
|
208
|
+
const statusSize = args.bitsPerStatus
|
|
209
|
+
const statusList = await BitstreamStatusList.decode({ encodedList, statusSize })
|
|
210
|
+
|
|
211
|
+
const numIndex = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
|
|
212
|
+
if (statusList.getLength() <= numIndex) {
|
|
213
|
+
throw new Error(`Status list index out of bounds, has ${statusList.getLength()} entries, requested ${numIndex}`)
|
|
214
|
+
}
|
|
215
|
+
return statusList.getStatus(numIndex)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult> {
|
|
219
|
+
const { statusListPayload, bitsPerStatus } = args
|
|
220
|
+
if (!bitsPerStatus || bitsPerStatus < 1) {
|
|
221
|
+
return Promise.reject('bitsPerStatus must be set for bitstring status lists and must be 1 or higher. (toStatusListDetails)')
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const uniform = CredentialMapper.toUniformCredential(statusListPayload)
|
|
225
|
+
const { issuer, credentialSubject } = uniform
|
|
226
|
+
const id = getAssertedValue('id', uniform.id)
|
|
227
|
+
const encodedList = getAssertedProperty('encodedList', credentialSubject)
|
|
228
|
+
const proofFormat: CredentialProofFormat = CredentialMapper.detectDocumentType(statusListPayload) === DocumentFormat.JWT ? 'jwt' : 'lds'
|
|
229
|
+
const credSubject = Array.isArray(credentialSubject) ? credentialSubject[0] : credentialSubject
|
|
230
|
+
const statusPurpose = getAssertedProperty('statusPurpose', credSubject)
|
|
231
|
+
const validFrom = uniform.validFrom ? new Date(uniform.validFrom) : undefined
|
|
232
|
+
const validUntil = uniform.validUntil ? new Date(uniform.validUntil) : undefined
|
|
233
|
+
const ttl = credSubject.ttl
|
|
234
|
+
const statuslistLength: number = BitstreamStatusList.getStatusListLength(encodedList, bitsPerStatus)
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
id,
|
|
238
|
+
encodedList,
|
|
239
|
+
issuer,
|
|
240
|
+
type: StatusListType.BitstringStatusList,
|
|
241
|
+
proofFormat,
|
|
242
|
+
length: statuslistLength,
|
|
243
|
+
statusListCredential: statusListPayload,
|
|
244
|
+
statuslistContentType: this.buildContentType(proofFormat),
|
|
245
|
+
bitstringStatusList: {
|
|
246
|
+
statusPurpose,
|
|
247
|
+
bitsPerStatus,
|
|
248
|
+
validFrom,
|
|
249
|
+
validUntil,
|
|
250
|
+
ttl,
|
|
251
|
+
},
|
|
252
|
+
...(args.correlationId && { correlationId: args.correlationId }),
|
|
253
|
+
...(args.driverType && { driverType: args.driverType }),
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private async createVerifiableCredential(
|
|
258
|
+
args: {
|
|
259
|
+
id: string
|
|
260
|
+
issuer: string | IIssuer
|
|
261
|
+
statusList?: BitstreamStatusList
|
|
262
|
+
proofFormat: VeramoProofFormat
|
|
263
|
+
statusPurpose: BitstringStatusPurpose
|
|
264
|
+
validFrom?: Date
|
|
265
|
+
validUntil?: Date
|
|
266
|
+
ttl?: number
|
|
267
|
+
keyRef?: string
|
|
268
|
+
},
|
|
269
|
+
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
270
|
+
): Promise<BitstringStatusListCredential> {
|
|
271
|
+
const identifier = await context.agent.identifierManagedGet({
|
|
272
|
+
identifier: typeof args.issuer === 'string' ? args.issuer : args.issuer.id,
|
|
273
|
+
vmRelationship: 'assertionMethod',
|
|
274
|
+
offlineWhenNoDIDRegistered: true,
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
const unsignedCredential = await createStatusListCredential(args)
|
|
278
|
+
|
|
279
|
+
const verifiableCredential = await context.agent.createVerifiableCredential({
|
|
280
|
+
credential: unsignedCredential,
|
|
281
|
+
keyRef: args.keyRef ?? identifier.kmsKeyRef,
|
|
282
|
+
proofFormat: args.proofFormat,
|
|
283
|
+
fetchRemoteContexts: true,
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
return CredentialMapper.toWrappedVerifiableCredential(verifiableCredential as StatusListCredential).original as BitstringStatusListCredential
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private buildContentType(proofFormat: CredentialProofFormat | undefined) {
|
|
290
|
+
switch (proofFormat) {
|
|
291
|
+
case 'jwt':
|
|
292
|
+
return `application/statuslist+jwt`
|
|
293
|
+
case 'cbor':
|
|
294
|
+
return `application/statuslist+cwt`
|
|
295
|
+
case 'lds':
|
|
296
|
+
return 'application/statuslist+ld+json'
|
|
297
|
+
default:
|
|
298
|
+
throw Error(`Unsupported content type '${proofFormat}' for status lists`)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
package/src/impl/IStatusList.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IAgentContext, ICredentialPlugin } from '@veramo/core'
|
|
2
2
|
import type { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
3
|
-
import
|
|
3
|
+
import {
|
|
4
|
+
BitstringStatus,
|
|
4
5
|
CheckStatusIndexArgs,
|
|
5
6
|
CreateStatusListArgs,
|
|
6
7
|
Status2021,
|
|
@@ -33,7 +34,7 @@ export interface IStatusList {
|
|
|
33
34
|
/**
|
|
34
35
|
* Checks the status at a given index in the status list
|
|
35
36
|
*/
|
|
36
|
-
checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | Status2021 | StatusOAuth>
|
|
37
|
+
checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | Status2021 | StatusOAuth | BitstringStatus>
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* Collects the status list details
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IAgentContext, ICredentialPlugin, IKeyManager } from '@veramo/core'
|
|
2
|
-
import { type CompactJWT, type
|
|
2
|
+
import { type CompactJWT, type CredentialProofFormat, type CWT, StatusListType } from '@sphereon/ssi-types'
|
|
3
3
|
import type {
|
|
4
4
|
CheckStatusIndexArgs,
|
|
5
5
|
CreateStatusListArgs,
|
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
UpdateStatusListFromEncodedListArgs,
|
|
11
11
|
UpdateStatusListIndexArgs,
|
|
12
12
|
} from '../types'
|
|
13
|
-
import { determineProofFormat, getAssertedValue, getAssertedValues } from '../utils'
|
|
13
|
+
import { determineProofFormat, ensureDate, getAssertedValue, getAssertedValues } from '../utils'
|
|
14
14
|
import type { IStatusList } from './IStatusList'
|
|
15
15
|
import { StatusList } from '@sd-jwt/jwt-status-list'
|
|
16
16
|
import type { IJwtService } from '@sphereon/ssi-sdk-ext.jwt-service'
|
|
@@ -20,7 +20,7 @@ import { createSignedCbor, decodeStatusListCWT } from './encoding/cbor'
|
|
|
20
20
|
|
|
21
21
|
type IRequiredContext = IAgentContext<ICredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>
|
|
22
22
|
|
|
23
|
-
export const DEFAULT_BITS_PER_STATUS = 1 // 1 bit is sufficient for 0x00 - "VALID"
|
|
23
|
+
export const DEFAULT_BITS_PER_STATUS = 1 // 1 bit is sufficient for 0x00 - "VALID" 0x01 - "INVALID" saving space in the process
|
|
24
24
|
export const DEFAULT_LIST_LENGTH = 250000
|
|
25
25
|
export const DEFAULT_PROOF_FORMAT = 'jwt' as CredentialProofFormat
|
|
26
26
|
|
|
@@ -32,7 +32,8 @@ export class OAuthStatusListImplementation implements IStatusList {
|
|
|
32
32
|
|
|
33
33
|
const proofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
|
|
34
34
|
const { issuer, id, oauthStatusList, keyRef } = args
|
|
35
|
-
const { bitsPerStatus
|
|
35
|
+
const { bitsPerStatus } = oauthStatusList
|
|
36
|
+
const expiresAt = ensureDate(oauthStatusList.expiresAt)
|
|
36
37
|
const length = args.length ?? DEFAULT_LIST_LENGTH
|
|
37
38
|
const issuerString = typeof issuer === 'string' ? issuer : issuer.id
|
|
38
39
|
const correlationId = getAssertedValue('correlationId', args.correlationId)
|
|
@@ -56,7 +57,8 @@ export class OAuthStatusListImplementation implements IStatusList {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
async updateStatusListIndex(args: UpdateStatusListIndexArgs, context: IRequiredContext): Promise<StatusListResult> {
|
|
59
|
-
const { statusListCredential, value,
|
|
60
|
+
const { statusListCredential, value, keyRef } = args
|
|
61
|
+
const expiresAt = ensureDate(args.expiresAt)
|
|
60
62
|
if (typeof statusListCredential !== 'string') {
|
|
61
63
|
return Promise.reject('statusListCredential in neither JWT nor CWT')
|
|
62
64
|
}
|
|
@@ -70,6 +72,10 @@ export class OAuthStatusListImplementation implements IStatusList {
|
|
|
70
72
|
throw new Error('Status list index out of bounds')
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
if (typeof value !== 'number') {
|
|
76
|
+
throw new Error('Status list values should be of type number')
|
|
77
|
+
}
|
|
78
|
+
|
|
73
79
|
statusList.setStatus(index, value)
|
|
74
80
|
const { statusListCredential: signedCredential, encodedList } = await this.createSignedStatusList(
|
|
75
81
|
proofFormat,
|
|
@@ -102,15 +108,15 @@ export class OAuthStatusListImplementation implements IStatusList {
|
|
|
102
108
|
throw new Error('OAuthStatusList options are required for type OAuthStatusList')
|
|
103
109
|
}
|
|
104
110
|
const { proofFormat, oauthStatusList, keyRef } = args
|
|
105
|
-
const { bitsPerStatus
|
|
111
|
+
const { bitsPerStatus } = oauthStatusList
|
|
112
|
+
const expiresAt = ensureDate(oauthStatusList.expiresAt)
|
|
106
113
|
|
|
107
114
|
const { issuer, id } = getAssertedValues(args)
|
|
108
115
|
const issuerString = typeof issuer === 'string' ? issuer : issuer.id
|
|
109
116
|
|
|
110
117
|
const listToUpdate = StatusList.decompressStatusList(args.encodedList, bitsPerStatus ?? DEFAULT_BITS_PER_STATUS)
|
|
111
118
|
const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
|
|
112
|
-
|
|
113
|
-
listToUpdate.setStatus(index, args.value ? 1 : 0)
|
|
119
|
+
listToUpdate.setStatus(index, args.value)
|
|
114
120
|
|
|
115
121
|
const { statusListCredential, encodedList } = await this.createSignedStatusList(
|
|
116
122
|
proofFormat ?? DEFAULT_PROOF_FORMAT,
|
|
@@ -138,7 +144,7 @@ export class OAuthStatusListImplementation implements IStatusList {
|
|
|
138
144
|
}
|
|
139
145
|
}
|
|
140
146
|
|
|
141
|
-
private buildContentType(proofFormat:
|
|
147
|
+
private buildContentType(proofFormat: CredentialProofFormat | undefined) {
|
|
142
148
|
return `application/statuslist+${proofFormat === 'cbor' ? 'cwt' : 'jwt'}`
|
|
143
149
|
}
|
|
144
150
|
|
|
@@ -153,7 +159,7 @@ 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)
|
|
@@ -184,7 +190,7 @@ export class OAuthStatusListImplementation implements IStatusList {
|
|
|
184
190
|
}
|
|
185
191
|
|
|
186
192
|
private async createSignedStatusList(
|
|
187
|
-
proofFormat:
|
|
193
|
+
proofFormat: CredentialProofFormat,
|
|
188
194
|
context: IAgentContext<ICredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>,
|
|
189
195
|
statusList: StatusList,
|
|
190
196
|
issuerString: string,
|
|
@@ -24,7 +24,7 @@ import { Status2021 } from '../types'
|
|
|
24
24
|
import { assertValidProofType, getAssertedProperty, getAssertedValue, getAssertedValues } from '../utils'
|
|
25
25
|
|
|
26
26
|
export const DEFAULT_LIST_LENGTH = 250000
|
|
27
|
-
export const DEFAULT_PROOF_FORMAT = 'lds' as
|
|
27
|
+
export const DEFAULT_PROOF_FORMAT = 'lds' as CredentialProofFormat
|
|
28
28
|
|
|
29
29
|
export class StatusList2021Implementation implements IStatusList {
|
|
30
30
|
async createNewStatusList(
|
|
@@ -126,7 +126,7 @@ export class StatusList2021Implementation implements IStatusList {
|
|
|
126
126
|
const { issuer, id } = getAssertedValues(args)
|
|
127
127
|
const statusList = await StatusList.decode({ encodedList: args.encodedList })
|
|
128
128
|
const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
|
|
129
|
-
statusList.setStatus(index, args.value)
|
|
129
|
+
statusList.setStatus(index, args.value !== 0)
|
|
130
130
|
|
|
131
131
|
const newEncodedList = await statusList.encode()
|
|
132
132
|
const credential = await this.createVerifiableCredential(
|
|
@@ -234,7 +234,7 @@ export class StatusList2021Implementation implements IStatusList {
|
|
|
234
234
|
return CredentialMapper.toWrappedVerifiableCredential(verifiableCredential as StatusListCredential).original as StatusListCredential
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
private buildContentType(proofFormat:
|
|
237
|
+
private buildContentType(proofFormat: CredentialProofFormat | undefined) {
|
|
238
238
|
switch (proofFormat) {
|
|
239
239
|
case 'jwt':
|
|
240
240
|
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 {
|
package/src/types/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
2
2
|
import {
|
|
3
|
+
type CredentialProofFormat,
|
|
3
4
|
type ICredential,
|
|
4
5
|
type ICredentialStatus,
|
|
5
6
|
type IIssuer,
|
|
6
7
|
type IVerifiableCredential,
|
|
7
8
|
type OrPromise,
|
|
8
|
-
type CredentialProofFormat,
|
|
9
9
|
type StatusListCredential,
|
|
10
10
|
StatusListCredentialIdMode,
|
|
11
11
|
StatusListDriverType,
|
|
@@ -26,6 +26,7 @@ import { DataSource } from 'typeorm'
|
|
|
26
26
|
import type { BitsPerStatus } from '@sd-jwt/jwt-status-list'
|
|
27
27
|
import type { SdJwtVcPayload } from '@sd-jwt/sd-jwt-vc'
|
|
28
28
|
import type { StatusListOpts } from '@sphereon/oid4vci-common'
|
|
29
|
+
import { BitstringStatusPurpose } from '@4sure-tech/vc-bitstring-status-lists'
|
|
29
30
|
|
|
30
31
|
export enum StatusOAuth {
|
|
31
32
|
Valid = 0,
|
|
@@ -38,6 +39,8 @@ export enum Status2021 {
|
|
|
38
39
|
Invalid = 1,
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
export type BitstringStatus = number
|
|
43
|
+
|
|
41
44
|
export type StatusList2021Args = {
|
|
42
45
|
indexingDirection: StatusListIndexingDirection
|
|
43
46
|
statusPurpose?: StatusPurpose2021
|
|
@@ -49,6 +52,14 @@ export type OAuthStatusListArgs = {
|
|
|
49
52
|
expiresAt?: Date
|
|
50
53
|
}
|
|
51
54
|
|
|
55
|
+
export type BitstringStatusListArgs = {
|
|
56
|
+
statusPurpose: BitstringStatusPurpose
|
|
57
|
+
bitsPerStatus: number
|
|
58
|
+
ttl?: number
|
|
59
|
+
validFrom?: Date
|
|
60
|
+
validUntil?: Date
|
|
61
|
+
}
|
|
62
|
+
|
|
52
63
|
export type BaseCreateNewStatusListArgs = {
|
|
53
64
|
type: StatusListType
|
|
54
65
|
id: string
|
|
@@ -59,6 +70,7 @@ export type BaseCreateNewStatusListArgs = {
|
|
|
59
70
|
keyRef?: string
|
|
60
71
|
statusList2021?: StatusList2021Args
|
|
61
72
|
oauthStatusList?: OAuthStatusListArgs
|
|
73
|
+
bitstringStatusList?: BitstringStatusListArgs
|
|
62
74
|
driverType?: StatusListDriverType
|
|
63
75
|
}
|
|
64
76
|
|
|
@@ -71,10 +83,18 @@ export type UpdateOAuthStatusListArgs = {
|
|
|
71
83
|
expiresAt?: Date
|
|
72
84
|
}
|
|
73
85
|
|
|
86
|
+
export type UpdateBitstringStatusListArgs = {
|
|
87
|
+
statusPurpose: BitstringStatusPurpose
|
|
88
|
+
bitsPerStatus: number
|
|
89
|
+
validFrom?: Date
|
|
90
|
+
validUntil?: Date
|
|
91
|
+
ttl?: number
|
|
92
|
+
}
|
|
93
|
+
|
|
74
94
|
export interface UpdateStatusListFromEncodedListArgs {
|
|
75
95
|
type?: StatusListType
|
|
76
96
|
statusListIndex: number | string
|
|
77
|
-
value:
|
|
97
|
+
value: number
|
|
78
98
|
proofFormat?: CredentialProofFormat
|
|
79
99
|
keyRef?: string
|
|
80
100
|
correlationId?: string
|
|
@@ -83,13 +103,14 @@ export interface UpdateStatusListFromEncodedListArgs {
|
|
|
83
103
|
id: string
|
|
84
104
|
statusList2021?: UpdateStatusList2021Args
|
|
85
105
|
oauthStatusList?: UpdateOAuthStatusListArgs
|
|
106
|
+
bitstringStatusList?: UpdateBitstringStatusListArgs
|
|
86
107
|
}
|
|
87
108
|
|
|
88
109
|
export interface UpdateStatusListFromStatusListCredentialArgs {
|
|
89
110
|
statusListCredential: StatusListCredential // | CompactJWT
|
|
90
111
|
keyRef?: string
|
|
91
112
|
statusListIndex: number | string
|
|
92
|
-
value: number | Status2021 | StatusOAuth
|
|
113
|
+
value: number | Status2021 | StatusOAuth | BitstringStatus
|
|
93
114
|
}
|
|
94
115
|
|
|
95
116
|
export interface StatusListResult {
|
|
@@ -103,6 +124,7 @@ export interface StatusListResult {
|
|
|
103
124
|
issuer: string | IIssuer
|
|
104
125
|
statusList2021?: StatusList2021Details
|
|
105
126
|
oauthStatusList?: OAuthStatusDetails
|
|
127
|
+
bitstringStatusList?: BitstringStatusDetails
|
|
106
128
|
|
|
107
129
|
// These cannot be deduced from the VC, so they are present when callers pass in these values as params
|
|
108
130
|
correlationId?: string
|
|
@@ -120,6 +142,14 @@ interface OAuthStatusDetails {
|
|
|
120
142
|
expiresAt?: Date
|
|
121
143
|
}
|
|
122
144
|
|
|
145
|
+
interface BitstringStatusDetails {
|
|
146
|
+
statusPurpose: BitstringStatusPurpose
|
|
147
|
+
bitsPerStatus: number
|
|
148
|
+
validFrom?: Date
|
|
149
|
+
validUntil?: Date
|
|
150
|
+
ttl?: number
|
|
151
|
+
}
|
|
152
|
+
|
|
123
153
|
export interface StatusList2021EntryCredentialStatus extends ICredentialStatus {
|
|
124
154
|
type: 'StatusList2021Entry'
|
|
125
155
|
statusPurpose: StatusPurpose2021
|
|
@@ -135,6 +165,16 @@ export interface StatusListOAuthEntryCredentialStatus extends ICredentialStatus
|
|
|
135
165
|
expiresAt?: Date
|
|
136
166
|
}
|
|
137
167
|
|
|
168
|
+
export interface BitstringStatusListEntryCredentialStatus extends ICredentialStatus {
|
|
169
|
+
type: 'BitstringStatusListEntry'
|
|
170
|
+
statusPurpose: BitstringStatusPurpose | BitstringStatusPurpose[]
|
|
171
|
+
statusListIndex: string
|
|
172
|
+
statusListCredential: string
|
|
173
|
+
bitsPerStatus?: number
|
|
174
|
+
statusMessage?: Array<BitstringStatus>
|
|
175
|
+
statusReference?: string | string[]
|
|
176
|
+
}
|
|
177
|
+
|
|
138
178
|
export interface StatusList2021ToVerifiableCredentialArgs {
|
|
139
179
|
issuer: string | IIssuer
|
|
140
180
|
id: string
|
|
@@ -154,12 +194,14 @@ export interface CreateStatusListArgs {
|
|
|
154
194
|
length?: number
|
|
155
195
|
statusList2021?: StatusList2021Args
|
|
156
196
|
oauthStatusList?: OAuthStatusListArgs
|
|
197
|
+
bitstringStatusList?: BitstringStatusListArgs
|
|
157
198
|
}
|
|
158
199
|
|
|
159
200
|
export interface UpdateStatusListIndexArgs {
|
|
160
201
|
statusListCredential: StatusListCredential // | CompactJWT
|
|
161
202
|
statusListIndex: number | string
|
|
162
|
-
value: number | Status2021 | StatusOAuth
|
|
203
|
+
value: number | Status2021 | StatusOAuth | BitstringStatus
|
|
204
|
+
bitsPerStatus?: number
|
|
163
205
|
keyRef?: string
|
|
164
206
|
expiresAt?: Date
|
|
165
207
|
}
|
|
@@ -167,12 +209,14 @@ export interface UpdateStatusListIndexArgs {
|
|
|
167
209
|
export interface CheckStatusIndexArgs {
|
|
168
210
|
statusListCredential: StatusListCredential // | CompactJWT
|
|
169
211
|
statusListIndex: string | number
|
|
212
|
+
bitsPerStatus?: number
|
|
170
213
|
}
|
|
171
214
|
|
|
172
215
|
export interface ToStatusListDetailsArgs {
|
|
173
216
|
statusListPayload: StatusListCredential
|
|
174
217
|
correlationId?: string
|
|
175
218
|
driverType?: StatusListDriverType
|
|
219
|
+
bitsPerStatus?: number
|
|
176
220
|
}
|
|
177
221
|
|
|
178
222
|
/**
|
package/src/utils.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { jwtDecode } from 'jwt-decode'
|
|
|
11
11
|
|
|
12
12
|
export function getAssertedStatusListType(type?: StatusListType) {
|
|
13
13
|
const assertedType = type ?? StatusListType.StatusList2021
|
|
14
|
-
if (![StatusListType.StatusList2021, StatusListType.OAuthStatusList].includes(assertedType)) {
|
|
14
|
+
if (![StatusListType.StatusList2021, StatusListType.OAuthStatusList, StatusListType.BitstringStatusList].includes(assertedType)) {
|
|
15
15
|
throw Error(`StatusList type ${assertedType} is not supported (yet)`)
|
|
16
16
|
}
|
|
17
17
|
return assertedType
|
|
@@ -39,8 +39,9 @@ export function getAssertedProperty<T extends object>(propertyName: string, obj:
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const ValidProofTypeMap = new Map<StatusListType, CredentialProofFormat[]>([
|
|
42
|
-
[StatusListType.StatusList2021, ['jwt', 'lds'
|
|
42
|
+
[StatusListType.StatusList2021, ['jwt', 'lds']],
|
|
43
43
|
[StatusListType.OAuthStatusList, ['jwt', 'cbor']],
|
|
44
|
+
[StatusListType.BitstringStatusList, ['lds']],
|
|
44
45
|
])
|
|
45
46
|
|
|
46
47
|
export function assertValidProofType(type: StatusListType, proofFormat: CredentialProofFormat) {
|
|
@@ -93,3 +94,35 @@ export function determineProofFormat(credential: StatusListCredential): Credenti
|
|
|
93
94
|
throw Error('Cannot determine credential payload type')
|
|
94
95
|
}
|
|
95
96
|
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Ensures a value is converted to a Date object if it's a valid date string,
|
|
100
|
+
* otherwise returns the original value or undefined
|
|
101
|
+
*
|
|
102
|
+
* @param value - The value to convert to Date (can be Date, string, or undefined)
|
|
103
|
+
* @returns Date object, undefined, or the original value if conversion fails
|
|
104
|
+
*/
|
|
105
|
+
export function ensureDate(value: Date | string | undefined): Date | undefined {
|
|
106
|
+
if (value === undefined || value === null) {
|
|
107
|
+
return undefined
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (value instanceof Date) {
|
|
111
|
+
return value
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (typeof value === 'string') {
|
|
115
|
+
if (value.trim() === '') {
|
|
116
|
+
return undefined
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const date = new Date(value)
|
|
120
|
+
// Check if the date is valid
|
|
121
|
+
if (isNaN(date.getTime())) {
|
|
122
|
+
return undefined
|
|
123
|
+
}
|
|
124
|
+
return date
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return undefined
|
|
128
|
+
}
|