@cheqd/did-provider-cheqd 4.5.4 → 4.6.0-develop.1
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/esm/agent/ICheqd.d.ts +305 -89
- package/build/esm/agent/ICheqd.d.ts.map +1 -1
- package/build/esm/agent/ICheqd.js +1567 -87
- package/build/esm/agent/ICheqd.js.map +1 -1
- package/build/esm/did-manager/cheqd-did-provider.d.ts +16 -5
- package/build/esm/did-manager/cheqd-did-provider.d.ts.map +1 -1
- package/build/esm/did-manager/cheqd-did-provider.js +11 -3
- package/build/esm/did-manager/cheqd-did-provider.js.map +1 -1
- package/build/esm/dkg-threshold/lit-protocol/v6.d.ts.map +1 -1
- package/build/esm/dkg-threshold/lit-protocol/v6.js +63 -33
- package/build/esm/dkg-threshold/lit-protocol/v6.js.map +1 -1
- package/build/esm/utils/helpers.d.ts +28 -7
- package/build/esm/utils/helpers.d.ts.map +1 -1
- package/build/esm/utils/helpers.js +120 -29
- package/build/esm/utils/helpers.js.map +1 -1
- package/build/tsconfig.esm.tsbuildinfo +1 -1
- package/build/tsconfig.types.tsbuildinfo +1 -1
- package/build/types/agent/ICheqd.d.ts +305 -89
- package/build/types/agent/ICheqd.d.ts.map +1 -1
- package/build/types/did-manager/cheqd-did-provider.d.ts +16 -5
- package/build/types/did-manager/cheqd-did-provider.d.ts.map +1 -1
- package/build/types/dkg-threshold/lit-protocol/v6.d.ts.map +1 -1
- package/build/types/utils/helpers.d.ts +28 -7
- package/build/types/utils/helpers.d.ts.map +1 -1
- package/package.json +6 -4
- package/src/agent/ICheqd.ts +2440 -379
- package/src/did-manager/cheqd-did-provider.ts +25 -10
- package/src/dkg-threshold/lit-protocol/v6.ts +65 -34
- package/src/utils/helpers.ts +172 -34
- package/tsconfig.json +2 -0
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
// unused vars are kept by convention
|
|
4
4
|
// non-null assertion is used when we know better than the compiler that the value is not null or undefined
|
|
5
5
|
import { CheqdNetwork, ResourceModule, createDidPayload, createDidVerificationMethod, createKeyPairBase64, createKeyPairHex, createVerificationKeys, } from '@cheqd/sdk';
|
|
6
|
-
import { DefaultRESTUrls,
|
|
6
|
+
import { DefaultRESTUrls, DefaultStatusListEncodings, DefaultStatusList2021ResourceTypes, DefaultStatusList2021StatusPurposeTypes, BitstringStatusPurposeTypes, BitstringStatusListResourceType, } from '../did-manager/cheqd-did-provider.js';
|
|
7
7
|
import { fromString, toString } from 'uint8arrays';
|
|
8
8
|
import { decodeJWT } from 'did-jwt';
|
|
9
9
|
import { StatusList } from '@digitalbazaar/vc-status-list';
|
|
10
|
+
import { Bitstring as DBBitstring } from '@digitalbazaar/bitstring';
|
|
10
11
|
import { v4 } from 'uuid';
|
|
11
12
|
import fs from 'fs';
|
|
12
13
|
import Debug from 'debug';
|
|
13
14
|
import { LitCompatibleCosmosChains, LitProtocol, } from '../dkg-threshold/lit-protocol/v6.js';
|
|
14
|
-
import { blobToHexString, getEncodedList, isEncodedList, randomFromRange, safeDeserialise, toBlob, } from '../utils/helpers.js';
|
|
15
|
+
import { blobToHexString, decodeWithMetadata, encodeWithMetadata, generateRandomStatusListIndex, getEncodedList, isEncodedList, isValidEncodedBitstring, randomFromRange, safeDeserialise, toBlob, } from '../utils/helpers.js';
|
|
15
16
|
import { DefaultResolverUrl } from '../did-manager/cheqd-did-resolver.js';
|
|
16
17
|
import { LitNetworksV2, LitProtocolV2 } from '../dkg-threshold/lit-protocol/v2.js';
|
|
17
18
|
const debug = Debug('veramo:did-provider-cheqd');
|
|
@@ -41,6 +42,16 @@ export const GenerateVersionIdMethodName = 'cheqdGenerateVersionId';
|
|
|
41
42
|
export const GenerateStatusList2021MethodName = 'cheqdGenerateStatusList2021';
|
|
42
43
|
export const IssueRevocableCredentialWithStatusList2021MethodName = 'cheqdIssueRevocableCredentialWithStatusList2021';
|
|
43
44
|
export const IssueSuspendableCredentialWithStatusList2021MethodName = 'cheqdIssueSuspendableCredentialWithStatusList2021';
|
|
45
|
+
export const CreateStatusListMethodName = 'cheqdCreateStatusList';
|
|
46
|
+
export const BroadcastStatusListMethodName = 'cheqdBroadcastStatusList';
|
|
47
|
+
export const GenerateStatusListMethodName = 'cheqdGenerateStatusList';
|
|
48
|
+
export const VerifyStatusListCredentialMethodName = 'cheqdVerifyStatusListCredential';
|
|
49
|
+
export const IssueCredentialWithStatusListMethodName = 'cheqdIssueCredentialWithStatusList';
|
|
50
|
+
export const VerifyCredentialWithStatusListMethodName = 'cheqdVerifyCredentialWithStatusList';
|
|
51
|
+
export const UpdateCredentialWithStatusListMethodName = 'cheqdUpdateCredentialWithStatusList';
|
|
52
|
+
export const BulkUpdateCredentialsWithStatusListMethodName = 'cheqdBulkUpdateCredentialsWithStatusList';
|
|
53
|
+
export const VerifyPresentationWithStatusListMethodName = 'cheqdVerifyPresentationWithStatusList';
|
|
54
|
+
// END: TODO, start Remove or update with status list 2021
|
|
44
55
|
export const VerifyCredentialMethodName = 'cheqdVerifyCredential';
|
|
45
56
|
export const VerifyPresentationMethodName = 'cheqdVerifyPresentation';
|
|
46
57
|
export const CheckCredentialStatusMethodName = 'cheqdCheckCredentialStatus';
|
|
@@ -50,12 +61,20 @@ export const SuspendCredentialMethodName = 'cheqdSuspendCredential';
|
|
|
50
61
|
export const SuspendCredentialsMethodName = 'cheqdSuspendCredentials';
|
|
51
62
|
export const UnsuspendCredentialMethodName = 'cheqdUnsuspendCredential';
|
|
52
63
|
export const UnsuspendCredentialsMethodName = 'cheqdUnsuspendCredentials';
|
|
64
|
+
// END: Remove or update with status list 2021
|
|
53
65
|
export const TransactSendTokensMethodName = 'cheqdTransactSendTokens';
|
|
54
66
|
export const ObservePaymentConditionMethodName = 'cheqdObservePaymentCondition';
|
|
55
67
|
export const MintCapacityCreditMethodName = 'cheqdMintCapacityCredit';
|
|
56
68
|
export const DelegateCapacityCreditMethodName = 'cheqdDelegateCapacityCredit';
|
|
57
69
|
export const DidPrefix = 'did';
|
|
58
70
|
export const CheqdDidMethod = 'cheqd';
|
|
71
|
+
export var BitstringStatusValue;
|
|
72
|
+
(function (BitstringStatusValue) {
|
|
73
|
+
BitstringStatusValue[BitstringStatusValue["VALID"] = 0] = "VALID";
|
|
74
|
+
BitstringStatusValue[BitstringStatusValue["REVOKED"] = 1] = "REVOKED";
|
|
75
|
+
BitstringStatusValue[BitstringStatusValue["SUSPENDED"] = 2] = "SUSPENDED";
|
|
76
|
+
BitstringStatusValue[BitstringStatusValue["UNKNOWN"] = 3] = "UNKNOWN";
|
|
77
|
+
})(BitstringStatusValue || (BitstringStatusValue = {}));
|
|
59
78
|
export class Cheqd {
|
|
60
79
|
methods;
|
|
61
80
|
schema = {
|
|
@@ -142,6 +161,22 @@ export class Cheqd {
|
|
|
142
161
|
type: 'object',
|
|
143
162
|
},
|
|
144
163
|
},
|
|
164
|
+
cheqdCreateStatusList: {
|
|
165
|
+
description: 'Create a new Bitstring Status List',
|
|
166
|
+
arguments: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
properties: {
|
|
169
|
+
args: {
|
|
170
|
+
type: 'object',
|
|
171
|
+
description: 'A cheqdCreateBitstringStatusListArgs object as any for extensibility',
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
required: ['args'],
|
|
175
|
+
},
|
|
176
|
+
returnType: {
|
|
177
|
+
type: 'object',
|
|
178
|
+
},
|
|
179
|
+
},
|
|
145
180
|
cheqdBroadcastStatusList2021: {
|
|
146
181
|
description: 'Broadcast a Status List 2021 to cheqd ledger',
|
|
147
182
|
arguments: {
|
|
@@ -149,7 +184,23 @@ export class Cheqd {
|
|
|
149
184
|
properties: {
|
|
150
185
|
args: {
|
|
151
186
|
type: 'object',
|
|
152
|
-
description: 'A
|
|
187
|
+
description: 'A cheqdBroadcastStatusListArgs object as any for extensibility',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
required: ['args'],
|
|
191
|
+
},
|
|
192
|
+
returnType: {
|
|
193
|
+
type: 'object',
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
cheqdBroadcastStatusList: {
|
|
197
|
+
description: 'Broadcast a Bitstring Status List to cheqd ledger',
|
|
198
|
+
arguments: {
|
|
199
|
+
type: 'object',
|
|
200
|
+
properties: {
|
|
201
|
+
args: {
|
|
202
|
+
type: 'object',
|
|
203
|
+
description: 'A cheqdBroadcastStatusListArgs object as any for extensibility',
|
|
153
204
|
},
|
|
154
205
|
},
|
|
155
206
|
required: ['args'],
|
|
@@ -235,6 +286,37 @@ export class Cheqd {
|
|
|
235
286
|
type: 'string',
|
|
236
287
|
},
|
|
237
288
|
},
|
|
289
|
+
cheqdGenerateStatusList: {
|
|
290
|
+
description: 'Generate a new Bitstring Status List',
|
|
291
|
+
arguments: {
|
|
292
|
+
type: 'object',
|
|
293
|
+
properties: {
|
|
294
|
+
args: {
|
|
295
|
+
type: 'object',
|
|
296
|
+
description: 'A cheqdGenerateStatusListArgs object as any for extensibility',
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
returnType: {
|
|
301
|
+
type: 'string',
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
cheqdIssueCredentialWithStatusList: {
|
|
305
|
+
description: 'Issue a revocable or suspendable credential with a Bitstring Status List as credential status registry',
|
|
306
|
+
arguments: {
|
|
307
|
+
type: 'object',
|
|
308
|
+
properties: {
|
|
309
|
+
args: {
|
|
310
|
+
type: 'object',
|
|
311
|
+
description: 'A cheqdIssueCredentialWithStatusListArgs object as any for extensibility',
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
required: ['args'],
|
|
315
|
+
},
|
|
316
|
+
returnType: {
|
|
317
|
+
type: 'object',
|
|
318
|
+
},
|
|
319
|
+
},
|
|
238
320
|
cheqdIssueRevocableCredentialWithStatusList2021: {
|
|
239
321
|
description: 'Issue a revocable credential with a Status List 2021 as credential status registry',
|
|
240
322
|
arguments: {
|
|
@@ -242,7 +324,7 @@ export class Cheqd {
|
|
|
242
324
|
properties: {
|
|
243
325
|
args: {
|
|
244
326
|
type: 'object',
|
|
245
|
-
description: 'A
|
|
327
|
+
description: 'A cheqdIssueRevocableCredentialWithStatusList2021Args object as any for extensibility',
|
|
246
328
|
},
|
|
247
329
|
},
|
|
248
330
|
required: ['args'],
|
|
@@ -258,7 +340,7 @@ export class Cheqd {
|
|
|
258
340
|
properties: {
|
|
259
341
|
args: {
|
|
260
342
|
type: 'object',
|
|
261
|
-
description: 'A
|
|
343
|
+
description: 'A cheqdIssueSuspendableCredentialWithStatusList2021Args object as any for extensibility',
|
|
262
344
|
},
|
|
263
345
|
},
|
|
264
346
|
required: ['args'],
|
|
@@ -274,7 +356,23 @@ export class Cheqd {
|
|
|
274
356
|
properties: {
|
|
275
357
|
args: {
|
|
276
358
|
type: 'object',
|
|
277
|
-
description: 'A
|
|
359
|
+
description: 'A cheqdVerifyCredentialWithStatusListArgs object as any for extensibility',
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
required: ['args'],
|
|
363
|
+
},
|
|
364
|
+
returnType: {
|
|
365
|
+
type: 'object',
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
cheqdVerifyCredentialWithStatusList: {
|
|
369
|
+
description: 'Verify a credential, enhanced by revocation / suspension check with a Bitstring Status List as credential status registry',
|
|
370
|
+
arguments: {
|
|
371
|
+
type: 'object',
|
|
372
|
+
properties: {
|
|
373
|
+
args: {
|
|
374
|
+
type: 'object',
|
|
375
|
+
description: 'A cheqdVerifyCredentialWithStatusListArgs object as any for extensibility',
|
|
278
376
|
},
|
|
279
377
|
},
|
|
280
378
|
required: ['args'],
|
|
@@ -290,7 +388,7 @@ export class Cheqd {
|
|
|
290
388
|
properties: {
|
|
291
389
|
args: {
|
|
292
390
|
type: 'object',
|
|
293
|
-
description: 'A
|
|
391
|
+
description: 'A cheqdVerifyPresentationWithStatusListArgs object as any for extensibility',
|
|
294
392
|
},
|
|
295
393
|
},
|
|
296
394
|
required: ['args'],
|
|
@@ -306,7 +404,7 @@ export class Cheqd {
|
|
|
306
404
|
properties: {
|
|
307
405
|
args: {
|
|
308
406
|
type: 'object',
|
|
309
|
-
description: 'A
|
|
407
|
+
description: 'A cheqdCheckCredentialStatusWithStatusListArgs object as any for extensibility',
|
|
310
408
|
},
|
|
311
409
|
},
|
|
312
410
|
required: ['args'],
|
|
@@ -322,7 +420,7 @@ export class Cheqd {
|
|
|
322
420
|
properties: {
|
|
323
421
|
args: {
|
|
324
422
|
type: 'object',
|
|
325
|
-
description: 'A
|
|
423
|
+
description: 'A cheqdRevokeCredentialWithStatusListArgs object as any for extensibility',
|
|
326
424
|
},
|
|
327
425
|
},
|
|
328
426
|
required: ['args'],
|
|
@@ -338,7 +436,7 @@ export class Cheqd {
|
|
|
338
436
|
properties: {
|
|
339
437
|
args: {
|
|
340
438
|
type: 'object',
|
|
341
|
-
description: 'A
|
|
439
|
+
description: 'A cheqdRevokeBulkCredentialsWithStatusListArgs object as any for extensibility',
|
|
342
440
|
},
|
|
343
441
|
},
|
|
344
442
|
required: ['args'],
|
|
@@ -354,7 +452,7 @@ export class Cheqd {
|
|
|
354
452
|
properties: {
|
|
355
453
|
args: {
|
|
356
454
|
type: 'object',
|
|
357
|
-
description: 'A
|
|
455
|
+
description: 'A cheqdSuspendCredentialWithStatusListArgs object as any for extensibility',
|
|
358
456
|
},
|
|
359
457
|
},
|
|
360
458
|
required: ['args'],
|
|
@@ -370,7 +468,7 @@ export class Cheqd {
|
|
|
370
468
|
properties: {
|
|
371
469
|
args: {
|
|
372
470
|
type: 'object',
|
|
373
|
-
description: 'A
|
|
471
|
+
description: 'A cheqdSuspendBulkCredentialsWithStatusListArgs object as any for extensibility',
|
|
374
472
|
},
|
|
375
473
|
},
|
|
376
474
|
required: ['args'],
|
|
@@ -386,7 +484,7 @@ export class Cheqd {
|
|
|
386
484
|
properties: {
|
|
387
485
|
args: {
|
|
388
486
|
type: 'object',
|
|
389
|
-
description: '
|
|
487
|
+
description: 'cheqdUnsuspendCredentialWithStatusListArgs object as any for extensibility',
|
|
390
488
|
},
|
|
391
489
|
},
|
|
392
490
|
required: ['args'],
|
|
@@ -402,7 +500,7 @@ export class Cheqd {
|
|
|
402
500
|
properties: {
|
|
403
501
|
args: {
|
|
404
502
|
type: 'object',
|
|
405
|
-
description: 'A
|
|
503
|
+
description: 'A cheqdUnsuspendBulkCredentialsWithStatusListArgs object as any for extensibility',
|
|
406
504
|
},
|
|
407
505
|
},
|
|
408
506
|
required: ['args'],
|
|
@@ -449,9 +547,19 @@ export class Cheqd {
|
|
|
449
547
|
supportedDidProviders;
|
|
450
548
|
didProvider;
|
|
451
549
|
providerId;
|
|
550
|
+
// Deprecate below constants in future versions
|
|
452
551
|
static defaultStatusList2021Length = 16 * 1024 * 8; // 16KB in bits or 131072 bits / entries
|
|
453
552
|
static defaultContextV1 = 'https://www.w3.org/2018/credentials/v1';
|
|
454
553
|
static statusList2021Context = 'https://w3id.org/vc-status-list-2021/v1';
|
|
554
|
+
// END: Deprecate
|
|
555
|
+
static DefaultBitstringContexts = {
|
|
556
|
+
v2: 'https://www.w3.org/ns/credentials/v2',
|
|
557
|
+
statusList: 'https://www.w3.org/ns/credentials/status/v1',
|
|
558
|
+
};
|
|
559
|
+
// Default bitstring status list size in bits
|
|
560
|
+
static DefaultBitstringStatusSize = 2; // 2 bits per credential (0, 1, 2, 3)
|
|
561
|
+
// Minimum bitstring length for compliance
|
|
562
|
+
static DefaultBitstringLength = 16 * 1024 * 8; // 16KB in bits or 131072 bits (spec minimum)
|
|
455
563
|
constructor(args) {
|
|
456
564
|
if (typeof args.providers !== 'object') {
|
|
457
565
|
throw new Error('[did-provider-cheqd]: at least one did provider is required');
|
|
@@ -466,16 +574,25 @@ export class Cheqd {
|
|
|
466
574
|
[CreateResourceMethodName]: this.CreateResource.bind(this),
|
|
467
575
|
[CreateStatusList2021MethodName]: this.CreateStatusList2021.bind(this),
|
|
468
576
|
[BroadcastStatusList2021MethodName]: this.BroadcastStatusList2021.bind(this),
|
|
577
|
+
[CreateStatusListMethodName]: this.CreateBitstringStatusList.bind(this),
|
|
578
|
+
[BroadcastStatusListMethodName]: this.BroadcastBitstringStatusList.bind(this),
|
|
469
579
|
[GenerateDidDocMethodName]: this.GenerateDidDoc.bind(this),
|
|
470
580
|
[GenerateDidDocWithLinkedResourceMethodName]: this.GenerateDidDocWithLinkedResource.bind(this),
|
|
471
581
|
[GenerateKeyPairMethodName]: this.GenerateIdentityKeys.bind(this),
|
|
472
582
|
[GenerateVersionIdMethodName]: this.GenerateVersionId.bind(this),
|
|
473
583
|
[GenerateStatusList2021MethodName]: this.GenerateStatusList2021.bind(this),
|
|
584
|
+
[GenerateStatusListMethodName]: this.GenerateBitstringStatusList.bind(this),
|
|
585
|
+
[VerifyStatusListCredentialMethodName]: this.VerifyStatusListCredential.bind(this),
|
|
586
|
+
[IssueCredentialWithStatusListMethodName]: this.IssueCredentialWithBitstringStatusList.bind(this),
|
|
474
587
|
[IssueRevocableCredentialWithStatusList2021MethodName]: this.IssueRevocableCredentialWithStatusList2021.bind(this),
|
|
475
588
|
[IssueSuspendableCredentialWithStatusList2021MethodName]: this.IssueSuspendableCredentialWithStatusList2021.bind(this),
|
|
476
589
|
[VerifyCredentialMethodName]: this.VerifyCredentialWithStatusList2021.bind(this),
|
|
590
|
+
[VerifyCredentialWithStatusListMethodName]: this.VerifyCredentialWithBitstringStatusList.bind(this),
|
|
477
591
|
[VerifyPresentationMethodName]: this.VerifyPresentationWithStatusList2021.bind(this),
|
|
478
592
|
[CheckCredentialStatusMethodName]: this.CheckCredentialStatusWithStatusList2021.bind(this),
|
|
593
|
+
[VerifyPresentationWithStatusListMethodName]: this.VerifyPresentationWithBitstringStatusList.bind(this),
|
|
594
|
+
[UpdateCredentialWithStatusListMethodName]: this.UpdateCredentialWithStatusList.bind(this),
|
|
595
|
+
[BulkUpdateCredentialsWithStatusListMethodName]: this.BulkUpdateCredentialsWithStatusList.bind(this),
|
|
479
596
|
[RevokeCredentialMethodName]: this.RevokeCredentialWithStatusList2021.bind(this),
|
|
480
597
|
[RevokeCredentialsMethodName]: this.RevokeBulkCredentialsWithStatusList2021.bind(this),
|
|
481
598
|
[SuspendCredentialMethodName]: this.SuspendCredentialWithStatusList2021.bind(this),
|
|
@@ -614,8 +731,8 @@ export class Cheqd {
|
|
|
614
731
|
if (typeof args.statusListEncoding !== 'string') {
|
|
615
732
|
throw new Error('[did-provider-cheqd]: statusListEncoding must be string');
|
|
616
733
|
}
|
|
617
|
-
if (!Object.values(
|
|
618
|
-
throw new Error(`[did-provider-cheqd]: statusListEncoding must be one of ${Object.values(
|
|
734
|
+
if (!Object.values(DefaultStatusListEncodings).includes(args.statusListEncoding)) {
|
|
735
|
+
throw new Error(`[did-provider-cheqd]: statusListEncoding must be one of ${Object.values(DefaultStatusListEncodings).join(', ')}`);
|
|
619
736
|
}
|
|
620
737
|
}
|
|
621
738
|
// validate validUntil
|
|
@@ -661,13 +778,13 @@ export class Cheqd {
|
|
|
661
778
|
// generate bitstring
|
|
662
779
|
const bitstring = await context.agent[GenerateStatusList2021MethodName]({
|
|
663
780
|
length: args?.statusListLength || Cheqd.defaultStatusList2021Length,
|
|
664
|
-
bitstringEncoding: args?.statusListEncoding ||
|
|
781
|
+
bitstringEncoding: args?.statusListEncoding || DefaultStatusListEncodings.base64url,
|
|
665
782
|
});
|
|
666
783
|
// construct data and metadata tuple
|
|
667
784
|
const data = args.encrypted
|
|
668
785
|
? await (async function (that) {
|
|
669
786
|
// encrypt bitstring - case: symmetric
|
|
670
|
-
const { encryptedString: symmetricEncryptionCiphertext, symmetricKey } = await LitProtocol.encryptDirect(fromString(bitstring, args?.statusListEncoding ||
|
|
787
|
+
const { encryptedString: symmetricEncryptionCiphertext, symmetricKey } = await LitProtocol.encryptDirect(fromString(bitstring, args?.statusListEncoding || DefaultStatusListEncodings.base64url));
|
|
671
788
|
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
672
789
|
const lit = await provider.instantiateDkgThresholdProtocolClient({});
|
|
673
790
|
// construct access control conditions
|
|
@@ -684,7 +801,7 @@ export class Cheqd {
|
|
|
684
801
|
}
|
|
685
802
|
}));
|
|
686
803
|
// encrypt bitstring - case: threshold
|
|
687
|
-
const { encryptedString: thresholdEncryptionCiphertext, stringHash } = await lit.encrypt(fromString(bitstring, args?.statusListEncoding ||
|
|
804
|
+
const { encryptedString: thresholdEncryptionCiphertext, stringHash } = await lit.encrypt(fromString(bitstring, args?.statusListEncoding || DefaultStatusListEncodings.base64url), unifiedAccessControlConditions);
|
|
688
805
|
// construct encoded list
|
|
689
806
|
const encodedList = `${await blobToHexString(symmetricEncryptionCiphertext)}-${toString(thresholdEncryptionCiphertext, 'hex')}`;
|
|
690
807
|
// return result tuple
|
|
@@ -701,7 +818,7 @@ export class Cheqd {
|
|
|
701
818
|
metadata: {
|
|
702
819
|
type: DefaultStatusList2021ResourceTypes.revocation,
|
|
703
820
|
encrypted: true,
|
|
704
|
-
encoding: args?.statusListEncoding ||
|
|
821
|
+
encoding: args?.statusListEncoding || DefaultStatusListEncodings.base64url,
|
|
705
822
|
statusListHash: stringHash,
|
|
706
823
|
paymentConditions: args.paymentConditions,
|
|
707
824
|
},
|
|
@@ -725,7 +842,7 @@ export class Cheqd {
|
|
|
725
842
|
metadata: {
|
|
726
843
|
type: DefaultStatusList2021ResourceTypes.suspension,
|
|
727
844
|
encrypted: true,
|
|
728
|
-
encoding: args?.statusListEncoding ||
|
|
845
|
+
encoding: args?.statusListEncoding || DefaultStatusListEncodings.base64url,
|
|
729
846
|
statusListHash: stringHash,
|
|
730
847
|
paymentConditions: args.paymentConditions,
|
|
731
848
|
},
|
|
@@ -755,7 +872,7 @@ export class Cheqd {
|
|
|
755
872
|
metadata: {
|
|
756
873
|
type: DefaultStatusList2021ResourceTypes.revocation,
|
|
757
874
|
encrypted: false,
|
|
758
|
-
encoding: args?.statusListEncoding ||
|
|
875
|
+
encoding: args?.statusListEncoding || DefaultStatusListEncodings.base64url,
|
|
759
876
|
},
|
|
760
877
|
},
|
|
761
878
|
undefined,
|
|
@@ -772,7 +889,7 @@ export class Cheqd {
|
|
|
772
889
|
metadata: {
|
|
773
890
|
type: DefaultStatusList2021ResourceTypes.suspension,
|
|
774
891
|
encrypted: false,
|
|
775
|
-
encoding: args?.statusListEncoding ||
|
|
892
|
+
encoding: args?.statusListEncoding || DefaultStatusListEncodings.base64url,
|
|
776
893
|
},
|
|
777
894
|
},
|
|
778
895
|
undefined,
|
|
@@ -799,7 +916,7 @@ export class Cheqd {
|
|
|
799
916
|
network: network,
|
|
800
917
|
}),
|
|
801
918
|
resource: data[0],
|
|
802
|
-
resourceMetadata: await Cheqd.
|
|
919
|
+
resourceMetadata: await Cheqd.fetchStatusListMetadata({
|
|
803
920
|
credentialStatus: {
|
|
804
921
|
id: `${DefaultResolverUrl}${args.issuerDid}?resourceName=${args.statusListName}&resourceType=${DefaultStatusList2021ResourceTypes[args.statusPurpose]}`,
|
|
805
922
|
type: 'StatusList2021Entry',
|
|
@@ -827,8 +944,250 @@ export class Cheqd {
|
|
|
827
944
|
}
|
|
828
945
|
// TODO: validate data as per bitstring
|
|
829
946
|
// validate resource type
|
|
830
|
-
|
|
831
|
-
|
|
947
|
+
const allowedTypes = [...Object.values(DefaultStatusList2021ResourceTypes), 'BitstringStatusListCredential'];
|
|
948
|
+
if (!Object.values(allowedTypes).includes(args?.payload?.resourceType)) {
|
|
949
|
+
throw new Error(`[did-provider-cheqd]: resourceType must be one of ${Object.values(allowedTypes).join(', ')}`);
|
|
950
|
+
}
|
|
951
|
+
this.providerId = Cheqd.generateProviderId(args.network);
|
|
952
|
+
this.didProvider = await Cheqd.getProviderFromNetwork(args.network, this.supportedDidProviders);
|
|
953
|
+
return await this.didProvider.createResource({
|
|
954
|
+
options: {
|
|
955
|
+
kms: args.kms,
|
|
956
|
+
payload: args.payload,
|
|
957
|
+
signInputs: args.signInputs,
|
|
958
|
+
fee: args?.fee ||
|
|
959
|
+
(await ResourceModule.generateCreateResourceJsonFees((await this.didProvider.getWalletAccounts())[0].address)),
|
|
960
|
+
},
|
|
961
|
+
}, context);
|
|
962
|
+
}
|
|
963
|
+
async CreateBitstringStatusList(args, context) {
|
|
964
|
+
if (typeof args.kms !== 'string') {
|
|
965
|
+
throw new Error('[did-provider-cheqd]: kms is required');
|
|
966
|
+
}
|
|
967
|
+
if (typeof args.issuerDid !== 'string' || !args.issuerDid) {
|
|
968
|
+
throw new Error('[did-provider-cheqd]: issuerDid is required');
|
|
969
|
+
}
|
|
970
|
+
if (typeof args.statusListName !== 'string' || !args.statusListName) {
|
|
971
|
+
throw new Error('[did-provider-cheqd]: statusListName is required');
|
|
972
|
+
}
|
|
973
|
+
if (!args.statusPurpose) {
|
|
974
|
+
throw new Error('[did-provider-cheqd]: statusPurpose is required');
|
|
975
|
+
}
|
|
976
|
+
if (typeof args.encrypted === 'undefined') {
|
|
977
|
+
throw new Error('[did-provider-cheqd]: encrypted is required');
|
|
978
|
+
}
|
|
979
|
+
// validate statusPurpose
|
|
980
|
+
const statusPurpose = Array.isArray(args.statusPurpose) ? args.statusPurpose[0] : args.statusPurpose;
|
|
981
|
+
if (!Object.values(BitstringStatusPurposeTypes).includes(statusPurpose)) {
|
|
982
|
+
throw new Error(`[did-provider-cheqd]: statusPurpose must be in ${Object.values(BitstringStatusPurposeTypes).join(', ')}`);
|
|
983
|
+
}
|
|
984
|
+
// validate statusListLength
|
|
985
|
+
if (args?.statusListLength) {
|
|
986
|
+
if (typeof args.statusListLength !== 'number') {
|
|
987
|
+
throw new Error('[did-provider-cheqd]: statusListLength must be number');
|
|
988
|
+
}
|
|
989
|
+
if (args.statusListLength < Cheqd.DefaultBitstringLength) {
|
|
990
|
+
throw new Error(`[did-provider-cheqd]: statusListLength must be greater than or equal to ${Cheqd.DefaultBitstringLength} number of entries`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
// validate statusListEncoding, W3C spec only supports base64url encoding
|
|
994
|
+
if (args?.statusListEncoding) {
|
|
995
|
+
if (typeof args.statusListEncoding !== 'string') {
|
|
996
|
+
throw new Error('[did-provider-cheqd]: statusListEncoding must be string');
|
|
997
|
+
}
|
|
998
|
+
if (args.statusListEncoding !== DefaultStatusListEncodings.base64url) {
|
|
999
|
+
throw new Error(`[did-provider-cheqd]: statusListEncoding must be ${DefaultStatusListEncodings.base64url}`);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
// validate validUntil
|
|
1003
|
+
if (args?.validUntil) {
|
|
1004
|
+
if (typeof args.validUntil !== 'string') {
|
|
1005
|
+
throw new Error('[did-provider-cheqd]: validUntil must be string');
|
|
1006
|
+
}
|
|
1007
|
+
if (new Date() <= new Date(args.validUntil)) {
|
|
1008
|
+
throw new Error('[did-provider-cheqd]: validUntil must be greater than current date');
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
// validate args in pairs - case: encrypted
|
|
1012
|
+
if (args.encrypted) {
|
|
1013
|
+
// validate paymentConditions
|
|
1014
|
+
if (!args?.paymentConditions ||
|
|
1015
|
+
!args?.paymentConditions?.length ||
|
|
1016
|
+
!Array.isArray(args?.paymentConditions) ||
|
|
1017
|
+
args?.paymentConditions.length === 0) {
|
|
1018
|
+
throw new Error('[did-provider-cheqd]: paymentConditions is required');
|
|
1019
|
+
}
|
|
1020
|
+
if (!args?.paymentConditions?.every((condition) => condition.feePaymentAddress && condition.feePaymentAmount && condition.intervalInSeconds)) {
|
|
1021
|
+
throw new Error('[did-provider-cheqd]: paymentConditions must contain feePaymentAddress and feeAmount and intervalInSeconds');
|
|
1022
|
+
}
|
|
1023
|
+
if (!args?.paymentConditions?.every((condition) => typeof condition.feePaymentAddress === 'string' &&
|
|
1024
|
+
typeof condition.feePaymentAmount === 'string' &&
|
|
1025
|
+
typeof condition.intervalInSeconds === 'number')) {
|
|
1026
|
+
throw new Error('[did-provider-cheqd]: feePaymentAddress and feePaymentAmount must be string and intervalInSeconds must be number');
|
|
1027
|
+
}
|
|
1028
|
+
if (!args?.paymentConditions?.every((condition) => condition.type === AccessControlConditionTypes.timelockPayment)) {
|
|
1029
|
+
throw new Error('[did-provider-cheqd]: paymentConditions must be of type timelockPayment');
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
// get network
|
|
1033
|
+
const network = args.issuerDid.split(':')[2];
|
|
1034
|
+
// define provider
|
|
1035
|
+
const provider = (function (that) {
|
|
1036
|
+
// switch on network
|
|
1037
|
+
return (that.supportedDidProviders.find((provider) => provider.network === network) ||
|
|
1038
|
+
(function () {
|
|
1039
|
+
throw new Error(`[did-provider-cheqd]: no relevant providers found`);
|
|
1040
|
+
})());
|
|
1041
|
+
})(this);
|
|
1042
|
+
// generate bitstring
|
|
1043
|
+
const bitstring = await context.agent[GenerateStatusListMethodName]({
|
|
1044
|
+
statusSize: args?.statusSize,
|
|
1045
|
+
length: args?.statusListLength,
|
|
1046
|
+
bitstringEncoding: args?.statusListEncoding || DefaultStatusListEncodings.base64url,
|
|
1047
|
+
});
|
|
1048
|
+
// Generate proof without credentialSubject.encodedList property
|
|
1049
|
+
const issuanceOptions = {
|
|
1050
|
+
credential: {
|
|
1051
|
+
'@context': [Cheqd.defaultContextV1], // TODO: use v2 context when v2 credential support enabled
|
|
1052
|
+
type: ['VerifiableCredential', BitstringStatusListResourceType],
|
|
1053
|
+
issuer: args.issuerDid,
|
|
1054
|
+
issuanceDate: new Date().toISOString(),
|
|
1055
|
+
expirationDate: args?.validUntil,
|
|
1056
|
+
credentialSubject: {
|
|
1057
|
+
type: 'BitstringStatusList',
|
|
1058
|
+
statusPurpose: args.statusPurpose,
|
|
1059
|
+
ttl: args?.ttl,
|
|
1060
|
+
},
|
|
1061
|
+
},
|
|
1062
|
+
proofFormat: 'jwt',
|
|
1063
|
+
};
|
|
1064
|
+
const issued = await context.agent.createVerifiableCredential(issuanceOptions);
|
|
1065
|
+
// construct data and metadata tuple
|
|
1066
|
+
const data = args.encrypted
|
|
1067
|
+
? await (async function (that) {
|
|
1068
|
+
// encrypt bitstring - case: symmetric
|
|
1069
|
+
const { encryptedString: symmetricEncryptionCiphertext, symmetricKey } = await LitProtocol.encryptDirect(fromString(bitstring, args?.statusListEncoding || DefaultStatusListEncodings.base64url));
|
|
1070
|
+
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
1071
|
+
const lit = await provider.instantiateDkgThresholdProtocolClient({});
|
|
1072
|
+
// construct access control conditions
|
|
1073
|
+
const unifiedAccessControlConditions = await Promise.all(args.paymentConditions.map(async (condition) => {
|
|
1074
|
+
switch (condition.type) {
|
|
1075
|
+
case AccessControlConditionTypes.timelockPayment:
|
|
1076
|
+
return await LitProtocol.generateCosmosAccessControlConditionInverseTimelock({
|
|
1077
|
+
key: '$.tx_responses.*.timestamp',
|
|
1078
|
+
comparator: '<=',
|
|
1079
|
+
value: `${condition.intervalInSeconds}`,
|
|
1080
|
+
}, condition.feePaymentAmount, condition.feePaymentAddress, condition?.blockHeight, args?.dkgOptions?.chain || that.didProvider.dkgOptions.chain);
|
|
1081
|
+
default:
|
|
1082
|
+
throw new Error(`[did-provider-cheqd]: unsupported access control condition type ${condition.type}`);
|
|
1083
|
+
}
|
|
1084
|
+
}));
|
|
1085
|
+
// encrypt bitstring - case: threshold
|
|
1086
|
+
const { encryptedString: thresholdEncryptionCiphertext, stringHash } = await lit.encrypt(fromString(bitstring, args?.statusListEncoding || DefaultStatusListEncodings.base64url), unifiedAccessControlConditions);
|
|
1087
|
+
// construct encoded list
|
|
1088
|
+
const { encodedList, symmetricLength } = await encodeWithMetadata(symmetricEncryptionCiphertext, thresholdEncryptionCiphertext);
|
|
1089
|
+
issued.credentialSubject = {
|
|
1090
|
+
type: 'BitstringStatusList',
|
|
1091
|
+
statusPurpose: args.statusPurpose,
|
|
1092
|
+
encodedList,
|
|
1093
|
+
ttl: args?.ttl,
|
|
1094
|
+
};
|
|
1095
|
+
// return result tuple
|
|
1096
|
+
return [
|
|
1097
|
+
{
|
|
1098
|
+
bitstringStatusListCredential: issued,
|
|
1099
|
+
metadata: {
|
|
1100
|
+
encrypted: true,
|
|
1101
|
+
encoding: args?.statusListEncoding || DefaultStatusListEncodings.base64url,
|
|
1102
|
+
length: args?.statusListLength || Cheqd.DefaultBitstringLength,
|
|
1103
|
+
statusSize: args?.statusSize,
|
|
1104
|
+
statusMessages: args?.statusMessages || [],
|
|
1105
|
+
statusListHash: stringHash,
|
|
1106
|
+
symmetricLength,
|
|
1107
|
+
paymentConditions: args.paymentConditions,
|
|
1108
|
+
},
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
symmetricEncryptionCiphertext: await blobToHexString(symmetricEncryptionCiphertext),
|
|
1112
|
+
thresholdEncryptionCiphertext: toString(thresholdEncryptionCiphertext, 'hex'),
|
|
1113
|
+
stringHash,
|
|
1114
|
+
symmetricKey: toString(symmetricKey, 'hex'),
|
|
1115
|
+
},
|
|
1116
|
+
];
|
|
1117
|
+
})(this)
|
|
1118
|
+
: await (async function () {
|
|
1119
|
+
issued.credentialSubject = {
|
|
1120
|
+
type: 'BitstringStatusList',
|
|
1121
|
+
statusPurpose: args.statusPurpose,
|
|
1122
|
+
encodedList: bitstring,
|
|
1123
|
+
ttl: args?.ttl,
|
|
1124
|
+
};
|
|
1125
|
+
return [
|
|
1126
|
+
{
|
|
1127
|
+
bitstringStatusListCredential: issued,
|
|
1128
|
+
metadata: {
|
|
1129
|
+
encrypted: false,
|
|
1130
|
+
encoding: args?.statusListEncoding || DefaultStatusListEncodings.base64url,
|
|
1131
|
+
length: args?.statusListLength || Cheqd.DefaultBitstringLength,
|
|
1132
|
+
statusSize: args?.statusSize,
|
|
1133
|
+
statusMessages: args?.statusMessages || [],
|
|
1134
|
+
},
|
|
1135
|
+
},
|
|
1136
|
+
undefined,
|
|
1137
|
+
];
|
|
1138
|
+
})();
|
|
1139
|
+
// construct payload
|
|
1140
|
+
const payload = {
|
|
1141
|
+
id: v4(),
|
|
1142
|
+
collectionId: args.issuerDid.split(':').reverse()[0],
|
|
1143
|
+
name: args.statusListName,
|
|
1144
|
+
resourceType: BitstringStatusListResourceType,
|
|
1145
|
+
version: args?.resourceVersion || new Date().toISOString(),
|
|
1146
|
+
alsoKnownAs: args?.alsoKnownAs || [],
|
|
1147
|
+
data: fromString(JSON.stringify(data[0]), 'utf-8'),
|
|
1148
|
+
};
|
|
1149
|
+
// return result
|
|
1150
|
+
return {
|
|
1151
|
+
created: await context.agent[BroadcastStatusListMethodName]({
|
|
1152
|
+
kms: args.kms,
|
|
1153
|
+
payload,
|
|
1154
|
+
network: network,
|
|
1155
|
+
}),
|
|
1156
|
+
resource: data[0],
|
|
1157
|
+
resourceMetadata: await Cheqd.fetchStatusListMetadata({
|
|
1158
|
+
credentialStatus: {
|
|
1159
|
+
id: `${DefaultResolverUrl}${args.issuerDid}?resourceName=${args.statusListName}&resourceType=${BitstringStatusListResourceType}`,
|
|
1160
|
+
},
|
|
1161
|
+
}),
|
|
1162
|
+
encrypted: args.encrypted,
|
|
1163
|
+
symmetricKey: args.encrypted && args.returnSymmetricKey ? data[1]?.symmetricKey : undefined,
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
async BroadcastBitstringStatusList(args, context) {
|
|
1167
|
+
if (typeof args.kms !== 'string') {
|
|
1168
|
+
throw new Error('[did-provider-cheqd]: kms is required');
|
|
1169
|
+
}
|
|
1170
|
+
if (typeof args.payload !== 'object') {
|
|
1171
|
+
throw new Error('[did-provider-cheqd]: payload object is required');
|
|
1172
|
+
}
|
|
1173
|
+
if (typeof args.network !== 'string') {
|
|
1174
|
+
throw new Error('[did-provider-cheqd]: network is required');
|
|
1175
|
+
}
|
|
1176
|
+
if (args?.file) {
|
|
1177
|
+
args.payload.data = await Cheqd.getFile(args.file);
|
|
1178
|
+
}
|
|
1179
|
+
if (typeof args?.payload?.data === 'string') {
|
|
1180
|
+
args.payload.data = fromString(args.payload.data, 'base64');
|
|
1181
|
+
}
|
|
1182
|
+
// Validate that data is present
|
|
1183
|
+
if (!args.payload.data) {
|
|
1184
|
+
throw new Error('[did-provider-cheqd]: payload.data is required for Bitstring Status List');
|
|
1185
|
+
}
|
|
1186
|
+
// Validate and parse the Bitstring Status List data
|
|
1187
|
+
await this.validateBitstringStatusListPayload(args.payload);
|
|
1188
|
+
// Validate against resource type
|
|
1189
|
+
if (args?.payload?.resourceType !== BitstringStatusListResourceType) {
|
|
1190
|
+
throw new Error(`[did-provider-cheqd]: resourceType must be ${BitstringStatusListResourceType}`);
|
|
832
1191
|
}
|
|
833
1192
|
this.providerId = Cheqd.generateProviderId(args.network);
|
|
834
1193
|
this.didProvider = await Cheqd.getProviderFromNetwork(args.network, this.supportedDidProviders);
|
|
@@ -842,6 +1201,115 @@ export class Cheqd {
|
|
|
842
1201
|
},
|
|
843
1202
|
}, context);
|
|
844
1203
|
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Validate Bitstring Status List payload structure and content
|
|
1206
|
+
*/
|
|
1207
|
+
async validateBitstringStatusListPayload(payload) {
|
|
1208
|
+
if (!payload.data) {
|
|
1209
|
+
throw new Error('[did-provider-cheqd]: payload.data is required');
|
|
1210
|
+
}
|
|
1211
|
+
let bitstringData;
|
|
1212
|
+
try {
|
|
1213
|
+
// Parse the data as BitstringStatusList
|
|
1214
|
+
const dataString = typeof payload.data === 'string' ? payload.data : toString(payload.data, 'utf-8');
|
|
1215
|
+
bitstringData = JSON.parse(dataString);
|
|
1216
|
+
}
|
|
1217
|
+
catch (error) {
|
|
1218
|
+
throw new Error(`[did-provider-cheqd]: Invalid BitstringStatusList data format: ${error.message}`);
|
|
1219
|
+
}
|
|
1220
|
+
const metadata = bitstringData.metadata;
|
|
1221
|
+
const bitstringCredential = bitstringData.bitstringStatusListCredential;
|
|
1222
|
+
// Validate required properties
|
|
1223
|
+
if (!bitstringCredential.credentialSubject.statusPurpose) {
|
|
1224
|
+
throw new Error('[did-provider-cheqd]: statusPurpose is required');
|
|
1225
|
+
}
|
|
1226
|
+
if (!bitstringCredential.credentialSubject.encodedList) {
|
|
1227
|
+
throw new Error('[did-provider-cheqd]: encodedList is required');
|
|
1228
|
+
}
|
|
1229
|
+
if (!bitstringCredential.issuanceDate) {
|
|
1230
|
+
throw new Error('[did-provider-cheqd]: issuanceDate is required');
|
|
1231
|
+
}
|
|
1232
|
+
// Validate status purpose
|
|
1233
|
+
const statusPurpose = Array.isArray(bitstringCredential.credentialSubject.statusPurpose)
|
|
1234
|
+
? bitstringCredential.credentialSubject.statusPurpose[0]
|
|
1235
|
+
: bitstringCredential.credentialSubject.statusPurpose;
|
|
1236
|
+
if (!Object.values(BitstringStatusPurposeTypes).includes(statusPurpose)) {
|
|
1237
|
+
throw new Error(`[did-provider-cheqd]: Invalid statusPurpose. Must be in: ${Object.values(BitstringStatusPurposeTypes).join(', ')}`);
|
|
1238
|
+
}
|
|
1239
|
+
// Validate encoded list format (should be base64url encoded)
|
|
1240
|
+
if (!isValidEncodedBitstring(bitstringCredential.credentialSubject.encodedList)) {
|
|
1241
|
+
throw new Error('[did-provider-cheqd]: Invalid encodedList format. Must be base64url encoded GZIP compressed bitstring');
|
|
1242
|
+
}
|
|
1243
|
+
// Validate dates
|
|
1244
|
+
try {
|
|
1245
|
+
new Date(bitstringCredential.issuanceDate);
|
|
1246
|
+
if (bitstringCredential.expirationDate) {
|
|
1247
|
+
const validUntil = new Date(bitstringCredential.expirationDate);
|
|
1248
|
+
const validFrom = new Date(bitstringCredential.issuanceDate);
|
|
1249
|
+
if (validUntil <= validFrom) {
|
|
1250
|
+
throw new Error('[did-provider-cheqd]: expirationDate must be after issuanceDate');
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
catch (error) {
|
|
1255
|
+
throw new Error(`[did-provider-cheqd]: Invalid date format: ${error.message}`);
|
|
1256
|
+
}
|
|
1257
|
+
// Validate status size if present
|
|
1258
|
+
if (metadata.statusSize !== undefined) {
|
|
1259
|
+
if (![1, 2, 4, 8].includes(metadata.statusSize)) {
|
|
1260
|
+
throw new Error('[did-provider-cheqd]: statusSize must be 1, 2, 4, or 8 bits');
|
|
1261
|
+
}
|
|
1262
|
+
// Validate status messages for multi-bit status
|
|
1263
|
+
if (metadata.statusSize > 1) {
|
|
1264
|
+
await this.validateStatusMessagesInPayload(metadata.statusMessages, metadata.statusSize);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
// Validate TTL if present
|
|
1268
|
+
if (bitstringCredential.credentialSubject.ttl !== undefined) {
|
|
1269
|
+
if (typeof bitstringCredential.credentialSubject.ttl !== 'number' ||
|
|
1270
|
+
bitstringCredential.credentialSubject.ttl < 0) {
|
|
1271
|
+
throw new Error('[did-provider-cheqd]: ttl must be a non-negative number');
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Validate status messages for multi-bit status
|
|
1277
|
+
*/
|
|
1278
|
+
async validateStatusMessagesInPayload(statusMessages, statusSize) {
|
|
1279
|
+
if (statusSize <= 1) {
|
|
1280
|
+
return; // No validation needed for single-bit status
|
|
1281
|
+
}
|
|
1282
|
+
const expectedMessageCount = Math.pow(2, statusSize);
|
|
1283
|
+
if (!statusMessages || statusMessages.length === 0) {
|
|
1284
|
+
throw new Error(`[did-provider-cheqd]: statusMessages is required for ${statusSize}-bit status (expected ${expectedMessageCount} messages)`);
|
|
1285
|
+
}
|
|
1286
|
+
if (statusMessages.length !== expectedMessageCount) {
|
|
1287
|
+
throw new Error(`[did-provider-cheqd]: statusMessages must have exactly ${expectedMessageCount} entries for ${statusSize}-bit status`);
|
|
1288
|
+
}
|
|
1289
|
+
// Validate each status message
|
|
1290
|
+
const expectedStatuses = new Set();
|
|
1291
|
+
for (let i = 0; i < expectedMessageCount; i++) {
|
|
1292
|
+
expectedStatuses.add(`0x${i.toString(16).toLowerCase()}`);
|
|
1293
|
+
}
|
|
1294
|
+
const actualStatuses = new Set(statusMessages.map((msg) => msg.status));
|
|
1295
|
+
for (const expected of expectedStatuses) {
|
|
1296
|
+
if (!actualStatuses.has(expected)) {
|
|
1297
|
+
throw new Error(`[did-provider-cheqd]: Missing status message for value ${expected}`);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
// Validate message format
|
|
1301
|
+
for (const statusMsg of statusMessages) {
|
|
1302
|
+
if (!statusMsg.status || !statusMsg.message) {
|
|
1303
|
+
throw new Error('[did-provider-cheqd]: Each status message must have both status and message properties');
|
|
1304
|
+
}
|
|
1305
|
+
if (typeof statusMsg.status !== 'string' || typeof statusMsg.message !== 'string') {
|
|
1306
|
+
throw new Error('[did-provider-cheqd]: status and message must be strings');
|
|
1307
|
+
}
|
|
1308
|
+
if (!statusMsg.status.match(/^0x[0-9a-f]+$/i)) {
|
|
1309
|
+
throw new Error(`[did-provider-cheqd]: Invalid status format ${statusMsg.status}. Must be hex prefixed with 0x`);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
845
1313
|
async GenerateDidDoc(args, context) {
|
|
846
1314
|
if (typeof args.verificationMethod !== 'string') {
|
|
847
1315
|
throw new Error('[did-provider-cheqd]: verificationMethod is required');
|
|
@@ -938,6 +1406,158 @@ export class Cheqd {
|
|
|
938
1406
|
return encoded;
|
|
939
1407
|
}
|
|
940
1408
|
}
|
|
1409
|
+
async GenerateBitstringStatusList(args, context) {
|
|
1410
|
+
const statusSize = args?.statusSize || 1; // default to 1 bit per entry // TODO change to 2 bits per entry after StatusList2021 is removed
|
|
1411
|
+
const length = args?.length || Cheqd.DefaultBitstringLength; // default to 131072
|
|
1412
|
+
// Total number of bits = entries * bits per entry
|
|
1413
|
+
const totalBits = length * statusSize;
|
|
1414
|
+
// Ensure bitstring is byte-aligned (i.e., multiple of 8 bits)
|
|
1415
|
+
const alignedLength = Math.ceil(totalBits / 8) * 8;
|
|
1416
|
+
const bitstring = args?.buffer
|
|
1417
|
+
? new DBBitstring({ buffer: args.buffer })
|
|
1418
|
+
: new DBBitstring({ length: alignedLength });
|
|
1419
|
+
// get compressed bits
|
|
1420
|
+
const compressed = await bitstring.compressBits();
|
|
1421
|
+
switch (args?.bitstringEncoding) {
|
|
1422
|
+
case 'hex':
|
|
1423
|
+
return toString(compressed, 'hex');
|
|
1424
|
+
case 'base64url':
|
|
1425
|
+
default:
|
|
1426
|
+
return toString(compressed, 'base64url');
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
async VerifyStatusListCredential(args, context) {
|
|
1430
|
+
// if jwt credential, decode it
|
|
1431
|
+
const credentialObj = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
|
|
1432
|
+
// Validate required fields
|
|
1433
|
+
if (!credentialObj || typeof credentialObj !== 'object') {
|
|
1434
|
+
return {
|
|
1435
|
+
verified: false,
|
|
1436
|
+
error: { message: 'Invalid credential format' },
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
const { credentialSubject, ...rest } = credentialObj;
|
|
1440
|
+
// Validate credentialSubject and encodedList
|
|
1441
|
+
if (!credentialSubject?.encodedList) {
|
|
1442
|
+
return {
|
|
1443
|
+
verified: false,
|
|
1444
|
+
error: { message: 'Missing encodedList in credentialSubject' },
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
// Validate that this is indeed a status list credential
|
|
1448
|
+
if (!credentialObj.type || !credentialObj.type.includes('BitstringStatusListCredential')) {
|
|
1449
|
+
return {
|
|
1450
|
+
verified: false,
|
|
1451
|
+
error: { message: 'Credential is not a BitstringStatusListCredential' },
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
// Extract encodedList and create credential without it for verification
|
|
1455
|
+
const { encodedList, ...restCredentialSubject } = credentialSubject || {};
|
|
1456
|
+
// Create formatted credential for verification without encodedList
|
|
1457
|
+
const formattedCredential = {
|
|
1458
|
+
credentialSubject: restCredentialSubject,
|
|
1459
|
+
...rest,
|
|
1460
|
+
};
|
|
1461
|
+
// verify default policies
|
|
1462
|
+
const verificationResult = await context.agent.verifyCredential({
|
|
1463
|
+
...args?.verificationArgs,
|
|
1464
|
+
credential: formattedCredential,
|
|
1465
|
+
policies: {
|
|
1466
|
+
...args?.verificationArgs?.policies,
|
|
1467
|
+
// Disable credentialStatus check for status list credentials to avoid circular dependency
|
|
1468
|
+
credentialStatus: false,
|
|
1469
|
+
},
|
|
1470
|
+
});
|
|
1471
|
+
// Additional validation for BitstringStatusListCredential
|
|
1472
|
+
if (verificationResult.verified) {
|
|
1473
|
+
// Basic validation that encodedList is properly formatted
|
|
1474
|
+
if (typeof encodedList !== 'string' || !encodedList) {
|
|
1475
|
+
return {
|
|
1476
|
+
verified: false,
|
|
1477
|
+
error: { message: 'Invalid encodedList format' },
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
// Validate encodedList format (should be base64url encoded)
|
|
1481
|
+
if (!isValidEncodedBitstring(encodedList)) {
|
|
1482
|
+
return {
|
|
1483
|
+
verified: false,
|
|
1484
|
+
error: { message: 'EncodedList validation failed' },
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
// Validate statusPurpose is a valid value
|
|
1488
|
+
const statusPurpose = Array.isArray(credentialObj.credentialSubject.statusPurpose)
|
|
1489
|
+
? credentialObj.credentialSubject.statusPurpose[0]
|
|
1490
|
+
: credentialObj.credentialSubject.statusPurpose;
|
|
1491
|
+
if (!Object.values(BitstringStatusPurposeTypes).includes(statusPurpose)) {
|
|
1492
|
+
return {
|
|
1493
|
+
verified: false,
|
|
1494
|
+
error: { message: `Invalid statusPurpose: ${credentialObj.credentialSubject.statusPurpose}` },
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
// If ttl is provided, validate it's a positive number
|
|
1498
|
+
if (restCredentialSubject.ttl !== undefined &&
|
|
1499
|
+
(typeof restCredentialSubject.ttl !== 'number' || restCredentialSubject.ttl <= 0)) {
|
|
1500
|
+
return {
|
|
1501
|
+
verified: false,
|
|
1502
|
+
error: { message: 'Invalid ttl value' },
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
return { verified: verificationResult.verified, error: verificationResult.error };
|
|
1507
|
+
}
|
|
1508
|
+
async IssueCredentialWithBitstringStatusList(args, context) {
|
|
1509
|
+
// validate resource type
|
|
1510
|
+
const allowedTypes = ['refresh', 'message'];
|
|
1511
|
+
if (!allowedTypes.includes(args.statusOptions.statusPurpose)) {
|
|
1512
|
+
throw new Error(`[did-provider-cheqd]: statusPurpose while issuance must be one of ${allowedTypes.join(', ')}`);
|
|
1513
|
+
}
|
|
1514
|
+
// construct issuer
|
|
1515
|
+
const issuer = args.issuanceOptions.credential.issuer.id
|
|
1516
|
+
? args.issuanceOptions.credential.issuer.id
|
|
1517
|
+
: args.issuanceOptions.credential.issuer;
|
|
1518
|
+
// generate status list credential
|
|
1519
|
+
const statusListCredential = `${DefaultResolverUrl}${issuer}?resourceName=${args.statusOptions.statusListName}&resourceType=${BitstringStatusListResourceType}`;
|
|
1520
|
+
// get latest status list
|
|
1521
|
+
const statuslist = await Cheqd.fetchBitstringStatusList({
|
|
1522
|
+
credentialStatus: {
|
|
1523
|
+
id: statusListCredential,
|
|
1524
|
+
},
|
|
1525
|
+
});
|
|
1526
|
+
// Validate statusPurpose with statusList.statusPurpose
|
|
1527
|
+
const sl_statusPurpose = statuslist.bitstringStatusListCredential.credentialSubject.statusPurpose;
|
|
1528
|
+
if (!Object.values(sl_statusPurpose).includes(args.statusOptions.statusPurpose)) {
|
|
1529
|
+
throw new Error(`[did-provider-cheqd]: statusPurpose must be one of ${Object.values(sl_statusPurpose).join(', ')}`);
|
|
1530
|
+
}
|
|
1531
|
+
// If statusPurpose is 'message', statusMessages and statusSize MUST be present in Credential
|
|
1532
|
+
if (args.statusOptions.statusPurpose === BitstringStatusPurposeTypes.message) {
|
|
1533
|
+
if (!statuslist.metadata.statusSize || !statuslist.metadata.statusMessages) {
|
|
1534
|
+
throw new Error('[did-provider-cheqd]: statusMessages and statusSize must be present for statusPurpose="message"');
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
// generate index
|
|
1538
|
+
const statusListIndex = await generateRandomStatusListIndex(args.statusOptions, {
|
|
1539
|
+
statusSize: statuslist.metadata.statusSize,
|
|
1540
|
+
length: statuslist.metadata.length,
|
|
1541
|
+
});
|
|
1542
|
+
// construct credential status
|
|
1543
|
+
const credentialStatus = {
|
|
1544
|
+
id: `${statusListCredential}#${statusListIndex}`,
|
|
1545
|
+
type: 'BitstringStatusListEntry',
|
|
1546
|
+
statusPurpose: args.statusOptions.statusPurpose || BitstringStatusPurposeTypes.message,
|
|
1547
|
+
statusListIndex: `${statusListIndex}`,
|
|
1548
|
+
statusListCredential,
|
|
1549
|
+
statusSize: statuslist.metadata.statusSize || 1,
|
|
1550
|
+
statusMessage: statuslist.metadata.statusMessages || [],
|
|
1551
|
+
};
|
|
1552
|
+
// add credential status to credential
|
|
1553
|
+
args.issuanceOptions.credential.credentialStatus = credentialStatus;
|
|
1554
|
+
// add relevant context
|
|
1555
|
+
args.issuanceOptions.credential['@context'] = this.addBitstringStatusListContexts(args.issuanceOptions.credential);
|
|
1556
|
+
// TODO: update Veramo so that default "https://www.w3.org/2018/credentials/v1" is not added in context
|
|
1557
|
+
// create a credential
|
|
1558
|
+
const credential = await context.agent.createVerifiableCredential(args.issuanceOptions);
|
|
1559
|
+
return credential;
|
|
1560
|
+
}
|
|
941
1561
|
async IssueRevocableCredentialWithStatusList2021(args, context) {
|
|
942
1562
|
// generate index
|
|
943
1563
|
const statusListIndex = args.statusOptions.statusListIndex ||
|
|
@@ -958,24 +1578,7 @@ export class Cheqd {
|
|
|
958
1578
|
// add credential status to credential
|
|
959
1579
|
args.issuanceOptions.credential.credentialStatus = credentialStatus;
|
|
960
1580
|
// add relevant context
|
|
961
|
-
args.issuanceOptions.credential['@context'] = (
|
|
962
|
-
// if no context is provided, add default context
|
|
963
|
-
if (!args.issuanceOptions.credential['@context']) {
|
|
964
|
-
return [Cheqd.defaultContextV1, Cheqd.statusList2021Context];
|
|
965
|
-
}
|
|
966
|
-
// if context is provided as an array, add default context if it is not already present
|
|
967
|
-
if (Array.isArray(args.issuanceOptions.credential['@context'])) {
|
|
968
|
-
if (args.issuanceOptions.credential['@context'].length === 0) {
|
|
969
|
-
return [Cheqd.defaultContextV1, Cheqd.statusList2021Context];
|
|
970
|
-
}
|
|
971
|
-
if (!args.issuanceOptions.credential['@context'].includes(Cheqd.statusList2021Context)) {
|
|
972
|
-
return [...args.issuanceOptions.credential['@context'], Cheqd.statusList2021Context];
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
// if context is provided as a string, add default context if it is not already present
|
|
976
|
-
if (typeof args.issuanceOptions.credential['@context'] === 'string')
|
|
977
|
-
return [Cheqd.defaultContextV1, Cheqd.statusList2021Context];
|
|
978
|
-
})();
|
|
1581
|
+
args.issuanceOptions.credential['@context'] = this.addStatusList2021Contexts(args.issuanceOptions.credential);
|
|
979
1582
|
// create a credential
|
|
980
1583
|
const credential = await context.agent.createVerifiableCredential(args.issuanceOptions);
|
|
981
1584
|
return credential;
|
|
@@ -1000,28 +1603,34 @@ export class Cheqd {
|
|
|
1000
1603
|
// add credential status to credential
|
|
1001
1604
|
args.issuanceOptions.credential.credentialStatus = credentialStatus;
|
|
1002
1605
|
// add relevant context
|
|
1003
|
-
args.issuanceOptions.credential['@context'] = (
|
|
1004
|
-
// if no context is provided, add default context
|
|
1005
|
-
if (!args.issuanceOptions.credential['@context']) {
|
|
1006
|
-
return [Cheqd.defaultContextV1, Cheqd.statusList2021Context];
|
|
1007
|
-
}
|
|
1008
|
-
// if context is provided as an array, add default context if it is not already present
|
|
1009
|
-
if (Array.isArray(args.issuanceOptions.credential['@context'])) {
|
|
1010
|
-
if (args.issuanceOptions.credential['@context'].length === 0) {
|
|
1011
|
-
return [Cheqd.defaultContextV1, Cheqd.statusList2021Context];
|
|
1012
|
-
}
|
|
1013
|
-
if (!args.issuanceOptions.credential['@context'].includes(Cheqd.statusList2021Context)) {
|
|
1014
|
-
return [...args.issuanceOptions.credential['@context'], Cheqd.statusList2021Context];
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
// if context is provided as a string, add default context if it is not already present
|
|
1018
|
-
if (typeof args.issuanceOptions.credential['@context'] === 'string')
|
|
1019
|
-
return [Cheqd.defaultContextV1, Cheqd.statusList2021Context];
|
|
1020
|
-
})();
|
|
1606
|
+
args.issuanceOptions.credential['@context'] = this.addStatusList2021Contexts(args.issuanceOptions.credential);
|
|
1021
1607
|
// create a credential
|
|
1022
1608
|
const credential = await context.agent.createVerifiableCredential(args.issuanceOptions);
|
|
1023
1609
|
return credential;
|
|
1024
1610
|
}
|
|
1611
|
+
addStatusList2021Contexts(credential) {
|
|
1612
|
+
const contexts = credential['@context'] || [];
|
|
1613
|
+
// if context is provided as an array, add default context if it is not already present
|
|
1614
|
+
if (Array.isArray(contexts)) {
|
|
1615
|
+
const requiredContexts = [Cheqd.defaultContextV1, Cheqd.statusList2021Context];
|
|
1616
|
+
const missingContexts = requiredContexts.filter((ctx) => !contexts.includes(ctx));
|
|
1617
|
+
return [...contexts, ...missingContexts];
|
|
1618
|
+
}
|
|
1619
|
+
// if no context return default context
|
|
1620
|
+
return [Cheqd.defaultContextV1, Cheqd.statusList2021Context];
|
|
1621
|
+
}
|
|
1622
|
+
addBitstringStatusListContexts(credential) {
|
|
1623
|
+
const contexts = credential['@context'] || [];
|
|
1624
|
+
// if context is provided as an array, add default context if it is not already present
|
|
1625
|
+
if (Array.isArray(contexts)) {
|
|
1626
|
+
// TODO: Credential V1 context is used now, replace when V2 is implemented
|
|
1627
|
+
const requiredContexts = [Cheqd.defaultContextV1, Cheqd.DefaultBitstringContexts.statusList];
|
|
1628
|
+
const missingContexts = requiredContexts.filter((ctx) => !contexts.includes(ctx));
|
|
1629
|
+
return [...contexts, ...missingContexts];
|
|
1630
|
+
}
|
|
1631
|
+
// if no context return default context
|
|
1632
|
+
return [Cheqd.DefaultBitstringContexts.v2, Cheqd.DefaultBitstringContexts.statusList];
|
|
1633
|
+
}
|
|
1025
1634
|
async VerifyCredentialWithStatusList2021(args, context) {
|
|
1026
1635
|
// verify default policies
|
|
1027
1636
|
const verificationResult = await context.agent.verifyCredential({
|
|
@@ -1062,6 +1671,259 @@ export class Cheqd {
|
|
|
1062
1671
|
throw new Error(`[did-provider-cheqd]: verify credential: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
|
|
1063
1672
|
}
|
|
1064
1673
|
}
|
|
1674
|
+
async VerifyCredentialWithBitstringStatusList(args, context) {
|
|
1675
|
+
// verify default policies
|
|
1676
|
+
const verificationResult = await context.agent.verifyCredential({
|
|
1677
|
+
...args?.verificationArgs,
|
|
1678
|
+
credential: args.credential,
|
|
1679
|
+
policies: {
|
|
1680
|
+
...args?.verificationArgs?.policies,
|
|
1681
|
+
credentialStatus: false,
|
|
1682
|
+
},
|
|
1683
|
+
});
|
|
1684
|
+
// if jwt credential, decode it
|
|
1685
|
+
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
|
|
1686
|
+
// early return if verification failed
|
|
1687
|
+
if (!verificationResult.verified) {
|
|
1688
|
+
return {
|
|
1689
|
+
verified: false,
|
|
1690
|
+
status: 1,
|
|
1691
|
+
purpose: credential.credentialStatus?.statusPurpose,
|
|
1692
|
+
valid: false,
|
|
1693
|
+
error: verificationResult.error,
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
// define issuer
|
|
1697
|
+
const issuer = typeof credential.issuer === 'string' ? credential.issuer : credential.issuer.id;
|
|
1698
|
+
// define provider, if applicable
|
|
1699
|
+
this.didProvider = await Cheqd.getProviderFromDidUrl(issuer, this.supportedDidProviders);
|
|
1700
|
+
// define provider id, if applicable
|
|
1701
|
+
this.providerId = Cheqd.generateProviderId(issuer);
|
|
1702
|
+
// define dkg options, if provided
|
|
1703
|
+
args.dkgOptions ||= this.didProvider.dkgOptions;
|
|
1704
|
+
// Fetch and verify the Bitstring status list VC
|
|
1705
|
+
let publishedList;
|
|
1706
|
+
try {
|
|
1707
|
+
publishedList = await Cheqd.fetchBitstringStatusList({
|
|
1708
|
+
credentialStatus: {
|
|
1709
|
+
id: credential.credentialStatus?.statusListCredential,
|
|
1710
|
+
},
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
catch {
|
|
1714
|
+
throw new Error('[did-provider-cheqd]: STATUS_RETRIEVAL_ERROR');
|
|
1715
|
+
}
|
|
1716
|
+
// Validate proof on the Bitstring status list VC
|
|
1717
|
+
const checkCredential = await this.VerifyStatusListCredential({ credential: publishedList.bitstringStatusListCredential }, context);
|
|
1718
|
+
if (!checkCredential.verified) {
|
|
1719
|
+
throw new Error('[did-provider-cheqd]: STATUS_VERIFICATION_ERROR');
|
|
1720
|
+
}
|
|
1721
|
+
const validationResult = await Cheqd.validateBitstringStatus(credential, publishedList, {
|
|
1722
|
+
...args.options,
|
|
1723
|
+
topArgs: args,
|
|
1724
|
+
instantiateDkgClient: () => this.didProvider.instantiateDkgThresholdProtocolClient(),
|
|
1725
|
+
});
|
|
1726
|
+
// verify credential status
|
|
1727
|
+
switch (credential.credentialStatus?.statusPurpose) {
|
|
1728
|
+
case BitstringStatusPurposeTypes.revocation:
|
|
1729
|
+
return {
|
|
1730
|
+
...verificationResult,
|
|
1731
|
+
revoked: !validationResult.valid,
|
|
1732
|
+
...validationResult,
|
|
1733
|
+
};
|
|
1734
|
+
case BitstringStatusPurposeTypes.suspension:
|
|
1735
|
+
return {
|
|
1736
|
+
...verificationResult,
|
|
1737
|
+
suspended: !validationResult.valid,
|
|
1738
|
+
...validationResult,
|
|
1739
|
+
};
|
|
1740
|
+
case BitstringStatusPurposeTypes.message:
|
|
1741
|
+
case BitstringStatusPurposeTypes.refresh:
|
|
1742
|
+
return { ...verificationResult, ...validationResult };
|
|
1743
|
+
default:
|
|
1744
|
+
throw new Error(`[did-provider-cheqd]: verify credential: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
static async validateBitstringStatus(vcToValidate, publishedList, options = { fetchList: true }) {
|
|
1748
|
+
if (!vcToValidate?.credentialStatus) {
|
|
1749
|
+
throw new Error('[did-provider-cheqd]: CREDENTIAL_STATUS_MISSING');
|
|
1750
|
+
}
|
|
1751
|
+
// validate dkgOptions
|
|
1752
|
+
if (!options?.topArgs?.dkgOptions) {
|
|
1753
|
+
throw new Error('[did-provider-cheqd]: dkgOptions is required');
|
|
1754
|
+
}
|
|
1755
|
+
const statusEntry = vcToValidate.credentialStatus;
|
|
1756
|
+
const { statusPurpose, statusListIndex, statusSize = 1, // default to 1 if not given
|
|
1757
|
+
} = statusEntry;
|
|
1758
|
+
// Validate statusPurpose match in Bitstring statuslist VC
|
|
1759
|
+
const listSubject = publishedList?.bitstringStatusListCredential.credentialSubject;
|
|
1760
|
+
const purposesDeclared = Array.isArray(listSubject?.statusPurpose)
|
|
1761
|
+
? listSubject.statusPurpose
|
|
1762
|
+
: [listSubject?.statusPurpose];
|
|
1763
|
+
if (!purposesDeclared.includes(statusPurpose)) {
|
|
1764
|
+
throw new Error("[did-provider-cheqd]: STATUS_VERIFICATION_ERROR 'statusPurpose' does not match Bitstring Status List 'statusPurpose'");
|
|
1765
|
+
}
|
|
1766
|
+
// Extract and decompress the bitstring
|
|
1767
|
+
const encoded = listSubject?.encodedList;
|
|
1768
|
+
if (!encoded) {
|
|
1769
|
+
throw new Error('[did-provider-cheqd]: STATUS_LIST_MISSING_ENCODED');
|
|
1770
|
+
}
|
|
1771
|
+
// validate encoded list
|
|
1772
|
+
if (!isValidEncodedBitstring(encoded))
|
|
1773
|
+
throw new Error('[did-provider-cheqd]: Invalid encodedList format. Must be base64url encoded GZIP compressed bitstring');
|
|
1774
|
+
// fetch bitstring status list inscribed in credential
|
|
1775
|
+
const bitstringStatusList = await Cheqd.fetchAndDecryptBitstring(publishedList, options);
|
|
1776
|
+
// Expand bitstring and validate size
|
|
1777
|
+
const decompressedBuffer = await DBBitstring.decodeBits({ encoded: bitstringStatusList });
|
|
1778
|
+
const decompressedBitstring = new DBBitstring({ buffer: decompressedBuffer });
|
|
1779
|
+
const totalBits = decompressedBitstring.length;
|
|
1780
|
+
const numEntries = Math.floor(totalBits / statusSize);
|
|
1781
|
+
if (numEntries < Cheqd.DefaultBitstringLength) {
|
|
1782
|
+
throw new Error('[did-provider-cheqd]: STATUS_LIST_LENGTH_ERROR');
|
|
1783
|
+
}
|
|
1784
|
+
// Compute index
|
|
1785
|
+
const index = parseInt(statusListIndex, 10);
|
|
1786
|
+
const bitPosition = index * statusSize;
|
|
1787
|
+
if (bitPosition + statusSize > totalBits) {
|
|
1788
|
+
throw new Error('[did-provider-cheqd]: RANGE_ERROR');
|
|
1789
|
+
}
|
|
1790
|
+
const value = Cheqd.getBitValue(decompressedBitstring, bitPosition, statusSize);
|
|
1791
|
+
const result = {
|
|
1792
|
+
status: value,
|
|
1793
|
+
purpose: statusPurpose,
|
|
1794
|
+
valid: value === 0,
|
|
1795
|
+
};
|
|
1796
|
+
// Lookup statusMessage. if statusSize > 1
|
|
1797
|
+
const statusMessages = statusEntry.statusMessage;
|
|
1798
|
+
if (statusPurpose === BitstringStatusPurposeTypes.message && Array.isArray(statusMessages)) {
|
|
1799
|
+
const messageEntry = statusMessages.find((msg) => msg.status === `0x${value.toString(16)}`);
|
|
1800
|
+
if (messageEntry) {
|
|
1801
|
+
result.message = messageEntry.message;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
return result;
|
|
1805
|
+
}
|
|
1806
|
+
async VerifyPresentationWithBitstringStatusList(args, context) {
|
|
1807
|
+
// Verify default presentation policies first
|
|
1808
|
+
const verificationResult = await context.agent.verifyPresentation({
|
|
1809
|
+
...args?.verificationArgs,
|
|
1810
|
+
presentation: args.presentation,
|
|
1811
|
+
policies: {
|
|
1812
|
+
...args?.verificationArgs?.policies,
|
|
1813
|
+
audience: false,
|
|
1814
|
+
credentialStatus: false, // We'll handle status verification separately
|
|
1815
|
+
},
|
|
1816
|
+
});
|
|
1817
|
+
// Early return if basic presentation verification failed
|
|
1818
|
+
if (!verificationResult.verified) {
|
|
1819
|
+
return {
|
|
1820
|
+
verified: false,
|
|
1821
|
+
status: 1,
|
|
1822
|
+
purpose: 'unknown',
|
|
1823
|
+
valid: false,
|
|
1824
|
+
error: verificationResult.error,
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
// Early return if no verifiable credentials are provided
|
|
1828
|
+
if (!args.presentation.verifiableCredential || !Array.isArray(args.presentation.verifiableCredential)) {
|
|
1829
|
+
throw new Error('[did-provider-cheqd]: verify presentation: presentation.verifiableCredential is required and must be an array');
|
|
1830
|
+
}
|
|
1831
|
+
if (args.presentation.verifiableCredential.length === 0) {
|
|
1832
|
+
throw new Error('[did-provider-cheqd]: verify presentation: presentation must contain at least one verifiable credential');
|
|
1833
|
+
}
|
|
1834
|
+
// Verify each credential's status
|
|
1835
|
+
const credentialResults = [];
|
|
1836
|
+
let overallValid = true;
|
|
1837
|
+
let hasRevoked = false;
|
|
1838
|
+
let hasSuspended = false;
|
|
1839
|
+
for (let credential of args.presentation.verifiableCredential) {
|
|
1840
|
+
// If JWT credential, decode it
|
|
1841
|
+
if (typeof credential === 'string') {
|
|
1842
|
+
credential = await Cheqd.decodeCredentialJWT(credential);
|
|
1843
|
+
}
|
|
1844
|
+
// Skip credentials without status (they're considered valid)
|
|
1845
|
+
if (!credential.credentialStatus || credential.credentialStatus.type !== 'BitstringStatusListEntry') {
|
|
1846
|
+
credentialResults.push({
|
|
1847
|
+
verified: true,
|
|
1848
|
+
status: 0,
|
|
1849
|
+
purpose: 'none',
|
|
1850
|
+
valid: true,
|
|
1851
|
+
});
|
|
1852
|
+
continue;
|
|
1853
|
+
}
|
|
1854
|
+
try {
|
|
1855
|
+
// Define issuer for provider selection
|
|
1856
|
+
const issuer = typeof credential.issuer === 'string'
|
|
1857
|
+
? credential.issuer
|
|
1858
|
+
: credential.issuer.id;
|
|
1859
|
+
// Define provider, if applicable
|
|
1860
|
+
this.didProvider = await Cheqd.getProviderFromDidUrl(issuer, this.supportedDidProviders);
|
|
1861
|
+
this.providerId = Cheqd.generateProviderId(issuer);
|
|
1862
|
+
args.dkgOptions ||= this.didProvider.dkgOptions;
|
|
1863
|
+
// Verify credential with Bitstring status list
|
|
1864
|
+
const credentialVerificationResult = await this.VerifyCredentialWithBitstringStatusList({
|
|
1865
|
+
credential: credential,
|
|
1866
|
+
verificationArgs: {
|
|
1867
|
+
...args?.verificationArgs,
|
|
1868
|
+
policies: {
|
|
1869
|
+
...args?.verificationArgs?.policies,
|
|
1870
|
+
credentialStatus: false, // We handle this manually
|
|
1871
|
+
},
|
|
1872
|
+
credential: '',
|
|
1873
|
+
},
|
|
1874
|
+
fetchList: args?.fetchList ?? true,
|
|
1875
|
+
dkgOptions: args.dkgOptions,
|
|
1876
|
+
options: args.options,
|
|
1877
|
+
}, context);
|
|
1878
|
+
credentialResults.push(credentialVerificationResult);
|
|
1879
|
+
// Track overall status
|
|
1880
|
+
if (!credentialVerificationResult.verified || !credentialVerificationResult.valid) {
|
|
1881
|
+
overallValid = false;
|
|
1882
|
+
}
|
|
1883
|
+
switch (credentialVerificationResult.status) {
|
|
1884
|
+
case BitstringStatusValue.REVOKED:
|
|
1885
|
+
hasRevoked = true;
|
|
1886
|
+
overallValid = false;
|
|
1887
|
+
break;
|
|
1888
|
+
case BitstringStatusValue.SUSPENDED:
|
|
1889
|
+
hasSuspended = true;
|
|
1890
|
+
overallValid = false;
|
|
1891
|
+
break;
|
|
1892
|
+
default:
|
|
1893
|
+
hasSuspended = false;
|
|
1894
|
+
hasRevoked = false;
|
|
1895
|
+
overallValid = true;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
catch (error) {
|
|
1899
|
+
credentialResults.push({
|
|
1900
|
+
verified: false,
|
|
1901
|
+
status: 1,
|
|
1902
|
+
purpose: credential.credentialStatus?.statusPurpose || 'unknown',
|
|
1903
|
+
valid: false,
|
|
1904
|
+
error: error,
|
|
1905
|
+
});
|
|
1906
|
+
overallValid = false;
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
// Determine overall verification result
|
|
1910
|
+
const allCredentialsVerified = credentialResults.every((result) => result.verified);
|
|
1911
|
+
const overallVerified = verificationResult.verified && allCredentialsVerified;
|
|
1912
|
+
// Find the most significant status issue for reporting
|
|
1913
|
+
const firstFailedResult = credentialResults.find((result) => !result.verified || !result.valid);
|
|
1914
|
+
const resultStatus = firstFailedResult?.status ?? 0;
|
|
1915
|
+
const resultPurpose = firstFailedResult?.purpose ?? credentialResults[0]?.purpose ?? 'unknown';
|
|
1916
|
+
return {
|
|
1917
|
+
verified: overallVerified,
|
|
1918
|
+
status: resultStatus,
|
|
1919
|
+
purpose: resultPurpose,
|
|
1920
|
+
valid: overallValid,
|
|
1921
|
+
revoked: hasRevoked,
|
|
1922
|
+
suspended: hasSuspended,
|
|
1923
|
+
message: firstFailedResult?.message,
|
|
1924
|
+
error: firstFailedResult?.error,
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1065
1927
|
async VerifyPresentationWithStatusList2021(args, context) {
|
|
1066
1928
|
// verify default policies
|
|
1067
1929
|
const verificationResult = await context.agent.verifyPresentation({
|
|
@@ -1181,6 +2043,340 @@ export class Cheqd {
|
|
|
1181
2043
|
throw new Error(`[did-provider-cheqd]: check status: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
|
|
1182
2044
|
}
|
|
1183
2045
|
}
|
|
2046
|
+
async UpdateCredentialWithStatusList(args, context) {
|
|
2047
|
+
// Verify credential if provided and update options are not
|
|
2048
|
+
if (args?.credential && !args?.updateOptions) {
|
|
2049
|
+
const verificationResult = await context.agent.verifyCredential({
|
|
2050
|
+
...args?.verificationOptions,
|
|
2051
|
+
credential: args.credential,
|
|
2052
|
+
policies: {
|
|
2053
|
+
credentialStatus: false,
|
|
2054
|
+
},
|
|
2055
|
+
});
|
|
2056
|
+
if (!verificationResult.verified) {
|
|
2057
|
+
return {
|
|
2058
|
+
updated: false,
|
|
2059
|
+
statusValue: BitstringStatusValue.UNKNOWN,
|
|
2060
|
+
statusMessage: 'unknown',
|
|
2061
|
+
error: verificationResult.error,
|
|
2062
|
+
};
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
if (typeof args.newStatus !== 'number' || args.newStatus < 0 || args.newStatus > 3)
|
|
2066
|
+
throw new Error('[did-provider-cheqd]: updateOptions.newStatus must be 0-3 (valid/revoked/suspended/unknown)');
|
|
2067
|
+
// if update options are provided, give precedence
|
|
2068
|
+
if (args?.updateOptions) {
|
|
2069
|
+
// Validate update options
|
|
2070
|
+
if (!args.updateOptions.issuerDid)
|
|
2071
|
+
throw new Error('[did-provider-cheqd]: updateOptions.issuerDid is required');
|
|
2072
|
+
if (!args.updateOptions.statusListName)
|
|
2073
|
+
throw new Error('[did-provider-cheqd]: updateOptions.statusListName is required');
|
|
2074
|
+
if (typeof args.updateOptions.statusListIndex !== 'number')
|
|
2075
|
+
throw new Error('[did-provider-cheqd]: updateOptions.statusListIndex is required');
|
|
2076
|
+
// Construct status list credential URL
|
|
2077
|
+
const statusListCredential = `${DefaultResolverUrl}${args.updateOptions.issuerDid}?resourceName=${args.updateOptions.statusListName}&resourceType=${BitstringStatusListResourceType}`;
|
|
2078
|
+
// fetch latest status list
|
|
2079
|
+
const statusList = await Cheqd.fetchBitstringStatusList({
|
|
2080
|
+
credentialStatus: {
|
|
2081
|
+
id: statusListCredential,
|
|
2082
|
+
},
|
|
2083
|
+
});
|
|
2084
|
+
// For multi-purpose status lists, we need to determine the appropriate statusPurpose
|
|
2085
|
+
// based on the credential's current status entry or the new status being set
|
|
2086
|
+
const statusPurpose = this.getStatusPurposeForMultiPurposeList(args.newStatus);
|
|
2087
|
+
// Override the credential with new status information
|
|
2088
|
+
args.credential = {
|
|
2089
|
+
'@context': [],
|
|
2090
|
+
issuer: args.updateOptions.issuerDid,
|
|
2091
|
+
credentialSubject: {},
|
|
2092
|
+
credentialStatus: {
|
|
2093
|
+
id: `${statusListCredential}#${args.updateOptions.statusListIndex}`,
|
|
2094
|
+
type: 'BitstringStatusListEntry',
|
|
2095
|
+
statusPurpose: statusPurpose,
|
|
2096
|
+
statusListIndex: `${args.updateOptions.statusListIndex}`,
|
|
2097
|
+
statusListCredential,
|
|
2098
|
+
statusSize: statusList.metadata.statusSize || 1,
|
|
2099
|
+
statusMessage: statusList.metadata.statusMessages || [],
|
|
2100
|
+
},
|
|
2101
|
+
issuanceDate: '',
|
|
2102
|
+
proof: {},
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
// if jwt credential, decode it
|
|
2106
|
+
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
|
|
2107
|
+
// Validate that credential MUST have credentialStatus
|
|
2108
|
+
if (!credential?.credentialStatus) {
|
|
2109
|
+
throw new Error('[did-provider-cheqd]: update: credential must have credentialStatus');
|
|
2110
|
+
}
|
|
2111
|
+
// Validate that credentialStatus is BitstringStatusListEntry
|
|
2112
|
+
if (credential.credentialStatus.type !== 'BitstringStatusListEntry') {
|
|
2113
|
+
throw new Error('[did-provider-cheqd]: update: credential must have BitstringStatusListEntry credentialStatus');
|
|
2114
|
+
}
|
|
2115
|
+
// validate args in pairs - case: statusListFile and statusList
|
|
2116
|
+
if (args.options?.statusListFile && args.options?.statusList) {
|
|
2117
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile and statusList are mutually exclusive');
|
|
2118
|
+
}
|
|
2119
|
+
// validate args in pairs - case: statusListFile and fetchList
|
|
2120
|
+
if (args.options?.statusListFile && args.options?.fetchList) {
|
|
2121
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile and fetchList are mutually exclusive');
|
|
2122
|
+
}
|
|
2123
|
+
// validate args in pairs - case: statusList and fetchList
|
|
2124
|
+
if (args.options?.statusList && args.options?.fetchList) {
|
|
2125
|
+
throw new Error('[did-provider-cheqd]: revocation: statusList and fetchList are mutually exclusive');
|
|
2126
|
+
}
|
|
2127
|
+
// validate args in pairs - case: publish
|
|
2128
|
+
if (args.options?.publish && !args.fetchList && !(args.options?.statusListFile || args.options?.statusList)) {
|
|
2129
|
+
throw new Error('[did-provider-cheqd]: revocation: publish requires statusListFile or statusList, if fetchList is disabled');
|
|
2130
|
+
}
|
|
2131
|
+
// Define issuer and provider
|
|
2132
|
+
const issuer = typeof credential.issuer === 'string' ? credential.issuer : credential.issuer.id;
|
|
2133
|
+
this.didProvider = await Cheqd.getProviderFromDidUrl(issuer, this.supportedDidProviders);
|
|
2134
|
+
this.providerId = Cheqd.generateProviderId(issuer);
|
|
2135
|
+
args.dkgOptions ||= this.didProvider.dkgOptions;
|
|
2136
|
+
// Perform the status update
|
|
2137
|
+
return await Cheqd.updateBitstringCredentialStatus(credential, {
|
|
2138
|
+
...args.options,
|
|
2139
|
+
topArgs: args,
|
|
2140
|
+
publishOptions: {
|
|
2141
|
+
context,
|
|
2142
|
+
statusListEncoding: args?.options?.statusListEncoding,
|
|
2143
|
+
statusListValidUntil: args?.options?.statusListValidUntil,
|
|
2144
|
+
resourceId: args?.options?.resourceId,
|
|
2145
|
+
resourceVersion: args?.options?.resourceVersion,
|
|
2146
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
2147
|
+
signInputs: args?.options?.signInputs,
|
|
2148
|
+
fee: args?.options?.fee,
|
|
2149
|
+
},
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
2152
|
+
getStatusPurposeForMultiPurposeList(newStatus) {
|
|
2153
|
+
// TODO Add map with messages
|
|
2154
|
+
// Since the default status list supports multiple purposes, we can use any of them
|
|
2155
|
+
// For multi-purpose lists, typically 'message' is used as it's the most flexible
|
|
2156
|
+
return BitstringStatusPurposeTypes.message;
|
|
2157
|
+
}
|
|
2158
|
+
// Core status update logic for multi-purpose Bitstring Status Lists
|
|
2159
|
+
static async updateBitstringCredentialStatus(credential, options) {
|
|
2160
|
+
try {
|
|
2161
|
+
// Validate credential status
|
|
2162
|
+
if (!credential.credentialStatus) {
|
|
2163
|
+
throw new Error('[did-provider-cheqd]: update: Credential status is not present');
|
|
2164
|
+
}
|
|
2165
|
+
// Fetch published status list
|
|
2166
|
+
const publishedList = await Cheqd.fetchBitstringStatusList(credential);
|
|
2167
|
+
// Validate that this is a multi-purpose status list with 2-bit status
|
|
2168
|
+
if (publishedList.metadata.statusSize !== 2) {
|
|
2169
|
+
throw new Error('[did-provider-cheqd]: update: Status list must use 2-bit status size');
|
|
2170
|
+
}
|
|
2171
|
+
// Validate status messages are present for 2-bit status
|
|
2172
|
+
if (!publishedList.metadata.statusMessages || publishedList.metadata.statusMessages.length !== 4) {
|
|
2173
|
+
throw new Error('[did-provider-cheqd]: update: Status list must have 4 status messages for 2-bit status');
|
|
2174
|
+
}
|
|
2175
|
+
// Early return if encrypted and no decryption key provided
|
|
2176
|
+
if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey) {
|
|
2177
|
+
throw new Error('[did-provider-cheqd]: update: symmetricKey is required for encrypted status list');
|
|
2178
|
+
}
|
|
2179
|
+
// Calculate positions and values
|
|
2180
|
+
const statusIndex = parseInt(credential.credentialStatus.statusListIndex, 10);
|
|
2181
|
+
const statusSize = publishedList.metadata.statusSize; // Should be 2
|
|
2182
|
+
const newStatusValue = options?.topArgs.newStatus;
|
|
2183
|
+
// Fetch and decrypt the current bitstring
|
|
2184
|
+
const currentBitstring = await Cheqd.fetchAndDecryptBitstring(publishedList, options);
|
|
2185
|
+
// Parse the bitstring
|
|
2186
|
+
const decompressedBuffer = await DBBitstring.decodeBits({ encoded: currentBitstring });
|
|
2187
|
+
const bitstring = new DBBitstring({ buffer: decompressedBuffer });
|
|
2188
|
+
// Get current status value
|
|
2189
|
+
const bitPosition = statusIndex * statusSize;
|
|
2190
|
+
const currentStatusValue = Cheqd.getBitValue(bitstring, bitPosition, statusSize);
|
|
2191
|
+
// Check if update is needed
|
|
2192
|
+
if (currentStatusValue === newStatusValue) {
|
|
2193
|
+
const statusMessage = publishedList.metadata.statusMessages.find((msg) => parseInt(msg.status, 16) === currentStatusValue)
|
|
2194
|
+
?.message || 'unknown';
|
|
2195
|
+
return {
|
|
2196
|
+
updated: false,
|
|
2197
|
+
statusValue: currentStatusValue,
|
|
2198
|
+
statusMessage,
|
|
2199
|
+
error: { message: `Credential already has status value ${newStatusValue} (${statusMessage})` },
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
// Update the bitstring
|
|
2203
|
+
Cheqd.setBitValue(bitstring, bitPosition, newStatusValue, statusSize);
|
|
2204
|
+
// Compress the updated bitstring
|
|
2205
|
+
const compressedBitstring = await bitstring.compressBits();
|
|
2206
|
+
const encodedBitstring = toString(compressedBitstring, 'base64url');
|
|
2207
|
+
// Create updated status list credential
|
|
2208
|
+
const updatedStatusListCredential = {
|
|
2209
|
+
...publishedList.bitstringStatusListCredential,
|
|
2210
|
+
credentialSubject: {
|
|
2211
|
+
...publishedList.bitstringStatusListCredential.credentialSubject,
|
|
2212
|
+
encodedList: encodedBitstring,
|
|
2213
|
+
},
|
|
2214
|
+
};
|
|
2215
|
+
const updatedStatusList = {
|
|
2216
|
+
bitstringStatusListCredential: updatedStatusListCredential,
|
|
2217
|
+
metadata: publishedList.metadata,
|
|
2218
|
+
};
|
|
2219
|
+
// Write to file if requested
|
|
2220
|
+
if (options?.topArgs?.writeToFile) {
|
|
2221
|
+
await Cheqd.writeFile(compressedBitstring, options?.statusListFile);
|
|
2222
|
+
}
|
|
2223
|
+
// Publish if requested
|
|
2224
|
+
const published = options?.topArgs?.publish
|
|
2225
|
+
? await Cheqd.publishUpdatedBitstringStatusList(updatedStatusList, credential, options)
|
|
2226
|
+
: undefined;
|
|
2227
|
+
// Get status message for new value
|
|
2228
|
+
const newStatusMessage = publishedList.metadata.statusMessages.find((msg) => parseInt(msg.status, 16) === newStatusValue)
|
|
2229
|
+
?.message || 'unknown';
|
|
2230
|
+
const previousStatusMessage = publishedList.metadata.statusMessages.find((msg) => parseInt(msg.status, 16) === currentStatusValue)
|
|
2231
|
+
?.message || 'unknown';
|
|
2232
|
+
return {
|
|
2233
|
+
updated: true,
|
|
2234
|
+
statusValue: newStatusValue,
|
|
2235
|
+
previousStatusValue: currentStatusValue,
|
|
2236
|
+
statusMessage: newStatusMessage,
|
|
2237
|
+
published: options?.topArgs?.publish ? !!published : undefined,
|
|
2238
|
+
statusList: options?.topArgs?.returnUpdatedStatusList ? updatedStatusList : undefined,
|
|
2239
|
+
symmetricKey: options?.topArgs?.returnSymmetricKey && published?.symmetricKey
|
|
2240
|
+
? published.symmetricKey
|
|
2241
|
+
: undefined,
|
|
2242
|
+
resourceMetadata: options?.topArgs?.returnStatusListMetadata
|
|
2243
|
+
? await Cheqd.fetchStatusListMetadata(credential)
|
|
2244
|
+
: undefined,
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
catch (error) {
|
|
2248
|
+
console.error('[did-provider-cheqd]: update error:', error);
|
|
2249
|
+
return {
|
|
2250
|
+
updated: false,
|
|
2251
|
+
statusValue: BitstringStatusValue.UNKNOWN,
|
|
2252
|
+
statusMessage: 'unknown',
|
|
2253
|
+
error: error,
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
// Helper function to fetch and decrypt bitstring (same as before)
|
|
2258
|
+
static async fetchAndDecryptBitstring(publishedList, options) {
|
|
2259
|
+
const topArgs = options?.topArgs;
|
|
2260
|
+
const encoded = publishedList.bitstringStatusListCredential.credentialSubject.encodedList;
|
|
2261
|
+
if (topArgs?.fetchList) {
|
|
2262
|
+
// if not encrypted, return published bitstring (always base64url encoded)
|
|
2263
|
+
if (!publishedList.metadata.encrypted) {
|
|
2264
|
+
return encoded;
|
|
2265
|
+
}
|
|
2266
|
+
// otherwise, Decrypt using threshold encryption
|
|
2267
|
+
const { thresholdEncryptionCiphertext } = decodeWithMetadata(publishedList.bitstringStatusListCredential.credentialSubject.encodedList, publishedList.metadata.symmetricLength);
|
|
2268
|
+
const lit = (await options.instantiateDkgClient());
|
|
2269
|
+
// construct access control conditions
|
|
2270
|
+
const unifiedAccessControlConditions = await Promise.all(publishedList.metadata.paymentConditions.map(async (condition) => {
|
|
2271
|
+
switch (condition.type) {
|
|
2272
|
+
case AccessControlConditionTypes.timelockPayment:
|
|
2273
|
+
return await LitProtocol.generateCosmosAccessControlConditionInverseTimelock({
|
|
2274
|
+
key: '$.tx_responses.*.timestamp',
|
|
2275
|
+
comparator: '<=',
|
|
2276
|
+
value: `${condition.intervalInSeconds}`,
|
|
2277
|
+
}, condition.feePaymentAmount, condition.feePaymentAddress, condition?.blockHeight, options?.topArgs?.dkgOptions?.chain);
|
|
2278
|
+
default:
|
|
2279
|
+
throw new Error(`[did-provider-cheqd]: unsupported access control condition type ${condition.type}`);
|
|
2280
|
+
}
|
|
2281
|
+
}));
|
|
2282
|
+
return await lit.decrypt(toString(thresholdEncryptionCiphertext, 'base64url'), publishedList.metadata.statusListHash, unifiedAccessControlConditions);
|
|
2283
|
+
}
|
|
2284
|
+
else {
|
|
2285
|
+
// Use provided symmetric key or file
|
|
2286
|
+
if (options?.statusListFile) {
|
|
2287
|
+
// if not encrypted, return bitstring
|
|
2288
|
+
if (!publishedList.metadata.encrypted) {
|
|
2289
|
+
// construct encoded status list
|
|
2290
|
+
const bitstring = new DBBitstring({
|
|
2291
|
+
buffer: await Cheqd.getFile(options.statusListFile),
|
|
2292
|
+
});
|
|
2293
|
+
const compressed = await bitstring.compressBits();
|
|
2294
|
+
// validate against published list
|
|
2295
|
+
if (encoded !== toString(compressed, 'base64url'))
|
|
2296
|
+
throw new Error('[did-provider-cheqd]: statusListFile does not match published Bitstring status list');
|
|
2297
|
+
// return compressed
|
|
2298
|
+
return compressed;
|
|
2299
|
+
}
|
|
2300
|
+
// otherwise, decrypt and return bitstring
|
|
2301
|
+
const scopedRawBlob = await toBlob(await Cheqd.getFile(options.statusListFile));
|
|
2302
|
+
const decrypted = toString(await LitProtocol.decryptDirect(scopedRawBlob, await safeDeserialise(options?.topArgs?.symmetricKey, fromString, ['hex'], 'Invalid symmetric key')), 'base64url');
|
|
2303
|
+
// validate against published list
|
|
2304
|
+
if (decrypted !== encoded)
|
|
2305
|
+
throw new Error('[did-provider-cheqd]: statusListFile does not match published Bitstring status list');
|
|
2306
|
+
// return decrypted
|
|
2307
|
+
return decrypted;
|
|
2308
|
+
}
|
|
2309
|
+
if (!options?.statusListInlineBitstring) {
|
|
2310
|
+
throw new Error('[did-provider-cheqd]: statusListInlineBitstring required if statusListFile not provided');
|
|
2311
|
+
}
|
|
2312
|
+
// validate against published list
|
|
2313
|
+
if (options?.statusListInlineBitstring !== encoded)
|
|
2314
|
+
throw new Error('[did-provider-cheqd]: statusListInlineBitstring does not match published bitstring status list');
|
|
2315
|
+
// otherwise, read from inline bitstring
|
|
2316
|
+
return options.statusListInlineBitstring;
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
// Helper function to publish updated status list
|
|
2320
|
+
static async publishUpdatedBitstringStatusList(updatedStatusList, credential, options) {
|
|
2321
|
+
const topArgs = options?.topArgs;
|
|
2322
|
+
// Fetch current metadata
|
|
2323
|
+
const statusListMetadata = await Cheqd.fetchStatusListMetadata(credential);
|
|
2324
|
+
// Handle encrypted publishing if needed
|
|
2325
|
+
if (topArgs.publishEncrypted && updatedStatusList.metadata.encrypted) {
|
|
2326
|
+
// Re-encrypt with new content
|
|
2327
|
+
const bitstring = updatedStatusList.bitstringStatusListCredential.credentialSubject.encodedList;
|
|
2328
|
+
// Encrypt bitstring - case: symmetric
|
|
2329
|
+
const { encryptedString: symmetricEncryptionCiphertext, symmetricKey } = await LitProtocol.encryptDirect(fromString(bitstring, 'base64url'));
|
|
2330
|
+
// Get DKG client and encrypt threshold
|
|
2331
|
+
const lit = await options.publishOptions.instantiateDkgClient();
|
|
2332
|
+
const unifiedAccessControlConditions = await Promise.all(updatedStatusList.metadata.paymentConditions.map(async (condition) => {
|
|
2333
|
+
switch (condition.type) {
|
|
2334
|
+
case AccessControlConditionTypes.timelockPayment:
|
|
2335
|
+
return await LitProtocol.generateCosmosAccessControlConditionInverseTimelock({
|
|
2336
|
+
key: '$.tx_responses.*.timestamp',
|
|
2337
|
+
comparator: '<=',
|
|
2338
|
+
value: `${condition.intervalInSeconds}`,
|
|
2339
|
+
}, condition.feePaymentAmount, condition.feePaymentAddress, condition?.blockHeight, topArgs?.dkgOptions?.chain);
|
|
2340
|
+
default:
|
|
2341
|
+
throw new Error(`[did-provider-cheqd]: unsupported access control condition type ${condition.type}`);
|
|
2342
|
+
}
|
|
2343
|
+
}));
|
|
2344
|
+
const { encryptedString: thresholdEncryptionCiphertext, stringHash } = await lit.encrypt(fromString(bitstring, 'base64url'), unifiedAccessControlConditions);
|
|
2345
|
+
// Update encoded list with encrypted content
|
|
2346
|
+
const { encodedList, symmetricLength } = await encodeWithMetadata(symmetricEncryptionCiphertext, thresholdEncryptionCiphertext);
|
|
2347
|
+
updatedStatusList.bitstringStatusListCredential.credentialSubject.encodedList = encodedList;
|
|
2348
|
+
updatedStatusList.metadata.statusListHash = stringHash;
|
|
2349
|
+
updatedStatusList.metadata.symmetricLength = symmetricLength;
|
|
2350
|
+
// Publish the encrypted status list
|
|
2351
|
+
await Cheqd.publishBitstringStatusList(fromString(JSON.stringify(updatedStatusList), 'utf-8'), statusListMetadata, options?.publishOptions);
|
|
2352
|
+
return { symmetricKey: toString(symmetricKey, 'hex') };
|
|
2353
|
+
}
|
|
2354
|
+
else {
|
|
2355
|
+
// Publish unencrypted
|
|
2356
|
+
await Cheqd.publishBitstringStatusList(fromString(JSON.stringify(updatedStatusList), 'utf-8'), statusListMetadata, options?.publishOptions);
|
|
2357
|
+
return {};
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
// Helper function to publish bitstring status list
|
|
2361
|
+
static async publishBitstringStatusList(statusListRaw, statusListMetadata, options) {
|
|
2362
|
+
// Construct payload
|
|
2363
|
+
const payload = {
|
|
2364
|
+
id: options?.resourceId || v4(),
|
|
2365
|
+
collectionId: statusListMetadata.resourceCollectionId,
|
|
2366
|
+
name: statusListMetadata.resourceName,
|
|
2367
|
+
version: options?.resourceVersion || new Date().toISOString(),
|
|
2368
|
+
alsoKnownAs: options?.resourceAlsoKnownAs || [],
|
|
2369
|
+
resourceType: BitstringStatusListResourceType,
|
|
2370
|
+
data: statusListRaw,
|
|
2371
|
+
};
|
|
2372
|
+
return await options.context.agent[BroadcastStatusListMethodName]({
|
|
2373
|
+
kms: (await options.context.agent.keyManagerGetKeyManagementSystems())[0],
|
|
2374
|
+
payload,
|
|
2375
|
+
network: statusListMetadata.resourceURI.split(':')[2],
|
|
2376
|
+
signInputs: options?.signInputs,
|
|
2377
|
+
fee: options?.fee,
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
1184
2380
|
async RevokeCredentialWithStatusList2021(args, context) {
|
|
1185
2381
|
// verify credential, if provided and revocation options are not
|
|
1186
2382
|
if (args?.credential && !args?.revocationOptions) {
|
|
@@ -1273,6 +2469,254 @@ export class Cheqd {
|
|
|
1273
2469
|
},
|
|
1274
2470
|
});
|
|
1275
2471
|
}
|
|
2472
|
+
async BulkUpdateCredentialsWithStatusList(args, context) {
|
|
2473
|
+
// validate new status value
|
|
2474
|
+
if (typeof args.newStatus !== 'number' || args.newStatus < 0 || args.newStatus > 3) {
|
|
2475
|
+
throw new Error('[did-provider-cheqd]: bulk update: newStatus must be 0-3 (valid/revoked/suspended/unknown)');
|
|
2476
|
+
}
|
|
2477
|
+
// verify credentials, if provided and update options are not
|
|
2478
|
+
if (args?.credentials && !args?.updateOptions) {
|
|
2479
|
+
const verificationResult = await Promise.all(args.credentials.map(async (credential) => {
|
|
2480
|
+
return await context.agent.verifyCredential({
|
|
2481
|
+
...args?.verificationOptions,
|
|
2482
|
+
credential,
|
|
2483
|
+
policies: {
|
|
2484
|
+
credentialStatus: false,
|
|
2485
|
+
},
|
|
2486
|
+
});
|
|
2487
|
+
}));
|
|
2488
|
+
// early return if verification failed for any credential
|
|
2489
|
+
if (verificationResult.some((result) => !result.verified)) {
|
|
2490
|
+
return {
|
|
2491
|
+
updated: Array(args.credentials.length).fill(false),
|
|
2492
|
+
statusValues: Array(args.credentials.length).fill(BitstringStatusValue.UNKNOWN),
|
|
2493
|
+
statusMessages: Array(args.credentials.length).fill('verification failed'),
|
|
2494
|
+
error: verificationResult.find((result) => !result.verified).error || {
|
|
2495
|
+
message: 'verification: could not verify credential',
|
|
2496
|
+
},
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
// if update options are provided, give precedence
|
|
2501
|
+
if (args?.updateOptions) {
|
|
2502
|
+
// validate update options
|
|
2503
|
+
if (!args.updateOptions.issuerDid) {
|
|
2504
|
+
throw new Error('[did-provider-cheqd]: bulk update: updateOptions.issuerDid is required');
|
|
2505
|
+
}
|
|
2506
|
+
if (!args.updateOptions.statusListName) {
|
|
2507
|
+
throw new Error('[did-provider-cheqd]: bulk update: updateOptions.statusListName is required');
|
|
2508
|
+
}
|
|
2509
|
+
if (!args.updateOptions.statusListIndices || !Array.isArray(args.updateOptions.statusListIndices)) {
|
|
2510
|
+
throw new Error('[did-provider-cheqd]: bulk update: updateOptions.statusListIndices is required and must be an array');
|
|
2511
|
+
}
|
|
2512
|
+
// Construct status list credential URL
|
|
2513
|
+
const statusListCredential = `${DefaultResolverUrl}${args.updateOptions.issuerDid}?resourceName=${args.updateOptions.statusListName}&resourceType=${BitstringStatusListResourceType}`;
|
|
2514
|
+
// fetch latest status list to get metadata
|
|
2515
|
+
const statusList = await Cheqd.fetchBitstringStatusList({
|
|
2516
|
+
credentialStatus: {
|
|
2517
|
+
id: statusListCredential,
|
|
2518
|
+
},
|
|
2519
|
+
});
|
|
2520
|
+
// For multi-purpose status lists, determine the appropriate statusPurpose
|
|
2521
|
+
const statusPurpose = this.getStatusPurposeForMultiPurposeList(args.newStatus);
|
|
2522
|
+
// construct credentials with proper status entries
|
|
2523
|
+
args.credentials = args.updateOptions.statusListIndices.map((index, i) => ({
|
|
2524
|
+
'@context': [],
|
|
2525
|
+
issuer: args.updateOptions.issuerDid,
|
|
2526
|
+
credentialSubject: {},
|
|
2527
|
+
credentialStatus: {
|
|
2528
|
+
id: `${statusListCredential}#${index}`,
|
|
2529
|
+
type: 'BitstringStatusListEntry',
|
|
2530
|
+
statusPurpose: statusPurpose,
|
|
2531
|
+
statusListIndex: `${index}`,
|
|
2532
|
+
statusListCredential,
|
|
2533
|
+
statusSize: statusList.metadata.statusSize || 1,
|
|
2534
|
+
statusMessage: statusList.metadata.statusMessages || [],
|
|
2535
|
+
},
|
|
2536
|
+
issuanceDate: '',
|
|
2537
|
+
proof: {},
|
|
2538
|
+
}));
|
|
2539
|
+
}
|
|
2540
|
+
// if jwt credentials, decode them
|
|
2541
|
+
const credentials = await Promise.all(args.credentials.map(async (credential) => typeof credential === 'string' ? await Cheqd.decodeCredentialJWT(credential) : credential));
|
|
2542
|
+
// Validate that ALL credentials MUST have credentialStatus
|
|
2543
|
+
const credentialsWithoutStatus = credentials.filter((credential, index) => !credential.credentialStatus);
|
|
2544
|
+
if (credentialsWithoutStatus.length > 0) {
|
|
2545
|
+
throw new Error(`[did-provider-cheqd]: bulk update: ${credentialsWithoutStatus.length} credential(s) missing credentialStatus`);
|
|
2546
|
+
}
|
|
2547
|
+
// Validate that all credentials have BitstringStatusListEntry type
|
|
2548
|
+
const invalidStatusTypes = credentials.filter((credential) => credential.credentialStatus?.type !== 'BitstringStatusListEntry');
|
|
2549
|
+
if (invalidStatusTypes.length > 0) {
|
|
2550
|
+
throw new Error(`[did-provider-cheqd]: bulk update: ${invalidStatusTypes.length} credential(s) must have BitstringStatusListEntry credentialStatus`);
|
|
2551
|
+
}
|
|
2552
|
+
// validate credentials - case: consistent issuer
|
|
2553
|
+
if (credentials
|
|
2554
|
+
.map((credential) => {
|
|
2555
|
+
return credential.issuer.id
|
|
2556
|
+
? credential.issuer.id
|
|
2557
|
+
: credential.issuer;
|
|
2558
|
+
})
|
|
2559
|
+
.filter((value, _, self) => value && value !== self[0]).length > 0) {
|
|
2560
|
+
throw new Error('[did-provider-cheqd]: bulk update: Credentials must be issued by the same issuer');
|
|
2561
|
+
}
|
|
2562
|
+
// validate credentials - case: status list index uniqueness
|
|
2563
|
+
if (credentials
|
|
2564
|
+
.map((credential) => credential.credentialStatus.statusListIndex)
|
|
2565
|
+
.filter((value, index, self) => self.indexOf(value) !== index).length > 0) {
|
|
2566
|
+
throw new Error('[did-provider-cheqd]: bulk update: Credentials must have unique status list index');
|
|
2567
|
+
}
|
|
2568
|
+
// validate credentials - case: status list credential consistency
|
|
2569
|
+
const statusListCredentialUrl = credentials[0].credentialStatus?.statusListCredential;
|
|
2570
|
+
if (!statusListCredentialUrl) {
|
|
2571
|
+
throw new Error('[did-provider-cheqd]: bulk update: Invalid status list credential URL');
|
|
2572
|
+
}
|
|
2573
|
+
if (!credentials.every((credential) => credential.credentialStatus?.statusListCredential === statusListCredentialUrl)) {
|
|
2574
|
+
throw new Error('[did-provider-cheqd]: bulk update: Credentials must belong to the same status list');
|
|
2575
|
+
}
|
|
2576
|
+
// validate credentials - case: status list type
|
|
2577
|
+
if (!credentials.every((credential) => credential.credentialStatus?.type === 'BitstringStatusListEntry')) {
|
|
2578
|
+
throw new Error('[did-provider-cheqd]: bulk update: Invalid status list type');
|
|
2579
|
+
}
|
|
2580
|
+
// validate args in pairs - case: statusListFile and statusList
|
|
2581
|
+
if (args.options?.statusListFile && args.options?.statusList) {
|
|
2582
|
+
throw new Error('[did-provider-cheqd]: bulk update: statusListFile and statusList are mutually exclusive');
|
|
2583
|
+
}
|
|
2584
|
+
// validate args in pairs - case: statusListFile and fetchList
|
|
2585
|
+
if (args.options?.statusListFile && args.options?.fetchList) {
|
|
2586
|
+
throw new Error('[did-provider-cheqd]: bulk update: statusListFile and fetchList are mutually exclusive');
|
|
2587
|
+
}
|
|
2588
|
+
// validate args in pairs - case: statusList and fetchList
|
|
2589
|
+
if (args.options?.statusList && args.options?.fetchList) {
|
|
2590
|
+
throw new Error('[did-provider-cheqd]: bulk update: statusList and fetchList are mutually exclusive');
|
|
2591
|
+
}
|
|
2592
|
+
// validate args in pairs - case: publish
|
|
2593
|
+
if (args.options?.publish && !args.fetchList && !(args.options?.statusListFile || args.options?.statusList)) {
|
|
2594
|
+
throw new Error('[did-provider-cheqd]: bulk update: publish requires statusListFile or statusList, if fetchList is disabled');
|
|
2595
|
+
}
|
|
2596
|
+
// Define issuer and provider
|
|
2597
|
+
const issuer = typeof credentials[0].issuer === 'string'
|
|
2598
|
+
? credentials[0].issuer
|
|
2599
|
+
: credentials[0].issuer.id;
|
|
2600
|
+
this.didProvider = await Cheqd.getProviderFromDidUrl(issuer, this.supportedDidProviders);
|
|
2601
|
+
this.providerId = Cheqd.generateProviderId(issuer);
|
|
2602
|
+
args.dkgOptions ||= this.didProvider.dkgOptions;
|
|
2603
|
+
try {
|
|
2604
|
+
// Fetch published status list
|
|
2605
|
+
const publishedList = await Cheqd.fetchBitstringStatusList(credentials[0]);
|
|
2606
|
+
// Error if encrypted and no decryption key provided
|
|
2607
|
+
if (publishedList.metadata.encrypted && !args?.symmetricKey) {
|
|
2608
|
+
throw new Error('[did-provider-cheqd]: bulk update: symmetricKey is required for encrypted status list');
|
|
2609
|
+
}
|
|
2610
|
+
// Fetch and decrypt the current bitstring
|
|
2611
|
+
const currentBitstring = await Cheqd.fetchAndDecryptBitstring(publishedList, {
|
|
2612
|
+
...args.options,
|
|
2613
|
+
topArgs: args,
|
|
2614
|
+
instantiateDkgClient: () => this.didProvider.instantiateDkgThresholdProtocolClient(),
|
|
2615
|
+
});
|
|
2616
|
+
// Parse the bitstring
|
|
2617
|
+
const decompressedBuffer = await DBBitstring.decodeBits({ encoded: currentBitstring });
|
|
2618
|
+
const bitstring = new DBBitstring({ buffer: decompressedBuffer });
|
|
2619
|
+
const statusSize = publishedList.metadata.statusSize || Cheqd.DefaultBitstringStatusSize;
|
|
2620
|
+
const newStatusValue = args.newStatus;
|
|
2621
|
+
// Process all credentials
|
|
2622
|
+
const results = [];
|
|
2623
|
+
let anyUpdated = false;
|
|
2624
|
+
for (const credential of credentials) {
|
|
2625
|
+
const statusIndex = parseInt(credential.credentialStatus.statusListIndex, 10);
|
|
2626
|
+
const bitPosition = statusIndex * statusSize;
|
|
2627
|
+
// Get current status value
|
|
2628
|
+
const currentStatusValue = Cheqd.getBitValue(bitstring, bitPosition, statusSize);
|
|
2629
|
+
// Check if update is needed
|
|
2630
|
+
if (currentStatusValue === newStatusValue) {
|
|
2631
|
+
const statusMessage = publishedList.metadata.statusMessages?.find((msg) => parseInt(msg.status, 16) === currentStatusValue)?.message || 'unknown';
|
|
2632
|
+
results.push({
|
|
2633
|
+
updated: false,
|
|
2634
|
+
statusValue: currentStatusValue,
|
|
2635
|
+
statusMessage,
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
else {
|
|
2639
|
+
// Update the bitstring
|
|
2640
|
+
Cheqd.setBitValue(bitstring, bitPosition, newStatusValue, statusSize);
|
|
2641
|
+
const newStatusMessage = publishedList.metadata.statusMessages?.find((msg) => parseInt(msg.status, 16) === newStatusValue)?.message || 'unknown';
|
|
2642
|
+
results.push({
|
|
2643
|
+
updated: true,
|
|
2644
|
+
statusValue: newStatusValue,
|
|
2645
|
+
previousStatusValue: currentStatusValue,
|
|
2646
|
+
statusMessage: newStatusMessage,
|
|
2647
|
+
});
|
|
2648
|
+
anyUpdated = true;
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
// If no updates needed, return early
|
|
2652
|
+
if (!anyUpdated) {
|
|
2653
|
+
return {
|
|
2654
|
+
updated: results.map((r) => r.updated),
|
|
2655
|
+
statusValues: results.map((r) => r.statusValue),
|
|
2656
|
+
previousStatusValues: results.map((r) => r.previousStatusValue).filter((v) => v !== undefined),
|
|
2657
|
+
statusMessages: results.map((r) => r.statusMessage).filter((m) => m !== undefined),
|
|
2658
|
+
};
|
|
2659
|
+
}
|
|
2660
|
+
// Compress the updated bitstring
|
|
2661
|
+
const compressedBitstring = await bitstring.compressBits();
|
|
2662
|
+
const encodedBitstring = toString(compressedBitstring, 'base64url');
|
|
2663
|
+
// Create updated status list credential
|
|
2664
|
+
const updatedStatusListCredential = {
|
|
2665
|
+
...publishedList.bitstringStatusListCredential,
|
|
2666
|
+
credentialSubject: {
|
|
2667
|
+
...publishedList.bitstringStatusListCredential.credentialSubject,
|
|
2668
|
+
encodedList: encodedBitstring,
|
|
2669
|
+
},
|
|
2670
|
+
};
|
|
2671
|
+
const updatedStatusList = {
|
|
2672
|
+
bitstringStatusListCredential: updatedStatusListCredential,
|
|
2673
|
+
metadata: publishedList.metadata,
|
|
2674
|
+
};
|
|
2675
|
+
// Write to file if requested
|
|
2676
|
+
if (args?.writeToFile) {
|
|
2677
|
+
await Cheqd.writeFile(compressedBitstring, args.options?.statusListFile);
|
|
2678
|
+
}
|
|
2679
|
+
// Publish if requested
|
|
2680
|
+
const published = args?.publish
|
|
2681
|
+
? await Cheqd.publishUpdatedBitstringStatusList(updatedStatusList, credentials[0], {
|
|
2682
|
+
...args.options,
|
|
2683
|
+
topArgs: args,
|
|
2684
|
+
publishOptions: {
|
|
2685
|
+
context,
|
|
2686
|
+
statusListEncoding: args?.options?.statusListEncoding,
|
|
2687
|
+
statusListValidUntil: args?.options?.statusListValidUntil,
|
|
2688
|
+
resourceId: args?.options?.resourceId,
|
|
2689
|
+
resourceVersion: args?.options?.resourceVersion,
|
|
2690
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
2691
|
+
signInputs: args?.options?.signInputs,
|
|
2692
|
+
fee: args?.options?.fee,
|
|
2693
|
+
instantiateDkgClient: () => this.didProvider.instantiateDkgThresholdProtocolClient(),
|
|
2694
|
+
},
|
|
2695
|
+
})
|
|
2696
|
+
: undefined;
|
|
2697
|
+
return {
|
|
2698
|
+
updated: results.map((r) => r.updated),
|
|
2699
|
+
statusValues: results.map((r) => r.statusValue),
|
|
2700
|
+
previousStatusValues: results.map((r) => r.previousStatusValue).filter((v) => v !== undefined),
|
|
2701
|
+
statusMessages: results.map((r) => r.statusMessage).filter((m) => m !== undefined),
|
|
2702
|
+
published: args?.publish ? !!published : undefined,
|
|
2703
|
+
statusList: args?.returnUpdatedStatusList ? updatedStatusList : undefined,
|
|
2704
|
+
symmetricKey: args?.returnSymmetricKey && published?.symmetricKey ? published.symmetricKey : undefined,
|
|
2705
|
+
resourceMetadata: args?.returnStatusListMetadata
|
|
2706
|
+
? await Cheqd.fetchStatusListMetadata(credentials[0])
|
|
2707
|
+
: undefined,
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
catch (error) {
|
|
2711
|
+
console.error('[did-provider-cheqd]: bulk update error:', error);
|
|
2712
|
+
return {
|
|
2713
|
+
updated: Array(credentials.length).fill(false),
|
|
2714
|
+
statusValues: Array(credentials.length).fill(BitstringStatusValue.UNKNOWN),
|
|
2715
|
+
statusMessages: Array(credentials.length).fill('update failed'),
|
|
2716
|
+
error: error,
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
1276
2720
|
async RevokeBulkCredentialsWithStatusList2021(args, context) {
|
|
1277
2721
|
// verify credential, if provided and revocation options are not
|
|
1278
2722
|
if (args?.credentials && !args?.revocationOptions) {
|
|
@@ -2098,13 +3542,13 @@ export class Cheqd {
|
|
|
2098
3542
|
const published = topArgs?.publish
|
|
2099
3543
|
? await (async function () {
|
|
2100
3544
|
// fetch status list 2021 metadata
|
|
2101
|
-
const statusListMetadata = await Cheqd.
|
|
3545
|
+
const statusListMetadata = await Cheqd.fetchStatusListMetadata(credential);
|
|
2102
3546
|
// publish status list 2021 as new version
|
|
2103
3547
|
const scoped = topArgs.publishEncrypted
|
|
2104
3548
|
? await (async function () {
|
|
2105
3549
|
// validate encoding, if provided
|
|
2106
3550
|
if (options?.publishOptions?.statusListEncoding &&
|
|
2107
|
-
!Object.values(
|
|
3551
|
+
!Object.values(DefaultStatusListEncodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
2108
3552
|
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
|
|
2109
3553
|
}
|
|
2110
3554
|
// validate validUntil, if provided
|
|
@@ -2233,7 +3677,7 @@ export class Cheqd {
|
|
|
2233
3677
|
: await (async function () {
|
|
2234
3678
|
// validate encoding, if provided
|
|
2235
3679
|
if (options?.publishOptions?.statusListEncoding &&
|
|
2236
|
-
!Object.values(
|
|
3680
|
+
!Object.values(DefaultStatusListEncodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
2237
3681
|
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
|
|
2238
3682
|
}
|
|
2239
3683
|
// validate validUntil, if provided
|
|
@@ -2293,7 +3737,7 @@ export class Cheqd {
|
|
|
2293
3737
|
? toString(published?.[1]?.symmetricKey, 'hex')
|
|
2294
3738
|
: undefined,
|
|
2295
3739
|
resourceMetadata: topArgs?.returnStatusListMetadata
|
|
2296
|
-
? await Cheqd.
|
|
3740
|
+
? await Cheqd.fetchStatusListMetadata(credential)
|
|
2297
3741
|
: undefined,
|
|
2298
3742
|
};
|
|
2299
3743
|
}
|
|
@@ -2437,13 +3881,13 @@ export class Cheqd {
|
|
|
2437
3881
|
const published = topArgs?.publish
|
|
2438
3882
|
? await (async function () {
|
|
2439
3883
|
// fetch status list 2021 metadata
|
|
2440
|
-
const statusListMetadata = await Cheqd.
|
|
3884
|
+
const statusListMetadata = await Cheqd.fetchStatusListMetadata(credentials[0]);
|
|
2441
3885
|
// publish status list 2021 as new version
|
|
2442
3886
|
const scoped = topArgs.publishEncrypted
|
|
2443
3887
|
? await (async function () {
|
|
2444
3888
|
// validate encoding, if provided
|
|
2445
3889
|
if (options?.publishOptions?.statusListEncoding &&
|
|
2446
|
-
!Object.values(
|
|
3890
|
+
!Object.values(DefaultStatusListEncodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
2447
3891
|
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
|
|
2448
3892
|
}
|
|
2449
3893
|
// validate validUntil, if provided
|
|
@@ -2572,7 +4016,7 @@ export class Cheqd {
|
|
|
2572
4016
|
: await (async function () {
|
|
2573
4017
|
// validate encoding, if provided
|
|
2574
4018
|
if (options?.publishOptions?.statusListEncoding &&
|
|
2575
|
-
!Object.values(
|
|
4019
|
+
!Object.values(DefaultStatusListEncodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
2576
4020
|
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
|
|
2577
4021
|
}
|
|
2578
4022
|
// validate validUntil, if provided
|
|
@@ -2632,7 +4076,7 @@ export class Cheqd {
|
|
|
2632
4076
|
? toString(published?.[1]?.symmetricKey, 'hex')
|
|
2633
4077
|
: undefined,
|
|
2634
4078
|
resourceMetadata: topArgs?.returnStatusListMetadata
|
|
2635
|
-
? await Cheqd.
|
|
4079
|
+
? await Cheqd.fetchStatusListMetadata(credentials[0])
|
|
2636
4080
|
: undefined,
|
|
2637
4081
|
};
|
|
2638
4082
|
}
|
|
@@ -2728,13 +4172,13 @@ export class Cheqd {
|
|
|
2728
4172
|
const published = topArgs?.publish
|
|
2729
4173
|
? await (async function () {
|
|
2730
4174
|
// fetch status list 2021 metadata
|
|
2731
|
-
const statusListMetadata = await Cheqd.
|
|
4175
|
+
const statusListMetadata = await Cheqd.fetchStatusListMetadata(credential);
|
|
2732
4176
|
// publish status list 2021 as new version
|
|
2733
4177
|
const scoped = topArgs.publishEncrypted
|
|
2734
4178
|
? await (async function () {
|
|
2735
4179
|
// validate encoding, if provided
|
|
2736
4180
|
if (options?.publishOptions?.statusListEncoding &&
|
|
2737
|
-
!Object.values(
|
|
4181
|
+
!Object.values(DefaultStatusListEncodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
2738
4182
|
throw new Error('[did-provider-cheqd]: suspension: Invalid status list encoding');
|
|
2739
4183
|
}
|
|
2740
4184
|
// validate validUntil, if provided
|
|
@@ -2863,7 +4307,7 @@ export class Cheqd {
|
|
|
2863
4307
|
: await (async function () {
|
|
2864
4308
|
// validate encoding, if provided
|
|
2865
4309
|
if (options?.publishOptions?.statusListEncoding &&
|
|
2866
|
-
!Object.values(
|
|
4310
|
+
!Object.values(DefaultStatusListEncodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
2867
4311
|
throw new Error('[did-provider-cheqd]: suspension: Invalid status list encoding');
|
|
2868
4312
|
}
|
|
2869
4313
|
// validate validUntil, if provided
|
|
@@ -2923,7 +4367,7 @@ export class Cheqd {
|
|
|
2923
4367
|
? toString(published?.[1]?.symmetricKey, 'hex')
|
|
2924
4368
|
: undefined,
|
|
2925
4369
|
resourceMetadata: topArgs?.returnStatusListMetadata
|
|
2926
|
-
? await Cheqd.
|
|
4370
|
+
? await Cheqd.fetchStatusListMetadata(credential)
|
|
2927
4371
|
: undefined,
|
|
2928
4372
|
};
|
|
2929
4373
|
}
|
|
@@ -3067,13 +4511,13 @@ export class Cheqd {
|
|
|
3067
4511
|
const published = topArgs?.publish
|
|
3068
4512
|
? await (async function () {
|
|
3069
4513
|
// fetch status list 2021 metadata
|
|
3070
|
-
const statusListMetadata = await Cheqd.
|
|
4514
|
+
const statusListMetadata = await Cheqd.fetchStatusListMetadata(credentials[0]);
|
|
3071
4515
|
// publish status list 2021 as new version
|
|
3072
4516
|
const scoped = topArgs.publishEncrypted
|
|
3073
4517
|
? await (async function () {
|
|
3074
4518
|
// validate encoding, if provided
|
|
3075
4519
|
if (options?.publishOptions?.statusListEncoding &&
|
|
3076
|
-
!Object.values(
|
|
4520
|
+
!Object.values(DefaultStatusListEncodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
3077
4521
|
throw new Error('[did-provider-cheqd]: suspension: Invalid status list encoding');
|
|
3078
4522
|
}
|
|
3079
4523
|
// validate validUntil, if provided
|
|
@@ -3202,7 +4646,7 @@ export class Cheqd {
|
|
|
3202
4646
|
: await (async function () {
|
|
3203
4647
|
// validate encoding, if provided
|
|
3204
4648
|
if (options?.publishOptions?.statusListEncoding &&
|
|
3205
|
-
!Object.values(
|
|
4649
|
+
!Object.values(DefaultStatusListEncodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
3206
4650
|
throw new Error('[did-provider-cheqd]: suspension: Invalid status list encoding');
|
|
3207
4651
|
}
|
|
3208
4652
|
// validate validUntil, if provided
|
|
@@ -3262,7 +4706,7 @@ export class Cheqd {
|
|
|
3262
4706
|
? toString(published?.[1]?.symmetricKey, 'hex')
|
|
3263
4707
|
: undefined,
|
|
3264
4708
|
resourceMetadata: topArgs?.returnStatusListMetadata
|
|
3265
|
-
? await Cheqd.
|
|
4709
|
+
? await Cheqd.fetchStatusListMetadata(credentials[0])
|
|
3266
4710
|
: undefined,
|
|
3267
4711
|
};
|
|
3268
4712
|
}
|
|
@@ -3358,13 +4802,13 @@ export class Cheqd {
|
|
|
3358
4802
|
const published = topArgs?.publish
|
|
3359
4803
|
? await (async function () {
|
|
3360
4804
|
// fetch status list 2021 metadata
|
|
3361
|
-
const statusListMetadata = await Cheqd.
|
|
4805
|
+
const statusListMetadata = await Cheqd.fetchStatusListMetadata(credential);
|
|
3362
4806
|
// publish status list 2021 as new version
|
|
3363
4807
|
const scoped = topArgs.publishEncrypted
|
|
3364
4808
|
? await (async function () {
|
|
3365
4809
|
// validate encoding, if provided
|
|
3366
4810
|
if (options?.publishOptions?.statusListEncoding &&
|
|
3367
|
-
!Object.values(
|
|
4811
|
+
!Object.values(DefaultStatusListEncodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
3368
4812
|
throw new Error('[did-provider-cheqd]: unsuspension: Invalid status list encoding');
|
|
3369
4813
|
}
|
|
3370
4814
|
// validate validUntil, if provided
|
|
@@ -3493,7 +4937,7 @@ export class Cheqd {
|
|
|
3493
4937
|
: await (async function () {
|
|
3494
4938
|
// validate encoding, if provided
|
|
3495
4939
|
if (options?.publishOptions?.statusListEncoding &&
|
|
3496
|
-
!Object.values(
|
|
4940
|
+
!Object.values(DefaultStatusListEncodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
3497
4941
|
throw new Error('[did-provider-cheqd]: unsuspension: Invalid status list encoding');
|
|
3498
4942
|
}
|
|
3499
4943
|
// validate validUntil, if provided
|
|
@@ -3553,7 +4997,7 @@ export class Cheqd {
|
|
|
3553
4997
|
? toString(published?.[1]?.symmetricKey, 'hex')
|
|
3554
4998
|
: undefined,
|
|
3555
4999
|
resourceMetadata: topArgs?.returnStatusListMetadata
|
|
3556
|
-
? await Cheqd.
|
|
5000
|
+
? await Cheqd.fetchStatusListMetadata(credential)
|
|
3557
5001
|
: undefined,
|
|
3558
5002
|
};
|
|
3559
5003
|
}
|
|
@@ -3697,13 +5141,13 @@ export class Cheqd {
|
|
|
3697
5141
|
const published = topArgs?.publish
|
|
3698
5142
|
? await (async function () {
|
|
3699
5143
|
// fetch status list 2021 metadata
|
|
3700
|
-
const statusListMetadata = await Cheqd.
|
|
5144
|
+
const statusListMetadata = await Cheqd.fetchStatusListMetadata(credentials[0]);
|
|
3701
5145
|
// publish status list 2021 as new version
|
|
3702
5146
|
const scoped = topArgs.publishEncrypted
|
|
3703
5147
|
? await (async function () {
|
|
3704
5148
|
// validate encoding, if provided
|
|
3705
5149
|
if (options?.publishOptions?.statusListEncoding &&
|
|
3706
|
-
!Object.values(
|
|
5150
|
+
!Object.values(DefaultStatusListEncodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
3707
5151
|
throw new Error('[did-provider-cheqd]: unsuspension: Invalid status list encoding');
|
|
3708
5152
|
}
|
|
3709
5153
|
// validate validUntil, if provided
|
|
@@ -3832,7 +5276,7 @@ export class Cheqd {
|
|
|
3832
5276
|
: await (async function () {
|
|
3833
5277
|
// validate encoding, if provided
|
|
3834
5278
|
if (options?.publishOptions?.statusListEncoding &&
|
|
3835
|
-
!Object.values(
|
|
5279
|
+
!Object.values(DefaultStatusListEncodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
3836
5280
|
throw new Error('[did-provider-cheqd]: unsuspension: Invalid status list encoding');
|
|
3837
5281
|
}
|
|
3838
5282
|
// validate validUntil, if provided
|
|
@@ -3892,7 +5336,7 @@ export class Cheqd {
|
|
|
3892
5336
|
? toString(published?.[1]?.symmetricKey, 'hex')
|
|
3893
5337
|
: undefined,
|
|
3894
5338
|
resourceMetadata: topArgs?.returnStatusListMetadata
|
|
3895
|
-
? await Cheqd.
|
|
5339
|
+
? await Cheqd.fetchStatusListMetadata(credentials[0])
|
|
3896
5340
|
: undefined,
|
|
3897
5341
|
};
|
|
3898
5342
|
}
|
|
@@ -4295,7 +5739,7 @@ export class Cheqd {
|
|
|
4295
5739
|
// otherwise, return content
|
|
4296
5740
|
return content;
|
|
4297
5741
|
}
|
|
4298
|
-
static async
|
|
5742
|
+
static async fetchStatusListMetadata(credential) {
|
|
4299
5743
|
// get base url
|
|
4300
5744
|
const baseUrl = new URL(credential.credentialStatus.id.split('#')[0]);
|
|
4301
5745
|
// get resource name
|
|
@@ -4326,6 +5770,26 @@ export class Cheqd {
|
|
|
4326
5770
|
return (resourceVersioning.find((resource) => !resource.nextVersionId) ||
|
|
4327
5771
|
resourceVersioning.sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime())[0]);
|
|
4328
5772
|
}
|
|
5773
|
+
/**
|
|
5774
|
+
* Fetch the JSON metadata from a bitstring status list credential URL
|
|
5775
|
+
*/
|
|
5776
|
+
static async fetchBitstringStatusList(credential) {
|
|
5777
|
+
// get base url
|
|
5778
|
+
const baseUrl = new URL(credential.credentialStatus.id.split('#')[0]);
|
|
5779
|
+
// fetch collection metadata
|
|
5780
|
+
const response = await fetch(baseUrl, {
|
|
5781
|
+
method: 'GET',
|
|
5782
|
+
headers: {
|
|
5783
|
+
Accept: 'application/json',
|
|
5784
|
+
'Content-Type': 'application/json',
|
|
5785
|
+
},
|
|
5786
|
+
});
|
|
5787
|
+
if (!response.ok) {
|
|
5788
|
+
throw new Error(`[did-provider-cheqd]: Bitstring Status List retrieval error ${response.status}: ${response.statusText}`);
|
|
5789
|
+
}
|
|
5790
|
+
const data = await response.json();
|
|
5791
|
+
return data;
|
|
5792
|
+
}
|
|
4329
5793
|
static async getProviderFromDidUrl(didUrl, providers, message) {
|
|
4330
5794
|
const provider = providers.find((provider) => didUrl.includes(`${DidPrefix}:${CheqdDidMethod}:${provider.network}:`));
|
|
4331
5795
|
if (!provider) {
|
|
@@ -4392,5 +5856,21 @@ export class Cheqd {
|
|
|
4392
5856
|
issuer: decodedCredential.payload.iss,
|
|
4393
5857
|
};
|
|
4394
5858
|
}
|
|
5859
|
+
static getBitValue(bitstring, bitIndex, statusSize = 1) {
|
|
5860
|
+
let value = 0;
|
|
5861
|
+
for (let i = 0; i < statusSize; i++) {
|
|
5862
|
+
const bit = bitstring.get(bitIndex + i);
|
|
5863
|
+
value |= bit << i;
|
|
5864
|
+
}
|
|
5865
|
+
return value;
|
|
5866
|
+
}
|
|
5867
|
+
// Helper function to set bit values in a bitstring (2-bit values)
|
|
5868
|
+
static setBitValue(bitstring, bitIndex, value, statusSize = 2) {
|
|
5869
|
+
for (let i = 0; i < statusSize; i++) {
|
|
5870
|
+
const bit = (value >> i) & 1;
|
|
5871
|
+
bitstring.set(bitIndex + i, bit === 1);
|
|
5872
|
+
}
|
|
5873
|
+
}
|
|
4395
5874
|
}
|
|
5875
|
+
export { BitstringStatusListResourceType, DefaultStatusListEncodings };
|
|
4396
5876
|
//# sourceMappingURL=ICheqd.js.map
|