@sphereon/ssi-sdk.vc-status-list 0.32.1-next.54 → 0.33.1-feature.jose.vcdm.55
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 +979 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +275 -0
- package/dist/index.d.ts +273 -3
- package/dist/index.js +946 -19
- package/dist/index.js.map +1 -1
- package/package.json +36 -14
- package/src/functions.ts +77 -161
- package/src/impl/IStatusList.ts +42 -0
- package/src/impl/OAuthStatusList.ts +206 -0
- package/src/impl/StatusList2021.ts +249 -0
- package/src/impl/StatusListFactory.ts +34 -0
- package/src/impl/encoding/cbor.ts +177 -0
- package/src/impl/encoding/common.ts +20 -0
- package/src/impl/encoding/jwt.ts +80 -0
- package/src/types/index.ts +162 -48
- package/src/utils.ts +95 -0
- package/dist/functions.d.ts +0 -69
- package/dist/functions.d.ts.map +0 -1
- package/dist/functions.js +0 -256
- package/dist/functions.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/types/index.d.ts +0 -111
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +0 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { IAgentContext, ICredentialPlugin } from '@veramo/core'
|
|
2
|
+
import type { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
3
|
+
import type {
|
|
4
|
+
CheckStatusIndexArgs,
|
|
5
|
+
CreateStatusListArgs,
|
|
6
|
+
Status2021,
|
|
7
|
+
StatusListResult,
|
|
8
|
+
StatusOAuth,
|
|
9
|
+
ToStatusListDetailsArgs,
|
|
10
|
+
UpdateStatusListFromEncodedListArgs,
|
|
11
|
+
UpdateStatusListIndexArgs,
|
|
12
|
+
} from '../types'
|
|
13
|
+
|
|
14
|
+
export interface IStatusList {
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new status list of the specific type
|
|
17
|
+
*/
|
|
18
|
+
createNewStatusList(args: CreateStatusListArgs, context: IAgentContext<ICredentialPlugin & IIdentifierResolution>): Promise<StatusListResult>
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Updates a status at the given index in the status list
|
|
22
|
+
*/
|
|
23
|
+
updateStatusListIndex(args: UpdateStatusListIndexArgs, context: IAgentContext<ICredentialPlugin & IIdentifierResolution>): Promise<StatusListResult>
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Updates a status list using a base64 encoded list of statuses
|
|
27
|
+
*/
|
|
28
|
+
updateStatusListFromEncodedList(
|
|
29
|
+
args: UpdateStatusListFromEncodedListArgs,
|
|
30
|
+
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
31
|
+
): Promise<StatusListResult>
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Checks the status at a given index in the status list
|
|
35
|
+
*/
|
|
36
|
+
checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | Status2021 | StatusOAuth>
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Collects the status list details
|
|
40
|
+
*/
|
|
41
|
+
toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult>
|
|
42
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { IAgentContext, ICredentialPlugin, IKeyManager } from '@veramo/core'
|
|
2
|
+
import { type CompactJWT, type CWT, type CredentialProofFormat, StatusListType } from '@sphereon/ssi-types'
|
|
3
|
+
import type {
|
|
4
|
+
CheckStatusIndexArgs,
|
|
5
|
+
CreateStatusListArgs,
|
|
6
|
+
SignedStatusListData,
|
|
7
|
+
StatusListResult,
|
|
8
|
+
StatusOAuth,
|
|
9
|
+
ToStatusListDetailsArgs,
|
|
10
|
+
UpdateStatusListFromEncodedListArgs,
|
|
11
|
+
UpdateStatusListIndexArgs,
|
|
12
|
+
} from '../types'
|
|
13
|
+
import { determineProofFormat, getAssertedValue, getAssertedValues } from '../utils'
|
|
14
|
+
import type { IStatusList } from './IStatusList'
|
|
15
|
+
import { StatusList } from '@sd-jwt/jwt-status-list'
|
|
16
|
+
import type { IJwtService } from '@sphereon/ssi-sdk-ext.jwt-service'
|
|
17
|
+
import type { IIdentifierResolution } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
18
|
+
import { createSignedJwt, decodeStatusListJWT } from './encoding/jwt'
|
|
19
|
+
import { createSignedCbor, decodeStatusListCWT } from './encoding/cbor'
|
|
20
|
+
|
|
21
|
+
type IRequiredContext = IAgentContext<ICredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>
|
|
22
|
+
|
|
23
|
+
export const DEFAULT_BITS_PER_STATUS = 1 // 1 bit is sufficient for 0x00 - "VALID" 0x01 - "INVALID" saving space in the process
|
|
24
|
+
export const DEFAULT_LIST_LENGTH = 250000
|
|
25
|
+
export const DEFAULT_PROOF_FORMAT = 'jwt' as CredentialProofFormat
|
|
26
|
+
|
|
27
|
+
export class OAuthStatusListImplementation implements IStatusList {
|
|
28
|
+
async createNewStatusList(args: CreateStatusListArgs, context: IRequiredContext): Promise<StatusListResult> {
|
|
29
|
+
if (!args.oauthStatusList) {
|
|
30
|
+
throw new Error('OAuthStatusList options are required for type OAuthStatusList')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const proofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
|
|
34
|
+
const { issuer, id, oauthStatusList, keyRef } = args
|
|
35
|
+
const { bitsPerStatus, expiresAt } = oauthStatusList
|
|
36
|
+
const length = args.length ?? DEFAULT_LIST_LENGTH
|
|
37
|
+
const issuerString = typeof issuer === 'string' ? issuer : issuer.id
|
|
38
|
+
const correlationId = getAssertedValue('correlationId', args.correlationId)
|
|
39
|
+
|
|
40
|
+
const statusList = new StatusList(new Array(length).fill(0), bitsPerStatus ?? DEFAULT_BITS_PER_STATUS)
|
|
41
|
+
const encodedList = statusList.compressStatusList()
|
|
42
|
+
const { statusListCredential } = await this.createSignedStatusList(proofFormat, context, statusList, issuerString, id, expiresAt, keyRef)
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
encodedList,
|
|
46
|
+
statusListCredential,
|
|
47
|
+
oauthStatusList: { bitsPerStatus },
|
|
48
|
+
length,
|
|
49
|
+
type: StatusListType.OAuthStatusList,
|
|
50
|
+
proofFormat,
|
|
51
|
+
id,
|
|
52
|
+
correlationId,
|
|
53
|
+
issuer,
|
|
54
|
+
statuslistContentType: this.buildContentType(proofFormat),
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async updateStatusListIndex(args: UpdateStatusListIndexArgs, context: IRequiredContext): Promise<StatusListResult> {
|
|
59
|
+
const { statusListCredential, value, expiresAt, keyRef } = args
|
|
60
|
+
if (typeof statusListCredential !== 'string') {
|
|
61
|
+
return Promise.reject('statusListCredential in neither JWT nor CWT')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const proofFormat = determineProofFormat(statusListCredential)
|
|
65
|
+
const decoded = proofFormat === 'jwt' ? decodeStatusListJWT(statusListCredential) : decodeStatusListCWT(statusListCredential)
|
|
66
|
+
const { statusList, issuer, id } = decoded
|
|
67
|
+
|
|
68
|
+
const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
|
|
69
|
+
if (index < 0 || index >= statusList.statusList.length) {
|
|
70
|
+
throw new Error('Status list index out of bounds')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
statusList.setStatus(index, value)
|
|
74
|
+
const { statusListCredential: signedCredential, encodedList } = await this.createSignedStatusList(
|
|
75
|
+
proofFormat,
|
|
76
|
+
context,
|
|
77
|
+
statusList,
|
|
78
|
+
issuer,
|
|
79
|
+
id,
|
|
80
|
+
expiresAt,
|
|
81
|
+
keyRef,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
statusListCredential: signedCredential,
|
|
86
|
+
encodedList,
|
|
87
|
+
oauthStatusList: {
|
|
88
|
+
bitsPerStatus: statusList.getBitsPerStatus(),
|
|
89
|
+
},
|
|
90
|
+
length: statusList.statusList.length,
|
|
91
|
+
type: StatusListType.OAuthStatusList,
|
|
92
|
+
proofFormat,
|
|
93
|
+
id,
|
|
94
|
+
issuer,
|
|
95
|
+
statuslistContentType: this.buildContentType(proofFormat),
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// FIXME: This still assumes only two values (boolean), whilst this list supports 8 bits max
|
|
100
|
+
async updateStatusListFromEncodedList(args: UpdateStatusListFromEncodedListArgs, context: IRequiredContext): Promise<StatusListResult> {
|
|
101
|
+
if (!args.oauthStatusList) {
|
|
102
|
+
throw new Error('OAuthStatusList options are required for type OAuthStatusList')
|
|
103
|
+
}
|
|
104
|
+
const { proofFormat, oauthStatusList, keyRef } = args
|
|
105
|
+
const { bitsPerStatus, expiresAt } = oauthStatusList
|
|
106
|
+
|
|
107
|
+
const { issuer, id } = getAssertedValues(args)
|
|
108
|
+
const issuerString = typeof issuer === 'string' ? issuer : issuer.id
|
|
109
|
+
|
|
110
|
+
const listToUpdate = StatusList.decompressStatusList(args.encodedList, bitsPerStatus ?? DEFAULT_BITS_PER_STATUS)
|
|
111
|
+
const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
|
|
112
|
+
// FIXME: See above.
|
|
113
|
+
listToUpdate.setStatus(index, args.value ? 1 : 0)
|
|
114
|
+
|
|
115
|
+
const { statusListCredential, encodedList } = await this.createSignedStatusList(
|
|
116
|
+
proofFormat ?? DEFAULT_PROOF_FORMAT,
|
|
117
|
+
context,
|
|
118
|
+
listToUpdate,
|
|
119
|
+
issuerString,
|
|
120
|
+
id,
|
|
121
|
+
expiresAt,
|
|
122
|
+
keyRef,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
encodedList,
|
|
127
|
+
statusListCredential,
|
|
128
|
+
oauthStatusList: {
|
|
129
|
+
bitsPerStatus,
|
|
130
|
+
expiresAt,
|
|
131
|
+
},
|
|
132
|
+
length: listToUpdate.statusList.length,
|
|
133
|
+
type: StatusListType.OAuthStatusList,
|
|
134
|
+
proofFormat: proofFormat ?? DEFAULT_PROOF_FORMAT,
|
|
135
|
+
id,
|
|
136
|
+
issuer,
|
|
137
|
+
statuslistContentType: this.buildContentType(proofFormat),
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private buildContentType(proofFormat: 'jwt' | 'lds' | 'EthereumEip712Signature2021' | 'cbor' | undefined) {
|
|
142
|
+
return `application/statuslist+${proofFormat === 'cbor' ? 'cwt' : 'jwt'}`
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | StatusOAuth> {
|
|
146
|
+
const { statusListCredential, statusListIndex } = args
|
|
147
|
+
if (typeof statusListCredential !== 'string') {
|
|
148
|
+
return Promise.reject('statusListCredential in neither JWT nor CWT')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const proofFormat = determineProofFormat(statusListCredential)
|
|
152
|
+
const { statusList } = proofFormat === 'jwt' ? decodeStatusListJWT(statusListCredential) : decodeStatusListCWT(statusListCredential)
|
|
153
|
+
|
|
154
|
+
const index = typeof statusListIndex === 'number' ? statusListIndex : parseInt(statusListIndex)
|
|
155
|
+
if (index < 0 || index >= statusList.statusList.length) {
|
|
156
|
+
throw new Error('Status list index out of bounds')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return statusList.getStatus(index)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult> {
|
|
163
|
+
const { statusListPayload } = args as { statusListPayload: CompactJWT | CWT }
|
|
164
|
+
const proofFormat = determineProofFormat(statusListPayload)
|
|
165
|
+
const decoded = proofFormat === 'jwt' ? decodeStatusListJWT(statusListPayload) : decodeStatusListCWT(statusListPayload)
|
|
166
|
+
const { statusList, issuer, id, exp } = decoded
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
id,
|
|
170
|
+
encodedList: statusList.compressStatusList(),
|
|
171
|
+
issuer,
|
|
172
|
+
type: StatusListType.OAuthStatusList,
|
|
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 }),
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async createSignedStatusList(
|
|
187
|
+
proofFormat: 'jwt' | 'lds' | 'EthereumEip712Signature2021' | 'cbor',
|
|
188
|
+
context: IAgentContext<ICredentialPlugin & IJwtService & IIdentifierResolution & IKeyManager>,
|
|
189
|
+
statusList: StatusList,
|
|
190
|
+
issuerString: string,
|
|
191
|
+
id: string,
|
|
192
|
+
expiresAt?: Date,
|
|
193
|
+
keyRef?: string,
|
|
194
|
+
): Promise<SignedStatusListData> {
|
|
195
|
+
switch (proofFormat) {
|
|
196
|
+
case 'jwt': {
|
|
197
|
+
return await createSignedJwt(context, statusList, issuerString, id, expiresAt, keyRef)
|
|
198
|
+
}
|
|
199
|
+
case 'cbor': {
|
|
200
|
+
return await createSignedCbor(context, statusList, issuerString, id, expiresAt, keyRef)
|
|
201
|
+
}
|
|
202
|
+
default:
|
|
203
|
+
throw new Error(`Invalid proof format '${proofFormat}' for OAuthStatusList`)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
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
|
+
DocumentFormat,
|
|
6
|
+
type IIssuer,
|
|
7
|
+
type CredentialProofFormat,
|
|
8
|
+
type StatusListCredential,
|
|
9
|
+
StatusListType,
|
|
10
|
+
} from '@sphereon/ssi-types'
|
|
11
|
+
|
|
12
|
+
import { StatusList } from '@sphereon/vc-status-list'
|
|
13
|
+
import type { IStatusList } from './IStatusList'
|
|
14
|
+
import type {
|
|
15
|
+
CheckStatusIndexArgs,
|
|
16
|
+
CreateStatusListArgs,
|
|
17
|
+
StatusListResult,
|
|
18
|
+
ToStatusListDetailsArgs,
|
|
19
|
+
UpdateStatusListFromEncodedListArgs,
|
|
20
|
+
UpdateStatusListIndexArgs,
|
|
21
|
+
} from '../types'
|
|
22
|
+
|
|
23
|
+
import { Status2021 } from '../types'
|
|
24
|
+
import { assertValidProofType, getAssertedProperty, getAssertedValue, getAssertedValues } from '../utils'
|
|
25
|
+
|
|
26
|
+
export const DEFAULT_LIST_LENGTH = 250000
|
|
27
|
+
export const DEFAULT_PROOF_FORMAT = 'lds' as VeramoProofFormat
|
|
28
|
+
|
|
29
|
+
export class StatusList2021Implementation implements IStatusList {
|
|
30
|
+
async createNewStatusList(
|
|
31
|
+
args: CreateStatusListArgs,
|
|
32
|
+
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
33
|
+
): Promise<StatusListResult> {
|
|
34
|
+
const length = args?.length ?? DEFAULT_LIST_LENGTH
|
|
35
|
+
const proofFormat: CredentialProofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
|
|
36
|
+
assertValidProofType(StatusListType.StatusList2021, proofFormat)
|
|
37
|
+
const veramoProofFormat: VeramoProofFormat = proofFormat as VeramoProofFormat
|
|
38
|
+
|
|
39
|
+
const { issuer, id } = args
|
|
40
|
+
const correlationId = getAssertedValue('correlationId', args.correlationId)
|
|
41
|
+
|
|
42
|
+
const list = new StatusList({ length })
|
|
43
|
+
const encodedList = await list.encode()
|
|
44
|
+
const statusPurpose = 'revocation'
|
|
45
|
+
|
|
46
|
+
const statusListCredential = await this.createVerifiableCredential(
|
|
47
|
+
{
|
|
48
|
+
...args,
|
|
49
|
+
encodedList,
|
|
50
|
+
proofFormat: veramoProofFormat,
|
|
51
|
+
},
|
|
52
|
+
context,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
encodedList,
|
|
57
|
+
statusListCredential: statusListCredential,
|
|
58
|
+
statusList2021: {
|
|
59
|
+
statusPurpose,
|
|
60
|
+
indexingDirection: 'rightToLeft',
|
|
61
|
+
},
|
|
62
|
+
length,
|
|
63
|
+
type: StatusListType.StatusList2021,
|
|
64
|
+
proofFormat,
|
|
65
|
+
id,
|
|
66
|
+
correlationId,
|
|
67
|
+
issuer,
|
|
68
|
+
statuslistContentType: this.buildContentType(proofFormat),
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async updateStatusListIndex(
|
|
73
|
+
args: UpdateStatusListIndexArgs,
|
|
74
|
+
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
75
|
+
): Promise<StatusListResult> {
|
|
76
|
+
const credential = args.statusListCredential
|
|
77
|
+
const uniform = CredentialMapper.toUniformCredential(credential)
|
|
78
|
+
const { issuer, credentialSubject } = uniform
|
|
79
|
+
const id = getAssertedValue('id', uniform.id)
|
|
80
|
+
const origEncodedList = getAssertedProperty('encodedList', credentialSubject)
|
|
81
|
+
|
|
82
|
+
const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
|
|
83
|
+
const statusList = await StatusList.decode({ encodedList: origEncodedList })
|
|
84
|
+
statusList.setStatus(index, args.value != 0)
|
|
85
|
+
const encodedList = await statusList.encode()
|
|
86
|
+
|
|
87
|
+
const proofFormat = CredentialMapper.detectDocumentType(credential) === DocumentFormat.JWT ? 'jwt' : 'lds'
|
|
88
|
+
const updatedCredential = await this.createVerifiableCredential(
|
|
89
|
+
{
|
|
90
|
+
...args,
|
|
91
|
+
id,
|
|
92
|
+
issuer,
|
|
93
|
+
encodedList,
|
|
94
|
+
proofFormat: proofFormat,
|
|
95
|
+
},
|
|
96
|
+
context,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
statusListCredential: updatedCredential,
|
|
101
|
+
encodedList,
|
|
102
|
+
statusList2021: {
|
|
103
|
+
...('statusPurpose' in credentialSubject ? { statusPurpose: credentialSubject.statusPurpose } : {}),
|
|
104
|
+
indexingDirection: 'rightToLeft',
|
|
105
|
+
},
|
|
106
|
+
length: statusList.length - 1,
|
|
107
|
+
type: StatusListType.StatusList2021,
|
|
108
|
+
proofFormat: proofFormat,
|
|
109
|
+
id,
|
|
110
|
+
issuer,
|
|
111
|
+
statuslistContentType: this.buildContentType(proofFormat),
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async updateStatusListFromEncodedList(
|
|
116
|
+
args: UpdateStatusListFromEncodedListArgs,
|
|
117
|
+
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
118
|
+
): Promise<StatusListResult> {
|
|
119
|
+
if (!args.statusList2021) {
|
|
120
|
+
throw new Error('statusList2021 options required for type StatusList2021')
|
|
121
|
+
}
|
|
122
|
+
const proofFormat: CredentialProofFormat = args?.proofFormat ?? DEFAULT_PROOF_FORMAT
|
|
123
|
+
assertValidProofType(StatusListType.StatusList2021, proofFormat)
|
|
124
|
+
const veramoProofFormat: VeramoProofFormat = proofFormat as VeramoProofFormat
|
|
125
|
+
|
|
126
|
+
const { issuer, id } = getAssertedValues(args)
|
|
127
|
+
const statusList = await StatusList.decode({ encodedList: args.encodedList })
|
|
128
|
+
const index = typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex)
|
|
129
|
+
statusList.setStatus(index, args.value)
|
|
130
|
+
|
|
131
|
+
const newEncodedList = await statusList.encode()
|
|
132
|
+
const credential = await this.createVerifiableCredential(
|
|
133
|
+
{
|
|
134
|
+
id,
|
|
135
|
+
issuer,
|
|
136
|
+
encodedList: newEncodedList,
|
|
137
|
+
proofFormat: veramoProofFormat,
|
|
138
|
+
keyRef: args.keyRef,
|
|
139
|
+
},
|
|
140
|
+
context,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
type: StatusListType.StatusList2021,
|
|
145
|
+
statusListCredential: credential,
|
|
146
|
+
encodedList: newEncodedList,
|
|
147
|
+
statusList2021: {
|
|
148
|
+
statusPurpose: args.statusList2021.statusPurpose,
|
|
149
|
+
indexingDirection: 'rightToLeft',
|
|
150
|
+
},
|
|
151
|
+
length: statusList.length,
|
|
152
|
+
proofFormat: args.proofFormat ?? 'lds',
|
|
153
|
+
id: id,
|
|
154
|
+
issuer: issuer,
|
|
155
|
+
statuslistContentType: this.buildContentType(proofFormat),
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async checkStatusIndex(args: CheckStatusIndexArgs): Promise<number | Status2021> {
|
|
160
|
+
const uniform = CredentialMapper.toUniformCredential(args.statusListCredential)
|
|
161
|
+
const { credentialSubject } = uniform
|
|
162
|
+
const encodedList = getAssertedProperty('encodedList', credentialSubject)
|
|
163
|
+
|
|
164
|
+
const statusList = await StatusList.decode({ encodedList })
|
|
165
|
+
const status = statusList.getStatus(typeof args.statusListIndex === 'number' ? args.statusListIndex : parseInt(args.statusListIndex))
|
|
166
|
+
return status ? Status2021.Invalid : Status2021.Valid
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async toStatusListDetails(args: ToStatusListDetailsArgs): Promise<StatusListResult> {
|
|
170
|
+
const { statusListPayload } = args
|
|
171
|
+
const uniform = CredentialMapper.toUniformCredential(statusListPayload)
|
|
172
|
+
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 })
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
id,
|
|
182
|
+
encodedList,
|
|
183
|
+
issuer,
|
|
184
|
+
type: StatusListType.StatusList2021,
|
|
185
|
+
proofFormat,
|
|
186
|
+
length: list.length,
|
|
187
|
+
statusListCredential: statusListPayload,
|
|
188
|
+
statuslistContentType: this.buildContentType(proofFormat),
|
|
189
|
+
statusList2021: {
|
|
190
|
+
indexingDirection: 'rightToLeft',
|
|
191
|
+
statusPurpose,
|
|
192
|
+
},
|
|
193
|
+
...(args.correlationId && { correlationId: args.correlationId }),
|
|
194
|
+
...(args.driverType && { driverType: args.driverType }),
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async createVerifiableCredential(
|
|
199
|
+
args: {
|
|
200
|
+
id: string
|
|
201
|
+
issuer: string | IIssuer
|
|
202
|
+
encodedList: string
|
|
203
|
+
proofFormat: VeramoProofFormat
|
|
204
|
+
keyRef?: string
|
|
205
|
+
},
|
|
206
|
+
context: IAgentContext<ICredentialPlugin & IIdentifierResolution>,
|
|
207
|
+
): Promise<StatusListCredential> {
|
|
208
|
+
const identifier = await context.agent.identifierManagedGet({
|
|
209
|
+
identifier: typeof args.issuer === 'string' ? args.issuer : args.issuer.id,
|
|
210
|
+
vmRelationship: 'assertionMethod',
|
|
211
|
+
offlineWhenNoDIDRegistered: true,
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
const credential = {
|
|
215
|
+
'@context': ['https://www.w3.org/2018/credentials/v1', 'https://w3id.org/vc/status-list/2021/v1'],
|
|
216
|
+
id: args.id,
|
|
217
|
+
issuer: args.issuer,
|
|
218
|
+
type: ['VerifiableCredential', 'StatusList2021Credential'],
|
|
219
|
+
credentialSubject: {
|
|
220
|
+
id: args.id,
|
|
221
|
+
type: 'StatusList2021',
|
|
222
|
+
statusPurpose: 'revocation',
|
|
223
|
+
encodedList: args.encodedList,
|
|
224
|
+
},
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const verifiableCredential = await context.agent.createVerifiableCredential({
|
|
228
|
+
credential,
|
|
229
|
+
keyRef: args.keyRef ?? identifier.kmsKeyRef,
|
|
230
|
+
proofFormat: args.proofFormat,
|
|
231
|
+
fetchRemoteContexts: true,
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
return CredentialMapper.toWrappedVerifiableCredential(verifiableCredential as StatusListCredential).original as StatusListCredential
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private buildContentType(proofFormat: 'jwt' | 'lds' | 'EthereumEip712Signature2021' | 'cbor' | undefined) {
|
|
238
|
+
switch (proofFormat) {
|
|
239
|
+
case 'jwt':
|
|
240
|
+
return `application/statuslist+jwt`
|
|
241
|
+
case 'cbor':
|
|
242
|
+
return `application/statuslist+cwt`
|
|
243
|
+
case 'lds':
|
|
244
|
+
return 'application/statuslist+ld+json'
|
|
245
|
+
default:
|
|
246
|
+
throw Error(`Unsupported content type '${proofFormat}' for status lists`)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { IStatusList } from './IStatusList'
|
|
2
|
+
import { StatusList2021Implementation } from './StatusList2021'
|
|
3
|
+
import { OAuthStatusListImplementation } from './OAuthStatusList'
|
|
4
|
+
import { StatusListType } from '@sphereon/ssi-types'
|
|
5
|
+
|
|
6
|
+
export class StatusListFactory {
|
|
7
|
+
private static instance: StatusListFactory
|
|
8
|
+
private implementations: Map<StatusListType, IStatusList>
|
|
9
|
+
|
|
10
|
+
private constructor() {
|
|
11
|
+
this.implementations = new Map()
|
|
12
|
+
this.implementations.set(StatusListType.StatusList2021, new StatusList2021Implementation())
|
|
13
|
+
this.implementations.set(StatusListType.OAuthStatusList, new OAuthStatusListImplementation())
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public static getInstance(): StatusListFactory {
|
|
17
|
+
if (!StatusListFactory.instance) {
|
|
18
|
+
StatusListFactory.instance = new StatusListFactory()
|
|
19
|
+
}
|
|
20
|
+
return StatusListFactory.instance
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public getByType(type: StatusListType): IStatusList {
|
|
24
|
+
const statusList = this.implementations.get(type)
|
|
25
|
+
if (!statusList) {
|
|
26
|
+
throw new Error(`No implementation found for status list type: ${type}`)
|
|
27
|
+
}
|
|
28
|
+
return statusList
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getStatusListImplementation(type: StatusListType): IStatusList {
|
|
33
|
+
return StatusListFactory.getInstance().getByType(type)
|
|
34
|
+
}
|