@didcid/keymaster 0.1.3 → 0.3.0
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/dist/cjs/index.cjs +0 -2
- package/dist/cjs/keymaster-client.cjs +26 -26
- package/dist/cjs/keymaster.cjs +279 -218
- package/dist/cjs/node.cjs +0 -2
- package/dist/esm/index.js +0 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/keymaster-client.js +26 -26
- package/dist/esm/keymaster-client.js.map +1 -1
- package/dist/esm/keymaster.js +220 -183
- package/dist/esm/keymaster.js.map +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/keymaster-client.d.ts +16 -14
- package/dist/types/keymaster.d.ts +22 -21
- package/dist/types/types.d.ts +26 -31
- package/package.json +2 -10
- package/dist/cjs/search-client.cjs +0 -87
- package/dist/esm/search-client.js +0 -81
- package/dist/esm/search-client.js.map +0 -1
- package/dist/types/search-client.d.ts +0 -9
package/dist/esm/keymaster.js
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { imageSize } from 'image-size';
|
|
2
2
|
import { fileTypeFromBuffer } from 'file-type';
|
|
3
|
+
import { base64url } from 'multiformats/bases/base64';
|
|
3
4
|
import { InvalidDIDError, InvalidParameterError, KeymasterError, UnknownIDError } from '@didcid/common/errors';
|
|
4
5
|
import { isWalletEncFile, isWalletFile } from './db/typeGuards.js';
|
|
5
6
|
import { isValidDID } from '@didcid/ipfs/utils';
|
|
6
7
|
import { decMnemonic, encMnemonic } from "./encryption.js";
|
|
8
|
+
function hexToBase64url(hex) {
|
|
9
|
+
const bytes = Buffer.from(hex, 'hex');
|
|
10
|
+
return base64url.baseEncode(bytes);
|
|
11
|
+
}
|
|
12
|
+
function base64urlToHex(b64) {
|
|
13
|
+
const bytes = base64url.baseDecode(b64);
|
|
14
|
+
return Buffer.from(bytes).toString('hex');
|
|
15
|
+
}
|
|
7
16
|
const DefaultSchema = {
|
|
8
17
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
9
18
|
"type": "object",
|
|
@@ -38,7 +47,6 @@ export default class Keymaster {
|
|
|
38
47
|
gatekeeper;
|
|
39
48
|
db;
|
|
40
49
|
cipher;
|
|
41
|
-
searchEngine;
|
|
42
50
|
defaultRegistry;
|
|
43
51
|
ephemeralRegistry;
|
|
44
52
|
maxNameLength;
|
|
@@ -55,9 +63,6 @@ export default class Keymaster {
|
|
|
55
63
|
if (!options.cipher || !options.cipher.verifySig) {
|
|
56
64
|
throw new InvalidParameterError('options.cipher');
|
|
57
65
|
}
|
|
58
|
-
if (options.search && !options.search.search) {
|
|
59
|
-
throw new InvalidParameterError('options.search');
|
|
60
|
-
}
|
|
61
66
|
if (!options.passphrase) {
|
|
62
67
|
throw new InvalidParameterError('options.passphrase');
|
|
63
68
|
}
|
|
@@ -65,7 +70,6 @@ export default class Keymaster {
|
|
|
65
70
|
this.gatekeeper = options.gatekeeper;
|
|
66
71
|
this.db = options.wallet;
|
|
67
72
|
this.cipher = options.cipher;
|
|
68
|
-
this.searchEngine = options.search;
|
|
69
73
|
this.defaultRegistry = options.defaultRegistry || 'hyperswarm';
|
|
70
74
|
this.ephemeralRegistry = 'hyperswarm';
|
|
71
75
|
this.maxNameLength = options.maxNameLength || 32;
|
|
@@ -306,13 +310,15 @@ export default class Keymaster {
|
|
|
306
310
|
publicJwk: keypair.publicJwk,
|
|
307
311
|
};
|
|
308
312
|
const msgHash = this.cipher.hashJSON(operation);
|
|
309
|
-
const
|
|
313
|
+
const signatureHex = this.cipher.signHash(msgHash, keypair.privateJwk);
|
|
310
314
|
const signed = {
|
|
311
315
|
...operation,
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
+
proof: {
|
|
317
|
+
type: "EcdsaSecp256k1Signature2019",
|
|
318
|
+
created: new Date(0).toISOString(),
|
|
319
|
+
verificationMethod: "#key-1",
|
|
320
|
+
proofPurpose: "authentication",
|
|
321
|
+
proofValue: hexToBase64url(signatureHex),
|
|
316
322
|
}
|
|
317
323
|
};
|
|
318
324
|
const did = await this.gatekeeper.createDID(signed);
|
|
@@ -333,14 +339,15 @@ export default class Keymaster {
|
|
|
333
339
|
doc,
|
|
334
340
|
};
|
|
335
341
|
const msgHash = this.cipher.hashJSON(operation);
|
|
336
|
-
const
|
|
342
|
+
const signatureHex = this.cipher.signHash(msgHash, keypair.privateJwk);
|
|
337
343
|
const signed = {
|
|
338
344
|
...operation,
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
345
|
+
proof: {
|
|
346
|
+
type: "EcdsaSecp256k1Signature2019",
|
|
347
|
+
created: new Date().toISOString(),
|
|
348
|
+
verificationMethod: `${did}#key-1`,
|
|
349
|
+
proofPurpose: "authentication",
|
|
350
|
+
proofValue: hexToBase64url(signatureHex),
|
|
344
351
|
}
|
|
345
352
|
};
|
|
346
353
|
return await this.gatekeeper.updateDID(signed);
|
|
@@ -365,14 +372,15 @@ export default class Keymaster {
|
|
|
365
372
|
data: { backup: backup },
|
|
366
373
|
};
|
|
367
374
|
const msgHash = this.cipher.hashJSON(operation);
|
|
368
|
-
const
|
|
375
|
+
const signatureHex = this.cipher.signHash(msgHash, keypair.privateJwk);
|
|
369
376
|
const signed = {
|
|
370
377
|
...operation,
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
378
|
+
proof: {
|
|
379
|
+
type: "EcdsaSecp256k1Signature2019",
|
|
380
|
+
created: new Date().toISOString(),
|
|
381
|
+
verificationMethod: `${seedBank.didDocument?.id}#key-1`,
|
|
382
|
+
proofPurpose: "authentication",
|
|
383
|
+
proofValue: hexToBase64url(signatureHex),
|
|
376
384
|
}
|
|
377
385
|
};
|
|
378
386
|
const backupDID = await this.gatekeeper.createDID(signed);
|
|
@@ -553,7 +561,7 @@ export default class Keymaster {
|
|
|
553
561
|
controller: id.did,
|
|
554
562
|
data,
|
|
555
563
|
};
|
|
556
|
-
const signed = await this.
|
|
564
|
+
const signed = await this.addProof(operation, controller, "authentication");
|
|
557
565
|
const did = await this.gatekeeper.createDID(signed);
|
|
558
566
|
// Keep assets that will be garbage-collected out of the owned list
|
|
559
567
|
if (!validUntil) {
|
|
@@ -742,7 +750,7 @@ export default class Keymaster {
|
|
|
742
750
|
throw new InvalidParameterError('did not encrypted JSON');
|
|
743
751
|
}
|
|
744
752
|
}
|
|
745
|
-
async
|
|
753
|
+
async addProof(obj, controller, proofPurpose = "assertionMethod") {
|
|
746
754
|
if (obj == null) {
|
|
747
755
|
throw new InvalidParameterError('obj');
|
|
748
756
|
}
|
|
@@ -750,18 +758,23 @@ export default class Keymaster {
|
|
|
750
758
|
const id = await this.fetchIdInfo(controller);
|
|
751
759
|
const keypair = await this.fetchKeyPair(controller);
|
|
752
760
|
if (!keypair) {
|
|
753
|
-
throw new KeymasterError('
|
|
761
|
+
throw new KeymasterError('addProof: no keypair');
|
|
754
762
|
}
|
|
763
|
+
// Get the key fragment from the DID document
|
|
764
|
+
const doc = await this.resolveDID(id.did, { confirm: true });
|
|
765
|
+
const keyFragment = doc.didDocument?.verificationMethod?.[0]?.id || '#key-1';
|
|
755
766
|
try {
|
|
756
767
|
const msgHash = this.cipher.hashJSON(obj);
|
|
757
|
-
const
|
|
768
|
+
const signatureHex = this.cipher.signHash(msgHash, keypair.privateJwk);
|
|
769
|
+
const proofValue = hexToBase64url(signatureHex);
|
|
758
770
|
return {
|
|
759
771
|
...obj,
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
772
|
+
proof: {
|
|
773
|
+
type: "EcdsaSecp256k1Signature2019",
|
|
774
|
+
created: new Date().toISOString(),
|
|
775
|
+
verificationMethod: `${id.did}${keyFragment}`,
|
|
776
|
+
proofPurpose,
|
|
777
|
+
proofValue,
|
|
765
778
|
}
|
|
766
779
|
};
|
|
767
780
|
}
|
|
@@ -769,24 +782,27 @@ export default class Keymaster {
|
|
|
769
782
|
throw new InvalidParameterError('obj');
|
|
770
783
|
}
|
|
771
784
|
}
|
|
772
|
-
async
|
|
773
|
-
if (!obj?.
|
|
785
|
+
async verifyProof(obj) {
|
|
786
|
+
if (!obj?.proof) {
|
|
774
787
|
return false;
|
|
775
788
|
}
|
|
776
|
-
const {
|
|
777
|
-
if (
|
|
789
|
+
const { proof } = obj;
|
|
790
|
+
if (proof.type !== "EcdsaSecp256k1Signature2019") {
|
|
778
791
|
return false;
|
|
779
792
|
}
|
|
780
|
-
|
|
781
|
-
delete jsonCopy.signature;
|
|
782
|
-
const msgHash = this.cipher.hashJSON(jsonCopy);
|
|
783
|
-
if (signature.hash && signature.hash !== msgHash) {
|
|
793
|
+
if (!proof.verificationMethod) {
|
|
784
794
|
return false;
|
|
785
795
|
}
|
|
786
|
-
|
|
796
|
+
// Extract DID from verificationMethod
|
|
797
|
+
const [signerDid] = proof.verificationMethod.split('#');
|
|
798
|
+
const jsonCopy = JSON.parse(JSON.stringify(obj));
|
|
799
|
+
delete jsonCopy.proof;
|
|
800
|
+
const msgHash = this.cipher.hashJSON(jsonCopy);
|
|
801
|
+
const doc = await this.resolveDID(signerDid, { versionTime: proof.created });
|
|
787
802
|
const publicJwk = this.getPublicKeyJwk(doc);
|
|
788
803
|
try {
|
|
789
|
-
|
|
804
|
+
const signatureHex = base64urlToHex(proof.proofValue);
|
|
805
|
+
return this.cipher.verifySig(msgHash, signatureHex, publicJwk);
|
|
790
806
|
}
|
|
791
807
|
catch (error) {
|
|
792
808
|
return false;
|
|
@@ -815,7 +831,7 @@ export default class Keymaster {
|
|
|
815
831
|
else if (current.didDocumentRegistration?.type === 'asset') {
|
|
816
832
|
controller = current.didDocument?.controller;
|
|
817
833
|
}
|
|
818
|
-
const signed = await this.
|
|
834
|
+
const signed = await this.addProof(operation, controller, "authentication");
|
|
819
835
|
return this.gatekeeper.updateDID(signed);
|
|
820
836
|
}
|
|
821
837
|
async revokeDID(id) {
|
|
@@ -837,7 +853,7 @@ export default class Keymaster {
|
|
|
837
853
|
else if (current.didDocumentRegistration?.type === 'asset') {
|
|
838
854
|
controller = current.didDocument?.controller;
|
|
839
855
|
}
|
|
840
|
-
const signed = await this.
|
|
856
|
+
const signed = await this.addProof(operation, controller, "authentication");
|
|
841
857
|
const ok = await this.gatekeeper.deleteDID(signed);
|
|
842
858
|
if (ok && current.didDocument?.controller) {
|
|
843
859
|
await this.removeFromOwned(did, current.didDocument.controller);
|
|
@@ -917,9 +933,13 @@ export default class Keymaster {
|
|
|
917
933
|
}
|
|
918
934
|
const controller = docs.didDocument?.controller || docs.didDocument?.id;
|
|
919
935
|
const isOwned = await this.idInWallet(controller);
|
|
920
|
-
//
|
|
936
|
+
// Convert versionSequence string to numeric version
|
|
937
|
+
const versionSequence = docs.didDocumentMetadata?.versionSequence;
|
|
938
|
+
const version = versionSequence ? parseInt(versionSequence, 10) : undefined;
|
|
939
|
+
// Augment the DID document metadata with the DID ownership status and numeric version
|
|
921
940
|
docs.didDocumentMetadata = {
|
|
922
941
|
...docs.didDocumentMetadata,
|
|
942
|
+
version,
|
|
923
943
|
isOwned,
|
|
924
944
|
};
|
|
925
945
|
return docs;
|
|
@@ -1038,13 +1058,15 @@ export default class Keymaster {
|
|
|
1038
1058
|
publicJwk: keypair.publicJwk,
|
|
1039
1059
|
};
|
|
1040
1060
|
const msgHash = this.cipher.hashJSON(operation);
|
|
1041
|
-
const
|
|
1061
|
+
const signatureHex = this.cipher.signHash(msgHash, keypair.privateJwk);
|
|
1042
1062
|
const signed = {
|
|
1043
1063
|
...operation,
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1064
|
+
proof: {
|
|
1065
|
+
type: "EcdsaSecp256k1Signature2019",
|
|
1066
|
+
created: new Date().toISOString(),
|
|
1067
|
+
verificationMethod: "#key-1",
|
|
1068
|
+
proofPurpose: "authentication",
|
|
1069
|
+
proofValue: hexToBase64url(signatureHex),
|
|
1048
1070
|
},
|
|
1049
1071
|
};
|
|
1050
1072
|
return signed;
|
|
@@ -1098,10 +1120,10 @@ export default class Keymaster {
|
|
|
1098
1120
|
if (!registry) {
|
|
1099
1121
|
throw new InvalidParameterError('no registry found for agent DID');
|
|
1100
1122
|
}
|
|
1101
|
-
const
|
|
1123
|
+
const backupStoreDid = await this.createAsset({ backup: backup }, { registry, controller: name });
|
|
1102
1124
|
if (doc.didDocumentData) {
|
|
1103
1125
|
const currentData = doc.didDocumentData;
|
|
1104
|
-
const updatedData = { ...currentData,
|
|
1126
|
+
const updatedData = { ...currentData, backupStore: backupStoreDid };
|
|
1105
1127
|
return this.updateDID(name, { didDocumentData: updatedData });
|
|
1106
1128
|
}
|
|
1107
1129
|
return false;
|
|
@@ -1111,14 +1133,14 @@ export default class Keymaster {
|
|
|
1111
1133
|
const keypair = await this.hdKeyPair();
|
|
1112
1134
|
const doc = await this.resolveDID(did);
|
|
1113
1135
|
const docData = doc.didDocumentData;
|
|
1114
|
-
if (!docData.
|
|
1115
|
-
throw new InvalidDIDError('didDocumentData missing
|
|
1136
|
+
if (!docData.backupStore) {
|
|
1137
|
+
throw new InvalidDIDError('didDocumentData missing backupStore');
|
|
1116
1138
|
}
|
|
1117
|
-
const
|
|
1118
|
-
if (typeof
|
|
1119
|
-
throw new InvalidDIDError('backup not found in
|
|
1139
|
+
const backupStore = await this.resolveAsset(docData.backupStore);
|
|
1140
|
+
if (typeof backupStore.backup !== 'string') {
|
|
1141
|
+
throw new InvalidDIDError('backup not found in backupStore');
|
|
1120
1142
|
}
|
|
1121
|
-
const backup = this.cipher.decryptMessage(keypair.publicJwk, keypair.privateJwk,
|
|
1143
|
+
const backup = this.cipher.decryptMessage(keypair.publicJwk, keypair.privateJwk, backupStore.backup);
|
|
1122
1144
|
const data = JSON.parse(backup);
|
|
1123
1145
|
await this.mutateWallet((wallet) => {
|
|
1124
1146
|
if (wallet.ids[data.name]) {
|
|
@@ -1162,6 +1184,7 @@ export default class Keymaster {
|
|
|
1162
1184
|
...doc.didDocument,
|
|
1163
1185
|
verificationMethod: [vmethod],
|
|
1164
1186
|
authentication: [vmethod.id],
|
|
1187
|
+
assertionMethod: [vmethod.id],
|
|
1165
1188
|
};
|
|
1166
1189
|
ok = await this.updateDID(id.did, { didDocument: updatedDidDocument });
|
|
1167
1190
|
if (!ok) {
|
|
@@ -1174,7 +1197,7 @@ export default class Keymaster {
|
|
|
1174
1197
|
async listNames(options = {}) {
|
|
1175
1198
|
const { includeIDs = false } = options;
|
|
1176
1199
|
const wallet = await this.loadWallet();
|
|
1177
|
-
const names = wallet.names || {};
|
|
1200
|
+
const names = { ...(wallet.names || {}) };
|
|
1178
1201
|
if (includeIDs) {
|
|
1179
1202
|
for (const [name, id] of Object.entries(wallet.ids || {})) {
|
|
1180
1203
|
names[name] = id.did;
|
|
@@ -1212,42 +1235,60 @@ export default class Keymaster {
|
|
|
1212
1235
|
const doc = await this.resolveDID(id);
|
|
1213
1236
|
return doc.didDocumentRegistration?.type === 'agent';
|
|
1214
1237
|
}
|
|
1215
|
-
async bindCredential(
|
|
1216
|
-
let { validFrom, validUntil,
|
|
1238
|
+
async bindCredential(subjectId, options = {}) {
|
|
1239
|
+
let { schema, validFrom, validUntil, claims, types } = options;
|
|
1217
1240
|
if (!validFrom) {
|
|
1218
1241
|
validFrom = new Date().toISOString();
|
|
1219
1242
|
}
|
|
1220
1243
|
const id = await this.fetchIdInfo();
|
|
1221
|
-
const type = await this.lookupDID(schemaId);
|
|
1222
1244
|
const subjectDID = await this.lookupDID(subjectId);
|
|
1223
|
-
|
|
1224
|
-
const schema = await this.getSchema(type);
|
|
1225
|
-
credential = this.generateSchema(schema);
|
|
1226
|
-
}
|
|
1227
|
-
return {
|
|
1245
|
+
const vc = {
|
|
1228
1246
|
"@context": [
|
|
1229
1247
|
"https://www.w3.org/ns/credentials/v2",
|
|
1230
1248
|
"https://www.w3.org/ns/credentials/examples/v2"
|
|
1231
1249
|
],
|
|
1232
|
-
type: ["VerifiableCredential",
|
|
1250
|
+
type: ["VerifiableCredential", ...(types || [])],
|
|
1233
1251
|
issuer: id.did,
|
|
1234
1252
|
validFrom,
|
|
1235
1253
|
validUntil,
|
|
1236
1254
|
credentialSubject: {
|
|
1237
1255
|
id: subjectDID,
|
|
1238
1256
|
},
|
|
1239
|
-
credential,
|
|
1240
1257
|
};
|
|
1258
|
+
// If schema provided, add credentialSchema and generate claims from schema
|
|
1259
|
+
if (schema) {
|
|
1260
|
+
const schemaDID = await this.lookupDID(schema);
|
|
1261
|
+
const schemaDoc = await this.getSchema(schemaDID);
|
|
1262
|
+
if (!claims && schemaDoc) {
|
|
1263
|
+
claims = this.generateSchema(schemaDoc);
|
|
1264
|
+
}
|
|
1265
|
+
// If schema has $credentialTypes, add them to credential types (avoiding duplicates)
|
|
1266
|
+
if (schemaDoc?.$credentialTypes) {
|
|
1267
|
+
const newTypes = schemaDoc.$credentialTypes.filter(t => !vc.type.includes(t));
|
|
1268
|
+
vc.type.push(...newTypes);
|
|
1269
|
+
}
|
|
1270
|
+
vc.credentialSchema = {
|
|
1271
|
+
id: schemaDID,
|
|
1272
|
+
type: "JsonSchema",
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
if (claims) {
|
|
1276
|
+
vc.credentialSubject = {
|
|
1277
|
+
id: subjectDID,
|
|
1278
|
+
...claims,
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
return vc;
|
|
1241
1282
|
}
|
|
1242
1283
|
async issueCredential(credential, options = {}) {
|
|
1243
1284
|
const id = await this.fetchIdInfo();
|
|
1244
1285
|
if (options.schema && options.subject) {
|
|
1245
|
-
credential = await this.bindCredential(options.
|
|
1286
|
+
credential = await this.bindCredential(options.subject, { schema: options.schema, claims: options.claims, ...options });
|
|
1246
1287
|
}
|
|
1247
1288
|
if (credential.issuer !== id.did) {
|
|
1248
1289
|
throw new InvalidParameterError('credential.issuer');
|
|
1249
1290
|
}
|
|
1250
|
-
const signed = await this.
|
|
1291
|
+
const signed = await this.addProof(credential);
|
|
1251
1292
|
return this.encryptJSON(signed, credential.credentialSubject.id, { ...options, includeHash: true });
|
|
1252
1293
|
}
|
|
1253
1294
|
async sendCredential(did, options = {}) {
|
|
@@ -1277,13 +1318,12 @@ export default class Keymaster {
|
|
|
1277
1318
|
throw new InvalidParameterError("did is not a credential");
|
|
1278
1319
|
}
|
|
1279
1320
|
if (!credential ||
|
|
1280
|
-
!credential.credential ||
|
|
1281
1321
|
!credential.credentialSubject ||
|
|
1282
1322
|
!credential.credentialSubject.id) {
|
|
1283
1323
|
throw new InvalidParameterError('credential');
|
|
1284
1324
|
}
|
|
1285
|
-
delete credential.
|
|
1286
|
-
const signed = await this.
|
|
1325
|
+
delete credential.proof;
|
|
1326
|
+
const signed = await this.addProof(credential);
|
|
1287
1327
|
const msg = JSON.stringify(signed);
|
|
1288
1328
|
const id = await this.fetchIdInfo();
|
|
1289
1329
|
const senderKeypair = await this.fetchKeyPair();
|
|
@@ -1377,8 +1417,8 @@ export default class Keymaster {
|
|
|
1377
1417
|
data.manifest = {};
|
|
1378
1418
|
}
|
|
1379
1419
|
if (!reveal) {
|
|
1380
|
-
// Remove the
|
|
1381
|
-
vc.
|
|
1420
|
+
// Remove the claim values, keep only the subject id
|
|
1421
|
+
vc.credentialSubject = { id: vc.credentialSubject.id };
|
|
1382
1422
|
}
|
|
1383
1423
|
data.manifest[credential] = vc;
|
|
1384
1424
|
const ok = await this.updateDID(id.did, { didDocumentData: doc.didDocumentData });
|
|
@@ -1436,8 +1476,8 @@ export default class Keymaster {
|
|
|
1436
1476
|
// Attestor not trusted by Verifier
|
|
1437
1477
|
continue;
|
|
1438
1478
|
}
|
|
1439
|
-
if (doc.
|
|
1440
|
-
// Wrong
|
|
1479
|
+
if (doc.credentialSchema?.id !== credential.schema) {
|
|
1480
|
+
// Wrong schema
|
|
1441
1481
|
continue;
|
|
1442
1482
|
}
|
|
1443
1483
|
// TBD test for VC expiry too
|
|
@@ -1562,7 +1602,7 @@ export default class Keymaster {
|
|
|
1562
1602
|
continue;
|
|
1563
1603
|
}
|
|
1564
1604
|
const vp = await this.decryptJSON(credential.vp);
|
|
1565
|
-
const isValid = await this.
|
|
1605
|
+
const isValid = await this.verifyProof(vp);
|
|
1566
1606
|
if (!isValid) {
|
|
1567
1607
|
continue;
|
|
1568
1608
|
}
|
|
@@ -1570,8 +1610,8 @@ export default class Keymaster {
|
|
|
1570
1610
|
continue;
|
|
1571
1611
|
}
|
|
1572
1612
|
// Check VP against VCs specified in challenge
|
|
1573
|
-
if (vp.
|
|
1574
|
-
const schema = vp.
|
|
1613
|
+
if (vp.credentialSchema?.id) {
|
|
1614
|
+
const schema = vp.credentialSchema.id;
|
|
1575
1615
|
const credential = challenge.credentials?.find(item => item.schema === schema);
|
|
1576
1616
|
if (!credential) {
|
|
1577
1617
|
continue;
|
|
@@ -2069,7 +2109,7 @@ export default class Keymaster {
|
|
|
2069
2109
|
delete poll.results;
|
|
2070
2110
|
return this.updateAsset(pollId, { poll });
|
|
2071
2111
|
}
|
|
2072
|
-
async
|
|
2112
|
+
async createVault(options = {}) {
|
|
2073
2113
|
const id = await this.fetchIdInfo();
|
|
2074
2114
|
const idKeypair = await this.fetchKeyPair();
|
|
2075
2115
|
// version defaults to 1. To make version undefined (unit testing), set options.version to 0
|
|
@@ -2084,7 +2124,7 @@ export default class Keymaster {
|
|
|
2084
2124
|
const members = this.cipher.encryptMessage(publicJwk, vaultKeypair.privateJwk, JSON.stringify({}));
|
|
2085
2125
|
const items = this.cipher.encryptMessage(vaultKeypair.publicJwk, vaultKeypair.privateJwk, JSON.stringify({}));
|
|
2086
2126
|
const sha256 = this.cipher.hashJSON({});
|
|
2087
|
-
const
|
|
2127
|
+
const vault = {
|
|
2088
2128
|
version,
|
|
2089
2129
|
publicJwk: vaultKeypair.publicJwk,
|
|
2090
2130
|
salt,
|
|
@@ -2094,46 +2134,46 @@ export default class Keymaster {
|
|
|
2094
2134
|
items,
|
|
2095
2135
|
sha256,
|
|
2096
2136
|
};
|
|
2097
|
-
await this.addMemberKey(
|
|
2098
|
-
return this.createAsset({
|
|
2137
|
+
await this.addMemberKey(vault, id.did, vaultKeypair.privateJwk);
|
|
2138
|
+
return this.createAsset({ vault }, options);
|
|
2099
2139
|
}
|
|
2100
|
-
async
|
|
2101
|
-
const asset = await this.resolveAsset(
|
|
2102
|
-
if (!asset.
|
|
2103
|
-
throw new InvalidParameterError('
|
|
2140
|
+
async getVault(vaultId, options) {
|
|
2141
|
+
const asset = await this.resolveAsset(vaultId, options);
|
|
2142
|
+
if (!asset.vault) {
|
|
2143
|
+
throw new InvalidParameterError('vaultId');
|
|
2104
2144
|
}
|
|
2105
|
-
return asset.
|
|
2145
|
+
return asset.vault;
|
|
2106
2146
|
}
|
|
2107
|
-
async
|
|
2147
|
+
async testVault(id, options) {
|
|
2108
2148
|
try {
|
|
2109
|
-
const
|
|
2110
|
-
return
|
|
2149
|
+
const vault = await this.getVault(id, options);
|
|
2150
|
+
return vault !== null;
|
|
2111
2151
|
}
|
|
2112
2152
|
catch (error) {
|
|
2113
2153
|
return false;
|
|
2114
2154
|
}
|
|
2115
2155
|
}
|
|
2116
|
-
generateSaltedId(
|
|
2117
|
-
if (!
|
|
2118
|
-
return this.cipher.hashMessage(
|
|
2156
|
+
generateSaltedId(vault, memberDID) {
|
|
2157
|
+
if (!vault.version) {
|
|
2158
|
+
return this.cipher.hashMessage(vault.salt + memberDID);
|
|
2119
2159
|
}
|
|
2120
2160
|
const suffix = memberDID.split(':').pop();
|
|
2121
|
-
return this.cipher.hashMessage(
|
|
2161
|
+
return this.cipher.hashMessage(vault.salt + suffix);
|
|
2122
2162
|
}
|
|
2123
|
-
async
|
|
2163
|
+
async decryptVault(vault) {
|
|
2124
2164
|
const wallet = await this.loadWallet();
|
|
2125
2165
|
const id = await this.fetchIdInfo();
|
|
2126
|
-
const myMemberId = this.generateSaltedId(
|
|
2127
|
-
const myVaultKey =
|
|
2166
|
+
const myMemberId = this.generateSaltedId(vault, id.did);
|
|
2167
|
+
const myVaultKey = vault.keys[myMemberId];
|
|
2128
2168
|
if (!myVaultKey) {
|
|
2129
|
-
throw new KeymasterError('No access to
|
|
2169
|
+
throw new KeymasterError('No access to vault');
|
|
2130
2170
|
}
|
|
2131
|
-
const privKeyJSON = await this.decryptWithDerivedKeys(wallet, id,
|
|
2171
|
+
const privKeyJSON = await this.decryptWithDerivedKeys(wallet, id, vault.publicJwk, myVaultKey);
|
|
2132
2172
|
const privateJwk = JSON.parse(privKeyJSON);
|
|
2133
2173
|
let config = {};
|
|
2134
2174
|
let isOwner = false;
|
|
2135
2175
|
try {
|
|
2136
|
-
const configJSON = await this.decryptWithDerivedKeys(wallet, id,
|
|
2176
|
+
const configJSON = await this.decryptWithDerivedKeys(wallet, id, vault.publicJwk, vault.config);
|
|
2137
2177
|
config = JSON.parse(configJSON);
|
|
2138
2178
|
isOwner = true;
|
|
2139
2179
|
}
|
|
@@ -2143,7 +2183,7 @@ export default class Keymaster {
|
|
|
2143
2183
|
let members = {};
|
|
2144
2184
|
if (config.secretMembers) {
|
|
2145
2185
|
try {
|
|
2146
|
-
const membersJSON = await this.decryptWithDerivedKeys(wallet, id,
|
|
2186
|
+
const membersJSON = await this.decryptWithDerivedKeys(wallet, id, vault.publicJwk, vault.members);
|
|
2147
2187
|
members = JSON.parse(membersJSON);
|
|
2148
2188
|
}
|
|
2149
2189
|
catch (error) {
|
|
@@ -2151,13 +2191,13 @@ export default class Keymaster {
|
|
|
2151
2191
|
}
|
|
2152
2192
|
else {
|
|
2153
2193
|
try {
|
|
2154
|
-
const membersJSON = this.cipher.decryptMessage(
|
|
2194
|
+
const membersJSON = this.cipher.decryptMessage(vault.publicJwk, privateJwk, vault.members);
|
|
2155
2195
|
members = JSON.parse(membersJSON);
|
|
2156
2196
|
}
|
|
2157
2197
|
catch (error) {
|
|
2158
2198
|
}
|
|
2159
2199
|
}
|
|
2160
|
-
const itemsJSON = this.cipher.decryptMessage(
|
|
2200
|
+
const itemsJSON = this.cipher.decryptMessage(vault.publicJwk, privateJwk, vault.items);
|
|
2161
2201
|
const items = JSON.parse(itemsJSON);
|
|
2162
2202
|
return {
|
|
2163
2203
|
isOwner,
|
|
@@ -2167,7 +2207,7 @@ export default class Keymaster {
|
|
|
2167
2207
|
items,
|
|
2168
2208
|
};
|
|
2169
2209
|
}
|
|
2170
|
-
async
|
|
2210
|
+
async checkVaultOwner(vaultId) {
|
|
2171
2211
|
const id = await this.fetchIdInfo();
|
|
2172
2212
|
const vaultDoc = await this.resolveDID(vaultId);
|
|
2173
2213
|
const controller = vaultDoc.didDocument?.controller;
|
|
@@ -2176,30 +2216,30 @@ export default class Keymaster {
|
|
|
2176
2216
|
}
|
|
2177
2217
|
return controller;
|
|
2178
2218
|
}
|
|
2179
|
-
async addMemberKey(
|
|
2219
|
+
async addMemberKey(vault, memberDID, privateJwk) {
|
|
2180
2220
|
const memberDoc = await this.resolveDID(memberDID, { confirm: true });
|
|
2181
2221
|
const memberPublicJwk = this.getPublicKeyJwk(memberDoc);
|
|
2182
2222
|
const memberKey = this.cipher.encryptMessage(memberPublicJwk, privateJwk, JSON.stringify(privateJwk));
|
|
2183
|
-
const memberKeyId = this.generateSaltedId(
|
|
2184
|
-
|
|
2223
|
+
const memberKeyId = this.generateSaltedId(vault, memberDID);
|
|
2224
|
+
vault.keys[memberKeyId] = memberKey;
|
|
2185
2225
|
}
|
|
2186
|
-
async checkVaultVersion(vaultId,
|
|
2187
|
-
if (
|
|
2226
|
+
async checkVaultVersion(vaultId, vault) {
|
|
2227
|
+
if (vault.version === 1) {
|
|
2188
2228
|
return;
|
|
2189
2229
|
}
|
|
2190
|
-
if (!
|
|
2230
|
+
if (!vault.version) {
|
|
2191
2231
|
const id = await this.fetchIdInfo();
|
|
2192
|
-
const { privateJwk, members } = await this.
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
await this.addMemberKey(
|
|
2232
|
+
const { privateJwk, members } = await this.decryptVault(vault);
|
|
2233
|
+
vault.version = 1;
|
|
2234
|
+
vault.keys = {};
|
|
2235
|
+
await this.addMemberKey(vault, id.did, privateJwk);
|
|
2196
2236
|
for (const memberDID of Object.keys(members)) {
|
|
2197
|
-
await this.addMemberKey(
|
|
2237
|
+
await this.addMemberKey(vault, memberDID, privateJwk);
|
|
2198
2238
|
}
|
|
2199
|
-
await this.updateAsset(vaultId, {
|
|
2239
|
+
await this.updateAsset(vaultId, { vault });
|
|
2200
2240
|
return;
|
|
2201
2241
|
}
|
|
2202
|
-
throw new KeymasterError('Unsupported
|
|
2242
|
+
throw new KeymasterError('Unsupported vault version');
|
|
2203
2243
|
}
|
|
2204
2244
|
getAgentDID(doc) {
|
|
2205
2245
|
if (doc.didDocumentRegistration?.type !== 'agent') {
|
|
@@ -2211,11 +2251,11 @@ export default class Keymaster {
|
|
|
2211
2251
|
}
|
|
2212
2252
|
return did;
|
|
2213
2253
|
}
|
|
2214
|
-
async
|
|
2215
|
-
const owner = await this.
|
|
2254
|
+
async addVaultMember(vaultId, memberId) {
|
|
2255
|
+
const owner = await this.checkVaultOwner(vaultId);
|
|
2216
2256
|
const idKeypair = await this.fetchKeyPair();
|
|
2217
|
-
const
|
|
2218
|
-
const { privateJwk, config, members } = await this.
|
|
2257
|
+
const vault = await this.getVault(vaultId);
|
|
2258
|
+
const { privateJwk, config, members } = await this.decryptVault(vault);
|
|
2219
2259
|
const memberDoc = await this.resolveDID(memberId, { confirm: true });
|
|
2220
2260
|
const memberDID = this.getAgentDID(memberDoc);
|
|
2221
2261
|
// Don't allow adding the vault owner
|
|
@@ -2223,16 +2263,16 @@ export default class Keymaster {
|
|
|
2223
2263
|
return false;
|
|
2224
2264
|
}
|
|
2225
2265
|
members[memberDID] = { added: new Date().toISOString() };
|
|
2226
|
-
const publicJwk = config.secretMembers ? idKeypair.publicJwk :
|
|
2227
|
-
|
|
2228
|
-
await this.addMemberKey(
|
|
2229
|
-
return this.updateAsset(vaultId, {
|
|
2266
|
+
const publicJwk = config.secretMembers ? idKeypair.publicJwk : vault.publicJwk;
|
|
2267
|
+
vault.members = this.cipher.encryptMessage(publicJwk, privateJwk, JSON.stringify(members));
|
|
2268
|
+
await this.addMemberKey(vault, memberDID, privateJwk);
|
|
2269
|
+
return this.updateAsset(vaultId, { vault });
|
|
2230
2270
|
}
|
|
2231
|
-
async
|
|
2232
|
-
const owner = await this.
|
|
2271
|
+
async removeVaultMember(vaultId, memberId) {
|
|
2272
|
+
const owner = await this.checkVaultOwner(vaultId);
|
|
2233
2273
|
const idKeypair = await this.fetchKeyPair();
|
|
2234
|
-
const
|
|
2235
|
-
const { privateJwk, config, members } = await this.
|
|
2274
|
+
const vault = await this.getVault(vaultId);
|
|
2275
|
+
const { privateJwk, config, members } = await this.decryptVault(vault);
|
|
2236
2276
|
const memberDoc = await this.resolveDID(memberId, { confirm: true });
|
|
2237
2277
|
const memberDID = this.getAgentDID(memberDoc);
|
|
2238
2278
|
// Don't allow removing the vault owner
|
|
@@ -2240,26 +2280,26 @@ export default class Keymaster {
|
|
|
2240
2280
|
return false;
|
|
2241
2281
|
}
|
|
2242
2282
|
delete members[memberDID];
|
|
2243
|
-
const publicJwk = config.secretMembers ? idKeypair.publicJwk :
|
|
2244
|
-
|
|
2245
|
-
const memberKeyId = this.generateSaltedId(
|
|
2246
|
-
delete
|
|
2247
|
-
return this.updateAsset(vaultId, {
|
|
2248
|
-
}
|
|
2249
|
-
async
|
|
2250
|
-
const
|
|
2251
|
-
const { members, isOwner } = await this.
|
|
2283
|
+
const publicJwk = config.secretMembers ? idKeypair.publicJwk : vault.publicJwk;
|
|
2284
|
+
vault.members = this.cipher.encryptMessage(publicJwk, privateJwk, JSON.stringify(members));
|
|
2285
|
+
const memberKeyId = this.generateSaltedId(vault, memberDID);
|
|
2286
|
+
delete vault.keys[memberKeyId];
|
|
2287
|
+
return this.updateAsset(vaultId, { vault });
|
|
2288
|
+
}
|
|
2289
|
+
async listVaultMembers(vaultId) {
|
|
2290
|
+
const vault = await this.getVault(vaultId);
|
|
2291
|
+
const { members, isOwner } = await this.decryptVault(vault);
|
|
2252
2292
|
if (isOwner) {
|
|
2253
|
-
await this.checkVaultVersion(vaultId,
|
|
2293
|
+
await this.checkVaultVersion(vaultId, vault);
|
|
2254
2294
|
}
|
|
2255
2295
|
return members;
|
|
2256
2296
|
}
|
|
2257
|
-
async
|
|
2258
|
-
await this.
|
|
2259
|
-
const
|
|
2260
|
-
const { privateJwk, items } = await this.
|
|
2297
|
+
async addVaultItem(vaultId, name, buffer) {
|
|
2298
|
+
await this.checkVaultOwner(vaultId);
|
|
2299
|
+
const vault = await this.getVault(vaultId);
|
|
2300
|
+
const { privateJwk, items } = await this.decryptVault(vault);
|
|
2261
2301
|
const validName = this.validateName(name);
|
|
2262
|
-
const encryptedData = this.cipher.encryptBytes(
|
|
2302
|
+
const encryptedData = this.cipher.encryptBytes(vault.publicJwk, privateJwk, buffer);
|
|
2263
2303
|
const cid = await this.gatekeeper.addText(encryptedData);
|
|
2264
2304
|
const sha256 = this.cipher.hashMessage(buffer);
|
|
2265
2305
|
const type = await this.getMimeType(buffer);
|
|
@@ -2272,32 +2312,32 @@ export default class Keymaster {
|
|
|
2272
2312
|
added: new Date().toISOString(),
|
|
2273
2313
|
data,
|
|
2274
2314
|
};
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
return this.updateAsset(vaultId, {
|
|
2278
|
-
}
|
|
2279
|
-
async
|
|
2280
|
-
await this.
|
|
2281
|
-
const
|
|
2282
|
-
const { privateJwk, items } = await this.
|
|
2315
|
+
vault.items = this.cipher.encryptMessage(vault.publicJwk, privateJwk, JSON.stringify(items));
|
|
2316
|
+
vault.sha256 = this.cipher.hashJSON(items);
|
|
2317
|
+
return this.updateAsset(vaultId, { vault });
|
|
2318
|
+
}
|
|
2319
|
+
async removeVaultItem(vaultId, name) {
|
|
2320
|
+
await this.checkVaultOwner(vaultId);
|
|
2321
|
+
const vault = await this.getVault(vaultId);
|
|
2322
|
+
const { privateJwk, items } = await this.decryptVault(vault);
|
|
2283
2323
|
delete items[name];
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
return this.updateAsset(vaultId, {
|
|
2324
|
+
vault.items = this.cipher.encryptMessage(vault.publicJwk, privateJwk, JSON.stringify(items));
|
|
2325
|
+
vault.sha256 = this.cipher.hashJSON(items);
|
|
2326
|
+
return this.updateAsset(vaultId, { vault });
|
|
2287
2327
|
}
|
|
2288
|
-
async
|
|
2289
|
-
const
|
|
2290
|
-
const { items } = await this.
|
|
2328
|
+
async listVaultItems(vaultId, options) {
|
|
2329
|
+
const vault = await this.getVault(vaultId, options);
|
|
2330
|
+
const { items } = await this.decryptVault(vault);
|
|
2291
2331
|
return items;
|
|
2292
2332
|
}
|
|
2293
|
-
async
|
|
2333
|
+
async getVaultItem(vaultId, name, options) {
|
|
2294
2334
|
try {
|
|
2295
|
-
const
|
|
2296
|
-
const { privateJwk, items } = await this.
|
|
2335
|
+
const vault = await this.getVault(vaultId, options);
|
|
2336
|
+
const { privateJwk, items } = await this.decryptVault(vault);
|
|
2297
2337
|
if (items[name]) {
|
|
2298
2338
|
const encryptedData = items[name].data || await this.gatekeeper.getText(items[name].cid);
|
|
2299
2339
|
if (encryptedData) {
|
|
2300
|
-
const bytes = this.cipher.decryptBytes(
|
|
2340
|
+
const bytes = this.cipher.decryptBytes(vault.publicJwk, privateJwk, encryptedData);
|
|
2301
2341
|
return Buffer.from(bytes);
|
|
2302
2342
|
}
|
|
2303
2343
|
}
|
|
@@ -2426,28 +2466,28 @@ export default class Keymaster {
|
|
|
2426
2466
|
}
|
|
2427
2467
|
async createDmail(message, options = {}) {
|
|
2428
2468
|
const dmail = await this.verifyDmail(message);
|
|
2429
|
-
const did = await this.
|
|
2469
|
+
const did = await this.createVault(options);
|
|
2430
2470
|
for (const toDID of dmail.to) {
|
|
2431
|
-
await this.
|
|
2471
|
+
await this.addVaultMember(did, toDID);
|
|
2432
2472
|
}
|
|
2433
2473
|
for (const ccDID of dmail.cc) {
|
|
2434
|
-
await this.
|
|
2474
|
+
await this.addVaultMember(did, ccDID);
|
|
2435
2475
|
}
|
|
2436
2476
|
const buffer = Buffer.from(JSON.stringify({ dmail }), 'utf-8');
|
|
2437
|
-
await this.
|
|
2477
|
+
await this.addVaultItem(did, DmailTags.DMAIL, buffer);
|
|
2438
2478
|
await this.fileDmail(did, [DmailTags.DRAFT]);
|
|
2439
2479
|
return did;
|
|
2440
2480
|
}
|
|
2441
2481
|
async updateDmail(did, message) {
|
|
2442
2482
|
const dmail = await this.verifyDmail(message);
|
|
2443
2483
|
for (const toDID of dmail.to) {
|
|
2444
|
-
await this.
|
|
2484
|
+
await this.addVaultMember(did, toDID);
|
|
2445
2485
|
}
|
|
2446
2486
|
for (const ccDID of dmail.cc) {
|
|
2447
|
-
await this.
|
|
2487
|
+
await this.addVaultMember(did, ccDID);
|
|
2448
2488
|
}
|
|
2449
2489
|
const buffer = Buffer.from(JSON.stringify({ dmail }), 'utf-8');
|
|
2450
|
-
return this.
|
|
2490
|
+
return this.addVaultItem(did, DmailTags.DMAIL, buffer);
|
|
2451
2491
|
}
|
|
2452
2492
|
async sendDmail(did) {
|
|
2453
2493
|
const dmail = await this.getDmailMessage(did);
|
|
@@ -2467,11 +2507,11 @@ export default class Keymaster {
|
|
|
2467
2507
|
return notice;
|
|
2468
2508
|
}
|
|
2469
2509
|
async getDmailMessage(did, options) {
|
|
2470
|
-
const
|
|
2471
|
-
if (!
|
|
2510
|
+
const isVault = await this.testVault(did, options);
|
|
2511
|
+
if (!isVault) {
|
|
2472
2512
|
return null;
|
|
2473
2513
|
}
|
|
2474
|
-
const buffer = await this.
|
|
2514
|
+
const buffer = await this.getVaultItem(did, DmailTags.DMAIL, options);
|
|
2475
2515
|
if (!buffer) {
|
|
2476
2516
|
return null;
|
|
2477
2517
|
}
|
|
@@ -2484,7 +2524,7 @@ export default class Keymaster {
|
|
|
2484
2524
|
}
|
|
2485
2525
|
}
|
|
2486
2526
|
async listDmailAttachments(did, options) {
|
|
2487
|
-
let items = await this.
|
|
2527
|
+
let items = await this.listVaultItems(did, options);
|
|
2488
2528
|
delete items[DmailTags.DMAIL]; // Remove the dmail item itself from attachments
|
|
2489
2529
|
return items;
|
|
2490
2530
|
}
|
|
@@ -2492,16 +2532,16 @@ export default class Keymaster {
|
|
|
2492
2532
|
if (name === DmailTags.DMAIL) {
|
|
2493
2533
|
throw new InvalidParameterError('Cannot add attachment with reserved name "dmail"');
|
|
2494
2534
|
}
|
|
2495
|
-
return this.
|
|
2535
|
+
return this.addVaultItem(did, name, buffer);
|
|
2496
2536
|
}
|
|
2497
2537
|
async removeDmailAttachment(did, name) {
|
|
2498
2538
|
if (name === DmailTags.DMAIL) {
|
|
2499
2539
|
throw new InvalidParameterError('Cannot remove attachment with reserved name "dmail"');
|
|
2500
2540
|
}
|
|
2501
|
-
return this.
|
|
2541
|
+
return this.removeVaultItem(did, name);
|
|
2502
2542
|
}
|
|
2503
2543
|
async getDmailAttachment(did, name) {
|
|
2504
|
-
return this.
|
|
2544
|
+
return this.getVaultItem(did, name);
|
|
2505
2545
|
}
|
|
2506
2546
|
async importDmail(did) {
|
|
2507
2547
|
const dmail = await this.getDmailMessage(did);
|
|
@@ -2603,23 +2643,20 @@ export default class Keymaster {
|
|
|
2603
2643
|
return true;
|
|
2604
2644
|
}
|
|
2605
2645
|
async searchNotices() {
|
|
2606
|
-
if (!this.searchEngine) {
|
|
2607
|
-
return false; // Search engine not available
|
|
2608
|
-
}
|
|
2609
2646
|
const id = await this.fetchIdInfo();
|
|
2610
2647
|
if (!id.notices) {
|
|
2611
2648
|
id.notices = {};
|
|
2612
2649
|
}
|
|
2613
2650
|
// Search for all notice DIDs sent to the current ID
|
|
2614
2651
|
const where = {
|
|
2615
|
-
"
|
|
2652
|
+
"notice.to[*]": {
|
|
2616
2653
|
"$in": [id.did]
|
|
2617
2654
|
}
|
|
2618
2655
|
};
|
|
2619
2656
|
let notices;
|
|
2620
2657
|
try {
|
|
2621
2658
|
// TBD search engine should not return expired notices
|
|
2622
|
-
notices = await this.
|
|
2659
|
+
notices = await this.gatekeeper.search({ where });
|
|
2623
2660
|
}
|
|
2624
2661
|
catch (error) {
|
|
2625
2662
|
throw new KeymasterError('Failed to search for notices');
|