@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.
- package/build/cjs/agent/ICheqd.d.ts +175 -22
- package/build/cjs/agent/ICheqd.d.ts.map +1 -1
- package/build/cjs/agent/ICheqd.js +1424 -221
- package/build/cjs/agent/ICheqd.js.map +1 -1
- package/build/cjs/did-manager/cheqd-did-provider.d.ts +20 -3
- package/build/cjs/did-manager/cheqd-did-provider.d.ts.map +1 -1
- package/build/cjs/did-manager/cheqd-did-provider.js +15 -1
- package/build/cjs/did-manager/cheqd-did-provider.js.map +1 -1
- package/build/esm/agent/ICheqd.d.ts +175 -22
- package/build/esm/agent/ICheqd.d.ts.map +1 -1
- package/build/esm/agent/ICheqd.js +1424 -221
- package/build/esm/agent/ICheqd.js.map +1 -1
- package/build/esm/did-manager/cheqd-did-provider.d.ts +20 -3
- package/build/esm/did-manager/cheqd-did-provider.d.ts.map +1 -1
- package/build/esm/did-manager/cheqd-did-provider.js +14 -0
- package/build/esm/did-manager/cheqd-did-provider.js.map +1 -1
- package/build/tsconfig.cjs.tsbuildinfo +1 -1
- package/build/tsconfig.esm.tsbuildinfo +1 -1
- package/build/tsconfig.types.tsbuildinfo +1 -1
- package/build/types/agent/ICheqd.d.ts +175 -22
- package/build/types/agent/ICheqd.d.ts.map +1 -1
- package/build/types/did-manager/cheqd-did-provider.d.ts +20 -3
- package/build/types/did-manager/cheqd-did-provider.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/agent/ICheqd.ts +1820 -363
- package/src/did-manager/cheqd-did-provider.ts +26 -3
|
@@ -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
|
-
|
|
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
|
|
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": "
|
|
176
|
+
"type": "object"
|
|
146
177
|
}
|
|
147
178
|
},
|
|
148
|
-
"
|
|
149
|
-
"description": "
|
|
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
|
|
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
|
-
[
|
|
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.
|
|
640
|
-
throw new Error('[did-provider-cheqd]:
|
|
670
|
+
if (typeof args.issuerDid !== 'string' || !args.issuerDid) {
|
|
671
|
+
throw new Error('[did-provider-cheqd]: issuerDid is required');
|
|
641
672
|
}
|
|
642
|
-
if (typeof args.
|
|
643
|
-
throw new Error('[did-provider-cheqd]:
|
|
673
|
+
if (typeof args.statusListName !== 'string' || !args.statusListName) {
|
|
674
|
+
throw new Error('[did-provider-cheqd]: statusListName is required');
|
|
644
675
|
}
|
|
645
|
-
if (args
|
|
646
|
-
|
|
676
|
+
if (typeof args.statusPurpose !== 'string' || !args.statusPurpose) {
|
|
677
|
+
throw new Error('[did-provider-cheqd]: statusPurpose is required');
|
|
647
678
|
}
|
|
648
|
-
if (typeof args
|
|
649
|
-
|
|
679
|
+
if (typeof args.encrypted === 'undefined') {
|
|
680
|
+
throw new Error('[did-provider-cheqd]: encrypted is required');
|
|
650
681
|
}
|
|
651
|
-
//
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
683
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1076
|
-
|
|
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
|
-
//
|
|
1115
|
-
|
|
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
|
-
//
|
|
1154
|
-
|
|
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
|
|
1278
|
-
const
|
|
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 (
|
|
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 (!
|
|
1298
|
-
return
|
|
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)(
|
|
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 (!
|
|
1309
|
-
|
|
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
|
-
|
|
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
|
-
:
|
|
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 ?
|
|
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
|
|
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
|
|
1386
|
-
const
|
|
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 (
|
|
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 (!
|
|
1406
|
-
return
|
|
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 (!
|
|
1417
|
-
|
|
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
|
-
|
|
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]:
|
|
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
|
-
:
|
|
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 ?
|
|
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
|
|
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
|
|
1494
|
-
const
|
|
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 (
|
|
1508
|
-
throw new Error('[did-provider-cheqd]:
|
|
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 (!
|
|
1514
|
-
return
|
|
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 (!
|
|
1525
|
-
|
|
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
|
-
|
|
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]:
|
|
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
|
-
:
|
|
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 ?
|
|
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
|
|
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
|
|
1602
|
-
const
|
|
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 (
|
|
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 (!
|
|
1622
|
-
return
|
|
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)(
|
|
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 (!
|
|
1638
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
|
1665
|
-
const
|
|
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 (
|
|
1679
|
-
throw new Error('[did-provider-cheqd]:
|
|
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 (!
|
|
1685
|
-
return
|
|
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)(
|
|
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 (!
|
|
1701
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
2930
|
+
alsoKnownAs: options?.resourceAlsoKnownAs || [],
|
|
2931
|
+
resourceType: statusList2021Metadata.resourceType,
|
|
1730
2932
|
data: statusList2021Raw
|
|
1731
2933
|
};
|
|
1732
|
-
return await options.context.agent[
|
|
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
|
|
2953
|
+
const content = await (await fetch(credential.credentialStatus.id.split('#')[0])).json();
|
|
1755
2954
|
// return raw if requested
|
|
1756
|
-
if (returnRaw)
|
|
1757
|
-
return
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
return
|
|
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
|
|
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}`);
|