@cheqd/did-provider-cheqd 3.3.2 → 3.4.0-develop.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.Cheqd = exports.AccessControlConditionReturnValueComparators = exports.AccessControlConditionTypes = void 0;
6
+ exports.Cheqd = exports.RemoteListPattern = exports.AccessControlConditionReturnValueComparators = exports.AccessControlConditionTypes = void 0;
7
7
  /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars, @typescript-eslint/no-non-null-assertion */
8
8
  // any is used for extensibility
9
9
  // unused vars are kept by convention
@@ -20,14 +20,45 @@ const lit_protocol_js_1 = require("../dkg-threshold/lit-protocol.js");
20
20
  const helpers_js_1 = require("../utils/helpers.js");
21
21
  const cheqd_did_resolver_js_1 = require("../did-manager/cheqd-did-resolver.js");
22
22
  const debug = (0, debug_1.default)('veramo:did-provider-cheqd');
23
- exports.AccessControlConditionTypes = { memoNonce: 'memoNonce', balance: 'balance' };
23
+ // TODO: revisit while implementing timelock payment
24
+ //? CheckSequenceVerify (CSV) is a script opcode used to implement relative timelocks.
25
+ //? It is a conditional opcode that consumes a minimum of two stack items and compares their relative locktime values.
26
+ //? If the relative locktime of the first item is equal to or greater than the relative locktime of the second item, then the script evaluates to true, otherwise it evaluates to false.
27
+ //? The relative locktime of an item is the number of seconds since the item was confirmed.
28
+ //? The following apply:
29
+ //? - Relative locktimes are measured from the time a transaction is confirmed, not from the time a transaction is created.
30
+ //? - Relative locktimes are not affected by time zone or daylight saving time.
31
+ //? - Relative locktimes are not affected by the time a transaction is broadcast.
32
+ //? - Relative locktimes are not affected by the time a transaction is mined.
33
+ //? - Relative locktimes are not affected by the time a transaction is included in a block.
34
+ //? - Relative locktimes are not affected by the time a block is mined.
35
+ // Implementation as per:
36
+ //
37
+ // - Generate a random nonce client-side.
38
+ // - Utilise the nonce as the memo.
39
+ // - Observed by the recipient client-side.
40
+ // - Gives turn to initiate decryption of the status list + status verification.
41
+ // - Define time interval OR block height interval client-side.
42
+ // - Utilised as inverse timelock.
43
+ // - Observed by dkg nodes.
44
+ // - TODO: Add chained AND conditions to return value tests, dkg node-side.
45
+ // - e.g. if timelockPayment (a <= now <= b) AND memoNonce strict equality (uuid, IMPORTANT: condition is updateable).
46
+ // - Optionally, recycled per encryption of the status list.
47
+ // - Will be published as part of the status list, if recycled + interval is altered.
48
+ // Issuer initiated immutable steps:
49
+ // - Initilise ACC with timelockPayment, nonce. <-- Nonce is timestamped as per time of encryption.
50
+ // - On a per transaction basis:
51
+ // - Update ACC with revelant memoNonce.
52
+ // - Rotate symmetric key.
53
+ exports.AccessControlConditionTypes = { timelockPayment: 'timelockPayment', memoNonce: 'memoNonce', balance: 'balance' };
24
54
  exports.AccessControlConditionReturnValueComparators = { lessThan: '<', greaterThan: '>', equalTo: '=', lessThanOrEqualTo: '<=', greaterThanOrEqualTo: '>=' };
55
+ exports.RemoteListPattern = /^(https:\/\/)?[a-z0-9_-]+(\.[a-z0-9_-]+)*\.[a-z]{2,}\/1\.0\/identifiers\/did:cheqd:[a-z]+:[a-zA-Z0-9-]+\?((resourceName=[^&]*)&(resourceType=[^&]*)|((resourceType=[^&]*)&(resourceName=[^&]*)))$/;
25
56
  const CreateIdentifierMethodName = 'cheqdCreateIdentifier';
26
57
  const UpdateIdentifierMethodName = 'cheqdUpdateIdentifier';
27
58
  const DeactivateIdentifierMethodName = 'cheqdDeactivateIdentifier';
28
59
  const CreateResourceMethodName = 'cheqdCreateLinkedResource';
29
60
  const CreateStatusList2021MethodName = 'cheqdCreateStatusList2021';
30
- const CreateEncryptedStatusList2021MethodName = 'cheqdCreateEncryptedStatusList2021';
61
+ const BroadcastStatusList2021MethodName = 'cheqdBroadcastStatusList2021';
31
62
  const GenerateDidDocMethodName = 'cheqdGenerateDidDoc';
32
63
  const GenerateDidDocWithLinkedResourceMethodName = 'cheqdGenerateDidDocWithLinkedResource';
33
64
  const GenerateKeyPairMethodName = 'cheqdGenerateIdentityKeys';
@@ -142,17 +173,17 @@ class Cheqd {
142
173
  ]
143
174
  },
144
175
  "returnType": {
145
- "type": "boolean"
176
+ "type": "object"
146
177
  }
147
178
  },
148
- "cheqdCreateEncryptedStatusList2021": {
149
- "description": "Create a new Encrypted Status List 2021",
179
+ "cheqdBroadcastStatusList2021": {
180
+ "description": "Broadcast a Status List 2021 to cheqd ledger",
150
181
  "arguments": {
151
182
  "type": "object",
152
183
  "properties": {
153
184
  "args": {
154
185
  "type": "object",
155
- "description": "A cheqdCreateEncryptedStatusList2021Args object as any for extensibility"
186
+ "description": "A cheqdBroadcastStatusList2021Args object as any for extensibility"
156
187
  }
157
188
  },
158
189
  "required": [
@@ -518,7 +549,7 @@ class Cheqd {
518
549
  [DeactivateIdentifierMethodName]: this.DeactivateIdentifier.bind(this),
519
550
  [CreateResourceMethodName]: this.CreateResource.bind(this),
520
551
  [CreateStatusList2021MethodName]: this.CreateStatusList2021.bind(this),
521
- [CreateEncryptedStatusList2021MethodName]: this.CreateEncryptedStatusList2021.bind(this),
552
+ [BroadcastStatusList2021MethodName]: this.BroadcastStatusList2021.bind(this),
522
553
  [GenerateDidDocMethodName]: this.GenerateDidDoc.bind(this),
523
554
  [GenerateDidDocWithLinkedResourceMethodName]: this.GenerateDidDocWithLinkedResource.bind(this),
524
555
  [GenerateKeyPairMethodName]: this.GenerateIdentityKeys.bind(this),
@@ -636,33 +667,125 @@ class Cheqd {
636
667
  if (typeof args.kms !== 'string') {
637
668
  throw new Error('[did-provider-cheqd]: kms is required');
638
669
  }
639
- if (typeof args.payload !== 'object') {
640
- throw new Error('[did-provider-cheqd]: payload object is required');
670
+ if (typeof args.issuerDid !== 'string' || !args.issuerDid) {
671
+ throw new Error('[did-provider-cheqd]: issuerDid is required');
641
672
  }
642
- if (typeof args.network !== 'string') {
643
- throw new Error('[did-provider-cheqd]: network is required');
673
+ if (typeof args.statusListName !== 'string' || !args.statusListName) {
674
+ throw new Error('[did-provider-cheqd]: statusListName is required');
644
675
  }
645
- if (args?.file) {
646
- args.payload.data = await Cheqd.getFile(args.file);
676
+ if (typeof args.statusPurpose !== 'string' || !args.statusPurpose) {
677
+ throw new Error('[did-provider-cheqd]: statusPurpose is required');
647
678
  }
648
- if (typeof args?.payload?.data === 'string') {
649
- args.payload.data = (0, uint8arrays_1.fromString)(args.payload.data, 'base64');
679
+ if (typeof args.encrypted === 'undefined') {
680
+ throw new Error('[did-provider-cheqd]: encrypted is required');
650
681
  }
651
- // TODO: validate data as per bitstring
652
- // set default resource type in runtime
653
- args.payload.resourceType = 'StatusList2021';
654
- this.providerId = Cheqd.generateProviderId(args.network);
655
- this.didProvider = await Cheqd.loadProvider({ id: this.providerId }, this.supportedDidProviders);
656
- return await this.didProvider.createResource({
657
- options: {
658
- kms: args.kms,
659
- payload: args.payload,
660
- signInputs: args.signInputs,
661
- fee: args?.fee
682
+ // validate statusPurpose
683
+ if (!Object.values(cheqd_did_provider_js_1.DefaultStatusList2021StatusPurposeTypes).includes(args.statusPurpose)) {
684
+ throw new Error(`[did-provider-cheqd]: statusPurpose must be one of ${Object.values(cheqd_did_provider_js_1.DefaultStatusList2021StatusPurposeTypes).join(', ')}`);
685
+ }
686
+ // validate statusListLength
687
+ if (args?.statusListLength) {
688
+ if (typeof args.statusListLength !== 'number') {
689
+ throw new Error('[did-provider-cheqd]: statusListLength must be number');
662
690
  }
663
- }, context);
691
+ if (args.statusListLength < Cheqd.defaultStatusList2021Length) {
692
+ throw new Error(`[did-provider-cheqd]: statusListLength must be greater than or equal to ${Cheqd.defaultStatusList2021Length} number of entries`);
693
+ }
694
+ }
695
+ // validate statusListEncoding
696
+ if (args?.statusListEncoding) {
697
+ if (typeof args.statusListEncoding !== 'string') {
698
+ throw new Error('[did-provider-cheqd]: statusListEncoding must be string');
699
+ }
700
+ if (!Object.values(cheqd_did_provider_js_1.DefaultStatusList2021Encodings).includes(args.statusListEncoding)) {
701
+ throw new Error(`[did-provider-cheqd]: statusListEncoding must be one of ${Object.values(cheqd_did_provider_js_1.DefaultStatusList2021Encodings).join(', ')}`);
702
+ }
703
+ }
704
+ // validate validUntil
705
+ if (args?.validUntil) {
706
+ if (typeof args.validUntil !== 'string') {
707
+ throw new Error('[did-provider-cheqd]: validUntil must be string');
708
+ }
709
+ if (new Date() <= new Date(args.validUntil)) {
710
+ throw new Error('[did-provider-cheqd]: validUntil must be greater than current date');
711
+ }
712
+ }
713
+ // validate args in pairs - case: encrypted
714
+ if (args.encrypted) {
715
+ // validate paymentConditions
716
+ if (!args?.paymentConditions || !args?.paymentConditions?.length || !Array.isArray(args?.paymentConditions) || args?.paymentConditions.length === 0) {
717
+ throw new Error('[did-provider-cheqd]: paymentConditions is required');
718
+ }
719
+ if (!args?.paymentConditions?.every((condition) => condition.feePaymentAddress && condition.feePaymentAmount)) {
720
+ throw new Error('[did-provider-cheqd]: paymentConditions must contain feePaymentAddress and feeAmount');
721
+ }
722
+ if (!args?.paymentConditions?.every((condition) => typeof condition.feePaymentAddress === 'string' && typeof condition.feePaymentAmount === 'string')) {
723
+ throw new Error('[did-provider-cheqd]: feePaymentAddress and feePaymentAmount must be string');
724
+ }
725
+ if (!args?.paymentConditions?.every((condition) => condition.type === exports.AccessControlConditionTypes.timelockPayment)) {
726
+ throw new Error('[did-provider-cheqd]: paymentConditions must be of type timelockPayment');
727
+ }
728
+ }
729
+ // get network
730
+ const network = args.issuerDid.split(':')[2];
731
+ // generate bitstring
732
+ const bitstring = await context.agent[GenerateStatusList2021MethodName]({ length: args?.statusListLength || Cheqd.defaultStatusList2021Length, bitstringEncoding: args?.statusListEncoding || cheqd_did_provider_js_1.DefaultStatusList2021Encodings.base64url });
733
+ // construct data
734
+ const data = args.encrypted
735
+ ? (await (async function () {
736
+ // TODO: implement
737
+ throw new Error('[did-provider-cheqd]: encrypted status list is not implemented yet');
738
+ }()))
739
+ : (await (async function () {
740
+ switch (args.statusPurpose) {
741
+ case cheqd_did_provider_js_1.DefaultStatusList2021StatusPurposeTypes.revocation:
742
+ return {
743
+ StatusList2021: {
744
+ type: cheqd_did_provider_js_1.DefaultStatusList2021ResourceTypes.revocation,
745
+ encodedList: bitstring,
746
+ validFrom: new Date().toISOString(),
747
+ validUntil: args?.validUntil
748
+ },
749
+ metadata: {
750
+ encrypted: false,
751
+ encoding: args?.statusListEncoding || cheqd_did_provider_js_1.DefaultStatusList2021Encodings.base64url,
752
+ }
753
+ };
754
+ case cheqd_did_provider_js_1.DefaultStatusList2021StatusPurposeTypes.suspension:
755
+ return {
756
+ StatusList2021: {
757
+ type: cheqd_did_provider_js_1.DefaultStatusList2021ResourceTypes.suspension,
758
+ encodedList: bitstring,
759
+ validFrom: new Date().toISOString(),
760
+ validUntil: args?.validUntil
761
+ },
762
+ metadata: {
763
+ encrypted: false,
764
+ encoding: args?.statusListEncoding || cheqd_did_provider_js_1.DefaultStatusList2021Encodings.base64url,
765
+ }
766
+ };
767
+ default:
768
+ throw new Error('[did-provider-cheqd]: statusPurpose is not valid');
769
+ }
770
+ }()));
771
+ // construct payload
772
+ const payload = {
773
+ id: (0, uuid_1.v4)(),
774
+ collectionId: args.issuerDid.split(':').reverse()[0],
775
+ name: args.statusListName,
776
+ resourceType: cheqd_did_provider_js_1.DefaultStatusList2021ResourceTypes[args.statusPurpose],
777
+ version: args?.resourceVersion || new Date().toISOString(),
778
+ alsoKnownAs: args?.alsoKnownAs || [],
779
+ data: (0, uint8arrays_1.fromString)(JSON.stringify(data), 'utf-8'),
780
+ };
781
+ // return result
782
+ return {
783
+ created: await context.agent[BroadcastStatusList2021MethodName]({ kms: args.kms, payload, network: network }),
784
+ statusList2021: data,
785
+ resourceMetadata: await Cheqd.fetchStatusList2021Metadata({ credentialStatus: { id: `${cheqd_did_resolver_js_1.resolverUrl}${args.issuerDid}?resourceName=${args.statusListName}&resourceType=${cheqd_did_provider_js_1.DefaultStatusList2021ResourceTypes[args.statusPurpose]}`, type: 'StatusList2021Entry' } })
786
+ };
664
787
  }
665
- async CreateEncryptedStatusList2021(args, context) {
788
+ async BroadcastStatusList2021(args, context) {
666
789
  if (typeof args.kms !== 'string') {
667
790
  throw new Error('[did-provider-cheqd]: kms is required');
668
791
  }
@@ -679,48 +802,13 @@ class Cheqd {
679
802
  args.payload.data = (0, uint8arrays_1.fromString)(args.payload.data, 'base64');
680
803
  }
681
804
  // TODO: validate data as per bitstring
682
- if (!args?.encryptionOptions) {
683
- throw new Error('[did-provider-cheqd]: encryptionOptions is required');
684
- }
685
- if (!args?.bootstrapOptions) {
686
- throw new Error('[did-provider-cheqd]: bootstrapOptions is required');
805
+ // validate resource type
806
+ if (!Object.values(cheqd_did_provider_js_1.DefaultStatusList2021ResourceTypes).includes(args?.payload?.resourceType)) {
807
+ throw new Error(`[did-provider-cheqd]: resourceType must be one of ${Object.values(cheqd_did_provider_js_1.DefaultStatusList2021ResourceTypes).join(', ')}`);
687
808
  }
688
- if (!args?.encryptionOptions?.accessControlConditions) {
689
- throw new Error('[did-provider-cheqd]: accessControlConditions is required');
690
- }
691
- // instantiate dkg-threshold client, in which case lit-protocol is used
692
- const lit = await lit_protocol_js_1.LitProtocol.create({
693
- chain: args.bootstrapOptions?.chain,
694
- litNetwork: args.bootstrapOptions?.litNetwork
695
- });
696
- // construct access control conditions
697
- const unifiedAccessControlConditions = await Promise.all(args.encryptionOptions.accessControlConditions.map(async (condition) => {
698
- switch (condition.type) {
699
- case exports.AccessControlConditionTypes.memoNonce:
700
- return await lit_protocol_js_1.LitProtocol.generateCosmosAccessControlConditionTransactionMemo({
701
- key: '$.txs.*.body.memo',
702
- comparator: 'contains',
703
- value: condition?.specificNonce || await lit_protocol_js_1.LitProtocol.generateTxNonce(condition?.nonceFormat)
704
- }, condition.amountObserved, condition.senderAddressObserved, condition.recipientAddressObserved, args.bootstrapOptions.chain);
705
- case exports.AccessControlConditionTypes.balance:
706
- return await lit_protocol_js_1.LitProtocol.generateCosmosAccessControlConditionBalance({
707
- key: '$.balances[0].amount',
708
- comparator: condition.comparator,
709
- value: condition.amountObserved
710
- }, args.bootstrapOptions.chain, condition.addressObserved);
711
- default:
712
- throw new Error(`[did-provider-cheqd]: accessControlCondition type is not supported`);
713
- }
714
- }));
715
- // encrypt data
716
- const { encryptedString, encryptedSymmetricKey, symmetricKey } = await lit.encrypt((0, uint8arrays_1.toString)(args.payload.data, 'base64url'), unifiedAccessControlConditions, true);
717
- // set encrypted data
718
- args.payload.data = new Uint8Array(await encryptedString.arrayBuffer());
719
- // set default resource type in runtime
720
- args.payload.resourceType = 'StatusList2021';
721
809
  this.providerId = Cheqd.generateProviderId(args.network);
722
810
  this.didProvider = await Cheqd.loadProvider({ id: this.providerId }, this.supportedDidProviders);
723
- const created = await this.didProvider.createResource({
811
+ return await this.didProvider.createResource({
724
812
  options: {
725
813
  kms: args.kms,
726
814
  payload: args.payload,
@@ -728,13 +816,6 @@ class Cheqd {
728
816
  fee: args?.fee
729
817
  }
730
818
  }, context);
731
- return {
732
- created,
733
- encryptedSymmetricKey,
734
- encryptedStatusList2021: await (0, helpers_js_1.blobToHexString)(encryptedString),
735
- symmetricKey: args?.encryptionOptions?.returnSymmetricKey ? (0, uint8arrays_1.toString)(symmetricKey, 'hex') : undefined,
736
- unifiedAccessControlConditions
737
- };
738
819
  }
739
820
  async GenerateDidDoc(args, context) {
740
821
  if (typeof args.verificationMethod !== 'string') {
@@ -884,14 +965,13 @@ class Cheqd {
884
965
  ? args.issuanceOptions.credential.issuer.id
885
966
  : args.issuanceOptions.credential.issuer;
886
967
  // generate status list credential
887
- const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${issuer}?resourceName=${args.statusOptions.statusListName}`;
968
+ const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${issuer}?resourceName=${args.statusOptions.statusListName}&resourceType=StatusList2021Revocation`;
888
969
  // construct credential status
889
970
  const credentialStatus = {
890
971
  id: `${statusListCredential}#${statusListIndex}`,
891
972
  type: 'StatusList2021Entry',
892
973
  statusPurpose: 'revocation',
893
974
  statusListIndex: `${statusListIndex}`,
894
- statusListCredential,
895
975
  };
896
976
  // add credential status to credential
897
977
  args.issuanceOptions.credential.credentialStatus = credentialStatus;
@@ -926,14 +1006,13 @@ class Cheqd {
926
1006
  ? args.issuanceOptions.credential.issuer.id
927
1007
  : args.issuanceOptions.credential.issuer;
928
1008
  // generate status list credential
929
- const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${issuer}?resourceName=${args.statusOptions.statusListName}`;
1009
+ const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${issuer}?resourceName=${args.statusOptions.statusListName}&resourceType=StatusList2021Suspension`;
930
1010
  // construct credential status
931
1011
  const credentialStatus = {
932
1012
  id: `${statusListCredential}#${statusListIndex}`,
933
1013
  type: 'StatusList2021Entry',
934
1014
  statusPurpose: 'suspension',
935
1015
  statusListIndex: `${statusListIndex}`,
936
- statusListCredential,
937
1016
  };
938
1017
  // add credential status to credential
939
1018
  args.issuanceOptions.credential.credentialStatus = credentialStatus;
@@ -972,18 +1051,20 @@ class Cheqd {
972
1051
  if (!verificationResult.verified) {
973
1052
  return { verified: false, error: verificationResult.error };
974
1053
  }
1054
+ // if jwt credential, decode it
1055
+ const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
975
1056
  // verify credential status
976
- switch (args.credential.credentialStatus?.statusPurpose) {
1057
+ switch (credential.credentialStatus?.statusPurpose) {
977
1058
  case 'revocation':
978
- if (await Cheqd.checkRevoked(args.credential, { ...args.options, topArgs: args }))
1059
+ if (await Cheqd.checkRevoked(credential, { ...args.options, topArgs: args }))
979
1060
  return { verified: false, revoked: true };
980
1061
  return { verified: true, revoked: false };
981
1062
  case 'suspension':
982
- if (await Cheqd.checkSuspended(args.credential, { ...args.options, topArgs: args }))
1063
+ if (await Cheqd.checkSuspended(credential, { ...args.options, topArgs: args }))
983
1064
  return { verified: false, suspended: true };
984
1065
  return { verified: true, suspended: false };
985
1066
  default:
986
- throw new Error(`[did-provider-cheqd]: verify credential: Unsupported status purpose: ${args.credential.credentialStatus?.statusPurpose}`);
1067
+ throw new Error(`[did-provider-cheqd]: verify credential: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
987
1068
  }
988
1069
  }
989
1070
  async VerifyPresentationWithStatusList2021(args, context) {
@@ -1003,16 +1084,8 @@ class Cheqd {
1003
1084
  // verify credential(s) status(es)
1004
1085
  for (let credential of args.presentation.verifiableCredential) {
1005
1086
  // if jwt credential, decode it
1006
- if (typeof credential === 'string') {
1007
- const decodedCredential = (0, did_jwt_1.decodeJWT)(credential);
1008
- // validate credential payload
1009
- if (!decodedCredential.payload)
1010
- throw new Error('[did-provider-cheqd]: verify presentation: decodedCredential.payload is required');
1011
- // validate credential payload ref as VerifiableCredential
1012
- if (!decodedCredential.payload.vc)
1013
- throw new Error('[did-provider-cheqd]: verify presentation: decodedCredential.payload.vc is required');
1014
- credential = decodedCredential.payload.vc;
1015
- }
1087
+ if (typeof credential === 'string')
1088
+ credential = await Cheqd.decodeCredentialJWT(credential);
1016
1089
  switch (credential.credentialStatus?.statusPurpose) {
1017
1090
  case 'revocation':
1018
1091
  if (await Cheqd.checkRevoked(credential, { ...args.options, topArgs: args }))
@@ -1029,23 +1102,71 @@ class Cheqd {
1029
1102
  return { verified: true };
1030
1103
  }
1031
1104
  async CheckCredentialStatusWithStatusList2021(args, context) {
1032
- switch (args.credential.credentialStatus?.statusPurpose) {
1105
+ // if jwt credential, decode it
1106
+ const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
1107
+ switch (credential.credentialStatus?.statusPurpose) {
1033
1108
  case 'revocation':
1034
- if (await Cheqd.checkRevoked(args.credential, { ...args.options, topArgs: args }))
1109
+ if (await Cheqd.checkRevoked(credential, { ...args.options, topArgs: args }))
1035
1110
  return { revoked: true };
1036
1111
  return { revoked: false };
1037
1112
  case 'suspension':
1038
- if (await Cheqd.checkSuspended(args.credential, { ...args.options, topArgs: args }))
1113
+ if (await Cheqd.checkSuspended(credential, { ...args.options, topArgs: args }))
1039
1114
  return { suspended: true };
1040
1115
  return { suspended: false };
1041
1116
  default:
1042
- throw new Error(`[did-provider-cheqd]: check status: Unsupported status purpose: ${args.credential.credentialStatus?.statusPurpose}`);
1117
+ throw new Error(`[did-provider-cheqd]: check status: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
1043
1118
  }
1044
1119
  }
1045
1120
  async RevokeCredentialWithStatusList2021(args, context) {
1121
+ // verify credential, if provided and revocation options are not
1122
+ if (args?.credential && !args?.revocationOptions) {
1123
+ const verificationResult = await context.agent.verifyCredential({
1124
+ credential: args.credential,
1125
+ policies: {
1126
+ credentialStatus: false
1127
+ }
1128
+ });
1129
+ // early return if verification failed
1130
+ if (!verificationResult.verified) {
1131
+ return { revoked: false, error: verificationResult.error };
1132
+ }
1133
+ }
1134
+ // if revocation options are provided, give precedence
1135
+ if (args?.revocationOptions) {
1136
+ // validate revocation options - case: revocationOptions.issuerDid
1137
+ if (!args.revocationOptions.issuerDid)
1138
+ throw new Error('[did-provider-cheqd]: revocation: revocationOptions.issuerDid is required');
1139
+ // validate revocation options - case: revocationOptions.statusListName
1140
+ if (!args.revocationOptions.statusListName)
1141
+ throw new Error('[did-provider-cheqd]: revocation: revocationOptions.statusListName is required');
1142
+ // validate revocation options - case: revocationOptions.statusListIndex
1143
+ if (!args.revocationOptions.statusListIndex)
1144
+ throw new Error('[did-provider-cheqd]: revocation: revocationOptions.statusListIndex is required');
1145
+ // construct status list credential
1146
+ const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${args.revocationOptions.issuerDid}?resourceName=${args.revocationOptions.statusListName}&resourceType=StatusList2021Revocation`;
1147
+ // construct credential status
1148
+ args.credential = {
1149
+ '@context': [],
1150
+ issuer: args.revocationOptions.issuerDid,
1151
+ credentialSubject: {},
1152
+ credentialStatus: {
1153
+ id: `${statusListCredential}#${args.revocationOptions.statusListIndex}`,
1154
+ type: 'StatusList2021Entry',
1155
+ statusPurpose: 'revocation',
1156
+ statusListIndex: `${args.revocationOptions.statusListIndex}`,
1157
+ },
1158
+ issuanceDate: '',
1159
+ proof: {}
1160
+ };
1161
+ }
1162
+ // validate args - case: credential
1163
+ if (!args.credential)
1164
+ throw new Error('[did-provider-cheqd]: revocation: credential is required');
1165
+ // if jwt credential, decode it
1166
+ const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
1046
1167
  // validate status purpose
1047
- if (args.credential.credentialStatus?.statusPurpose !== 'revocation') {
1048
- throw new Error(`[did-provider-cheqd]: revocation: Unsupported status purpose: ${args.credential.credentialStatus?.statusPurpose}`);
1168
+ if (credential.credentialStatus?.statusPurpose !== 'revocation') {
1169
+ throw new Error(`[did-provider-cheqd]: revocation: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
1049
1170
  }
1050
1171
  // validate args in pairs - case: statusListFile and statusList
1051
1172
  if (args.options?.statusListFile && args.options?.statusList) {
@@ -1064,26 +1185,151 @@ class Cheqd {
1064
1185
  throw new Error('[did-provider-cheqd]: revocation: publish requires statusListFile or statusList, if fetchList is disabled');
1065
1186
  }
1066
1187
  // revoke credential
1067
- return await Cheqd.revokeCredential(args.credential, {
1188
+ return await Cheqd.revokeCredential(credential, {
1068
1189
  ...args.options,
1069
1190
  topArgs: args,
1070
1191
  publishOptions: {
1071
1192
  context,
1193
+ statusListEncoding: args?.options?.statusListEncoding,
1194
+ statusListValidUntil: args?.options?.statusListValidUntil,
1072
1195
  resourceId: args?.options?.resourceId,
1073
1196
  resourceVersion: args?.options?.resourceVersion,
1197
+ resourceAlsoKnownAs: args?.options?.alsoKnownAs,
1074
1198
  signInputs: args?.options?.signInputs,
1075
1199
  fee: args?.options?.fee
1076
1200
  }
1077
1201
  });
1078
1202
  }
1079
1203
  async RevokeBulkCredentialsWithStatusList2021(args, context) {
1080
- // TODO: implement
1081
- throw new Error('[did-provider-cheqd]: revocation: bulk revocation is not implemented yet');
1204
+ // verify credential, if provided and revocation options are not
1205
+ if (args?.credentials && !args?.revocationOptions) {
1206
+ const verificationResult = await Promise.all(args.credentials.map(async (credential) => {
1207
+ return await context.agent.verifyCredential({
1208
+ credential,
1209
+ policies: {
1210
+ credentialStatus: false
1211
+ }
1212
+ });
1213
+ }));
1214
+ // early return if verification failed for any credential
1215
+ if (verificationResult.some(result => !result.verified)) {
1216
+ // define verified
1217
+ return { revoked: Array(args.credentials.length).fill(false), error: verificationResult.find(result => !result.verified).error || { message: 'verification: could not verify credential' } };
1218
+ }
1219
+ }
1220
+ // if revocation options are provided, give precedence
1221
+ if (args?.revocationOptions) {
1222
+ // validate revocation options - case: revocationOptions.issuerDid
1223
+ if (!args.revocationOptions.issuerDid)
1224
+ throw new Error('[did-provider-cheqd]: revocation: revocationOptions.issuerDid is required');
1225
+ // validate revocation options - case: revocationOptions.statusListName
1226
+ if (!args.revocationOptions.statusListName)
1227
+ throw new Error('[did-provider-cheqd]: revocation: revocationOptions.statusListName is required');
1228
+ // validate revocation options - case: revocationOptions.statusListIndices
1229
+ if (!args.revocationOptions.statusListIndices || !args.revocationOptions.statusListIndices.length || args.revocationOptions.statusListIndices.length === 0 || !args.revocationOptions.statusListIndices.every(index => !isNaN(+index)))
1230
+ throw new Error('[did-provider-cheqd]: revocation: revocationOptions.statusListIndex is required and must be an array of indices');
1231
+ // construct status list credential
1232
+ const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${args.revocationOptions.issuerDid}?resourceName=${args.revocationOptions.statusListName}&resourceType=StatusList2021Revocation`;
1233
+ // construct credential status
1234
+ args.credentials = args.revocationOptions.statusListIndices.map(index => ({
1235
+ '@context': [],
1236
+ issuer: args.revocationOptions.issuerDid,
1237
+ credentialSubject: {},
1238
+ credentialStatus: {
1239
+ id: `${statusListCredential}#${index}`,
1240
+ type: 'StatusList2021Entry',
1241
+ statusPurpose: 'revocation',
1242
+ statusListIndex: `${index}`,
1243
+ },
1244
+ issuanceDate: '',
1245
+ proof: {}
1246
+ }));
1247
+ }
1248
+ // validate args - case: credentials
1249
+ if (!args.credentials || !args.credentials.length || args.credentials.length === 0)
1250
+ throw new Error('[did-provider-cheqd]: revocation: credentials is required and must be an array of credentials');
1251
+ // if jwt credentials, decode them
1252
+ const credentials = await Promise.all(args.credentials.map(async (credential) => typeof credential === 'string' ? await Cheqd.decodeCredentialJWT(credential) : credential));
1253
+ // validate args in pairs - case: statusListFile and statusList
1254
+ if (args.options?.statusListFile && args.options?.statusList) {
1255
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile and statusList are mutually exclusive');
1256
+ }
1257
+ // validate args in pairs - case: statusListFile and fetchList
1258
+ if (args.options?.statusListFile && args.options?.fetchList) {
1259
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile and fetchList are mutually exclusive');
1260
+ }
1261
+ // validate args in pairs - case: statusList and fetchList
1262
+ if (args.options?.statusList && args.options?.fetchList) {
1263
+ throw new Error('[did-provider-cheqd]: revocation: statusList and fetchList are mutually exclusive');
1264
+ }
1265
+ // validate args in pairs - case: publish
1266
+ if (args.options?.publish && !args.fetchList && !(args.options?.statusListFile || args.options?.statusList)) {
1267
+ throw new Error('[did-provider-cheqd]: revocation: publish requires statusListFile or statusList, if fetchList is disabled');
1268
+ }
1269
+ // revoke credentials
1270
+ return await Cheqd.revokeCredentials(credentials, {
1271
+ ...args.options,
1272
+ topArgs: args,
1273
+ publishOptions: {
1274
+ context,
1275
+ resourceId: args?.options?.resourceId,
1276
+ resourceVersion: args?.options?.resourceVersion,
1277
+ resourceAlsoKnownAs: args?.options?.alsoKnownAs,
1278
+ signInputs: args?.options?.signInputs,
1279
+ fee: args?.options?.fee
1280
+ }
1281
+ });
1082
1282
  }
1083
1283
  async SuspendCredentialWithStatusList2021(args, context) {
1284
+ // verify credential, if provided and suspension options are not
1285
+ if (args?.credential && !args?.suspensionOptions) {
1286
+ const verificationResult = await context.agent.verifyCredential({
1287
+ credential: args.credential,
1288
+ policies: {
1289
+ credentialStatus: false
1290
+ }
1291
+ });
1292
+ // early return if verification failed
1293
+ if (!verificationResult.verified) {
1294
+ return { suspended: false, error: verificationResult.error };
1295
+ }
1296
+ }
1297
+ // if suspension options are provided, give precedence
1298
+ if (args?.suspensionOptions) {
1299
+ // validate suspension options - case: suspensionOptions.issuerDid
1300
+ if (!args.suspensionOptions.issuerDid)
1301
+ throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.issuerDid is required');
1302
+ // validate suspension options - case: suspensionOptions.statusListName
1303
+ if (!args.suspensionOptions.statusListName)
1304
+ throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.statusListName is required');
1305
+ // validate suspension options - case: suspensionOptions.statusListIndex
1306
+ if (!args.suspensionOptions.statusListIndex)
1307
+ throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.statusListIndex is required');
1308
+ // construct status list credential
1309
+ const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${args.suspensionOptions.issuerDid}?resourceName=${args.suspensionOptions.statusListName}&resourceType=StatusList2021Suspension`;
1310
+ // construct credential status
1311
+ args.credential = {
1312
+ '@context': [],
1313
+ issuer: args.suspensionOptions.issuerDid,
1314
+ credentialSubject: {},
1315
+ credentialStatus: {
1316
+ id: `${statusListCredential}#${args.suspensionOptions.statusListIndex}`,
1317
+ type: 'StatusList2021Entry',
1318
+ statusPurpose: 'suspension',
1319
+ statusListIndex: `${args.suspensionOptions.statusListIndex}`,
1320
+ },
1321
+ issuanceDate: '',
1322
+ proof: {}
1323
+ };
1324
+ }
1325
+ // validate args - case: credential
1326
+ if (!args.credential)
1327
+ throw new Error('[did-provider-cheqd]: suspension: credential is required');
1328
+ // if jwt credential, decode it
1329
+ const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
1084
1330
  // validate status purpose
1085
- if (args.credential.credentialStatus?.statusPurpose !== 'suspension') {
1086
- throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${args.credential.credentialStatus?.statusPurpose}`);
1331
+ if (credential.credentialStatus?.statusPurpose !== 'suspension') {
1332
+ throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
1087
1333
  }
1088
1334
  // validate args in pairs - case: statusListFile and statusList
1089
1335
  if (args.options?.statusListFile && args.options?.statusList) {
@@ -1102,26 +1348,151 @@ class Cheqd {
1102
1348
  throw new Error('[did-provider-cheqd]: suspension: publish requires statusListFile or statusList, if fetchList is disabled');
1103
1349
  }
1104
1350
  // suspend credential
1105
- return await Cheqd.suspendCredential(args.credential, {
1351
+ return await Cheqd.suspendCredential(credential, {
1106
1352
  ...args.options,
1107
1353
  topArgs: args,
1108
1354
  publishOptions: {
1109
1355
  context,
1356
+ statusListEncoding: args?.options?.statusListEncoding,
1357
+ statusListValidUntil: args?.options?.statusListValidUntil,
1110
1358
  resourceId: args?.options?.resourceId,
1111
1359
  resourceVersion: args?.options?.resourceVersion,
1360
+ resourceAlsoKnownAs: args?.options?.alsoKnownAs,
1112
1361
  signInputs: args?.options?.signInputs,
1113
1362
  fee: args?.options?.fee
1114
1363
  }
1115
1364
  });
1116
1365
  }
1117
1366
  async SuspendBulkCredentialsWithStatusList2021(args, context) {
1118
- // TODO: implement
1119
- throw new Error('[did-provider-cheqd]: suspension: bulk suspension is not implemented yet');
1367
+ // verify credential, if provided and suspension options are not
1368
+ if (args?.credentials && !args?.suspensionOptions) {
1369
+ const verificationResult = await Promise.all(args.credentials.map(async (credential) => {
1370
+ return await context.agent.verifyCredential({
1371
+ credential,
1372
+ policies: {
1373
+ credentialStatus: false
1374
+ }
1375
+ });
1376
+ }));
1377
+ // early return if verification failed for any credential
1378
+ if (verificationResult.some(result => !result.verified)) {
1379
+ // define verified
1380
+ return { suspended: Array(args.credentials.length).fill(false), error: verificationResult.find(result => !result.verified).error || { message: 'verification: could not verify credential' } };
1381
+ }
1382
+ }
1383
+ // if suspension options are provided, give precedence
1384
+ if (args?.suspensionOptions) {
1385
+ // validate suspension options - case: suspensionOptions.issuerDid
1386
+ if (!args.suspensionOptions.issuerDid)
1387
+ throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.issuerDid is required');
1388
+ // validate suspension options - case: suspensionOptions.statusListName
1389
+ if (!args.suspensionOptions.statusListName)
1390
+ throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.statusListName is required');
1391
+ // validate suspension options - case: suspensionOptions.statusListIndices
1392
+ if (!args.suspensionOptions.statusListIndices || !args.suspensionOptions.statusListIndices.length || args.suspensionOptions.statusListIndices.length === 0 || !args.suspensionOptions.statusListIndices.every(index => !isNaN(+index)))
1393
+ throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.statusListIndex is required and must be an array of indices');
1394
+ // construct status list credential
1395
+ const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${args.suspensionOptions.issuerDid}?resourceName=${args.suspensionOptions.statusListName}&resourceType=StatusList2021Suspension`;
1396
+ // construct credential status
1397
+ args.credentials = args.suspensionOptions.statusListIndices.map(index => ({
1398
+ '@context': [],
1399
+ issuer: args.suspensionOptions.issuerDid,
1400
+ credentialSubject: {},
1401
+ credentialStatus: {
1402
+ id: `${statusListCredential}#${index}`,
1403
+ type: 'StatusList2021Entry',
1404
+ statusPurpose: 'suspension',
1405
+ statusListIndex: `${index}`,
1406
+ },
1407
+ issuanceDate: '',
1408
+ proof: {}
1409
+ }));
1410
+ }
1411
+ // validate args - case: credentials
1412
+ if (!args.credentials || !args.credentials.length || args.credentials.length === 0)
1413
+ throw new Error('[did-provider-cheqd]: suspension: credentials is required and must be an array of credentials');
1414
+ // if jwt credentials, decode them
1415
+ const credentials = await Promise.all(args.credentials.map(async (credential) => typeof credential === 'string' ? await Cheqd.decodeCredentialJWT(credential) : credential));
1416
+ // validate args in pairs - case: statusListFile and statusList
1417
+ if (args.options?.statusListFile && args.options?.statusList) {
1418
+ throw new Error('[did-provider-cheqd]: suspension: statusListFile and statusList are mutually exclusive');
1419
+ }
1420
+ // validate args in pairs - case: statusListFile and fetchList
1421
+ if (args.options?.statusListFile && args.options?.fetchList) {
1422
+ throw new Error('[did-provider-cheqd]: suspension: statusListFile and fetchList are mutually exclusive');
1423
+ }
1424
+ // validate args in pairs - case: statusList and fetchList
1425
+ if (args.options?.statusList && args.options?.fetchList) {
1426
+ throw new Error('[did-provider-cheqd]: suspension: statusList and fetchList are mutually exclusive');
1427
+ }
1428
+ // validate args in pairs - case: publish
1429
+ if (args.options?.publish && !args.fetchList && !(args.options?.statusListFile || args.options?.statusList)) {
1430
+ throw new Error('[did-provider-cheqd]: suspension: publish requires statusListFile or statusList, if fetchList is disabled');
1431
+ }
1432
+ // suspend credentials
1433
+ return await Cheqd.suspendCredentials(credentials, {
1434
+ ...args.options,
1435
+ topArgs: args,
1436
+ publishOptions: {
1437
+ context,
1438
+ resourceId: args?.options?.resourceId,
1439
+ resourceVersion: args?.options?.resourceVersion,
1440
+ resourceAlsoKnownAs: args?.options?.alsoKnownAs,
1441
+ signInputs: args?.options?.signInputs,
1442
+ fee: args?.options?.fee
1443
+ }
1444
+ });
1120
1445
  }
1121
1446
  async UnsuspendCredentialWithStatusList2021(args, context) {
1447
+ // verify credential, if provided and unsuspension options are not
1448
+ if (args?.credential && !args?.unsuspensionOptions) {
1449
+ const verificationResult = await context.agent.verifyCredential({
1450
+ credential: args.credential,
1451
+ policies: {
1452
+ credentialStatus: false
1453
+ }
1454
+ });
1455
+ // early return if verification failed
1456
+ if (!verificationResult.verified) {
1457
+ return { unsuspended: false, error: verificationResult.error };
1458
+ }
1459
+ }
1460
+ // if unsuspension options are provided, give precedence
1461
+ if (args?.unsuspensionOptions) {
1462
+ // validate unsuspension options - case: unsuspensionOptions.issuerDid
1463
+ if (!args.unsuspensionOptions.issuerDid)
1464
+ throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.issuerDid is required');
1465
+ // validate unsuspension options - case: unsuspensionOptions.statusListName
1466
+ if (!args.unsuspensionOptions.statusListName)
1467
+ throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.statusListName is required');
1468
+ // validate unsuspension options - case: unsuspensionOptions.statusListIndex
1469
+ if (!args.unsuspensionOptions.statusListIndex)
1470
+ throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.statusListIndex is required');
1471
+ // construct status list credential
1472
+ const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${args.unsuspensionOptions.issuerDid}?resourceName=${args.unsuspensionOptions.statusListName}&resourceType=StatusList2021Suspension`;
1473
+ // construct credential status
1474
+ args.credential = {
1475
+ '@context': [],
1476
+ issuer: args.unsuspensionOptions.issuerDid,
1477
+ credentialSubject: {},
1478
+ credentialStatus: {
1479
+ id: `${statusListCredential}#${args.unsuspensionOptions.statusListIndex}`,
1480
+ type: 'StatusList2021Entry',
1481
+ statusPurpose: 'suspension',
1482
+ statusListIndex: `${args.unsuspensionOptions.statusListIndex}`,
1483
+ },
1484
+ issuanceDate: '',
1485
+ proof: {}
1486
+ };
1487
+ }
1488
+ // validate args - case: credential
1489
+ if (!args.credential)
1490
+ throw new Error('[did-provider-cheqd]: unsuspension: credential is required');
1491
+ // if jwt credential, decode it
1492
+ const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
1122
1493
  // validate status purpose
1123
- if (args.credential.credentialStatus?.statusPurpose !== 'suspension') {
1124
- throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${args.credential.credentialStatus?.statusPurpose}`);
1494
+ if (credential.credentialStatus?.statusPurpose !== 'suspension') {
1495
+ throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
1125
1496
  }
1126
1497
  // validate args in pairs - case: statusListFile and statusList
1127
1498
  if (args.options?.statusListFile && args.options?.statusList) {
@@ -1140,21 +1511,100 @@ class Cheqd {
1140
1511
  throw new Error('[did-provider-cheqd]: suspension: publish requires statusListFile or statusList, if fetchList is disabled');
1141
1512
  }
1142
1513
  // suspend credential
1143
- return await Cheqd.unsuspendCredential(args.credential, {
1514
+ return await Cheqd.unsuspendCredential(credential, {
1144
1515
  ...args.options,
1145
1516
  topArgs: args,
1146
1517
  publishOptions: {
1147
1518
  context,
1519
+ statusListEncoding: args?.options?.statusListEncoding,
1520
+ statusListValidUntil: args?.options?.statusListValidUntil,
1148
1521
  resourceId: args?.options?.resourceId,
1149
1522
  resourceVersion: args?.options?.resourceVersion,
1523
+ resourceAlsoKnownAs: args?.options?.alsoKnownAs,
1150
1524
  signInputs: args?.options?.signInputs,
1151
1525
  fee: args?.options?.fee
1152
1526
  }
1153
1527
  });
1154
1528
  }
1155
1529
  async UnsuspendBulkCredentialsWithStatusList2021(args, context) {
1156
- // TODO: implement
1157
- throw new Error('[did-provider-cheqd]: suspension: bulk unsuspension is not implemented yet');
1530
+ // verify credential, if provided and unsuspension options are not
1531
+ if (args?.credentials && !args?.unsuspensionOptions) {
1532
+ const verificationResult = await Promise.all(args.credentials.map(async (credential) => {
1533
+ return await context.agent.verifyCredential({
1534
+ credential,
1535
+ policies: {
1536
+ credentialStatus: false
1537
+ }
1538
+ });
1539
+ }));
1540
+ // early return if verification failed for any credential
1541
+ if (verificationResult.some(result => !result.verified)) {
1542
+ // define verified
1543
+ return { unsuspended: Array(args.credentials.length).fill(false), error: verificationResult.find(result => !result.verified).error || { message: 'verification: could not verify credential' } };
1544
+ }
1545
+ }
1546
+ // if unsuspension options are provided, give precedence
1547
+ if (args?.unsuspensionOptions) {
1548
+ // validate unsuspension options - case: unsuspensionOptions.issuerDid
1549
+ if (!args.unsuspensionOptions.issuerDid)
1550
+ throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.issuerDid is required');
1551
+ // validate unsuspension options - case: unsuspensionOptions.statusListName
1552
+ if (!args.unsuspensionOptions.statusListName)
1553
+ throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.statusListName is required');
1554
+ // validate unsuspension options - case: unsuspensionOptions.statusListIndices
1555
+ if (!args.unsuspensionOptions.statusListIndices || !args.unsuspensionOptions.statusListIndices.length || args.unsuspensionOptions.statusListIndices.length === 0 || !args.unsuspensionOptions.statusListIndices.every(index => !isNaN(+index)))
1556
+ throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.statusListIndex is required and must be an array of indices');
1557
+ // construct status list credential
1558
+ const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${args.unsuspensionOptions.issuerDid}?resourceName=${args.unsuspensionOptions.statusListName}&resourceType=StatusList2021Suspension`;
1559
+ // construct credential status
1560
+ args.credentials = args.unsuspensionOptions.statusListIndices.map(index => ({
1561
+ '@context': [],
1562
+ issuer: args.unsuspensionOptions.issuerDid,
1563
+ credentialSubject: {},
1564
+ credentialStatus: {
1565
+ id: `${statusListCredential}#${index}`,
1566
+ type: 'StatusList2021Entry',
1567
+ statusPurpose: 'suspension',
1568
+ statusListIndex: `${index}`,
1569
+ },
1570
+ issuanceDate: '',
1571
+ proof: {}
1572
+ }));
1573
+ }
1574
+ // validate args - case: credentials
1575
+ if (!args.credentials || !args.credentials.length || args.credentials.length === 0)
1576
+ throw new Error('[did-provider-cheqd]: unsuspension: credentials is required and must be an array of credentials');
1577
+ // if jwt credentials, decode them
1578
+ const credentials = await Promise.all(args.credentials.map(async (credential) => typeof credential === 'string' ? await Cheqd.decodeCredentialJWT(credential) : credential));
1579
+ // validate args in pairs - case: statusListFile and statusList
1580
+ if (args.options?.statusListFile && args.options?.statusList) {
1581
+ throw new Error('[did-provider-cheqd]: unsuspension: statusListFile and statusList are mutually exclusive');
1582
+ }
1583
+ // validate args in pairs - case: statusListFile and fetchList
1584
+ if (args.options?.statusListFile && args.options?.fetchList) {
1585
+ throw new Error('[did-provider-cheqd]: unsuspension: statusListFile and fetchList are mutually exclusive');
1586
+ }
1587
+ // validate args in pairs - case: statusList and fetchList
1588
+ if (args.options?.statusList && args.options?.fetchList) {
1589
+ throw new Error('[did-provider-cheqd]: unsuspension: statusList and fetchList are mutually exclusive');
1590
+ }
1591
+ // validate args in pairs - case: publish
1592
+ if (args.options?.publish && !args.fetchList && !(args.options?.statusListFile || args.options?.statusList)) {
1593
+ throw new Error('[did-provider-cheqd]: unsuspension: publish requires statusListFile or statusList, if fetchList is disabled');
1594
+ }
1595
+ // suspend credentials
1596
+ return await Cheqd.unsuspendCredentials(credentials, {
1597
+ ...args.options,
1598
+ topArgs: args,
1599
+ publishOptions: {
1600
+ context,
1601
+ resourceId: args?.options?.resourceId,
1602
+ resourceVersion: args?.options?.resourceVersion,
1603
+ resourceAlsoKnownAs: args?.options?.alsoKnownAs,
1604
+ signInputs: args?.options?.signInputs,
1605
+ fee: args?.options?.fee
1606
+ }
1607
+ });
1158
1608
  }
1159
1609
  async TransactVerifierPaysIssuer(args, context) {
1160
1610
  try {
@@ -1277,46 +1727,56 @@ class Cheqd {
1277
1727
  // validate status purpose
1278
1728
  if (credential?.credentialStatus?.statusPurpose !== 'revocation')
1279
1729
  throw new Error('[did-provider-cheqd]: revocation: Invalid status purpose');
1280
- // fetch status list 2021 metadata
1281
- const metadata = (await Cheqd.fetchStatusList2021Metadata(credential));
1282
- // detect if encrypted
1283
- const isEncrypted = function () {
1284
- switch (metadata.mediaType) {
1285
- case 'application/octet-stream':
1286
- return true;
1287
- case 'application/gzip':
1288
- return false;
1289
- default:
1290
- throw new Error(`[did-provider-cheqd]: revocation: Unsupported media type: ${metadata.mediaType}`);
1291
- }
1292
- }();
1730
+ // fetch status list 2021
1731
+ const publishedList = (await Cheqd.fetchStatusList2021(credential));
1293
1732
  // early return, if encrypted and no decryption key provided
1294
- if (isEncrypted && !options?.topArgs?.symmetricKey)
1733
+ if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey)
1295
1734
  throw new Error('[did-provider-cheqd]: revocation: symmetricKey is required, if status list 2021 is encrypted');
1296
1735
  // fetch status list 2021 inscribed in credential
1297
1736
  const statusList2021 = options?.topArgs?.fetchList
1298
1737
  ? (await async function () {
1299
1738
  // if not encrypted, return bitstring
1300
- if (!isEncrypted)
1301
- return await Cheqd.fetchStatusList2021(credential);
1739
+ if (!publishedList.metadata.encrypted)
1740
+ return publishedList.metadata.encoding === 'base64url'
1741
+ ? publishedList.StatusList2021.encodedList
1742
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
1302
1743
  // otherwise, decrypt and return bitstring
1303
- const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.fetchStatusList2021(credential, true));
1744
+ const scopedRawBlob = await (0, helpers_js_1.toBlob)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding));
1304
1745
  // decrypt
1305
1746
  return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
1306
1747
  }())
1307
1748
  : (await async function () {
1749
+ // transcode to base64url, if needed
1750
+ const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
1751
+ ? publishedList.StatusList2021.encodedList
1752
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
1308
1753
  // if status list 2021 is not fetched, read from file
1309
1754
  if (options?.statusListFile) {
1310
1755
  // if not encrypted, return bitstring
1311
- if (!isEncrypted)
1312
- return new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
1756
+ if (!publishedList.metadata.encrypted) {
1757
+ // construct encoded status list
1758
+ const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
1759
+ // validate against published list
1760
+ if (encoded !== publishedListTranscoded)
1761
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
1762
+ // return encoded
1763
+ return encoded;
1764
+ }
1313
1765
  // otherwise, decrypt and return bitstring
1314
1766
  const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
1315
1767
  // decrypt
1316
- return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
1768
+ const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
1769
+ // validate against published list
1770
+ if (decrypted !== publishedListTranscoded)
1771
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
1772
+ // return decrypted
1773
+ return decrypted;
1317
1774
  }
1318
1775
  if (!options?.statusListInlineBitstring)
1319
1776
  throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
1777
+ // validate against published list
1778
+ if (options?.statusListInlineBitstring !== publishedListTranscoded)
1779
+ throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
1320
1780
  // otherwise, read from inline bitstring
1321
1781
  return options?.statusListInlineBitstring;
1322
1782
  }());
@@ -1356,7 +1816,45 @@ class Cheqd {
1356
1816
  { encryptedString, encryptedSymmetricKey, symmetricKey: (0, uint8arrays_1.toString)(symmetricKey, 'hex') }
1357
1817
  ];
1358
1818
  }())
1359
- : [await Cheqd.publishStatusList2021((0, uint8arrays_1.fromString)(bitstring, 'base64url'), statusListMetadata, options?.publishOptions), undefined];
1819
+ : (await async function () {
1820
+ // validate encoding, if provided
1821
+ if (options?.publishOptions?.statusListEncoding && !Object.values(cheqd_did_provider_js_1.DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
1822
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
1823
+ }
1824
+ // validate validUntil, if provided
1825
+ if (options?.publishOptions?.statusListValidUntil) {
1826
+ // validate validUntil as string
1827
+ if (typeof options?.publishOptions?.statusListValidUntil !== 'string')
1828
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)');
1829
+ // validate validUntil as date
1830
+ if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil)))
1831
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)');
1832
+ // validate validUntil as future date
1833
+ if (new Date(options?.publishOptions?.statusListValidUntil) < new Date())
1834
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)');
1835
+ // validate validUntil towards validFrom
1836
+ if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom))
1837
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)');
1838
+ }
1839
+ // define status list content
1840
+ const content = {
1841
+ StatusList2021: {
1842
+ type: publishedList.StatusList2021.type,
1843
+ encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options.publishOptions.statusListEncoding),
1844
+ validFrom: publishedList.StatusList2021.validFrom,
1845
+ validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
1846
+ },
1847
+ metadata: {
1848
+ encoding: options?.publishOptions?.statusListEncoding || publishedList.metadata.encoding,
1849
+ encrypted: false,
1850
+ }
1851
+ };
1852
+ // return tuple of publish result and encryption relevant metadata
1853
+ return [
1854
+ await Cheqd.publishStatusList2021((0, uint8arrays_1.fromString)(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
1855
+ undefined
1856
+ ];
1857
+ }());
1360
1858
  // early exit, if publish failed
1361
1859
  if (!scoped[0])
1362
1860
  throw new Error('[did-provider-cheqd]: revocation: Failed to publish status list 2021');
@@ -1367,7 +1865,7 @@ class Cheqd {
1367
1865
  return {
1368
1866
  revoked: true,
1369
1867
  published: topArgs?.publish ? true : undefined,
1370
- statusList: topArgs?.returnUpdatedStatusList ? bitstring : undefined,
1868
+ statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credential) : undefined,
1371
1869
  encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await (0, helpers_js_1.blobToHexString)(published?.[1]?.encryptedString) : undefined,
1372
1870
  encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? published?.[1]?.encryptedSymmetricKey : undefined,
1373
1871
  symmetricKey: topArgs?.returnSymmetricKey ? published?.[1]?.symmetricKey : undefined,
@@ -1375,56 +1873,264 @@ class Cheqd {
1375
1873
  };
1376
1874
  }
1377
1875
  catch (error) {
1378
- // silent fail + early exit, optimised for parallelisation, use with Promise.allSettled
1876
+ // silent fail + early exit
1379
1877
  console.error(error);
1380
1878
  return { revoked: false, error: error };
1381
1879
  }
1382
1880
  }
1881
+ static async revokeCredentials(credentials, options) {
1882
+ // validate credentials - case: empty
1883
+ if (!credentials.length || credentials.length === 0)
1884
+ throw new Error('[did-provider-cheqd]: revocation: No credentials provided');
1885
+ // validate credentials - case: consistent issuer
1886
+ if (credentials.map((credential) => {
1887
+ return (credential.issuer.id)
1888
+ ? credential.issuer.id
1889
+ : credential.issuer;
1890
+ }).filter((value, _, self) => value && value !== self[0]).length > 0)
1891
+ throw new Error('[did-provider-cheqd]: revocation: Credentials must be issued by the same issuer');
1892
+ // validate credentials - case: status list index
1893
+ if (credentials.map((credential) => credential.credentialStatus.statusListIndex).filter((value, index, self) => self.indexOf(value) !== index).length > 0)
1894
+ throw new Error('[did-provider-cheqd]: revocation: Credentials must have unique status list index');
1895
+ // validate credentials - case: status purpose
1896
+ if (!credentials.every((credential) => credential.credentialStatus?.statusPurpose === 'revocation'))
1897
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status purpose');
1898
+ // validate credentials - case: status list id
1899
+ const remote = credentials[0].credentialStatus?.id
1900
+ ? credentials[0].credentialStatus.id.split('#')[0]
1901
+ : (function () {
1902
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list id');
1903
+ }());
1904
+ // validate credentials - case: status list id format
1905
+ if (!exports.RemoteListPattern.test(remote))
1906
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list id format: expected: https://<optional_subdomain>.<sld>.<tld>/1.0/identifiers/<did:cheqd:<namespace>:<method_specific_id>>?resourceName=<resource_name>&resourceType=<resource_type>');
1907
+ if (!credentials.every((credential) => {
1908
+ return credential.credentialStatus.id.split('#')[0] === remote;
1909
+ }))
1910
+ throw new Error('[did-provider-cheqd]: revocation: Credentials must belong to the same status list');
1911
+ // validate credentials - case: status list type
1912
+ if (!credentials.every((credential) => credential.credentialStatus?.type === 'StatusList2021Entry'))
1913
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list type');
1914
+ try {
1915
+ // fetch status list 2021
1916
+ const publishedList = (await Cheqd.fetchStatusList2021(credentials[0]));
1917
+ // early return, if encrypted and no decryption key provided
1918
+ if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey)
1919
+ throw new Error('[did-provider-cheqd]: revocation: symmetricKey is required, if status list 2021 is encrypted');
1920
+ // fetch status list 2021 inscribed in credential
1921
+ const statusList2021 = options?.topArgs?.fetchList
1922
+ ? (await async function () {
1923
+ // if not encrypted, return bitstring
1924
+ if (!publishedList.metadata.encrypted)
1925
+ return publishedList.metadata.encoding === 'base64url'
1926
+ ? publishedList.StatusList2021.encodedList
1927
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
1928
+ // otherwise, decrypt and return bitstring
1929
+ const scopedRawBlob = await (0, helpers_js_1.toBlob)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding));
1930
+ // decrypt
1931
+ return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
1932
+ }())
1933
+ : (await async function () {
1934
+ // transcode to base64url, if needed
1935
+ const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
1936
+ ? publishedList.StatusList2021.encodedList
1937
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
1938
+ // if status list 2021 is not fetched, read from file
1939
+ if (options?.statusListFile) {
1940
+ // if not encrypted, return bitstring
1941
+ if (!publishedList.metadata.encrypted) {
1942
+ // construct encoded status list
1943
+ const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
1944
+ // validate against published list
1945
+ if (encoded !== publishedListTranscoded)
1946
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
1947
+ // return encoded
1948
+ return encoded;
1949
+ }
1950
+ // otherwise, decrypt and return bitstring
1951
+ const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
1952
+ // decrypt
1953
+ const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
1954
+ // validate against published list
1955
+ if (decrypted !== publishedListTranscoded)
1956
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
1957
+ // return decrypted
1958
+ return decrypted;
1959
+ }
1960
+ if (!options?.statusListInlineBitstring)
1961
+ throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
1962
+ // validate against published list
1963
+ if (options?.statusListInlineBitstring !== publishedListTranscoded)
1964
+ throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
1965
+ // otherwise, read from inline bitstring
1966
+ return options?.statusListInlineBitstring;
1967
+ }());
1968
+ // parse status list 2021
1969
+ const statusList = await vc_status_list_1.StatusList.decode({ encodedList: statusList2021 });
1970
+ // initiate bulk revocation
1971
+ const revoked = await Promise.allSettled(credentials.map((credential) => {
1972
+ return async function () {
1973
+ // early return, if no credential status
1974
+ if (!credential.credentialStatus)
1975
+ return { revoked: false };
1976
+ // early exit, if credential is already revoked
1977
+ if (statusList.getStatus(Number(credential.credentialStatus.statusListIndex)))
1978
+ return { revoked: false };
1979
+ // update revocation status
1980
+ statusList.setStatus(Number(credential.credentialStatus.statusListIndex), true);
1981
+ // return revocation status
1982
+ return { revoked: true };
1983
+ }();
1984
+ }));
1985
+ // revert bulk ops, if some failed
1986
+ if (revoked.some((result) => result.status === 'fulfilled' && !result.value.revoked))
1987
+ throw new Error(`[did-provider-cheqd]: revocation: Bulk revocation failed: already revoked credentials in revocation bundle: raw log: ${JSON.stringify(revoked.map((result) => ({ revoked: result.status === 'fulfilled' ? result.value.revoked : false })))}`);
1988
+ // set in-memory status list ref
1989
+ const bitstring = await statusList.encode();
1990
+ // cast top-level args
1991
+ const topArgs = options?.topArgs;
1992
+ // write status list 2021 to file, if provided
1993
+ if (topArgs?.writeToFile) {
1994
+ await Cheqd.writeFile((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options?.statusListFile);
1995
+ }
1996
+ // publish status list 2021, if provided
1997
+ const published = topArgs?.publish
1998
+ ? (await async function () {
1999
+ // fetch status list 2021 metadata
2000
+ const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(credentials[0]);
2001
+ // publish status list 2021 as new version
2002
+ const scoped = topArgs.publishEncrypted
2003
+ ? (await async function () {
2004
+ // instantiate dkg-threshold client, in which case lit-protocol is used
2005
+ const lit = await lit_protocol_js_1.LitProtocol.create({
2006
+ chain: options?.topArgs?.bootstrapOptions?.chain,
2007
+ litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
2008
+ });
2009
+ // encrypt
2010
+ const { encryptedString, encryptedSymmetricKey, symmetricKey } = await lit.encrypt(bitstring, options?.topArgs?.encryptionOptions?.unifiedAccessControlConditions, true);
2011
+ // return tuple of publish result and encryption relevant metadata
2012
+ return [
2013
+ await Cheqd.publishStatusList2021(new Uint8Array(await encryptedString.arrayBuffer()), statusListMetadata, options?.publishOptions),
2014
+ { encryptedString, encryptedSymmetricKey, symmetricKey: (0, uint8arrays_1.toString)(symmetricKey, 'hex') }
2015
+ ];
2016
+ }())
2017
+ : (await async function () {
2018
+ // validate encoding, if provided
2019
+ if (options?.publishOptions?.statusListEncoding && !Object.values(cheqd_did_provider_js_1.DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
2020
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
2021
+ }
2022
+ // validate validUntil, if provided
2023
+ if (options?.publishOptions?.statusListValidUntil) {
2024
+ // validate validUntil as string
2025
+ if (typeof options?.publishOptions?.statusListValidUntil !== 'string')
2026
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)');
2027
+ // validate validUntil as date
2028
+ if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil)))
2029
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)');
2030
+ // validate validUntil as future date
2031
+ if (new Date(options?.publishOptions?.statusListValidUntil) < new Date())
2032
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)');
2033
+ // validate validUntil towards validFrom
2034
+ if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom))
2035
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)');
2036
+ }
2037
+ // define status list content
2038
+ const content = {
2039
+ StatusList2021: {
2040
+ type: publishedList.StatusList2021.type,
2041
+ encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options.publishOptions.statusListEncoding),
2042
+ validFrom: publishedList.StatusList2021.validFrom,
2043
+ validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
2044
+ },
2045
+ metadata: {
2046
+ encoding: options?.publishOptions?.statusListEncoding || publishedList.metadata.encoding,
2047
+ encrypted: false,
2048
+ }
2049
+ };
2050
+ // return tuple of publish result and encryption relevant metadata
2051
+ return [
2052
+ await Cheqd.publishStatusList2021((0, uint8arrays_1.fromString)(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
2053
+ undefined
2054
+ ];
2055
+ }());
2056
+ // early exit, if publish failed
2057
+ if (!scoped[0])
2058
+ throw new Error('[did-provider-cheqd]: revocation: Failed to publish status list 2021');
2059
+ // return publish result
2060
+ return scoped;
2061
+ }())
2062
+ : undefined;
2063
+ return {
2064
+ revoked: revoked.map((result) => result.status === 'fulfilled' ? result.value.revoked : false),
2065
+ published: topArgs?.publish ? true : undefined,
2066
+ statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credentials[0]) : undefined,
2067
+ encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await (0, helpers_js_1.blobToHexString)(published?.[1]?.encryptedString) : undefined,
2068
+ encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? published?.[1]?.encryptedSymmetricKey : undefined,
2069
+ symmetricKey: topArgs?.returnSymmetricKey ? published?.[1]?.symmetricKey : undefined,
2070
+ resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(credentials[0]) : undefined
2071
+ };
2072
+ }
2073
+ catch (error) {
2074
+ // silent fail + early exit
2075
+ console.error(error);
2076
+ return { revoked: [], error: error };
2077
+ }
2078
+ }
1383
2079
  static async suspendCredential(credential, options) {
1384
2080
  try {
1385
2081
  // validate status purpose
1386
2082
  if (credential?.credentialStatus?.statusPurpose !== 'suspension')
1387
2083
  throw new Error('[did-provider-cheqd]: suspension: Invalid status purpose');
1388
- // fetch status list 2021 metadata
1389
- const metadata = (await Cheqd.fetchStatusList2021Metadata(credential));
1390
- // detect if encrypted
1391
- const isEncrypted = function () {
1392
- switch (metadata.mediaType) {
1393
- case 'application/octet-stream':
1394
- return true;
1395
- case 'application/gzip':
1396
- return false;
1397
- default:
1398
- throw new Error(`[did-provider-cheqd]: suspension: Unsupported media type: ${metadata.mediaType}`);
1399
- }
1400
- }();
2084
+ // fetch status list 2021
2085
+ const publishedList = (await Cheqd.fetchStatusList2021(credential));
1401
2086
  // early return, if encrypted and no decryption key provided
1402
- if (isEncrypted && !options?.topArgs?.symmetricKey)
2087
+ if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey)
1403
2088
  throw new Error('[did-provider-cheqd]: suspension: symmetricKey is required, if status list 2021 is encrypted');
1404
2089
  // fetch status list 2021 inscribed in credential
1405
2090
  const statusList2021 = options?.topArgs?.fetchList
1406
2091
  ? (await async function () {
1407
2092
  // if not encrypted, return bitstring
1408
- if (!isEncrypted)
1409
- return await Cheqd.fetchStatusList2021(credential);
2093
+ if (!publishedList.metadata.encrypted)
2094
+ return publishedList.metadata.encoding === 'base64url'
2095
+ ? publishedList.StatusList2021.encodedList
2096
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
1410
2097
  // otherwise, decrypt and return bitstring
1411
2098
  const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.fetchStatusList2021(credential, true));
1412
2099
  // decrypt
1413
2100
  return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
1414
2101
  }())
1415
2102
  : (await async function () {
2103
+ // transcode to base64url, if needed
2104
+ const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
2105
+ ? publishedList.StatusList2021.encodedList
2106
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
1416
2107
  // if status list 2021 is not fetched, read from file
1417
2108
  if (options?.statusListFile) {
1418
2109
  // if not encrypted, return bitstring
1419
- if (!isEncrypted)
1420
- return new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
2110
+ if (!publishedList.metadata.encrypted) {
2111
+ // construct encoded status list
2112
+ const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
2113
+ // validate against published list
2114
+ if (encoded !== publishedListTranscoded)
2115
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
2116
+ // return encoded
2117
+ return encoded;
2118
+ }
1421
2119
  // otherwise, decrypt and return bitstring
1422
2120
  const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
1423
2121
  // decrypt
1424
- return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
2122
+ const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
2123
+ // validate against published list
2124
+ if (decrypted !== publishedListTranscoded)
2125
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
2126
+ // return decrypted
2127
+ return decrypted;
1425
2128
  }
1426
2129
  if (!options?.statusListInlineBitstring)
1427
- throw new Error('[did-provider-cheqd]: suspension: statusListInlineBitstring is required, if statusListFile is not provided');
2130
+ throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
2131
+ // validate against published list
2132
+ if (options?.statusListInlineBitstring !== publishedListTranscoded)
2133
+ throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
1428
2134
  // otherwise, read from inline bitstring
1429
2135
  return options?.statusListInlineBitstring;
1430
2136
  }());
@@ -1464,7 +2170,45 @@ class Cheqd {
1464
2170
  { encryptedString, encryptedSymmetricKey, symmetricKey: (0, uint8arrays_1.toString)(symmetricKey, 'hex') }
1465
2171
  ];
1466
2172
  }())
1467
- : [await Cheqd.publishStatusList2021((0, uint8arrays_1.fromString)(bitstring, 'base64url'), statusListMetadata, options?.publishOptions), undefined];
2173
+ : (await async function () {
2174
+ // validate encoding, if provided
2175
+ if (options?.publishOptions?.statusListEncoding && !Object.values(cheqd_did_provider_js_1.DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
2176
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
2177
+ }
2178
+ // validate validUntil, if provided
2179
+ if (options?.publishOptions?.statusListValidUntil) {
2180
+ // validate validUntil as string
2181
+ if (typeof options?.publishOptions?.statusListValidUntil !== 'string')
2182
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)');
2183
+ // validate validUntil as date
2184
+ if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil)))
2185
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)');
2186
+ // validate validUntil as future date
2187
+ if (new Date(options?.publishOptions?.statusListValidUntil) < new Date())
2188
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)');
2189
+ // validate validUntil towards validFrom
2190
+ if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom))
2191
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)');
2192
+ }
2193
+ // define status list content
2194
+ const content = {
2195
+ StatusList2021: {
2196
+ type: publishedList.StatusList2021.type,
2197
+ encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options.publishOptions.statusListEncoding),
2198
+ validFrom: publishedList.StatusList2021.validFrom,
2199
+ validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
2200
+ },
2201
+ metadata: {
2202
+ encoding: options?.publishOptions?.statusListEncoding || publishedList.metadata.encoding,
2203
+ encrypted: false,
2204
+ }
2205
+ };
2206
+ // return tuple of publish result and encryption relevant metadata
2207
+ return [
2208
+ await Cheqd.publishStatusList2021((0, uint8arrays_1.fromString)(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
2209
+ undefined
2210
+ ];
2211
+ }());
1468
2212
  // early exit, if publish failed
1469
2213
  if (!scoped[0])
1470
2214
  throw new Error('[did-provider-cheqd]: suspension: Failed to publish status list 2021');
@@ -1475,7 +2219,7 @@ class Cheqd {
1475
2219
  return {
1476
2220
  suspended: true,
1477
2221
  published: topArgs?.publish ? true : undefined,
1478
- statusList: topArgs?.returnUpdatedStatusList ? bitstring : undefined,
2222
+ statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credential) : undefined,
1479
2223
  encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await (0, helpers_js_1.blobToHexString)(published?.[1]?.encryptedString) : undefined,
1480
2224
  encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? published?.[1]?.encryptedSymmetricKey : undefined,
1481
2225
  symmetricKey: topArgs?.returnSymmetricKey ? published?.[1]?.symmetricKey : undefined,
@@ -1483,56 +2227,264 @@ class Cheqd {
1483
2227
  };
1484
2228
  }
1485
2229
  catch (error) {
1486
- // silent fail + early exit, optimised for parallelisation, use with Promise.allSettled
2230
+ // silent fail + early exit
1487
2231
  console.error(error);
1488
2232
  return { suspended: false, error: error };
1489
2233
  }
1490
2234
  }
2235
+ static async suspendCredentials(credentials, options) {
2236
+ // validate credentials - case: empty
2237
+ if (!credentials.length || credentials.length === 0)
2238
+ throw new Error('[did-provider-cheqd]: suspension: No credentials provided');
2239
+ // validate credentials - case: consistent issuer
2240
+ if (credentials.map((credential) => {
2241
+ return (credential.issuer.id)
2242
+ ? credential.issuer.id
2243
+ : credential.issuer;
2244
+ }).filter((value, _, self) => value && value !== self[0]).length > 0)
2245
+ throw new Error('[did-provider-cheqd]: suspension: Credentials must be issued by the same issuer');
2246
+ // validate credentials - case: status list index
2247
+ if (credentials.map((credential) => credential.credentialStatus.statusListIndex).filter((value, index, self) => self.indexOf(value) !== index).length > 0)
2248
+ throw new Error('[did-provider-cheqd]: suspension: Credentials must have unique status list index');
2249
+ // validate credentials - case: status purpose
2250
+ if (!credentials.every((credential) => credential.credentialStatus?.statusPurpose === 'suspension'))
2251
+ throw new Error('[did-provider-cheqd]: suspension: Invalid status purpose');
2252
+ // validate credentials - case: status list id
2253
+ const remote = credentials[0].credentialStatus?.id
2254
+ ? credentials[0].credentialStatus.id.split('#')[0]
2255
+ : (function () {
2256
+ throw new Error('[did-provider-cheqd]: suspension: Invalid status list id');
2257
+ }());
2258
+ // validate credentials - case: status list id format
2259
+ if (!exports.RemoteListPattern.test(remote))
2260
+ throw new Error('[did-provider-cheqd]: suspension: Invalid status list id format: expected: https://<optional_subdomain>.<sld>.<tld>/1.0/identifiers/<did:cheqd:<namespace>:<method_specific_id>>?resourceName=<resource_name>&resourceType=<resource_type>');
2261
+ if (!credentials.every((credential) => {
2262
+ return credential.credentialStatus.id.split('#')[0] === remote;
2263
+ }))
2264
+ throw new Error('[did-provider-cheqd]: suspension: Credentials must belong to the same status list');
2265
+ // validate credentials - case: status list type
2266
+ if (!credentials.every((credential) => credential.credentialStatus?.type === 'StatusList2021Entry'))
2267
+ throw new Error('[did-provider-cheqd]: suspension: Invalid status list type');
2268
+ try {
2269
+ // fetch status list 2021
2270
+ const publishedList = (await Cheqd.fetchStatusList2021(credentials[0]));
2271
+ // early return, if encrypted and no decryption key provided
2272
+ if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey)
2273
+ throw new Error('[did-provider-cheqd]: suspension: symmetricKey is required, if status list 2021 is encrypted');
2274
+ // fetch status list 2021 inscribed in credential
2275
+ const statusList2021 = options?.topArgs?.fetchList
2276
+ ? (await async function () {
2277
+ // if not encrypted, return bitstring
2278
+ if (!publishedList.metadata.encrypted)
2279
+ return publishedList.metadata.encoding === 'base64url'
2280
+ ? publishedList.StatusList2021.encodedList
2281
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
2282
+ // otherwise, decrypt and return bitstring
2283
+ const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.fetchStatusList2021(credentials[0], true));
2284
+ // decrypt
2285
+ return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
2286
+ }())
2287
+ : (await async function () {
2288
+ // transcode to base64url, if needed
2289
+ const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
2290
+ ? publishedList.StatusList2021.encodedList
2291
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
2292
+ // if status list 2021 is not fetched, read from file
2293
+ if (options?.statusListFile) {
2294
+ // if not encrypted, return bitstring
2295
+ if (!publishedList.metadata.encrypted) {
2296
+ // construct encoded status list
2297
+ const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
2298
+ // validate against published list
2299
+ if (encoded !== publishedListTranscoded)
2300
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
2301
+ // return encoded
2302
+ return encoded;
2303
+ }
2304
+ // otherwise, decrypt and return bitstring
2305
+ const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
2306
+ // decrypt
2307
+ const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
2308
+ // validate against published list
2309
+ if (decrypted !== publishedListTranscoded)
2310
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
2311
+ // return decrypted
2312
+ return decrypted;
2313
+ }
2314
+ if (!options?.statusListInlineBitstring)
2315
+ throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
2316
+ // validate against published list
2317
+ if (options?.statusListInlineBitstring !== publishedListTranscoded)
2318
+ throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
2319
+ // otherwise, read from inline bitstring
2320
+ return options?.statusListInlineBitstring;
2321
+ }());
2322
+ // parse status list 2021
2323
+ const statusList = await vc_status_list_1.StatusList.decode({ encodedList: statusList2021 });
2324
+ // initiate bulk suspension
2325
+ const suspended = await Promise.allSettled(credentials.map((credential) => {
2326
+ return async function () {
2327
+ // early return, if no credential status
2328
+ if (!credential.credentialStatus)
2329
+ return { suspended: false };
2330
+ // early exit, if credential is already suspended
2331
+ if (statusList.getStatus(Number(credential.credentialStatus.statusListIndex)))
2332
+ return { suspended: false };
2333
+ // update suspension status
2334
+ statusList.setStatus(Number(credential.credentialStatus.statusListIndex), true);
2335
+ // return suspension status
2336
+ return { suspended: true };
2337
+ }();
2338
+ }));
2339
+ // revert bulk ops, if some failed
2340
+ if (suspended.some((result) => result.status === 'fulfilled' && !result.value.suspended))
2341
+ throw new Error(`[did-provider-cheqd]: suspension: Bulk suspension failed: already suspended credentials in suspension bundle: raw log: ${JSON.stringify(suspended.map((result) => ({ suspended: result.status === 'fulfilled' ? result.value.suspended : false })))}`);
2342
+ // set in-memory status list ref
2343
+ const bitstring = await statusList.encode();
2344
+ // cast top-level args
2345
+ const topArgs = options?.topArgs;
2346
+ // write status list 2021 to file, if provided
2347
+ if (topArgs?.writeToFile) {
2348
+ await Cheqd.writeFile((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options?.statusListFile);
2349
+ }
2350
+ // publish status list 2021, if provided
2351
+ const published = topArgs?.publish
2352
+ ? (await async function () {
2353
+ // fetch status list 2021 metadata
2354
+ const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(credentials[0]);
2355
+ // publish status list 2021 as new version
2356
+ const scoped = topArgs.publishEncrypted
2357
+ ? (await async function () {
2358
+ // instantiate dkg-threshold client, in which case lit-protocol is used
2359
+ const lit = await lit_protocol_js_1.LitProtocol.create({
2360
+ chain: options?.topArgs?.bootstrapOptions?.chain,
2361
+ litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
2362
+ });
2363
+ // encrypt
2364
+ const { encryptedString, encryptedSymmetricKey, symmetricKey } = await lit.encrypt(bitstring, options?.topArgs?.encryptionOptions?.unifiedAccessControlConditions, true);
2365
+ // return tuple of publish result and encryption relevant metadata
2366
+ return [
2367
+ await Cheqd.publishStatusList2021(new Uint8Array(await encryptedString.arrayBuffer()), statusListMetadata, options?.publishOptions),
2368
+ { encryptedString, encryptedSymmetricKey, symmetricKey: (0, uint8arrays_1.toString)(symmetricKey, 'hex') }
2369
+ ];
2370
+ }())
2371
+ : (await async function () {
2372
+ // validate encoding, if provided
2373
+ if (options?.publishOptions?.statusListEncoding && !Object.values(cheqd_did_provider_js_1.DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
2374
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
2375
+ }
2376
+ // validate validUntil, if provided
2377
+ if (options?.publishOptions?.statusListValidUntil) {
2378
+ // validate validUntil as string
2379
+ if (typeof options?.publishOptions?.statusListValidUntil !== 'string')
2380
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)');
2381
+ // validate validUntil as date
2382
+ if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil)))
2383
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)');
2384
+ // validate validUntil as future date
2385
+ if (new Date(options?.publishOptions?.statusListValidUntil) < new Date())
2386
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)');
2387
+ // validate validUntil towards validFrom
2388
+ if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom))
2389
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)');
2390
+ }
2391
+ // define status list content
2392
+ const content = {
2393
+ StatusList2021: {
2394
+ type: publishedList.StatusList2021.type,
2395
+ encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options.publishOptions.statusListEncoding),
2396
+ validFrom: publishedList.StatusList2021.validFrom,
2397
+ validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
2398
+ },
2399
+ metadata: {
2400
+ encoding: options?.publishOptions?.statusListEncoding || publishedList.metadata.encoding,
2401
+ encrypted: false,
2402
+ }
2403
+ };
2404
+ // return tuple of publish result and encryption relevant metadata
2405
+ return [
2406
+ await Cheqd.publishStatusList2021((0, uint8arrays_1.fromString)(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
2407
+ undefined
2408
+ ];
2409
+ }());
2410
+ // early exit, if publish failed
2411
+ if (!scoped[0])
2412
+ throw new Error('[did-provider-cheqd]: suspension: Failed to publish status list 2021');
2413
+ // return publish result
2414
+ return scoped;
2415
+ }())
2416
+ : undefined;
2417
+ return {
2418
+ suspended: suspended.map((result) => result.status === 'fulfilled' ? result.value.suspended : false),
2419
+ published: topArgs?.publish ? true : undefined,
2420
+ statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credentials[0]) : undefined,
2421
+ encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await (0, helpers_js_1.blobToHexString)(published?.[1]?.encryptedString) : undefined,
2422
+ encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? published?.[1]?.encryptedSymmetricKey : undefined,
2423
+ symmetricKey: topArgs?.returnSymmetricKey ? published?.[1]?.symmetricKey : undefined,
2424
+ resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(credentials[0]) : undefined
2425
+ };
2426
+ }
2427
+ catch (error) {
2428
+ // silent fail + early exit
2429
+ console.error(error);
2430
+ return { suspended: [], error: error };
2431
+ }
2432
+ }
1491
2433
  static async unsuspendCredential(credential, options) {
1492
2434
  try {
1493
2435
  // validate status purpose
1494
2436
  if (credential?.credentialStatus?.statusPurpose !== 'suspension')
1495
2437
  throw new Error('[did-provider-cheqd]: unsuspension: Invalid status purpose');
1496
- // fetch status list 2021 metadata
1497
- const metadata = (await Cheqd.fetchStatusList2021Metadata(credential));
1498
- // detect if encrypted
1499
- const isEncrypted = function () {
1500
- switch (metadata.mediaType) {
1501
- case 'application/octet-stream':
1502
- return true;
1503
- case 'application/gzip':
1504
- return false;
1505
- default:
1506
- throw new Error(`[did-provider-cheqd]: unsuspension: Unsupported media type: ${metadata.mediaType}`);
1507
- }
1508
- }();
2438
+ // fetch status list 2021
2439
+ const publishedList = (await Cheqd.fetchStatusList2021(credential));
1509
2440
  // early return, if encrypted and no decryption key provided
1510
- if (isEncrypted && !options?.topArgs?.symmetricKey)
1511
- throw new Error('[did-provider-cheqd]: unsuspension: symmetricKey is required, if status list 2021 is encrypted');
2441
+ if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey)
2442
+ throw new Error('[did-provider-cheqd]: suspension: symmetricKey is required, if status list 2021 is encrypted');
1512
2443
  // fetch status list 2021 inscribed in credential
1513
2444
  const statusList2021 = options?.topArgs?.fetchList
1514
2445
  ? (await async function () {
1515
2446
  // if not encrypted, return bitstring
1516
- if (!isEncrypted)
1517
- return await Cheqd.fetchStatusList2021(credential);
2447
+ if (!publishedList.metadata.encrypted)
2448
+ return publishedList.metadata.encoding === 'base64url'
2449
+ ? publishedList.StatusList2021.encodedList
2450
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
1518
2451
  // otherwise, decrypt and return bitstring
1519
2452
  const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.fetchStatusList2021(credential, true));
1520
2453
  // decrypt
1521
2454
  return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
1522
2455
  }())
1523
2456
  : (await async function () {
2457
+ // transcode to base64url, if needed
2458
+ const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
2459
+ ? publishedList.StatusList2021.encodedList
2460
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
1524
2461
  // if status list 2021 is not fetched, read from file
1525
2462
  if (options?.statusListFile) {
1526
2463
  // if not encrypted, return bitstring
1527
- if (!isEncrypted)
1528
- return new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
2464
+ if (!publishedList.metadata.encrypted) {
2465
+ // construct encoded status list
2466
+ const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
2467
+ // validate against published list
2468
+ if (encoded !== publishedListTranscoded)
2469
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
2470
+ // return encoded
2471
+ return encoded;
2472
+ }
1529
2473
  // otherwise, decrypt and return bitstring
1530
2474
  const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
1531
2475
  // decrypt
1532
- return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
2476
+ const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
2477
+ // validate against published list
2478
+ if (decrypted !== publishedListTranscoded)
2479
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
2480
+ // return decrypted
2481
+ return decrypted;
1533
2482
  }
1534
2483
  if (!options?.statusListInlineBitstring)
1535
- throw new Error('[did-provider-cheqd]: unsuspension: statusListInlineBitstring is required, if statusListFile is not provided');
2484
+ throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
2485
+ // validate against published list
2486
+ if (options?.statusListInlineBitstring !== publishedListTranscoded)
2487
+ throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
1536
2488
  // otherwise, read from inline bitstring
1537
2489
  return options?.statusListInlineBitstring;
1538
2490
  }());
@@ -1572,7 +2524,45 @@ class Cheqd {
1572
2524
  { encryptedString, encryptedSymmetricKey, symmetricKey: (0, uint8arrays_1.toString)(symmetricKey, 'hex') }
1573
2525
  ];
1574
2526
  }())
1575
- : [await Cheqd.publishStatusList2021((0, uint8arrays_1.fromString)(bitstring, 'base64url'), statusListMetadata, options?.publishOptions), undefined];
2527
+ : (await async function () {
2528
+ // validate encoding, if provided
2529
+ if (options?.publishOptions?.statusListEncoding && !Object.values(cheqd_did_provider_js_1.DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
2530
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
2531
+ }
2532
+ // validate validUntil, if provided
2533
+ if (options?.publishOptions?.statusListValidUntil) {
2534
+ // validate validUntil as string
2535
+ if (typeof options?.publishOptions?.statusListValidUntil !== 'string')
2536
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)');
2537
+ // validate validUntil as date
2538
+ if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil)))
2539
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)');
2540
+ // validate validUntil as future date
2541
+ if (new Date(options?.publishOptions?.statusListValidUntil) < new Date())
2542
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)');
2543
+ // validate validUntil towards validFrom
2544
+ if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom))
2545
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)');
2546
+ }
2547
+ // define status list content
2548
+ const content = {
2549
+ StatusList2021: {
2550
+ type: publishedList.StatusList2021.type,
2551
+ encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options.publishOptions.statusListEncoding),
2552
+ validFrom: publishedList.StatusList2021.validFrom,
2553
+ validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
2554
+ },
2555
+ metadata: {
2556
+ encoding: options?.publishOptions?.statusListEncoding || publishedList.metadata.encoding,
2557
+ encrypted: false,
2558
+ }
2559
+ };
2560
+ // return tuple of publish result and encryption relevant metadata
2561
+ return [
2562
+ await Cheqd.publishStatusList2021((0, uint8arrays_1.fromString)(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
2563
+ undefined
2564
+ ];
2565
+ }());
1576
2566
  // early exit, if publish failed
1577
2567
  if (!scoped[0])
1578
2568
  throw new Error('[did-provider-cheqd]: unsuspension: Failed to publish status list 2021');
@@ -1583,7 +2573,7 @@ class Cheqd {
1583
2573
  return {
1584
2574
  unsuspended: true,
1585
2575
  published: topArgs?.publish ? true : undefined,
1586
- statusList: topArgs?.returnUpdatedStatusList ? bitstring : undefined,
2576
+ statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credential) : undefined,
1587
2577
  encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await (0, helpers_js_1.blobToHexString)(published?.[1]?.encryptedString) : undefined,
1588
2578
  encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? published?.[1]?.encryptedSymmetricKey : undefined,
1589
2579
  symmetricKey: topArgs?.returnSymmetricKey ? published?.[1]?.symmetricKey : undefined,
@@ -1591,40 +2581,229 @@ class Cheqd {
1591
2581
  };
1592
2582
  }
1593
2583
  catch (error) {
1594
- // silent fail + early exit, optimised for parallelisation, use with Promise.allSettled
2584
+ // silent fail + early exit
1595
2585
  console.error(error);
1596
2586
  return { unsuspended: false, error: error };
1597
2587
  }
1598
2588
  }
2589
+ static async unsuspendCredentials(credentials, options) {
2590
+ // validate credentials - case: empty
2591
+ if (!credentials.length || credentials.length === 0)
2592
+ throw new Error('[did-provider-cheqd]: unsuspension: No credentials provided');
2593
+ // validate credentials - case: consistent issuer
2594
+ if (credentials.map((credential) => {
2595
+ return (credential.issuer.id)
2596
+ ? credential.issuer.id
2597
+ : credential.issuer;
2598
+ }).filter((value, _, self) => value && value !== self[0]).length > 0)
2599
+ throw new Error('[did-provider-cheqd]: unsuspension: Credentials must be issued by the same issuer');
2600
+ // validate credentials - case: status list index
2601
+ if (credentials.map((credential) => credential.credentialStatus.statusListIndex).filter((value, index, self) => self.indexOf(value) !== index).length > 0)
2602
+ throw new Error('[did-provider-cheqd]: unsuspension: Credentials must have unique status list index');
2603
+ // validate credentials - case: status purpose
2604
+ if (!credentials.every((credential) => credential.credentialStatus?.statusPurpose === 'suspension'))
2605
+ throw new Error('[did-provider-cheqd]: unsuspension: Invalid status purpose');
2606
+ // validate credentials - case: status list id
2607
+ const remote = credentials[0].credentialStatus?.id
2608
+ ? credentials[0].credentialStatus.id.split('#')[0]
2609
+ : (function () {
2610
+ throw new Error('[did-provider-cheqd]: unsuspension: Invalid status list id');
2611
+ }());
2612
+ // validate credentials - case: status list id format
2613
+ if (!exports.RemoteListPattern.test(remote))
2614
+ throw new Error('[did-provider-cheqd]: unsuspension: Invalid status list id format: expected: https://<optional_subdomain>.<sld>.<tld>/1.0/identifiers/<did:cheqd:<namespace>:<method_specific_id>>?resourceName=<resource_name>&resourceType=<resource_type>');
2615
+ if (!credentials.every((credential) => {
2616
+ return credential.credentialStatus.id.split('#')[0] === remote;
2617
+ }))
2618
+ throw new Error('[did-provider-cheqd]: unsuspension: Credentials must belong to the same status list');
2619
+ // validate credentials - case: status list type
2620
+ if (!credentials.every((credential) => credential.credentialStatus?.type === 'StatusList2021Entry'))
2621
+ throw new Error('[did-provider-cheqd]: unsuspension: Invalid status list type');
2622
+ try {
2623
+ // fetch status list 2021
2624
+ const publishedList = (await Cheqd.fetchStatusList2021(credentials[0]));
2625
+ // early return, if encrypted and no decryption key provided
2626
+ if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey)
2627
+ throw new Error('[did-provider-cheqd]: unsuspension: symmetricKey is required, if status list 2021 is encrypted');
2628
+ // fetch status list 2021 inscribed in credential
2629
+ const statusList2021 = options?.topArgs?.fetchList
2630
+ ? (await async function () {
2631
+ // if not encrypted, return bitstring
2632
+ if (!publishedList.metadata.encrypted)
2633
+ return publishedList.metadata.encoding === 'base64url'
2634
+ ? publishedList.StatusList2021.encodedList
2635
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
2636
+ // otherwise, decrypt and return bitstring
2637
+ const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.fetchStatusList2021(credentials[0], true));
2638
+ // decrypt
2639
+ return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
2640
+ }())
2641
+ : (await async function () {
2642
+ // transcode to base64url, if needed
2643
+ const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
2644
+ ? publishedList.StatusList2021.encodedList
2645
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
2646
+ // if status list 2021 is not fetched, read from file
2647
+ if (options?.statusListFile) {
2648
+ // if not encrypted, return bitstring
2649
+ if (!publishedList.metadata.encrypted) {
2650
+ // construct encoded status list
2651
+ const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
2652
+ // validate against published list
2653
+ if (encoded !== publishedListTranscoded)
2654
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
2655
+ // return encoded
2656
+ return encoded;
2657
+ }
2658
+ // otherwise, decrypt and return bitstring
2659
+ const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
2660
+ // decrypt
2661
+ const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
2662
+ // validate against published list
2663
+ if (decrypted !== publishedListTranscoded)
2664
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
2665
+ // return decrypted
2666
+ return decrypted;
2667
+ }
2668
+ if (!options?.statusListInlineBitstring)
2669
+ throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
2670
+ // validate against published list
2671
+ if (options?.statusListInlineBitstring !== publishedListTranscoded)
2672
+ throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
2673
+ // otherwise, read from inline bitstring
2674
+ return options?.statusListInlineBitstring;
2675
+ }());
2676
+ // parse status list 2021
2677
+ const statusList = await vc_status_list_1.StatusList.decode({ encodedList: statusList2021 });
2678
+ // initiate bulk unsuspension
2679
+ const unsuspended = await Promise.allSettled(credentials.map((credential) => {
2680
+ return async function () {
2681
+ // early return, if no credential status
2682
+ if (!credential.credentialStatus)
2683
+ return { unsuspended: false };
2684
+ // early exit, if credential is already unsuspended
2685
+ if (!statusList.getStatus(Number(credential.credentialStatus.statusListIndex)))
2686
+ return { unsuspended: true };
2687
+ // update unsuspension status
2688
+ statusList.setStatus(Number(credential.credentialStatus.statusListIndex), false);
2689
+ // return unsuspension status
2690
+ return { unsuspended: true };
2691
+ }();
2692
+ }));
2693
+ // revert bulk ops, if some failed
2694
+ if (unsuspended.some((result) => result.status === 'fulfilled' && !result.value.unsuspended))
2695
+ throw new Error(`[did-provider-cheqd]: unsuspension: Bulk unsuspension failed: already unsuspended credentials in unsuspension bundle: raw log: ${JSON.stringify(unsuspended.map((result) => ({ unsuspended: result.status === 'fulfilled' ? result.value.unsuspended : false })))}`);
2696
+ // set in-memory status list ref
2697
+ const bitstring = await statusList.encode();
2698
+ // cast top-level args
2699
+ const topArgs = options?.topArgs;
2700
+ // write status list 2021 to file, if provided
2701
+ if (topArgs?.writeToFile) {
2702
+ await Cheqd.writeFile((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options?.statusListFile);
2703
+ }
2704
+ // publish status list 2021, if provided
2705
+ const published = topArgs?.publish
2706
+ ? (await async function () {
2707
+ // fetch status list 2021 metadata
2708
+ const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(credentials[0]);
2709
+ // publish status list 2021 as new version
2710
+ const scoped = topArgs.publishEncrypted
2711
+ ? (await async function () {
2712
+ // instantiate dkg-threshold client, in which case lit-protocol is used
2713
+ const lit = await lit_protocol_js_1.LitProtocol.create({
2714
+ chain: options?.topArgs?.bootstrapOptions?.chain,
2715
+ litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
2716
+ });
2717
+ // encrypt
2718
+ const { encryptedString, encryptedSymmetricKey, symmetricKey } = await lit.encrypt(bitstring, options?.topArgs?.encryptionOptions?.unifiedAccessControlConditions, true);
2719
+ // return tuple of publish result and encryption relevant metadata
2720
+ return [
2721
+ await Cheqd.publishStatusList2021(new Uint8Array(await encryptedString.arrayBuffer()), statusListMetadata, options?.publishOptions),
2722
+ { encryptedString, encryptedSymmetricKey, symmetricKey: (0, uint8arrays_1.toString)(symmetricKey, 'hex') }
2723
+ ];
2724
+ }())
2725
+ : (await async function () {
2726
+ // validate encoding, if provided
2727
+ if (options?.publishOptions?.statusListEncoding && !Object.values(cheqd_did_provider_js_1.DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
2728
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
2729
+ }
2730
+ // validate validUntil, if provided
2731
+ if (options?.publishOptions?.statusListValidUntil) {
2732
+ // validate validUntil as string
2733
+ if (typeof options?.publishOptions?.statusListValidUntil !== 'string')
2734
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)');
2735
+ // validate validUntil as date
2736
+ if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil)))
2737
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)');
2738
+ // validate validUntil as future date
2739
+ if (new Date(options?.publishOptions?.statusListValidUntil) < new Date())
2740
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)');
2741
+ // validate validUntil towards validFrom
2742
+ if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom))
2743
+ throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)');
2744
+ }
2745
+ // define status list content
2746
+ const content = {
2747
+ StatusList2021: {
2748
+ type: publishedList.StatusList2021.type,
2749
+ encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options.publishOptions.statusListEncoding),
2750
+ validFrom: publishedList.StatusList2021.validFrom,
2751
+ validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
2752
+ },
2753
+ metadata: {
2754
+ encoding: options?.publishOptions?.statusListEncoding || publishedList.metadata.encoding,
2755
+ encrypted: false,
2756
+ }
2757
+ };
2758
+ // return tuple of publish result and encryption relevant metadata
2759
+ return [
2760
+ await Cheqd.publishStatusList2021((0, uint8arrays_1.fromString)(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
2761
+ undefined
2762
+ ];
2763
+ }());
2764
+ // early exit, if publish failed
2765
+ if (!scoped[0])
2766
+ throw new Error('[did-provider-cheqd]: unsuspension: Failed to publish status list 2021');
2767
+ // return publish result
2768
+ return scoped;
2769
+ }())
2770
+ : undefined;
2771
+ return {
2772
+ unsuspended: unsuspended.map((result) => result.status === 'fulfilled' ? result.value.unsuspended : false),
2773
+ published: topArgs?.publish ? true : undefined,
2774
+ statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credentials[0]) : undefined,
2775
+ encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await (0, helpers_js_1.blobToHexString)(published?.[1]?.encryptedString) : undefined,
2776
+ encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? published?.[1]?.encryptedSymmetricKey : undefined,
2777
+ symmetricKey: topArgs?.returnSymmetricKey ? published?.[1]?.symmetricKey : undefined,
2778
+ resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(credentials[0]) : undefined
2779
+ };
2780
+ }
2781
+ catch (error) {
2782
+ // silent fail + early exit
2783
+ console.error(error);
2784
+ return { unsuspended: [], error: error };
2785
+ }
2786
+ }
1599
2787
  static async checkRevoked(credential, options = { fetchList: true }) {
1600
2788
  // validate status purpose
1601
2789
  if (credential.credentialStatus?.statusPurpose !== 'revocation') {
1602
2790
  throw new Error(`[did-provider-cheqd]: revocation: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
1603
2791
  }
1604
- // fetch status list 2021 metadata
1605
- const metadata = (await Cheqd.fetchStatusList2021Metadata(credential));
1606
- // detect if encrypted
1607
- const isEncrypted = function () {
1608
- switch (metadata.mediaType) {
1609
- case 'application/octet-stream':
1610
- return true;
1611
- case 'application/gzip':
1612
- return false;
1613
- default:
1614
- throw new Error(`[did-provider-cheqd]: revocation: Unsupported media type: ${metadata.mediaType}`);
1615
- }
1616
- }();
2792
+ // fetch status list 2021
2793
+ const publishedList = (await Cheqd.fetchStatusList2021(credential));
1617
2794
  // early return, if encrypted and decryption key is not provided
1618
- if (isEncrypted && !options?.topArgs?.encryptedSymmetricKey)
2795
+ if (publishedList.metadata.encrypted && !options?.topArgs?.encryptedSymmetricKey)
1619
2796
  throw new Error('[did-provider-cheqd]: revocation: encryptedSymmetricKey is required, if status list 2021 is encrypted');
1620
2797
  // fetch status list 2021 inscribed in credential
1621
2798
  const statusList2021 = options?.topArgs?.fetchList
1622
2799
  ? (await async function () {
1623
2800
  // if not encrypted, return bitstring
1624
- if (!isEncrypted)
1625
- return await Cheqd.fetchStatusList2021(credential);
2801
+ if (!publishedList.metadata.encrypted)
2802
+ return publishedList.metadata.encoding === 'base64url'
2803
+ ? publishedList.StatusList2021.encodedList
2804
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
1626
2805
  // otherwise, decrypt and return bitstring
1627
- const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.fetchStatusList2021(credential, true));
2806
+ const scopedRawBlob = await (0, helpers_js_1.toBlob)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding));
1628
2807
  // instantiate dkg-threshold client, in which case lit-protocol is used
1629
2808
  const lit = await lit_protocol_js_1.LitProtocol.create({
1630
2809
  chain: options?.topArgs?.bootstrapOptions?.chain,
@@ -1634,23 +2813,37 @@ class Cheqd {
1634
2813
  return await lit.decrypt(scopedRawBlob, options?.topArgs?.encryptedSymmetricKey, options?.topArgs?.decryptionOptions?.unifiedAccessControlConditions);
1635
2814
  }())
1636
2815
  : (await async function () {
2816
+ // transcode to base64url, if needed
2817
+ const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
2818
+ ? publishedList.StatusList2021.encodedList
2819
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
1637
2820
  // if status list 2021 is not fetched, read from file
1638
2821
  if (options?.statusListFile) {
1639
2822
  // if not encrypted, return bitstring
1640
- if (!isEncrypted)
1641
- return new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
2823
+ if (!publishedList.metadata.encrypted) {
2824
+ // construct encoded status list
2825
+ const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
2826
+ // validate against published list
2827
+ if (encoded !== publishedListTranscoded)
2828
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
2829
+ // return encoded
2830
+ return encoded;
2831
+ }
1642
2832
  // otherwise, decrypt and return bitstring
1643
2833
  const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
1644
- // instantiate dkg-threshold client, in which case lit-protocol is used
1645
- const lit = await lit_protocol_js_1.LitProtocol.create({
1646
- chain: options?.topArgs?.bootstrapOptions?.chain,
1647
- litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
1648
- });
1649
2834
  // decrypt
1650
- return await lit.decrypt(scopedRawBlob, options?.topArgs?.encryptedSymmetricKey, options?.topArgs?.decryptionOptions?.unifiedAccessControlConditions);
2835
+ const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
2836
+ // validate against published list
2837
+ if (decrypted !== publishedListTranscoded)
2838
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
2839
+ // return decrypted
2840
+ return decrypted;
1651
2841
  }
1652
2842
  if (!options?.statusListInlineBitstring)
1653
- throw new Error(' [did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
2843
+ throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
2844
+ // validate against published list
2845
+ if (options?.statusListInlineBitstring !== publishedListTranscoded)
2846
+ throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
1654
2847
  // otherwise, read from inline bitstring
1655
2848
  return options?.statusListInlineBitstring;
1656
2849
  }());
@@ -1664,30 +2857,21 @@ class Cheqd {
1664
2857
  if (credential.credentialStatus?.statusPurpose !== 'suspension') {
1665
2858
  throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
1666
2859
  }
1667
- // fetch status list 2021 metadata
1668
- const metadata = (await Cheqd.fetchStatusList2021Metadata(credential));
1669
- // detect if encrypted
1670
- const isEncrypted = function () {
1671
- switch (metadata.mediaType) {
1672
- case 'application/octet-stream':
1673
- return true;
1674
- case 'application/gzip':
1675
- return false;
1676
- default:
1677
- throw new Error(`[did-provider-cheqd]: suspension: Unsupported media type: ${metadata.mediaType}`);
1678
- }
1679
- }();
2860
+ // fetch status list 2021
2861
+ const publishedList = (await Cheqd.fetchStatusList2021(credential));
1680
2862
  // early return, if encrypted and decryption key is not provided
1681
- if (isEncrypted && !options?.topArgs?.encryptedSymmetricKey)
1682
- throw new Error('[did-provider-cheqd]: suspension: encryptedSymmetricKey is required, if status list 2021 is encrypted');
2863
+ if (publishedList.metadata.encrypted && !options?.topArgs?.encryptedSymmetricKey)
2864
+ throw new Error('[did-provider-cheqd]: revocation: encryptedSymmetricKey is required, if status list 2021 is encrypted');
1683
2865
  // fetch status list 2021 inscribed in credential
1684
2866
  const statusList2021 = options?.topArgs?.fetchList
1685
2867
  ? (await async function () {
1686
2868
  // if not encrypted, return bitstring
1687
- if (!isEncrypted)
1688
- return await Cheqd.fetchStatusList2021(credential);
2869
+ if (!publishedList.metadata.encrypted)
2870
+ return publishedList.metadata.encoding === 'base64url'
2871
+ ? publishedList.StatusList2021.encodedList
2872
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
1689
2873
  // otherwise, decrypt and return bitstring
1690
- const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.fetchStatusList2021(credential, true));
2874
+ const scopedRawBlob = await (0, helpers_js_1.toBlob)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding));
1691
2875
  // instantiate dkg-threshold client, in which case lit-protocol is used
1692
2876
  const lit = await lit_protocol_js_1.LitProtocol.create({
1693
2877
  chain: options?.topArgs?.bootstrapOptions?.chain,
@@ -1697,23 +2881,37 @@ class Cheqd {
1697
2881
  return await lit.decrypt(scopedRawBlob, options?.topArgs?.encryptedSymmetricKey, options?.topArgs?.decryptionOptions?.unifiedAccessControlConditions);
1698
2882
  }())
1699
2883
  : (await async function () {
2884
+ // transcode to base64url, if needed
2885
+ const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
2886
+ ? publishedList.StatusList2021.encodedList
2887
+ : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
1700
2888
  // if status list 2021 is not fetched, read from file
1701
2889
  if (options?.statusListFile) {
1702
2890
  // if not encrypted, return bitstring
1703
- if (!isEncrypted)
1704
- return new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
2891
+ if (!publishedList.metadata.encrypted) {
2892
+ // construct encoded status list
2893
+ const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
2894
+ // validate against published list
2895
+ if (encoded !== publishedListTranscoded)
2896
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
2897
+ // return encoded
2898
+ return encoded;
2899
+ }
1705
2900
  // otherwise, decrypt and return bitstring
1706
2901
  const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
1707
- // instantiate dkg-threshold client, in which case lit-protocol is used
1708
- const lit = await lit_protocol_js_1.LitProtocol.create({
1709
- chain: options?.topArgs?.bootstrapOptions?.chain,
1710
- litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
1711
- });
1712
2902
  // decrypt
1713
- return await lit.decrypt(scopedRawBlob, options?.topArgs?.encryptedSymmetricKey, options?.topArgs?.decryptionOptions?.unifiedAccessControlConditions);
2903
+ const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
2904
+ // validate against published list
2905
+ if (decrypted !== publishedListTranscoded)
2906
+ throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
2907
+ // return decrypted
2908
+ return decrypted;
1714
2909
  }
1715
2910
  if (!options?.statusListInlineBitstring)
1716
- throw new Error(' [did-provider-cheqd]: suspension: statusListInlineBitstring is required, if statusListFile is not provided');
2911
+ throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
2912
+ // validate against published list
2913
+ if (options?.statusListInlineBitstring !== publishedListTranscoded)
2914
+ throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
1717
2915
  // otherwise, read from inline bitstring
1718
2916
  return options?.statusListInlineBitstring;
1719
2917
  }());
@@ -1729,10 +2927,11 @@ class Cheqd {
1729
2927
  id: options?.resourceId || (0, uuid_1.v4)(),
1730
2928
  name: statusList2021Metadata.resourceName,
1731
2929
  version: options?.resourceVersion || new Date().toISOString(),
1732
- resourceType: 'StatusList2021',
2930
+ alsoKnownAs: options?.resourceAlsoKnownAs || [],
2931
+ resourceType: statusList2021Metadata.resourceType,
1733
2932
  data: statusList2021Raw
1734
2933
  };
1735
- return await options.context.agent[CreateStatusList2021MethodName]({
2934
+ return await options.context.agent[BroadcastStatusList2021MethodName]({
1736
2935
  kms: (await options.context.agent.keyManagerGetKeyManagementSystems())[0],
1737
2936
  payload,
1738
2937
  network: statusList2021Metadata.resourceURI.split(':')[2],
@@ -1750,25 +2949,26 @@ class Cheqd {
1750
2949
  // validate credential status list status purpose
1751
2950
  if (credential.credentialStatus.statusPurpose !== 'revocation' && credential.credentialStatus.statusPurpose !== 'suspension')
1752
2951
  throw new Error('[did-provider-cheqd]: fetch status list: Credential status purpose is not valid');
1753
- // validate credential status list status list credential
1754
- if (!credential.credentialStatus.statusListCredential)
1755
- throw new Error('[did-provider-cheqd]: fetch status list: Credential status list credential is not present');
1756
2952
  // fetch status list 2021
1757
- const raw = await (await fetch(credential.credentialStatus.statusListCredential)).arrayBuffer();
2953
+ const content = await (await fetch(credential.credentialStatus.id.split('#')[0])).json();
1758
2954
  // return raw if requested
1759
- if (returnRaw)
1760
- return new Uint8Array(raw);
1761
- // otherwise, parse to bitstring and return
1762
- const bitstring = (0, uint8arrays_1.toString)(new Uint8Array(raw), 'base64url');
1763
- return bitstring;
2955
+ if (returnRaw) {
2956
+ return (0, uint8arrays_1.fromString)(content.StatusList2021.encodedList, content.metadata.encoding);
2957
+ }
2958
+ // otherwise, return content
2959
+ return content;
1764
2960
  }
1765
2961
  static async fetchStatusList2021Metadata(credential) {
1766
2962
  // get base url
1767
- const baseUrl = new URL(credential.credentialStatus?.statusListCredential);
2963
+ const baseUrl = new URL(credential.credentialStatus.id.split('#')[0]);
1768
2964
  // get resource name
1769
2965
  const resourceName = baseUrl.searchParams.get('resourceName');
2966
+ // get resource type
2967
+ const resourceType = baseUrl.searchParams.get('resourceType');
1770
2968
  // unset resource name
1771
2969
  baseUrl.searchParams.delete('resourceName');
2970
+ // unset resource type
2971
+ baseUrl.searchParams.delete('resourceType');
1772
2972
  // construct metadata url
1773
2973
  const metadataUrl = `${baseUrl.toString()}/metadata`;
1774
2974
  // fetch collection metadata
@@ -1777,7 +2977,7 @@ class Cheqd {
1777
2977
  if (!collectionMetadata?.contentStream?.linkedResourceMetadata)
1778
2978
  throw new Error('[did-provider-cheqd]: fetch status list metadata: No linked resources found');
1779
2979
  // find relevant resources by resource name
1780
- const resourceVersioning = collectionMetadata.contentStream.linkedResourceMetadata.filter((resource) => resource.resourceName === resourceName);
2980
+ const resourceVersioning = collectionMetadata.contentStream.linkedResourceMetadata.filter((resource) => resource.resourceName === resourceName && resource.resourceType === resourceType);
1781
2981
  // early exit if no relevant resources
1782
2982
  if (!resourceVersioning.length || resourceVersioning.length === 0)
1783
2983
  throw new Error(`[did-provider-cheqd]: fetch status list metadata: No relevant resources found by resource name ${resourceName}`);
@@ -1828,6 +3028,16 @@ class Cheqd {
1828
3028
  });
1829
3029
  });
1830
3030
  }
3031
+ static async decodeCredentialJWT(jwt) {
3032
+ const decodedCredential = (0, did_jwt_1.decodeJWT)(jwt);
3033
+ // validate credential payload
3034
+ if (!decodedCredential.payload)
3035
+ throw new Error('[did-provider-cheqd]: decode jwt: decodedCredential.payload is required');
3036
+ // validate credential payload vc property as VerifiableCredential
3037
+ if (!decodedCredential.payload.vc)
3038
+ throw new Error('[did-provider-cheqd]: decode jwt: decodedCredential.payload.vc is required');
3039
+ return decodedCredential.payload.vc;
3040
+ }
1831
3041
  }
1832
3042
  exports.Cheqd = Cheqd;
1833
3043
  //# sourceMappingURL=ICheqd.js.map