@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.
Files changed (30) hide show
  1. package/build/esm/agent/ICheqd.d.ts +305 -89
  2. package/build/esm/agent/ICheqd.d.ts.map +1 -1
  3. package/build/esm/agent/ICheqd.js +1567 -87
  4. package/build/esm/agent/ICheqd.js.map +1 -1
  5. package/build/esm/did-manager/cheqd-did-provider.d.ts +16 -5
  6. package/build/esm/did-manager/cheqd-did-provider.d.ts.map +1 -1
  7. package/build/esm/did-manager/cheqd-did-provider.js +11 -3
  8. package/build/esm/did-manager/cheqd-did-provider.js.map +1 -1
  9. package/build/esm/dkg-threshold/lit-protocol/v6.d.ts.map +1 -1
  10. package/build/esm/dkg-threshold/lit-protocol/v6.js +63 -33
  11. package/build/esm/dkg-threshold/lit-protocol/v6.js.map +1 -1
  12. package/build/esm/utils/helpers.d.ts +28 -7
  13. package/build/esm/utils/helpers.d.ts.map +1 -1
  14. package/build/esm/utils/helpers.js +120 -29
  15. package/build/esm/utils/helpers.js.map +1 -1
  16. package/build/tsconfig.esm.tsbuildinfo +1 -1
  17. package/build/tsconfig.types.tsbuildinfo +1 -1
  18. package/build/types/agent/ICheqd.d.ts +305 -89
  19. package/build/types/agent/ICheqd.d.ts.map +1 -1
  20. package/build/types/did-manager/cheqd-did-provider.d.ts +16 -5
  21. package/build/types/did-manager/cheqd-did-provider.d.ts.map +1 -1
  22. package/build/types/dkg-threshold/lit-protocol/v6.d.ts.map +1 -1
  23. package/build/types/utils/helpers.d.ts +28 -7
  24. package/build/types/utils/helpers.d.ts.map +1 -1
  25. package/package.json +6 -4
  26. package/src/agent/ICheqd.ts +2440 -379
  27. package/src/did-manager/cheqd-did-provider.ts +25 -10
  28. package/src/dkg-threshold/lit-protocol/v6.ts +65 -34
  29. package/src/utils/helpers.ts +172 -34
  30. 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, DefaultStatusList2021Encodings, DefaultStatusList2021ResourceTypes, DefaultStatusList2021StatusPurposeTypes, } from '../did-manager/cheqd-did-provider.js';
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 cheqdBroadcastStatusList2021Args object as any for extensibility',
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 cheqdIssueCredentialWithStatusList2021Args object as any for extensibility',
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 cheqdIssueCredentialWithStatusList2021Args object as any for extensibility',
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 cheqdVerifyCredentialWithStatusList2021Args object as any for extensibility',
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 cheqdVerifyPresentationWithStatusList2021Args object as any for extensibility',
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 cheqdCheckCredentialStatusWithStatusList2021Args object as any for extensibility',
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 cheqdRevokeCredentialWithStatusList2021Args object as any for extensibility',
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 cheqdRevokeBulkCredentialsWithStatusList2021Args object as any for extensibility',
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 cheqdSuspendCredentialWithStatusList2021Args object as any for extensibility',
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 cheqdSuspendBulkCredentialsWithStatusList2021Args object as any for extensibility',
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: 'cheqdUnsuspendCredentialWithStatusList2021Args object as any for extensibility',
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 cheqdUnsuspendBulkCredentialsWithStatusList2021Args object as any for extensibility',
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(DefaultStatusList2021Encodings).includes(args.statusListEncoding)) {
618
- throw new Error(`[did-provider-cheqd]: statusListEncoding must be one of ${Object.values(DefaultStatusList2021Encodings).join(', ')}`);
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 || DefaultStatusList2021Encodings.base64url,
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 || DefaultStatusList2021Encodings.base64url));
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 || DefaultStatusList2021Encodings.base64url), unifiedAccessControlConditions);
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 || DefaultStatusList2021Encodings.base64url,
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 || DefaultStatusList2021Encodings.base64url,
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 || DefaultStatusList2021Encodings.base64url,
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 || DefaultStatusList2021Encodings.base64url,
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.fetchStatusList2021Metadata({
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
- if (!Object.values(DefaultStatusList2021ResourceTypes).includes(args?.payload?.resourceType)) {
831
- throw new Error(`[did-provider-cheqd]: resourceType must be one of ${Object.values(DefaultStatusList2021ResourceTypes).join(', ')}`);
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'] = (function () {
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'] = (function () {
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.fetchStatusList2021Metadata(credential);
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(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
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(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
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.fetchStatusList2021Metadata(credential)
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.fetchStatusList2021Metadata(credentials[0]);
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(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
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(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
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.fetchStatusList2021Metadata(credentials[0])
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.fetchStatusList2021Metadata(credential);
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(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
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(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
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.fetchStatusList2021Metadata(credential)
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.fetchStatusList2021Metadata(credentials[0]);
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(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
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(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
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.fetchStatusList2021Metadata(credentials[0])
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.fetchStatusList2021Metadata(credential);
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(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
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(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
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.fetchStatusList2021Metadata(credential)
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.fetchStatusList2021Metadata(credentials[0]);
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(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
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(DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
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.fetchStatusList2021Metadata(credentials[0])
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 fetchStatusList2021Metadata(credential) {
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