@cheqd/did-provider-cheqd 3.4.0-develop.1 → 3.4.0-develop.2
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/build/cjs/agent/ICheqd.d.ts +175 -22
- package/build/cjs/agent/ICheqd.d.ts.map +1 -1
- package/build/cjs/agent/ICheqd.js +1424 -221
- package/build/cjs/agent/ICheqd.js.map +1 -1
- package/build/cjs/did-manager/cheqd-did-provider.d.ts +20 -3
- package/build/cjs/did-manager/cheqd-did-provider.d.ts.map +1 -1
- package/build/cjs/did-manager/cheqd-did-provider.js +15 -1
- package/build/cjs/did-manager/cheqd-did-provider.js.map +1 -1
- package/build/esm/agent/ICheqd.d.ts +175 -22
- package/build/esm/agent/ICheqd.d.ts.map +1 -1
- package/build/esm/agent/ICheqd.js +1424 -221
- package/build/esm/agent/ICheqd.js.map +1 -1
- package/build/esm/did-manager/cheqd-did-provider.d.ts +20 -3
- package/build/esm/did-manager/cheqd-did-provider.d.ts.map +1 -1
- package/build/esm/did-manager/cheqd-did-provider.js +14 -0
- package/build/esm/did-manager/cheqd-did-provider.js.map +1 -1
- package/build/tsconfig.cjs.tsbuildinfo +1 -1
- package/build/tsconfig.esm.tsbuildinfo +1 -1
- package/build/tsconfig.types.tsbuildinfo +1 -1
- package/build/types/agent/ICheqd.d.ts +175 -22
- package/build/types/agent/ICheqd.d.ts.map +1 -1
- package/build/types/did-manager/cheqd-did-provider.d.ts +20 -3
- package/build/types/did-manager/cheqd-did-provider.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/agent/ICheqd.ts +1820 -363
- package/src/did-manager/cheqd-did-provider.ts +26 -3
package/src/agent/ICheqd.ts
CHANGED
|
@@ -38,7 +38,8 @@ import {
|
|
|
38
38
|
IDIDManager,
|
|
39
39
|
IDataStore,
|
|
40
40
|
IResolver,
|
|
41
|
-
W3CVerifiableCredential
|
|
41
|
+
W3CVerifiableCredential,
|
|
42
|
+
ICredentialVerifier
|
|
42
43
|
} from '@veramo/core'
|
|
43
44
|
import {
|
|
44
45
|
CheqdDIDProvider,
|
|
@@ -46,7 +47,13 @@ import {
|
|
|
46
47
|
TImportableEd25519Key,
|
|
47
48
|
ResourcePayload,
|
|
48
49
|
StatusList2021ResourcePayload,
|
|
49
|
-
DefaultRESTUrls
|
|
50
|
+
DefaultRESTUrls,
|
|
51
|
+
DefaultStatusList2021Encodings,
|
|
52
|
+
DefaultStatusList2021ResourceTypes,
|
|
53
|
+
DefaultStatusList2021StatusPurposeTypes,
|
|
54
|
+
DefaultStatusList2021Encoding,
|
|
55
|
+
DefaultStatusList2021ResourceType,
|
|
56
|
+
DefaultStatusList2021StatusPurposeType,
|
|
50
57
|
} from '../did-manager/cheqd-did-provider.js'
|
|
51
58
|
import {
|
|
52
59
|
fromString,
|
|
@@ -65,12 +72,18 @@ import {
|
|
|
65
72
|
LitProtocol,
|
|
66
73
|
TxNonceFormat
|
|
67
74
|
} from '../dkg-threshold/lit-protocol.js';
|
|
68
|
-
import {
|
|
75
|
+
import {
|
|
76
|
+
blobToHexString,
|
|
77
|
+
randomFromRange,
|
|
78
|
+
toBlob,
|
|
79
|
+
unescapeUnicode
|
|
80
|
+
} from '../utils/helpers.js'
|
|
69
81
|
import { resolverUrl } from '../did-manager/cheqd-did-resolver.js'
|
|
82
|
+
import { AlternativeUri } from '@cheqd/ts-proto/cheqd/resource/v2/resource.js'
|
|
70
83
|
|
|
71
84
|
const debug = Debug('veramo:did-provider-cheqd')
|
|
72
85
|
|
|
73
|
-
export type IContext = IAgentContext<IDIDManager & IKeyManager & IDataStore & IResolver & ICredentialIssuer & ICheqd>
|
|
86
|
+
export type IContext = IAgentContext<IDIDManager & IKeyManager & IDataStore & IResolver & ICredentialIssuer & ICredentialVerifier & ICheqd>
|
|
74
87
|
export type TExportedDIDDocWithKeys = { didDoc: DIDDocument, keys: TImportableEd25519Key[], versionId?: string }
|
|
75
88
|
export type TExportedDIDDocWithLinkedResourceWithKeys = TExportedDIDDocWithKeys & { linkedResource: LinkedResource }
|
|
76
89
|
export type LinkedResourceMetadataResolutionResult = { resourceURI: string, resourceCollectionId: string, resourceId: string, resourceName: string, resourceType: string, mediaType: string, resourceVersion?: string, created: string, checksum: string, previousVersionId: string | null, nextVersionId: string | null }
|
|
@@ -80,28 +93,70 @@ export type ShallowTypedTxTxResponses = { height: string, txhash: string, codesp
|
|
|
80
93
|
export type ShallowTypedTxsResponse = { txs: ShallowTypedTx[], tx_responses: ShallowTypedTxTxResponses[], pagination: string | null, total: string } | undefined
|
|
81
94
|
export type VerificationResult = { verified: boolean, revoked?: boolean, suspended?: boolean, error?: IVerifyResult['error'] }
|
|
82
95
|
export type StatusCheckResult = { revoked?: boolean, suspended?: boolean, error?: IError }
|
|
83
|
-
export type RevocationResult = { revoked: boolean, error?: IError, statusList?:
|
|
84
|
-
export type
|
|
85
|
-
export type
|
|
96
|
+
export type RevocationResult = { revoked: boolean, error?: IError, statusList?: StatusList2021Revocation, encryptedStatusList?: string, encryptedSymmetricKey?: string, symmetricKey?: string, published?: boolean, resourceMetadata?: LinkedResourceMetadataResolutionResult }
|
|
97
|
+
export type BulkRevocationResult = { revoked: boolean[], error?: IError, statusList?: StatusList2021Revocation, encryptedStatusList?: string, encryptedSymmetricKey?: string, symmetricKey?: string, published?: boolean, resourceMetadata?: LinkedResourceMetadataResolutionResult }
|
|
98
|
+
export type SuspensionResult = { suspended: boolean, error?: IError, statusList?: StatusList2021Suspension, encryptedStatusList?: string, encryptedSymmetricKey?: string, symmetricKey?: string, published?: boolean, resourceMetadata?: LinkedResourceMetadataResolutionResult }
|
|
99
|
+
export type BulkSuspensionResult = { suspended: boolean[], error?: IError, statusList?: StatusList2021Suspension, encryptedStatusList?: string, encryptedSymmetricKey?: string, symmetricKey?: string, published?: boolean, resourceMetadata?: LinkedResourceMetadataResolutionResult }
|
|
100
|
+
export type UnsuspensionResult = { unsuspended: boolean, error?: IError, statusList?: StatusList2021Suspension, encryptedStatusList?: string, encryptedSymmetricKey?: string, symmetricKey?: string, published?: boolean, resourceMetadata?: LinkedResourceMetadataResolutionResult }
|
|
101
|
+
export type BulkUnsuspensionResult = { unsuspended: boolean[], error?: IError, statusList?: StatusList2021Suspension, encryptedStatusList?: string, encryptedSymmetricKey?: string, symmetricKey?: string, published?: boolean, resourceMetadata?: LinkedResourceMetadataResolutionResult }
|
|
86
102
|
export type Bitstring = string
|
|
103
|
+
export type StatusList2021Revocation = { StatusList2021: { type: typeof DefaultStatusList2021ResourceTypes.revocation, encodedList: string, validFrom: string, validUntil?: string }, metadata: { encrypted: boolean, encoding: DefaultStatusList2021Encoding, paymentConditions?: PaymentCondition[] } }
|
|
104
|
+
export type StatusList2021Suspension = { StatusList2021: { type: typeof DefaultStatusList2021ResourceTypes.suspension, encodedList: string, validFrom: string, validUntil?: string }, metadata: { encrypted: boolean, encoding: DefaultStatusList2021Encoding, paymentConditions?: PaymentCondition[] } }
|
|
87
105
|
export type AccessControlConditionType = typeof AccessControlConditionTypes[keyof typeof AccessControlConditionTypes]
|
|
88
106
|
export type AccessControlConditionReturnValueComparator = typeof AccessControlConditionReturnValueComparators[keyof typeof AccessControlConditionReturnValueComparators]
|
|
89
107
|
export type AccessControlConditionMemoNonceArgs = { senderAddressObserved: string, recipientAddressObserved: string, amountObserved: string, specificNonce?: string, nonceFormat?: TxNonceFormat, type: Extract<AccessControlConditionType, 'memoNonce'> }
|
|
90
108
|
export type AccessControlConditionBalanceArgs = { addressObserved: string, amountObserved: string, comparator: AccessControlConditionReturnValueComparator, type: Extract<AccessControlConditionType, 'balance'>}
|
|
109
|
+
export type PaymentCondition = { feePaymentAddress: string, feePaymentAmount: string, type: Extract<AccessControlConditionType, 'timelockPayment'> }
|
|
110
|
+
export type CreateStatusList2021Result = { created: boolean, error?: Error, statusList2021: StatusList2021Revocation | StatusList2021Suspension, resourceMetadata: LinkedResourceMetadataResolutionResult, encrypted?: boolean, encryptedSymmetricKey?: string, symmetricKey?: string, encryptedStatusList2021?: string, unifiedAccessControlConditions?: CosmosAccessControlCondition[] }
|
|
91
111
|
export type CreateEncryptedStatusList2021Result = { created: boolean, error?: Error, encryptedSymmetricKey: string, symmetricKey?: string, encryptedStatusList2021: string, unifiedAccessControlConditions: CosmosAccessControlCondition[] }
|
|
92
112
|
export type GenerateEncryptedStatusList2021Result = { encryptedSymmetricKey: string, encryptedStatusList2021: string, unifiedAccessControlConditions: CosmosAccessControlCondition[] }
|
|
93
113
|
export type TransactionResult = { successful: boolean, transactionHash?: string, events?: DeliverTxResponse['events'], rawLog?: string, txResponse?: DeliverTxResponse, error?: IError }
|
|
94
114
|
export type ObservationResult = { subscribed: boolean, meetsCondition: boolean, transactionHash?: string, events?: DeliverTxResponse['events'], rawLog?: string, txResponse?: ShallowTypedTxTxResponses, error?: IError }
|
|
95
115
|
|
|
96
|
-
|
|
116
|
+
// TODO: revisit while implementing timelock payment
|
|
117
|
+
//? CheckSequenceVerify (CSV) is a script opcode used to implement relative timelocks.
|
|
118
|
+
//? It is a conditional opcode that consumes a minimum of two stack items and compares their relative locktime values.
|
|
119
|
+
//? If the relative locktime of the first item is equal to or greater than the relative locktime of the second item, then the script evaluates to true, otherwise it evaluates to false.
|
|
120
|
+
//? The relative locktime of an item is the number of seconds since the item was confirmed.
|
|
121
|
+
//? The following apply:
|
|
122
|
+
//? - Relative locktimes are measured from the time a transaction is confirmed, not from the time a transaction is created.
|
|
123
|
+
//? - Relative locktimes are not affected by time zone or daylight saving time.
|
|
124
|
+
//? - Relative locktimes are not affected by the time a transaction is broadcast.
|
|
125
|
+
//? - Relative locktimes are not affected by the time a transaction is mined.
|
|
126
|
+
//? - Relative locktimes are not affected by the time a transaction is included in a block.
|
|
127
|
+
//? - Relative locktimes are not affected by the time a block is mined.
|
|
128
|
+
|
|
129
|
+
// Implementation as per:
|
|
130
|
+
//
|
|
131
|
+
// - Generate a random nonce client-side.
|
|
132
|
+
// - Utilise the nonce as the memo.
|
|
133
|
+
// - Observed by the recipient client-side.
|
|
134
|
+
// - Gives turn to initiate decryption of the status list + status verification.
|
|
135
|
+
// - Define time interval OR block height interval client-side.
|
|
136
|
+
// - Utilised as inverse timelock.
|
|
137
|
+
// - Observed by dkg nodes.
|
|
138
|
+
// - TODO: Add chained AND conditions to return value tests, dkg node-side.
|
|
139
|
+
// - e.g. if timelockPayment (a <= now <= b) AND memoNonce strict equality (uuid, IMPORTANT: condition is updateable).
|
|
140
|
+
// - Optionally, recycled per encryption of the status list.
|
|
141
|
+
// - Will be published as part of the status list, if recycled + interval is altered.
|
|
142
|
+
|
|
143
|
+
// Issuer initiated immutable steps:
|
|
144
|
+
// - Initilise ACC with timelockPayment, nonce. <-- Nonce is timestamped as per time of encryption.
|
|
145
|
+
// - On a per transaction basis:
|
|
146
|
+
// - Update ACC with revelant memoNonce.
|
|
147
|
+
// - Rotate symmetric key.
|
|
148
|
+
|
|
149
|
+
export const AccessControlConditionTypes = { timelockPayment: 'timelockPayment', memoNonce: 'memoNonce', balance: 'balance' } as const
|
|
97
150
|
export const AccessControlConditionReturnValueComparators = { lessThan: '<', greaterThan: '>', equalTo: '=', lessThanOrEqualTo: '<=', greaterThanOrEqualTo: '>=' } as const
|
|
98
151
|
|
|
152
|
+
export const RemoteListPattern = /^(https:\/\/)?[a-z0-9_-]+(\.[a-z0-9_-]+)*\.[a-z]{2,}\/1\.0\/identifiers\/did:cheqd:[a-z]+:[a-zA-Z0-9-]+\?((resourceName=[^&]*)&(resourceType=[^&]*)|((resourceType=[^&]*)&(resourceName=[^&]*)))$/
|
|
153
|
+
|
|
99
154
|
const CreateIdentifierMethodName = 'cheqdCreateIdentifier'
|
|
100
155
|
const UpdateIdentifierMethodName = 'cheqdUpdateIdentifier'
|
|
101
156
|
const DeactivateIdentifierMethodName = 'cheqdDeactivateIdentifier'
|
|
102
157
|
const CreateResourceMethodName = 'cheqdCreateLinkedResource'
|
|
103
158
|
const CreateStatusList2021MethodName = 'cheqdCreateStatusList2021'
|
|
104
|
-
const
|
|
159
|
+
const BroadcastStatusList2021MethodName = 'cheqdBroadcastStatusList2021'
|
|
105
160
|
const GenerateDidDocMethodName = 'cheqdGenerateDidDoc'
|
|
106
161
|
const GenerateDidDocWithLinkedResourceMethodName = 'cheqdGenerateDidDocWithLinkedResource'
|
|
107
162
|
const GenerateKeyPairMethodName = 'cheqdGenerateIdentityKeys'
|
|
@@ -159,6 +214,41 @@ export interface ICheqdCreateLinkedResourceArgs {
|
|
|
159
214
|
}
|
|
160
215
|
|
|
161
216
|
export interface ICheqdCreateStatusList2021Args {
|
|
217
|
+
kms: string
|
|
218
|
+
issuerDid: string
|
|
219
|
+
statusListName: string
|
|
220
|
+
statusPurpose: DefaultStatusList2021StatusPurposeType
|
|
221
|
+
encrypted: boolean
|
|
222
|
+
paymentConditions?: PaymentCondition[]
|
|
223
|
+
resourceVersion?: ResourcePayload['version']
|
|
224
|
+
alsoKnownAs?: ResourcePayload['alsoKnownAs']
|
|
225
|
+
statusListLength?: number
|
|
226
|
+
statusListEncoding?: DefaultStatusList2021Encoding
|
|
227
|
+
validUntil?: string
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export interface ICheqdCreateUnencryptedStatusList2021Args {
|
|
231
|
+
kms: string
|
|
232
|
+
payload: StatusList2021ResourcePayload
|
|
233
|
+
network: CheqdNetwork
|
|
234
|
+
file?: string
|
|
235
|
+
signInputs?: ISignInputs[]
|
|
236
|
+
fee?: DidStdFee
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface ICheqdCreateEncryptedStatusList2021Args extends ICheqdCreateUnencryptedStatusList2021Args {
|
|
240
|
+
encryptionOptions: {
|
|
241
|
+
accessControlConditions: (AccessControlConditionMemoNonceArgs | AccessControlConditionBalanceArgs)[]
|
|
242
|
+
returnSymmetricKey?: boolean
|
|
243
|
+
}
|
|
244
|
+
bootstrapOptions: {
|
|
245
|
+
chain?: LitCompatibleCosmosChain,
|
|
246
|
+
litNetwork?: LitNetwork,
|
|
247
|
+
}
|
|
248
|
+
[key: string]: any
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface ICheqdBroadcastStatusList2021Args {
|
|
162
252
|
kms: string
|
|
163
253
|
payload: StatusList2021ResourcePayload
|
|
164
254
|
network: CheqdNetwork
|
|
@@ -167,7 +257,7 @@ export interface ICheqdCreateStatusList2021Args {
|
|
|
167
257
|
fee?: DidStdFee
|
|
168
258
|
}
|
|
169
259
|
|
|
170
|
-
export interface
|
|
260
|
+
export interface ICheqdBroadcastEncryptedStatusList2021Args extends ICheqdCreateUnencryptedStatusList2021Args {
|
|
171
261
|
encryptionOptions: {
|
|
172
262
|
accessControlConditions: (AccessControlConditionMemoNonceArgs | AccessControlConditionBalanceArgs)[]
|
|
173
263
|
returnSymmetricKey?: boolean
|
|
@@ -200,7 +290,7 @@ export interface ICheqdGenerateVersionIdArgs {
|
|
|
200
290
|
export interface ICheqdGenerateStatusList2021Args {
|
|
201
291
|
length?: number
|
|
202
292
|
buffer?: Uint8Array
|
|
203
|
-
bitstringEncoding?:
|
|
293
|
+
bitstringEncoding?: DefaultStatusList2021Encoding
|
|
204
294
|
[key: string]: any
|
|
205
295
|
}
|
|
206
296
|
|
|
@@ -284,7 +374,8 @@ export interface ICheqdCheckCredentialStatusWithStatusList2021Args {
|
|
|
284
374
|
}
|
|
285
375
|
|
|
286
376
|
export interface ICheqdRevokeCredentialWithStatusList2021Args {
|
|
287
|
-
credential
|
|
377
|
+
credential?: W3CVerifiableCredential
|
|
378
|
+
revocationOptions?: ICheqdRevokeCredentialWithStatusList2021Options
|
|
288
379
|
fetchList?: boolean
|
|
289
380
|
publish?: boolean
|
|
290
381
|
publishEncrypted?: boolean
|
|
@@ -299,7 +390,8 @@ export interface ICheqdRevokeCredentialWithStatusList2021Args {
|
|
|
299
390
|
}
|
|
300
391
|
|
|
301
392
|
export interface ICheqdRevokeBulkCredentialsWithStatusList2021Args {
|
|
302
|
-
credentials
|
|
393
|
+
credentials?: W3CVerifiableCredential[]
|
|
394
|
+
revocationOptions?: ICheqdRevokeBulkCredentialsWithStatusList2021Options
|
|
303
395
|
fetchList?: boolean
|
|
304
396
|
publish?: boolean
|
|
305
397
|
publishEncrypted?: boolean
|
|
@@ -314,7 +406,8 @@ export interface ICheqdRevokeBulkCredentialsWithStatusList2021Args {
|
|
|
314
406
|
}
|
|
315
407
|
|
|
316
408
|
export interface ICheqdSuspendCredentialWithStatusList2021Args {
|
|
317
|
-
credential
|
|
409
|
+
credential?: W3CVerifiableCredential
|
|
410
|
+
suspensionOptions?: ICheqdSuspendCredentialWithStatusList2021Options
|
|
318
411
|
fetchList?: boolean
|
|
319
412
|
publish?: boolean
|
|
320
413
|
publishEncrypted?: boolean
|
|
@@ -329,7 +422,8 @@ export interface ICheqdSuspendCredentialWithStatusList2021Args {
|
|
|
329
422
|
}
|
|
330
423
|
|
|
331
424
|
export interface ICheqdSuspendBulkCredentialsWithStatusList2021Args {
|
|
332
|
-
credentials
|
|
425
|
+
credentials?: W3CVerifiableCredential[]
|
|
426
|
+
suspensionOptions?: ICheqdSuspendBulkCredentialsWithStatusList2021Options
|
|
333
427
|
fetchList?: boolean
|
|
334
428
|
publish?: boolean
|
|
335
429
|
publishEncrypted?: boolean
|
|
@@ -344,7 +438,8 @@ export interface ICheqdSuspendBulkCredentialsWithStatusList2021Args {
|
|
|
344
438
|
}
|
|
345
439
|
|
|
346
440
|
export interface ICheqdUnsuspendCredentialWithStatusList2021Args {
|
|
347
|
-
credential
|
|
441
|
+
credential?: W3CVerifiableCredential
|
|
442
|
+
unsuspensionOptions?: ICheqdUnsuspendCredentialWithStatusList2021Options
|
|
348
443
|
fetchList?: boolean
|
|
349
444
|
publish?: boolean
|
|
350
445
|
publishEncrypted?: boolean
|
|
@@ -359,7 +454,8 @@ export interface ICheqdUnsuspendCredentialWithStatusList2021Args {
|
|
|
359
454
|
}
|
|
360
455
|
|
|
361
456
|
export interface ICheqdUnsuspendBulkCredentialsWithStatusList2021Args {
|
|
362
|
-
credentials
|
|
457
|
+
credentials?: W3CVerifiableCredential[]
|
|
458
|
+
unsuspensionOptions?: ICheqdUnsuspendBulkCredentialsWithStatusList2021Options
|
|
363
459
|
fetchList?: boolean
|
|
364
460
|
publish?: boolean
|
|
365
461
|
publishEncrypted?: boolean
|
|
@@ -397,13 +493,55 @@ export interface ICheqdStatusList2021Options {
|
|
|
397
493
|
[key: string]: any
|
|
398
494
|
}
|
|
399
495
|
|
|
496
|
+
export interface ICheqdRevokeCredentialWithStatusList2021Options {
|
|
497
|
+
issuerDid: string
|
|
498
|
+
statusListName: string
|
|
499
|
+
statusListIndex: number
|
|
500
|
+
statusListVersion?: string
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export interface ICheqdRevokeBulkCredentialsWithStatusList2021Options {
|
|
504
|
+
issuerDid: string
|
|
505
|
+
statusListName: string
|
|
506
|
+
statusListIndices: number[]
|
|
507
|
+
statusListVersion?: string
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export interface ICheqdSuspendCredentialWithStatusList2021Options {
|
|
511
|
+
issuerDid: string
|
|
512
|
+
statusListName: string
|
|
513
|
+
statusListIndex: number
|
|
514
|
+
statusListVersion?: string
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export interface ICheqdSuspendBulkCredentialsWithStatusList2021Options {
|
|
518
|
+
issuerDid: string
|
|
519
|
+
statusListName: string
|
|
520
|
+
statusListIndices: number[]
|
|
521
|
+
statusListVersion?: string
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
export interface ICheqdUnsuspendCredentialWithStatusList2021Options {
|
|
525
|
+
issuerDid: string
|
|
526
|
+
statusListName: string
|
|
527
|
+
statusListIndex: number
|
|
528
|
+
statusListVersion?: string
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export interface ICheqdUnsuspendBulkCredentialsWithStatusList2021Options {
|
|
532
|
+
issuerDid: string
|
|
533
|
+
statusListName: string
|
|
534
|
+
statusListIndices: number[]
|
|
535
|
+
statusListVersion?: string
|
|
536
|
+
}
|
|
537
|
+
|
|
400
538
|
export interface ICheqd extends IPluginMethodMap {
|
|
401
539
|
[CreateIdentifierMethodName]: (args: ICheqdCreateIdentifierArgs, context: IContext) => Promise<Omit<IIdentifier, 'provider'>>
|
|
402
540
|
[UpdateIdentifierMethodName]: (args: ICheqdUpdateIdentifierArgs, context: IContext) => Promise<Omit<IIdentifier, 'provider'>>,
|
|
403
541
|
[DeactivateIdentifierMethodName]: (args: ICheqdDeactivateIdentifierArgs, context: IContext) => Promise<boolean>,
|
|
404
542
|
[CreateResourceMethodName]: (args: ICheqdCreateLinkedResourceArgs, context: IContext) => Promise<boolean>,
|
|
405
|
-
[CreateStatusList2021MethodName]: (args: ICheqdCreateStatusList2021Args, context: IContext) => Promise<
|
|
406
|
-
[
|
|
543
|
+
[CreateStatusList2021MethodName]: (args: ICheqdCreateStatusList2021Args, context: IContext) => Promise<CreateStatusList2021Result>,
|
|
544
|
+
[BroadcastStatusList2021MethodName]: (args: ICheqdBroadcastStatusList2021Args, context: IContext) => Promise<boolean>,
|
|
407
545
|
[GenerateDidDocMethodName]: (args: ICheqdGenerateDidDocArgs, context: IContext) => Promise<TExportedDIDDocWithKeys>,
|
|
408
546
|
[GenerateDidDocWithLinkedResourceMethodName]: (args: ICheqdGenerateDidDocWithLinkedResourceArgs, context: IContext) => Promise<TExportedDIDDocWithLinkedResourceWithKeys>,
|
|
409
547
|
[GenerateKeyPairMethodName]: (args: ICheqdGenerateKeyPairArgs, context: IContext) => Promise<TImportableEd25519Key>
|
|
@@ -416,11 +554,11 @@ export interface ICheqd extends IPluginMethodMap {
|
|
|
416
554
|
[VerifyPresentationMethodName]: (args: ICheqdVerifyPresentationWithStatusList2021Args, context: IContext) => Promise<VerificationResult>
|
|
417
555
|
[CheckCredentialStatusMethodName]: (args: ICheqdCheckCredentialStatusWithStatusList2021Args, context: IContext) => Promise<StatusCheckResult>
|
|
418
556
|
[RevokeCredentialMethodName]: (args: ICheqdRevokeCredentialWithStatusList2021Args, context: IContext) => Promise<RevocationResult>
|
|
419
|
-
[RevokeCredentialsMethodName]: (args: ICheqdRevokeBulkCredentialsWithStatusList2021Args, context: IContext) => Promise<
|
|
557
|
+
[RevokeCredentialsMethodName]: (args: ICheqdRevokeBulkCredentialsWithStatusList2021Args, context: IContext) => Promise<BulkRevocationResult>
|
|
420
558
|
[SuspendCredentialMethodName]: (args: ICheqdSuspendCredentialWithStatusList2021Args, context: IContext) => Promise<SuspensionResult>
|
|
421
|
-
[SuspendCredentialsMethodName]: (args: ICheqdSuspendBulkCredentialsWithStatusList2021Args, context: IContext) => Promise<
|
|
559
|
+
[SuspendCredentialsMethodName]: (args: ICheqdSuspendBulkCredentialsWithStatusList2021Args, context: IContext) => Promise<BulkSuspensionResult>
|
|
422
560
|
[UnsuspendCredentialMethodName]: (args: ICheqdUnsuspendCredentialWithStatusList2021Args, context: IContext) => Promise<UnsuspensionResult>
|
|
423
|
-
[UnsuspendCredentialsMethodName]: (args: ICheqdUnsuspendBulkCredentialsWithStatusList2021Args, context: IContext) => Promise<
|
|
561
|
+
[UnsuspendCredentialsMethodName]: (args: ICheqdUnsuspendBulkCredentialsWithStatusList2021Args, context: IContext) => Promise<BulkUnsuspensionResult>
|
|
424
562
|
[TransactVerifierPaysIssuerMethodName]: (args: ICheqdTransactVerifierPaysIssuerArgs, context: IContext) => Promise<TransactionResult>
|
|
425
563
|
[ObserveVerifierPaysIssuerMethodName]: (args: ICheqdObserveVerifierPaysIssuerArgs, context: IContext) => Promise<ObservationResult>
|
|
426
564
|
}
|
|
@@ -518,17 +656,17 @@ export class Cheqd implements IAgentPlugin {
|
|
|
518
656
|
]
|
|
519
657
|
},
|
|
520
658
|
"returnType": {
|
|
521
|
-
"type": "
|
|
659
|
+
"type": "object"
|
|
522
660
|
}
|
|
523
661
|
},
|
|
524
|
-
"
|
|
525
|
-
"description": "
|
|
662
|
+
"cheqdBroadcastStatusList2021": {
|
|
663
|
+
"description": "Broadcast a Status List 2021 to cheqd ledger",
|
|
526
664
|
"arguments": {
|
|
527
665
|
"type": "object",
|
|
528
666
|
"properties": {
|
|
529
667
|
"args": {
|
|
530
668
|
"type": "object",
|
|
531
|
-
"description": "A
|
|
669
|
+
"description": "A cheqdBroadcastStatusList2021Args object as any for extensibility"
|
|
532
670
|
}
|
|
533
671
|
},
|
|
534
672
|
"required": [
|
|
@@ -898,7 +1036,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
898
1036
|
[DeactivateIdentifierMethodName]: this.DeactivateIdentifier.bind(this),
|
|
899
1037
|
[CreateResourceMethodName]: this.CreateResource.bind(this),
|
|
900
1038
|
[CreateStatusList2021MethodName]: this.CreateStatusList2021.bind(this),
|
|
901
|
-
[
|
|
1039
|
+
[BroadcastStatusList2021MethodName]: this.BroadcastStatusList2021.bind(this),
|
|
902
1040
|
[GenerateDidDocMethodName]: this.GenerateDidDoc.bind(this),
|
|
903
1041
|
[GenerateDidDocWithLinkedResourceMethodName]: this.GenerateDidDocWithLinkedResource.bind(this),
|
|
904
1042
|
[GenerateKeyPairMethodName]: this.GenerateIdentityKeys.bind(this),
|
|
@@ -1041,41 +1179,145 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1041
1179
|
throw new Error('[did-provider-cheqd]: kms is required')
|
|
1042
1180
|
}
|
|
1043
1181
|
|
|
1044
|
-
if (typeof args.
|
|
1045
|
-
throw new Error('[did-provider-cheqd]:
|
|
1182
|
+
if (typeof args.issuerDid !== 'string' || !args.issuerDid) {
|
|
1183
|
+
throw new Error('[did-provider-cheqd]: issuerDid is required')
|
|
1046
1184
|
}
|
|
1047
1185
|
|
|
1048
|
-
if (typeof args.
|
|
1049
|
-
throw new Error('[did-provider-cheqd]:
|
|
1186
|
+
if (typeof args.statusListName !== 'string' || !args.statusListName) {
|
|
1187
|
+
throw new Error('[did-provider-cheqd]: statusListName is required')
|
|
1050
1188
|
}
|
|
1051
1189
|
|
|
1052
|
-
if (args
|
|
1053
|
-
|
|
1190
|
+
if (typeof args.statusPurpose !== 'string' || !args.statusPurpose) {
|
|
1191
|
+
throw new Error('[did-provider-cheqd]: statusPurpose is required')
|
|
1054
1192
|
}
|
|
1055
1193
|
|
|
1056
|
-
if (typeof args
|
|
1057
|
-
|
|
1194
|
+
if (typeof args.encrypted === 'undefined') {
|
|
1195
|
+
throw new Error('[did-provider-cheqd]: encrypted is required')
|
|
1058
1196
|
}
|
|
1059
1197
|
|
|
1060
|
-
//
|
|
1198
|
+
// validate statusPurpose
|
|
1199
|
+
if (!Object.values(DefaultStatusList2021StatusPurposeTypes).includes(args.statusPurpose)) {
|
|
1200
|
+
throw new Error(`[did-provider-cheqd]: statusPurpose must be one of ${Object.values(DefaultStatusList2021StatusPurposeTypes).join(', ')}`)
|
|
1201
|
+
}
|
|
1061
1202
|
|
|
1062
|
-
//
|
|
1063
|
-
args
|
|
1203
|
+
// validate statusListLength
|
|
1204
|
+
if (args?.statusListLength) {
|
|
1205
|
+
if (typeof args.statusListLength !== 'number') {
|
|
1206
|
+
throw new Error('[did-provider-cheqd]: statusListLength must be number')
|
|
1207
|
+
}
|
|
1064
1208
|
|
|
1065
|
-
|
|
1066
|
-
|
|
1209
|
+
if (args.statusListLength < Cheqd.defaultStatusList2021Length) {
|
|
1210
|
+
throw new Error(`[did-provider-cheqd]: statusListLength must be greater than or equal to ${Cheqd.defaultStatusList2021Length} number of entries`)
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1067
1213
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
signInputs: args.signInputs,
|
|
1073
|
-
fee: args?.fee
|
|
1214
|
+
// validate statusListEncoding
|
|
1215
|
+
if (args?.statusListEncoding) {
|
|
1216
|
+
if (typeof args.statusListEncoding !== 'string') {
|
|
1217
|
+
throw new Error('[did-provider-cheqd]: statusListEncoding must be string')
|
|
1074
1218
|
}
|
|
1075
|
-
|
|
1219
|
+
|
|
1220
|
+
if (!Object.values(DefaultStatusList2021Encodings).includes(args.statusListEncoding)) {
|
|
1221
|
+
throw new Error(`[did-provider-cheqd]: statusListEncoding must be one of ${Object.values(DefaultStatusList2021Encodings).join(', ')}`)
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// validate validUntil
|
|
1226
|
+
if (args?.validUntil) {
|
|
1227
|
+
if (typeof args.validUntil !== 'string') {
|
|
1228
|
+
throw new Error('[did-provider-cheqd]: validUntil must be string')
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
if (new Date() <= new Date(args.validUntil)) {
|
|
1232
|
+
throw new Error('[did-provider-cheqd]: validUntil must be greater than current date')
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// validate args in pairs - case: encrypted
|
|
1237
|
+
if (args.encrypted) {
|
|
1238
|
+
// validate paymentConditions
|
|
1239
|
+
if (!args?.paymentConditions || !args?.paymentConditions?.length || !Array.isArray(args?.paymentConditions) || args?.paymentConditions.length === 0) {
|
|
1240
|
+
throw new Error('[did-provider-cheqd]: paymentConditions is required')
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if (!args?.paymentConditions?.every((condition) => condition.feePaymentAddress && condition.feePaymentAmount)) {
|
|
1244
|
+
throw new Error('[did-provider-cheqd]: paymentConditions must contain feePaymentAddress and feeAmount')
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
if (!args?.paymentConditions?.every((condition) => typeof condition.feePaymentAddress === 'string' && typeof condition.feePaymentAmount === 'string')) {
|
|
1248
|
+
throw new Error('[did-provider-cheqd]: feePaymentAddress and feePaymentAmount must be string')
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
if (!args?.paymentConditions?.every((condition) => condition.type === AccessControlConditionTypes.timelockPayment)) {
|
|
1252
|
+
throw new Error('[did-provider-cheqd]: paymentConditions must be of type timelockPayment')
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// get network
|
|
1257
|
+
const network = args.issuerDid.split(':')[2]
|
|
1258
|
+
|
|
1259
|
+
// generate bitstring
|
|
1260
|
+
const bitstring = await context.agent[GenerateStatusList2021MethodName]({ length: args?.statusListLength || Cheqd.defaultStatusList2021Length, bitstringEncoding: args?.statusListEncoding || DefaultStatusList2021Encodings.base64url })
|
|
1261
|
+
|
|
1262
|
+
// construct data
|
|
1263
|
+
const data = args.encrypted
|
|
1264
|
+
? (await (async function () {
|
|
1265
|
+
// TODO: implement
|
|
1266
|
+
throw new Error('[did-provider-cheqd]: encrypted status list is not implemented yet')
|
|
1267
|
+
}()))
|
|
1268
|
+
: (await (async function () {
|
|
1269
|
+
switch (args.statusPurpose) {
|
|
1270
|
+
case DefaultStatusList2021StatusPurposeTypes.revocation:
|
|
1271
|
+
return {
|
|
1272
|
+
StatusList2021: {
|
|
1273
|
+
type: DefaultStatusList2021ResourceTypes.revocation,
|
|
1274
|
+
encodedList: bitstring,
|
|
1275
|
+
validFrom: new Date().toISOString(),
|
|
1276
|
+
validUntil: args?.validUntil
|
|
1277
|
+
},
|
|
1278
|
+
metadata: {
|
|
1279
|
+
encrypted: false,
|
|
1280
|
+
encoding: args?.statusListEncoding || DefaultStatusList2021Encodings.base64url,
|
|
1281
|
+
}
|
|
1282
|
+
} satisfies StatusList2021Revocation
|
|
1283
|
+
case DefaultStatusList2021StatusPurposeTypes.suspension:
|
|
1284
|
+
return {
|
|
1285
|
+
StatusList2021: {
|
|
1286
|
+
type: DefaultStatusList2021ResourceTypes.suspension,
|
|
1287
|
+
encodedList: bitstring,
|
|
1288
|
+
validFrom: new Date().toISOString(),
|
|
1289
|
+
validUntil: args?.validUntil
|
|
1290
|
+
},
|
|
1291
|
+
metadata: {
|
|
1292
|
+
encrypted: false,
|
|
1293
|
+
encoding: args?.statusListEncoding || DefaultStatusList2021Encodings.base64url,
|
|
1294
|
+
}
|
|
1295
|
+
} satisfies StatusList2021Suspension
|
|
1296
|
+
default:
|
|
1297
|
+
throw new Error('[did-provider-cheqd]: statusPurpose is not valid')
|
|
1298
|
+
}
|
|
1299
|
+
}()))
|
|
1300
|
+
|
|
1301
|
+
// construct payload
|
|
1302
|
+
const payload = {
|
|
1303
|
+
id: v4(),
|
|
1304
|
+
collectionId: args.issuerDid.split(':').reverse()[0],
|
|
1305
|
+
name: args.statusListName,
|
|
1306
|
+
resourceType: DefaultStatusList2021ResourceTypes[args.statusPurpose],
|
|
1307
|
+
version: args?.resourceVersion || new Date().toISOString(),
|
|
1308
|
+
alsoKnownAs: args?.alsoKnownAs || [],
|
|
1309
|
+
data: fromString(JSON.stringify(data), 'utf-8'),
|
|
1310
|
+
} satisfies StatusList2021ResourcePayload
|
|
1311
|
+
|
|
1312
|
+
// return result
|
|
1313
|
+
return {
|
|
1314
|
+
created: await context.agent[BroadcastStatusList2021MethodName]({ kms: args.kms, payload, network: network as CheqdNetwork }),
|
|
1315
|
+
statusList2021: data,
|
|
1316
|
+
resourceMetadata: await Cheqd.fetchStatusList2021Metadata({ credentialStatus: { id: `${resolverUrl}${args.issuerDid}?resourceName=${args.statusListName}&resourceType=${DefaultStatusList2021ResourceTypes[args.statusPurpose]}`, type: 'StatusList2021Entry' } } as VerifiableCredential)
|
|
1317
|
+
} satisfies CreateStatusList2021Result
|
|
1076
1318
|
}
|
|
1077
1319
|
|
|
1078
|
-
private async
|
|
1320
|
+
private async BroadcastStatusList2021(args: ICheqdBroadcastStatusList2021Args, context: IContext) {
|
|
1079
1321
|
if (typeof args.kms !== 'string') {
|
|
1080
1322
|
throw new Error('[did-provider-cheqd]: kms is required')
|
|
1081
1323
|
}
|
|
@@ -1098,65 +1340,15 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1098
1340
|
|
|
1099
1341
|
// TODO: validate data as per bitstring
|
|
1100
1342
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
if (!args?.bootstrapOptions) {
|
|
1106
|
-
throw new Error('[did-provider-cheqd]: bootstrapOptions is required')
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
if (!args?.encryptionOptions?.accessControlConditions) {
|
|
1110
|
-
throw new Error('[did-provider-cheqd]: accessControlConditions is required')
|
|
1343
|
+
// validate resource type
|
|
1344
|
+
if (!Object.values(DefaultStatusList2021ResourceTypes).includes(args?.payload?.resourceType)) {
|
|
1345
|
+
throw new Error(`[did-provider-cheqd]: resourceType must be one of ${Object.values(DefaultStatusList2021ResourceTypes).join(', ')}`)
|
|
1111
1346
|
}
|
|
1112
1347
|
|
|
1113
|
-
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
1114
|
-
const lit = await LitProtocol.create({
|
|
1115
|
-
chain: args.bootstrapOptions?.chain,
|
|
1116
|
-
litNetwork: args.bootstrapOptions?.litNetwork
|
|
1117
|
-
})
|
|
1118
|
-
|
|
1119
|
-
// construct access control conditions
|
|
1120
|
-
const unifiedAccessControlConditions = await Promise.all(args.encryptionOptions.accessControlConditions.map(async (condition) => {
|
|
1121
|
-
switch (condition.type) {
|
|
1122
|
-
case AccessControlConditionTypes.memoNonce:
|
|
1123
|
-
return await LitProtocol.generateCosmosAccessControlConditionTransactionMemo({
|
|
1124
|
-
key: '$.txs.*.body.memo',
|
|
1125
|
-
comparator: 'contains',
|
|
1126
|
-
value: condition?.specificNonce || await LitProtocol.generateTxNonce(condition?.nonceFormat)
|
|
1127
|
-
},
|
|
1128
|
-
condition.amountObserved,
|
|
1129
|
-
condition.senderAddressObserved,
|
|
1130
|
-
condition.recipientAddressObserved,
|
|
1131
|
-
args.bootstrapOptions.chain
|
|
1132
|
-
)
|
|
1133
|
-
case AccessControlConditionTypes.balance:
|
|
1134
|
-
return await LitProtocol.generateCosmosAccessControlConditionBalance({
|
|
1135
|
-
key: '$.balances[0].amount',
|
|
1136
|
-
comparator: condition.comparator,
|
|
1137
|
-
value: condition.amountObserved
|
|
1138
|
-
},
|
|
1139
|
-
args.bootstrapOptions.chain,
|
|
1140
|
-
condition.addressObserved
|
|
1141
|
-
)
|
|
1142
|
-
default:
|
|
1143
|
-
throw new Error(`[did-provider-cheqd]: accessControlCondition type is not supported`)
|
|
1144
|
-
}
|
|
1145
|
-
}))
|
|
1146
|
-
|
|
1147
|
-
// encrypt data
|
|
1148
|
-
const { encryptedString, encryptedSymmetricKey, symmetricKey } = await lit.encrypt(toString(args.payload.data!, 'base64url'), unifiedAccessControlConditions, true)
|
|
1149
|
-
|
|
1150
|
-
// set encrypted data
|
|
1151
|
-
args.payload.data = new Uint8Array(await encryptedString.arrayBuffer())
|
|
1152
|
-
|
|
1153
|
-
// set default resource type in runtime
|
|
1154
|
-
args.payload.resourceType = 'StatusList2021'
|
|
1155
|
-
|
|
1156
1348
|
this.providerId = Cheqd.generateProviderId(args.network)
|
|
1157
1349
|
this.didProvider = await Cheqd.loadProvider({ id: this.providerId } as DIDDocument, this.supportedDidProviders)
|
|
1158
1350
|
|
|
1159
|
-
|
|
1351
|
+
return await this.didProvider.createResource({
|
|
1160
1352
|
options: {
|
|
1161
1353
|
kms: args.kms,
|
|
1162
1354
|
payload: args.payload,
|
|
@@ -1164,14 +1356,6 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1164
1356
|
fee: args?.fee
|
|
1165
1357
|
}
|
|
1166
1358
|
}, context)
|
|
1167
|
-
|
|
1168
|
-
return {
|
|
1169
|
-
created,
|
|
1170
|
-
encryptedSymmetricKey,
|
|
1171
|
-
encryptedStatusList2021: await blobToHexString(encryptedString),
|
|
1172
|
-
symmetricKey: args?.encryptionOptions?.returnSymmetricKey ? toString(symmetricKey!, 'hex') : undefined,
|
|
1173
|
-
unifiedAccessControlConditions
|
|
1174
|
-
} satisfies CreateEncryptedStatusList2021Result
|
|
1175
1359
|
}
|
|
1176
1360
|
|
|
1177
1361
|
private async GenerateDidDoc(
|
|
@@ -1360,7 +1544,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1360
1544
|
: args.issuanceOptions.credential.issuer as string
|
|
1361
1545
|
|
|
1362
1546
|
// generate status list credential
|
|
1363
|
-
const statusListCredential = `${resolverUrl}${issuer}?resourceName=${args.statusOptions.statusListName}`
|
|
1547
|
+
const statusListCredential = `${resolverUrl}${issuer}?resourceName=${args.statusOptions.statusListName}&resourceType=StatusList2021Revocation`
|
|
1364
1548
|
|
|
1365
1549
|
// construct credential status
|
|
1366
1550
|
const credentialStatus = {
|
|
@@ -1368,7 +1552,6 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1368
1552
|
type: 'StatusList2021Entry',
|
|
1369
1553
|
statusPurpose: 'revocation',
|
|
1370
1554
|
statusListIndex: `${statusListIndex}`,
|
|
1371
|
-
statusListCredential,
|
|
1372
1555
|
}
|
|
1373
1556
|
|
|
1374
1557
|
// add credential status to credential
|
|
@@ -1412,7 +1595,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1412
1595
|
: args.issuanceOptions.credential.issuer as string
|
|
1413
1596
|
|
|
1414
1597
|
// generate status list credential
|
|
1415
|
-
const statusListCredential = `${resolverUrl}${issuer}?resourceName=${args.statusOptions.statusListName}`
|
|
1598
|
+
const statusListCredential = `${resolverUrl}${issuer}?resourceName=${args.statusOptions.statusListName}&resourceType=StatusList2021Suspension`
|
|
1416
1599
|
|
|
1417
1600
|
// construct credential status
|
|
1418
1601
|
const credentialStatus = {
|
|
@@ -1420,7 +1603,6 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1420
1603
|
type: 'StatusList2021Entry',
|
|
1421
1604
|
statusPurpose: 'suspension',
|
|
1422
1605
|
statusListIndex: `${statusListIndex}`,
|
|
1423
|
-
statusListCredential,
|
|
1424
1606
|
}
|
|
1425
1607
|
|
|
1426
1608
|
// add credential status to credential
|
|
@@ -1468,7 +1650,9 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1468
1650
|
return { verified: false, error: verificationResult.error }
|
|
1469
1651
|
}
|
|
1470
1652
|
|
|
1653
|
+
// if jwt credential, decode it
|
|
1471
1654
|
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential
|
|
1655
|
+
|
|
1472
1656
|
// verify credential status
|
|
1473
1657
|
switch (credential.credentialStatus?.statusPurpose) {
|
|
1474
1658
|
case 'revocation':
|
|
@@ -1519,7 +1703,9 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1519
1703
|
}
|
|
1520
1704
|
|
|
1521
1705
|
private async CheckCredentialStatusWithStatusList2021(args: ICheqdCheckCredentialStatusWithStatusList2021Args, context: IContext): Promise<StatusCheckResult> {
|
|
1706
|
+
// if jwt credential, decode it
|
|
1522
1707
|
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential
|
|
1708
|
+
|
|
1523
1709
|
switch (credential.credentialStatus?.statusPurpose) {
|
|
1524
1710
|
case 'revocation':
|
|
1525
1711
|
if (await Cheqd.checkRevoked(credential, { ...args.options, topArgs: args })) return { revoked: true }
|
|
@@ -1533,7 +1719,57 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1533
1719
|
}
|
|
1534
1720
|
|
|
1535
1721
|
private async RevokeCredentialWithStatusList2021(args: ICheqdRevokeCredentialWithStatusList2021Args, context: IContext): Promise<RevocationResult> {
|
|
1722
|
+
// verify credential, if provided and revocation options are not
|
|
1723
|
+
if (args?.credential && !args?.revocationOptions) {
|
|
1724
|
+
const verificationResult = await context.agent.verifyCredential({
|
|
1725
|
+
credential: args.credential,
|
|
1726
|
+
policies: {
|
|
1727
|
+
credentialStatus: false
|
|
1728
|
+
}
|
|
1729
|
+
} satisfies IVerifyCredentialArgs)
|
|
1730
|
+
|
|
1731
|
+
// early return if verification failed
|
|
1732
|
+
if (!verificationResult.verified) {
|
|
1733
|
+
return { revoked: false, error: verificationResult.error }
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
// if revocation options are provided, give precedence
|
|
1738
|
+
if (args?.revocationOptions) {
|
|
1739
|
+
// validate revocation options - case: revocationOptions.issuerDid
|
|
1740
|
+
if (!args.revocationOptions.issuerDid) throw new Error('[did-provider-cheqd]: revocation: revocationOptions.issuerDid is required')
|
|
1741
|
+
|
|
1742
|
+
// validate revocation options - case: revocationOptions.statusListName
|
|
1743
|
+
if (!args.revocationOptions.statusListName) throw new Error('[did-provider-cheqd]: revocation: revocationOptions.statusListName is required')
|
|
1744
|
+
|
|
1745
|
+
// validate revocation options - case: revocationOptions.statusListIndex
|
|
1746
|
+
if (!args.revocationOptions.statusListIndex) throw new Error('[did-provider-cheqd]: revocation: revocationOptions.statusListIndex is required')
|
|
1747
|
+
|
|
1748
|
+
// construct status list credential
|
|
1749
|
+
const statusListCredential = `${resolverUrl}${args.revocationOptions.issuerDid}?resourceName=${args.revocationOptions.statusListName}&resourceType=StatusList2021Revocation`
|
|
1750
|
+
|
|
1751
|
+
// construct credential status
|
|
1752
|
+
args.credential = {
|
|
1753
|
+
'@context': [],
|
|
1754
|
+
issuer: args.revocationOptions.issuerDid,
|
|
1755
|
+
credentialSubject: {},
|
|
1756
|
+
credentialStatus: {
|
|
1757
|
+
id: `${statusListCredential}#${args.revocationOptions.statusListIndex}`,
|
|
1758
|
+
type: 'StatusList2021Entry',
|
|
1759
|
+
statusPurpose: 'revocation',
|
|
1760
|
+
statusListIndex: `${args.revocationOptions.statusListIndex}`,
|
|
1761
|
+
},
|
|
1762
|
+
issuanceDate: '',
|
|
1763
|
+
proof: {}
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
// validate args - case: credential
|
|
1768
|
+
if (!args.credential) throw new Error('[did-provider-cheqd]: revocation: credential is required')
|
|
1769
|
+
|
|
1770
|
+
// if jwt credential, decode it
|
|
1536
1771
|
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential
|
|
1772
|
+
|
|
1537
1773
|
// validate status purpose
|
|
1538
1774
|
if (credential.credentialStatus?.statusPurpose !== 'revocation') {
|
|
1539
1775
|
throw new Error(`[did-provider-cheqd]: revocation: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`)
|
|
@@ -1565,21 +1801,160 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1565
1801
|
topArgs: args,
|
|
1566
1802
|
publishOptions: {
|
|
1567
1803
|
context,
|
|
1804
|
+
statusListEncoding: args?.options?.statusListEncoding,
|
|
1805
|
+
statusListValidUntil: args?.options?.statusListValidUntil,
|
|
1568
1806
|
resourceId: args?.options?.resourceId,
|
|
1569
1807
|
resourceVersion: args?.options?.resourceVersion,
|
|
1808
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
1570
1809
|
signInputs: args?.options?.signInputs,
|
|
1571
1810
|
fee: args?.options?.fee
|
|
1572
1811
|
}
|
|
1573
1812
|
})
|
|
1574
1813
|
}
|
|
1575
1814
|
|
|
1576
|
-
private async RevokeBulkCredentialsWithStatusList2021(args: ICheqdRevokeBulkCredentialsWithStatusList2021Args, context: IContext): Promise<
|
|
1577
|
-
//
|
|
1578
|
-
|
|
1815
|
+
private async RevokeBulkCredentialsWithStatusList2021(args: ICheqdRevokeBulkCredentialsWithStatusList2021Args, context: IContext): Promise<BulkRevocationResult> {
|
|
1816
|
+
// verify credential, if provided and revocation options are not
|
|
1817
|
+
if (args?.credentials && !args?.revocationOptions) {
|
|
1818
|
+
|
|
1819
|
+
const verificationResult = await Promise.all(args.credentials.map(async (credential) => {
|
|
1820
|
+
return await context.agent.verifyCredential({
|
|
1821
|
+
credential,
|
|
1822
|
+
policies: {
|
|
1823
|
+
credentialStatus: false
|
|
1824
|
+
}
|
|
1825
|
+
} satisfies IVerifyCredentialArgs)
|
|
1826
|
+
}))
|
|
1827
|
+
|
|
1828
|
+
// early return if verification failed for any credential
|
|
1829
|
+
if (verificationResult.some(result => !result.verified)) {
|
|
1830
|
+
// define verified
|
|
1831
|
+
return { revoked: Array(args.credentials.length).fill(false), error: verificationResult.find(result => !result.verified)!.error || { message: 'verification: could not verify credential' } }
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
// if revocation options are provided, give precedence
|
|
1836
|
+
if (args?.revocationOptions) {
|
|
1837
|
+
// validate revocation options - case: revocationOptions.issuerDid
|
|
1838
|
+
if (!args.revocationOptions.issuerDid) throw new Error('[did-provider-cheqd]: revocation: revocationOptions.issuerDid is required')
|
|
1839
|
+
|
|
1840
|
+
// validate revocation options - case: revocationOptions.statusListName
|
|
1841
|
+
if (!args.revocationOptions.statusListName) throw new Error('[did-provider-cheqd]: revocation: revocationOptions.statusListName is required')
|
|
1842
|
+
|
|
1843
|
+
// validate revocation options - case: revocationOptions.statusListIndices
|
|
1844
|
+
if (!args.revocationOptions.statusListIndices || !args.revocationOptions.statusListIndices.length || args.revocationOptions.statusListIndices.length === 0 || !args.revocationOptions.statusListIndices.every(index => !isNaN(+index))) throw new Error('[did-provider-cheqd]: revocation: revocationOptions.statusListIndex is required and must be an array of indices')
|
|
1845
|
+
|
|
1846
|
+
// construct status list credential
|
|
1847
|
+
const statusListCredential = `${resolverUrl}${args.revocationOptions.issuerDid}?resourceName=${args.revocationOptions.statusListName}&resourceType=StatusList2021Revocation`
|
|
1848
|
+
|
|
1849
|
+
// construct credential status
|
|
1850
|
+
args.credentials = args.revocationOptions.statusListIndices.map(index => ({
|
|
1851
|
+
'@context': [],
|
|
1852
|
+
issuer: args.revocationOptions!.issuerDid,
|
|
1853
|
+
credentialSubject: {},
|
|
1854
|
+
credentialStatus: {
|
|
1855
|
+
id: `${statusListCredential}#${index}`,
|
|
1856
|
+
type: 'StatusList2021Entry',
|
|
1857
|
+
statusPurpose: 'revocation',
|
|
1858
|
+
statusListIndex: `${index}`,
|
|
1859
|
+
},
|
|
1860
|
+
issuanceDate: '',
|
|
1861
|
+
proof: {}
|
|
1862
|
+
}))
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
// validate args - case: credentials
|
|
1866
|
+
if (!args.credentials || !args.credentials.length || args.credentials.length === 0) throw new Error('[did-provider-cheqd]: revocation: credentials is required and must be an array of credentials')
|
|
1867
|
+
|
|
1868
|
+
// if jwt credentials, decode them
|
|
1869
|
+
const credentials = await Promise.all(args.credentials.map(async credential => typeof credential === 'string' ? await Cheqd.decodeCredentialJWT(credential) : credential))
|
|
1870
|
+
|
|
1871
|
+
// validate args in pairs - case: statusListFile and statusList
|
|
1872
|
+
if (args.options?.statusListFile && args.options?.statusList) {
|
|
1873
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile and statusList are mutually exclusive')
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
// validate args in pairs - case: statusListFile and fetchList
|
|
1877
|
+
if (args.options?.statusListFile && args.options?.fetchList) {
|
|
1878
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile and fetchList are mutually exclusive')
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// validate args in pairs - case: statusList and fetchList
|
|
1882
|
+
if (args.options?.statusList && args.options?.fetchList) {
|
|
1883
|
+
throw new Error('[did-provider-cheqd]: revocation: statusList and fetchList are mutually exclusive')
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
// validate args in pairs - case: publish
|
|
1887
|
+
if (args.options?.publish && !args.fetchList && !(args.options?.statusListFile || args.options?.statusList)) {
|
|
1888
|
+
throw new Error('[did-provider-cheqd]: revocation: publish requires statusListFile or statusList, if fetchList is disabled')
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
// revoke credentials
|
|
1892
|
+
return await Cheqd.revokeCredentials(credentials, {
|
|
1893
|
+
...args.options,
|
|
1894
|
+
topArgs: args,
|
|
1895
|
+
publishOptions: {
|
|
1896
|
+
context,
|
|
1897
|
+
resourceId: args?.options?.resourceId,
|
|
1898
|
+
resourceVersion: args?.options?.resourceVersion,
|
|
1899
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
1900
|
+
signInputs: args?.options?.signInputs,
|
|
1901
|
+
fee: args?.options?.fee
|
|
1902
|
+
}
|
|
1903
|
+
})
|
|
1579
1904
|
}
|
|
1580
1905
|
|
|
1581
1906
|
private async SuspendCredentialWithStatusList2021(args: ICheqdSuspendCredentialWithStatusList2021Args, context: IContext): Promise<SuspensionResult> {
|
|
1907
|
+
// verify credential, if provided and suspension options are not
|
|
1908
|
+
if (args?.credential && !args?.suspensionOptions) {
|
|
1909
|
+
const verificationResult = await context.agent.verifyCredential({
|
|
1910
|
+
credential: args.credential,
|
|
1911
|
+
policies: {
|
|
1912
|
+
credentialStatus: false
|
|
1913
|
+
}
|
|
1914
|
+
} satisfies IVerifyCredentialArgs)
|
|
1915
|
+
|
|
1916
|
+
// early return if verification failed
|
|
1917
|
+
if (!verificationResult.verified) {
|
|
1918
|
+
return { suspended: false, error: verificationResult.error }
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
// if suspension options are provided, give precedence
|
|
1923
|
+
if (args?.suspensionOptions) {
|
|
1924
|
+
// validate suspension options - case: suspensionOptions.issuerDid
|
|
1925
|
+
if (!args.suspensionOptions.issuerDid) throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.issuerDid is required')
|
|
1926
|
+
|
|
1927
|
+
// validate suspension options - case: suspensionOptions.statusListName
|
|
1928
|
+
if (!args.suspensionOptions.statusListName) throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.statusListName is required')
|
|
1929
|
+
|
|
1930
|
+
// validate suspension options - case: suspensionOptions.statusListIndex
|
|
1931
|
+
if (!args.suspensionOptions.statusListIndex) throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.statusListIndex is required')
|
|
1932
|
+
|
|
1933
|
+
// construct status list credential
|
|
1934
|
+
const statusListCredential = `${resolverUrl}${args.suspensionOptions.issuerDid}?resourceName=${args.suspensionOptions.statusListName}&resourceType=StatusList2021Suspension`
|
|
1935
|
+
|
|
1936
|
+
// construct credential status
|
|
1937
|
+
args.credential = {
|
|
1938
|
+
'@context': [],
|
|
1939
|
+
issuer: args.suspensionOptions.issuerDid,
|
|
1940
|
+
credentialSubject: {},
|
|
1941
|
+
credentialStatus: {
|
|
1942
|
+
id: `${statusListCredential}#${args.suspensionOptions.statusListIndex}`,
|
|
1943
|
+
type: 'StatusList2021Entry',
|
|
1944
|
+
statusPurpose: 'suspension',
|
|
1945
|
+
statusListIndex: `${args.suspensionOptions.statusListIndex}`,
|
|
1946
|
+
},
|
|
1947
|
+
issuanceDate: '',
|
|
1948
|
+
proof: {}
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
// validate args - case: credential
|
|
1953
|
+
if (!args.credential) throw new Error('[did-provider-cheqd]: suspension: credential is required')
|
|
1954
|
+
|
|
1955
|
+
// if jwt credential, decode it
|
|
1582
1956
|
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential
|
|
1957
|
+
|
|
1583
1958
|
// validate status purpose
|
|
1584
1959
|
if (credential.credentialStatus?.statusPurpose !== 'suspension') {
|
|
1585
1960
|
throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`)
|
|
@@ -1611,26 +1986,73 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1611
1986
|
topArgs: args,
|
|
1612
1987
|
publishOptions: {
|
|
1613
1988
|
context,
|
|
1989
|
+
statusListEncoding: args?.options?.statusListEncoding,
|
|
1990
|
+
statusListValidUntil: args?.options?.statusListValidUntil,
|
|
1614
1991
|
resourceId: args?.options?.resourceId,
|
|
1615
1992
|
resourceVersion: args?.options?.resourceVersion,
|
|
1993
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
1616
1994
|
signInputs: args?.options?.signInputs,
|
|
1617
1995
|
fee: args?.options?.fee
|
|
1618
1996
|
}
|
|
1619
1997
|
})
|
|
1620
1998
|
}
|
|
1621
1999
|
|
|
1622
|
-
private async SuspendBulkCredentialsWithStatusList2021(args: ICheqdSuspendBulkCredentialsWithStatusList2021Args, context: IContext): Promise<
|
|
1623
|
-
//
|
|
1624
|
-
|
|
1625
|
-
|
|
2000
|
+
private async SuspendBulkCredentialsWithStatusList2021(args: ICheqdSuspendBulkCredentialsWithStatusList2021Args, context: IContext): Promise<BulkSuspensionResult> {
|
|
2001
|
+
// verify credential, if provided and suspension options are not
|
|
2002
|
+
if (args?.credentials && !args?.suspensionOptions) {
|
|
2003
|
+
|
|
2004
|
+
const verificationResult = await Promise.all(args.credentials.map(async (credential) => {
|
|
2005
|
+
return await context.agent.verifyCredential({
|
|
2006
|
+
credential,
|
|
2007
|
+
policies: {
|
|
2008
|
+
credentialStatus: false
|
|
2009
|
+
}
|
|
2010
|
+
} satisfies IVerifyCredentialArgs)
|
|
2011
|
+
}))
|
|
1626
2012
|
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
2013
|
+
// early return if verification failed for any credential
|
|
2014
|
+
if (verificationResult.some(result => !result.verified)) {
|
|
2015
|
+
// define verified
|
|
2016
|
+
return { suspended: Array(args.credentials.length).fill(false), error: verificationResult.find(result => !result.verified)!.error || { message: 'verification: could not verify credential' } }
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
// if suspension options are provided, give precedence
|
|
2021
|
+
if (args?.suspensionOptions) {
|
|
2022
|
+
// validate suspension options - case: suspensionOptions.issuerDid
|
|
2023
|
+
if (!args.suspensionOptions.issuerDid) throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.issuerDid is required')
|
|
2024
|
+
|
|
2025
|
+
// validate suspension options - case: suspensionOptions.statusListName
|
|
2026
|
+
if (!args.suspensionOptions.statusListName) throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.statusListName is required')
|
|
2027
|
+
|
|
2028
|
+
// validate suspension options - case: suspensionOptions.statusListIndices
|
|
2029
|
+
if (!args.suspensionOptions.statusListIndices || !args.suspensionOptions.statusListIndices.length || args.suspensionOptions.statusListIndices.length === 0 || !args.suspensionOptions.statusListIndices.every(index => !isNaN(+index))) throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.statusListIndex is required and must be an array of indices')
|
|
2030
|
+
|
|
2031
|
+
// construct status list credential
|
|
2032
|
+
const statusListCredential = `${resolverUrl}${args.suspensionOptions.issuerDid}?resourceName=${args.suspensionOptions.statusListName}&resourceType=StatusList2021Suspension`
|
|
2033
|
+
|
|
2034
|
+
// construct credential status
|
|
2035
|
+
args.credentials = args.suspensionOptions.statusListIndices.map(index => ({
|
|
2036
|
+
'@context': [],
|
|
2037
|
+
issuer: args.suspensionOptions!.issuerDid,
|
|
2038
|
+
credentialSubject: {},
|
|
2039
|
+
credentialStatus: {
|
|
2040
|
+
id: `${statusListCredential}#${index}`,
|
|
2041
|
+
type: 'StatusList2021Entry',
|
|
2042
|
+
statusPurpose: 'suspension',
|
|
2043
|
+
statusListIndex: `${index}`,
|
|
2044
|
+
},
|
|
2045
|
+
issuanceDate: '',
|
|
2046
|
+
proof: {}
|
|
2047
|
+
}))
|
|
1632
2048
|
}
|
|
1633
2049
|
|
|
2050
|
+
// validate args - case: credentials
|
|
2051
|
+
if (!args.credentials || !args.credentials.length || args.credentials.length === 0) throw new Error('[did-provider-cheqd]: suspension: credentials is required and must be an array of credentials')
|
|
2052
|
+
|
|
2053
|
+
// if jwt credentials, decode them
|
|
2054
|
+
const credentials = await Promise.all(args.credentials.map(async credential => typeof credential === 'string' ? await Cheqd.decodeCredentialJWT(credential) : credential))
|
|
2055
|
+
|
|
1634
2056
|
// validate args in pairs - case: statusListFile and statusList
|
|
1635
2057
|
if (args.options?.statusListFile && args.options?.statusList) {
|
|
1636
2058
|
throw new Error('[did-provider-cheqd]: suspension: statusListFile and statusList are mutually exclusive')
|
|
@@ -1651,44 +2073,225 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1651
2073
|
throw new Error('[did-provider-cheqd]: suspension: publish requires statusListFile or statusList, if fetchList is disabled')
|
|
1652
2074
|
}
|
|
1653
2075
|
|
|
1654
|
-
// suspend
|
|
1655
|
-
return await Cheqd.
|
|
2076
|
+
// suspend credentials
|
|
2077
|
+
return await Cheqd.suspendCredentials(credentials, {
|
|
1656
2078
|
...args.options,
|
|
1657
2079
|
topArgs: args,
|
|
1658
2080
|
publishOptions: {
|
|
1659
2081
|
context,
|
|
1660
2082
|
resourceId: args?.options?.resourceId,
|
|
1661
2083
|
resourceVersion: args?.options?.resourceVersion,
|
|
2084
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
1662
2085
|
signInputs: args?.options?.signInputs,
|
|
1663
2086
|
fee: args?.options?.fee
|
|
1664
2087
|
}
|
|
1665
2088
|
})
|
|
1666
2089
|
}
|
|
1667
2090
|
|
|
1668
|
-
private async
|
|
1669
|
-
//
|
|
1670
|
-
|
|
1671
|
-
|
|
2091
|
+
private async UnsuspendCredentialWithStatusList2021(args: ICheqdUnsuspendCredentialWithStatusList2021Args, context: IContext): Promise<UnsuspensionResult> {
|
|
2092
|
+
// verify credential, if provided and unsuspension options are not
|
|
2093
|
+
if (args?.credential && !args?.unsuspensionOptions) {
|
|
2094
|
+
const verificationResult = await context.agent.verifyCredential({
|
|
2095
|
+
credential: args.credential,
|
|
2096
|
+
policies: {
|
|
2097
|
+
credentialStatus: false
|
|
2098
|
+
}
|
|
2099
|
+
} satisfies IVerifyCredentialArgs)
|
|
1672
2100
|
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
amount: args.amount,
|
|
1679
|
-
memoNonce: args.memoNonce,
|
|
1680
|
-
txBytes: args.txBytes,
|
|
1681
|
-
})
|
|
2101
|
+
// early return if verification failed
|
|
2102
|
+
if (!verificationResult.verified) {
|
|
2103
|
+
return { unsuspended: false, error: verificationResult.error }
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
1682
2106
|
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
2107
|
+
// if unsuspension options are provided, give precedence
|
|
2108
|
+
if (args?.unsuspensionOptions) {
|
|
2109
|
+
// validate unsuspension options - case: unsuspensionOptions.issuerDid
|
|
2110
|
+
if (!args.unsuspensionOptions.issuerDid) throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.issuerDid is required')
|
|
2111
|
+
|
|
2112
|
+
// validate unsuspension options - case: unsuspensionOptions.statusListName
|
|
2113
|
+
if (!args.unsuspensionOptions.statusListName) throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.statusListName is required')
|
|
2114
|
+
|
|
2115
|
+
// validate unsuspension options - case: unsuspensionOptions.statusListIndex
|
|
2116
|
+
if (!args.unsuspensionOptions.statusListIndex) throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.statusListIndex is required')
|
|
2117
|
+
|
|
2118
|
+
// construct status list credential
|
|
2119
|
+
const statusListCredential = `${resolverUrl}${args.unsuspensionOptions.issuerDid}?resourceName=${args.unsuspensionOptions.statusListName}&resourceType=StatusList2021Suspension`
|
|
2120
|
+
|
|
2121
|
+
// construct credential status
|
|
2122
|
+
args.credential = {
|
|
2123
|
+
'@context': [],
|
|
2124
|
+
issuer: args.unsuspensionOptions.issuerDid,
|
|
2125
|
+
credentialSubject: {},
|
|
2126
|
+
credentialStatus: {
|
|
2127
|
+
id: `${statusListCredential}#${args.unsuspensionOptions.statusListIndex}`,
|
|
2128
|
+
type: 'StatusList2021Entry',
|
|
2129
|
+
statusPurpose: 'suspension',
|
|
2130
|
+
statusListIndex: `${args.unsuspensionOptions.statusListIndex}`,
|
|
2131
|
+
},
|
|
2132
|
+
issuanceDate: '',
|
|
2133
|
+
proof: {}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
// validate args - case: credential
|
|
2138
|
+
if (!args.credential) throw new Error('[did-provider-cheqd]: unsuspension: credential is required')
|
|
2139
|
+
|
|
2140
|
+
// if jwt credential, decode it
|
|
2141
|
+
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential
|
|
2142
|
+
|
|
2143
|
+
// validate status purpose
|
|
2144
|
+
if (credential.credentialStatus?.statusPurpose !== 'suspension') {
|
|
2145
|
+
throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`)
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
// validate args in pairs - case: statusListFile and statusList
|
|
2149
|
+
if (args.options?.statusListFile && args.options?.statusList) {
|
|
2150
|
+
throw new Error('[did-provider-cheqd]: suspension: statusListFile and statusList are mutually exclusive')
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
// validate args in pairs - case: statusListFile and fetchList
|
|
2154
|
+
if (args.options?.statusListFile && args.options?.fetchList) {
|
|
2155
|
+
throw new Error('[did-provider-cheqd]: suspension: statusListFile and fetchList are mutually exclusive')
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
// validate args in pairs - case: statusList and fetchList
|
|
2159
|
+
if (args.options?.statusList && args.options?.fetchList) {
|
|
2160
|
+
throw new Error('[did-provider-cheqd]: suspension: statusList and fetchList are mutually exclusive')
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
// validate args in pairs - case: publish
|
|
2164
|
+
if (args.options?.publish && !args.fetchList && !(args.options?.statusListFile || args.options?.statusList)) {
|
|
2165
|
+
throw new Error('[did-provider-cheqd]: suspension: publish requires statusListFile or statusList, if fetchList is disabled')
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
// suspend credential
|
|
2169
|
+
return await Cheqd.unsuspendCredential(credential, {
|
|
2170
|
+
...args.options,
|
|
2171
|
+
topArgs: args,
|
|
2172
|
+
publishOptions: {
|
|
2173
|
+
context,
|
|
2174
|
+
statusListEncoding: args?.options?.statusListEncoding,
|
|
2175
|
+
statusListValidUntil: args?.options?.statusListValidUntil,
|
|
2176
|
+
resourceId: args?.options?.resourceId,
|
|
2177
|
+
resourceVersion: args?.options?.resourceVersion,
|
|
2178
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
2179
|
+
signInputs: args?.options?.signInputs,
|
|
2180
|
+
fee: args?.options?.fee
|
|
2181
|
+
}
|
|
2182
|
+
})
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
private async UnsuspendBulkCredentialsWithStatusList2021(args: ICheqdUnsuspendBulkCredentialsWithStatusList2021Args, context: IContext): Promise<BulkUnsuspensionResult> {
|
|
2186
|
+
// verify credential, if provided and unsuspension options are not
|
|
2187
|
+
if (args?.credentials && !args?.unsuspensionOptions) {
|
|
2188
|
+
|
|
2189
|
+
const verificationResult = await Promise.all(args.credentials.map(async (credential) => {
|
|
2190
|
+
return await context.agent.verifyCredential({
|
|
2191
|
+
credential,
|
|
2192
|
+
policies: {
|
|
2193
|
+
credentialStatus: false
|
|
2194
|
+
}
|
|
2195
|
+
} satisfies IVerifyCredentialArgs)
|
|
2196
|
+
}))
|
|
2197
|
+
|
|
2198
|
+
// early return if verification failed for any credential
|
|
2199
|
+
if (verificationResult.some(result => !result.verified)) {
|
|
2200
|
+
// define verified
|
|
2201
|
+
return { unsuspended: Array(args.credentials.length).fill(false), error: verificationResult.find(result => !result.verified)!.error || { message: 'verification: could not verify credential' } }
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
// if unsuspension options are provided, give precedence
|
|
2206
|
+
if (args?.unsuspensionOptions) {
|
|
2207
|
+
// validate unsuspension options - case: unsuspensionOptions.issuerDid
|
|
2208
|
+
if (!args.unsuspensionOptions.issuerDid) throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.issuerDid is required')
|
|
2209
|
+
|
|
2210
|
+
// validate unsuspension options - case: unsuspensionOptions.statusListName
|
|
2211
|
+
if (!args.unsuspensionOptions.statusListName) throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.statusListName is required')
|
|
2212
|
+
|
|
2213
|
+
// validate unsuspension options - case: unsuspensionOptions.statusListIndices
|
|
2214
|
+
if (!args.unsuspensionOptions.statusListIndices || !args.unsuspensionOptions.statusListIndices.length || args.unsuspensionOptions.statusListIndices.length === 0 || !args.unsuspensionOptions.statusListIndices.every(index => !isNaN(+index))) throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.statusListIndex is required and must be an array of indices')
|
|
2215
|
+
|
|
2216
|
+
// construct status list credential
|
|
2217
|
+
const statusListCredential = `${resolverUrl}${args.unsuspensionOptions.issuerDid}?resourceName=${args.unsuspensionOptions.statusListName}&resourceType=StatusList2021Suspension`
|
|
2218
|
+
|
|
2219
|
+
// construct credential status
|
|
2220
|
+
args.credentials = args.unsuspensionOptions.statusListIndices.map(index => ({
|
|
2221
|
+
'@context': [],
|
|
2222
|
+
issuer: args.unsuspensionOptions!.issuerDid,
|
|
2223
|
+
credentialSubject: {},
|
|
2224
|
+
credentialStatus: {
|
|
2225
|
+
id: `${statusListCredential}#${index}`,
|
|
2226
|
+
type: 'StatusList2021Entry',
|
|
2227
|
+
statusPurpose: 'suspension',
|
|
2228
|
+
statusListIndex: `${index}`,
|
|
2229
|
+
},
|
|
2230
|
+
issuanceDate: '',
|
|
2231
|
+
proof: {}
|
|
2232
|
+
}))
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
// validate args - case: credentials
|
|
2236
|
+
if (!args.credentials || !args.credentials.length || args.credentials.length === 0) throw new Error('[did-provider-cheqd]: unsuspension: credentials is required and must be an array of credentials')
|
|
2237
|
+
|
|
2238
|
+
// if jwt credentials, decode them
|
|
2239
|
+
const credentials = await Promise.all(args.credentials.map(async credential => typeof credential === 'string' ? await Cheqd.decodeCredentialJWT(credential) : credential))
|
|
2240
|
+
|
|
2241
|
+
// validate args in pairs - case: statusListFile and statusList
|
|
2242
|
+
if (args.options?.statusListFile && args.options?.statusList) {
|
|
2243
|
+
throw new Error('[did-provider-cheqd]: unsuspension: statusListFile and statusList are mutually exclusive')
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
// validate args in pairs - case: statusListFile and fetchList
|
|
2247
|
+
if (args.options?.statusListFile && args.options?.fetchList) {
|
|
2248
|
+
throw new Error('[did-provider-cheqd]: unsuspension: statusListFile and fetchList are mutually exclusive')
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
// validate args in pairs - case: statusList and fetchList
|
|
2252
|
+
if (args.options?.statusList && args.options?.fetchList) {
|
|
2253
|
+
throw new Error('[did-provider-cheqd]: unsuspension: statusList and fetchList are mutually exclusive')
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
// validate args in pairs - case: publish
|
|
2257
|
+
if (args.options?.publish && !args.fetchList && !(args.options?.statusListFile || args.options?.statusList)) {
|
|
2258
|
+
throw new Error('[did-provider-cheqd]: unsuspension: publish requires statusListFile or statusList, if fetchList is disabled')
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
// suspend credentials
|
|
2262
|
+
return await Cheqd.unsuspendCredentials(credentials, {
|
|
2263
|
+
...args.options,
|
|
2264
|
+
topArgs: args,
|
|
2265
|
+
publishOptions: {
|
|
2266
|
+
context,
|
|
2267
|
+
resourceId: args?.options?.resourceId,
|
|
2268
|
+
resourceVersion: args?.options?.resourceVersion,
|
|
2269
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
2270
|
+
signInputs: args?.options?.signInputs,
|
|
2271
|
+
fee: args?.options?.fee
|
|
2272
|
+
}
|
|
2273
|
+
})
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
private async TransactVerifierPaysIssuer(args: ICheqdTransactVerifierPaysIssuerArgs, context: IContext): Promise<TransactionResult> {
|
|
2277
|
+
try {
|
|
2278
|
+
// delegate to provider
|
|
2279
|
+
const transactionResult = await this.didProvider.transactSendTokens({
|
|
2280
|
+
recipientAddress: args.recipientAddress,
|
|
2281
|
+
amount: args.amount,
|
|
2282
|
+
memoNonce: args.memoNonce,
|
|
2283
|
+
txBytes: args.txBytes,
|
|
2284
|
+
})
|
|
2285
|
+
|
|
2286
|
+
// return transaction result
|
|
2287
|
+
return {
|
|
2288
|
+
successful: !transactionResult.code,
|
|
2289
|
+
transactionHash: transactionResult.transactionHash,
|
|
2290
|
+
events: transactionResult.events,
|
|
2291
|
+
rawLog: transactionResult.rawLog,
|
|
2292
|
+
txResponse: args?.returnTxResponse ? transactionResult : undefined
|
|
2293
|
+
} satisfies TransactionResult
|
|
2294
|
+
} catch (error) {
|
|
1692
2295
|
// return error
|
|
1693
2296
|
return {
|
|
1694
2297
|
successful: false,
|
|
@@ -1744,113 +2347,739 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1744
2347
|
}
|
|
1745
2348
|
}
|
|
1746
2349
|
|
|
1747
|
-
// validate access control conditions components - case: senderAddress
|
|
1748
|
-
if (!args.senderAddress) {
|
|
1749
|
-
throw new Error('[did-provider-cheqd]: observation: senderAddress is required')
|
|
1750
|
-
}
|
|
2350
|
+
// validate access control conditions components - case: senderAddress
|
|
2351
|
+
if (!args.senderAddress) {
|
|
2352
|
+
throw new Error('[did-provider-cheqd]: observation: senderAddress is required')
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
// validate access control conditions components - case: recipientAddress
|
|
2356
|
+
if (!args.recipientAddress) {
|
|
2357
|
+
throw new Error('[did-provider-cheqd]: observation: recipientAddress is required')
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
// validate access control conditions components - case: amount
|
|
2361
|
+
if (!args.amount || !args.amount.amount || !args.amount.denom || args.amount.denom !== 'ncheq') {
|
|
2362
|
+
throw new Error('[did-provider-cheqd]: observation: amount is required, and must be an object with amount and denom valid string properties, amongst which denom must be `ncheq`')
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
// validate access control conditions components - case: memoNonce
|
|
2366
|
+
if (!args.memoNonce) {
|
|
2367
|
+
throw new Error('[did-provider-cheqd]: observation: memoNonce is required')
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
// validate access control conditions components - case: network
|
|
2371
|
+
if (!args.network) {
|
|
2372
|
+
throw new Error('[did-provider-cheqd]: observation: network is required')
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
try {
|
|
2376
|
+
// otherwise, construct url, as per components
|
|
2377
|
+
const url = `${DefaultRESTUrls[args.network]}/cosmos/tx/v1beta1/txs?events=transfer.recipient='${args.recipientAddress}'&events=transfer.sender='${args.senderAddress}'&events=transfer.amount='${args.amount.amount}${args.amount.denom}'`
|
|
2378
|
+
|
|
2379
|
+
// fetch relevant txs
|
|
2380
|
+
const txs = await (await fetch(url)).json() as ShallowTypedTxsResponse
|
|
2381
|
+
|
|
2382
|
+
// skim through txs for relevant events, in which case memoNonce is present and strict equals to the one provided
|
|
2383
|
+
const meetsConditionTxIndex = txs?.txs?.findIndex(tx => unescapeUnicode(tx.body.memo) === unescapeUnicode(args.memoNonce))
|
|
2384
|
+
|
|
2385
|
+
// define meetsCondition
|
|
2386
|
+
const meetsCondition = (typeof meetsConditionTxIndex !== 'undefined' && meetsConditionTxIndex !== -1)
|
|
2387
|
+
|
|
2388
|
+
// return observation result
|
|
2389
|
+
return {
|
|
2390
|
+
subscribed: true,
|
|
2391
|
+
meetsCondition: meetsCondition,
|
|
2392
|
+
transactionHash: meetsCondition ? txs!.tx_responses[meetsConditionTxIndex].txhash : undefined,
|
|
2393
|
+
events: meetsCondition ? txs!.tx_responses[meetsConditionTxIndex].events : undefined,
|
|
2394
|
+
rawLog: meetsCondition ? txs!.tx_responses[meetsConditionTxIndex].raw_log : undefined,
|
|
2395
|
+
txResponse: meetsCondition ? (args?.returnTxResponse ? txs!.tx_responses[meetsConditionTxIndex] : undefined) : undefined
|
|
2396
|
+
} satisfies ObservationResult
|
|
2397
|
+
} catch (error) {
|
|
2398
|
+
// return error
|
|
2399
|
+
return {
|
|
2400
|
+
subscribed: false,
|
|
2401
|
+
meetsCondition: false,
|
|
2402
|
+
error: error as IError
|
|
2403
|
+
} satisfies ObservationResult
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
static async revokeCredential(credential: VerifiableCredential, options?: ICheqdStatusList2021Options): Promise<RevocationResult> {
|
|
2408
|
+
try {
|
|
2409
|
+
// validate status purpose
|
|
2410
|
+
if (credential?.credentialStatus?.statusPurpose !== 'revocation') throw new Error('[did-provider-cheqd]: revocation: Invalid status purpose')
|
|
2411
|
+
|
|
2412
|
+
// fetch status list 2021
|
|
2413
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credential)) as StatusList2021Revocation
|
|
2414
|
+
|
|
2415
|
+
// early return, if encrypted and no decryption key provided
|
|
2416
|
+
if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey) throw new Error('[did-provider-cheqd]: revocation: symmetricKey is required, if status list 2021 is encrypted')
|
|
2417
|
+
|
|
2418
|
+
// fetch status list 2021 inscribed in credential
|
|
2419
|
+
const statusList2021 = options?.topArgs?.fetchList
|
|
2420
|
+
? (await async function () {
|
|
2421
|
+
// if not encrypted, return bitstring
|
|
2422
|
+
if (!publishedList.metadata.encrypted)
|
|
2423
|
+
return publishedList.metadata.encoding === 'base64url'
|
|
2424
|
+
? publishedList.StatusList2021.encodedList
|
|
2425
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
2426
|
+
|
|
2427
|
+
// otherwise, decrypt and return bitstring
|
|
2428
|
+
const scopedRawBlob = await toBlob(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding))
|
|
2429
|
+
|
|
2430
|
+
// decrypt
|
|
2431
|
+
return await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
2432
|
+
}())
|
|
2433
|
+
: (await async function () {
|
|
2434
|
+
// transcode to base64url, if needed
|
|
2435
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
2436
|
+
? publishedList.StatusList2021.encodedList
|
|
2437
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
2438
|
+
|
|
2439
|
+
// if status list 2021 is not fetched, read from file
|
|
2440
|
+
if (options?.statusListFile) {
|
|
2441
|
+
// if not encrypted, return bitstring
|
|
2442
|
+
if (!publishedList.metadata.encrypted) {
|
|
2443
|
+
// construct encoded status list
|
|
2444
|
+
const encoded = new StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode() as Bitstring
|
|
2445
|
+
|
|
2446
|
+
// validate against published list
|
|
2447
|
+
if (encoded !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
2448
|
+
|
|
2449
|
+
// return encoded
|
|
2450
|
+
return encoded
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
// otherwise, decrypt and return bitstring
|
|
2454
|
+
const scopedRawBlob = await toBlob(await Cheqd.getFile(options.statusListFile))
|
|
2455
|
+
|
|
2456
|
+
// decrypt
|
|
2457
|
+
const decrypted = await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
2458
|
+
|
|
2459
|
+
// validate against published list
|
|
2460
|
+
if (decrypted !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
2461
|
+
|
|
2462
|
+
// return decrypted
|
|
2463
|
+
return decrypted
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
if (!options?.statusListInlineBitstring) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided')
|
|
2467
|
+
|
|
2468
|
+
// validate against published list
|
|
2469
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021')
|
|
2470
|
+
|
|
2471
|
+
// otherwise, read from inline bitstring
|
|
2472
|
+
return options?.statusListInlineBitstring
|
|
2473
|
+
}())
|
|
2474
|
+
|
|
2475
|
+
// parse status list 2021
|
|
2476
|
+
const statusList = await StatusList.decode({ encodedList: statusList2021 })
|
|
2477
|
+
|
|
2478
|
+
// early exit, if credential is already revoked
|
|
2479
|
+
if (statusList.getStatus(Number(credential.credentialStatus.statusListIndex))) return { revoked: false }
|
|
2480
|
+
|
|
2481
|
+
// update revocation status
|
|
2482
|
+
statusList.setStatus(Number(credential.credentialStatus.statusListIndex), true)
|
|
2483
|
+
|
|
2484
|
+
// set in-memory status list ref
|
|
2485
|
+
const bitstring = await statusList.encode() as Bitstring
|
|
2486
|
+
|
|
2487
|
+
// cast top-level args
|
|
2488
|
+
const topArgs = options?.topArgs as ICheqdRevokeCredentialWithStatusList2021Args
|
|
2489
|
+
|
|
2490
|
+
// write status list 2021 to file, if provided
|
|
2491
|
+
if (topArgs?.writeToFile) {
|
|
2492
|
+
await Cheqd.writeFile(fromString(bitstring, 'base64url'), options?.statusListFile)
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
// publish status list 2021, if provided
|
|
2496
|
+
const published = topArgs?.publish
|
|
2497
|
+
? (await async function () {
|
|
2498
|
+
// fetch status list 2021 metadata
|
|
2499
|
+
const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(credential)
|
|
2500
|
+
|
|
2501
|
+
// publish status list 2021 as new version
|
|
2502
|
+
const scoped = topArgs.publishEncrypted
|
|
2503
|
+
? (await async function () {
|
|
2504
|
+
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
2505
|
+
const lit = await LitProtocol.create({
|
|
2506
|
+
chain: options?.topArgs?.bootstrapOptions?.chain,
|
|
2507
|
+
litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
|
|
2508
|
+
})
|
|
2509
|
+
|
|
2510
|
+
// encrypt
|
|
2511
|
+
const { encryptedString, encryptedSymmetricKey, symmetricKey } = await lit.encrypt(bitstring, options?.topArgs?.encryptionOptions?.unifiedAccessControlConditions, true)
|
|
2512
|
+
|
|
2513
|
+
// return tuple of publish result and encryption relevant metadata
|
|
2514
|
+
return [
|
|
2515
|
+
await Cheqd.publishStatusList2021(new Uint8Array(await encryptedString.arrayBuffer()), statusListMetadata, options?.publishOptions),
|
|
2516
|
+
{ encryptedString, encryptedSymmetricKey, symmetricKey: toString(symmetricKey!, 'hex') }
|
|
2517
|
+
]
|
|
2518
|
+
}())
|
|
2519
|
+
: (await async function () {
|
|
2520
|
+
// validate encoding, if provided
|
|
2521
|
+
if (options?.publishOptions?.statusListEncoding && !Object.values(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
2522
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding')
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
// validate validUntil, if provided
|
|
2526
|
+
if (options?.publishOptions?.statusListValidUntil) {
|
|
2527
|
+
// validate validUntil as string
|
|
2528
|
+
if (typeof options?.publishOptions?.statusListValidUntil !== 'string') throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)')
|
|
2529
|
+
|
|
2530
|
+
// validate validUntil as date
|
|
2531
|
+
if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil))) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)')
|
|
2532
|
+
|
|
2533
|
+
// validate validUntil as future date
|
|
2534
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) < new Date()) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)')
|
|
2535
|
+
|
|
2536
|
+
// validate validUntil towards validFrom
|
|
2537
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom)) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)')
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
// define status list content
|
|
2541
|
+
const content = {
|
|
2542
|
+
StatusList2021: {
|
|
2543
|
+
type: publishedList.StatusList2021.type,
|
|
2544
|
+
encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : toString(fromString(bitstring, 'base64url'), options!.publishOptions.statusListEncoding as DefaultStatusList2021Encoding),
|
|
2545
|
+
validFrom: publishedList.StatusList2021.validFrom,
|
|
2546
|
+
validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
|
|
2547
|
+
},
|
|
2548
|
+
metadata: {
|
|
2549
|
+
encoding: (options?.publishOptions?.statusListEncoding as DefaultStatusList2021Encoding | undefined) || publishedList.metadata.encoding,
|
|
2550
|
+
encrypted: false,
|
|
2551
|
+
}
|
|
2552
|
+
} satisfies StatusList2021Revocation
|
|
2553
|
+
|
|
2554
|
+
// return tuple of publish result and encryption relevant metadata
|
|
2555
|
+
return [
|
|
2556
|
+
await Cheqd.publishStatusList2021(fromString(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
|
|
2557
|
+
undefined
|
|
2558
|
+
]
|
|
2559
|
+
}())
|
|
2560
|
+
|
|
2561
|
+
// early exit, if publish failed
|
|
2562
|
+
if (!scoped[0]) throw new Error('[did-provider-cheqd]: revocation: Failed to publish status list 2021')
|
|
2563
|
+
|
|
2564
|
+
// return publish result
|
|
2565
|
+
return scoped
|
|
2566
|
+
}())
|
|
2567
|
+
: undefined
|
|
2568
|
+
|
|
2569
|
+
return {
|
|
2570
|
+
revoked: true,
|
|
2571
|
+
published: topArgs?.publish ? true : undefined,
|
|
2572
|
+
statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credential) as StatusList2021Revocation : undefined,
|
|
2573
|
+
encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await blobToHexString((published?.[1] as { encryptedString: Blob })?.encryptedString) : undefined,
|
|
2574
|
+
encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? (published?.[1] as { encryptedSymmetricKey: string })?.encryptedSymmetricKey : undefined,
|
|
2575
|
+
symmetricKey: topArgs?.returnSymmetricKey ? (published?.[1] as { symmetricKey: string })?.symmetricKey : undefined,
|
|
2576
|
+
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(credential) : undefined
|
|
2577
|
+
} satisfies RevocationResult
|
|
2578
|
+
} catch (error) {
|
|
2579
|
+
// silent fail + early exit
|
|
2580
|
+
console.error(error)
|
|
2581
|
+
|
|
2582
|
+
return { revoked: false, error: error as IError } satisfies RevocationResult
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
static async revokeCredentials(credentials: VerifiableCredential[], options?: ICheqdStatusList2021Options): Promise<BulkRevocationResult> {
|
|
2587
|
+
// validate credentials - case: empty
|
|
2588
|
+
if (!credentials.length || credentials.length === 0) throw new Error('[did-provider-cheqd]: revocation: No credentials provided')
|
|
2589
|
+
|
|
2590
|
+
// validate credentials - case: consistent issuer
|
|
2591
|
+
if (credentials.map((credential) => {
|
|
2592
|
+
return ((credential.issuer as { id: string }).id)
|
|
2593
|
+
? (credential.issuer as { id: string }).id
|
|
2594
|
+
: credential.issuer as string
|
|
2595
|
+
}).filter((value, _, self) => value && value !== self[0]).length > 0) throw new Error('[did-provider-cheqd]: revocation: Credentials must be issued by the same issuer')
|
|
2596
|
+
|
|
2597
|
+
// validate credentials - case: status list index
|
|
2598
|
+
if (credentials.map((credential) => credential.credentialStatus!.statusListIndex).filter((value, index, self) => self.indexOf(value) !== index).length > 0) throw new Error('[did-provider-cheqd]: revocation: Credentials must have unique status list index')
|
|
2599
|
+
|
|
2600
|
+
// validate credentials - case: status purpose
|
|
2601
|
+
if (!credentials.every((credential) => credential.credentialStatus?.statusPurpose === 'revocation')) throw new Error('[did-provider-cheqd]: revocation: Invalid status purpose')
|
|
2602
|
+
|
|
2603
|
+
// validate credentials - case: status list id
|
|
2604
|
+
const remote = credentials[0].credentialStatus?.id
|
|
2605
|
+
? (credentials[0].credentialStatus as { id: string }).id.split('#')[0]
|
|
2606
|
+
: (function(){
|
|
2607
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list id')
|
|
2608
|
+
}())
|
|
2609
|
+
|
|
2610
|
+
// validate credentials - case: status list id format
|
|
2611
|
+
if (!RemoteListPattern.test(remote)) throw new Error('[did-provider-cheqd]: revocation: Invalid status list id format: expected: https://<optional_subdomain>.<sld>.<tld>/1.0/identifiers/<did:cheqd:<namespace>:<method_specific_id>>?resourceName=<resource_name>&resourceType=<resource_type>')
|
|
2612
|
+
|
|
2613
|
+
if (!credentials.every((credential) => {
|
|
2614
|
+
return (credential.credentialStatus as { id: string }).id.split('#')[0] === remote
|
|
2615
|
+
})) throw new Error('[did-provider-cheqd]: revocation: Credentials must belong to the same status list')
|
|
2616
|
+
|
|
2617
|
+
// validate credentials - case: status list type
|
|
2618
|
+
if (!credentials.every((credential) => credential.credentialStatus?.type === 'StatusList2021Entry')) throw new Error('[did-provider-cheqd]: revocation: Invalid status list type')
|
|
2619
|
+
|
|
2620
|
+
try {
|
|
2621
|
+
// fetch status list 2021
|
|
2622
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credentials[0])) as StatusList2021Revocation
|
|
2623
|
+
|
|
2624
|
+
// early return, if encrypted and no decryption key provided
|
|
2625
|
+
if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey) throw new Error('[did-provider-cheqd]: revocation: symmetricKey is required, if status list 2021 is encrypted')
|
|
2626
|
+
|
|
2627
|
+
// fetch status list 2021 inscribed in credential
|
|
2628
|
+
const statusList2021 = options?.topArgs?.fetchList
|
|
2629
|
+
? (await async function () {
|
|
2630
|
+
// if not encrypted, return bitstring
|
|
2631
|
+
if (!publishedList.metadata.encrypted)
|
|
2632
|
+
return publishedList.metadata.encoding === 'base64url'
|
|
2633
|
+
? publishedList.StatusList2021.encodedList
|
|
2634
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
2635
|
+
|
|
2636
|
+
// otherwise, decrypt and return bitstring
|
|
2637
|
+
const scopedRawBlob = await toBlob(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding))
|
|
2638
|
+
|
|
2639
|
+
// decrypt
|
|
2640
|
+
return await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
2641
|
+
}())
|
|
2642
|
+
: (await async function () {
|
|
2643
|
+
// transcode to base64url, if needed
|
|
2644
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
2645
|
+
? publishedList.StatusList2021.encodedList
|
|
2646
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
2647
|
+
|
|
2648
|
+
// if status list 2021 is not fetched, read from file
|
|
2649
|
+
if (options?.statusListFile) {
|
|
2650
|
+
// if not encrypted, return bitstring
|
|
2651
|
+
if (!publishedList.metadata.encrypted) {
|
|
2652
|
+
// construct encoded status list
|
|
2653
|
+
const encoded = new StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode() as Bitstring
|
|
2654
|
+
|
|
2655
|
+
// validate against published list
|
|
2656
|
+
if (encoded !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
2657
|
+
|
|
2658
|
+
// return encoded
|
|
2659
|
+
return encoded
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
// otherwise, decrypt and return bitstring
|
|
2663
|
+
const scopedRawBlob = await toBlob(await Cheqd.getFile(options.statusListFile))
|
|
2664
|
+
|
|
2665
|
+
// decrypt
|
|
2666
|
+
const decrypted = await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
2667
|
+
|
|
2668
|
+
// validate against published list
|
|
2669
|
+
if (decrypted !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
2670
|
+
|
|
2671
|
+
// return decrypted
|
|
2672
|
+
return decrypted
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
if (!options?.statusListInlineBitstring) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided')
|
|
2676
|
+
|
|
2677
|
+
// validate against published list
|
|
2678
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021')
|
|
2679
|
+
|
|
2680
|
+
// otherwise, read from inline bitstring
|
|
2681
|
+
return options?.statusListInlineBitstring
|
|
2682
|
+
}())
|
|
2683
|
+
|
|
2684
|
+
// parse status list 2021
|
|
2685
|
+
const statusList = await StatusList.decode({ encodedList: statusList2021 })
|
|
2686
|
+
|
|
2687
|
+
// initiate bulk revocation
|
|
2688
|
+
const revoked = await Promise.allSettled(credentials.map((credential) => {
|
|
2689
|
+
return async function () {
|
|
2690
|
+
// early return, if no credential status
|
|
2691
|
+
if (!credential.credentialStatus) return { revoked: false }
|
|
2692
|
+
|
|
2693
|
+
// early exit, if credential is already revoked
|
|
2694
|
+
if (statusList.getStatus(Number(credential.credentialStatus.statusListIndex))) return { revoked: false }
|
|
2695
|
+
|
|
2696
|
+
// update revocation status
|
|
2697
|
+
statusList.setStatus(Number(credential.credentialStatus.statusListIndex), true)
|
|
2698
|
+
|
|
2699
|
+
// return revocation status
|
|
2700
|
+
return { revoked: true }
|
|
2701
|
+
}()
|
|
2702
|
+
})) satisfies PromiseSettledResult<RevocationResult>[]
|
|
2703
|
+
|
|
2704
|
+
// revert bulk ops, if some failed
|
|
2705
|
+
if (revoked.some((result) => result.status === 'fulfilled' && !result.value.revoked ))
|
|
2706
|
+
throw new Error(`[did-provider-cheqd]: revocation: Bulk revocation failed: already revoked credentials in revocation bundle: raw log: ${JSON.stringify(revoked.map((result) => ({ revoked: result.status === 'fulfilled' ? result.value.revoked : false })))}`)
|
|
2707
|
+
|
|
2708
|
+
// set in-memory status list ref
|
|
2709
|
+
const bitstring = await statusList.encode() as Bitstring
|
|
2710
|
+
|
|
2711
|
+
// cast top-level args
|
|
2712
|
+
const topArgs = options?.topArgs as ICheqdRevokeCredentialWithStatusList2021Args
|
|
2713
|
+
|
|
2714
|
+
// write status list 2021 to file, if provided
|
|
2715
|
+
if (topArgs?.writeToFile) {
|
|
2716
|
+
await Cheqd.writeFile(fromString(bitstring, 'base64url'), options?.statusListFile)
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
// publish status list 2021, if provided
|
|
2720
|
+
const published = topArgs?.publish
|
|
2721
|
+
? (await async function () {
|
|
2722
|
+
// fetch status list 2021 metadata
|
|
2723
|
+
const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(credentials[0])
|
|
2724
|
+
|
|
2725
|
+
// publish status list 2021 as new version
|
|
2726
|
+
const scoped = topArgs.publishEncrypted
|
|
2727
|
+
? (await async function () {
|
|
2728
|
+
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
2729
|
+
const lit = await LitProtocol.create({
|
|
2730
|
+
chain: options?.topArgs?.bootstrapOptions?.chain,
|
|
2731
|
+
litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
|
|
2732
|
+
})
|
|
2733
|
+
|
|
2734
|
+
// encrypt
|
|
2735
|
+
const { encryptedString, encryptedSymmetricKey, symmetricKey } = await lit.encrypt(bitstring, options?.topArgs?.encryptionOptions?.unifiedAccessControlConditions, true)
|
|
2736
|
+
|
|
2737
|
+
// return tuple of publish result and encryption relevant metadata
|
|
2738
|
+
return [
|
|
2739
|
+
await Cheqd.publishStatusList2021(new Uint8Array(await encryptedString.arrayBuffer()), statusListMetadata, options?.publishOptions),
|
|
2740
|
+
{ encryptedString, encryptedSymmetricKey, symmetricKey: toString(symmetricKey!, 'hex') }
|
|
2741
|
+
]
|
|
2742
|
+
}())
|
|
2743
|
+
: (await async function () {
|
|
2744
|
+
// validate encoding, if provided
|
|
2745
|
+
if (options?.publishOptions?.statusListEncoding && !Object.values(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
2746
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding')
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
// validate validUntil, if provided
|
|
2750
|
+
if (options?.publishOptions?.statusListValidUntil) {
|
|
2751
|
+
// validate validUntil as string
|
|
2752
|
+
if (typeof options?.publishOptions?.statusListValidUntil !== 'string') throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)')
|
|
2753
|
+
|
|
2754
|
+
// validate validUntil as date
|
|
2755
|
+
if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil))) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)')
|
|
2756
|
+
|
|
2757
|
+
// validate validUntil as future date
|
|
2758
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) < new Date()) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)')
|
|
2759
|
+
|
|
2760
|
+
// validate validUntil towards validFrom
|
|
2761
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom)) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)')
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
// define status list content
|
|
2765
|
+
const content = {
|
|
2766
|
+
StatusList2021: {
|
|
2767
|
+
type: publishedList.StatusList2021.type,
|
|
2768
|
+
encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : toString(fromString(bitstring, 'base64url'), options!.publishOptions.statusListEncoding as DefaultStatusList2021Encoding),
|
|
2769
|
+
validFrom: publishedList.StatusList2021.validFrom,
|
|
2770
|
+
validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
|
|
2771
|
+
},
|
|
2772
|
+
metadata: {
|
|
2773
|
+
encoding: (options?.publishOptions?.statusListEncoding as DefaultStatusList2021Encoding | undefined) || publishedList.metadata.encoding,
|
|
2774
|
+
encrypted: false,
|
|
2775
|
+
}
|
|
2776
|
+
} satisfies StatusList2021Revocation
|
|
2777
|
+
|
|
2778
|
+
// return tuple of publish result and encryption relevant metadata
|
|
2779
|
+
return [
|
|
2780
|
+
await Cheqd.publishStatusList2021(fromString(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
|
|
2781
|
+
undefined
|
|
2782
|
+
]
|
|
2783
|
+
}())
|
|
2784
|
+
|
|
2785
|
+
// early exit, if publish failed
|
|
2786
|
+
if (!scoped[0]) throw new Error('[did-provider-cheqd]: revocation: Failed to publish status list 2021')
|
|
2787
|
+
|
|
2788
|
+
// return publish result
|
|
2789
|
+
return scoped
|
|
2790
|
+
}())
|
|
2791
|
+
: undefined
|
|
2792
|
+
|
|
2793
|
+
return {
|
|
2794
|
+
revoked: revoked.map((result) => result.status === 'fulfilled' ? result.value.revoked : false),
|
|
2795
|
+
published: topArgs?.publish ? true : undefined,
|
|
2796
|
+
statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credentials[0]) as StatusList2021Revocation : undefined,
|
|
2797
|
+
encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await blobToHexString((published?.[1] as { encryptedString: Blob })?.encryptedString) : undefined,
|
|
2798
|
+
encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? (published?.[1] as { encryptedSymmetricKey: string })?.encryptedSymmetricKey : undefined,
|
|
2799
|
+
symmetricKey: topArgs?.returnSymmetricKey ? (published?.[1] as { symmetricKey: string })?.symmetricKey : undefined,
|
|
2800
|
+
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(credentials[0]) : undefined
|
|
2801
|
+
} satisfies BulkRevocationResult
|
|
2802
|
+
} catch (error) {
|
|
2803
|
+
// silent fail + early exit
|
|
2804
|
+
console.error(error)
|
|
2805
|
+
|
|
2806
|
+
return { revoked: [], error: error as IError } satisfies BulkRevocationResult
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
static async suspendCredential(credential: VerifiableCredential, options?: ICheqdStatusList2021Options): Promise<SuspensionResult> {
|
|
2811
|
+
try {
|
|
2812
|
+
// validate status purpose
|
|
2813
|
+
if (credential?.credentialStatus?.statusPurpose !== 'suspension') throw new Error('[did-provider-cheqd]: suspension: Invalid status purpose')
|
|
2814
|
+
|
|
2815
|
+
// fetch status list 2021
|
|
2816
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credential)) as StatusList2021Suspension
|
|
2817
|
+
|
|
2818
|
+
// early return, if encrypted and no decryption key provided
|
|
2819
|
+
if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey) throw new Error('[did-provider-cheqd]: suspension: symmetricKey is required, if status list 2021 is encrypted')
|
|
2820
|
+
|
|
2821
|
+
// fetch status list 2021 inscribed in credential
|
|
2822
|
+
const statusList2021 = options?.topArgs?.fetchList
|
|
2823
|
+
? (await async function () {
|
|
2824
|
+
// if not encrypted, return bitstring
|
|
2825
|
+
if (!publishedList.metadata.encrypted)
|
|
2826
|
+
return publishedList.metadata.encoding === 'base64url'
|
|
2827
|
+
? publishedList.StatusList2021.encodedList
|
|
2828
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
2829
|
+
|
|
2830
|
+
// otherwise, decrypt and return bitstring
|
|
2831
|
+
const scopedRawBlob = await toBlob(await Cheqd.fetchStatusList2021(credential, true) as Uint8Array)
|
|
2832
|
+
|
|
2833
|
+
// decrypt
|
|
2834
|
+
return await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
2835
|
+
}())
|
|
2836
|
+
: (await async function () {
|
|
2837
|
+
// transcode to base64url, if needed
|
|
2838
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
2839
|
+
? publishedList.StatusList2021.encodedList
|
|
2840
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
2841
|
+
|
|
2842
|
+
// if status list 2021 is not fetched, read from file
|
|
2843
|
+
if (options?.statusListFile) {
|
|
2844
|
+
// if not encrypted, return bitstring
|
|
2845
|
+
if (!publishedList.metadata.encrypted) {
|
|
2846
|
+
// construct encoded status list
|
|
2847
|
+
const encoded = new StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode() as Bitstring
|
|
2848
|
+
|
|
2849
|
+
// validate against published list
|
|
2850
|
+
if (encoded !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
2851
|
+
|
|
2852
|
+
// return encoded
|
|
2853
|
+
return encoded
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
// otherwise, decrypt and return bitstring
|
|
2857
|
+
const scopedRawBlob = await toBlob(await Cheqd.getFile(options.statusListFile))
|
|
2858
|
+
|
|
2859
|
+
// decrypt
|
|
2860
|
+
const decrypted = await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
2861
|
+
|
|
2862
|
+
// validate against published list
|
|
2863
|
+
if (decrypted !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
2864
|
+
|
|
2865
|
+
// return decrypted
|
|
2866
|
+
return decrypted
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
if (!options?.statusListInlineBitstring) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided')
|
|
2870
|
+
|
|
2871
|
+
// validate against published list
|
|
2872
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021')
|
|
2873
|
+
|
|
2874
|
+
// otherwise, read from inline bitstring
|
|
2875
|
+
return options?.statusListInlineBitstring
|
|
2876
|
+
}())
|
|
2877
|
+
|
|
2878
|
+
// parse status list 2021
|
|
2879
|
+
const statusList = await StatusList.decode({ encodedList: statusList2021 })
|
|
2880
|
+
|
|
2881
|
+
// early exit, if already suspended
|
|
2882
|
+
if (statusList.getStatus(Number(credential.credentialStatus.statusListIndex))) return { suspended: true } satisfies SuspensionResult
|
|
2883
|
+
|
|
2884
|
+
// update suspension status
|
|
2885
|
+
statusList.setStatus(Number(credential.credentialStatus.statusListIndex), true)
|
|
2886
|
+
|
|
2887
|
+
// set in-memory status list ref
|
|
2888
|
+
const bitstring = await statusList.encode() as Bitstring
|
|
2889
|
+
|
|
2890
|
+
// cast top-level args
|
|
2891
|
+
const topArgs = options?.topArgs as ICheqdSuspendCredentialWithStatusList2021Args
|
|
2892
|
+
|
|
2893
|
+
// write status list 2021 to file, if provided
|
|
2894
|
+
if (topArgs?.writeToFile) {
|
|
2895
|
+
await Cheqd.writeFile(fromString(bitstring, 'base64url'), options?.statusListFile)
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
// publish status list 2021, if provided
|
|
2899
|
+
const published = topArgs?.publish
|
|
2900
|
+
? (await async function () {
|
|
2901
|
+
// fetch status list 2021 metadata
|
|
2902
|
+
const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(credential)
|
|
2903
|
+
|
|
2904
|
+
// publish status list 2021 as new version
|
|
2905
|
+
const scoped = topArgs.publishEncrypted
|
|
2906
|
+
? (await async function () {
|
|
2907
|
+
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
2908
|
+
const lit = await LitProtocol.create({
|
|
2909
|
+
chain: options?.topArgs?.bootstrapOptions?.chain,
|
|
2910
|
+
litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
|
|
2911
|
+
})
|
|
1751
2912
|
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
throw new Error('[did-provider-cheqd]: observation: recipientAddress is required')
|
|
1755
|
-
}
|
|
2913
|
+
// encrypt
|
|
2914
|
+
const { encryptedString, encryptedSymmetricKey, symmetricKey } = await lit.encrypt(bitstring, options?.topArgs?.encryptionOptions?.unifiedAccessControlConditions, true)
|
|
1756
2915
|
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
2916
|
+
// return tuple of publish result and encryption relevant metadata
|
|
2917
|
+
return [
|
|
2918
|
+
await Cheqd.publishStatusList2021(new Uint8Array(await encryptedString.arrayBuffer()), statusListMetadata, options?.publishOptions),
|
|
2919
|
+
{ encryptedString, encryptedSymmetricKey, symmetricKey: toString(symmetricKey!, 'hex') }
|
|
2920
|
+
]
|
|
2921
|
+
}())
|
|
2922
|
+
: (await async function () {
|
|
2923
|
+
// validate encoding, if provided
|
|
2924
|
+
if (options?.publishOptions?.statusListEncoding && !Object.values(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
2925
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding')
|
|
2926
|
+
}
|
|
1761
2927
|
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
2928
|
+
// validate validUntil, if provided
|
|
2929
|
+
if (options?.publishOptions?.statusListValidUntil) {
|
|
2930
|
+
// validate validUntil as string
|
|
2931
|
+
if (typeof options?.publishOptions?.statusListValidUntil !== 'string') throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)')
|
|
1766
2932
|
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
throw new Error('[did-provider-cheqd]: observation: network is required')
|
|
1770
|
-
}
|
|
2933
|
+
// validate validUntil as date
|
|
2934
|
+
if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil))) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)')
|
|
1771
2935
|
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
const url = `${DefaultRESTUrls[args.network]}/cosmos/tx/v1beta1/txs?events=transfer.recipient='${args.recipientAddress}'&events=transfer.sender='${args.senderAddress}'&events=transfer.amount='${args.amount.amount}${args.amount.denom}'`
|
|
2936
|
+
// validate validUntil as future date
|
|
2937
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) < new Date()) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)')
|
|
1775
2938
|
|
|
1776
|
-
|
|
1777
|
-
|
|
2939
|
+
// validate validUntil towards validFrom
|
|
2940
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom)) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)')
|
|
2941
|
+
}
|
|
1778
2942
|
|
|
1779
|
-
|
|
1780
|
-
|
|
2943
|
+
// define status list content
|
|
2944
|
+
const content = {
|
|
2945
|
+
StatusList2021: {
|
|
2946
|
+
type: publishedList.StatusList2021.type,
|
|
2947
|
+
encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : toString(fromString(bitstring, 'base64url'), options!.publishOptions.statusListEncoding as DefaultStatusList2021Encoding),
|
|
2948
|
+
validFrom: publishedList.StatusList2021.validFrom,
|
|
2949
|
+
validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
|
|
2950
|
+
},
|
|
2951
|
+
metadata: {
|
|
2952
|
+
encoding: (options?.publishOptions?.statusListEncoding as DefaultStatusList2021Encoding | undefined) || publishedList.metadata.encoding,
|
|
2953
|
+
encrypted: false,
|
|
2954
|
+
}
|
|
2955
|
+
} satisfies StatusList2021Suspension
|
|
1781
2956
|
|
|
1782
|
-
|
|
1783
|
-
|
|
2957
|
+
// return tuple of publish result and encryption relevant metadata
|
|
2958
|
+
return [
|
|
2959
|
+
await Cheqd.publishStatusList2021(fromString(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
|
|
2960
|
+
undefined
|
|
2961
|
+
]
|
|
2962
|
+
}())
|
|
2963
|
+
|
|
2964
|
+
// early exit, if publish failed
|
|
2965
|
+
if (!scoped[0]) throw new Error('[did-provider-cheqd]: suspension: Failed to publish status list 2021')
|
|
2966
|
+
|
|
2967
|
+
// return publish result
|
|
2968
|
+
return scoped
|
|
2969
|
+
}())
|
|
2970
|
+
: undefined
|
|
1784
2971
|
|
|
1785
|
-
// return observation result
|
|
1786
2972
|
return {
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
2973
|
+
suspended: true,
|
|
2974
|
+
published: topArgs?.publish ? true : undefined,
|
|
2975
|
+
statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credential) as StatusList2021Suspension : undefined,
|
|
2976
|
+
encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await blobToHexString((published?.[1] as { encryptedString: Blob })?.encryptedString) : undefined,
|
|
2977
|
+
encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? (published?.[1] as { encryptedSymmetricKey: string })?.encryptedSymmetricKey : undefined,
|
|
2978
|
+
symmetricKey: topArgs?.returnSymmetricKey ? (published?.[1] as { symmetricKey: string })?.symmetricKey : undefined,
|
|
2979
|
+
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(credential) : undefined
|
|
2980
|
+
} satisfies SuspensionResult
|
|
1794
2981
|
} catch (error) {
|
|
1795
|
-
//
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
error: error as IError
|
|
1800
|
-
} satisfies ObservationResult
|
|
2982
|
+
// silent fail + early exit
|
|
2983
|
+
console.error(error)
|
|
2984
|
+
|
|
2985
|
+
return { suspended: false, error: error as IError } satisfies SuspensionResult
|
|
1801
2986
|
}
|
|
1802
2987
|
}
|
|
1803
2988
|
|
|
1804
|
-
static async
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
if (credential?.credentialStatus?.statusPurpose !== 'revocation') throw new Error('[did-provider-cheqd]: revocation: Invalid status purpose')
|
|
2989
|
+
static async suspendCredentials(credentials: VerifiableCredential[], options?: ICheqdStatusList2021Options): Promise<BulkSuspensionResult> {
|
|
2990
|
+
// validate credentials - case: empty
|
|
2991
|
+
if (!credentials.length || credentials.length === 0) throw new Error('[did-provider-cheqd]: suspension: No credentials provided')
|
|
1808
2992
|
|
|
1809
|
-
|
|
1810
|
-
|
|
2993
|
+
// validate credentials - case: consistent issuer
|
|
2994
|
+
if (credentials.map((credential) => {
|
|
2995
|
+
return ((credential.issuer as { id: string }).id)
|
|
2996
|
+
? (credential.issuer as { id: string }).id
|
|
2997
|
+
: credential.issuer as string
|
|
2998
|
+
}).filter((value, _, self) => value && value !== self[0]).length > 0) throw new Error('[did-provider-cheqd]: suspension: Credentials must be issued by the same issuer')
|
|
1811
2999
|
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
3000
|
+
// validate credentials - case: status list index
|
|
3001
|
+
if (credentials.map((credential) => credential.credentialStatus!.statusListIndex).filter((value, index, self) => self.indexOf(value) !== index).length > 0) throw new Error('[did-provider-cheqd]: suspension: Credentials must have unique status list index')
|
|
3002
|
+
|
|
3003
|
+
// validate credentials - case: status purpose
|
|
3004
|
+
if (!credentials.every((credential) => credential.credentialStatus?.statusPurpose === 'suspension')) throw new Error('[did-provider-cheqd]: suspension: Invalid status purpose')
|
|
3005
|
+
|
|
3006
|
+
// validate credentials - case: status list id
|
|
3007
|
+
const remote = credentials[0].credentialStatus?.id
|
|
3008
|
+
? (credentials[0].credentialStatus as { id: string }).id.split('#')[0]
|
|
3009
|
+
: (function(){
|
|
3010
|
+
throw new Error('[did-provider-cheqd]: suspension: Invalid status list id')
|
|
3011
|
+
}())
|
|
3012
|
+
|
|
3013
|
+
// validate credentials - case: status list id format
|
|
3014
|
+
if (!RemoteListPattern.test(remote)) throw new Error('[did-provider-cheqd]: suspension: Invalid status list id format: expected: https://<optional_subdomain>.<sld>.<tld>/1.0/identifiers/<did:cheqd:<namespace>:<method_specific_id>>?resourceName=<resource_name>&resourceType=<resource_type>')
|
|
3015
|
+
|
|
3016
|
+
if (!credentials.every((credential) => {
|
|
3017
|
+
return (credential.credentialStatus as { id: string }).id.split('#')[0] === remote
|
|
3018
|
+
})) throw new Error('[did-provider-cheqd]: suspension: Credentials must belong to the same status list')
|
|
3019
|
+
|
|
3020
|
+
// validate credentials - case: status list type
|
|
3021
|
+
if (!credentials.every((credential) => credential.credentialStatus?.type === 'StatusList2021Entry')) throw new Error('[did-provider-cheqd]: suspension: Invalid status list type')
|
|
3022
|
+
|
|
3023
|
+
try {
|
|
3024
|
+
// fetch status list 2021
|
|
3025
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credentials[0])) as StatusList2021Suspension
|
|
1823
3026
|
|
|
1824
3027
|
// early return, if encrypted and no decryption key provided
|
|
1825
|
-
if (
|
|
3028
|
+
if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey) throw new Error('[did-provider-cheqd]: suspension: symmetricKey is required, if status list 2021 is encrypted')
|
|
1826
3029
|
|
|
1827
3030
|
// fetch status list 2021 inscribed in credential
|
|
1828
3031
|
const statusList2021 = options?.topArgs?.fetchList
|
|
1829
3032
|
? (await async function () {
|
|
1830
3033
|
// if not encrypted, return bitstring
|
|
1831
|
-
if (!
|
|
3034
|
+
if (!publishedList.metadata.encrypted)
|
|
3035
|
+
return publishedList.metadata.encoding === 'base64url'
|
|
3036
|
+
? publishedList.StatusList2021.encodedList
|
|
3037
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
1832
3038
|
|
|
1833
3039
|
// otherwise, decrypt and return bitstring
|
|
1834
|
-
const scopedRawBlob = await toBlob(await Cheqd.fetchStatusList2021(
|
|
3040
|
+
const scopedRawBlob = await toBlob(await Cheqd.fetchStatusList2021(credentials[0], true) as Uint8Array)
|
|
1835
3041
|
|
|
1836
3042
|
// decrypt
|
|
1837
3043
|
return await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
1838
3044
|
}())
|
|
1839
3045
|
: (await async function () {
|
|
3046
|
+
// transcode to base64url, if needed
|
|
3047
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
3048
|
+
? publishedList.StatusList2021.encodedList
|
|
3049
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
3050
|
+
|
|
1840
3051
|
// if status list 2021 is not fetched, read from file
|
|
1841
3052
|
if (options?.statusListFile) {
|
|
1842
3053
|
// if not encrypted, return bitstring
|
|
1843
|
-
if (!
|
|
3054
|
+
if (!publishedList.metadata.encrypted) {
|
|
3055
|
+
// construct encoded status list
|
|
3056
|
+
const encoded = new StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode() as Bitstring
|
|
3057
|
+
|
|
3058
|
+
// validate against published list
|
|
3059
|
+
if (encoded !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
3060
|
+
|
|
3061
|
+
// return encoded
|
|
3062
|
+
return encoded
|
|
3063
|
+
}
|
|
1844
3064
|
|
|
1845
3065
|
// otherwise, decrypt and return bitstring
|
|
1846
3066
|
const scopedRawBlob = await toBlob(await Cheqd.getFile(options.statusListFile))
|
|
1847
3067
|
|
|
1848
3068
|
// decrypt
|
|
1849
|
-
|
|
3069
|
+
const decrypted = await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
3070
|
+
|
|
3071
|
+
// validate against published list
|
|
3072
|
+
if (decrypted !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
3073
|
+
|
|
3074
|
+
// return decrypted
|
|
3075
|
+
return decrypted
|
|
1850
3076
|
}
|
|
1851
3077
|
|
|
1852
3078
|
if (!options?.statusListInlineBitstring) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided')
|
|
1853
3079
|
|
|
3080
|
+
// validate against published list
|
|
3081
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021')
|
|
3082
|
+
|
|
1854
3083
|
// otherwise, read from inline bitstring
|
|
1855
3084
|
return options?.statusListInlineBitstring
|
|
1856
3085
|
}())
|
|
@@ -1858,11 +3087,26 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1858
3087
|
// parse status list 2021
|
|
1859
3088
|
const statusList = await StatusList.decode({ encodedList: statusList2021 })
|
|
1860
3089
|
|
|
1861
|
-
//
|
|
1862
|
-
|
|
3090
|
+
// initiate bulk suspension
|
|
3091
|
+
const suspended = await Promise.allSettled(credentials.map((credential) => {
|
|
3092
|
+
return async function () {
|
|
3093
|
+
// early return, if no credential status
|
|
3094
|
+
if (!credential.credentialStatus) return { suspended: false }
|
|
1863
3095
|
|
|
1864
|
-
|
|
1865
|
-
|
|
3096
|
+
// early exit, if credential is already suspended
|
|
3097
|
+
if (statusList.getStatus(Number(credential.credentialStatus.statusListIndex))) return { suspended: false }
|
|
3098
|
+
|
|
3099
|
+
// update suspension status
|
|
3100
|
+
statusList.setStatus(Number(credential.credentialStatus.statusListIndex), true)
|
|
3101
|
+
|
|
3102
|
+
// return suspension status
|
|
3103
|
+
return { suspended: true }
|
|
3104
|
+
}()
|
|
3105
|
+
})) satisfies PromiseSettledResult<SuspensionResult>[]
|
|
3106
|
+
|
|
3107
|
+
// revert bulk ops, if some failed
|
|
3108
|
+
if (suspended.some((result) => result.status === 'fulfilled' && !result.value.suspended ))
|
|
3109
|
+
throw new Error(`[did-provider-cheqd]: suspension: Bulk suspension failed: already suspended credentials in suspension bundle: raw log: ${JSON.stringify(suspended.map((result) => ({ suspended: result.status === 'fulfilled' ? result.value.suspended : false })))}`)
|
|
1866
3110
|
|
|
1867
3111
|
// set in-memory status list ref
|
|
1868
3112
|
const bitstring = await statusList.encode() as Bitstring
|
|
@@ -1879,7 +3123,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1879
3123
|
const published = topArgs?.publish
|
|
1880
3124
|
? (await async function () {
|
|
1881
3125
|
// fetch status list 2021 metadata
|
|
1882
|
-
const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(
|
|
3126
|
+
const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(credentials[0])
|
|
1883
3127
|
|
|
1884
3128
|
// publish status list 2021 as new version
|
|
1885
3129
|
const scoped = topArgs.publishEncrypted
|
|
@@ -1899,10 +3143,50 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1899
3143
|
{ encryptedString, encryptedSymmetricKey, symmetricKey: toString(symmetricKey!, 'hex') }
|
|
1900
3144
|
]
|
|
1901
3145
|
}())
|
|
1902
|
-
:
|
|
3146
|
+
: (await async function () {
|
|
3147
|
+
// validate encoding, if provided
|
|
3148
|
+
if (options?.publishOptions?.statusListEncoding && !Object.values(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
3149
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding')
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
// validate validUntil, if provided
|
|
3153
|
+
if (options?.publishOptions?.statusListValidUntil) {
|
|
3154
|
+
// validate validUntil as string
|
|
3155
|
+
if (typeof options?.publishOptions?.statusListValidUntil !== 'string') throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)')
|
|
3156
|
+
|
|
3157
|
+
// validate validUntil as date
|
|
3158
|
+
if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil))) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)')
|
|
3159
|
+
|
|
3160
|
+
// validate validUntil as future date
|
|
3161
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) < new Date()) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)')
|
|
3162
|
+
|
|
3163
|
+
// validate validUntil towards validFrom
|
|
3164
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom)) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)')
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
// define status list content
|
|
3168
|
+
const content = {
|
|
3169
|
+
StatusList2021: {
|
|
3170
|
+
type: publishedList.StatusList2021.type,
|
|
3171
|
+
encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : toString(fromString(bitstring, 'base64url'), options!.publishOptions.statusListEncoding as DefaultStatusList2021Encoding),
|
|
3172
|
+
validFrom: publishedList.StatusList2021.validFrom,
|
|
3173
|
+
validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
|
|
3174
|
+
},
|
|
3175
|
+
metadata: {
|
|
3176
|
+
encoding: (options?.publishOptions?.statusListEncoding as DefaultStatusList2021Encoding | undefined) || publishedList.metadata.encoding,
|
|
3177
|
+
encrypted: false,
|
|
3178
|
+
}
|
|
3179
|
+
} satisfies StatusList2021Suspension
|
|
3180
|
+
|
|
3181
|
+
// return tuple of publish result and encryption relevant metadata
|
|
3182
|
+
return [
|
|
3183
|
+
await Cheqd.publishStatusList2021(fromString(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
|
|
3184
|
+
undefined
|
|
3185
|
+
]
|
|
3186
|
+
}())
|
|
1903
3187
|
|
|
1904
3188
|
// early exit, if publish failed
|
|
1905
|
-
if (!scoped[0]) throw new Error('[did-provider-cheqd]:
|
|
3189
|
+
if (!scoped[0]) throw new Error('[did-provider-cheqd]: suspension: Failed to publish status list 2021')
|
|
1906
3190
|
|
|
1907
3191
|
// return publish result
|
|
1908
3192
|
return scoped
|
|
@@ -1910,50 +3194,41 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1910
3194
|
: undefined
|
|
1911
3195
|
|
|
1912
3196
|
return {
|
|
1913
|
-
|
|
3197
|
+
suspended: suspended.map((result) => result.status === 'fulfilled' ? result.value.suspended : false),
|
|
1914
3198
|
published: topArgs?.publish ? true : undefined,
|
|
1915
|
-
statusList: topArgs?.returnUpdatedStatusList ?
|
|
3199
|
+
statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credentials[0]) as StatusList2021Suspension : undefined,
|
|
1916
3200
|
encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await blobToHexString((published?.[1] as { encryptedString: Blob })?.encryptedString) : undefined,
|
|
1917
3201
|
encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? (published?.[1] as { encryptedSymmetricKey: string })?.encryptedSymmetricKey : undefined,
|
|
1918
3202
|
symmetricKey: topArgs?.returnSymmetricKey ? (published?.[1] as { symmetricKey: string })?.symmetricKey : undefined,
|
|
1919
|
-
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(
|
|
1920
|
-
} satisfies
|
|
3203
|
+
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(credentials[0]) : undefined
|
|
3204
|
+
} satisfies BulkSuspensionResult
|
|
1921
3205
|
} catch (error) {
|
|
1922
|
-
// silent fail + early exit
|
|
3206
|
+
// silent fail + early exit
|
|
1923
3207
|
console.error(error)
|
|
1924
3208
|
|
|
1925
|
-
return {
|
|
3209
|
+
return { suspended: [], error: error as IError } satisfies BulkSuspensionResult
|
|
1926
3210
|
}
|
|
1927
3211
|
}
|
|
1928
3212
|
|
|
1929
|
-
static async
|
|
3213
|
+
static async unsuspendCredential(credential: VerifiableCredential, options?: ICheqdStatusList2021Options): Promise<UnsuspensionResult> {
|
|
1930
3214
|
try {
|
|
1931
3215
|
// validate status purpose
|
|
1932
|
-
if (credential?.credentialStatus?.statusPurpose !== 'suspension') throw new Error('[did-provider-cheqd]:
|
|
1933
|
-
|
|
1934
|
-
// fetch status list 2021 metadata
|
|
1935
|
-
const metadata = (await Cheqd.fetchStatusList2021Metadata(credential))
|
|
3216
|
+
if (credential?.credentialStatus?.statusPurpose !== 'suspension') throw new Error('[did-provider-cheqd]: unsuspension: Invalid status purpose')
|
|
1936
3217
|
|
|
1937
|
-
//
|
|
1938
|
-
const
|
|
1939
|
-
switch (metadata.mediaType) {
|
|
1940
|
-
case 'application/octet-stream':
|
|
1941
|
-
return true
|
|
1942
|
-
case 'application/gzip':
|
|
1943
|
-
return false
|
|
1944
|
-
default:
|
|
1945
|
-
throw new Error(`[did-provider-cheqd]: suspension: Unsupported media type: ${metadata.mediaType}`)
|
|
1946
|
-
}
|
|
1947
|
-
}()
|
|
3218
|
+
// fetch status list 2021
|
|
3219
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credential)) as StatusList2021Suspension
|
|
1948
3220
|
|
|
1949
3221
|
// early return, if encrypted and no decryption key provided
|
|
1950
|
-
if (
|
|
3222
|
+
if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey) throw new Error('[did-provider-cheqd]: suspension: symmetricKey is required, if status list 2021 is encrypted')
|
|
1951
3223
|
|
|
1952
3224
|
// fetch status list 2021 inscribed in credential
|
|
1953
3225
|
const statusList2021 = options?.topArgs?.fetchList
|
|
1954
3226
|
? (await async function () {
|
|
1955
3227
|
// if not encrypted, return bitstring
|
|
1956
|
-
if (!
|
|
3228
|
+
if (!publishedList.metadata.encrypted)
|
|
3229
|
+
return publishedList.metadata.encoding === 'base64url'
|
|
3230
|
+
? publishedList.StatusList2021.encodedList
|
|
3231
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
1957
3232
|
|
|
1958
3233
|
// otherwise, decrypt and return bitstring
|
|
1959
3234
|
const scopedRawBlob = await toBlob(await Cheqd.fetchStatusList2021(credential, true) as Uint8Array)
|
|
@@ -1962,19 +3237,42 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1962
3237
|
return await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
1963
3238
|
}())
|
|
1964
3239
|
: (await async function () {
|
|
3240
|
+
// transcode to base64url, if needed
|
|
3241
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
3242
|
+
? publishedList.StatusList2021.encodedList
|
|
3243
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
3244
|
+
|
|
1965
3245
|
// if status list 2021 is not fetched, read from file
|
|
1966
3246
|
if (options?.statusListFile) {
|
|
1967
3247
|
// if not encrypted, return bitstring
|
|
1968
|
-
if (!
|
|
3248
|
+
if (!publishedList.metadata.encrypted) {
|
|
3249
|
+
// construct encoded status list
|
|
3250
|
+
const encoded = new StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode() as Bitstring
|
|
3251
|
+
|
|
3252
|
+
// validate against published list
|
|
3253
|
+
if (encoded !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
3254
|
+
|
|
3255
|
+
// return encoded
|
|
3256
|
+
return encoded
|
|
3257
|
+
}
|
|
1969
3258
|
|
|
1970
3259
|
// otherwise, decrypt and return bitstring
|
|
1971
3260
|
const scopedRawBlob = await toBlob(await Cheqd.getFile(options.statusListFile))
|
|
1972
3261
|
|
|
1973
3262
|
// decrypt
|
|
1974
|
-
|
|
3263
|
+
const decrypted = await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
3264
|
+
|
|
3265
|
+
// validate against published list
|
|
3266
|
+
if (decrypted !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
3267
|
+
|
|
3268
|
+
// return decrypted
|
|
3269
|
+
return decrypted
|
|
1975
3270
|
}
|
|
1976
3271
|
|
|
1977
|
-
if (!options?.statusListInlineBitstring) throw new Error('[did-provider-cheqd]:
|
|
3272
|
+
if (!options?.statusListInlineBitstring) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided')
|
|
3273
|
+
|
|
3274
|
+
// validate against published list
|
|
3275
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021')
|
|
1978
3276
|
|
|
1979
3277
|
// otherwise, read from inline bitstring
|
|
1980
3278
|
return options?.statusListInlineBitstring
|
|
@@ -1983,11 +3281,11 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1983
3281
|
// parse status list 2021
|
|
1984
3282
|
const statusList = await StatusList.decode({ encodedList: statusList2021 })
|
|
1985
3283
|
|
|
1986
|
-
// early exit, if already
|
|
1987
|
-
if (statusList.getStatus(Number(credential.credentialStatus.statusListIndex))) return {
|
|
3284
|
+
// early exit, if already unsuspended
|
|
3285
|
+
if (!statusList.getStatus(Number(credential.credentialStatus.statusListIndex))) return { unsuspended: true } satisfies UnsuspensionResult
|
|
1988
3286
|
|
|
1989
3287
|
// update suspension status
|
|
1990
|
-
statusList.setStatus(Number(credential.credentialStatus.statusListIndex),
|
|
3288
|
+
statusList.setStatus(Number(credential.credentialStatus.statusListIndex), false)
|
|
1991
3289
|
|
|
1992
3290
|
// set in-memory status list ref
|
|
1993
3291
|
const bitstring = await statusList.encode() as Bitstring
|
|
@@ -2024,10 +3322,50 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2024
3322
|
{ encryptedString, encryptedSymmetricKey, symmetricKey: toString(symmetricKey!, 'hex') }
|
|
2025
3323
|
]
|
|
2026
3324
|
}())
|
|
2027
|
-
:
|
|
3325
|
+
: (await async function () {
|
|
3326
|
+
// validate encoding, if provided
|
|
3327
|
+
if (options?.publishOptions?.statusListEncoding && !Object.values(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
3328
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding')
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
// validate validUntil, if provided
|
|
3332
|
+
if (options?.publishOptions?.statusListValidUntil) {
|
|
3333
|
+
// validate validUntil as string
|
|
3334
|
+
if (typeof options?.publishOptions?.statusListValidUntil !== 'string') throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)')
|
|
3335
|
+
|
|
3336
|
+
// validate validUntil as date
|
|
3337
|
+
if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil))) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)')
|
|
3338
|
+
|
|
3339
|
+
// validate validUntil as future date
|
|
3340
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) < new Date()) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)')
|
|
3341
|
+
|
|
3342
|
+
// validate validUntil towards validFrom
|
|
3343
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom)) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)')
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
// define status list content
|
|
3347
|
+
const content = {
|
|
3348
|
+
StatusList2021: {
|
|
3349
|
+
type: publishedList.StatusList2021.type,
|
|
3350
|
+
encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : toString(fromString(bitstring, 'base64url'), options!.publishOptions.statusListEncoding as DefaultStatusList2021Encoding),
|
|
3351
|
+
validFrom: publishedList.StatusList2021.validFrom,
|
|
3352
|
+
validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
|
|
3353
|
+
},
|
|
3354
|
+
metadata: {
|
|
3355
|
+
encoding: (options?.publishOptions?.statusListEncoding as DefaultStatusList2021Encoding | undefined) || publishedList.metadata.encoding,
|
|
3356
|
+
encrypted: false,
|
|
3357
|
+
}
|
|
3358
|
+
} satisfies StatusList2021Suspension
|
|
3359
|
+
|
|
3360
|
+
// return tuple of publish result and encryption relevant metadata
|
|
3361
|
+
return [
|
|
3362
|
+
await Cheqd.publishStatusList2021(fromString(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
|
|
3363
|
+
undefined
|
|
3364
|
+
]
|
|
3365
|
+
}())
|
|
2028
3366
|
|
|
2029
3367
|
// early exit, if publish failed
|
|
2030
|
-
if (!scoped[0]) throw new Error('[did-provider-cheqd]:
|
|
3368
|
+
if (!scoped[0]) throw new Error('[did-provider-cheqd]: unsuspension: Failed to publish status list 2021')
|
|
2031
3369
|
|
|
2032
3370
|
// return publish result
|
|
2033
3371
|
return scoped
|
|
@@ -2035,71 +3373,115 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2035
3373
|
: undefined
|
|
2036
3374
|
|
|
2037
3375
|
return {
|
|
2038
|
-
|
|
3376
|
+
unsuspended: true,
|
|
2039
3377
|
published: topArgs?.publish ? true : undefined,
|
|
2040
|
-
statusList: topArgs?.returnUpdatedStatusList ?
|
|
3378
|
+
statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credential) as StatusList2021Suspension : undefined,
|
|
2041
3379
|
encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await blobToHexString((published?.[1] as { encryptedString: Blob })?.encryptedString) : undefined,
|
|
2042
3380
|
encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? (published?.[1] as { encryptedSymmetricKey: string })?.encryptedSymmetricKey : undefined,
|
|
2043
3381
|
symmetricKey: topArgs?.returnSymmetricKey ? (published?.[1] as { symmetricKey: string })?.symmetricKey : undefined,
|
|
2044
3382
|
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(credential) : undefined
|
|
2045
|
-
} satisfies
|
|
3383
|
+
} satisfies UnsuspensionResult
|
|
2046
3384
|
} catch (error) {
|
|
2047
|
-
// silent fail + early exit
|
|
3385
|
+
// silent fail + early exit
|
|
2048
3386
|
console.error(error)
|
|
2049
3387
|
|
|
2050
|
-
return {
|
|
3388
|
+
return { unsuspended: false, error: error as IError } satisfies UnsuspensionResult
|
|
2051
3389
|
}
|
|
2052
3390
|
}
|
|
2053
3391
|
|
|
2054
|
-
static async
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
if (credential?.credentialStatus?.statusPurpose !== 'suspension') throw new Error('[did-provider-cheqd]: unsuspension: Invalid status purpose')
|
|
3392
|
+
static async unsuspendCredentials(credentials: VerifiableCredential[], options?: ICheqdStatusList2021Options): Promise<BulkUnsuspensionResult> {
|
|
3393
|
+
// validate credentials - case: empty
|
|
3394
|
+
if (!credentials.length || credentials.length === 0) throw new Error('[did-provider-cheqd]: unsuspension: No credentials provided')
|
|
2058
3395
|
|
|
2059
|
-
|
|
2060
|
-
|
|
3396
|
+
// validate credentials - case: consistent issuer
|
|
3397
|
+
if (credentials.map((credential) => {
|
|
3398
|
+
return ((credential.issuer as { id: string }).id)
|
|
3399
|
+
? (credential.issuer as { id: string }).id
|
|
3400
|
+
: credential.issuer as string
|
|
3401
|
+
}).filter((value, _, self) => value && value !== self[0]).length > 0) throw new Error('[did-provider-cheqd]: unsuspension: Credentials must be issued by the same issuer')
|
|
2061
3402
|
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
3403
|
+
// validate credentials - case: status list index
|
|
3404
|
+
if (credentials.map((credential) => credential.credentialStatus!.statusListIndex).filter((value, index, self) => self.indexOf(value) !== index).length > 0) throw new Error('[did-provider-cheqd]: unsuspension: Credentials must have unique status list index')
|
|
3405
|
+
|
|
3406
|
+
// validate credentials - case: status purpose
|
|
3407
|
+
if (!credentials.every((credential) => credential.credentialStatus?.statusPurpose === 'suspension')) throw new Error('[did-provider-cheqd]: unsuspension: Invalid status purpose')
|
|
3408
|
+
|
|
3409
|
+
// validate credentials - case: status list id
|
|
3410
|
+
const remote = credentials[0].credentialStatus?.id
|
|
3411
|
+
? (credentials[0].credentialStatus as { id: string }).id.split('#')[0]
|
|
3412
|
+
: (function(){
|
|
3413
|
+
throw new Error('[did-provider-cheqd]: unsuspension: Invalid status list id')
|
|
3414
|
+
}())
|
|
3415
|
+
|
|
3416
|
+
// validate credentials - case: status list id format
|
|
3417
|
+
if (!RemoteListPattern.test(remote)) throw new Error('[did-provider-cheqd]: unsuspension: Invalid status list id format: expected: https://<optional_subdomain>.<sld>.<tld>/1.0/identifiers/<did:cheqd:<namespace>:<method_specific_id>>?resourceName=<resource_name>&resourceType=<resource_type>')
|
|
3418
|
+
|
|
3419
|
+
if (!credentials.every((credential) => {
|
|
3420
|
+
return (credential.credentialStatus as { id: string }).id.split('#')[0] === remote
|
|
3421
|
+
})) throw new Error('[did-provider-cheqd]: unsuspension: Credentials must belong to the same status list')
|
|
3422
|
+
|
|
3423
|
+
// validate credentials - case: status list type
|
|
3424
|
+
if (!credentials.every((credential) => credential.credentialStatus?.type === 'StatusList2021Entry')) throw new Error('[did-provider-cheqd]: unsuspension: Invalid status list type')
|
|
3425
|
+
|
|
3426
|
+
try {
|
|
3427
|
+
// fetch status list 2021
|
|
3428
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credentials[0])) as StatusList2021Suspension
|
|
2073
3429
|
|
|
2074
3430
|
// early return, if encrypted and no decryption key provided
|
|
2075
|
-
if (
|
|
3431
|
+
if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey) throw new Error('[did-provider-cheqd]: unsuspension: symmetricKey is required, if status list 2021 is encrypted')
|
|
2076
3432
|
|
|
2077
3433
|
// fetch status list 2021 inscribed in credential
|
|
2078
3434
|
const statusList2021 = options?.topArgs?.fetchList
|
|
2079
3435
|
? (await async function () {
|
|
2080
3436
|
// if not encrypted, return bitstring
|
|
2081
|
-
if (!
|
|
3437
|
+
if (!publishedList.metadata.encrypted)
|
|
3438
|
+
return publishedList.metadata.encoding === 'base64url'
|
|
3439
|
+
? publishedList.StatusList2021.encodedList
|
|
3440
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
2082
3441
|
|
|
2083
3442
|
// otherwise, decrypt and return bitstring
|
|
2084
|
-
const scopedRawBlob = await toBlob(await Cheqd.fetchStatusList2021(
|
|
3443
|
+
const scopedRawBlob = await toBlob(await Cheqd.fetchStatusList2021(credentials[0], true) as Uint8Array)
|
|
2085
3444
|
|
|
2086
3445
|
// decrypt
|
|
2087
3446
|
return await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
2088
3447
|
}())
|
|
2089
3448
|
: (await async function () {
|
|
3449
|
+
// transcode to base64url, if needed
|
|
3450
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
3451
|
+
? publishedList.StatusList2021.encodedList
|
|
3452
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
3453
|
+
|
|
2090
3454
|
// if status list 2021 is not fetched, read from file
|
|
2091
3455
|
if (options?.statusListFile) {
|
|
2092
3456
|
// if not encrypted, return bitstring
|
|
2093
|
-
if (!
|
|
3457
|
+
if (!publishedList.metadata.encrypted) {
|
|
3458
|
+
// construct encoded status list
|
|
3459
|
+
const encoded = new StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode() as Bitstring
|
|
3460
|
+
|
|
3461
|
+
// validate against published list
|
|
3462
|
+
if (encoded !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
3463
|
+
|
|
3464
|
+
// return encoded
|
|
3465
|
+
return encoded
|
|
3466
|
+
}
|
|
2094
3467
|
|
|
2095
3468
|
// otherwise, decrypt and return bitstring
|
|
2096
3469
|
const scopedRawBlob = await toBlob(await Cheqd.getFile(options.statusListFile))
|
|
2097
3470
|
|
|
2098
3471
|
// decrypt
|
|
2099
|
-
|
|
3472
|
+
const decrypted = await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
3473
|
+
|
|
3474
|
+
// validate against published list
|
|
3475
|
+
if (decrypted !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
3476
|
+
|
|
3477
|
+
// return decrypted
|
|
3478
|
+
return decrypted
|
|
2100
3479
|
}
|
|
2101
3480
|
|
|
2102
|
-
if (!options?.statusListInlineBitstring) throw new Error('[did-provider-cheqd]:
|
|
3481
|
+
if (!options?.statusListInlineBitstring) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided')
|
|
3482
|
+
|
|
3483
|
+
// validate against published list
|
|
3484
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021')
|
|
2103
3485
|
|
|
2104
3486
|
// otherwise, read from inline bitstring
|
|
2105
3487
|
return options?.statusListInlineBitstring
|
|
@@ -2108,17 +3490,32 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2108
3490
|
// parse status list 2021
|
|
2109
3491
|
const statusList = await StatusList.decode({ encodedList: statusList2021 })
|
|
2110
3492
|
|
|
2111
|
-
//
|
|
2112
|
-
|
|
3493
|
+
// initiate bulk unsuspension
|
|
3494
|
+
const unsuspended = await Promise.allSettled(credentials.map((credential) => {
|
|
3495
|
+
return async function () {
|
|
3496
|
+
// early return, if no credential status
|
|
3497
|
+
if (!credential.credentialStatus) return { unsuspended: false }
|
|
2113
3498
|
|
|
2114
|
-
|
|
2115
|
-
|
|
3499
|
+
// early exit, if credential is already unsuspended
|
|
3500
|
+
if (!statusList.getStatus(Number(credential.credentialStatus.statusListIndex))) return { unsuspended: true }
|
|
3501
|
+
|
|
3502
|
+
// update unsuspension status
|
|
3503
|
+
statusList.setStatus(Number(credential.credentialStatus.statusListIndex), false)
|
|
3504
|
+
|
|
3505
|
+
// return unsuspension status
|
|
3506
|
+
return { unsuspended: true }
|
|
3507
|
+
}()
|
|
3508
|
+
})) satisfies PromiseSettledResult<UnsuspensionResult>[]
|
|
3509
|
+
|
|
3510
|
+
// revert bulk ops, if some failed
|
|
3511
|
+
if (unsuspended.some((result) => result.status === 'fulfilled' && !result.value.unsuspended ))
|
|
3512
|
+
throw new Error(`[did-provider-cheqd]: unsuspension: Bulk unsuspension failed: already unsuspended credentials in unsuspension bundle: raw log: ${JSON.stringify(unsuspended.map((result) => ({ unsuspended: result.status === 'fulfilled' ? result.value.unsuspended : false })))}`)
|
|
2116
3513
|
|
|
2117
3514
|
// set in-memory status list ref
|
|
2118
3515
|
const bitstring = await statusList.encode() as Bitstring
|
|
2119
3516
|
|
|
2120
3517
|
// cast top-level args
|
|
2121
|
-
const topArgs = options?.topArgs as
|
|
3518
|
+
const topArgs = options?.topArgs as ICheqdRevokeCredentialWithStatusList2021Args
|
|
2122
3519
|
|
|
2123
3520
|
// write status list 2021 to file, if provided
|
|
2124
3521
|
if (topArgs?.writeToFile) {
|
|
@@ -2129,7 +3526,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2129
3526
|
const published = topArgs?.publish
|
|
2130
3527
|
? (await async function () {
|
|
2131
3528
|
// fetch status list 2021 metadata
|
|
2132
|
-
const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(
|
|
3529
|
+
const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(credentials[0])
|
|
2133
3530
|
|
|
2134
3531
|
// publish status list 2021 as new version
|
|
2135
3532
|
const scoped = topArgs.publishEncrypted
|
|
@@ -2149,7 +3546,47 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2149
3546
|
{ encryptedString, encryptedSymmetricKey, symmetricKey: toString(symmetricKey!, 'hex') }
|
|
2150
3547
|
]
|
|
2151
3548
|
}())
|
|
2152
|
-
:
|
|
3549
|
+
: (await async function () {
|
|
3550
|
+
// validate encoding, if provided
|
|
3551
|
+
if (options?.publishOptions?.statusListEncoding && !Object.values(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
3552
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding')
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
// validate validUntil, if provided
|
|
3556
|
+
if (options?.publishOptions?.statusListValidUntil) {
|
|
3557
|
+
// validate validUntil as string
|
|
3558
|
+
if (typeof options?.publishOptions?.statusListValidUntil !== 'string') throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)')
|
|
3559
|
+
|
|
3560
|
+
// validate validUntil as date
|
|
3561
|
+
if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil))) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)')
|
|
3562
|
+
|
|
3563
|
+
// validate validUntil as future date
|
|
3564
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) < new Date()) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)')
|
|
3565
|
+
|
|
3566
|
+
// validate validUntil towards validFrom
|
|
3567
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom)) throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)')
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
// define status list content
|
|
3571
|
+
const content = {
|
|
3572
|
+
StatusList2021: {
|
|
3573
|
+
type: publishedList.StatusList2021.type,
|
|
3574
|
+
encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : toString(fromString(bitstring, 'base64url'), options!.publishOptions.statusListEncoding as DefaultStatusList2021Encoding),
|
|
3575
|
+
validFrom: publishedList.StatusList2021.validFrom,
|
|
3576
|
+
validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
|
|
3577
|
+
},
|
|
3578
|
+
metadata: {
|
|
3579
|
+
encoding: (options?.publishOptions?.statusListEncoding as DefaultStatusList2021Encoding | undefined) || publishedList.metadata.encoding,
|
|
3580
|
+
encrypted: false,
|
|
3581
|
+
}
|
|
3582
|
+
} satisfies StatusList2021Suspension
|
|
3583
|
+
|
|
3584
|
+
// return tuple of publish result and encryption relevant metadata
|
|
3585
|
+
return [
|
|
3586
|
+
await Cheqd.publishStatusList2021(fromString(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
|
|
3587
|
+
undefined
|
|
3588
|
+
]
|
|
3589
|
+
}())
|
|
2153
3590
|
|
|
2154
3591
|
// early exit, if publish failed
|
|
2155
3592
|
if (!scoped[0]) throw new Error('[did-provider-cheqd]: unsuspension: Failed to publish status list 2021')
|
|
@@ -2160,19 +3597,19 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2160
3597
|
: undefined
|
|
2161
3598
|
|
|
2162
3599
|
return {
|
|
2163
|
-
unsuspended:
|
|
3600
|
+
unsuspended: unsuspended.map((result) => result.status === 'fulfilled' ? result.value.unsuspended : false),
|
|
2164
3601
|
published: topArgs?.publish ? true : undefined,
|
|
2165
|
-
statusList: topArgs?.returnUpdatedStatusList ?
|
|
3602
|
+
statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credentials[0]) as StatusList2021Suspension : undefined,
|
|
2166
3603
|
encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await blobToHexString((published?.[1] as { encryptedString: Blob })?.encryptedString) : undefined,
|
|
2167
3604
|
encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? (published?.[1] as { encryptedSymmetricKey: string })?.encryptedSymmetricKey : undefined,
|
|
2168
3605
|
symmetricKey: topArgs?.returnSymmetricKey ? (published?.[1] as { symmetricKey: string })?.symmetricKey : undefined,
|
|
2169
|
-
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(
|
|
2170
|
-
} satisfies
|
|
3606
|
+
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(credentials[0]) : undefined
|
|
3607
|
+
} satisfies BulkUnsuspensionResult
|
|
2171
3608
|
} catch (error) {
|
|
2172
|
-
// silent fail + early exit
|
|
3609
|
+
// silent fail + early exit
|
|
2173
3610
|
console.error(error)
|
|
2174
3611
|
|
|
2175
|
-
return { unsuspended:
|
|
3612
|
+
return { unsuspended: [], error: error as IError } satisfies BulkUnsuspensionResult
|
|
2176
3613
|
}
|
|
2177
3614
|
}
|
|
2178
3615
|
|
|
@@ -2182,32 +3619,23 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2182
3619
|
throw new Error(`[did-provider-cheqd]: revocation: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`)
|
|
2183
3620
|
}
|
|
2184
3621
|
|
|
2185
|
-
// fetch status list 2021
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
// detect if encrypted
|
|
2189
|
-
const isEncrypted = function() {
|
|
2190
|
-
switch (metadata.mediaType) {
|
|
2191
|
-
case 'application/octet-stream':
|
|
2192
|
-
return true
|
|
2193
|
-
case 'application/gzip':
|
|
2194
|
-
return false
|
|
2195
|
-
default:
|
|
2196
|
-
throw new Error(`[did-provider-cheqd]: revocation: Unsupported media type: ${metadata.mediaType}`)
|
|
2197
|
-
}
|
|
2198
|
-
}()
|
|
3622
|
+
// fetch status list 2021
|
|
3623
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credential)) as StatusList2021Revocation
|
|
2199
3624
|
|
|
2200
3625
|
// early return, if encrypted and decryption key is not provided
|
|
2201
|
-
if (
|
|
3626
|
+
if (publishedList.metadata.encrypted && !options?.topArgs?.encryptedSymmetricKey) throw new Error('[did-provider-cheqd]: revocation: encryptedSymmetricKey is required, if status list 2021 is encrypted')
|
|
2202
3627
|
|
|
2203
3628
|
// fetch status list 2021 inscribed in credential
|
|
2204
3629
|
const statusList2021 = options?.topArgs?.fetchList
|
|
2205
3630
|
? (await async function () {
|
|
2206
3631
|
// if not encrypted, return bitstring
|
|
2207
|
-
if (!
|
|
3632
|
+
if (!publishedList.metadata.encrypted)
|
|
3633
|
+
return publishedList.metadata.encoding === 'base64url'
|
|
3634
|
+
? publishedList.StatusList2021.encodedList
|
|
3635
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
2208
3636
|
|
|
2209
3637
|
// otherwise, decrypt and return bitstring
|
|
2210
|
-
const scopedRawBlob = await toBlob(
|
|
3638
|
+
const scopedRawBlob = await toBlob(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding))
|
|
2211
3639
|
|
|
2212
3640
|
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
2213
3641
|
const lit = await LitProtocol.create({
|
|
@@ -2219,25 +3647,42 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2219
3647
|
return await lit.decrypt(scopedRawBlob, options?.topArgs?.encryptedSymmetricKey, options?.topArgs?.decryptionOptions?.unifiedAccessControlConditions)
|
|
2220
3648
|
}())
|
|
2221
3649
|
: (await async function () {
|
|
3650
|
+
// transcode to base64url, if needed
|
|
3651
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
3652
|
+
? publishedList.StatusList2021.encodedList
|
|
3653
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
3654
|
+
|
|
2222
3655
|
// if status list 2021 is not fetched, read from file
|
|
2223
3656
|
if (options?.statusListFile) {
|
|
2224
3657
|
// if not encrypted, return bitstring
|
|
2225
|
-
if (!
|
|
3658
|
+
if (!publishedList.metadata.encrypted) {
|
|
3659
|
+
// construct encoded status list
|
|
3660
|
+
const encoded = new StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode() as Bitstring
|
|
3661
|
+
|
|
3662
|
+
// validate against published list
|
|
3663
|
+
if (encoded !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
3664
|
+
|
|
3665
|
+
// return encoded
|
|
3666
|
+
return encoded
|
|
3667
|
+
}
|
|
2226
3668
|
|
|
2227
3669
|
// otherwise, decrypt and return bitstring
|
|
2228
3670
|
const scopedRawBlob = await toBlob(await Cheqd.getFile(options.statusListFile))
|
|
2229
3671
|
|
|
2230
|
-
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
2231
|
-
const lit = await LitProtocol.create({
|
|
2232
|
-
chain: options?.topArgs?.bootstrapOptions?.chain,
|
|
2233
|
-
litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
|
|
2234
|
-
})
|
|
2235
|
-
|
|
2236
3672
|
// decrypt
|
|
2237
|
-
|
|
3673
|
+
const decrypted = await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
3674
|
+
|
|
3675
|
+
// validate against published list
|
|
3676
|
+
if (decrypted !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
3677
|
+
|
|
3678
|
+
// return decrypted
|
|
3679
|
+
return decrypted
|
|
2238
3680
|
}
|
|
2239
3681
|
|
|
2240
|
-
if (!options?.statusListInlineBitstring) throw new Error('
|
|
3682
|
+
if (!options?.statusListInlineBitstring) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided')
|
|
3683
|
+
|
|
3684
|
+
// validate against published list
|
|
3685
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021')
|
|
2241
3686
|
|
|
2242
3687
|
// otherwise, read from inline bitstring
|
|
2243
3688
|
return options?.statusListInlineBitstring
|
|
@@ -2256,32 +3701,23 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2256
3701
|
throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`)
|
|
2257
3702
|
}
|
|
2258
3703
|
|
|
2259
|
-
// fetch status list 2021
|
|
2260
|
-
const
|
|
2261
|
-
|
|
2262
|
-
// detect if encrypted
|
|
2263
|
-
const isEncrypted = function() {
|
|
2264
|
-
switch (metadata.mediaType) {
|
|
2265
|
-
case 'application/octet-stream':
|
|
2266
|
-
return true
|
|
2267
|
-
case 'application/gzip':
|
|
2268
|
-
return false
|
|
2269
|
-
default:
|
|
2270
|
-
throw new Error(`[did-provider-cheqd]: suspension: Unsupported media type: ${metadata.mediaType}`)
|
|
2271
|
-
}
|
|
2272
|
-
}()
|
|
3704
|
+
// fetch status list 2021
|
|
3705
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credential)) as StatusList2021Suspension
|
|
2273
3706
|
|
|
2274
3707
|
// early return, if encrypted and decryption key is not provided
|
|
2275
|
-
if (
|
|
3708
|
+
if (publishedList.metadata.encrypted && !options?.topArgs?.encryptedSymmetricKey) throw new Error('[did-provider-cheqd]: revocation: encryptedSymmetricKey is required, if status list 2021 is encrypted')
|
|
2276
3709
|
|
|
2277
3710
|
// fetch status list 2021 inscribed in credential
|
|
2278
3711
|
const statusList2021 = options?.topArgs?.fetchList
|
|
2279
3712
|
? (await async function () {
|
|
2280
3713
|
// if not encrypted, return bitstring
|
|
2281
|
-
if (!
|
|
3714
|
+
if (!publishedList.metadata.encrypted)
|
|
3715
|
+
return publishedList.metadata.encoding === 'base64url'
|
|
3716
|
+
? publishedList.StatusList2021.encodedList
|
|
3717
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
2282
3718
|
|
|
2283
3719
|
// otherwise, decrypt and return bitstring
|
|
2284
|
-
const scopedRawBlob = await toBlob(
|
|
3720
|
+
const scopedRawBlob = await toBlob(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding))
|
|
2285
3721
|
|
|
2286
3722
|
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
2287
3723
|
const lit = await LitProtocol.create({
|
|
@@ -2293,25 +3729,42 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2293
3729
|
return await lit.decrypt(scopedRawBlob, options?.topArgs?.encryptedSymmetricKey, options?.topArgs?.decryptionOptions?.unifiedAccessControlConditions)
|
|
2294
3730
|
}())
|
|
2295
3731
|
: (await async function () {
|
|
3732
|
+
// transcode to base64url, if needed
|
|
3733
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
3734
|
+
? publishedList.StatusList2021.encodedList
|
|
3735
|
+
: toString(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding), 'base64url')
|
|
3736
|
+
|
|
2296
3737
|
// if status list 2021 is not fetched, read from file
|
|
2297
3738
|
if (options?.statusListFile) {
|
|
2298
3739
|
// if not encrypted, return bitstring
|
|
2299
|
-
if (!
|
|
3740
|
+
if (!publishedList.metadata.encrypted) {
|
|
3741
|
+
// construct encoded status list
|
|
3742
|
+
const encoded = new StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode() as Bitstring
|
|
3743
|
+
|
|
3744
|
+
// validate against published list
|
|
3745
|
+
if (encoded !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
3746
|
+
|
|
3747
|
+
// return encoded
|
|
3748
|
+
return encoded
|
|
3749
|
+
}
|
|
2300
3750
|
|
|
2301
3751
|
// otherwise, decrypt and return bitstring
|
|
2302
3752
|
const scopedRawBlob = await toBlob(await Cheqd.getFile(options.statusListFile))
|
|
2303
3753
|
|
|
2304
|
-
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
2305
|
-
const lit = await LitProtocol.create({
|
|
2306
|
-
chain: options?.topArgs?.bootstrapOptions?.chain,
|
|
2307
|
-
litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
|
|
2308
|
-
})
|
|
2309
|
-
|
|
2310
3754
|
// decrypt
|
|
2311
|
-
|
|
3755
|
+
const decrypted = await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
3756
|
+
|
|
3757
|
+
// validate against published list
|
|
3758
|
+
if (decrypted !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021')
|
|
3759
|
+
|
|
3760
|
+
// return decrypted
|
|
3761
|
+
return decrypted
|
|
2312
3762
|
}
|
|
2313
3763
|
|
|
2314
|
-
if (!options?.statusListInlineBitstring) throw new Error('
|
|
3764
|
+
if (!options?.statusListInlineBitstring) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided')
|
|
3765
|
+
|
|
3766
|
+
// validate against published list
|
|
3767
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021')
|
|
2315
3768
|
|
|
2316
3769
|
// otherwise, read from inline bitstring
|
|
2317
3770
|
return options?.statusListInlineBitstring
|
|
@@ -2324,18 +3777,19 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2324
3777
|
return !!statusList.getStatus(Number(credential.credentialStatus.statusListIndex))
|
|
2325
3778
|
}
|
|
2326
3779
|
|
|
2327
|
-
static async publishStatusList2021(statusList2021Raw: Uint8Array, statusList2021Metadata: LinkedResourceMetadataResolutionResult, options: { context: IContext, resourceId?: string, resourceVersion?: string, signInputs?: ISignInputs[], fee?: DidStdFee }): Promise<boolean> {
|
|
3780
|
+
static async publishStatusList2021(statusList2021Raw: Uint8Array, statusList2021Metadata: LinkedResourceMetadataResolutionResult, options: { context: IContext, resourceId?: string, resourceVersion?: string, resourceAlsoKnownAs?: AlternativeUri[], signInputs?: ISignInputs[], fee?: DidStdFee }): Promise<boolean> {
|
|
2328
3781
|
// construct status list 2021 payload from previous version + new version
|
|
2329
3782
|
const payload = {
|
|
2330
3783
|
collectionId: statusList2021Metadata.resourceCollectionId,
|
|
2331
3784
|
id: options?.resourceId || v4(),
|
|
2332
3785
|
name: statusList2021Metadata.resourceName,
|
|
2333
3786
|
version: options?.resourceVersion || new Date().toISOString(),
|
|
2334
|
-
|
|
3787
|
+
alsoKnownAs: options?.resourceAlsoKnownAs || [],
|
|
3788
|
+
resourceType: statusList2021Metadata.resourceType as DefaultStatusList2021ResourceType,
|
|
2335
3789
|
data: statusList2021Raw
|
|
2336
3790
|
} satisfies StatusList2021ResourcePayload
|
|
2337
3791
|
|
|
2338
|
-
return await options.context.agent[
|
|
3792
|
+
return await options.context.agent[BroadcastStatusList2021MethodName]({
|
|
2339
3793
|
kms: (await options.context.agent.keyManagerGetKeyManagementSystems())[0],
|
|
2340
3794
|
payload,
|
|
2341
3795
|
network: statusList2021Metadata.resourceURI.split(':')[2] as CheqdNetwork,
|
|
@@ -2344,7 +3798,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2344
3798
|
})
|
|
2345
3799
|
}
|
|
2346
3800
|
|
|
2347
|
-
static async fetchStatusList2021(credential: VerifiableCredential, returnRaw = false): Promise<
|
|
3801
|
+
static async fetchStatusList2021(credential: VerifiableCredential, returnRaw = false): Promise<StatusList2021Revocation | StatusList2021Suspension | Uint8Array> {
|
|
2348
3802
|
// validate credential status
|
|
2349
3803
|
if (!credential.credentialStatus) throw new Error('[did-provider-cheqd]: fetch status list: Credential status is not present')
|
|
2350
3804
|
|
|
@@ -2354,31 +3808,34 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2354
3808
|
// validate credential status list status purpose
|
|
2355
3809
|
if (credential.credentialStatus.statusPurpose !== 'revocation' && credential.credentialStatus.statusPurpose !== 'suspension') throw new Error('[did-provider-cheqd]: fetch status list: Credential status purpose is not valid')
|
|
2356
3810
|
|
|
2357
|
-
// validate credential status list status list credential
|
|
2358
|
-
if (!credential.credentialStatus.statusListCredential) throw new Error('[did-provider-cheqd]: fetch status list: Credential status list credential is not present')
|
|
2359
|
-
|
|
2360
3811
|
// fetch status list 2021
|
|
2361
|
-
const
|
|
3812
|
+
const content = await (await fetch(credential.credentialStatus.id.split('#')[0])).json() as StatusList2021Revocation | StatusList2021Suspension
|
|
2362
3813
|
|
|
2363
3814
|
// return raw if requested
|
|
2364
|
-
if (returnRaw)
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
const bitstring = toString(new Uint8Array(raw), 'base64url')
|
|
3815
|
+
if (returnRaw) {
|
|
3816
|
+
return fromString(content.StatusList2021.encodedList, content.metadata.encoding as DefaultStatusList2021Encoding)
|
|
3817
|
+
}
|
|
2368
3818
|
|
|
2369
|
-
return
|
|
3819
|
+
// otherwise, return content
|
|
3820
|
+
return content
|
|
2370
3821
|
}
|
|
2371
3822
|
|
|
2372
3823
|
static async fetchStatusList2021Metadata(credential: VerifiableCredential): Promise<LinkedResourceMetadataResolutionResult> {
|
|
2373
3824
|
// get base url
|
|
2374
|
-
const baseUrl = new URL(credential.credentialStatus
|
|
2375
|
-
|
|
3825
|
+
const baseUrl = new URL(credential.credentialStatus!.id.split('#')[0])
|
|
3826
|
+
|
|
2376
3827
|
// get resource name
|
|
2377
3828
|
const resourceName = baseUrl.searchParams.get('resourceName')
|
|
2378
3829
|
|
|
3830
|
+
// get resource type
|
|
3831
|
+
const resourceType = baseUrl.searchParams.get('resourceType')
|
|
3832
|
+
|
|
2379
3833
|
// unset resource name
|
|
2380
3834
|
baseUrl.searchParams.delete('resourceName')
|
|
2381
3835
|
|
|
3836
|
+
// unset resource type
|
|
3837
|
+
baseUrl.searchParams.delete('resourceType')
|
|
3838
|
+
|
|
2382
3839
|
// construct metadata url
|
|
2383
3840
|
const metadataUrl = `${baseUrl.toString()}/metadata`
|
|
2384
3841
|
|
|
@@ -2389,7 +3846,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2389
3846
|
if (!collectionMetadata?.contentStream?.linkedResourceMetadata) throw new Error('[did-provider-cheqd]: fetch status list metadata: No linked resources found')
|
|
2390
3847
|
|
|
2391
3848
|
// find relevant resources by resource name
|
|
2392
|
-
const resourceVersioning = collectionMetadata.contentStream.linkedResourceMetadata.filter((resource) => resource.resourceName === resourceName)
|
|
3849
|
+
const resourceVersioning = collectionMetadata.contentStream.linkedResourceMetadata.filter((resource) => resource.resourceName === resourceName && resource.resourceType === resourceType)
|
|
2393
3850
|
|
|
2394
3851
|
// early exit if no relevant resources
|
|
2395
3852
|
if (!resourceVersioning.length || resourceVersioning.length === 0) throw new Error(`[did-provider-cheqd]: fetch status list metadata: No relevant resources found by resource name ${resourceName}`)
|