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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,6 +1051,7 @@ class Cheqd {
972
1051
  if (!verificationResult.verified) {
973
1052
  return { verified: false, error: verificationResult.error };
974
1053
  }
1054
+ // if jwt credential, decode it
975
1055
  const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
976
1056
  // verify credential status
977
1057
  switch (credential.credentialStatus?.statusPurpose) {
@@ -1022,6 +1102,7 @@ class Cheqd {
1022
1102
  return { verified: true };
1023
1103
  }
1024
1104
  async CheckCredentialStatusWithStatusList2021(args, context) {
1105
+ // if jwt credential, decode it
1025
1106
  const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
1026
1107
  switch (credential.credentialStatus?.statusPurpose) {
1027
1108
  case 'revocation':
@@ -1037,6 +1118,51 @@ class Cheqd {
1037
1118
  }
1038
1119
  }
1039
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
1040
1166
  const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
1041
1167
  // validate status purpose
1042
1168
  if (credential.credentialStatus?.statusPurpose !== 'revocation') {
@@ -1064,18 +1190,142 @@ class Cheqd {
1064
1190
  topArgs: args,
1065
1191
  publishOptions: {
1066
1192
  context,
1193
+ statusListEncoding: args?.options?.statusListEncoding,
1194
+ statusListValidUntil: args?.options?.statusListValidUntil,
1067
1195
  resourceId: args?.options?.resourceId,
1068
1196
  resourceVersion: args?.options?.resourceVersion,
1197
+ resourceAlsoKnownAs: args?.options?.alsoKnownAs,
1069
1198
  signInputs: args?.options?.signInputs,
1070
1199
  fee: args?.options?.fee
1071
1200
  }
1072
1201
  });
1073
1202
  }
1074
1203
  async RevokeBulkCredentialsWithStatusList2021(args, context) {
1075
- // TODO: implement
1076
- 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
+ });
1077
1282
  }
1078
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
1079
1329
  const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
1080
1330
  // validate status purpose
1081
1331
  if (credential.credentialStatus?.statusPurpose !== 'suspension') {
@@ -1103,18 +1353,142 @@ class Cheqd {
1103
1353
  topArgs: args,
1104
1354
  publishOptions: {
1105
1355
  context,
1356
+ statusListEncoding: args?.options?.statusListEncoding,
1357
+ statusListValidUntil: args?.options?.statusListValidUntil,
1106
1358
  resourceId: args?.options?.resourceId,
1107
1359
  resourceVersion: args?.options?.resourceVersion,
1360
+ resourceAlsoKnownAs: args?.options?.alsoKnownAs,
1108
1361
  signInputs: args?.options?.signInputs,
1109
1362
  fee: args?.options?.fee
1110
1363
  }
1111
1364
  });
1112
1365
  }
1113
1366
  async SuspendBulkCredentialsWithStatusList2021(args, context) {
1114
- // TODO: implement
1115
- 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
+ });
1116
1445
  }
1117
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
1118
1492
  const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
1119
1493
  // validate status purpose
1120
1494
  if (credential.credentialStatus?.statusPurpose !== 'suspension') {
@@ -1142,16 +1516,95 @@ class Cheqd {
1142
1516
  topArgs: args,
1143
1517
  publishOptions: {
1144
1518
  context,
1519
+ statusListEncoding: args?.options?.statusListEncoding,
1520
+ statusListValidUntil: args?.options?.statusListValidUntil,
1145
1521
  resourceId: args?.options?.resourceId,
1146
1522
  resourceVersion: args?.options?.resourceVersion,
1523
+ resourceAlsoKnownAs: args?.options?.alsoKnownAs,
1147
1524
  signInputs: args?.options?.signInputs,
1148
1525
  fee: args?.options?.fee
1149
1526
  }
1150
1527
  });
1151
1528
  }
1152
1529
  async UnsuspendBulkCredentialsWithStatusList2021(args, context) {
1153
- // TODO: implement
1154
- 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
+ });
1155
1608
  }
1156
1609
  async TransactVerifierPaysIssuer(args, context) {
1157
1610
  try {
@@ -1274,46 +1727,56 @@ class Cheqd {
1274
1727
  // validate status purpose
1275
1728
  if (credential?.credentialStatus?.statusPurpose !== 'revocation')
1276
1729
  throw new Error('[did-provider-cheqd]: revocation: Invalid status purpose');
1277
- // fetch status list 2021 metadata
1278
- const metadata = (await Cheqd.fetchStatusList2021Metadata(credential));
1279
- // detect if encrypted
1280
- const isEncrypted = function () {
1281
- switch (metadata.mediaType) {
1282
- case 'application/octet-stream':
1283
- return true;
1284
- case 'application/gzip':
1285
- return false;
1286
- default:
1287
- throw new Error(`[did-provider-cheqd]: revocation: Unsupported media type: ${metadata.mediaType}`);
1288
- }
1289
- }();
1730
+ // fetch status list 2021
1731
+ const publishedList = (await Cheqd.fetchStatusList2021(credential));
1290
1732
  // early return, if encrypted and no decryption key provided
1291
- if (isEncrypted && !options?.topArgs?.symmetricKey)
1733
+ if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey)
1292
1734
  throw new Error('[did-provider-cheqd]: revocation: symmetricKey is required, if status list 2021 is encrypted');
1293
1735
  // fetch status list 2021 inscribed in credential
1294
1736
  const statusList2021 = options?.topArgs?.fetchList
1295
1737
  ? (await async function () {
1296
1738
  // if not encrypted, return bitstring
1297
- if (!isEncrypted)
1298
- 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');
1299
1743
  // otherwise, decrypt and return bitstring
1300
- 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));
1301
1745
  // decrypt
1302
1746
  return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
1303
1747
  }())
1304
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');
1305
1753
  // if status list 2021 is not fetched, read from file
1306
1754
  if (options?.statusListFile) {
1307
1755
  // if not encrypted, return bitstring
1308
- if (!isEncrypted)
1309
- 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
+ }
1310
1765
  // otherwise, decrypt and return bitstring
1311
1766
  const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
1312
1767
  // decrypt
1313
- 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;
1314
1774
  }
1315
1775
  if (!options?.statusListInlineBitstring)
1316
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');
1317
1780
  // otherwise, read from inline bitstring
1318
1781
  return options?.statusListInlineBitstring;
1319
1782
  }());
@@ -1353,7 +1816,45 @@ class Cheqd {
1353
1816
  { encryptedString, encryptedSymmetricKey, symmetricKey: (0, uint8arrays_1.toString)(symmetricKey, 'hex') }
1354
1817
  ];
1355
1818
  }())
1356
- : [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
+ }());
1357
1858
  // early exit, if publish failed
1358
1859
  if (!scoped[0])
1359
1860
  throw new Error('[did-provider-cheqd]: revocation: Failed to publish status list 2021');
@@ -1364,7 +1865,7 @@ class Cheqd {
1364
1865
  return {
1365
1866
  revoked: true,
1366
1867
  published: topArgs?.publish ? true : undefined,
1367
- statusList: topArgs?.returnUpdatedStatusList ? bitstring : undefined,
1868
+ statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credential) : undefined,
1368
1869
  encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await (0, helpers_js_1.blobToHexString)(published?.[1]?.encryptedString) : undefined,
1369
1870
  encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? published?.[1]?.encryptedSymmetricKey : undefined,
1370
1871
  symmetricKey: topArgs?.returnSymmetricKey ? published?.[1]?.symmetricKey : undefined,
@@ -1372,56 +1873,264 @@ class Cheqd {
1372
1873
  };
1373
1874
  }
1374
1875
  catch (error) {
1375
- // silent fail + early exit, optimised for parallelisation, use with Promise.allSettled
1876
+ // silent fail + early exit
1376
1877
  console.error(error);
1377
1878
  return { revoked: false, error: error };
1378
1879
  }
1379
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
+ }
1380
2079
  static async suspendCredential(credential, options) {
1381
2080
  try {
1382
2081
  // validate status purpose
1383
2082
  if (credential?.credentialStatus?.statusPurpose !== 'suspension')
1384
2083
  throw new Error('[did-provider-cheqd]: suspension: Invalid status purpose');
1385
- // fetch status list 2021 metadata
1386
- const metadata = (await Cheqd.fetchStatusList2021Metadata(credential));
1387
- // detect if encrypted
1388
- const isEncrypted = function () {
1389
- switch (metadata.mediaType) {
1390
- case 'application/octet-stream':
1391
- return true;
1392
- case 'application/gzip':
1393
- return false;
1394
- default:
1395
- throw new Error(`[did-provider-cheqd]: suspension: Unsupported media type: ${metadata.mediaType}`);
1396
- }
1397
- }();
2084
+ // fetch status list 2021
2085
+ const publishedList = (await Cheqd.fetchStatusList2021(credential));
1398
2086
  // early return, if encrypted and no decryption key provided
1399
- if (isEncrypted && !options?.topArgs?.symmetricKey)
2087
+ if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey)
1400
2088
  throw new Error('[did-provider-cheqd]: suspension: symmetricKey is required, if status list 2021 is encrypted');
1401
2089
  // fetch status list 2021 inscribed in credential
1402
2090
  const statusList2021 = options?.topArgs?.fetchList
1403
2091
  ? (await async function () {
1404
2092
  // if not encrypted, return bitstring
1405
- if (!isEncrypted)
1406
- 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');
1407
2097
  // otherwise, decrypt and return bitstring
1408
2098
  const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.fetchStatusList2021(credential, true));
1409
2099
  // decrypt
1410
2100
  return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
1411
2101
  }())
1412
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');
1413
2107
  // if status list 2021 is not fetched, read from file
1414
2108
  if (options?.statusListFile) {
1415
2109
  // if not encrypted, return bitstring
1416
- if (!isEncrypted)
1417
- 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
+ }
1418
2119
  // otherwise, decrypt and return bitstring
1419
2120
  const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
1420
2121
  // decrypt
1421
- 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;
1422
2128
  }
1423
2129
  if (!options?.statusListInlineBitstring)
1424
- 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');
1425
2134
  // otherwise, read from inline bitstring
1426
2135
  return options?.statusListInlineBitstring;
1427
2136
  }());
@@ -1461,7 +2170,45 @@ class Cheqd {
1461
2170
  { encryptedString, encryptedSymmetricKey, symmetricKey: (0, uint8arrays_1.toString)(symmetricKey, 'hex') }
1462
2171
  ];
1463
2172
  }())
1464
- : [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
+ }());
1465
2212
  // early exit, if publish failed
1466
2213
  if (!scoped[0])
1467
2214
  throw new Error('[did-provider-cheqd]: suspension: Failed to publish status list 2021');
@@ -1472,7 +2219,7 @@ class Cheqd {
1472
2219
  return {
1473
2220
  suspended: true,
1474
2221
  published: topArgs?.publish ? true : undefined,
1475
- statusList: topArgs?.returnUpdatedStatusList ? bitstring : undefined,
2222
+ statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credential) : undefined,
1476
2223
  encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await (0, helpers_js_1.blobToHexString)(published?.[1]?.encryptedString) : undefined,
1477
2224
  encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? published?.[1]?.encryptedSymmetricKey : undefined,
1478
2225
  symmetricKey: topArgs?.returnSymmetricKey ? published?.[1]?.symmetricKey : undefined,
@@ -1480,56 +2227,264 @@ class Cheqd {
1480
2227
  };
1481
2228
  }
1482
2229
  catch (error) {
1483
- // silent fail + early exit, optimised for parallelisation, use with Promise.allSettled
2230
+ // silent fail + early exit
1484
2231
  console.error(error);
1485
2232
  return { suspended: false, error: error };
1486
2233
  }
1487
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
+ }
1488
2433
  static async unsuspendCredential(credential, options) {
1489
2434
  try {
1490
2435
  // validate status purpose
1491
2436
  if (credential?.credentialStatus?.statusPurpose !== 'suspension')
1492
2437
  throw new Error('[did-provider-cheqd]: unsuspension: Invalid status purpose');
1493
- // fetch status list 2021 metadata
1494
- const metadata = (await Cheqd.fetchStatusList2021Metadata(credential));
1495
- // detect if encrypted
1496
- const isEncrypted = function () {
1497
- switch (metadata.mediaType) {
1498
- case 'application/octet-stream':
1499
- return true;
1500
- case 'application/gzip':
1501
- return false;
1502
- default:
1503
- throw new Error(`[did-provider-cheqd]: unsuspension: Unsupported media type: ${metadata.mediaType}`);
1504
- }
1505
- }();
2438
+ // fetch status list 2021
2439
+ const publishedList = (await Cheqd.fetchStatusList2021(credential));
1506
2440
  // early return, if encrypted and no decryption key provided
1507
- if (isEncrypted && !options?.topArgs?.symmetricKey)
1508
- 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');
1509
2443
  // fetch status list 2021 inscribed in credential
1510
2444
  const statusList2021 = options?.topArgs?.fetchList
1511
2445
  ? (await async function () {
1512
2446
  // if not encrypted, return bitstring
1513
- if (!isEncrypted)
1514
- 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');
1515
2451
  // otherwise, decrypt and return bitstring
1516
2452
  const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.fetchStatusList2021(credential, true));
1517
2453
  // decrypt
1518
2454
  return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
1519
2455
  }())
1520
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');
1521
2461
  // if status list 2021 is not fetched, read from file
1522
2462
  if (options?.statusListFile) {
1523
2463
  // if not encrypted, return bitstring
1524
- if (!isEncrypted)
1525
- 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
+ }
1526
2473
  // otherwise, decrypt and return bitstring
1527
2474
  const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
1528
2475
  // decrypt
1529
- 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;
1530
2482
  }
1531
2483
  if (!options?.statusListInlineBitstring)
1532
- 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');
1533
2488
  // otherwise, read from inline bitstring
1534
2489
  return options?.statusListInlineBitstring;
1535
2490
  }());
@@ -1569,7 +2524,45 @@ class Cheqd {
1569
2524
  { encryptedString, encryptedSymmetricKey, symmetricKey: (0, uint8arrays_1.toString)(symmetricKey, 'hex') }
1570
2525
  ];
1571
2526
  }())
1572
- : [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
+ }());
1573
2566
  // early exit, if publish failed
1574
2567
  if (!scoped[0])
1575
2568
  throw new Error('[did-provider-cheqd]: unsuspension: Failed to publish status list 2021');
@@ -1580,7 +2573,7 @@ class Cheqd {
1580
2573
  return {
1581
2574
  unsuspended: true,
1582
2575
  published: topArgs?.publish ? true : undefined,
1583
- statusList: topArgs?.returnUpdatedStatusList ? bitstring : undefined,
2576
+ statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credential) : undefined,
1584
2577
  encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await (0, helpers_js_1.blobToHexString)(published?.[1]?.encryptedString) : undefined,
1585
2578
  encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? published?.[1]?.encryptedSymmetricKey : undefined,
1586
2579
  symmetricKey: topArgs?.returnSymmetricKey ? published?.[1]?.symmetricKey : undefined,
@@ -1588,40 +2581,229 @@ class Cheqd {
1588
2581
  };
1589
2582
  }
1590
2583
  catch (error) {
1591
- // silent fail + early exit, optimised for parallelisation, use with Promise.allSettled
2584
+ // silent fail + early exit
1592
2585
  console.error(error);
1593
2586
  return { unsuspended: false, error: error };
1594
2587
  }
1595
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
+ }
1596
2787
  static async checkRevoked(credential, options = { fetchList: true }) {
1597
2788
  // validate status purpose
1598
2789
  if (credential.credentialStatus?.statusPurpose !== 'revocation') {
1599
2790
  throw new Error(`[did-provider-cheqd]: revocation: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
1600
2791
  }
1601
- // fetch status list 2021 metadata
1602
- const metadata = (await Cheqd.fetchStatusList2021Metadata(credential));
1603
- // detect if encrypted
1604
- const isEncrypted = function () {
1605
- switch (metadata.mediaType) {
1606
- case 'application/octet-stream':
1607
- return true;
1608
- case 'application/gzip':
1609
- return false;
1610
- default:
1611
- throw new Error(`[did-provider-cheqd]: revocation: Unsupported media type: ${metadata.mediaType}`);
1612
- }
1613
- }();
2792
+ // fetch status list 2021
2793
+ const publishedList = (await Cheqd.fetchStatusList2021(credential));
1614
2794
  // early return, if encrypted and decryption key is not provided
1615
- if (isEncrypted && !options?.topArgs?.encryptedSymmetricKey)
2795
+ if (publishedList.metadata.encrypted && !options?.topArgs?.encryptedSymmetricKey)
1616
2796
  throw new Error('[did-provider-cheqd]: revocation: encryptedSymmetricKey is required, if status list 2021 is encrypted');
1617
2797
  // fetch status list 2021 inscribed in credential
1618
2798
  const statusList2021 = options?.topArgs?.fetchList
1619
2799
  ? (await async function () {
1620
2800
  // if not encrypted, return bitstring
1621
- if (!isEncrypted)
1622
- 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');
1623
2805
  // otherwise, decrypt and return bitstring
1624
- 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));
1625
2807
  // instantiate dkg-threshold client, in which case lit-protocol is used
1626
2808
  const lit = await lit_protocol_js_1.LitProtocol.create({
1627
2809
  chain: options?.topArgs?.bootstrapOptions?.chain,
@@ -1631,23 +2813,37 @@ class Cheqd {
1631
2813
  return await lit.decrypt(scopedRawBlob, options?.topArgs?.encryptedSymmetricKey, options?.topArgs?.decryptionOptions?.unifiedAccessControlConditions);
1632
2814
  }())
1633
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');
1634
2820
  // if status list 2021 is not fetched, read from file
1635
2821
  if (options?.statusListFile) {
1636
2822
  // if not encrypted, return bitstring
1637
- if (!isEncrypted)
1638
- 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
+ }
1639
2832
  // otherwise, decrypt and return bitstring
1640
2833
  const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
1641
- // instantiate dkg-threshold client, in which case lit-protocol is used
1642
- const lit = await lit_protocol_js_1.LitProtocol.create({
1643
- chain: options?.topArgs?.bootstrapOptions?.chain,
1644
- litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
1645
- });
1646
2834
  // decrypt
1647
- 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;
1648
2841
  }
1649
2842
  if (!options?.statusListInlineBitstring)
1650
- 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');
1651
2847
  // otherwise, read from inline bitstring
1652
2848
  return options?.statusListInlineBitstring;
1653
2849
  }());
@@ -1661,30 +2857,21 @@ class Cheqd {
1661
2857
  if (credential.credentialStatus?.statusPurpose !== 'suspension') {
1662
2858
  throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
1663
2859
  }
1664
- // fetch status list 2021 metadata
1665
- const metadata = (await Cheqd.fetchStatusList2021Metadata(credential));
1666
- // detect if encrypted
1667
- const isEncrypted = function () {
1668
- switch (metadata.mediaType) {
1669
- case 'application/octet-stream':
1670
- return true;
1671
- case 'application/gzip':
1672
- return false;
1673
- default:
1674
- throw new Error(`[did-provider-cheqd]: suspension: Unsupported media type: ${metadata.mediaType}`);
1675
- }
1676
- }();
2860
+ // fetch status list 2021
2861
+ const publishedList = (await Cheqd.fetchStatusList2021(credential));
1677
2862
  // early return, if encrypted and decryption key is not provided
1678
- if (isEncrypted && !options?.topArgs?.encryptedSymmetricKey)
1679
- 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');
1680
2865
  // fetch status list 2021 inscribed in credential
1681
2866
  const statusList2021 = options?.topArgs?.fetchList
1682
2867
  ? (await async function () {
1683
2868
  // if not encrypted, return bitstring
1684
- if (!isEncrypted)
1685
- 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');
1686
2873
  // otherwise, decrypt and return bitstring
1687
- 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));
1688
2875
  // instantiate dkg-threshold client, in which case lit-protocol is used
1689
2876
  const lit = await lit_protocol_js_1.LitProtocol.create({
1690
2877
  chain: options?.topArgs?.bootstrapOptions?.chain,
@@ -1694,23 +2881,37 @@ class Cheqd {
1694
2881
  return await lit.decrypt(scopedRawBlob, options?.topArgs?.encryptedSymmetricKey, options?.topArgs?.decryptionOptions?.unifiedAccessControlConditions);
1695
2882
  }())
1696
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');
1697
2888
  // if status list 2021 is not fetched, read from file
1698
2889
  if (options?.statusListFile) {
1699
2890
  // if not encrypted, return bitstring
1700
- if (!isEncrypted)
1701
- 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
+ }
1702
2900
  // otherwise, decrypt and return bitstring
1703
2901
  const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
1704
- // instantiate dkg-threshold client, in which case lit-protocol is used
1705
- const lit = await lit_protocol_js_1.LitProtocol.create({
1706
- chain: options?.topArgs?.bootstrapOptions?.chain,
1707
- litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
1708
- });
1709
2902
  // decrypt
1710
- 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;
1711
2909
  }
1712
2910
  if (!options?.statusListInlineBitstring)
1713
- 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');
1714
2915
  // otherwise, read from inline bitstring
1715
2916
  return options?.statusListInlineBitstring;
1716
2917
  }());
@@ -1726,10 +2927,11 @@ class Cheqd {
1726
2927
  id: options?.resourceId || (0, uuid_1.v4)(),
1727
2928
  name: statusList2021Metadata.resourceName,
1728
2929
  version: options?.resourceVersion || new Date().toISOString(),
1729
- resourceType: 'StatusList2021',
2930
+ alsoKnownAs: options?.resourceAlsoKnownAs || [],
2931
+ resourceType: statusList2021Metadata.resourceType,
1730
2932
  data: statusList2021Raw
1731
2933
  };
1732
- return await options.context.agent[CreateStatusList2021MethodName]({
2934
+ return await options.context.agent[BroadcastStatusList2021MethodName]({
1733
2935
  kms: (await options.context.agent.keyManagerGetKeyManagementSystems())[0],
1734
2936
  payload,
1735
2937
  network: statusList2021Metadata.resourceURI.split(':')[2],
@@ -1747,25 +2949,26 @@ class Cheqd {
1747
2949
  // validate credential status list status purpose
1748
2950
  if (credential.credentialStatus.statusPurpose !== 'revocation' && credential.credentialStatus.statusPurpose !== 'suspension')
1749
2951
  throw new Error('[did-provider-cheqd]: fetch status list: Credential status purpose is not valid');
1750
- // validate credential status list status list credential
1751
- if (!credential.credentialStatus.statusListCredential)
1752
- throw new Error('[did-provider-cheqd]: fetch status list: Credential status list credential is not present');
1753
2952
  // fetch status list 2021
1754
- const raw = await (await fetch(credential.credentialStatus.statusListCredential)).arrayBuffer();
2953
+ const content = await (await fetch(credential.credentialStatus.id.split('#')[0])).json();
1755
2954
  // return raw if requested
1756
- if (returnRaw)
1757
- return new Uint8Array(raw);
1758
- // otherwise, parse to bitstring and return
1759
- const bitstring = (0, uint8arrays_1.toString)(new Uint8Array(raw), 'base64url');
1760
- 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;
1761
2960
  }
1762
2961
  static async fetchStatusList2021Metadata(credential) {
1763
2962
  // get base url
1764
- const baseUrl = new URL(credential.credentialStatus?.statusListCredential);
2963
+ const baseUrl = new URL(credential.credentialStatus.id.split('#')[0]);
1765
2964
  // get resource name
1766
2965
  const resourceName = baseUrl.searchParams.get('resourceName');
2966
+ // get resource type
2967
+ const resourceType = baseUrl.searchParams.get('resourceType');
1767
2968
  // unset resource name
1768
2969
  baseUrl.searchParams.delete('resourceName');
2970
+ // unset resource type
2971
+ baseUrl.searchParams.delete('resourceType');
1769
2972
  // construct metadata url
1770
2973
  const metadataUrl = `${baseUrl.toString()}/metadata`;
1771
2974
  // fetch collection metadata
@@ -1774,7 +2977,7 @@ class Cheqd {
1774
2977
  if (!collectionMetadata?.contentStream?.linkedResourceMetadata)
1775
2978
  throw new Error('[did-provider-cheqd]: fetch status list metadata: No linked resources found');
1776
2979
  // find relevant resources by resource name
1777
- const resourceVersioning = collectionMetadata.contentStream.linkedResourceMetadata.filter((resource) => resource.resourceName === resourceName);
2980
+ const resourceVersioning = collectionMetadata.contentStream.linkedResourceMetadata.filter((resource) => resource.resourceName === resourceName && resource.resourceType === resourceType);
1778
2981
  // early exit if no relevant resources
1779
2982
  if (!resourceVersioning.length || resourceVersioning.length === 0)
1780
2983
  throw new Error(`[did-provider-cheqd]: fetch status list metadata: No relevant resources found by resource name ${resourceName}`);