@cheqd/did-provider-cheqd 3.3.2 → 3.4.0-develop.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/agent/ICheqd.d.ts +178 -24
- package/build/cjs/agent/ICheqd.d.ts.map +1 -1
- package/build/cjs/agent/ICheqd.js +1458 -248
- 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 +178 -24
- package/build/esm/agent/ICheqd.d.ts.map +1 -1
- package/build/esm/agent/ICheqd.js +1458 -248
- 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 +178 -24
- 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 +1893 -428
- 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,18 +1051,20 @@ class Cheqd {
|
|
|
972
1051
|
if (!verificationResult.verified) {
|
|
973
1052
|
return { verified: false, error: verificationResult.error };
|
|
974
1053
|
}
|
|
1054
|
+
// if jwt credential, decode it
|
|
1055
|
+
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
|
|
975
1056
|
// verify credential status
|
|
976
|
-
switch (
|
|
1057
|
+
switch (credential.credentialStatus?.statusPurpose) {
|
|
977
1058
|
case 'revocation':
|
|
978
|
-
if (await Cheqd.checkRevoked(
|
|
1059
|
+
if (await Cheqd.checkRevoked(credential, { ...args.options, topArgs: args }))
|
|
979
1060
|
return { verified: false, revoked: true };
|
|
980
1061
|
return { verified: true, revoked: false };
|
|
981
1062
|
case 'suspension':
|
|
982
|
-
if (await Cheqd.checkSuspended(
|
|
1063
|
+
if (await Cheqd.checkSuspended(credential, { ...args.options, topArgs: args }))
|
|
983
1064
|
return { verified: false, suspended: true };
|
|
984
1065
|
return { verified: true, suspended: false };
|
|
985
1066
|
default:
|
|
986
|
-
throw new Error(`[did-provider-cheqd]: verify credential: Unsupported status purpose: ${
|
|
1067
|
+
throw new Error(`[did-provider-cheqd]: verify credential: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
|
|
987
1068
|
}
|
|
988
1069
|
}
|
|
989
1070
|
async VerifyPresentationWithStatusList2021(args, context) {
|
|
@@ -1003,16 +1084,8 @@ class Cheqd {
|
|
|
1003
1084
|
// verify credential(s) status(es)
|
|
1004
1085
|
for (let credential of args.presentation.verifiableCredential) {
|
|
1005
1086
|
// if jwt credential, decode it
|
|
1006
|
-
if (typeof credential === 'string')
|
|
1007
|
-
|
|
1008
|
-
// validate credential payload
|
|
1009
|
-
if (!decodedCredential.payload)
|
|
1010
|
-
throw new Error('[did-provider-cheqd]: verify presentation: decodedCredential.payload is required');
|
|
1011
|
-
// validate credential payload ref as VerifiableCredential
|
|
1012
|
-
if (!decodedCredential.payload.vc)
|
|
1013
|
-
throw new Error('[did-provider-cheqd]: verify presentation: decodedCredential.payload.vc is required');
|
|
1014
|
-
credential = decodedCredential.payload.vc;
|
|
1015
|
-
}
|
|
1087
|
+
if (typeof credential === 'string')
|
|
1088
|
+
credential = await Cheqd.decodeCredentialJWT(credential);
|
|
1016
1089
|
switch (credential.credentialStatus?.statusPurpose) {
|
|
1017
1090
|
case 'revocation':
|
|
1018
1091
|
if (await Cheqd.checkRevoked(credential, { ...args.options, topArgs: args }))
|
|
@@ -1029,23 +1102,71 @@ class Cheqd {
|
|
|
1029
1102
|
return { verified: true };
|
|
1030
1103
|
}
|
|
1031
1104
|
async CheckCredentialStatusWithStatusList2021(args, context) {
|
|
1032
|
-
|
|
1105
|
+
// if jwt credential, decode it
|
|
1106
|
+
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
|
|
1107
|
+
switch (credential.credentialStatus?.statusPurpose) {
|
|
1033
1108
|
case 'revocation':
|
|
1034
|
-
if (await Cheqd.checkRevoked(
|
|
1109
|
+
if (await Cheqd.checkRevoked(credential, { ...args.options, topArgs: args }))
|
|
1035
1110
|
return { revoked: true };
|
|
1036
1111
|
return { revoked: false };
|
|
1037
1112
|
case 'suspension':
|
|
1038
|
-
if (await Cheqd.checkSuspended(
|
|
1113
|
+
if (await Cheqd.checkSuspended(credential, { ...args.options, topArgs: args }))
|
|
1039
1114
|
return { suspended: true };
|
|
1040
1115
|
return { suspended: false };
|
|
1041
1116
|
default:
|
|
1042
|
-
throw new Error(`[did-provider-cheqd]: check status: Unsupported status purpose: ${
|
|
1117
|
+
throw new Error(`[did-provider-cheqd]: check status: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
|
|
1043
1118
|
}
|
|
1044
1119
|
}
|
|
1045
1120
|
async RevokeCredentialWithStatusList2021(args, context) {
|
|
1121
|
+
// verify credential, if provided and revocation options are not
|
|
1122
|
+
if (args?.credential && !args?.revocationOptions) {
|
|
1123
|
+
const verificationResult = await context.agent.verifyCredential({
|
|
1124
|
+
credential: args.credential,
|
|
1125
|
+
policies: {
|
|
1126
|
+
credentialStatus: false
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
// early return if verification failed
|
|
1130
|
+
if (!verificationResult.verified) {
|
|
1131
|
+
return { revoked: false, error: verificationResult.error };
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
// if revocation options are provided, give precedence
|
|
1135
|
+
if (args?.revocationOptions) {
|
|
1136
|
+
// validate revocation options - case: revocationOptions.issuerDid
|
|
1137
|
+
if (!args.revocationOptions.issuerDid)
|
|
1138
|
+
throw new Error('[did-provider-cheqd]: revocation: revocationOptions.issuerDid is required');
|
|
1139
|
+
// validate revocation options - case: revocationOptions.statusListName
|
|
1140
|
+
if (!args.revocationOptions.statusListName)
|
|
1141
|
+
throw new Error('[did-provider-cheqd]: revocation: revocationOptions.statusListName is required');
|
|
1142
|
+
// validate revocation options - case: revocationOptions.statusListIndex
|
|
1143
|
+
if (!args.revocationOptions.statusListIndex)
|
|
1144
|
+
throw new Error('[did-provider-cheqd]: revocation: revocationOptions.statusListIndex is required');
|
|
1145
|
+
// construct status list credential
|
|
1146
|
+
const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${args.revocationOptions.issuerDid}?resourceName=${args.revocationOptions.statusListName}&resourceType=StatusList2021Revocation`;
|
|
1147
|
+
// construct credential status
|
|
1148
|
+
args.credential = {
|
|
1149
|
+
'@context': [],
|
|
1150
|
+
issuer: args.revocationOptions.issuerDid,
|
|
1151
|
+
credentialSubject: {},
|
|
1152
|
+
credentialStatus: {
|
|
1153
|
+
id: `${statusListCredential}#${args.revocationOptions.statusListIndex}`,
|
|
1154
|
+
type: 'StatusList2021Entry',
|
|
1155
|
+
statusPurpose: 'revocation',
|
|
1156
|
+
statusListIndex: `${args.revocationOptions.statusListIndex}`,
|
|
1157
|
+
},
|
|
1158
|
+
issuanceDate: '',
|
|
1159
|
+
proof: {}
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
// validate args - case: credential
|
|
1163
|
+
if (!args.credential)
|
|
1164
|
+
throw new Error('[did-provider-cheqd]: revocation: credential is required');
|
|
1165
|
+
// if jwt credential, decode it
|
|
1166
|
+
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
|
|
1046
1167
|
// validate status purpose
|
|
1047
|
-
if (
|
|
1048
|
-
throw new Error(`[did-provider-cheqd]: revocation: Unsupported status purpose: ${
|
|
1168
|
+
if (credential.credentialStatus?.statusPurpose !== 'revocation') {
|
|
1169
|
+
throw new Error(`[did-provider-cheqd]: revocation: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
|
|
1049
1170
|
}
|
|
1050
1171
|
// validate args in pairs - case: statusListFile and statusList
|
|
1051
1172
|
if (args.options?.statusListFile && args.options?.statusList) {
|
|
@@ -1064,26 +1185,151 @@ class Cheqd {
|
|
|
1064
1185
|
throw new Error('[did-provider-cheqd]: revocation: publish requires statusListFile or statusList, if fetchList is disabled');
|
|
1065
1186
|
}
|
|
1066
1187
|
// revoke credential
|
|
1067
|
-
return await Cheqd.revokeCredential(
|
|
1188
|
+
return await Cheqd.revokeCredential(credential, {
|
|
1068
1189
|
...args.options,
|
|
1069
1190
|
topArgs: args,
|
|
1070
1191
|
publishOptions: {
|
|
1071
1192
|
context,
|
|
1193
|
+
statusListEncoding: args?.options?.statusListEncoding,
|
|
1194
|
+
statusListValidUntil: args?.options?.statusListValidUntil,
|
|
1072
1195
|
resourceId: args?.options?.resourceId,
|
|
1073
1196
|
resourceVersion: args?.options?.resourceVersion,
|
|
1197
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
1074
1198
|
signInputs: args?.options?.signInputs,
|
|
1075
1199
|
fee: args?.options?.fee
|
|
1076
1200
|
}
|
|
1077
1201
|
});
|
|
1078
1202
|
}
|
|
1079
1203
|
async RevokeBulkCredentialsWithStatusList2021(args, context) {
|
|
1080
|
-
//
|
|
1081
|
-
|
|
1204
|
+
// verify credential, if provided and revocation options are not
|
|
1205
|
+
if (args?.credentials && !args?.revocationOptions) {
|
|
1206
|
+
const verificationResult = await Promise.all(args.credentials.map(async (credential) => {
|
|
1207
|
+
return await context.agent.verifyCredential({
|
|
1208
|
+
credential,
|
|
1209
|
+
policies: {
|
|
1210
|
+
credentialStatus: false
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1213
|
+
}));
|
|
1214
|
+
// early return if verification failed for any credential
|
|
1215
|
+
if (verificationResult.some(result => !result.verified)) {
|
|
1216
|
+
// define verified
|
|
1217
|
+
return { revoked: Array(args.credentials.length).fill(false), error: verificationResult.find(result => !result.verified).error || { message: 'verification: could not verify credential' } };
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
// if revocation options are provided, give precedence
|
|
1221
|
+
if (args?.revocationOptions) {
|
|
1222
|
+
// validate revocation options - case: revocationOptions.issuerDid
|
|
1223
|
+
if (!args.revocationOptions.issuerDid)
|
|
1224
|
+
throw new Error('[did-provider-cheqd]: revocation: revocationOptions.issuerDid is required');
|
|
1225
|
+
// validate revocation options - case: revocationOptions.statusListName
|
|
1226
|
+
if (!args.revocationOptions.statusListName)
|
|
1227
|
+
throw new Error('[did-provider-cheqd]: revocation: revocationOptions.statusListName is required');
|
|
1228
|
+
// validate revocation options - case: revocationOptions.statusListIndices
|
|
1229
|
+
if (!args.revocationOptions.statusListIndices || !args.revocationOptions.statusListIndices.length || args.revocationOptions.statusListIndices.length === 0 || !args.revocationOptions.statusListIndices.every(index => !isNaN(+index)))
|
|
1230
|
+
throw new Error('[did-provider-cheqd]: revocation: revocationOptions.statusListIndex is required and must be an array of indices');
|
|
1231
|
+
// construct status list credential
|
|
1232
|
+
const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${args.revocationOptions.issuerDid}?resourceName=${args.revocationOptions.statusListName}&resourceType=StatusList2021Revocation`;
|
|
1233
|
+
// construct credential status
|
|
1234
|
+
args.credentials = args.revocationOptions.statusListIndices.map(index => ({
|
|
1235
|
+
'@context': [],
|
|
1236
|
+
issuer: args.revocationOptions.issuerDid,
|
|
1237
|
+
credentialSubject: {},
|
|
1238
|
+
credentialStatus: {
|
|
1239
|
+
id: `${statusListCredential}#${index}`,
|
|
1240
|
+
type: 'StatusList2021Entry',
|
|
1241
|
+
statusPurpose: 'revocation',
|
|
1242
|
+
statusListIndex: `${index}`,
|
|
1243
|
+
},
|
|
1244
|
+
issuanceDate: '',
|
|
1245
|
+
proof: {}
|
|
1246
|
+
}));
|
|
1247
|
+
}
|
|
1248
|
+
// validate args - case: credentials
|
|
1249
|
+
if (!args.credentials || !args.credentials.length || args.credentials.length === 0)
|
|
1250
|
+
throw new Error('[did-provider-cheqd]: revocation: credentials is required and must be an array of credentials');
|
|
1251
|
+
// if jwt credentials, decode them
|
|
1252
|
+
const credentials = await Promise.all(args.credentials.map(async (credential) => typeof credential === 'string' ? await Cheqd.decodeCredentialJWT(credential) : credential));
|
|
1253
|
+
// validate args in pairs - case: statusListFile and statusList
|
|
1254
|
+
if (args.options?.statusListFile && args.options?.statusList) {
|
|
1255
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile and statusList are mutually exclusive');
|
|
1256
|
+
}
|
|
1257
|
+
// validate args in pairs - case: statusListFile and fetchList
|
|
1258
|
+
if (args.options?.statusListFile && args.options?.fetchList) {
|
|
1259
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile and fetchList are mutually exclusive');
|
|
1260
|
+
}
|
|
1261
|
+
// validate args in pairs - case: statusList and fetchList
|
|
1262
|
+
if (args.options?.statusList && args.options?.fetchList) {
|
|
1263
|
+
throw new Error('[did-provider-cheqd]: revocation: statusList and fetchList are mutually exclusive');
|
|
1264
|
+
}
|
|
1265
|
+
// validate args in pairs - case: publish
|
|
1266
|
+
if (args.options?.publish && !args.fetchList && !(args.options?.statusListFile || args.options?.statusList)) {
|
|
1267
|
+
throw new Error('[did-provider-cheqd]: revocation: publish requires statusListFile or statusList, if fetchList is disabled');
|
|
1268
|
+
}
|
|
1269
|
+
// revoke credentials
|
|
1270
|
+
return await Cheqd.revokeCredentials(credentials, {
|
|
1271
|
+
...args.options,
|
|
1272
|
+
topArgs: args,
|
|
1273
|
+
publishOptions: {
|
|
1274
|
+
context,
|
|
1275
|
+
resourceId: args?.options?.resourceId,
|
|
1276
|
+
resourceVersion: args?.options?.resourceVersion,
|
|
1277
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
1278
|
+
signInputs: args?.options?.signInputs,
|
|
1279
|
+
fee: args?.options?.fee
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1082
1282
|
}
|
|
1083
1283
|
async SuspendCredentialWithStatusList2021(args, context) {
|
|
1284
|
+
// verify credential, if provided and suspension options are not
|
|
1285
|
+
if (args?.credential && !args?.suspensionOptions) {
|
|
1286
|
+
const verificationResult = await context.agent.verifyCredential({
|
|
1287
|
+
credential: args.credential,
|
|
1288
|
+
policies: {
|
|
1289
|
+
credentialStatus: false
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
// early return if verification failed
|
|
1293
|
+
if (!verificationResult.verified) {
|
|
1294
|
+
return { suspended: false, error: verificationResult.error };
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
// if suspension options are provided, give precedence
|
|
1298
|
+
if (args?.suspensionOptions) {
|
|
1299
|
+
// validate suspension options - case: suspensionOptions.issuerDid
|
|
1300
|
+
if (!args.suspensionOptions.issuerDid)
|
|
1301
|
+
throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.issuerDid is required');
|
|
1302
|
+
// validate suspension options - case: suspensionOptions.statusListName
|
|
1303
|
+
if (!args.suspensionOptions.statusListName)
|
|
1304
|
+
throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.statusListName is required');
|
|
1305
|
+
// validate suspension options - case: suspensionOptions.statusListIndex
|
|
1306
|
+
if (!args.suspensionOptions.statusListIndex)
|
|
1307
|
+
throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.statusListIndex is required');
|
|
1308
|
+
// construct status list credential
|
|
1309
|
+
const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${args.suspensionOptions.issuerDid}?resourceName=${args.suspensionOptions.statusListName}&resourceType=StatusList2021Suspension`;
|
|
1310
|
+
// construct credential status
|
|
1311
|
+
args.credential = {
|
|
1312
|
+
'@context': [],
|
|
1313
|
+
issuer: args.suspensionOptions.issuerDid,
|
|
1314
|
+
credentialSubject: {},
|
|
1315
|
+
credentialStatus: {
|
|
1316
|
+
id: `${statusListCredential}#${args.suspensionOptions.statusListIndex}`,
|
|
1317
|
+
type: 'StatusList2021Entry',
|
|
1318
|
+
statusPurpose: 'suspension',
|
|
1319
|
+
statusListIndex: `${args.suspensionOptions.statusListIndex}`,
|
|
1320
|
+
},
|
|
1321
|
+
issuanceDate: '',
|
|
1322
|
+
proof: {}
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
// validate args - case: credential
|
|
1326
|
+
if (!args.credential)
|
|
1327
|
+
throw new Error('[did-provider-cheqd]: suspension: credential is required');
|
|
1328
|
+
// if jwt credential, decode it
|
|
1329
|
+
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
|
|
1084
1330
|
// validate status purpose
|
|
1085
|
-
if (
|
|
1086
|
-
throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${
|
|
1331
|
+
if (credential.credentialStatus?.statusPurpose !== 'suspension') {
|
|
1332
|
+
throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
|
|
1087
1333
|
}
|
|
1088
1334
|
// validate args in pairs - case: statusListFile and statusList
|
|
1089
1335
|
if (args.options?.statusListFile && args.options?.statusList) {
|
|
@@ -1102,26 +1348,151 @@ class Cheqd {
|
|
|
1102
1348
|
throw new Error('[did-provider-cheqd]: suspension: publish requires statusListFile or statusList, if fetchList is disabled');
|
|
1103
1349
|
}
|
|
1104
1350
|
// suspend credential
|
|
1105
|
-
return await Cheqd.suspendCredential(
|
|
1351
|
+
return await Cheqd.suspendCredential(credential, {
|
|
1106
1352
|
...args.options,
|
|
1107
1353
|
topArgs: args,
|
|
1108
1354
|
publishOptions: {
|
|
1109
1355
|
context,
|
|
1356
|
+
statusListEncoding: args?.options?.statusListEncoding,
|
|
1357
|
+
statusListValidUntil: args?.options?.statusListValidUntil,
|
|
1110
1358
|
resourceId: args?.options?.resourceId,
|
|
1111
1359
|
resourceVersion: args?.options?.resourceVersion,
|
|
1360
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
1112
1361
|
signInputs: args?.options?.signInputs,
|
|
1113
1362
|
fee: args?.options?.fee
|
|
1114
1363
|
}
|
|
1115
1364
|
});
|
|
1116
1365
|
}
|
|
1117
1366
|
async SuspendBulkCredentialsWithStatusList2021(args, context) {
|
|
1118
|
-
//
|
|
1119
|
-
|
|
1367
|
+
// verify credential, if provided and suspension options are not
|
|
1368
|
+
if (args?.credentials && !args?.suspensionOptions) {
|
|
1369
|
+
const verificationResult = await Promise.all(args.credentials.map(async (credential) => {
|
|
1370
|
+
return await context.agent.verifyCredential({
|
|
1371
|
+
credential,
|
|
1372
|
+
policies: {
|
|
1373
|
+
credentialStatus: false
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
}));
|
|
1377
|
+
// early return if verification failed for any credential
|
|
1378
|
+
if (verificationResult.some(result => !result.verified)) {
|
|
1379
|
+
// define verified
|
|
1380
|
+
return { suspended: Array(args.credentials.length).fill(false), error: verificationResult.find(result => !result.verified).error || { message: 'verification: could not verify credential' } };
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
// if suspension options are provided, give precedence
|
|
1384
|
+
if (args?.suspensionOptions) {
|
|
1385
|
+
// validate suspension options - case: suspensionOptions.issuerDid
|
|
1386
|
+
if (!args.suspensionOptions.issuerDid)
|
|
1387
|
+
throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.issuerDid is required');
|
|
1388
|
+
// validate suspension options - case: suspensionOptions.statusListName
|
|
1389
|
+
if (!args.suspensionOptions.statusListName)
|
|
1390
|
+
throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.statusListName is required');
|
|
1391
|
+
// validate suspension options - case: suspensionOptions.statusListIndices
|
|
1392
|
+
if (!args.suspensionOptions.statusListIndices || !args.suspensionOptions.statusListIndices.length || args.suspensionOptions.statusListIndices.length === 0 || !args.suspensionOptions.statusListIndices.every(index => !isNaN(+index)))
|
|
1393
|
+
throw new Error('[did-provider-cheqd]: suspension: suspensionOptions.statusListIndex is required and must be an array of indices');
|
|
1394
|
+
// construct status list credential
|
|
1395
|
+
const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${args.suspensionOptions.issuerDid}?resourceName=${args.suspensionOptions.statusListName}&resourceType=StatusList2021Suspension`;
|
|
1396
|
+
// construct credential status
|
|
1397
|
+
args.credentials = args.suspensionOptions.statusListIndices.map(index => ({
|
|
1398
|
+
'@context': [],
|
|
1399
|
+
issuer: args.suspensionOptions.issuerDid,
|
|
1400
|
+
credentialSubject: {},
|
|
1401
|
+
credentialStatus: {
|
|
1402
|
+
id: `${statusListCredential}#${index}`,
|
|
1403
|
+
type: 'StatusList2021Entry',
|
|
1404
|
+
statusPurpose: 'suspension',
|
|
1405
|
+
statusListIndex: `${index}`,
|
|
1406
|
+
},
|
|
1407
|
+
issuanceDate: '',
|
|
1408
|
+
proof: {}
|
|
1409
|
+
}));
|
|
1410
|
+
}
|
|
1411
|
+
// validate args - case: credentials
|
|
1412
|
+
if (!args.credentials || !args.credentials.length || args.credentials.length === 0)
|
|
1413
|
+
throw new Error('[did-provider-cheqd]: suspension: credentials is required and must be an array of credentials');
|
|
1414
|
+
// if jwt credentials, decode them
|
|
1415
|
+
const credentials = await Promise.all(args.credentials.map(async (credential) => typeof credential === 'string' ? await Cheqd.decodeCredentialJWT(credential) : credential));
|
|
1416
|
+
// validate args in pairs - case: statusListFile and statusList
|
|
1417
|
+
if (args.options?.statusListFile && args.options?.statusList) {
|
|
1418
|
+
throw new Error('[did-provider-cheqd]: suspension: statusListFile and statusList are mutually exclusive');
|
|
1419
|
+
}
|
|
1420
|
+
// validate args in pairs - case: statusListFile and fetchList
|
|
1421
|
+
if (args.options?.statusListFile && args.options?.fetchList) {
|
|
1422
|
+
throw new Error('[did-provider-cheqd]: suspension: statusListFile and fetchList are mutually exclusive');
|
|
1423
|
+
}
|
|
1424
|
+
// validate args in pairs - case: statusList and fetchList
|
|
1425
|
+
if (args.options?.statusList && args.options?.fetchList) {
|
|
1426
|
+
throw new Error('[did-provider-cheqd]: suspension: statusList and fetchList are mutually exclusive');
|
|
1427
|
+
}
|
|
1428
|
+
// validate args in pairs - case: publish
|
|
1429
|
+
if (args.options?.publish && !args.fetchList && !(args.options?.statusListFile || args.options?.statusList)) {
|
|
1430
|
+
throw new Error('[did-provider-cheqd]: suspension: publish requires statusListFile or statusList, if fetchList is disabled');
|
|
1431
|
+
}
|
|
1432
|
+
// suspend credentials
|
|
1433
|
+
return await Cheqd.suspendCredentials(credentials, {
|
|
1434
|
+
...args.options,
|
|
1435
|
+
topArgs: args,
|
|
1436
|
+
publishOptions: {
|
|
1437
|
+
context,
|
|
1438
|
+
resourceId: args?.options?.resourceId,
|
|
1439
|
+
resourceVersion: args?.options?.resourceVersion,
|
|
1440
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
1441
|
+
signInputs: args?.options?.signInputs,
|
|
1442
|
+
fee: args?.options?.fee
|
|
1443
|
+
}
|
|
1444
|
+
});
|
|
1120
1445
|
}
|
|
1121
1446
|
async UnsuspendCredentialWithStatusList2021(args, context) {
|
|
1447
|
+
// verify credential, if provided and unsuspension options are not
|
|
1448
|
+
if (args?.credential && !args?.unsuspensionOptions) {
|
|
1449
|
+
const verificationResult = await context.agent.verifyCredential({
|
|
1450
|
+
credential: args.credential,
|
|
1451
|
+
policies: {
|
|
1452
|
+
credentialStatus: false
|
|
1453
|
+
}
|
|
1454
|
+
});
|
|
1455
|
+
// early return if verification failed
|
|
1456
|
+
if (!verificationResult.verified) {
|
|
1457
|
+
return { unsuspended: false, error: verificationResult.error };
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
// if unsuspension options are provided, give precedence
|
|
1461
|
+
if (args?.unsuspensionOptions) {
|
|
1462
|
+
// validate unsuspension options - case: unsuspensionOptions.issuerDid
|
|
1463
|
+
if (!args.unsuspensionOptions.issuerDid)
|
|
1464
|
+
throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.issuerDid is required');
|
|
1465
|
+
// validate unsuspension options - case: unsuspensionOptions.statusListName
|
|
1466
|
+
if (!args.unsuspensionOptions.statusListName)
|
|
1467
|
+
throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.statusListName is required');
|
|
1468
|
+
// validate unsuspension options - case: unsuspensionOptions.statusListIndex
|
|
1469
|
+
if (!args.unsuspensionOptions.statusListIndex)
|
|
1470
|
+
throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.statusListIndex is required');
|
|
1471
|
+
// construct status list credential
|
|
1472
|
+
const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${args.unsuspensionOptions.issuerDid}?resourceName=${args.unsuspensionOptions.statusListName}&resourceType=StatusList2021Suspension`;
|
|
1473
|
+
// construct credential status
|
|
1474
|
+
args.credential = {
|
|
1475
|
+
'@context': [],
|
|
1476
|
+
issuer: args.unsuspensionOptions.issuerDid,
|
|
1477
|
+
credentialSubject: {},
|
|
1478
|
+
credentialStatus: {
|
|
1479
|
+
id: `${statusListCredential}#${args.unsuspensionOptions.statusListIndex}`,
|
|
1480
|
+
type: 'StatusList2021Entry',
|
|
1481
|
+
statusPurpose: 'suspension',
|
|
1482
|
+
statusListIndex: `${args.unsuspensionOptions.statusListIndex}`,
|
|
1483
|
+
},
|
|
1484
|
+
issuanceDate: '',
|
|
1485
|
+
proof: {}
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
// validate args - case: credential
|
|
1489
|
+
if (!args.credential)
|
|
1490
|
+
throw new Error('[did-provider-cheqd]: unsuspension: credential is required');
|
|
1491
|
+
// if jwt credential, decode it
|
|
1492
|
+
const credential = typeof args.credential === 'string' ? await Cheqd.decodeCredentialJWT(args.credential) : args.credential;
|
|
1122
1493
|
// validate status purpose
|
|
1123
|
-
if (
|
|
1124
|
-
throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${
|
|
1494
|
+
if (credential.credentialStatus?.statusPurpose !== 'suspension') {
|
|
1495
|
+
throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
|
|
1125
1496
|
}
|
|
1126
1497
|
// validate args in pairs - case: statusListFile and statusList
|
|
1127
1498
|
if (args.options?.statusListFile && args.options?.statusList) {
|
|
@@ -1140,21 +1511,100 @@ class Cheqd {
|
|
|
1140
1511
|
throw new Error('[did-provider-cheqd]: suspension: publish requires statusListFile or statusList, if fetchList is disabled');
|
|
1141
1512
|
}
|
|
1142
1513
|
// suspend credential
|
|
1143
|
-
return await Cheqd.unsuspendCredential(
|
|
1514
|
+
return await Cheqd.unsuspendCredential(credential, {
|
|
1144
1515
|
...args.options,
|
|
1145
1516
|
topArgs: args,
|
|
1146
1517
|
publishOptions: {
|
|
1147
1518
|
context,
|
|
1519
|
+
statusListEncoding: args?.options?.statusListEncoding,
|
|
1520
|
+
statusListValidUntil: args?.options?.statusListValidUntil,
|
|
1148
1521
|
resourceId: args?.options?.resourceId,
|
|
1149
1522
|
resourceVersion: args?.options?.resourceVersion,
|
|
1523
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
1150
1524
|
signInputs: args?.options?.signInputs,
|
|
1151
1525
|
fee: args?.options?.fee
|
|
1152
1526
|
}
|
|
1153
1527
|
});
|
|
1154
1528
|
}
|
|
1155
1529
|
async UnsuspendBulkCredentialsWithStatusList2021(args, context) {
|
|
1156
|
-
//
|
|
1157
|
-
|
|
1530
|
+
// verify credential, if provided and unsuspension options are not
|
|
1531
|
+
if (args?.credentials && !args?.unsuspensionOptions) {
|
|
1532
|
+
const verificationResult = await Promise.all(args.credentials.map(async (credential) => {
|
|
1533
|
+
return await context.agent.verifyCredential({
|
|
1534
|
+
credential,
|
|
1535
|
+
policies: {
|
|
1536
|
+
credentialStatus: false
|
|
1537
|
+
}
|
|
1538
|
+
});
|
|
1539
|
+
}));
|
|
1540
|
+
// early return if verification failed for any credential
|
|
1541
|
+
if (verificationResult.some(result => !result.verified)) {
|
|
1542
|
+
// define verified
|
|
1543
|
+
return { unsuspended: Array(args.credentials.length).fill(false), error: verificationResult.find(result => !result.verified).error || { message: 'verification: could not verify credential' } };
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
// if unsuspension options are provided, give precedence
|
|
1547
|
+
if (args?.unsuspensionOptions) {
|
|
1548
|
+
// validate unsuspension options - case: unsuspensionOptions.issuerDid
|
|
1549
|
+
if (!args.unsuspensionOptions.issuerDid)
|
|
1550
|
+
throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.issuerDid is required');
|
|
1551
|
+
// validate unsuspension options - case: unsuspensionOptions.statusListName
|
|
1552
|
+
if (!args.unsuspensionOptions.statusListName)
|
|
1553
|
+
throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.statusListName is required');
|
|
1554
|
+
// validate unsuspension options - case: unsuspensionOptions.statusListIndices
|
|
1555
|
+
if (!args.unsuspensionOptions.statusListIndices || !args.unsuspensionOptions.statusListIndices.length || args.unsuspensionOptions.statusListIndices.length === 0 || !args.unsuspensionOptions.statusListIndices.every(index => !isNaN(+index)))
|
|
1556
|
+
throw new Error('[did-provider-cheqd]: unsuspension: unsuspensionOptions.statusListIndex is required and must be an array of indices');
|
|
1557
|
+
// construct status list credential
|
|
1558
|
+
const statusListCredential = `${cheqd_did_resolver_js_1.resolverUrl}${args.unsuspensionOptions.issuerDid}?resourceName=${args.unsuspensionOptions.statusListName}&resourceType=StatusList2021Suspension`;
|
|
1559
|
+
// construct credential status
|
|
1560
|
+
args.credentials = args.unsuspensionOptions.statusListIndices.map(index => ({
|
|
1561
|
+
'@context': [],
|
|
1562
|
+
issuer: args.unsuspensionOptions.issuerDid,
|
|
1563
|
+
credentialSubject: {},
|
|
1564
|
+
credentialStatus: {
|
|
1565
|
+
id: `${statusListCredential}#${index}`,
|
|
1566
|
+
type: 'StatusList2021Entry',
|
|
1567
|
+
statusPurpose: 'suspension',
|
|
1568
|
+
statusListIndex: `${index}`,
|
|
1569
|
+
},
|
|
1570
|
+
issuanceDate: '',
|
|
1571
|
+
proof: {}
|
|
1572
|
+
}));
|
|
1573
|
+
}
|
|
1574
|
+
// validate args - case: credentials
|
|
1575
|
+
if (!args.credentials || !args.credentials.length || args.credentials.length === 0)
|
|
1576
|
+
throw new Error('[did-provider-cheqd]: unsuspension: credentials is required and must be an array of credentials');
|
|
1577
|
+
// if jwt credentials, decode them
|
|
1578
|
+
const credentials = await Promise.all(args.credentials.map(async (credential) => typeof credential === 'string' ? await Cheqd.decodeCredentialJWT(credential) : credential));
|
|
1579
|
+
// validate args in pairs - case: statusListFile and statusList
|
|
1580
|
+
if (args.options?.statusListFile && args.options?.statusList) {
|
|
1581
|
+
throw new Error('[did-provider-cheqd]: unsuspension: statusListFile and statusList are mutually exclusive');
|
|
1582
|
+
}
|
|
1583
|
+
// validate args in pairs - case: statusListFile and fetchList
|
|
1584
|
+
if (args.options?.statusListFile && args.options?.fetchList) {
|
|
1585
|
+
throw new Error('[did-provider-cheqd]: unsuspension: statusListFile and fetchList are mutually exclusive');
|
|
1586
|
+
}
|
|
1587
|
+
// validate args in pairs - case: statusList and fetchList
|
|
1588
|
+
if (args.options?.statusList && args.options?.fetchList) {
|
|
1589
|
+
throw new Error('[did-provider-cheqd]: unsuspension: statusList and fetchList are mutually exclusive');
|
|
1590
|
+
}
|
|
1591
|
+
// validate args in pairs - case: publish
|
|
1592
|
+
if (args.options?.publish && !args.fetchList && !(args.options?.statusListFile || args.options?.statusList)) {
|
|
1593
|
+
throw new Error('[did-provider-cheqd]: unsuspension: publish requires statusListFile or statusList, if fetchList is disabled');
|
|
1594
|
+
}
|
|
1595
|
+
// suspend credentials
|
|
1596
|
+
return await Cheqd.unsuspendCredentials(credentials, {
|
|
1597
|
+
...args.options,
|
|
1598
|
+
topArgs: args,
|
|
1599
|
+
publishOptions: {
|
|
1600
|
+
context,
|
|
1601
|
+
resourceId: args?.options?.resourceId,
|
|
1602
|
+
resourceVersion: args?.options?.resourceVersion,
|
|
1603
|
+
resourceAlsoKnownAs: args?.options?.alsoKnownAs,
|
|
1604
|
+
signInputs: args?.options?.signInputs,
|
|
1605
|
+
fee: args?.options?.fee
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1158
1608
|
}
|
|
1159
1609
|
async TransactVerifierPaysIssuer(args, context) {
|
|
1160
1610
|
try {
|
|
@@ -1277,46 +1727,56 @@ class Cheqd {
|
|
|
1277
1727
|
// validate status purpose
|
|
1278
1728
|
if (credential?.credentialStatus?.statusPurpose !== 'revocation')
|
|
1279
1729
|
throw new Error('[did-provider-cheqd]: revocation: Invalid status purpose');
|
|
1280
|
-
// fetch status list 2021
|
|
1281
|
-
const
|
|
1282
|
-
// detect if encrypted
|
|
1283
|
-
const isEncrypted = function () {
|
|
1284
|
-
switch (metadata.mediaType) {
|
|
1285
|
-
case 'application/octet-stream':
|
|
1286
|
-
return true;
|
|
1287
|
-
case 'application/gzip':
|
|
1288
|
-
return false;
|
|
1289
|
-
default:
|
|
1290
|
-
throw new Error(`[did-provider-cheqd]: revocation: Unsupported media type: ${metadata.mediaType}`);
|
|
1291
|
-
}
|
|
1292
|
-
}();
|
|
1730
|
+
// fetch status list 2021
|
|
1731
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credential));
|
|
1293
1732
|
// early return, if encrypted and no decryption key provided
|
|
1294
|
-
if (
|
|
1733
|
+
if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey)
|
|
1295
1734
|
throw new Error('[did-provider-cheqd]: revocation: symmetricKey is required, if status list 2021 is encrypted');
|
|
1296
1735
|
// fetch status list 2021 inscribed in credential
|
|
1297
1736
|
const statusList2021 = options?.topArgs?.fetchList
|
|
1298
1737
|
? (await async function () {
|
|
1299
1738
|
// if not encrypted, return bitstring
|
|
1300
|
-
if (!
|
|
1301
|
-
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');
|
|
1302
1743
|
// otherwise, decrypt and return bitstring
|
|
1303
|
-
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));
|
|
1304
1745
|
// decrypt
|
|
1305
1746
|
return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
|
|
1306
1747
|
}())
|
|
1307
1748
|
: (await async function () {
|
|
1749
|
+
// transcode to base64url, if needed
|
|
1750
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
1751
|
+
? publishedList.StatusList2021.encodedList
|
|
1752
|
+
: (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
|
|
1308
1753
|
// if status list 2021 is not fetched, read from file
|
|
1309
1754
|
if (options?.statusListFile) {
|
|
1310
1755
|
// if not encrypted, return bitstring
|
|
1311
|
-
if (!
|
|
1312
|
-
|
|
1756
|
+
if (!publishedList.metadata.encrypted) {
|
|
1757
|
+
// construct encoded status list
|
|
1758
|
+
const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
|
|
1759
|
+
// validate against published list
|
|
1760
|
+
if (encoded !== publishedListTranscoded)
|
|
1761
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
1762
|
+
// return encoded
|
|
1763
|
+
return encoded;
|
|
1764
|
+
}
|
|
1313
1765
|
// otherwise, decrypt and return bitstring
|
|
1314
1766
|
const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
|
|
1315
1767
|
// decrypt
|
|
1316
|
-
|
|
1768
|
+
const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
|
|
1769
|
+
// validate against published list
|
|
1770
|
+
if (decrypted !== publishedListTranscoded)
|
|
1771
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
1772
|
+
// return decrypted
|
|
1773
|
+
return decrypted;
|
|
1317
1774
|
}
|
|
1318
1775
|
if (!options?.statusListInlineBitstring)
|
|
1319
1776
|
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
|
|
1777
|
+
// validate against published list
|
|
1778
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded)
|
|
1779
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
|
|
1320
1780
|
// otherwise, read from inline bitstring
|
|
1321
1781
|
return options?.statusListInlineBitstring;
|
|
1322
1782
|
}());
|
|
@@ -1356,7 +1816,45 @@ class Cheqd {
|
|
|
1356
1816
|
{ encryptedString, encryptedSymmetricKey, symmetricKey: (0, uint8arrays_1.toString)(symmetricKey, 'hex') }
|
|
1357
1817
|
];
|
|
1358
1818
|
}())
|
|
1359
|
-
:
|
|
1819
|
+
: (await async function () {
|
|
1820
|
+
// validate encoding, if provided
|
|
1821
|
+
if (options?.publishOptions?.statusListEncoding && !Object.values(cheqd_did_provider_js_1.DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
1822
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
|
|
1823
|
+
}
|
|
1824
|
+
// validate validUntil, if provided
|
|
1825
|
+
if (options?.publishOptions?.statusListValidUntil) {
|
|
1826
|
+
// validate validUntil as string
|
|
1827
|
+
if (typeof options?.publishOptions?.statusListValidUntil !== 'string')
|
|
1828
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)');
|
|
1829
|
+
// validate validUntil as date
|
|
1830
|
+
if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil)))
|
|
1831
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)');
|
|
1832
|
+
// validate validUntil as future date
|
|
1833
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) < new Date())
|
|
1834
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)');
|
|
1835
|
+
// validate validUntil towards validFrom
|
|
1836
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom))
|
|
1837
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)');
|
|
1838
|
+
}
|
|
1839
|
+
// define status list content
|
|
1840
|
+
const content = {
|
|
1841
|
+
StatusList2021: {
|
|
1842
|
+
type: publishedList.StatusList2021.type,
|
|
1843
|
+
encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options.publishOptions.statusListEncoding),
|
|
1844
|
+
validFrom: publishedList.StatusList2021.validFrom,
|
|
1845
|
+
validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
|
|
1846
|
+
},
|
|
1847
|
+
metadata: {
|
|
1848
|
+
encoding: options?.publishOptions?.statusListEncoding || publishedList.metadata.encoding,
|
|
1849
|
+
encrypted: false,
|
|
1850
|
+
}
|
|
1851
|
+
};
|
|
1852
|
+
// return tuple of publish result and encryption relevant metadata
|
|
1853
|
+
return [
|
|
1854
|
+
await Cheqd.publishStatusList2021((0, uint8arrays_1.fromString)(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
|
|
1855
|
+
undefined
|
|
1856
|
+
];
|
|
1857
|
+
}());
|
|
1360
1858
|
// early exit, if publish failed
|
|
1361
1859
|
if (!scoped[0])
|
|
1362
1860
|
throw new Error('[did-provider-cheqd]: revocation: Failed to publish status list 2021');
|
|
@@ -1367,7 +1865,7 @@ class Cheqd {
|
|
|
1367
1865
|
return {
|
|
1368
1866
|
revoked: true,
|
|
1369
1867
|
published: topArgs?.publish ? true : undefined,
|
|
1370
|
-
statusList: topArgs?.returnUpdatedStatusList ?
|
|
1868
|
+
statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credential) : undefined,
|
|
1371
1869
|
encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await (0, helpers_js_1.blobToHexString)(published?.[1]?.encryptedString) : undefined,
|
|
1372
1870
|
encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? published?.[1]?.encryptedSymmetricKey : undefined,
|
|
1373
1871
|
symmetricKey: topArgs?.returnSymmetricKey ? published?.[1]?.symmetricKey : undefined,
|
|
@@ -1375,56 +1873,264 @@ class Cheqd {
|
|
|
1375
1873
|
};
|
|
1376
1874
|
}
|
|
1377
1875
|
catch (error) {
|
|
1378
|
-
// silent fail + early exit
|
|
1876
|
+
// silent fail + early exit
|
|
1379
1877
|
console.error(error);
|
|
1380
1878
|
return { revoked: false, error: error };
|
|
1381
1879
|
}
|
|
1382
1880
|
}
|
|
1881
|
+
static async revokeCredentials(credentials, options) {
|
|
1882
|
+
// validate credentials - case: empty
|
|
1883
|
+
if (!credentials.length || credentials.length === 0)
|
|
1884
|
+
throw new Error('[did-provider-cheqd]: revocation: No credentials provided');
|
|
1885
|
+
// validate credentials - case: consistent issuer
|
|
1886
|
+
if (credentials.map((credential) => {
|
|
1887
|
+
return (credential.issuer.id)
|
|
1888
|
+
? credential.issuer.id
|
|
1889
|
+
: credential.issuer;
|
|
1890
|
+
}).filter((value, _, self) => value && value !== self[0]).length > 0)
|
|
1891
|
+
throw new Error('[did-provider-cheqd]: revocation: Credentials must be issued by the same issuer');
|
|
1892
|
+
// validate credentials - case: status list index
|
|
1893
|
+
if (credentials.map((credential) => credential.credentialStatus.statusListIndex).filter((value, index, self) => self.indexOf(value) !== index).length > 0)
|
|
1894
|
+
throw new Error('[did-provider-cheqd]: revocation: Credentials must have unique status list index');
|
|
1895
|
+
// validate credentials - case: status purpose
|
|
1896
|
+
if (!credentials.every((credential) => credential.credentialStatus?.statusPurpose === 'revocation'))
|
|
1897
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status purpose');
|
|
1898
|
+
// validate credentials - case: status list id
|
|
1899
|
+
const remote = credentials[0].credentialStatus?.id
|
|
1900
|
+
? credentials[0].credentialStatus.id.split('#')[0]
|
|
1901
|
+
: (function () {
|
|
1902
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list id');
|
|
1903
|
+
}());
|
|
1904
|
+
// validate credentials - case: status list id format
|
|
1905
|
+
if (!exports.RemoteListPattern.test(remote))
|
|
1906
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list id format: expected: https://<optional_subdomain>.<sld>.<tld>/1.0/identifiers/<did:cheqd:<namespace>:<method_specific_id>>?resourceName=<resource_name>&resourceType=<resource_type>');
|
|
1907
|
+
if (!credentials.every((credential) => {
|
|
1908
|
+
return credential.credentialStatus.id.split('#')[0] === remote;
|
|
1909
|
+
}))
|
|
1910
|
+
throw new Error('[did-provider-cheqd]: revocation: Credentials must belong to the same status list');
|
|
1911
|
+
// validate credentials - case: status list type
|
|
1912
|
+
if (!credentials.every((credential) => credential.credentialStatus?.type === 'StatusList2021Entry'))
|
|
1913
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list type');
|
|
1914
|
+
try {
|
|
1915
|
+
// fetch status list 2021
|
|
1916
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credentials[0]));
|
|
1917
|
+
// early return, if encrypted and no decryption key provided
|
|
1918
|
+
if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey)
|
|
1919
|
+
throw new Error('[did-provider-cheqd]: revocation: symmetricKey is required, if status list 2021 is encrypted');
|
|
1920
|
+
// fetch status list 2021 inscribed in credential
|
|
1921
|
+
const statusList2021 = options?.topArgs?.fetchList
|
|
1922
|
+
? (await async function () {
|
|
1923
|
+
// if not encrypted, return bitstring
|
|
1924
|
+
if (!publishedList.metadata.encrypted)
|
|
1925
|
+
return publishedList.metadata.encoding === 'base64url'
|
|
1926
|
+
? publishedList.StatusList2021.encodedList
|
|
1927
|
+
: (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
|
|
1928
|
+
// otherwise, decrypt and return bitstring
|
|
1929
|
+
const scopedRawBlob = await (0, helpers_js_1.toBlob)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding));
|
|
1930
|
+
// decrypt
|
|
1931
|
+
return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
|
|
1932
|
+
}())
|
|
1933
|
+
: (await async function () {
|
|
1934
|
+
// transcode to base64url, if needed
|
|
1935
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
1936
|
+
? publishedList.StatusList2021.encodedList
|
|
1937
|
+
: (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
|
|
1938
|
+
// if status list 2021 is not fetched, read from file
|
|
1939
|
+
if (options?.statusListFile) {
|
|
1940
|
+
// if not encrypted, return bitstring
|
|
1941
|
+
if (!publishedList.metadata.encrypted) {
|
|
1942
|
+
// construct encoded status list
|
|
1943
|
+
const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
|
|
1944
|
+
// validate against published list
|
|
1945
|
+
if (encoded !== publishedListTranscoded)
|
|
1946
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
1947
|
+
// return encoded
|
|
1948
|
+
return encoded;
|
|
1949
|
+
}
|
|
1950
|
+
// otherwise, decrypt and return bitstring
|
|
1951
|
+
const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
|
|
1952
|
+
// decrypt
|
|
1953
|
+
const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
|
|
1954
|
+
// validate against published list
|
|
1955
|
+
if (decrypted !== publishedListTranscoded)
|
|
1956
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
1957
|
+
// return decrypted
|
|
1958
|
+
return decrypted;
|
|
1959
|
+
}
|
|
1960
|
+
if (!options?.statusListInlineBitstring)
|
|
1961
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
|
|
1962
|
+
// validate against published list
|
|
1963
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded)
|
|
1964
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
|
|
1965
|
+
// otherwise, read from inline bitstring
|
|
1966
|
+
return options?.statusListInlineBitstring;
|
|
1967
|
+
}());
|
|
1968
|
+
// parse status list 2021
|
|
1969
|
+
const statusList = await vc_status_list_1.StatusList.decode({ encodedList: statusList2021 });
|
|
1970
|
+
// initiate bulk revocation
|
|
1971
|
+
const revoked = await Promise.allSettled(credentials.map((credential) => {
|
|
1972
|
+
return async function () {
|
|
1973
|
+
// early return, if no credential status
|
|
1974
|
+
if (!credential.credentialStatus)
|
|
1975
|
+
return { revoked: false };
|
|
1976
|
+
// early exit, if credential is already revoked
|
|
1977
|
+
if (statusList.getStatus(Number(credential.credentialStatus.statusListIndex)))
|
|
1978
|
+
return { revoked: false };
|
|
1979
|
+
// update revocation status
|
|
1980
|
+
statusList.setStatus(Number(credential.credentialStatus.statusListIndex), true);
|
|
1981
|
+
// return revocation status
|
|
1982
|
+
return { revoked: true };
|
|
1983
|
+
}();
|
|
1984
|
+
}));
|
|
1985
|
+
// revert bulk ops, if some failed
|
|
1986
|
+
if (revoked.some((result) => result.status === 'fulfilled' && !result.value.revoked))
|
|
1987
|
+
throw new Error(`[did-provider-cheqd]: revocation: Bulk revocation failed: already revoked credentials in revocation bundle: raw log: ${JSON.stringify(revoked.map((result) => ({ revoked: result.status === 'fulfilled' ? result.value.revoked : false })))}`);
|
|
1988
|
+
// set in-memory status list ref
|
|
1989
|
+
const bitstring = await statusList.encode();
|
|
1990
|
+
// cast top-level args
|
|
1991
|
+
const topArgs = options?.topArgs;
|
|
1992
|
+
// write status list 2021 to file, if provided
|
|
1993
|
+
if (topArgs?.writeToFile) {
|
|
1994
|
+
await Cheqd.writeFile((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options?.statusListFile);
|
|
1995
|
+
}
|
|
1996
|
+
// publish status list 2021, if provided
|
|
1997
|
+
const published = topArgs?.publish
|
|
1998
|
+
? (await async function () {
|
|
1999
|
+
// fetch status list 2021 metadata
|
|
2000
|
+
const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(credentials[0]);
|
|
2001
|
+
// publish status list 2021 as new version
|
|
2002
|
+
const scoped = topArgs.publishEncrypted
|
|
2003
|
+
? (await async function () {
|
|
2004
|
+
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
2005
|
+
const lit = await lit_protocol_js_1.LitProtocol.create({
|
|
2006
|
+
chain: options?.topArgs?.bootstrapOptions?.chain,
|
|
2007
|
+
litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
|
|
2008
|
+
});
|
|
2009
|
+
// encrypt
|
|
2010
|
+
const { encryptedString, encryptedSymmetricKey, symmetricKey } = await lit.encrypt(bitstring, options?.topArgs?.encryptionOptions?.unifiedAccessControlConditions, true);
|
|
2011
|
+
// return tuple of publish result and encryption relevant metadata
|
|
2012
|
+
return [
|
|
2013
|
+
await Cheqd.publishStatusList2021(new Uint8Array(await encryptedString.arrayBuffer()), statusListMetadata, options?.publishOptions),
|
|
2014
|
+
{ encryptedString, encryptedSymmetricKey, symmetricKey: (0, uint8arrays_1.toString)(symmetricKey, 'hex') }
|
|
2015
|
+
];
|
|
2016
|
+
}())
|
|
2017
|
+
: (await async function () {
|
|
2018
|
+
// validate encoding, if provided
|
|
2019
|
+
if (options?.publishOptions?.statusListEncoding && !Object.values(cheqd_did_provider_js_1.DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
2020
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
|
|
2021
|
+
}
|
|
2022
|
+
// validate validUntil, if provided
|
|
2023
|
+
if (options?.publishOptions?.statusListValidUntil) {
|
|
2024
|
+
// validate validUntil as string
|
|
2025
|
+
if (typeof options?.publishOptions?.statusListValidUntil !== 'string')
|
|
2026
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)');
|
|
2027
|
+
// validate validUntil as date
|
|
2028
|
+
if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil)))
|
|
2029
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)');
|
|
2030
|
+
// validate validUntil as future date
|
|
2031
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) < new Date())
|
|
2032
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)');
|
|
2033
|
+
// validate validUntil towards validFrom
|
|
2034
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom))
|
|
2035
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)');
|
|
2036
|
+
}
|
|
2037
|
+
// define status list content
|
|
2038
|
+
const content = {
|
|
2039
|
+
StatusList2021: {
|
|
2040
|
+
type: publishedList.StatusList2021.type,
|
|
2041
|
+
encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options.publishOptions.statusListEncoding),
|
|
2042
|
+
validFrom: publishedList.StatusList2021.validFrom,
|
|
2043
|
+
validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
|
|
2044
|
+
},
|
|
2045
|
+
metadata: {
|
|
2046
|
+
encoding: options?.publishOptions?.statusListEncoding || publishedList.metadata.encoding,
|
|
2047
|
+
encrypted: false,
|
|
2048
|
+
}
|
|
2049
|
+
};
|
|
2050
|
+
// return tuple of publish result and encryption relevant metadata
|
|
2051
|
+
return [
|
|
2052
|
+
await Cheqd.publishStatusList2021((0, uint8arrays_1.fromString)(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
|
|
2053
|
+
undefined
|
|
2054
|
+
];
|
|
2055
|
+
}());
|
|
2056
|
+
// early exit, if publish failed
|
|
2057
|
+
if (!scoped[0])
|
|
2058
|
+
throw new Error('[did-provider-cheqd]: revocation: Failed to publish status list 2021');
|
|
2059
|
+
// return publish result
|
|
2060
|
+
return scoped;
|
|
2061
|
+
}())
|
|
2062
|
+
: undefined;
|
|
2063
|
+
return {
|
|
2064
|
+
revoked: revoked.map((result) => result.status === 'fulfilled' ? result.value.revoked : false),
|
|
2065
|
+
published: topArgs?.publish ? true : undefined,
|
|
2066
|
+
statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credentials[0]) : undefined,
|
|
2067
|
+
encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await (0, helpers_js_1.blobToHexString)(published?.[1]?.encryptedString) : undefined,
|
|
2068
|
+
encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? published?.[1]?.encryptedSymmetricKey : undefined,
|
|
2069
|
+
symmetricKey: topArgs?.returnSymmetricKey ? published?.[1]?.symmetricKey : undefined,
|
|
2070
|
+
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(credentials[0]) : undefined
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
catch (error) {
|
|
2074
|
+
// silent fail + early exit
|
|
2075
|
+
console.error(error);
|
|
2076
|
+
return { revoked: [], error: error };
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
1383
2079
|
static async suspendCredential(credential, options) {
|
|
1384
2080
|
try {
|
|
1385
2081
|
// validate status purpose
|
|
1386
2082
|
if (credential?.credentialStatus?.statusPurpose !== 'suspension')
|
|
1387
2083
|
throw new Error('[did-provider-cheqd]: suspension: Invalid status purpose');
|
|
1388
|
-
// fetch status list 2021
|
|
1389
|
-
const
|
|
1390
|
-
// detect if encrypted
|
|
1391
|
-
const isEncrypted = function () {
|
|
1392
|
-
switch (metadata.mediaType) {
|
|
1393
|
-
case 'application/octet-stream':
|
|
1394
|
-
return true;
|
|
1395
|
-
case 'application/gzip':
|
|
1396
|
-
return false;
|
|
1397
|
-
default:
|
|
1398
|
-
throw new Error(`[did-provider-cheqd]: suspension: Unsupported media type: ${metadata.mediaType}`);
|
|
1399
|
-
}
|
|
1400
|
-
}();
|
|
2084
|
+
// fetch status list 2021
|
|
2085
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credential));
|
|
1401
2086
|
// early return, if encrypted and no decryption key provided
|
|
1402
|
-
if (
|
|
2087
|
+
if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey)
|
|
1403
2088
|
throw new Error('[did-provider-cheqd]: suspension: symmetricKey is required, if status list 2021 is encrypted');
|
|
1404
2089
|
// fetch status list 2021 inscribed in credential
|
|
1405
2090
|
const statusList2021 = options?.topArgs?.fetchList
|
|
1406
2091
|
? (await async function () {
|
|
1407
2092
|
// if not encrypted, return bitstring
|
|
1408
|
-
if (!
|
|
1409
|
-
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');
|
|
1410
2097
|
// otherwise, decrypt and return bitstring
|
|
1411
2098
|
const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.fetchStatusList2021(credential, true));
|
|
1412
2099
|
// decrypt
|
|
1413
2100
|
return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
|
|
1414
2101
|
}())
|
|
1415
2102
|
: (await async function () {
|
|
2103
|
+
// transcode to base64url, if needed
|
|
2104
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
2105
|
+
? publishedList.StatusList2021.encodedList
|
|
2106
|
+
: (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
|
|
1416
2107
|
// if status list 2021 is not fetched, read from file
|
|
1417
2108
|
if (options?.statusListFile) {
|
|
1418
2109
|
// if not encrypted, return bitstring
|
|
1419
|
-
if (!
|
|
1420
|
-
|
|
2110
|
+
if (!publishedList.metadata.encrypted) {
|
|
2111
|
+
// construct encoded status list
|
|
2112
|
+
const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
|
|
2113
|
+
// validate against published list
|
|
2114
|
+
if (encoded !== publishedListTranscoded)
|
|
2115
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
2116
|
+
// return encoded
|
|
2117
|
+
return encoded;
|
|
2118
|
+
}
|
|
1421
2119
|
// otherwise, decrypt and return bitstring
|
|
1422
2120
|
const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
|
|
1423
2121
|
// decrypt
|
|
1424
|
-
|
|
2122
|
+
const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
|
|
2123
|
+
// validate against published list
|
|
2124
|
+
if (decrypted !== publishedListTranscoded)
|
|
2125
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
2126
|
+
// return decrypted
|
|
2127
|
+
return decrypted;
|
|
1425
2128
|
}
|
|
1426
2129
|
if (!options?.statusListInlineBitstring)
|
|
1427
|
-
throw new Error('[did-provider-cheqd]:
|
|
2130
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
|
|
2131
|
+
// validate against published list
|
|
2132
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded)
|
|
2133
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
|
|
1428
2134
|
// otherwise, read from inline bitstring
|
|
1429
2135
|
return options?.statusListInlineBitstring;
|
|
1430
2136
|
}());
|
|
@@ -1464,7 +2170,45 @@ class Cheqd {
|
|
|
1464
2170
|
{ encryptedString, encryptedSymmetricKey, symmetricKey: (0, uint8arrays_1.toString)(symmetricKey, 'hex') }
|
|
1465
2171
|
];
|
|
1466
2172
|
}())
|
|
1467
|
-
:
|
|
2173
|
+
: (await async function () {
|
|
2174
|
+
// validate encoding, if provided
|
|
2175
|
+
if (options?.publishOptions?.statusListEncoding && !Object.values(cheqd_did_provider_js_1.DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
2176
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
|
|
2177
|
+
}
|
|
2178
|
+
// validate validUntil, if provided
|
|
2179
|
+
if (options?.publishOptions?.statusListValidUntil) {
|
|
2180
|
+
// validate validUntil as string
|
|
2181
|
+
if (typeof options?.publishOptions?.statusListValidUntil !== 'string')
|
|
2182
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)');
|
|
2183
|
+
// validate validUntil as date
|
|
2184
|
+
if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil)))
|
|
2185
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)');
|
|
2186
|
+
// validate validUntil as future date
|
|
2187
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) < new Date())
|
|
2188
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)');
|
|
2189
|
+
// validate validUntil towards validFrom
|
|
2190
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom))
|
|
2191
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)');
|
|
2192
|
+
}
|
|
2193
|
+
// define status list content
|
|
2194
|
+
const content = {
|
|
2195
|
+
StatusList2021: {
|
|
2196
|
+
type: publishedList.StatusList2021.type,
|
|
2197
|
+
encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options.publishOptions.statusListEncoding),
|
|
2198
|
+
validFrom: publishedList.StatusList2021.validFrom,
|
|
2199
|
+
validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
|
|
2200
|
+
},
|
|
2201
|
+
metadata: {
|
|
2202
|
+
encoding: options?.publishOptions?.statusListEncoding || publishedList.metadata.encoding,
|
|
2203
|
+
encrypted: false,
|
|
2204
|
+
}
|
|
2205
|
+
};
|
|
2206
|
+
// return tuple of publish result and encryption relevant metadata
|
|
2207
|
+
return [
|
|
2208
|
+
await Cheqd.publishStatusList2021((0, uint8arrays_1.fromString)(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
|
|
2209
|
+
undefined
|
|
2210
|
+
];
|
|
2211
|
+
}());
|
|
1468
2212
|
// early exit, if publish failed
|
|
1469
2213
|
if (!scoped[0])
|
|
1470
2214
|
throw new Error('[did-provider-cheqd]: suspension: Failed to publish status list 2021');
|
|
@@ -1475,7 +2219,7 @@ class Cheqd {
|
|
|
1475
2219
|
return {
|
|
1476
2220
|
suspended: true,
|
|
1477
2221
|
published: topArgs?.publish ? true : undefined,
|
|
1478
|
-
statusList: topArgs?.returnUpdatedStatusList ?
|
|
2222
|
+
statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credential) : undefined,
|
|
1479
2223
|
encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await (0, helpers_js_1.blobToHexString)(published?.[1]?.encryptedString) : undefined,
|
|
1480
2224
|
encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? published?.[1]?.encryptedSymmetricKey : undefined,
|
|
1481
2225
|
symmetricKey: topArgs?.returnSymmetricKey ? published?.[1]?.symmetricKey : undefined,
|
|
@@ -1483,56 +2227,264 @@ class Cheqd {
|
|
|
1483
2227
|
};
|
|
1484
2228
|
}
|
|
1485
2229
|
catch (error) {
|
|
1486
|
-
// silent fail + early exit
|
|
2230
|
+
// silent fail + early exit
|
|
1487
2231
|
console.error(error);
|
|
1488
2232
|
return { suspended: false, error: error };
|
|
1489
2233
|
}
|
|
1490
2234
|
}
|
|
2235
|
+
static async suspendCredentials(credentials, options) {
|
|
2236
|
+
// validate credentials - case: empty
|
|
2237
|
+
if (!credentials.length || credentials.length === 0)
|
|
2238
|
+
throw new Error('[did-provider-cheqd]: suspension: No credentials provided');
|
|
2239
|
+
// validate credentials - case: consistent issuer
|
|
2240
|
+
if (credentials.map((credential) => {
|
|
2241
|
+
return (credential.issuer.id)
|
|
2242
|
+
? credential.issuer.id
|
|
2243
|
+
: credential.issuer;
|
|
2244
|
+
}).filter((value, _, self) => value && value !== self[0]).length > 0)
|
|
2245
|
+
throw new Error('[did-provider-cheqd]: suspension: Credentials must be issued by the same issuer');
|
|
2246
|
+
// validate credentials - case: status list index
|
|
2247
|
+
if (credentials.map((credential) => credential.credentialStatus.statusListIndex).filter((value, index, self) => self.indexOf(value) !== index).length > 0)
|
|
2248
|
+
throw new Error('[did-provider-cheqd]: suspension: Credentials must have unique status list index');
|
|
2249
|
+
// validate credentials - case: status purpose
|
|
2250
|
+
if (!credentials.every((credential) => credential.credentialStatus?.statusPurpose === 'suspension'))
|
|
2251
|
+
throw new Error('[did-provider-cheqd]: suspension: Invalid status purpose');
|
|
2252
|
+
// validate credentials - case: status list id
|
|
2253
|
+
const remote = credentials[0].credentialStatus?.id
|
|
2254
|
+
? credentials[0].credentialStatus.id.split('#')[0]
|
|
2255
|
+
: (function () {
|
|
2256
|
+
throw new Error('[did-provider-cheqd]: suspension: Invalid status list id');
|
|
2257
|
+
}());
|
|
2258
|
+
// validate credentials - case: status list id format
|
|
2259
|
+
if (!exports.RemoteListPattern.test(remote))
|
|
2260
|
+
throw new Error('[did-provider-cheqd]: suspension: Invalid status list id format: expected: https://<optional_subdomain>.<sld>.<tld>/1.0/identifiers/<did:cheqd:<namespace>:<method_specific_id>>?resourceName=<resource_name>&resourceType=<resource_type>');
|
|
2261
|
+
if (!credentials.every((credential) => {
|
|
2262
|
+
return credential.credentialStatus.id.split('#')[0] === remote;
|
|
2263
|
+
}))
|
|
2264
|
+
throw new Error('[did-provider-cheqd]: suspension: Credentials must belong to the same status list');
|
|
2265
|
+
// validate credentials - case: status list type
|
|
2266
|
+
if (!credentials.every((credential) => credential.credentialStatus?.type === 'StatusList2021Entry'))
|
|
2267
|
+
throw new Error('[did-provider-cheqd]: suspension: Invalid status list type');
|
|
2268
|
+
try {
|
|
2269
|
+
// fetch status list 2021
|
|
2270
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credentials[0]));
|
|
2271
|
+
// early return, if encrypted and no decryption key provided
|
|
2272
|
+
if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey)
|
|
2273
|
+
throw new Error('[did-provider-cheqd]: suspension: symmetricKey is required, if status list 2021 is encrypted');
|
|
2274
|
+
// fetch status list 2021 inscribed in credential
|
|
2275
|
+
const statusList2021 = options?.topArgs?.fetchList
|
|
2276
|
+
? (await async function () {
|
|
2277
|
+
// if not encrypted, return bitstring
|
|
2278
|
+
if (!publishedList.metadata.encrypted)
|
|
2279
|
+
return publishedList.metadata.encoding === 'base64url'
|
|
2280
|
+
? publishedList.StatusList2021.encodedList
|
|
2281
|
+
: (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
|
|
2282
|
+
// otherwise, decrypt and return bitstring
|
|
2283
|
+
const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.fetchStatusList2021(credentials[0], true));
|
|
2284
|
+
// decrypt
|
|
2285
|
+
return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
|
|
2286
|
+
}())
|
|
2287
|
+
: (await async function () {
|
|
2288
|
+
// transcode to base64url, if needed
|
|
2289
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
2290
|
+
? publishedList.StatusList2021.encodedList
|
|
2291
|
+
: (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
|
|
2292
|
+
// if status list 2021 is not fetched, read from file
|
|
2293
|
+
if (options?.statusListFile) {
|
|
2294
|
+
// if not encrypted, return bitstring
|
|
2295
|
+
if (!publishedList.metadata.encrypted) {
|
|
2296
|
+
// construct encoded status list
|
|
2297
|
+
const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
|
|
2298
|
+
// validate against published list
|
|
2299
|
+
if (encoded !== publishedListTranscoded)
|
|
2300
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
2301
|
+
// return encoded
|
|
2302
|
+
return encoded;
|
|
2303
|
+
}
|
|
2304
|
+
// otherwise, decrypt and return bitstring
|
|
2305
|
+
const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
|
|
2306
|
+
// decrypt
|
|
2307
|
+
const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
|
|
2308
|
+
// validate against published list
|
|
2309
|
+
if (decrypted !== publishedListTranscoded)
|
|
2310
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
2311
|
+
// return decrypted
|
|
2312
|
+
return decrypted;
|
|
2313
|
+
}
|
|
2314
|
+
if (!options?.statusListInlineBitstring)
|
|
2315
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
|
|
2316
|
+
// validate against published list
|
|
2317
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded)
|
|
2318
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
|
|
2319
|
+
// otherwise, read from inline bitstring
|
|
2320
|
+
return options?.statusListInlineBitstring;
|
|
2321
|
+
}());
|
|
2322
|
+
// parse status list 2021
|
|
2323
|
+
const statusList = await vc_status_list_1.StatusList.decode({ encodedList: statusList2021 });
|
|
2324
|
+
// initiate bulk suspension
|
|
2325
|
+
const suspended = await Promise.allSettled(credentials.map((credential) => {
|
|
2326
|
+
return async function () {
|
|
2327
|
+
// early return, if no credential status
|
|
2328
|
+
if (!credential.credentialStatus)
|
|
2329
|
+
return { suspended: false };
|
|
2330
|
+
// early exit, if credential is already suspended
|
|
2331
|
+
if (statusList.getStatus(Number(credential.credentialStatus.statusListIndex)))
|
|
2332
|
+
return { suspended: false };
|
|
2333
|
+
// update suspension status
|
|
2334
|
+
statusList.setStatus(Number(credential.credentialStatus.statusListIndex), true);
|
|
2335
|
+
// return suspension status
|
|
2336
|
+
return { suspended: true };
|
|
2337
|
+
}();
|
|
2338
|
+
}));
|
|
2339
|
+
// revert bulk ops, if some failed
|
|
2340
|
+
if (suspended.some((result) => result.status === 'fulfilled' && !result.value.suspended))
|
|
2341
|
+
throw new Error(`[did-provider-cheqd]: suspension: Bulk suspension failed: already suspended credentials in suspension bundle: raw log: ${JSON.stringify(suspended.map((result) => ({ suspended: result.status === 'fulfilled' ? result.value.suspended : false })))}`);
|
|
2342
|
+
// set in-memory status list ref
|
|
2343
|
+
const bitstring = await statusList.encode();
|
|
2344
|
+
// cast top-level args
|
|
2345
|
+
const topArgs = options?.topArgs;
|
|
2346
|
+
// write status list 2021 to file, if provided
|
|
2347
|
+
if (topArgs?.writeToFile) {
|
|
2348
|
+
await Cheqd.writeFile((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options?.statusListFile);
|
|
2349
|
+
}
|
|
2350
|
+
// publish status list 2021, if provided
|
|
2351
|
+
const published = topArgs?.publish
|
|
2352
|
+
? (await async function () {
|
|
2353
|
+
// fetch status list 2021 metadata
|
|
2354
|
+
const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(credentials[0]);
|
|
2355
|
+
// publish status list 2021 as new version
|
|
2356
|
+
const scoped = topArgs.publishEncrypted
|
|
2357
|
+
? (await async function () {
|
|
2358
|
+
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
2359
|
+
const lit = await lit_protocol_js_1.LitProtocol.create({
|
|
2360
|
+
chain: options?.topArgs?.bootstrapOptions?.chain,
|
|
2361
|
+
litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
|
|
2362
|
+
});
|
|
2363
|
+
// encrypt
|
|
2364
|
+
const { encryptedString, encryptedSymmetricKey, symmetricKey } = await lit.encrypt(bitstring, options?.topArgs?.encryptionOptions?.unifiedAccessControlConditions, true);
|
|
2365
|
+
// return tuple of publish result and encryption relevant metadata
|
|
2366
|
+
return [
|
|
2367
|
+
await Cheqd.publishStatusList2021(new Uint8Array(await encryptedString.arrayBuffer()), statusListMetadata, options?.publishOptions),
|
|
2368
|
+
{ encryptedString, encryptedSymmetricKey, symmetricKey: (0, uint8arrays_1.toString)(symmetricKey, 'hex') }
|
|
2369
|
+
];
|
|
2370
|
+
}())
|
|
2371
|
+
: (await async function () {
|
|
2372
|
+
// validate encoding, if provided
|
|
2373
|
+
if (options?.publishOptions?.statusListEncoding && !Object.values(cheqd_did_provider_js_1.DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
2374
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
|
|
2375
|
+
}
|
|
2376
|
+
// validate validUntil, if provided
|
|
2377
|
+
if (options?.publishOptions?.statusListValidUntil) {
|
|
2378
|
+
// validate validUntil as string
|
|
2379
|
+
if (typeof options?.publishOptions?.statusListValidUntil !== 'string')
|
|
2380
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)');
|
|
2381
|
+
// validate validUntil as date
|
|
2382
|
+
if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil)))
|
|
2383
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)');
|
|
2384
|
+
// validate validUntil as future date
|
|
2385
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) < new Date())
|
|
2386
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)');
|
|
2387
|
+
// validate validUntil towards validFrom
|
|
2388
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom))
|
|
2389
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)');
|
|
2390
|
+
}
|
|
2391
|
+
// define status list content
|
|
2392
|
+
const content = {
|
|
2393
|
+
StatusList2021: {
|
|
2394
|
+
type: publishedList.StatusList2021.type,
|
|
2395
|
+
encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options.publishOptions.statusListEncoding),
|
|
2396
|
+
validFrom: publishedList.StatusList2021.validFrom,
|
|
2397
|
+
validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
|
|
2398
|
+
},
|
|
2399
|
+
metadata: {
|
|
2400
|
+
encoding: options?.publishOptions?.statusListEncoding || publishedList.metadata.encoding,
|
|
2401
|
+
encrypted: false,
|
|
2402
|
+
}
|
|
2403
|
+
};
|
|
2404
|
+
// return tuple of publish result and encryption relevant metadata
|
|
2405
|
+
return [
|
|
2406
|
+
await Cheqd.publishStatusList2021((0, uint8arrays_1.fromString)(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
|
|
2407
|
+
undefined
|
|
2408
|
+
];
|
|
2409
|
+
}());
|
|
2410
|
+
// early exit, if publish failed
|
|
2411
|
+
if (!scoped[0])
|
|
2412
|
+
throw new Error('[did-provider-cheqd]: suspension: Failed to publish status list 2021');
|
|
2413
|
+
// return publish result
|
|
2414
|
+
return scoped;
|
|
2415
|
+
}())
|
|
2416
|
+
: undefined;
|
|
2417
|
+
return {
|
|
2418
|
+
suspended: suspended.map((result) => result.status === 'fulfilled' ? result.value.suspended : false),
|
|
2419
|
+
published: topArgs?.publish ? true : undefined,
|
|
2420
|
+
statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credentials[0]) : undefined,
|
|
2421
|
+
encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await (0, helpers_js_1.blobToHexString)(published?.[1]?.encryptedString) : undefined,
|
|
2422
|
+
encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? published?.[1]?.encryptedSymmetricKey : undefined,
|
|
2423
|
+
symmetricKey: topArgs?.returnSymmetricKey ? published?.[1]?.symmetricKey : undefined,
|
|
2424
|
+
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(credentials[0]) : undefined
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
catch (error) {
|
|
2428
|
+
// silent fail + early exit
|
|
2429
|
+
console.error(error);
|
|
2430
|
+
return { suspended: [], error: error };
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
1491
2433
|
static async unsuspendCredential(credential, options) {
|
|
1492
2434
|
try {
|
|
1493
2435
|
// validate status purpose
|
|
1494
2436
|
if (credential?.credentialStatus?.statusPurpose !== 'suspension')
|
|
1495
2437
|
throw new Error('[did-provider-cheqd]: unsuspension: Invalid status purpose');
|
|
1496
|
-
// fetch status list 2021
|
|
1497
|
-
const
|
|
1498
|
-
// detect if encrypted
|
|
1499
|
-
const isEncrypted = function () {
|
|
1500
|
-
switch (metadata.mediaType) {
|
|
1501
|
-
case 'application/octet-stream':
|
|
1502
|
-
return true;
|
|
1503
|
-
case 'application/gzip':
|
|
1504
|
-
return false;
|
|
1505
|
-
default:
|
|
1506
|
-
throw new Error(`[did-provider-cheqd]: unsuspension: Unsupported media type: ${metadata.mediaType}`);
|
|
1507
|
-
}
|
|
1508
|
-
}();
|
|
2438
|
+
// fetch status list 2021
|
|
2439
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credential));
|
|
1509
2440
|
// early return, if encrypted and no decryption key provided
|
|
1510
|
-
if (
|
|
1511
|
-
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');
|
|
1512
2443
|
// fetch status list 2021 inscribed in credential
|
|
1513
2444
|
const statusList2021 = options?.topArgs?.fetchList
|
|
1514
2445
|
? (await async function () {
|
|
1515
2446
|
// if not encrypted, return bitstring
|
|
1516
|
-
if (!
|
|
1517
|
-
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');
|
|
1518
2451
|
// otherwise, decrypt and return bitstring
|
|
1519
2452
|
const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.fetchStatusList2021(credential, true));
|
|
1520
2453
|
// decrypt
|
|
1521
2454
|
return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
|
|
1522
2455
|
}())
|
|
1523
2456
|
: (await async function () {
|
|
2457
|
+
// transcode to base64url, if needed
|
|
2458
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
2459
|
+
? publishedList.StatusList2021.encodedList
|
|
2460
|
+
: (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
|
|
1524
2461
|
// if status list 2021 is not fetched, read from file
|
|
1525
2462
|
if (options?.statusListFile) {
|
|
1526
2463
|
// if not encrypted, return bitstring
|
|
1527
|
-
if (!
|
|
1528
|
-
|
|
2464
|
+
if (!publishedList.metadata.encrypted) {
|
|
2465
|
+
// construct encoded status list
|
|
2466
|
+
const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
|
|
2467
|
+
// validate against published list
|
|
2468
|
+
if (encoded !== publishedListTranscoded)
|
|
2469
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
2470
|
+
// return encoded
|
|
2471
|
+
return encoded;
|
|
2472
|
+
}
|
|
1529
2473
|
// otherwise, decrypt and return bitstring
|
|
1530
2474
|
const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
|
|
1531
2475
|
// decrypt
|
|
1532
|
-
|
|
2476
|
+
const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
|
|
2477
|
+
// validate against published list
|
|
2478
|
+
if (decrypted !== publishedListTranscoded)
|
|
2479
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
2480
|
+
// return decrypted
|
|
2481
|
+
return decrypted;
|
|
1533
2482
|
}
|
|
1534
2483
|
if (!options?.statusListInlineBitstring)
|
|
1535
|
-
throw new Error('[did-provider-cheqd]:
|
|
2484
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
|
|
2485
|
+
// validate against published list
|
|
2486
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded)
|
|
2487
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
|
|
1536
2488
|
// otherwise, read from inline bitstring
|
|
1537
2489
|
return options?.statusListInlineBitstring;
|
|
1538
2490
|
}());
|
|
@@ -1572,7 +2524,45 @@ class Cheqd {
|
|
|
1572
2524
|
{ encryptedString, encryptedSymmetricKey, symmetricKey: (0, uint8arrays_1.toString)(symmetricKey, 'hex') }
|
|
1573
2525
|
];
|
|
1574
2526
|
}())
|
|
1575
|
-
:
|
|
2527
|
+
: (await async function () {
|
|
2528
|
+
// validate encoding, if provided
|
|
2529
|
+
if (options?.publishOptions?.statusListEncoding && !Object.values(cheqd_did_provider_js_1.DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
2530
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
|
|
2531
|
+
}
|
|
2532
|
+
// validate validUntil, if provided
|
|
2533
|
+
if (options?.publishOptions?.statusListValidUntil) {
|
|
2534
|
+
// validate validUntil as string
|
|
2535
|
+
if (typeof options?.publishOptions?.statusListValidUntil !== 'string')
|
|
2536
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)');
|
|
2537
|
+
// validate validUntil as date
|
|
2538
|
+
if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil)))
|
|
2539
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)');
|
|
2540
|
+
// validate validUntil as future date
|
|
2541
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) < new Date())
|
|
2542
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)');
|
|
2543
|
+
// validate validUntil towards validFrom
|
|
2544
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom))
|
|
2545
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)');
|
|
2546
|
+
}
|
|
2547
|
+
// define status list content
|
|
2548
|
+
const content = {
|
|
2549
|
+
StatusList2021: {
|
|
2550
|
+
type: publishedList.StatusList2021.type,
|
|
2551
|
+
encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options.publishOptions.statusListEncoding),
|
|
2552
|
+
validFrom: publishedList.StatusList2021.validFrom,
|
|
2553
|
+
validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
|
|
2554
|
+
},
|
|
2555
|
+
metadata: {
|
|
2556
|
+
encoding: options?.publishOptions?.statusListEncoding || publishedList.metadata.encoding,
|
|
2557
|
+
encrypted: false,
|
|
2558
|
+
}
|
|
2559
|
+
};
|
|
2560
|
+
// return tuple of publish result and encryption relevant metadata
|
|
2561
|
+
return [
|
|
2562
|
+
await Cheqd.publishStatusList2021((0, uint8arrays_1.fromString)(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
|
|
2563
|
+
undefined
|
|
2564
|
+
];
|
|
2565
|
+
}());
|
|
1576
2566
|
// early exit, if publish failed
|
|
1577
2567
|
if (!scoped[0])
|
|
1578
2568
|
throw new Error('[did-provider-cheqd]: unsuspension: Failed to publish status list 2021');
|
|
@@ -1583,7 +2573,7 @@ class Cheqd {
|
|
|
1583
2573
|
return {
|
|
1584
2574
|
unsuspended: true,
|
|
1585
2575
|
published: topArgs?.publish ? true : undefined,
|
|
1586
|
-
statusList: topArgs?.returnUpdatedStatusList ?
|
|
2576
|
+
statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credential) : undefined,
|
|
1587
2577
|
encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await (0, helpers_js_1.blobToHexString)(published?.[1]?.encryptedString) : undefined,
|
|
1588
2578
|
encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? published?.[1]?.encryptedSymmetricKey : undefined,
|
|
1589
2579
|
symmetricKey: topArgs?.returnSymmetricKey ? published?.[1]?.symmetricKey : undefined,
|
|
@@ -1591,40 +2581,229 @@ class Cheqd {
|
|
|
1591
2581
|
};
|
|
1592
2582
|
}
|
|
1593
2583
|
catch (error) {
|
|
1594
|
-
// silent fail + early exit
|
|
2584
|
+
// silent fail + early exit
|
|
1595
2585
|
console.error(error);
|
|
1596
2586
|
return { unsuspended: false, error: error };
|
|
1597
2587
|
}
|
|
1598
2588
|
}
|
|
2589
|
+
static async unsuspendCredentials(credentials, options) {
|
|
2590
|
+
// validate credentials - case: empty
|
|
2591
|
+
if (!credentials.length || credentials.length === 0)
|
|
2592
|
+
throw new Error('[did-provider-cheqd]: unsuspension: No credentials provided');
|
|
2593
|
+
// validate credentials - case: consistent issuer
|
|
2594
|
+
if (credentials.map((credential) => {
|
|
2595
|
+
return (credential.issuer.id)
|
|
2596
|
+
? credential.issuer.id
|
|
2597
|
+
: credential.issuer;
|
|
2598
|
+
}).filter((value, _, self) => value && value !== self[0]).length > 0)
|
|
2599
|
+
throw new Error('[did-provider-cheqd]: unsuspension: Credentials must be issued by the same issuer');
|
|
2600
|
+
// validate credentials - case: status list index
|
|
2601
|
+
if (credentials.map((credential) => credential.credentialStatus.statusListIndex).filter((value, index, self) => self.indexOf(value) !== index).length > 0)
|
|
2602
|
+
throw new Error('[did-provider-cheqd]: unsuspension: Credentials must have unique status list index');
|
|
2603
|
+
// validate credentials - case: status purpose
|
|
2604
|
+
if (!credentials.every((credential) => credential.credentialStatus?.statusPurpose === 'suspension'))
|
|
2605
|
+
throw new Error('[did-provider-cheqd]: unsuspension: Invalid status purpose');
|
|
2606
|
+
// validate credentials - case: status list id
|
|
2607
|
+
const remote = credentials[0].credentialStatus?.id
|
|
2608
|
+
? credentials[0].credentialStatus.id.split('#')[0]
|
|
2609
|
+
: (function () {
|
|
2610
|
+
throw new Error('[did-provider-cheqd]: unsuspension: Invalid status list id');
|
|
2611
|
+
}());
|
|
2612
|
+
// validate credentials - case: status list id format
|
|
2613
|
+
if (!exports.RemoteListPattern.test(remote))
|
|
2614
|
+
throw new Error('[did-provider-cheqd]: unsuspension: Invalid status list id format: expected: https://<optional_subdomain>.<sld>.<tld>/1.0/identifiers/<did:cheqd:<namespace>:<method_specific_id>>?resourceName=<resource_name>&resourceType=<resource_type>');
|
|
2615
|
+
if (!credentials.every((credential) => {
|
|
2616
|
+
return credential.credentialStatus.id.split('#')[0] === remote;
|
|
2617
|
+
}))
|
|
2618
|
+
throw new Error('[did-provider-cheqd]: unsuspension: Credentials must belong to the same status list');
|
|
2619
|
+
// validate credentials - case: status list type
|
|
2620
|
+
if (!credentials.every((credential) => credential.credentialStatus?.type === 'StatusList2021Entry'))
|
|
2621
|
+
throw new Error('[did-provider-cheqd]: unsuspension: Invalid status list type');
|
|
2622
|
+
try {
|
|
2623
|
+
// fetch status list 2021
|
|
2624
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credentials[0]));
|
|
2625
|
+
// early return, if encrypted and no decryption key provided
|
|
2626
|
+
if (publishedList.metadata.encrypted && !options?.topArgs?.symmetricKey)
|
|
2627
|
+
throw new Error('[did-provider-cheqd]: unsuspension: symmetricKey is required, if status list 2021 is encrypted');
|
|
2628
|
+
// fetch status list 2021 inscribed in credential
|
|
2629
|
+
const statusList2021 = options?.topArgs?.fetchList
|
|
2630
|
+
? (await async function () {
|
|
2631
|
+
// if not encrypted, return bitstring
|
|
2632
|
+
if (!publishedList.metadata.encrypted)
|
|
2633
|
+
return publishedList.metadata.encoding === 'base64url'
|
|
2634
|
+
? publishedList.StatusList2021.encodedList
|
|
2635
|
+
: (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
|
|
2636
|
+
// otherwise, decrypt and return bitstring
|
|
2637
|
+
const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.fetchStatusList2021(credentials[0], true));
|
|
2638
|
+
// decrypt
|
|
2639
|
+
return await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
|
|
2640
|
+
}())
|
|
2641
|
+
: (await async function () {
|
|
2642
|
+
// transcode to base64url, if needed
|
|
2643
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
2644
|
+
? publishedList.StatusList2021.encodedList
|
|
2645
|
+
: (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
|
|
2646
|
+
// if status list 2021 is not fetched, read from file
|
|
2647
|
+
if (options?.statusListFile) {
|
|
2648
|
+
// if not encrypted, return bitstring
|
|
2649
|
+
if (!publishedList.metadata.encrypted) {
|
|
2650
|
+
// construct encoded status list
|
|
2651
|
+
const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
|
|
2652
|
+
// validate against published list
|
|
2653
|
+
if (encoded !== publishedListTranscoded)
|
|
2654
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
2655
|
+
// return encoded
|
|
2656
|
+
return encoded;
|
|
2657
|
+
}
|
|
2658
|
+
// otherwise, decrypt and return bitstring
|
|
2659
|
+
const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
|
|
2660
|
+
// decrypt
|
|
2661
|
+
const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
|
|
2662
|
+
// validate against published list
|
|
2663
|
+
if (decrypted !== publishedListTranscoded)
|
|
2664
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
2665
|
+
// return decrypted
|
|
2666
|
+
return decrypted;
|
|
2667
|
+
}
|
|
2668
|
+
if (!options?.statusListInlineBitstring)
|
|
2669
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
|
|
2670
|
+
// validate against published list
|
|
2671
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded)
|
|
2672
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
|
|
2673
|
+
// otherwise, read from inline bitstring
|
|
2674
|
+
return options?.statusListInlineBitstring;
|
|
2675
|
+
}());
|
|
2676
|
+
// parse status list 2021
|
|
2677
|
+
const statusList = await vc_status_list_1.StatusList.decode({ encodedList: statusList2021 });
|
|
2678
|
+
// initiate bulk unsuspension
|
|
2679
|
+
const unsuspended = await Promise.allSettled(credentials.map((credential) => {
|
|
2680
|
+
return async function () {
|
|
2681
|
+
// early return, if no credential status
|
|
2682
|
+
if (!credential.credentialStatus)
|
|
2683
|
+
return { unsuspended: false };
|
|
2684
|
+
// early exit, if credential is already unsuspended
|
|
2685
|
+
if (!statusList.getStatus(Number(credential.credentialStatus.statusListIndex)))
|
|
2686
|
+
return { unsuspended: true };
|
|
2687
|
+
// update unsuspension status
|
|
2688
|
+
statusList.setStatus(Number(credential.credentialStatus.statusListIndex), false);
|
|
2689
|
+
// return unsuspension status
|
|
2690
|
+
return { unsuspended: true };
|
|
2691
|
+
}();
|
|
2692
|
+
}));
|
|
2693
|
+
// revert bulk ops, if some failed
|
|
2694
|
+
if (unsuspended.some((result) => result.status === 'fulfilled' && !result.value.unsuspended))
|
|
2695
|
+
throw new Error(`[did-provider-cheqd]: unsuspension: Bulk unsuspension failed: already unsuspended credentials in unsuspension bundle: raw log: ${JSON.stringify(unsuspended.map((result) => ({ unsuspended: result.status === 'fulfilled' ? result.value.unsuspended : false })))}`);
|
|
2696
|
+
// set in-memory status list ref
|
|
2697
|
+
const bitstring = await statusList.encode();
|
|
2698
|
+
// cast top-level args
|
|
2699
|
+
const topArgs = options?.topArgs;
|
|
2700
|
+
// write status list 2021 to file, if provided
|
|
2701
|
+
if (topArgs?.writeToFile) {
|
|
2702
|
+
await Cheqd.writeFile((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options?.statusListFile);
|
|
2703
|
+
}
|
|
2704
|
+
// publish status list 2021, if provided
|
|
2705
|
+
const published = topArgs?.publish
|
|
2706
|
+
? (await async function () {
|
|
2707
|
+
// fetch status list 2021 metadata
|
|
2708
|
+
const statusListMetadata = await Cheqd.fetchStatusList2021Metadata(credentials[0]);
|
|
2709
|
+
// publish status list 2021 as new version
|
|
2710
|
+
const scoped = topArgs.publishEncrypted
|
|
2711
|
+
? (await async function () {
|
|
2712
|
+
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
2713
|
+
const lit = await lit_protocol_js_1.LitProtocol.create({
|
|
2714
|
+
chain: options?.topArgs?.bootstrapOptions?.chain,
|
|
2715
|
+
litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
|
|
2716
|
+
});
|
|
2717
|
+
// encrypt
|
|
2718
|
+
const { encryptedString, encryptedSymmetricKey, symmetricKey } = await lit.encrypt(bitstring, options?.topArgs?.encryptionOptions?.unifiedAccessControlConditions, true);
|
|
2719
|
+
// return tuple of publish result and encryption relevant metadata
|
|
2720
|
+
return [
|
|
2721
|
+
await Cheqd.publishStatusList2021(new Uint8Array(await encryptedString.arrayBuffer()), statusListMetadata, options?.publishOptions),
|
|
2722
|
+
{ encryptedString, encryptedSymmetricKey, symmetricKey: (0, uint8arrays_1.toString)(symmetricKey, 'hex') }
|
|
2723
|
+
];
|
|
2724
|
+
}())
|
|
2725
|
+
: (await async function () {
|
|
2726
|
+
// validate encoding, if provided
|
|
2727
|
+
if (options?.publishOptions?.statusListEncoding && !Object.values(cheqd_did_provider_js_1.DefaultStatusList2021Encodings).includes(options?.publishOptions?.statusListEncoding)) {
|
|
2728
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list encoding');
|
|
2729
|
+
}
|
|
2730
|
+
// validate validUntil, if provided
|
|
2731
|
+
if (options?.publishOptions?.statusListValidUntil) {
|
|
2732
|
+
// validate validUntil as string
|
|
2733
|
+
if (typeof options?.publishOptions?.statusListValidUntil !== 'string')
|
|
2734
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be string)');
|
|
2735
|
+
// validate validUntil as date
|
|
2736
|
+
if (isNaN(Date.parse(options?.publishOptions?.statusListValidUntil)))
|
|
2737
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be date)');
|
|
2738
|
+
// validate validUntil as future date
|
|
2739
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) < new Date())
|
|
2740
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be future date)');
|
|
2741
|
+
// validate validUntil towards validFrom
|
|
2742
|
+
if (new Date(options?.publishOptions?.statusListValidUntil) <= new Date(publishedList.StatusList2021.validFrom))
|
|
2743
|
+
throw new Error('[did-provider-cheqd]: revocation: Invalid status list validUntil (must be after validFrom)');
|
|
2744
|
+
}
|
|
2745
|
+
// define status list content
|
|
2746
|
+
const content = {
|
|
2747
|
+
StatusList2021: {
|
|
2748
|
+
type: publishedList.StatusList2021.type,
|
|
2749
|
+
encodedList: publishedList.metadata.encoding === 'base64url' ? bitstring : (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(bitstring, 'base64url'), options.publishOptions.statusListEncoding),
|
|
2750
|
+
validFrom: publishedList.StatusList2021.validFrom,
|
|
2751
|
+
validUntil: options?.publishOptions?.statusListValidUntil || publishedList.StatusList2021.validUntil
|
|
2752
|
+
},
|
|
2753
|
+
metadata: {
|
|
2754
|
+
encoding: options?.publishOptions?.statusListEncoding || publishedList.metadata.encoding,
|
|
2755
|
+
encrypted: false,
|
|
2756
|
+
}
|
|
2757
|
+
};
|
|
2758
|
+
// return tuple of publish result and encryption relevant metadata
|
|
2759
|
+
return [
|
|
2760
|
+
await Cheqd.publishStatusList2021((0, uint8arrays_1.fromString)(JSON.stringify(content), 'utf-8'), statusListMetadata, options?.publishOptions),
|
|
2761
|
+
undefined
|
|
2762
|
+
];
|
|
2763
|
+
}());
|
|
2764
|
+
// early exit, if publish failed
|
|
2765
|
+
if (!scoped[0])
|
|
2766
|
+
throw new Error('[did-provider-cheqd]: unsuspension: Failed to publish status list 2021');
|
|
2767
|
+
// return publish result
|
|
2768
|
+
return scoped;
|
|
2769
|
+
}())
|
|
2770
|
+
: undefined;
|
|
2771
|
+
return {
|
|
2772
|
+
unsuspended: unsuspended.map((result) => result.status === 'fulfilled' ? result.value.unsuspended : false),
|
|
2773
|
+
published: topArgs?.publish ? true : undefined,
|
|
2774
|
+
statusList: topArgs?.returnUpdatedStatusList ? await Cheqd.fetchStatusList2021(credentials[0]) : undefined,
|
|
2775
|
+
encryptedStatusList: topArgs?.returnUpdatedEncryptedStatusList ? await (0, helpers_js_1.blobToHexString)(published?.[1]?.encryptedString) : undefined,
|
|
2776
|
+
encryptedSymmetricKey: topArgs?.returnEncryptedSymmetricKey ? published?.[1]?.encryptedSymmetricKey : undefined,
|
|
2777
|
+
symmetricKey: topArgs?.returnSymmetricKey ? published?.[1]?.symmetricKey : undefined,
|
|
2778
|
+
resourceMetadata: topArgs?.returnStatusListMetadata ? await Cheqd.fetchStatusList2021Metadata(credentials[0]) : undefined
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
catch (error) {
|
|
2782
|
+
// silent fail + early exit
|
|
2783
|
+
console.error(error);
|
|
2784
|
+
return { unsuspended: [], error: error };
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
1599
2787
|
static async checkRevoked(credential, options = { fetchList: true }) {
|
|
1600
2788
|
// validate status purpose
|
|
1601
2789
|
if (credential.credentialStatus?.statusPurpose !== 'revocation') {
|
|
1602
2790
|
throw new Error(`[did-provider-cheqd]: revocation: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
|
|
1603
2791
|
}
|
|
1604
|
-
// fetch status list 2021
|
|
1605
|
-
const
|
|
1606
|
-
// detect if encrypted
|
|
1607
|
-
const isEncrypted = function () {
|
|
1608
|
-
switch (metadata.mediaType) {
|
|
1609
|
-
case 'application/octet-stream':
|
|
1610
|
-
return true;
|
|
1611
|
-
case 'application/gzip':
|
|
1612
|
-
return false;
|
|
1613
|
-
default:
|
|
1614
|
-
throw new Error(`[did-provider-cheqd]: revocation: Unsupported media type: ${metadata.mediaType}`);
|
|
1615
|
-
}
|
|
1616
|
-
}();
|
|
2792
|
+
// fetch status list 2021
|
|
2793
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credential));
|
|
1617
2794
|
// early return, if encrypted and decryption key is not provided
|
|
1618
|
-
if (
|
|
2795
|
+
if (publishedList.metadata.encrypted && !options?.topArgs?.encryptedSymmetricKey)
|
|
1619
2796
|
throw new Error('[did-provider-cheqd]: revocation: encryptedSymmetricKey is required, if status list 2021 is encrypted');
|
|
1620
2797
|
// fetch status list 2021 inscribed in credential
|
|
1621
2798
|
const statusList2021 = options?.topArgs?.fetchList
|
|
1622
2799
|
? (await async function () {
|
|
1623
2800
|
// if not encrypted, return bitstring
|
|
1624
|
-
if (!
|
|
1625
|
-
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');
|
|
1626
2805
|
// otherwise, decrypt and return bitstring
|
|
1627
|
-
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));
|
|
1628
2807
|
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
1629
2808
|
const lit = await lit_protocol_js_1.LitProtocol.create({
|
|
1630
2809
|
chain: options?.topArgs?.bootstrapOptions?.chain,
|
|
@@ -1634,23 +2813,37 @@ class Cheqd {
|
|
|
1634
2813
|
return await lit.decrypt(scopedRawBlob, options?.topArgs?.encryptedSymmetricKey, options?.topArgs?.decryptionOptions?.unifiedAccessControlConditions);
|
|
1635
2814
|
}())
|
|
1636
2815
|
: (await async function () {
|
|
2816
|
+
// transcode to base64url, if needed
|
|
2817
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
2818
|
+
? publishedList.StatusList2021.encodedList
|
|
2819
|
+
: (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
|
|
1637
2820
|
// if status list 2021 is not fetched, read from file
|
|
1638
2821
|
if (options?.statusListFile) {
|
|
1639
2822
|
// if not encrypted, return bitstring
|
|
1640
|
-
if (!
|
|
1641
|
-
|
|
2823
|
+
if (!publishedList.metadata.encrypted) {
|
|
2824
|
+
// construct encoded status list
|
|
2825
|
+
const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
|
|
2826
|
+
// validate against published list
|
|
2827
|
+
if (encoded !== publishedListTranscoded)
|
|
2828
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
2829
|
+
// return encoded
|
|
2830
|
+
return encoded;
|
|
2831
|
+
}
|
|
1642
2832
|
// otherwise, decrypt and return bitstring
|
|
1643
2833
|
const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
|
|
1644
|
-
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
1645
|
-
const lit = await lit_protocol_js_1.LitProtocol.create({
|
|
1646
|
-
chain: options?.topArgs?.bootstrapOptions?.chain,
|
|
1647
|
-
litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
|
|
1648
|
-
});
|
|
1649
2834
|
// decrypt
|
|
1650
|
-
|
|
2835
|
+
const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
|
|
2836
|
+
// validate against published list
|
|
2837
|
+
if (decrypted !== publishedListTranscoded)
|
|
2838
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
2839
|
+
// return decrypted
|
|
2840
|
+
return decrypted;
|
|
1651
2841
|
}
|
|
1652
2842
|
if (!options?.statusListInlineBitstring)
|
|
1653
|
-
throw new Error('
|
|
2843
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
|
|
2844
|
+
// validate against published list
|
|
2845
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded)
|
|
2846
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
|
|
1654
2847
|
// otherwise, read from inline bitstring
|
|
1655
2848
|
return options?.statusListInlineBitstring;
|
|
1656
2849
|
}());
|
|
@@ -1664,30 +2857,21 @@ class Cheqd {
|
|
|
1664
2857
|
if (credential.credentialStatus?.statusPurpose !== 'suspension') {
|
|
1665
2858
|
throw new Error(`[did-provider-cheqd]: suspension: Unsupported status purpose: ${credential.credentialStatus?.statusPurpose}`);
|
|
1666
2859
|
}
|
|
1667
|
-
// fetch status list 2021
|
|
1668
|
-
const
|
|
1669
|
-
// detect if encrypted
|
|
1670
|
-
const isEncrypted = function () {
|
|
1671
|
-
switch (metadata.mediaType) {
|
|
1672
|
-
case 'application/octet-stream':
|
|
1673
|
-
return true;
|
|
1674
|
-
case 'application/gzip':
|
|
1675
|
-
return false;
|
|
1676
|
-
default:
|
|
1677
|
-
throw new Error(`[did-provider-cheqd]: suspension: Unsupported media type: ${metadata.mediaType}`);
|
|
1678
|
-
}
|
|
1679
|
-
}();
|
|
2860
|
+
// fetch status list 2021
|
|
2861
|
+
const publishedList = (await Cheqd.fetchStatusList2021(credential));
|
|
1680
2862
|
// early return, if encrypted and decryption key is not provided
|
|
1681
|
-
if (
|
|
1682
|
-
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');
|
|
1683
2865
|
// fetch status list 2021 inscribed in credential
|
|
1684
2866
|
const statusList2021 = options?.topArgs?.fetchList
|
|
1685
2867
|
? (await async function () {
|
|
1686
2868
|
// if not encrypted, return bitstring
|
|
1687
|
-
if (!
|
|
1688
|
-
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');
|
|
1689
2873
|
// otherwise, decrypt and return bitstring
|
|
1690
|
-
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));
|
|
1691
2875
|
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
1692
2876
|
const lit = await lit_protocol_js_1.LitProtocol.create({
|
|
1693
2877
|
chain: options?.topArgs?.bootstrapOptions?.chain,
|
|
@@ -1697,23 +2881,37 @@ class Cheqd {
|
|
|
1697
2881
|
return await lit.decrypt(scopedRawBlob, options?.topArgs?.encryptedSymmetricKey, options?.topArgs?.decryptionOptions?.unifiedAccessControlConditions);
|
|
1698
2882
|
}())
|
|
1699
2883
|
: (await async function () {
|
|
2884
|
+
// transcode to base64url, if needed
|
|
2885
|
+
const publishedListTranscoded = publishedList.metadata.encoding === 'base64url'
|
|
2886
|
+
? publishedList.StatusList2021.encodedList
|
|
2887
|
+
: (0, uint8arrays_1.toString)((0, uint8arrays_1.fromString)(publishedList.StatusList2021.encodedList, publishedList.metadata.encoding), 'base64url');
|
|
1700
2888
|
// if status list 2021 is not fetched, read from file
|
|
1701
2889
|
if (options?.statusListFile) {
|
|
1702
2890
|
// if not encrypted, return bitstring
|
|
1703
|
-
if (!
|
|
1704
|
-
|
|
2891
|
+
if (!publishedList.metadata.encrypted) {
|
|
2892
|
+
// construct encoded status list
|
|
2893
|
+
const encoded = new vc_status_list_1.StatusList({ buffer: await Cheqd.getFile(options.statusListFile) }).encode();
|
|
2894
|
+
// validate against published list
|
|
2895
|
+
if (encoded !== publishedListTranscoded)
|
|
2896
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
2897
|
+
// return encoded
|
|
2898
|
+
return encoded;
|
|
2899
|
+
}
|
|
1705
2900
|
// otherwise, decrypt and return bitstring
|
|
1706
2901
|
const scopedRawBlob = await (0, helpers_js_1.toBlob)(await Cheqd.getFile(options.statusListFile));
|
|
1707
|
-
// instantiate dkg-threshold client, in which case lit-protocol is used
|
|
1708
|
-
const lit = await lit_protocol_js_1.LitProtocol.create({
|
|
1709
|
-
chain: options?.topArgs?.bootstrapOptions?.chain,
|
|
1710
|
-
litNetwork: options?.topArgs?.bootstrapOptions?.litNetwork
|
|
1711
|
-
});
|
|
1712
2902
|
// decrypt
|
|
1713
|
-
|
|
2903
|
+
const decrypted = await lit_protocol_js_1.LitProtocol.decryptDirect(scopedRawBlob, (0, uint8arrays_1.fromString)(options?.topArgs?.symmetricKey, 'hex'));
|
|
2904
|
+
// validate against published list
|
|
2905
|
+
if (decrypted !== publishedListTranscoded)
|
|
2906
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListFile does not match published status list 2021');
|
|
2907
|
+
// return decrypted
|
|
2908
|
+
return decrypted;
|
|
1714
2909
|
}
|
|
1715
2910
|
if (!options?.statusListInlineBitstring)
|
|
1716
|
-
throw new Error('
|
|
2911
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring is required, if statusListFile is not provided');
|
|
2912
|
+
// validate against published list
|
|
2913
|
+
if (options?.statusListInlineBitstring !== publishedListTranscoded)
|
|
2914
|
+
throw new Error('[did-provider-cheqd]: revocation: statusListInlineBitstring does not match published status list 2021');
|
|
1717
2915
|
// otherwise, read from inline bitstring
|
|
1718
2916
|
return options?.statusListInlineBitstring;
|
|
1719
2917
|
}());
|
|
@@ -1729,10 +2927,11 @@ class Cheqd {
|
|
|
1729
2927
|
id: options?.resourceId || (0, uuid_1.v4)(),
|
|
1730
2928
|
name: statusList2021Metadata.resourceName,
|
|
1731
2929
|
version: options?.resourceVersion || new Date().toISOString(),
|
|
1732
|
-
|
|
2930
|
+
alsoKnownAs: options?.resourceAlsoKnownAs || [],
|
|
2931
|
+
resourceType: statusList2021Metadata.resourceType,
|
|
1733
2932
|
data: statusList2021Raw
|
|
1734
2933
|
};
|
|
1735
|
-
return await options.context.agent[
|
|
2934
|
+
return await options.context.agent[BroadcastStatusList2021MethodName]({
|
|
1736
2935
|
kms: (await options.context.agent.keyManagerGetKeyManagementSystems())[0],
|
|
1737
2936
|
payload,
|
|
1738
2937
|
network: statusList2021Metadata.resourceURI.split(':')[2],
|
|
@@ -1750,25 +2949,26 @@ class Cheqd {
|
|
|
1750
2949
|
// validate credential status list status purpose
|
|
1751
2950
|
if (credential.credentialStatus.statusPurpose !== 'revocation' && credential.credentialStatus.statusPurpose !== 'suspension')
|
|
1752
2951
|
throw new Error('[did-provider-cheqd]: fetch status list: Credential status purpose is not valid');
|
|
1753
|
-
// validate credential status list status list credential
|
|
1754
|
-
if (!credential.credentialStatus.statusListCredential)
|
|
1755
|
-
throw new Error('[did-provider-cheqd]: fetch status list: Credential status list credential is not present');
|
|
1756
2952
|
// fetch status list 2021
|
|
1757
|
-
const
|
|
2953
|
+
const content = await (await fetch(credential.credentialStatus.id.split('#')[0])).json();
|
|
1758
2954
|
// return raw if requested
|
|
1759
|
-
if (returnRaw)
|
|
1760
|
-
return
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
return
|
|
2955
|
+
if (returnRaw) {
|
|
2956
|
+
return (0, uint8arrays_1.fromString)(content.StatusList2021.encodedList, content.metadata.encoding);
|
|
2957
|
+
}
|
|
2958
|
+
// otherwise, return content
|
|
2959
|
+
return content;
|
|
1764
2960
|
}
|
|
1765
2961
|
static async fetchStatusList2021Metadata(credential) {
|
|
1766
2962
|
// get base url
|
|
1767
|
-
const baseUrl = new URL(credential.credentialStatus
|
|
2963
|
+
const baseUrl = new URL(credential.credentialStatus.id.split('#')[0]);
|
|
1768
2964
|
// get resource name
|
|
1769
2965
|
const resourceName = baseUrl.searchParams.get('resourceName');
|
|
2966
|
+
// get resource type
|
|
2967
|
+
const resourceType = baseUrl.searchParams.get('resourceType');
|
|
1770
2968
|
// unset resource name
|
|
1771
2969
|
baseUrl.searchParams.delete('resourceName');
|
|
2970
|
+
// unset resource type
|
|
2971
|
+
baseUrl.searchParams.delete('resourceType');
|
|
1772
2972
|
// construct metadata url
|
|
1773
2973
|
const metadataUrl = `${baseUrl.toString()}/metadata`;
|
|
1774
2974
|
// fetch collection metadata
|
|
@@ -1777,7 +2977,7 @@ class Cheqd {
|
|
|
1777
2977
|
if (!collectionMetadata?.contentStream?.linkedResourceMetadata)
|
|
1778
2978
|
throw new Error('[did-provider-cheqd]: fetch status list metadata: No linked resources found');
|
|
1779
2979
|
// find relevant resources by resource name
|
|
1780
|
-
const resourceVersioning = collectionMetadata.contentStream.linkedResourceMetadata.filter((resource) => resource.resourceName === resourceName);
|
|
2980
|
+
const resourceVersioning = collectionMetadata.contentStream.linkedResourceMetadata.filter((resource) => resource.resourceName === resourceName && resource.resourceType === resourceType);
|
|
1781
2981
|
// early exit if no relevant resources
|
|
1782
2982
|
if (!resourceVersioning.length || resourceVersioning.length === 0)
|
|
1783
2983
|
throw new Error(`[did-provider-cheqd]: fetch status list metadata: No relevant resources found by resource name ${resourceName}`);
|
|
@@ -1828,6 +3028,16 @@ class Cheqd {
|
|
|
1828
3028
|
});
|
|
1829
3029
|
});
|
|
1830
3030
|
}
|
|
3031
|
+
static async decodeCredentialJWT(jwt) {
|
|
3032
|
+
const decodedCredential = (0, did_jwt_1.decodeJWT)(jwt);
|
|
3033
|
+
// validate credential payload
|
|
3034
|
+
if (!decodedCredential.payload)
|
|
3035
|
+
throw new Error('[did-provider-cheqd]: decode jwt: decodedCredential.payload is required');
|
|
3036
|
+
// validate credential payload vc property as VerifiableCredential
|
|
3037
|
+
if (!decodedCredential.payload.vc)
|
|
3038
|
+
throw new Error('[did-provider-cheqd]: decode jwt: decodedCredential.payload.vc is required');
|
|
3039
|
+
return decodedCredential.payload.vc;
|
|
3040
|
+
}
|
|
1831
3041
|
}
|
|
1832
3042
|
exports.Cheqd = Cheqd;
|
|
1833
3043
|
//# sourceMappingURL=ICheqd.js.map
|