@cheqd/did-provider-cheqd 3.3.2 → 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 +178 -24
- package/build/cjs/agent/ICheqd.d.ts.map +1 -1
- package/build/cjs/agent/ICheqd.js +1458 -248
- 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 +178 -24
- package/build/esm/agent/ICheqd.d.ts.map +1 -1
- package/build/esm/agent/ICheqd.js +1458 -248
- 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 +178 -24
- 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 +1893 -428
- package/src/did-manager/cheqd-did-provider.ts +26 -3
package/src/agent/ICheqd.ts
CHANGED
|
@@ -37,7 +37,9 @@ import {
|
|
|
37
37
|
ICredentialIssuer,
|
|
38
38
|
IDIDManager,
|
|
39
39
|
IDataStore,
|
|
40
|
-
IResolver
|
|
40
|
+
IResolver,
|
|
41
|
+
W3CVerifiableCredential,
|
|
42
|
+
ICredentialVerifier
|
|
41
43
|
} from '@veramo/core'
|
|
42
44
|
import {
|
|
43
45
|
CheqdDIDProvider,
|
|
@@ -45,7 +47,13 @@ import {
|
|
|
45
47
|
TImportableEd25519Key,
|
|
46
48
|
ResourcePayload,
|
|
47
49
|
StatusList2021ResourcePayload,
|
|
48
|
-
DefaultRESTUrls
|
|
50
|
+
DefaultRESTUrls,
|
|
51
|
+
DefaultStatusList2021Encodings,
|
|
52
|
+
DefaultStatusList2021ResourceTypes,
|
|
53
|
+
DefaultStatusList2021StatusPurposeTypes,
|
|
54
|
+
DefaultStatusList2021Encoding,
|
|
55
|
+
DefaultStatusList2021ResourceType,
|
|
56
|
+
DefaultStatusList2021StatusPurposeType,
|
|
49
57
|
} from '../did-manager/cheqd-did-provider.js'
|
|
50
58
|
import {
|
|
51
59
|
fromString,
|
|
@@ -64,12 +72,18 @@ import {
|
|
|
64
72
|
LitProtocol,
|
|
65
73
|
TxNonceFormat
|
|
66
74
|
} from '../dkg-threshold/lit-protocol.js';
|
|
67
|
-
import {
|
|
75
|
+
import {
|
|
76
|
+
blobToHexString,
|
|
77
|
+
randomFromRange,
|
|
78
|
+
toBlob,
|
|
79
|
+
unescapeUnicode
|
|
80
|
+
} from '../utils/helpers.js'
|
|
68
81
|
import { resolverUrl } from '../did-manager/cheqd-did-resolver.js'
|
|
82
|
+
import { AlternativeUri } from '@cheqd/ts-proto/cheqd/resource/v2/resource.js'
|
|
69
83
|
|
|
70
84
|
const debug = Debug('veramo:did-provider-cheqd')
|
|
71
85
|
|
|
72
|
-
export type IContext = IAgentContext<IDIDManager & IKeyManager & IDataStore & IResolver & ICredentialIssuer & ICheqd>
|
|
86
|
+
export type IContext = IAgentContext<IDIDManager & IKeyManager & IDataStore & IResolver & ICredentialIssuer & ICredentialVerifier & ICheqd>
|
|
73
87
|
export type TExportedDIDDocWithKeys = { didDoc: DIDDocument, keys: TImportableEd25519Key[], versionId?: string }
|
|
74
88
|
export type TExportedDIDDocWithLinkedResourceWithKeys = TExportedDIDDocWithKeys & { linkedResource: LinkedResource }
|
|
75
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 }
|
|
@@ -79,28 +93,70 @@ export type ShallowTypedTxTxResponses = { height: string, txhash: string, codesp
|
|
|
79
93
|
export type ShallowTypedTxsResponse = { txs: ShallowTypedTx[], tx_responses: ShallowTypedTxTxResponses[], pagination: string | null, total: string } | undefined
|
|
80
94
|
export type VerificationResult = { verified: boolean, revoked?: boolean, suspended?: boolean, error?: IVerifyResult['error'] }
|
|
81
95
|
export type StatusCheckResult = { revoked?: boolean, suspended?: boolean, error?: IError }
|
|
82
|
-
export type RevocationResult = { revoked: boolean, error?: IError, statusList?:
|
|
83
|
-
export type
|
|
84
|
-
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 }
|
|
85
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[] } }
|
|
86
105
|
export type AccessControlConditionType = typeof AccessControlConditionTypes[keyof typeof AccessControlConditionTypes]
|
|
87
106
|
export type AccessControlConditionReturnValueComparator = typeof AccessControlConditionReturnValueComparators[keyof typeof AccessControlConditionReturnValueComparators]
|
|
88
107
|
export type AccessControlConditionMemoNonceArgs = { senderAddressObserved: string, recipientAddressObserved: string, amountObserved: string, specificNonce?: string, nonceFormat?: TxNonceFormat, type: Extract<AccessControlConditionType, 'memoNonce'> }
|
|
89
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[] }
|
|
90
111
|
export type CreateEncryptedStatusList2021Result = { created: boolean, error?: Error, encryptedSymmetricKey: string, symmetricKey?: string, encryptedStatusList2021: string, unifiedAccessControlConditions: CosmosAccessControlCondition[] }
|
|
91
112
|
export type GenerateEncryptedStatusList2021Result = { encryptedSymmetricKey: string, encryptedStatusList2021: string, unifiedAccessControlConditions: CosmosAccessControlCondition[] }
|
|
92
113
|
export type TransactionResult = { successful: boolean, transactionHash?: string, events?: DeliverTxResponse['events'], rawLog?: string, txResponse?: DeliverTxResponse, error?: IError }
|
|
93
114
|
export type ObservationResult = { subscribed: boolean, meetsCondition: boolean, transactionHash?: string, events?: DeliverTxResponse['events'], rawLog?: string, txResponse?: ShallowTypedTxTxResponses, error?: IError }
|
|
94
115
|
|
|
95
|
-
|
|
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
|
|
96
150
|
export const AccessControlConditionReturnValueComparators = { lessThan: '<', greaterThan: '>', equalTo: '=', lessThanOrEqualTo: '<=', greaterThanOrEqualTo: '>=' } as const
|
|
97
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
|
+
|
|
98
154
|
const CreateIdentifierMethodName = 'cheqdCreateIdentifier'
|
|
99
155
|
const UpdateIdentifierMethodName = 'cheqdUpdateIdentifier'
|
|
100
156
|
const DeactivateIdentifierMethodName = 'cheqdDeactivateIdentifier'
|
|
101
157
|
const CreateResourceMethodName = 'cheqdCreateLinkedResource'
|
|
102
158
|
const CreateStatusList2021MethodName = 'cheqdCreateStatusList2021'
|
|
103
|
-
const
|
|
159
|
+
const BroadcastStatusList2021MethodName = 'cheqdBroadcastStatusList2021'
|
|
104
160
|
const GenerateDidDocMethodName = 'cheqdGenerateDidDoc'
|
|
105
161
|
const GenerateDidDocWithLinkedResourceMethodName = 'cheqdGenerateDidDocWithLinkedResource'
|
|
106
162
|
const GenerateKeyPairMethodName = 'cheqdGenerateIdentityKeys'
|
|
@@ -158,6 +214,41 @@ export interface ICheqdCreateLinkedResourceArgs {
|
|
|
158
214
|
}
|
|
159
215
|
|
|
160
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 {
|
|
161
252
|
kms: string
|
|
162
253
|
payload: StatusList2021ResourcePayload
|
|
163
254
|
network: CheqdNetwork
|
|
@@ -166,7 +257,7 @@ export interface ICheqdCreateStatusList2021Args {
|
|
|
166
257
|
fee?: DidStdFee
|
|
167
258
|
}
|
|
168
259
|
|
|
169
|
-
export interface
|
|
260
|
+
export interface ICheqdBroadcastEncryptedStatusList2021Args extends ICheqdCreateUnencryptedStatusList2021Args {
|
|
170
261
|
encryptionOptions: {
|
|
171
262
|
accessControlConditions: (AccessControlConditionMemoNonceArgs | AccessControlConditionBalanceArgs)[]
|
|
172
263
|
returnSymmetricKey?: boolean
|
|
@@ -199,7 +290,7 @@ export interface ICheqdGenerateVersionIdArgs {
|
|
|
199
290
|
export interface ICheqdGenerateStatusList2021Args {
|
|
200
291
|
length?: number
|
|
201
292
|
buffer?: Uint8Array
|
|
202
|
-
bitstringEncoding?:
|
|
293
|
+
bitstringEncoding?: DefaultStatusList2021Encoding
|
|
203
294
|
[key: string]: any
|
|
204
295
|
}
|
|
205
296
|
|
|
@@ -241,7 +332,7 @@ export interface ICheqdIssueSuspendableCredentialWithStatusList2021Args {
|
|
|
241
332
|
}
|
|
242
333
|
|
|
243
334
|
export interface ICheqdVerifyCredentialWithStatusList2021Args {
|
|
244
|
-
credential:
|
|
335
|
+
credential: W3CVerifiableCredential
|
|
245
336
|
fetchList?: boolean
|
|
246
337
|
encryptedSymmetricKey?: string
|
|
247
338
|
options?: ICheqdStatusList2021Options
|
|
@@ -269,7 +360,7 @@ export interface ICheqdVerifyPresentationWithStatusList2021Args {
|
|
|
269
360
|
}
|
|
270
361
|
|
|
271
362
|
export interface ICheqdCheckCredentialStatusWithStatusList2021Args {
|
|
272
|
-
credential:
|
|
363
|
+
credential: W3CVerifiableCredential
|
|
273
364
|
fetchList?: boolean
|
|
274
365
|
encryptedSymmetricKey?: string
|
|
275
366
|
options?: ICheqdStatusList2021Options
|
|
@@ -283,7 +374,8 @@ export interface ICheqdCheckCredentialStatusWithStatusList2021Args {
|
|
|
283
374
|
}
|
|
284
375
|
|
|
285
376
|
export interface ICheqdRevokeCredentialWithStatusList2021Args {
|
|
286
|
-
credential
|
|
377
|
+
credential?: W3CVerifiableCredential
|
|
378
|
+
revocationOptions?: ICheqdRevokeCredentialWithStatusList2021Options
|
|
287
379
|
fetchList?: boolean
|
|
288
380
|
publish?: boolean
|
|
289
381
|
publishEncrypted?: boolean
|
|
@@ -298,7 +390,8 @@ export interface ICheqdRevokeCredentialWithStatusList2021Args {
|
|
|
298
390
|
}
|
|
299
391
|
|
|
300
392
|
export interface ICheqdRevokeBulkCredentialsWithStatusList2021Args {
|
|
301
|
-
credentials
|
|
393
|
+
credentials?: W3CVerifiableCredential[]
|
|
394
|
+
revocationOptions?: ICheqdRevokeBulkCredentialsWithStatusList2021Options
|
|
302
395
|
fetchList?: boolean
|
|
303
396
|
publish?: boolean
|
|
304
397
|
publishEncrypted?: boolean
|
|
@@ -313,7 +406,8 @@ export interface ICheqdRevokeBulkCredentialsWithStatusList2021Args {
|
|
|
313
406
|
}
|
|
314
407
|
|
|
315
408
|
export interface ICheqdSuspendCredentialWithStatusList2021Args {
|
|
316
|
-
credential
|
|
409
|
+
credential?: W3CVerifiableCredential
|
|
410
|
+
suspensionOptions?: ICheqdSuspendCredentialWithStatusList2021Options
|
|
317
411
|
fetchList?: boolean
|
|
318
412
|
publish?: boolean
|
|
319
413
|
publishEncrypted?: boolean
|
|
@@ -328,7 +422,8 @@ export interface ICheqdSuspendCredentialWithStatusList2021Args {
|
|
|
328
422
|
}
|
|
329
423
|
|
|
330
424
|
export interface ICheqdSuspendBulkCredentialsWithStatusList2021Args {
|
|
331
|
-
credentials
|
|
425
|
+
credentials?: W3CVerifiableCredential[]
|
|
426
|
+
suspensionOptions?: ICheqdSuspendBulkCredentialsWithStatusList2021Options
|
|
332
427
|
fetchList?: boolean
|
|
333
428
|
publish?: boolean
|
|
334
429
|
publishEncrypted?: boolean
|
|
@@ -343,7 +438,8 @@ export interface ICheqdSuspendBulkCredentialsWithStatusList2021Args {
|
|
|
343
438
|
}
|
|
344
439
|
|
|
345
440
|
export interface ICheqdUnsuspendCredentialWithStatusList2021Args {
|
|
346
|
-
credential
|
|
441
|
+
credential?: W3CVerifiableCredential
|
|
442
|
+
unsuspensionOptions?: ICheqdUnsuspendCredentialWithStatusList2021Options
|
|
347
443
|
fetchList?: boolean
|
|
348
444
|
publish?: boolean
|
|
349
445
|
publishEncrypted?: boolean
|
|
@@ -358,7 +454,8 @@ export interface ICheqdUnsuspendCredentialWithStatusList2021Args {
|
|
|
358
454
|
}
|
|
359
455
|
|
|
360
456
|
export interface ICheqdUnsuspendBulkCredentialsWithStatusList2021Args {
|
|
361
|
-
credentials
|
|
457
|
+
credentials?: W3CVerifiableCredential[]
|
|
458
|
+
unsuspensionOptions?: ICheqdUnsuspendBulkCredentialsWithStatusList2021Options
|
|
362
459
|
fetchList?: boolean
|
|
363
460
|
publish?: boolean
|
|
364
461
|
publishEncrypted?: boolean
|
|
@@ -396,13 +493,55 @@ export interface ICheqdStatusList2021Options {
|
|
|
396
493
|
[key: string]: any
|
|
397
494
|
}
|
|
398
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
|
+
|
|
399
538
|
export interface ICheqd extends IPluginMethodMap {
|
|
400
539
|
[CreateIdentifierMethodName]: (args: ICheqdCreateIdentifierArgs, context: IContext) => Promise<Omit<IIdentifier, 'provider'>>
|
|
401
540
|
[UpdateIdentifierMethodName]: (args: ICheqdUpdateIdentifierArgs, context: IContext) => Promise<Omit<IIdentifier, 'provider'>>,
|
|
402
541
|
[DeactivateIdentifierMethodName]: (args: ICheqdDeactivateIdentifierArgs, context: IContext) => Promise<boolean>,
|
|
403
542
|
[CreateResourceMethodName]: (args: ICheqdCreateLinkedResourceArgs, context: IContext) => Promise<boolean>,
|
|
404
|
-
[CreateStatusList2021MethodName]: (args: ICheqdCreateStatusList2021Args, context: IContext) => Promise<
|
|
405
|
-
[
|
|
543
|
+
[CreateStatusList2021MethodName]: (args: ICheqdCreateStatusList2021Args, context: IContext) => Promise<CreateStatusList2021Result>,
|
|
544
|
+
[BroadcastStatusList2021MethodName]: (args: ICheqdBroadcastStatusList2021Args, context: IContext) => Promise<boolean>,
|
|
406
545
|
[GenerateDidDocMethodName]: (args: ICheqdGenerateDidDocArgs, context: IContext) => Promise<TExportedDIDDocWithKeys>,
|
|
407
546
|
[GenerateDidDocWithLinkedResourceMethodName]: (args: ICheqdGenerateDidDocWithLinkedResourceArgs, context: IContext) => Promise<TExportedDIDDocWithLinkedResourceWithKeys>,
|
|
408
547
|
[GenerateKeyPairMethodName]: (args: ICheqdGenerateKeyPairArgs, context: IContext) => Promise<TImportableEd25519Key>
|
|
@@ -415,11 +554,11 @@ export interface ICheqd extends IPluginMethodMap {
|
|
|
415
554
|
[VerifyPresentationMethodName]: (args: ICheqdVerifyPresentationWithStatusList2021Args, context: IContext) => Promise<VerificationResult>
|
|
416
555
|
[CheckCredentialStatusMethodName]: (args: ICheqdCheckCredentialStatusWithStatusList2021Args, context: IContext) => Promise<StatusCheckResult>
|
|
417
556
|
[RevokeCredentialMethodName]: (args: ICheqdRevokeCredentialWithStatusList2021Args, context: IContext) => Promise<RevocationResult>
|
|
418
|
-
[RevokeCredentialsMethodName]: (args: ICheqdRevokeBulkCredentialsWithStatusList2021Args, context: IContext) => Promise<
|
|
557
|
+
[RevokeCredentialsMethodName]: (args: ICheqdRevokeBulkCredentialsWithStatusList2021Args, context: IContext) => Promise<BulkRevocationResult>
|
|
419
558
|
[SuspendCredentialMethodName]: (args: ICheqdSuspendCredentialWithStatusList2021Args, context: IContext) => Promise<SuspensionResult>
|
|
420
|
-
[SuspendCredentialsMethodName]: (args: ICheqdSuspendBulkCredentialsWithStatusList2021Args, context: IContext) => Promise<
|
|
559
|
+
[SuspendCredentialsMethodName]: (args: ICheqdSuspendBulkCredentialsWithStatusList2021Args, context: IContext) => Promise<BulkSuspensionResult>
|
|
421
560
|
[UnsuspendCredentialMethodName]: (args: ICheqdUnsuspendCredentialWithStatusList2021Args, context: IContext) => Promise<UnsuspensionResult>
|
|
422
|
-
[UnsuspendCredentialsMethodName]: (args: ICheqdUnsuspendBulkCredentialsWithStatusList2021Args, context: IContext) => Promise<
|
|
561
|
+
[UnsuspendCredentialsMethodName]: (args: ICheqdUnsuspendBulkCredentialsWithStatusList2021Args, context: IContext) => Promise<BulkUnsuspensionResult>
|
|
423
562
|
[TransactVerifierPaysIssuerMethodName]: (args: ICheqdTransactVerifierPaysIssuerArgs, context: IContext) => Promise<TransactionResult>
|
|
424
563
|
[ObserveVerifierPaysIssuerMethodName]: (args: ICheqdObserveVerifierPaysIssuerArgs, context: IContext) => Promise<ObservationResult>
|
|
425
564
|
}
|
|
@@ -517,17 +656,17 @@ export class Cheqd implements IAgentPlugin {
|
|
|
517
656
|
]
|
|
518
657
|
},
|
|
519
658
|
"returnType": {
|
|
520
|
-
"type": "
|
|
659
|
+
"type": "object"
|
|
521
660
|
}
|
|
522
661
|
},
|
|
523
|
-
"
|
|
524
|
-
"description": "
|
|
662
|
+
"cheqdBroadcastStatusList2021": {
|
|
663
|
+
"description": "Broadcast a Status List 2021 to cheqd ledger",
|
|
525
664
|
"arguments": {
|
|
526
665
|
"type": "object",
|
|
527
666
|
"properties": {
|
|
528
667
|
"args": {
|
|
529
668
|
"type": "object",
|
|
530
|
-
"description": "A
|
|
669
|
+
"description": "A cheqdBroadcastStatusList2021Args object as any for extensibility"
|
|
531
670
|
}
|
|
532
671
|
},
|
|
533
672
|
"required": [
|
|
@@ -897,7 +1036,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
897
1036
|
[DeactivateIdentifierMethodName]: this.DeactivateIdentifier.bind(this),
|
|
898
1037
|
[CreateResourceMethodName]: this.CreateResource.bind(this),
|
|
899
1038
|
[CreateStatusList2021MethodName]: this.CreateStatusList2021.bind(this),
|
|
900
|
-
[
|
|
1039
|
+
[BroadcastStatusList2021MethodName]: this.BroadcastStatusList2021.bind(this),
|
|
901
1040
|
[GenerateDidDocMethodName]: this.GenerateDidDoc.bind(this),
|
|
902
1041
|
[GenerateDidDocWithLinkedResourceMethodName]: this.GenerateDidDocWithLinkedResource.bind(this),
|
|
903
1042
|
[GenerateKeyPairMethodName]: this.GenerateIdentityKeys.bind(this),
|
|
@@ -1040,41 +1179,145 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1040
1179
|
throw new Error('[did-provider-cheqd]: kms is required')
|
|
1041
1180
|
}
|
|
1042
1181
|
|
|
1043
|
-
if (typeof args.
|
|
1044
|
-
throw new Error('[did-provider-cheqd]:
|
|
1182
|
+
if (typeof args.issuerDid !== 'string' || !args.issuerDid) {
|
|
1183
|
+
throw new Error('[did-provider-cheqd]: issuerDid is required')
|
|
1045
1184
|
}
|
|
1046
1185
|
|
|
1047
|
-
if (typeof args.
|
|
1048
|
-
throw new Error('[did-provider-cheqd]:
|
|
1186
|
+
if (typeof args.statusListName !== 'string' || !args.statusListName) {
|
|
1187
|
+
throw new Error('[did-provider-cheqd]: statusListName is required')
|
|
1049
1188
|
}
|
|
1050
1189
|
|
|
1051
|
-
if (args
|
|
1052
|
-
|
|
1190
|
+
if (typeof args.statusPurpose !== 'string' || !args.statusPurpose) {
|
|
1191
|
+
throw new Error('[did-provider-cheqd]: statusPurpose is required')
|
|
1053
1192
|
}
|
|
1054
1193
|
|
|
1055
|
-
if (typeof args
|
|
1056
|
-
|
|
1194
|
+
if (typeof args.encrypted === 'undefined') {
|
|
1195
|
+
throw new Error('[did-provider-cheqd]: encrypted is required')
|
|
1057
1196
|
}
|
|
1058
1197
|
|
|
1059
|
-
//
|
|
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
|
+
}
|
|
1060
1202
|
|
|
1061
|
-
//
|
|
1062
|
-
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
|
+
}
|
|
1063
1208
|
|
|
1064
|
-
|
|
1065
|
-
|
|
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
|
+
}
|
|
1066
1213
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
signInputs: args.signInputs,
|
|
1072
|
-
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')
|
|
1073
1218
|
}
|
|
1074
|
-
|
|
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
|
|
1075
1318
|
}
|
|
1076
1319
|
|
|
1077
|
-
private async
|
|
1320
|
+
private async BroadcastStatusList2021(args: ICheqdBroadcastStatusList2021Args, context: IContext) {
|
|
1078
1321
|
if (typeof args.kms !== 'string') {
|
|
1079
1322
|
throw new Error('[did-provider-cheqd]: kms is required')
|
|
1080
1323
|
}
|
|
@@ -1097,65 +1340,15 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1097
1340
|
|
|
1098
1341
|
// TODO: validate data as per bitstring
|
|
1099
1342
|
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
if (!args?.bootstrapOptions) {
|
|
1105
|
-
throw new Error('[did-provider-cheqd]: bootstrapOptions is required')
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
if (!args?.encryptionOptions?.accessControlConditions) {
|
|
1109
|
-
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(', ')}`)
|
|
1110
1346
|
}
|
|
1111
1347
|
|
|
1112
|
-
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
1113
|
-
const lit = await LitProtocol.create({
|
|
1114
|
-
chain: args.bootstrapOptions?.chain,
|
|
1115
|
-
litNetwork: args.bootstrapOptions?.litNetwork
|
|
1116
|
-
})
|
|
1117
|
-
|
|
1118
|
-
// construct access control conditions
|
|
1119
|
-
const unifiedAccessControlConditions = await Promise.all(args.encryptionOptions.accessControlConditions.map(async (condition) => {
|
|
1120
|
-
switch (condition.type) {
|
|
1121
|
-
case AccessControlConditionTypes.memoNonce:
|
|
1122
|
-
return await LitProtocol.generateCosmosAccessControlConditionTransactionMemo({
|
|
1123
|
-
key: '$.txs.*.body.memo',
|
|
1124
|
-
comparator: 'contains',
|
|
1125
|
-
value: condition?.specificNonce || await LitProtocol.generateTxNonce(condition?.nonceFormat)
|
|
1126
|
-
},
|
|
1127
|
-
condition.amountObserved,
|
|
1128
|
-
condition.senderAddressObserved,
|
|
1129
|
-
condition.recipientAddressObserved,
|
|
1130
|
-
args.bootstrapOptions.chain
|
|
1131
|
-
)
|
|
1132
|
-
case AccessControlConditionTypes.balance:
|
|
1133
|
-
return await LitProtocol.generateCosmosAccessControlConditionBalance({
|
|
1134
|
-
key: '$.balances[0].amount',
|
|
1135
|
-
comparator: condition.comparator,
|
|
1136
|
-
value: condition.amountObserved
|
|
1137
|
-
},
|
|
1138
|
-
args.bootstrapOptions.chain,
|
|
1139
|
-
condition.addressObserved
|
|
1140
|
-
)
|
|
1141
|
-
default:
|
|
1142
|
-
throw new Error(`[did-provider-cheqd]: accessControlCondition type is not supported`)
|
|
1143
|
-
}
|
|
1144
|
-
}))
|
|
1145
|
-
|
|
1146
|
-
// encrypt data
|
|
1147
|
-
const { encryptedString, encryptedSymmetricKey, symmetricKey } = await lit.encrypt(toString(args.payload.data!, 'base64url'), unifiedAccessControlConditions, true)
|
|
1148
|
-
|
|
1149
|
-
// set encrypted data
|
|
1150
|
-
args.payload.data = new Uint8Array(await encryptedString.arrayBuffer())
|
|
1151
|
-
|
|
1152
|
-
// set default resource type in runtime
|
|
1153
|
-
args.payload.resourceType = 'StatusList2021'
|
|
1154
|
-
|
|
1155
1348
|
this.providerId = Cheqd.generateProviderId(args.network)
|
|
1156
1349
|
this.didProvider = await Cheqd.loadProvider({ id: this.providerId } as DIDDocument, this.supportedDidProviders)
|
|
1157
1350
|
|
|
1158
|
-
|
|
1351
|
+
return await this.didProvider.createResource({
|
|
1159
1352
|
options: {
|
|
1160
1353
|
kms: args.kms,
|
|
1161
1354
|
payload: args.payload,
|
|
@@ -1163,14 +1356,6 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1163
1356
|
fee: args?.fee
|
|
1164
1357
|
}
|
|
1165
1358
|
}, context)
|
|
1166
|
-
|
|
1167
|
-
return {
|
|
1168
|
-
created,
|
|
1169
|
-
encryptedSymmetricKey,
|
|
1170
|
-
encryptedStatusList2021: await blobToHexString(encryptedString),
|
|
1171
|
-
symmetricKey: args?.encryptionOptions?.returnSymmetricKey ? toString(symmetricKey!, 'hex') : undefined,
|
|
1172
|
-
unifiedAccessControlConditions
|
|
1173
|
-
} satisfies CreateEncryptedStatusList2021Result
|
|
1174
1359
|
}
|
|
1175
1360
|
|
|
1176
1361
|
private async GenerateDidDoc(
|
|
@@ -1282,7 +1467,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1282
1467
|
return toString(fromString(encoded, 'base64url'), 'hex')
|
|
1283
1468
|
default:
|
|
1284
1469
|
return encoded
|
|
1285
|
-
}
|
|
1470
|
+
}
|
|
1286
1471
|
}
|
|
1287
1472
|
|
|
1288
1473
|
private async GenerateEncryptedStatusList2021(args: ICheqdGenerateEncryptedStatusList2021Args, context: IContext): Promise<GenerateEncryptedStatusList2021Result> {
|
|
@@ -1359,7 +1544,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1359
1544
|
: args.issuanceOptions.credential.issuer as string
|
|
1360
1545
|
|
|
1361
1546
|
// generate status list credential
|
|
1362
|
-
const statusListCredential = `${resolverUrl}${issuer}?resourceName=${args.statusOptions.statusListName}`
|
|
1547
|
+
const statusListCredential = `${resolverUrl}${issuer}?resourceName=${args.statusOptions.statusListName}&resourceType=StatusList2021Revocation`
|
|
1363
1548
|
|
|
1364
1549
|
// construct credential status
|
|
1365
1550
|
const credentialStatus = {
|
|
@@ -1367,7 +1552,6 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1367
1552
|
type: 'StatusList2021Entry',
|
|
1368
1553
|
statusPurpose: 'revocation',
|
|
1369
1554
|
statusListIndex: `${statusListIndex}`,
|
|
1370
|
-
statusListCredential,
|
|
1371
1555
|
}
|
|
1372
1556
|
|
|
1373
1557
|
// add credential status to credential
|
|
@@ -1411,7 +1595,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1411
1595
|
: args.issuanceOptions.credential.issuer as string
|
|
1412
1596
|
|
|
1413
1597
|
// generate status list credential
|
|
1414
|
-
const statusListCredential = `${resolverUrl}${issuer}?resourceName=${args.statusOptions.statusListName}`
|
|
1598
|
+
const statusListCredential = `${resolverUrl}${issuer}?resourceName=${args.statusOptions.statusListName}&resourceType=StatusList2021Suspension`
|
|
1415
1599
|
|
|
1416
1600
|
// construct credential status
|
|
1417
1601
|
const credentialStatus = {
|
|
@@ -1419,7 +1603,6 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1419
1603
|
type: 'StatusList2021Entry',
|
|
1420
1604
|
statusPurpose: 'suspension',
|
|
1421
1605
|
statusListIndex: `${statusListIndex}`,
|
|
1422
|
-
statusListCredential,
|
|
1423
1606
|
}
|
|
1424
1607
|
|
|
1425
1608
|
// add credential status to credential
|
|
@@ -1467,16 +1650,19 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1467
1650
|
return { verified: false, error: verificationResult.error }
|
|
1468
1651
|
}
|
|
1469
1652
|
|
|
1653
|
+
// if jwt credential, decode it
|
|
1654
|
+
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential
|
|
1655
|
+
|
|
1470
1656
|
// verify credential status
|
|
1471
|
-
switch (
|
|
1657
|
+
switch (credential.credentialStatus?.statusPurpose) {
|
|
1472
1658
|
case 'revocation':
|
|
1473
|
-
if (await Cheqd.checkRevoked(
|
|
1659
|
+
if (await Cheqd.checkRevoked(credential, { ...args.options, topArgs: args })) return { verified: false, revoked: true }
|
|
1474
1660
|
return { verified: true, revoked: false }
|
|
1475
1661
|
case 'suspension':
|
|
1476
|
-
if (await Cheqd.checkSuspended(
|
|
1662
|
+
if (await Cheqd.checkSuspended(credential, { ...args.options, topArgs: args })) return { verified: false, suspended: true }
|
|
1477
1663
|
return { verified: true, suspended: false }
|
|
1478
1664
|
default:
|
|
1479
|
-
throw new Error(`[did-provider-cheqd]: verify credential: Unsupported status purpose: ${
|
|
1665
|
+
throw new Error(`[did-provider-cheqd]: verify credential: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`)
|
|
1480
1666
|
}
|
|
1481
1667
|
}
|
|
1482
1668
|
|
|
@@ -1499,17 +1685,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1499
1685
|
// verify credential(s) status(es)
|
|
1500
1686
|
for (let credential of args.presentation.verifiableCredential) {
|
|
1501
1687
|
// if jwt credential, decode it
|
|
1502
|
-
if (typeof credential === 'string')
|
|
1503
|
-
const decodedCredential = decodeJWT(credential)
|
|
1504
|
-
|
|
1505
|
-
// validate credential payload
|
|
1506
|
-
if (!decodedCredential.payload) throw new Error('[did-provider-cheqd]: verify presentation: decodedCredential.payload is required')
|
|
1507
|
-
|
|
1508
|
-
// validate credential payload ref as VerifiableCredential
|
|
1509
|
-
if (!decodedCredential.payload.vc) throw new Error('[did-provider-cheqd]: verify presentation: decodedCredential.payload.vc is required')
|
|
1510
|
-
|
|
1511
|
-
credential = decodedCredential.payload.vc as VerifiableCredential
|
|
1512
|
-
}
|
|
1688
|
+
if (typeof credential === 'string') credential = await Cheqd.decodeCredentialJWT(credential)
|
|
1513
1689
|
|
|
1514
1690
|
switch (credential.credentialStatus?.statusPurpose) {
|
|
1515
1691
|
case 'revocation':
|
|
@@ -1527,22 +1703,76 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1527
1703
|
}
|
|
1528
1704
|
|
|
1529
1705
|
private async CheckCredentialStatusWithStatusList2021(args: ICheqdCheckCredentialStatusWithStatusList2021Args, context: IContext): Promise<StatusCheckResult> {
|
|
1530
|
-
|
|
1706
|
+
// if jwt credential, decode it
|
|
1707
|
+
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential
|
|
1708
|
+
|
|
1709
|
+
switch (credential.credentialStatus?.statusPurpose) {
|
|
1531
1710
|
case 'revocation':
|
|
1532
|
-
if (await Cheqd.checkRevoked(
|
|
1711
|
+
if (await Cheqd.checkRevoked(credential, { ...args.options, topArgs: args })) return { revoked: true }
|
|
1533
1712
|
return { revoked: false }
|
|
1534
1713
|
case 'suspension':
|
|
1535
|
-
if (await Cheqd.checkSuspended(
|
|
1714
|
+
if (await Cheqd.checkSuspended(credential, { ...args.options, topArgs: args })) return { suspended: true }
|
|
1536
1715
|
return { suspended: false }
|
|
1537
1716
|
default:
|
|
1538
|
-
throw new Error(`[did-provider-cheqd]: check status: Unsupported status purpose: ${
|
|
1717
|
+
throw new Error(`[did-provider-cheqd]: check status: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`)
|
|
1539
1718
|
}
|
|
1540
1719
|
}
|
|
1541
1720
|
|
|
1542
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
|
|
1771
|
+
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential
|
|
1772
|
+
|
|
1543
1773
|
// validate status purpose
|
|
1544
|
-
if (
|
|
1545
|
-
throw new Error(`[did-provider-cheqd]: revocation: Unsupported status purpose: ${
|
|
1774
|
+
if (credential.credentialStatus?.statusPurpose !== 'revocation') {
|
|
1775
|
+
throw new Error(`[did-provider-cheqd]: revocation: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`)
|
|
1546
1776
|
}
|
|
1547
1777
|
|
|
1548
1778
|
// validate args in pairs - case: statusListFile and statusList
|
|
@@ -1566,73 +1796,168 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1566
1796
|
}
|
|
1567
1797
|
|
|
1568
1798
|
// revoke credential
|
|
1569
|
-
return await Cheqd.revokeCredential(
|
|
1799
|
+
return await Cheqd.revokeCredential(credential, {
|
|
1570
1800
|
...args.options,
|
|
1571
1801
|
topArgs: args,
|
|
1572
1802
|
publishOptions: {
|
|
1573
1803
|
context,
|
|
1804
|
+
statusListEncoding: args?.options?.statusListEncoding,
|
|
1805
|
+
statusListValidUntil: args?.options?.statusListValidUntil,
|
|
1574
1806
|
resourceId: args?.options?.resourceId,
|
|
1575
1807
|
resourceVersion: args?.options?.resourceVersion,
|
|
1808
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
1576
1809
|
signInputs: args?.options?.signInputs,
|
|
1577
1810
|
fee: args?.options?.fee
|
|
1578
1811
|
}
|
|
1579
1812
|
})
|
|
1580
1813
|
}
|
|
1581
1814
|
|
|
1582
|
-
private async RevokeBulkCredentialsWithStatusList2021(args: ICheqdRevokeBulkCredentialsWithStatusList2021Args, context: IContext): Promise<
|
|
1583
|
-
//
|
|
1584
|
-
|
|
1585
|
-
|
|
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
|
+
}))
|
|
1586
1827
|
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
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
|
+
}))
|
|
1591
1863
|
}
|
|
1592
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
|
+
|
|
1593
1871
|
// validate args in pairs - case: statusListFile and statusList
|
|
1594
1872
|
if (args.options?.statusListFile && args.options?.statusList) {
|
|
1595
|
-
throw new Error('[did-provider-cheqd]:
|
|
1873
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile and statusList are mutually exclusive')
|
|
1596
1874
|
}
|
|
1597
1875
|
|
|
1598
1876
|
// validate args in pairs - case: statusListFile and fetchList
|
|
1599
1877
|
if (args.options?.statusListFile && args.options?.fetchList) {
|
|
1600
|
-
throw new Error('[did-provider-cheqd]:
|
|
1878
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile and fetchList are mutually exclusive')
|
|
1601
1879
|
}
|
|
1602
1880
|
|
|
1603
1881
|
// validate args in pairs - case: statusList and fetchList
|
|
1604
1882
|
if (args.options?.statusList && args.options?.fetchList) {
|
|
1605
|
-
throw new Error('[did-provider-cheqd]:
|
|
1883
|
+
throw new Error('[did-provider-cheqd]: revocation: statusList and fetchList are mutually exclusive')
|
|
1606
1884
|
}
|
|
1607
1885
|
|
|
1608
1886
|
// validate args in pairs - case: publish
|
|
1609
1887
|
if (args.options?.publish && !args.fetchList && !(args.options?.statusListFile || args.options?.statusList)) {
|
|
1610
|
-
throw new Error('[did-provider-cheqd]:
|
|
1888
|
+
throw new Error('[did-provider-cheqd]: revocation: publish requires statusListFile or statusList, if fetchList is disabled')
|
|
1611
1889
|
}
|
|
1612
1890
|
|
|
1613
|
-
//
|
|
1614
|
-
return await Cheqd.
|
|
1891
|
+
// revoke credentials
|
|
1892
|
+
return await Cheqd.revokeCredentials(credentials, {
|
|
1615
1893
|
...args.options,
|
|
1616
1894
|
topArgs: args,
|
|
1617
1895
|
publishOptions: {
|
|
1618
1896
|
context,
|
|
1619
1897
|
resourceId: args?.options?.resourceId,
|
|
1620
1898
|
resourceVersion: args?.options?.resourceVersion,
|
|
1899
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
1621
1900
|
signInputs: args?.options?.signInputs,
|
|
1622
1901
|
fee: args?.options?.fee
|
|
1623
1902
|
}
|
|
1624
1903
|
})
|
|
1625
1904
|
}
|
|
1626
1905
|
|
|
1627
|
-
private async
|
|
1628
|
-
//
|
|
1629
|
-
|
|
1630
|
-
|
|
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
|
|
1956
|
+
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential
|
|
1631
1957
|
|
|
1632
|
-
private async UnsuspendCredentialWithStatusList2021(args: ICheqdUnsuspendCredentialWithStatusList2021Args, context: IContext): Promise<UnsuspensionResult> {
|
|
1633
1958
|
// validate status purpose
|
|
1634
|
-
if (
|
|
1635
|
-
throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${
|
|
1959
|
+
if (credential.credentialStatus?.statusPurpose !== 'suspension') {
|
|
1960
|
+
throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`)
|
|
1636
1961
|
}
|
|
1637
1962
|
|
|
1638
1963
|
// validate args in pairs - case: statusListFile and statusList
|
|
@@ -1656,81 +1981,355 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1656
1981
|
}
|
|
1657
1982
|
|
|
1658
1983
|
// suspend credential
|
|
1659
|
-
return await Cheqd.
|
|
1984
|
+
return await Cheqd.suspendCredential(credential, {
|
|
1660
1985
|
...args.options,
|
|
1661
1986
|
topArgs: args,
|
|
1662
1987
|
publishOptions: {
|
|
1663
1988
|
context,
|
|
1989
|
+
statusListEncoding: args?.options?.statusListEncoding,
|
|
1990
|
+
statusListValidUntil: args?.options?.statusListValidUntil,
|
|
1664
1991
|
resourceId: args?.options?.resourceId,
|
|
1665
1992
|
resourceVersion: args?.options?.resourceVersion,
|
|
1993
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
1666
1994
|
signInputs: args?.options?.signInputs,
|
|
1667
1995
|
fee: args?.options?.fee
|
|
1668
1996
|
}
|
|
1669
1997
|
})
|
|
1670
1998
|
}
|
|
1671
1999
|
|
|
1672
|
-
private async
|
|
1673
|
-
//
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
txBytes: args.txBytes,
|
|
1685
|
-
})
|
|
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
|
+
}))
|
|
1686
2012
|
|
|
1687
|
-
// return
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
rawLog: transactionResult.rawLog,
|
|
1693
|
-
txResponse: args?.returnTxResponse ? transactionResult : undefined
|
|
1694
|
-
} satisfies TransactionResult
|
|
1695
|
-
} catch (error) {
|
|
1696
|
-
// return error
|
|
1697
|
-
return {
|
|
1698
|
-
successful: false,
|
|
1699
|
-
error: error as IError
|
|
1700
|
-
} satisfies TransactionResult
|
|
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
|
+
}
|
|
1701
2018
|
}
|
|
1702
|
-
}
|
|
1703
2019
|
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
// define network
|
|
1709
|
-
const network = (function() {
|
|
1710
|
-
switch (args.unifiedAccessControlCondition.chain) {
|
|
1711
|
-
case LitCompatibleCosmosChains.cheqdMainnet:
|
|
1712
|
-
return CheqdNetwork.Mainnet
|
|
1713
|
-
case LitCompatibleCosmosChains.cheqdTestnet:
|
|
1714
|
-
return CheqdNetwork.Testnet
|
|
1715
|
-
default:
|
|
1716
|
-
throw new Error(`[did-provider-cheqd]: observe: Unsupported chain: ${args.unifiedAccessControlCondition.chain}`)
|
|
1717
|
-
}
|
|
1718
|
-
}())
|
|
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')
|
|
1719
2024
|
|
|
1720
|
-
|
|
1721
|
-
|
|
2025
|
+
// validate suspension options - case: suspensionOptions.statusListName
|
|
2026
|
+
if (!args.suspensionOptions.statusListName) throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.statusListName is required')
|
|
1722
2027
|
|
|
1723
|
-
|
|
1724
|
-
|
|
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')
|
|
1725
2030
|
|
|
1726
|
-
|
|
1727
|
-
|
|
2031
|
+
// construct status list credential
|
|
2032
|
+
const statusListCredential = `${resolverUrl}${args.suspensionOptions.issuerDid}?resourceName=${args.suspensionOptions.statusListName}&resourceType=StatusList2021Suspension`
|
|
1728
2033
|
|
|
1729
|
-
|
|
1730
|
-
|
|
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
|
+
}))
|
|
2048
|
+
}
|
|
1731
2049
|
|
|
1732
|
-
|
|
1733
|
-
|
|
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
|
+
|
|
2056
|
+
// validate args in pairs - case: statusListFile and statusList
|
|
2057
|
+
if (args.options?.statusListFile && args.options?.statusList) {
|
|
2058
|
+
throw new Error('[did-provider-cheqd]: suspension: statusListFile and statusList are mutually exclusive')
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
// validate args in pairs - case: statusListFile and fetchList
|
|
2062
|
+
if (args.options?.statusListFile && args.options?.fetchList) {
|
|
2063
|
+
throw new Error('[did-provider-cheqd]: suspension: statusListFile and fetchList are mutually exclusive')
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
// validate args in pairs - case: statusList and fetchList
|
|
2067
|
+
if (args.options?.statusList && args.options?.fetchList) {
|
|
2068
|
+
throw new Error('[did-provider-cheqd]: suspension: statusList and fetchList are mutually exclusive')
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
// validate args in pairs - case: publish
|
|
2072
|
+
if (args.options?.publish && !args.fetchList && !(args.options?.statusListFile || args.options?.statusList)) {
|
|
2073
|
+
throw new Error('[did-provider-cheqd]: suspension: publish requires statusListFile or statusList, if fetchList is disabled')
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
// suspend credentials
|
|
2077
|
+
return await Cheqd.suspendCredentials(credentials, {
|
|
2078
|
+
...args.options,
|
|
2079
|
+
topArgs: args,
|
|
2080
|
+
publishOptions: {
|
|
2081
|
+
context,
|
|
2082
|
+
resourceId: args?.options?.resourceId,
|
|
2083
|
+
resourceVersion: args?.options?.resourceVersion,
|
|
2084
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
2085
|
+
signInputs: args?.options?.signInputs,
|
|
2086
|
+
fee: args?.options?.fee
|
|
2087
|
+
}
|
|
2088
|
+
})
|
|
2089
|
+
}
|
|
2090
|
+
|
|
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)
|
|
2100
|
+
|
|
2101
|
+
// early return if verification failed
|
|
2102
|
+
if (!verificationResult.verified) {
|
|
2103
|
+
return { unsuspended: false, error: verificationResult.error }
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
|
|
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) {
|
|
2295
|
+
// return error
|
|
2296
|
+
return {
|
|
2297
|
+
successful: false,
|
|
2298
|
+
error: error as IError
|
|
2299
|
+
} satisfies TransactionResult
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
private async ObserveVerifierPaysIssuer(args: ICheqdObserveVerifierPaysIssuerArgs, context: IContext): Promise<ObservationResult> {
|
|
2304
|
+
// verify with raw unified access control conditions, if any
|
|
2305
|
+
if (args?.unifiedAccessControlCondition) {
|
|
2306
|
+
try {
|
|
2307
|
+
// define network
|
|
2308
|
+
const network = (function() {
|
|
2309
|
+
switch (args.unifiedAccessControlCondition.chain) {
|
|
2310
|
+
case LitCompatibleCosmosChains.cheqdMainnet:
|
|
2311
|
+
return CheqdNetwork.Mainnet
|
|
2312
|
+
case LitCompatibleCosmosChains.cheqdTestnet:
|
|
2313
|
+
return CheqdNetwork.Testnet
|
|
2314
|
+
default:
|
|
2315
|
+
throw new Error(`[did-provider-cheqd]: observe: Unsupported chain: ${args.unifiedAccessControlCondition.chain}`)
|
|
2316
|
+
}
|
|
2317
|
+
}())
|
|
2318
|
+
|
|
2319
|
+
// construct url
|
|
2320
|
+
const url = `${DefaultRESTUrls[network]}${args.unifiedAccessControlCondition.path}`
|
|
2321
|
+
|
|
2322
|
+
// fetch relevant txs
|
|
2323
|
+
const txs = await (await fetch(url)).json() as ShallowTypedTxsResponse
|
|
2324
|
+
|
|
2325
|
+
// skim through txs for relevant events, in which case memoNonce is present and strict equals to the one provided
|
|
2326
|
+
const meetsConditionTxIndex = txs?.txs?.findIndex(tx => unescapeUnicode(tx.body.memo) === unescapeUnicode(args.unifiedAccessControlCondition!.returnValueTest.value))
|
|
2327
|
+
|
|
2328
|
+
// define meetsCondition
|
|
2329
|
+
const meetsCondition = (typeof meetsConditionTxIndex !== 'undefined' && meetsConditionTxIndex !== -1)
|
|
2330
|
+
|
|
2331
|
+
// return observation result
|
|
2332
|
+
return {
|
|
1734
2333
|
subscribed: true,
|
|
1735
2334
|
meetsCondition: meetsCondition,
|
|
1736
2335
|
transactionHash: meetsCondition ? txs!.tx_responses[meetsConditionTxIndex].txhash : undefined,
|
|
@@ -1748,113 +2347,739 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1748
2347
|
}
|
|
1749
2348
|
}
|
|
1750
2349
|
|
|
1751
|
-
// validate access control conditions components - case: senderAddress
|
|
1752
|
-
if (!args.senderAddress) {
|
|
1753
|
-
throw new Error('[did-provider-cheqd]: observation: senderAddress is required')
|
|
1754
|
-
}
|
|
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
|
+
})
|
|
2912
|
+
|
|
2913
|
+
// encrypt
|
|
2914
|
+
const { encryptedString, encryptedSymmetricKey, symmetricKey } = await lit.encrypt(bitstring, options?.topArgs?.encryptionOptions?.unifiedAccessControlConditions, true)
|
|
1755
2915
|
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
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
|
+
}
|
|
1760
2927
|
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
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)')
|
|
1765
2932
|
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
throw new Error('[did-provider-cheqd]: observation: memoNonce is required')
|
|
1769
|
-
}
|
|
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)')
|
|
1770
2935
|
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
throw new Error('[did-provider-cheqd]: observation: network is required')
|
|
1774
|
-
}
|
|
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
|
-
|
|
1778
|
-
|
|
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
|
+
}
|
|
1779
2942
|
|
|
1780
|
-
|
|
1781
|
-
|
|
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
|
|
1782
2956
|
|
|
1783
|
-
|
|
1784
|
-
|
|
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
|
+
}())
|
|
1785
2963
|
|
|
1786
|
-
|
|
1787
|
-
|
|
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
|
|
1788
2971
|
|
|
1789
|
-
// return observation result
|
|
1790
2972
|
return {
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
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
|
|
1798
2981
|
} catch (error) {
|
|
1799
|
-
//
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
error: error as IError
|
|
1804
|
-
} satisfies ObservationResult
|
|
2982
|
+
// silent fail + early exit
|
|
2983
|
+
console.error(error)
|
|
2984
|
+
|
|
2985
|
+
return { suspended: false, error: error as IError } satisfies SuspensionResult
|
|
1805
2986
|
}
|
|
1806
2987
|
}
|
|
1807
2988
|
|
|
1808
|
-
static async
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
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')
|
|
1812
2992
|
|
|
1813
|
-
|
|
1814
|
-
|
|
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')
|
|
1815
2999
|
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
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
|
|
1827
3026
|
|
|
1828
3027
|
// early return, if encrypted and no decryption key provided
|
|
1829
|
-
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')
|
|
1830
3029
|
|
|
1831
3030
|
// fetch status list 2021 inscribed in credential
|
|
1832
3031
|
const statusList2021 = options?.topArgs?.fetchList
|
|
1833
3032
|
? (await async function () {
|
|
1834
3033
|
// if not encrypted, return bitstring
|
|
1835
|
-
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')
|
|
1836
3038
|
|
|
1837
3039
|
// otherwise, decrypt and return bitstring
|
|
1838
|
-
const scopedRawBlob = await toBlob(await Cheqd.fetchStatusList2021(
|
|
3040
|
+
const scopedRawBlob = await toBlob(await Cheqd.fetchStatusList2021(credentials[0], true) as Uint8Array)
|
|
1839
3041
|
|
|
1840
3042
|
// decrypt
|
|
1841
3043
|
return await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
1842
3044
|
}())
|
|
1843
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
|
+
|
|
1844
3051
|
// if status list 2021 is not fetched, read from file
|
|
1845
3052
|
if (options?.statusListFile) {
|
|
1846
3053
|
// if not encrypted, return bitstring
|
|
1847
|
-
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
|
+
}
|
|
1848
3064
|
|
|
1849
3065
|
// otherwise, decrypt and return bitstring
|
|
1850
3066
|
const scopedRawBlob = await toBlob(await Cheqd.getFile(options.statusListFile))
|
|
1851
3067
|
|
|
1852
3068
|
// decrypt
|
|
1853
|
-
|
|
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
|
|
1854
3076
|
}
|
|
1855
3077
|
|
|
1856
3078
|
if (!options?.statusListInlineBitstring) throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided')
|
|
1857
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
|
+
|
|
1858
3083
|
// otherwise, read from inline bitstring
|
|
1859
3084
|
return options?.statusListInlineBitstring
|
|
1860
3085
|
}())
|
|
@@ -1862,11 +3087,26 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1862
3087
|
// parse status list 2021
|
|
1863
3088
|
const statusList = await StatusList.decode({ encodedList: statusList2021 })
|
|
1864
3089
|
|
|
1865
|
-
//
|
|
1866
|
-
|
|
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 }
|
|
1867
3095
|
|
|
1868
|
-
|
|
1869
|
-
|
|
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 })))}`)
|
|
1870
3110
|
|
|
1871
3111
|
// set in-memory status list ref
|
|
1872
3112
|
const bitstring = await statusList.encode() as Bitstring
|
|
@@ -1883,7 +3123,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1883
3123
|
const published = topArgs?.publish
|
|
1884
3124
|
? (await async function () {
|
|
1885
3125
|
// fetch status list 2021 metadata
|
|
1886
|
-
const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(
|
|
3126
|
+
const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(credentials[0])
|
|
1887
3127
|
|
|
1888
3128
|
// publish status list 2021 as new version
|
|
1889
3129
|
const scoped = topArgs.publishEncrypted
|
|
@@ -1903,10 +3143,50 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1903
3143
|
{ encryptedString, encryptedSymmetricKey, symmetricKey: toString(symmetricKey!, 'hex') }
|
|
1904
3144
|
]
|
|
1905
3145
|
}())
|
|
1906
|
-
:
|
|
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
|
+
}())
|
|
1907
3187
|
|
|
1908
3188
|
// early exit, if publish failed
|
|
1909
|
-
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')
|
|
1910
3190
|
|
|
1911
3191
|
// return publish result
|
|
1912
3192
|
return scoped
|
|
@@ -1914,50 +3194,41 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1914
3194
|
: undefined
|
|
1915
3195
|
|
|
1916
3196
|
return {
|
|
1917
|
-
|
|
3197
|
+
suspended: suspended.map((result) => result.status === 'fulfilled' ? result.value.suspended : false),
|
|
1918
3198
|
published: topArgs?.publish ? true : undefined,
|
|
1919
|
-
statusList: topArgs?.returnUpdatedStatusList ?
|
|
3199
|
+
statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credentials[0]) as StatusList2021Suspension : undefined,
|
|
1920
3200
|
encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await blobToHexString((published?.[1] as { encryptedString: Blob })?.encryptedString) : undefined,
|
|
1921
3201
|
encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? (published?.[1] as { encryptedSymmetricKey: string })?.encryptedSymmetricKey : undefined,
|
|
1922
3202
|
symmetricKey: topArgs?.returnSymmetricKey ? (published?.[1] as { symmetricKey: string })?.symmetricKey : undefined,
|
|
1923
|
-
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(
|
|
1924
|
-
} satisfies
|
|
3203
|
+
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(credentials[0]) : undefined
|
|
3204
|
+
} satisfies BulkSuspensionResult
|
|
1925
3205
|
} catch (error) {
|
|
1926
|
-
// silent fail + early exit
|
|
3206
|
+
// silent fail + early exit
|
|
1927
3207
|
console.error(error)
|
|
1928
3208
|
|
|
1929
|
-
return {
|
|
3209
|
+
return { suspended: [], error: error as IError } satisfies BulkSuspensionResult
|
|
1930
3210
|
}
|
|
1931
3211
|
}
|
|
1932
3212
|
|
|
1933
|
-
static async
|
|
3213
|
+
static async unsuspendCredential(credential: VerifiableCredential, options?: ICheqdStatusList2021Options): Promise<UnsuspensionResult> {
|
|
1934
3214
|
try {
|
|
1935
3215
|
// validate status purpose
|
|
1936
|
-
if (credential?.credentialStatus?.statusPurpose !== 'suspension') throw new Error('[did-provider-cheqd]:
|
|
1937
|
-
|
|
1938
|
-
// fetch status list 2021 metadata
|
|
1939
|
-
const metadata = (await Cheqd.fetchStatusList2021Metadata(credential))
|
|
3216
|
+
if (credential?.credentialStatus?.statusPurpose !== 'suspension') throw new Error('[did-provider-cheqd]: unsuspension: Invalid status purpose')
|
|
1940
3217
|
|
|
1941
|
-
//
|
|
1942
|
-
const
|
|
1943
|
-
switch (metadata.mediaType) {
|
|
1944
|
-
case 'application/octet-stream':
|
|
1945
|
-
return true
|
|
1946
|
-
case 'application/gzip':
|
|
1947
|
-
return false
|
|
1948
|
-
default:
|
|
1949
|
-
throw new Error(`[did-provider-cheqd]: suspension: Unsupported media type: ${metadata.mediaType}`)
|
|
1950
|
-
}
|
|
1951
|
-
}()
|
|
3218
|
+
// fetch status list 2021
|
|
3219
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credential)) as StatusList2021Suspension
|
|
1952
3220
|
|
|
1953
3221
|
// early return, if encrypted and no decryption key provided
|
|
1954
|
-
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')
|
|
1955
3223
|
|
|
1956
3224
|
// fetch status list 2021 inscribed in credential
|
|
1957
3225
|
const statusList2021 = options?.topArgs?.fetchList
|
|
1958
3226
|
? (await async function () {
|
|
1959
3227
|
// if not encrypted, return bitstring
|
|
1960
|
-
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')
|
|
1961
3232
|
|
|
1962
3233
|
// otherwise, decrypt and return bitstring
|
|
1963
3234
|
const scopedRawBlob = await toBlob(await Cheqd.fetchStatusList2021(credential, true) as Uint8Array)
|
|
@@ -1966,19 +3237,42 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1966
3237
|
return await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
1967
3238
|
}())
|
|
1968
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
|
+
|
|
1969
3245
|
// if status list 2021 is not fetched, read from file
|
|
1970
3246
|
if (options?.statusListFile) {
|
|
1971
3247
|
// if not encrypted, return bitstring
|
|
1972
|
-
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
|
+
}
|
|
1973
3258
|
|
|
1974
3259
|
// otherwise, decrypt and return bitstring
|
|
1975
3260
|
const scopedRawBlob = await toBlob(await Cheqd.getFile(options.statusListFile))
|
|
1976
3261
|
|
|
1977
3262
|
// decrypt
|
|
1978
|
-
|
|
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
|
|
1979
3270
|
}
|
|
1980
3271
|
|
|
1981
|
-
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')
|
|
1982
3276
|
|
|
1983
3277
|
// otherwise, read from inline bitstring
|
|
1984
3278
|
return options?.statusListInlineBitstring
|
|
@@ -1987,11 +3281,11 @@ export class Cheqd implements IAgentPlugin {
|
|
|
1987
3281
|
// parse status list 2021
|
|
1988
3282
|
const statusList = await StatusList.decode({ encodedList: statusList2021 })
|
|
1989
3283
|
|
|
1990
|
-
// early exit, if already
|
|
1991
|
-
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
|
|
1992
3286
|
|
|
1993
3287
|
// update suspension status
|
|
1994
|
-
statusList.setStatus(Number(credential.credentialStatus.statusListIndex),
|
|
3288
|
+
statusList.setStatus(Number(credential.credentialStatus.statusListIndex), false)
|
|
1995
3289
|
|
|
1996
3290
|
// set in-memory status list ref
|
|
1997
3291
|
const bitstring = await statusList.encode() as Bitstring
|
|
@@ -2028,10 +3322,50 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2028
3322
|
{ encryptedString, encryptedSymmetricKey, symmetricKey: toString(symmetricKey!, 'hex') }
|
|
2029
3323
|
]
|
|
2030
3324
|
}())
|
|
2031
|
-
:
|
|
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
|
+
}())
|
|
2032
3366
|
|
|
2033
3367
|
// early exit, if publish failed
|
|
2034
|
-
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')
|
|
2035
3369
|
|
|
2036
3370
|
// return publish result
|
|
2037
3371
|
return scoped
|
|
@@ -2039,71 +3373,115 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2039
3373
|
: undefined
|
|
2040
3374
|
|
|
2041
3375
|
return {
|
|
2042
|
-
|
|
3376
|
+
unsuspended: true,
|
|
2043
3377
|
published: topArgs?.publish ? true : undefined,
|
|
2044
|
-
statusList: topArgs?.returnUpdatedStatusList ?
|
|
3378
|
+
statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credential) as StatusList2021Suspension : undefined,
|
|
2045
3379
|
encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await blobToHexString((published?.[1] as { encryptedString: Blob })?.encryptedString) : undefined,
|
|
2046
3380
|
encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? (published?.[1] as { encryptedSymmetricKey: string })?.encryptedSymmetricKey : undefined,
|
|
2047
3381
|
symmetricKey: topArgs?.returnSymmetricKey ? (published?.[1] as { symmetricKey: string })?.symmetricKey : undefined,
|
|
2048
3382
|
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(credential) : undefined
|
|
2049
|
-
} satisfies
|
|
3383
|
+
} satisfies UnsuspensionResult
|
|
2050
3384
|
} catch (error) {
|
|
2051
|
-
// silent fail + early exit
|
|
3385
|
+
// silent fail + early exit
|
|
2052
3386
|
console.error(error)
|
|
2053
3387
|
|
|
2054
|
-
return {
|
|
3388
|
+
return { unsuspended: false, error: error as IError } satisfies UnsuspensionResult
|
|
2055
3389
|
}
|
|
2056
3390
|
}
|
|
2057
3391
|
|
|
2058
|
-
static async
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
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')
|
|
2062
3395
|
|
|
2063
|
-
|
|
2064
|
-
|
|
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')
|
|
2065
3402
|
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
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
|
|
2077
3429
|
|
|
2078
3430
|
// early return, if encrypted and no decryption key provided
|
|
2079
|
-
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')
|
|
2080
3432
|
|
|
2081
3433
|
// fetch status list 2021 inscribed in credential
|
|
2082
3434
|
const statusList2021 = options?.topArgs?.fetchList
|
|
2083
3435
|
? (await async function () {
|
|
2084
3436
|
// if not encrypted, return bitstring
|
|
2085
|
-
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')
|
|
2086
3441
|
|
|
2087
3442
|
// otherwise, decrypt and return bitstring
|
|
2088
|
-
const scopedRawBlob = await toBlob(await Cheqd.fetchStatusList2021(
|
|
3443
|
+
const scopedRawBlob = await toBlob(await Cheqd.fetchStatusList2021(credentials[0], true) as Uint8Array)
|
|
2089
3444
|
|
|
2090
3445
|
// decrypt
|
|
2091
3446
|
return await LitProtocol.decryptDirect(scopedRawBlob, fromString(options?.topArgs?.symmetricKey, 'hex'))
|
|
2092
3447
|
}())
|
|
2093
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
|
+
|
|
2094
3454
|
// if status list 2021 is not fetched, read from file
|
|
2095
3455
|
if (options?.statusListFile) {
|
|
2096
3456
|
// if not encrypted, return bitstring
|
|
2097
|
-
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
|
+
}
|
|
2098
3467
|
|
|
2099
3468
|
// otherwise, decrypt and return bitstring
|
|
2100
3469
|
const scopedRawBlob = await toBlob(await Cheqd.getFile(options.statusListFile))
|
|
2101
3470
|
|
|
2102
3471
|
// decrypt
|
|
2103
|
-
|
|
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
|
|
2104
3479
|
}
|
|
2105
3480
|
|
|
2106
|
-
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')
|
|
2107
3485
|
|
|
2108
3486
|
// otherwise, read from inline bitstring
|
|
2109
3487
|
return options?.statusListInlineBitstring
|
|
@@ -2112,17 +3490,32 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2112
3490
|
// parse status list 2021
|
|
2113
3491
|
const statusList = await StatusList.decode({ encodedList: statusList2021 })
|
|
2114
3492
|
|
|
2115
|
-
//
|
|
2116
|
-
|
|
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 }
|
|
2117
3498
|
|
|
2118
|
-
|
|
2119
|
-
|
|
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 })))}`)
|
|
2120
3513
|
|
|
2121
3514
|
// set in-memory status list ref
|
|
2122
3515
|
const bitstring = await statusList.encode() as Bitstring
|
|
2123
3516
|
|
|
2124
3517
|
// cast top-level args
|
|
2125
|
-
const topArgs = options?.topArgs as
|
|
3518
|
+
const topArgs = options?.topArgs as ICheqdRevokeCredentialWithStatusList2021Args
|
|
2126
3519
|
|
|
2127
3520
|
// write status list 2021 to file, if provided
|
|
2128
3521
|
if (topArgs?.writeToFile) {
|
|
@@ -2133,7 +3526,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2133
3526
|
const published = topArgs?.publish
|
|
2134
3527
|
? (await async function () {
|
|
2135
3528
|
// fetch status list 2021 metadata
|
|
2136
|
-
const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(
|
|
3529
|
+
const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(credentials[0])
|
|
2137
3530
|
|
|
2138
3531
|
// publish status list 2021 as new version
|
|
2139
3532
|
const scoped = topArgs.publishEncrypted
|
|
@@ -2153,7 +3546,47 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2153
3546
|
{ encryptedString, encryptedSymmetricKey, symmetricKey: toString(symmetricKey!, 'hex') }
|
|
2154
3547
|
]
|
|
2155
3548
|
}())
|
|
2156
|
-
:
|
|
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
|
+
}())
|
|
2157
3590
|
|
|
2158
3591
|
// early exit, if publish failed
|
|
2159
3592
|
if (!scoped[0]) throw new Error('[did-provider-cheqd]: unsuspension: Failed to publish status list 2021')
|
|
@@ -2164,19 +3597,19 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2164
3597
|
: undefined
|
|
2165
3598
|
|
|
2166
3599
|
return {
|
|
2167
|
-
unsuspended:
|
|
3600
|
+
unsuspended: unsuspended.map((result) => result.status === 'fulfilled' ? result.value.unsuspended : false),
|
|
2168
3601
|
published: topArgs?.publish ? true : undefined,
|
|
2169
|
-
statusList: topArgs?.returnUpdatedStatusList ?
|
|
3602
|
+
statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credentials[0]) as StatusList2021Suspension : undefined,
|
|
2170
3603
|
encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await blobToHexString((published?.[1] as { encryptedString: Blob })?.encryptedString) : undefined,
|
|
2171
3604
|
encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? (published?.[1] as { encryptedSymmetricKey: string })?.encryptedSymmetricKey : undefined,
|
|
2172
3605
|
symmetricKey: topArgs?.returnSymmetricKey ? (published?.[1] as { symmetricKey: string })?.symmetricKey : undefined,
|
|
2173
|
-
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(
|
|
2174
|
-
} satisfies
|
|
3606
|
+
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(credentials[0]) : undefined
|
|
3607
|
+
} satisfies BulkUnsuspensionResult
|
|
2175
3608
|
} catch (error) {
|
|
2176
|
-
// silent fail + early exit
|
|
3609
|
+
// silent fail + early exit
|
|
2177
3610
|
console.error(error)
|
|
2178
3611
|
|
|
2179
|
-
return { unsuspended:
|
|
3612
|
+
return { unsuspended: [], error: error as IError } satisfies BulkUnsuspensionResult
|
|
2180
3613
|
}
|
|
2181
3614
|
}
|
|
2182
3615
|
|
|
@@ -2186,32 +3619,23 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2186
3619
|
throw new Error(`[did-provider-cheqd]: revocation: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`)
|
|
2187
3620
|
}
|
|
2188
3621
|
|
|
2189
|
-
// fetch status list 2021
|
|
2190
|
-
const
|
|
2191
|
-
|
|
2192
|
-
// detect if encrypted
|
|
2193
|
-
const isEncrypted = function() {
|
|
2194
|
-
switch (metadata.mediaType) {
|
|
2195
|
-
case 'application/octet-stream':
|
|
2196
|
-
return true
|
|
2197
|
-
case 'application/gzip':
|
|
2198
|
-
return false
|
|
2199
|
-
default:
|
|
2200
|
-
throw new Error(`[did-provider-cheqd]: revocation: Unsupported media type: ${metadata.mediaType}`)
|
|
2201
|
-
}
|
|
2202
|
-
}()
|
|
3622
|
+
// fetch status list 2021
|
|
3623
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credential)) as StatusList2021Revocation
|
|
2203
3624
|
|
|
2204
3625
|
// early return, if encrypted and decryption key is not provided
|
|
2205
|
-
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')
|
|
2206
3627
|
|
|
2207
3628
|
// fetch status list 2021 inscribed in credential
|
|
2208
3629
|
const statusList2021 = options?.topArgs?.fetchList
|
|
2209
3630
|
? (await async function () {
|
|
2210
3631
|
// if not encrypted, return bitstring
|
|
2211
|
-
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')
|
|
2212
3636
|
|
|
2213
3637
|
// otherwise, decrypt and return bitstring
|
|
2214
|
-
const scopedRawBlob = await toBlob(
|
|
3638
|
+
const scopedRawBlob = await toBlob(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding))
|
|
2215
3639
|
|
|
2216
3640
|
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
2217
3641
|
const lit = await LitProtocol.create({
|
|
@@ -2223,25 +3647,42 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2223
3647
|
return await lit.decrypt(scopedRawBlob, options?.topArgs?.encryptedSymmetricKey, options?.topArgs?.decryptionOptions?.unifiedAccessControlConditions)
|
|
2224
3648
|
}())
|
|
2225
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
|
+
|
|
2226
3655
|
// if status list 2021 is not fetched, read from file
|
|
2227
3656
|
if (options?.statusListFile) {
|
|
2228
3657
|
// if not encrypted, return bitstring
|
|
2229
|
-
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
|
+
}
|
|
2230
3668
|
|
|
2231
3669
|
// otherwise, decrypt and return bitstring
|
|
2232
3670
|
const scopedRawBlob = await toBlob(await Cheqd.getFile(options.statusListFile))
|
|
2233
3671
|
|
|
2234
|
-
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
2235
|
-
const lit = await LitProtocol.create({
|
|
2236
|
-
chain: options?.topArgs?.bootstrapOptions?.chain,
|
|
2237
|
-
litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
|
|
2238
|
-
})
|
|
2239
|
-
|
|
2240
3672
|
// decrypt
|
|
2241
|
-
|
|
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
|
|
2242
3680
|
}
|
|
2243
3681
|
|
|
2244
|
-
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')
|
|
2245
3686
|
|
|
2246
3687
|
// otherwise, read from inline bitstring
|
|
2247
3688
|
return options?.statusListInlineBitstring
|
|
@@ -2260,32 +3701,23 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2260
3701
|
throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`)
|
|
2261
3702
|
}
|
|
2262
3703
|
|
|
2263
|
-
// fetch status list 2021
|
|
2264
|
-
const
|
|
2265
|
-
|
|
2266
|
-
// detect if encrypted
|
|
2267
|
-
const isEncrypted = function() {
|
|
2268
|
-
switch (metadata.mediaType) {
|
|
2269
|
-
case 'application/octet-stream':
|
|
2270
|
-
return true
|
|
2271
|
-
case 'application/gzip':
|
|
2272
|
-
return false
|
|
2273
|
-
default:
|
|
2274
|
-
throw new Error(`[did-provider-cheqd]: suspension: Unsupported media type: ${metadata.mediaType}`)
|
|
2275
|
-
}
|
|
2276
|
-
}()
|
|
3704
|
+
// fetch status list 2021
|
|
3705
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credential)) as StatusList2021Suspension
|
|
2277
3706
|
|
|
2278
3707
|
// early return, if encrypted and decryption key is not provided
|
|
2279
|
-
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')
|
|
2280
3709
|
|
|
2281
3710
|
// fetch status list 2021 inscribed in credential
|
|
2282
3711
|
const statusList2021 = options?.topArgs?.fetchList
|
|
2283
3712
|
? (await async function () {
|
|
2284
3713
|
// if not encrypted, return bitstring
|
|
2285
|
-
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')
|
|
2286
3718
|
|
|
2287
3719
|
// otherwise, decrypt and return bitstring
|
|
2288
|
-
const scopedRawBlob = await toBlob(
|
|
3720
|
+
const scopedRawBlob = await toBlob(fromString(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding as DefaultStatusList2021Encoding))
|
|
2289
3721
|
|
|
2290
3722
|
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
2291
3723
|
const lit = await LitProtocol.create({
|
|
@@ -2297,25 +3729,42 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2297
3729
|
return await lit.decrypt(scopedRawBlob, options?.topArgs?.encryptedSymmetricKey, options?.topArgs?.decryptionOptions?.unifiedAccessControlConditions)
|
|
2298
3730
|
}())
|
|
2299
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
|
+
|
|
2300
3737
|
// if status list 2021 is not fetched, read from file
|
|
2301
3738
|
if (options?.statusListFile) {
|
|
2302
3739
|
// if not encrypted, return bitstring
|
|
2303
|
-
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
|
+
}
|
|
2304
3750
|
|
|
2305
3751
|
// otherwise, decrypt and return bitstring
|
|
2306
3752
|
const scopedRawBlob = await toBlob(await Cheqd.getFile(options.statusListFile))
|
|
2307
3753
|
|
|
2308
|
-
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
2309
|
-
const lit = await LitProtocol.create({
|
|
2310
|
-
chain: options?.topArgs?.bootstrapOptions?.chain,
|
|
2311
|
-
litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
|
|
2312
|
-
})
|
|
2313
|
-
|
|
2314
3754
|
// decrypt
|
|
2315
|
-
|
|
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
|
|
2316
3762
|
}
|
|
2317
3763
|
|
|
2318
|
-
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')
|
|
2319
3768
|
|
|
2320
3769
|
// otherwise, read from inline bitstring
|
|
2321
3770
|
return options?.statusListInlineBitstring
|
|
@@ -2328,18 +3777,19 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2328
3777
|
return !!statusList.getStatus(Number(credential.credentialStatus.statusListIndex))
|
|
2329
3778
|
}
|
|
2330
3779
|
|
|
2331
|
-
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> {
|
|
2332
3781
|
// construct status list 2021 payload from previous version + new version
|
|
2333
3782
|
const payload = {
|
|
2334
3783
|
collectionId: statusList2021Metadata.resourceCollectionId,
|
|
2335
3784
|
id: options?.resourceId || v4(),
|
|
2336
3785
|
name: statusList2021Metadata.resourceName,
|
|
2337
3786
|
version: options?.resourceVersion || new Date().toISOString(),
|
|
2338
|
-
|
|
3787
|
+
alsoKnownAs: options?.resourceAlsoKnownAs || [],
|
|
3788
|
+
resourceType: statusList2021Metadata.resourceType as DefaultStatusList2021ResourceType,
|
|
2339
3789
|
data: statusList2021Raw
|
|
2340
3790
|
} satisfies StatusList2021ResourcePayload
|
|
2341
3791
|
|
|
2342
|
-
return await options.context.agent[
|
|
3792
|
+
return await options.context.agent[BroadcastStatusList2021MethodName]({
|
|
2343
3793
|
kms: (await options.context.agent.keyManagerGetKeyManagementSystems())[0],
|
|
2344
3794
|
payload,
|
|
2345
3795
|
network: statusList2021Metadata.resourceURI.split(':')[2] as CheqdNetwork,
|
|
@@ -2348,7 +3798,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2348
3798
|
})
|
|
2349
3799
|
}
|
|
2350
3800
|
|
|
2351
|
-
static async fetchStatusList2021(credential: VerifiableCredential, returnRaw = false): Promise<
|
|
3801
|
+
static async fetchStatusList2021(credential: VerifiableCredential, returnRaw = false): Promise<StatusList2021Revocation | StatusList2021Suspension | Uint8Array> {
|
|
2352
3802
|
// validate credential status
|
|
2353
3803
|
if (!credential.credentialStatus) throw new Error('[did-provider-cheqd]: fetch status list: Credential status is not present')
|
|
2354
3804
|
|
|
@@ -2358,31 +3808,34 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2358
3808
|
// validate credential status list status purpose
|
|
2359
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')
|
|
2360
3810
|
|
|
2361
|
-
// validate credential status list status list credential
|
|
2362
|
-
if (!credential.credentialStatus.statusListCredential) throw new Error('[did-provider-cheqd]: fetch status list: Credential status list credential is not present')
|
|
2363
|
-
|
|
2364
3811
|
// fetch status list 2021
|
|
2365
|
-
const
|
|
3812
|
+
const content = await (await fetch(credential.credentialStatus.id.split('#')[0])).json() as StatusList2021Revocation | StatusList2021Suspension
|
|
2366
3813
|
|
|
2367
3814
|
// return raw if requested
|
|
2368
|
-
if (returnRaw)
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
const bitstring = toString(new Uint8Array(raw), 'base64url')
|
|
3815
|
+
if (returnRaw) {
|
|
3816
|
+
return fromString(content.StatusList2021.encodedList, content.metadata.encoding as DefaultStatusList2021Encoding)
|
|
3817
|
+
}
|
|
2372
3818
|
|
|
2373
|
-
return
|
|
3819
|
+
// otherwise, return content
|
|
3820
|
+
return content
|
|
2374
3821
|
}
|
|
2375
3822
|
|
|
2376
3823
|
static async fetchStatusList2021Metadata(credential: VerifiableCredential): Promise<LinkedResourceMetadataResolutionResult> {
|
|
2377
3824
|
// get base url
|
|
2378
|
-
const baseUrl = new URL(credential.credentialStatus
|
|
2379
|
-
|
|
3825
|
+
const baseUrl = new URL(credential.credentialStatus!.id.split('#')[0])
|
|
3826
|
+
|
|
2380
3827
|
// get resource name
|
|
2381
3828
|
const resourceName = baseUrl.searchParams.get('resourceName')
|
|
2382
3829
|
|
|
3830
|
+
// get resource type
|
|
3831
|
+
const resourceType = baseUrl.searchParams.get('resourceType')
|
|
3832
|
+
|
|
2383
3833
|
// unset resource name
|
|
2384
3834
|
baseUrl.searchParams.delete('resourceName')
|
|
2385
3835
|
|
|
3836
|
+
// unset resource type
|
|
3837
|
+
baseUrl.searchParams.delete('resourceType')
|
|
3838
|
+
|
|
2386
3839
|
// construct metadata url
|
|
2387
3840
|
const metadataUrl = `${baseUrl.toString()}/metadata`
|
|
2388
3841
|
|
|
@@ -2393,7 +3846,7 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2393
3846
|
if (!collectionMetadata?.contentStream?.linkedResourceMetadata) throw new Error('[did-provider-cheqd]: fetch status list metadata: No linked resources found')
|
|
2394
3847
|
|
|
2395
3848
|
// find relevant resources by resource name
|
|
2396
|
-
const resourceVersioning = collectionMetadata.contentStream.linkedResourceMetadata.filter((resource) => resource.resourceName === resourceName)
|
|
3849
|
+
const resourceVersioning = collectionMetadata.contentStream.linkedResourceMetadata.filter((resource) => resource.resourceName === resourceName && resource.resourceType === resourceType)
|
|
2397
3850
|
|
|
2398
3851
|
// early exit if no relevant resources
|
|
2399
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}`)
|
|
@@ -2453,4 +3906,16 @@ export class Cheqd implements IAgentPlugin {
|
|
|
2453
3906
|
})
|
|
2454
3907
|
})
|
|
2455
3908
|
}
|
|
3909
|
+
|
|
3910
|
+
static async decodeCredentialJWT(jwt: string): Promise<VerifiableCredential> {
|
|
3911
|
+
const decodedCredential = decodeJWT(jwt)
|
|
3912
|
+
|
|
3913
|
+
// validate credential payload
|
|
3914
|
+
if (!decodedCredential.payload) throw new Error('[did-provider-cheqd]: decode jwt: decodedCredential.payload is required')
|
|
3915
|
+
|
|
3916
|
+
// validate credential payload vc property as VerifiableCredential
|
|
3917
|
+
if (!decodedCredential.payload.vc) throw new Error('[did-provider-cheqd]: decode jwt: decodedCredential.payload.vc is required')
|
|
3918
|
+
|
|
3919
|
+
return decodedCredential.payload.vc satisfies VerifiableCredential
|
|
3920
|
+
}
|
|
2456
3921
|
}
|