@didcid/keymaster 0.1.3

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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -0
  3. package/dist/cjs/db/abstract-base.cjs +25 -0
  4. package/dist/cjs/db/cache.cjs +27 -0
  5. package/dist/cjs/db/chrome.cjs +32 -0
  6. package/dist/cjs/db/json-memory.cjs +24 -0
  7. package/dist/cjs/db/json.cjs +35 -0
  8. package/dist/cjs/db/mongo.cjs +57 -0
  9. package/dist/cjs/db/redis.cjs +55 -0
  10. package/dist/cjs/db/sqlite.cjs +69 -0
  11. package/dist/cjs/db/typeGuards.cjs +11 -0
  12. package/dist/cjs/db/web.cjs +29 -0
  13. package/dist/cjs/encryption.cjs +59 -0
  14. package/dist/cjs/index.cjs +32 -0
  15. package/dist/cjs/keymaster-client.cjs +1139 -0
  16. package/dist/cjs/keymaster.cjs +3787 -0
  17. package/dist/cjs/node.cjs +45 -0
  18. package/dist/cjs/search-client.cjs +87 -0
  19. package/dist/esm/db/abstract-base.js +22 -0
  20. package/dist/esm/db/abstract-base.js.map +1 -0
  21. package/dist/esm/db/cache.js +21 -0
  22. package/dist/esm/db/cache.js.map +1 -0
  23. package/dist/esm/db/chrome.js +26 -0
  24. package/dist/esm/db/chrome.js.map +1 -0
  25. package/dist/esm/db/json-memory.js +18 -0
  26. package/dist/esm/db/json-memory.js.map +1 -0
  27. package/dist/esm/db/json.js +29 -0
  28. package/dist/esm/db/json.js.map +1 -0
  29. package/dist/esm/db/mongo.js +51 -0
  30. package/dist/esm/db/mongo.js.map +1 -0
  31. package/dist/esm/db/redis.js +49 -0
  32. package/dist/esm/db/redis.js.map +1 -0
  33. package/dist/esm/db/sqlite.js +63 -0
  34. package/dist/esm/db/sqlite.js.map +1 -0
  35. package/dist/esm/db/typeGuards.js +7 -0
  36. package/dist/esm/db/typeGuards.js.map +1 -0
  37. package/dist/esm/db/web.js +23 -0
  38. package/dist/esm/db/web.js.map +1 -0
  39. package/dist/esm/encryption.js +55 -0
  40. package/dist/esm/encryption.js.map +1 -0
  41. package/dist/esm/index.js +11 -0
  42. package/dist/esm/index.js.map +1 -0
  43. package/dist/esm/keymaster-client.js +1133 -0
  44. package/dist/esm/keymaster-client.js.map +1 -0
  45. package/dist/esm/keymaster.js +2733 -0
  46. package/dist/esm/keymaster.js.map +1 -0
  47. package/dist/esm/node.js +7 -0
  48. package/dist/esm/node.js.map +1 -0
  49. package/dist/esm/search-client.js +81 -0
  50. package/dist/esm/search-client.js.map +1 -0
  51. package/dist/esm/types.js +2 -0
  52. package/dist/esm/types.js.map +1 -0
  53. package/dist/types/db/abstract-base.d.ts +7 -0
  54. package/dist/types/db/cache.d.ts +9 -0
  55. package/dist/types/db/chrome.d.ts +8 -0
  56. package/dist/types/db/json-memory.d.ts +7 -0
  57. package/dist/types/db/json.d.ts +9 -0
  58. package/dist/types/db/mongo.d.ts +15 -0
  59. package/dist/types/db/redis.d.ts +13 -0
  60. package/dist/types/db/sqlite.d.ts +12 -0
  61. package/dist/types/db/typeGuards.d.ts +3 -0
  62. package/dist/types/db/web.d.ts +8 -0
  63. package/dist/types/encryption.d.ts +10 -0
  64. package/dist/types/index.d.ts +10 -0
  65. package/dist/types/keymaster-client.d.ts +134 -0
  66. package/dist/types/keymaster.d.ts +211 -0
  67. package/dist/types/node.d.ts +6 -0
  68. package/dist/types/search-client.d.ts +9 -0
  69. package/dist/types/types.d.ts +373 -0
  70. package/package.json +171 -0
@@ -0,0 +1,3787 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var imageSize = require('image-size');
6
+ var fileType = require('file-type');
7
+ var db_typeGuards = require('./db/typeGuards.cjs');
8
+ require('crypto');
9
+ var encryption = require('./encryption.cjs');
10
+
11
+ class ArchonError extends Error {
12
+ type;
13
+ detail;
14
+ constructor(type, detail) {
15
+ const message = detail ? `${type}: ${detail}` : type;
16
+ super(message);
17
+ this.type = type;
18
+ this.detail = detail;
19
+ }
20
+ }
21
+ class InvalidDIDError extends ArchonError {
22
+ static type = 'Invalid DID';
23
+ constructor(detail) {
24
+ super(InvalidDIDError.type, detail);
25
+ }
26
+ }
27
+ class InvalidParameterError extends ArchonError {
28
+ static type = 'Invalid parameter';
29
+ constructor(detail) {
30
+ super(InvalidParameterError.type, detail);
31
+ }
32
+ }
33
+ class KeymasterError extends ArchonError {
34
+ static type = 'Keymaster';
35
+ constructor(detail) {
36
+ super(KeymasterError.type, detail);
37
+ }
38
+ }
39
+ class UnknownIDError extends ArchonError {
40
+ static type = 'Unknown ID';
41
+ constructor(detail) {
42
+ super(UnknownIDError.type, detail);
43
+ }
44
+ }
45
+
46
+ function equals$1(aa, bb) {
47
+ if (aa === bb) {
48
+ return true;
49
+ }
50
+ if (aa.byteLength !== bb.byteLength) {
51
+ return false;
52
+ }
53
+ for (let ii = 0; ii < aa.byteLength; ii++) {
54
+ if (aa[ii] !== bb[ii]) {
55
+ return false;
56
+ }
57
+ }
58
+ return true;
59
+ }
60
+ function coerce(o) {
61
+ if (o instanceof Uint8Array && o.constructor.name === 'Uint8Array') {
62
+ return o;
63
+ }
64
+ if (o instanceof ArrayBuffer) {
65
+ return new Uint8Array(o);
66
+ }
67
+ if (ArrayBuffer.isView(o)) {
68
+ return new Uint8Array(o.buffer, o.byteOffset, o.byteLength);
69
+ }
70
+ throw new Error('Unknown type, must be binary type');
71
+ }
72
+
73
+ /* eslint-disable */
74
+ // base-x encoding / decoding
75
+ // Copyright (c) 2018 base-x contributors
76
+ // Copyright (c) 2014-2018 The Bitcoin Core developers (base58.cpp)
77
+ // Distributed under the MIT software license, see the accompanying
78
+ // file LICENSE or http://www.opensource.org/licenses/mit-license.php.
79
+ /**
80
+ * @param {string} ALPHABET
81
+ * @param {any} name
82
+ */
83
+ function base(ALPHABET, name) {
84
+ if (ALPHABET.length >= 255) {
85
+ throw new TypeError('Alphabet too long');
86
+ }
87
+ var BASE_MAP = new Uint8Array(256);
88
+ for (var j = 0; j < BASE_MAP.length; j++) {
89
+ BASE_MAP[j] = 255;
90
+ }
91
+ for (var i = 0; i < ALPHABET.length; i++) {
92
+ var x = ALPHABET.charAt(i);
93
+ var xc = x.charCodeAt(0);
94
+ if (BASE_MAP[xc] !== 255) {
95
+ throw new TypeError(x + ' is ambiguous');
96
+ }
97
+ BASE_MAP[xc] = i;
98
+ }
99
+ var BASE = ALPHABET.length;
100
+ var LEADER = ALPHABET.charAt(0);
101
+ var FACTOR = Math.log(BASE) / Math.log(256); // log(BASE) / log(256), rounded up
102
+ var iFACTOR = Math.log(256) / Math.log(BASE); // log(256) / log(BASE), rounded up
103
+ /**
104
+ * @param {any[] | Iterable<number>} source
105
+ */
106
+ function encode(source) {
107
+ // @ts-ignore
108
+ if (source instanceof Uint8Array)
109
+ ;
110
+ else if (ArrayBuffer.isView(source)) {
111
+ source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
112
+ }
113
+ else if (Array.isArray(source)) {
114
+ source = Uint8Array.from(source);
115
+ }
116
+ if (!(source instanceof Uint8Array)) {
117
+ throw new TypeError('Expected Uint8Array');
118
+ }
119
+ if (source.length === 0) {
120
+ return '';
121
+ }
122
+ // Skip & count leading zeroes.
123
+ var zeroes = 0;
124
+ var length = 0;
125
+ var pbegin = 0;
126
+ var pend = source.length;
127
+ while (pbegin !== pend && source[pbegin] === 0) {
128
+ pbegin++;
129
+ zeroes++;
130
+ }
131
+ // Allocate enough space in big-endian base58 representation.
132
+ var size = ((pend - pbegin) * iFACTOR + 1) >>> 0;
133
+ var b58 = new Uint8Array(size);
134
+ // Process the bytes.
135
+ while (pbegin !== pend) {
136
+ var carry = source[pbegin];
137
+ // Apply "b58 = b58 * 256 + ch".
138
+ var i = 0;
139
+ for (var it1 = size - 1; (carry !== 0 || i < length) && (it1 !== -1); it1--, i++) {
140
+ carry += (256 * b58[it1]) >>> 0;
141
+ b58[it1] = (carry % BASE) >>> 0;
142
+ carry = (carry / BASE) >>> 0;
143
+ }
144
+ if (carry !== 0) {
145
+ throw new Error('Non-zero carry');
146
+ }
147
+ length = i;
148
+ pbegin++;
149
+ }
150
+ // Skip leading zeroes in base58 result.
151
+ var it2 = size - length;
152
+ while (it2 !== size && b58[it2] === 0) {
153
+ it2++;
154
+ }
155
+ // Translate the result into a string.
156
+ var str = LEADER.repeat(zeroes);
157
+ for (; it2 < size; ++it2) {
158
+ str += ALPHABET.charAt(b58[it2]);
159
+ }
160
+ return str;
161
+ }
162
+ /**
163
+ * @param {string | string[]} source
164
+ */
165
+ function decodeUnsafe(source) {
166
+ if (typeof source !== 'string') {
167
+ throw new TypeError('Expected String');
168
+ }
169
+ if (source.length === 0) {
170
+ return new Uint8Array();
171
+ }
172
+ var psz = 0;
173
+ // Skip leading spaces.
174
+ if (source[psz] === ' ') {
175
+ return;
176
+ }
177
+ // Skip and count leading '1's.
178
+ var zeroes = 0;
179
+ var length = 0;
180
+ while (source[psz] === LEADER) {
181
+ zeroes++;
182
+ psz++;
183
+ }
184
+ // Allocate enough space in big-endian base256 representation.
185
+ var size = (((source.length - psz) * FACTOR) + 1) >>> 0; // log(58) / log(256), rounded up.
186
+ var b256 = new Uint8Array(size);
187
+ // Process the characters.
188
+ while (source[psz]) {
189
+ // Decode character
190
+ var carry = BASE_MAP[source.charCodeAt(psz)];
191
+ // Invalid character
192
+ if (carry === 255) {
193
+ return;
194
+ }
195
+ var i = 0;
196
+ for (var it3 = size - 1; (carry !== 0 || i < length) && (it3 !== -1); it3--, i++) {
197
+ carry += (BASE * b256[it3]) >>> 0;
198
+ b256[it3] = (carry % 256) >>> 0;
199
+ carry = (carry / 256) >>> 0;
200
+ }
201
+ if (carry !== 0) {
202
+ throw new Error('Non-zero carry');
203
+ }
204
+ length = i;
205
+ psz++;
206
+ }
207
+ // Skip trailing spaces.
208
+ if (source[psz] === ' ') {
209
+ return;
210
+ }
211
+ // Skip leading zeroes in b256.
212
+ var it4 = size - length;
213
+ while (it4 !== size && b256[it4] === 0) {
214
+ it4++;
215
+ }
216
+ var vch = new Uint8Array(zeroes + (size - it4));
217
+ var j = zeroes;
218
+ while (it4 !== size) {
219
+ vch[j++] = b256[it4++];
220
+ }
221
+ return vch;
222
+ }
223
+ /**
224
+ * @param {string | string[]} string
225
+ */
226
+ function decode(string) {
227
+ var buffer = decodeUnsafe(string);
228
+ if (buffer) {
229
+ return buffer;
230
+ }
231
+ throw new Error(`Non-${name} character`);
232
+ }
233
+ return {
234
+ encode: encode,
235
+ decodeUnsafe: decodeUnsafe,
236
+ decode: decode
237
+ };
238
+ }
239
+ var src = base;
240
+ var _brrp__multiformats_scope_baseX = src;
241
+
242
+ /**
243
+ * Class represents both BaseEncoder and MultibaseEncoder meaning it
244
+ * can be used to encode to multibase or base encode without multibase
245
+ * prefix.
246
+ */
247
+ class Encoder {
248
+ name;
249
+ prefix;
250
+ baseEncode;
251
+ constructor(name, prefix, baseEncode) {
252
+ this.name = name;
253
+ this.prefix = prefix;
254
+ this.baseEncode = baseEncode;
255
+ }
256
+ encode(bytes) {
257
+ if (bytes instanceof Uint8Array) {
258
+ return `${this.prefix}${this.baseEncode(bytes)}`;
259
+ }
260
+ else {
261
+ throw Error('Unknown type, must be binary type');
262
+ }
263
+ }
264
+ }
265
+ /**
266
+ * Class represents both BaseDecoder and MultibaseDecoder so it could be used
267
+ * to decode multibases (with matching prefix) or just base decode strings
268
+ * with corresponding base encoding.
269
+ */
270
+ class Decoder {
271
+ name;
272
+ prefix;
273
+ baseDecode;
274
+ prefixCodePoint;
275
+ constructor(name, prefix, baseDecode) {
276
+ this.name = name;
277
+ this.prefix = prefix;
278
+ const prefixCodePoint = prefix.codePointAt(0);
279
+ /* c8 ignore next 3 */
280
+ if (prefixCodePoint === undefined) {
281
+ throw new Error('Invalid prefix character');
282
+ }
283
+ this.prefixCodePoint = prefixCodePoint;
284
+ this.baseDecode = baseDecode;
285
+ }
286
+ decode(text) {
287
+ if (typeof text === 'string') {
288
+ if (text.codePointAt(0) !== this.prefixCodePoint) {
289
+ throw Error(`Unable to decode multibase string ${JSON.stringify(text)}, ${this.name} decoder only supports inputs prefixed with ${this.prefix}`);
290
+ }
291
+ return this.baseDecode(text.slice(this.prefix.length));
292
+ }
293
+ else {
294
+ throw Error('Can only multibase decode strings');
295
+ }
296
+ }
297
+ or(decoder) {
298
+ return or(this, decoder);
299
+ }
300
+ }
301
+ class ComposedDecoder {
302
+ decoders;
303
+ constructor(decoders) {
304
+ this.decoders = decoders;
305
+ }
306
+ or(decoder) {
307
+ return or(this, decoder);
308
+ }
309
+ decode(input) {
310
+ const prefix = input[0];
311
+ const decoder = this.decoders[prefix];
312
+ if (decoder != null) {
313
+ return decoder.decode(input);
314
+ }
315
+ else {
316
+ throw RangeError(`Unable to decode multibase string ${JSON.stringify(input)}, only inputs prefixed with ${Object.keys(this.decoders)} are supported`);
317
+ }
318
+ }
319
+ }
320
+ function or(left, right) {
321
+ return new ComposedDecoder({
322
+ ...(left.decoders ?? { [left.prefix]: left }),
323
+ ...(right.decoders ?? { [right.prefix]: right })
324
+ });
325
+ }
326
+ class Codec {
327
+ name;
328
+ prefix;
329
+ baseEncode;
330
+ baseDecode;
331
+ encoder;
332
+ decoder;
333
+ constructor(name, prefix, baseEncode, baseDecode) {
334
+ this.name = name;
335
+ this.prefix = prefix;
336
+ this.baseEncode = baseEncode;
337
+ this.baseDecode = baseDecode;
338
+ this.encoder = new Encoder(name, prefix, baseEncode);
339
+ this.decoder = new Decoder(name, prefix, baseDecode);
340
+ }
341
+ encode(input) {
342
+ return this.encoder.encode(input);
343
+ }
344
+ decode(input) {
345
+ return this.decoder.decode(input);
346
+ }
347
+ }
348
+ function from({ name, prefix, encode, decode }) {
349
+ return new Codec(name, prefix, encode, decode);
350
+ }
351
+ function baseX({ name, prefix, alphabet }) {
352
+ const { encode, decode } = _brrp__multiformats_scope_baseX(alphabet, name);
353
+ return from({
354
+ prefix,
355
+ name,
356
+ encode,
357
+ decode: (text) => coerce(decode(text))
358
+ });
359
+ }
360
+ function decode$3(string, alphabetIdx, bitsPerChar, name) {
361
+ // Count the padding bytes:
362
+ let end = string.length;
363
+ while (string[end - 1] === '=') {
364
+ --end;
365
+ }
366
+ // Allocate the output:
367
+ const out = new Uint8Array((end * bitsPerChar / 8) | 0);
368
+ // Parse the data:
369
+ let bits = 0; // Number of bits currently in the buffer
370
+ let buffer = 0; // Bits waiting to be written out, MSB first
371
+ let written = 0; // Next byte to write
372
+ for (let i = 0; i < end; ++i) {
373
+ // Read one character from the string:
374
+ const value = alphabetIdx[string[i]];
375
+ if (value === undefined) {
376
+ throw new SyntaxError(`Non-${name} character`);
377
+ }
378
+ // Append the bits to the buffer:
379
+ buffer = (buffer << bitsPerChar) | value;
380
+ bits += bitsPerChar;
381
+ // Write out some bits if the buffer has a byte's worth:
382
+ if (bits >= 8) {
383
+ bits -= 8;
384
+ out[written++] = 0xff & (buffer >> bits);
385
+ }
386
+ }
387
+ // Verify that we have received just enough bits:
388
+ if (bits >= bitsPerChar || (0xff & (buffer << (8 - bits))) !== 0) {
389
+ throw new SyntaxError('Unexpected end of data');
390
+ }
391
+ return out;
392
+ }
393
+ function encode$1(data, alphabet, bitsPerChar) {
394
+ const pad = alphabet[alphabet.length - 1] === '=';
395
+ const mask = (1 << bitsPerChar) - 1;
396
+ let out = '';
397
+ let bits = 0; // Number of bits currently in the buffer
398
+ let buffer = 0; // Bits waiting to be written out, MSB first
399
+ for (let i = 0; i < data.length; ++i) {
400
+ // Slurp data into the buffer:
401
+ buffer = (buffer << 8) | data[i];
402
+ bits += 8;
403
+ // Write out as much as we can:
404
+ while (bits > bitsPerChar) {
405
+ bits -= bitsPerChar;
406
+ out += alphabet[mask & (buffer >> bits)];
407
+ }
408
+ }
409
+ // Partial character:
410
+ if (bits !== 0) {
411
+ out += alphabet[mask & (buffer << (bitsPerChar - bits))];
412
+ }
413
+ // Add padding characters until we hit a byte boundary:
414
+ if (pad) {
415
+ while (((out.length * bitsPerChar) & 7) !== 0) {
416
+ out += '=';
417
+ }
418
+ }
419
+ return out;
420
+ }
421
+ function createAlphabetIdx(alphabet) {
422
+ // Build the character lookup table:
423
+ const alphabetIdx = {};
424
+ for (let i = 0; i < alphabet.length; ++i) {
425
+ alphabetIdx[alphabet[i]] = i;
426
+ }
427
+ return alphabetIdx;
428
+ }
429
+ /**
430
+ * RFC4648 Factory
431
+ */
432
+ function rfc4648({ name, prefix, bitsPerChar, alphabet }) {
433
+ const alphabetIdx = createAlphabetIdx(alphabet);
434
+ return from({
435
+ prefix,
436
+ name,
437
+ encode(input) {
438
+ return encode$1(input, alphabet, bitsPerChar);
439
+ },
440
+ decode(input) {
441
+ return decode$3(input, alphabetIdx, bitsPerChar, name);
442
+ }
443
+ });
444
+ }
445
+
446
+ const base32 = rfc4648({
447
+ prefix: 'b',
448
+ name: 'base32',
449
+ alphabet: 'abcdefghijklmnopqrstuvwxyz234567',
450
+ bitsPerChar: 5
451
+ });
452
+ rfc4648({
453
+ prefix: 'B',
454
+ name: 'base32upper',
455
+ alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
456
+ bitsPerChar: 5
457
+ });
458
+ rfc4648({
459
+ prefix: 'c',
460
+ name: 'base32pad',
461
+ alphabet: 'abcdefghijklmnopqrstuvwxyz234567=',
462
+ bitsPerChar: 5
463
+ });
464
+ rfc4648({
465
+ prefix: 'C',
466
+ name: 'base32padupper',
467
+ alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=',
468
+ bitsPerChar: 5
469
+ });
470
+ rfc4648({
471
+ prefix: 'v',
472
+ name: 'base32hex',
473
+ alphabet: '0123456789abcdefghijklmnopqrstuv',
474
+ bitsPerChar: 5
475
+ });
476
+ rfc4648({
477
+ prefix: 'V',
478
+ name: 'base32hexupper',
479
+ alphabet: '0123456789ABCDEFGHIJKLMNOPQRSTUV',
480
+ bitsPerChar: 5
481
+ });
482
+ rfc4648({
483
+ prefix: 't',
484
+ name: 'base32hexpad',
485
+ alphabet: '0123456789abcdefghijklmnopqrstuv=',
486
+ bitsPerChar: 5
487
+ });
488
+ rfc4648({
489
+ prefix: 'T',
490
+ name: 'base32hexpadupper',
491
+ alphabet: '0123456789ABCDEFGHIJKLMNOPQRSTUV=',
492
+ bitsPerChar: 5
493
+ });
494
+ rfc4648({
495
+ prefix: 'h',
496
+ name: 'base32z',
497
+ alphabet: 'ybndrfg8ejkmcpqxot1uwisza345h769',
498
+ bitsPerChar: 5
499
+ });
500
+
501
+ const base36 = baseX({
502
+ prefix: 'k',
503
+ name: 'base36',
504
+ alphabet: '0123456789abcdefghijklmnopqrstuvwxyz'
505
+ });
506
+ baseX({
507
+ prefix: 'K',
508
+ name: 'base36upper',
509
+ alphabet: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
510
+ });
511
+
512
+ const base58btc = baseX({
513
+ name: 'base58btc',
514
+ prefix: 'z',
515
+ alphabet: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
516
+ });
517
+ baseX({
518
+ name: 'base58flickr',
519
+ prefix: 'Z',
520
+ alphabet: '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'
521
+ });
522
+
523
+ /* eslint-disable */
524
+ var encode_1 = encode;
525
+ var MSB = 0x80, MSBALL = -128, INT = Math.pow(2, 31);
526
+ /**
527
+ * @param {number} num
528
+ * @param {number[]} out
529
+ * @param {number} offset
530
+ */
531
+ function encode(num, out, offset) {
532
+ out = out || [];
533
+ offset = offset || 0;
534
+ var oldOffset = offset;
535
+ while (num >= INT) {
536
+ out[offset++] = (num & 0xFF) | MSB;
537
+ num /= 128;
538
+ }
539
+ while (num & MSBALL) {
540
+ out[offset++] = (num & 0xFF) | MSB;
541
+ num >>>= 7;
542
+ }
543
+ out[offset] = num | 0;
544
+ // @ts-ignore
545
+ encode.bytes = offset - oldOffset + 1;
546
+ return out;
547
+ }
548
+ var decode$2 = read;
549
+ var MSB$1 = 0x80, REST$1 = 0x7F;
550
+ /**
551
+ * @param {string | any[]} buf
552
+ * @param {number} offset
553
+ */
554
+ function read(buf, offset) {
555
+ var res = 0, offset = offset || 0, shift = 0, counter = offset, b, l = buf.length;
556
+ do {
557
+ if (counter >= l) {
558
+ // @ts-ignore
559
+ read.bytes = 0;
560
+ throw new RangeError('Could not decode varint');
561
+ }
562
+ b = buf[counter++];
563
+ res += shift < 28
564
+ ? (b & REST$1) << shift
565
+ : (b & REST$1) * Math.pow(2, shift);
566
+ shift += 7;
567
+ } while (b >= MSB$1);
568
+ // @ts-ignore
569
+ read.bytes = counter - offset;
570
+ return res;
571
+ }
572
+ var N1 = Math.pow(2, 7);
573
+ var N2 = Math.pow(2, 14);
574
+ var N3 = Math.pow(2, 21);
575
+ var N4 = Math.pow(2, 28);
576
+ var N5 = Math.pow(2, 35);
577
+ var N6 = Math.pow(2, 42);
578
+ var N7 = Math.pow(2, 49);
579
+ var N8 = Math.pow(2, 56);
580
+ var N9 = Math.pow(2, 63);
581
+ var length = function (/** @type {number} */ value) {
582
+ return (value < N1 ? 1
583
+ : value < N2 ? 2
584
+ : value < N3 ? 3
585
+ : value < N4 ? 4
586
+ : value < N5 ? 5
587
+ : value < N6 ? 6
588
+ : value < N7 ? 7
589
+ : value < N8 ? 8
590
+ : value < N9 ? 9
591
+ : 10);
592
+ };
593
+ var varint = {
594
+ encode: encode_1,
595
+ decode: decode$2,
596
+ encodingLength: length
597
+ };
598
+ var _brrp_varint = varint;
599
+
600
+ function decode$1(data, offset = 0) {
601
+ const code = _brrp_varint.decode(data, offset);
602
+ return [code, _brrp_varint.decode.bytes];
603
+ }
604
+ function encodeTo(int, target, offset = 0) {
605
+ _brrp_varint.encode(int, target, offset);
606
+ return target;
607
+ }
608
+ function encodingLength(int) {
609
+ return _brrp_varint.encodingLength(int);
610
+ }
611
+
612
+ /**
613
+ * Creates a multihash digest.
614
+ */
615
+ function create(code, digest) {
616
+ const size = digest.byteLength;
617
+ const sizeOffset = encodingLength(code);
618
+ const digestOffset = sizeOffset + encodingLength(size);
619
+ const bytes = new Uint8Array(digestOffset + size);
620
+ encodeTo(code, bytes, 0);
621
+ encodeTo(size, bytes, sizeOffset);
622
+ bytes.set(digest, digestOffset);
623
+ return new Digest(code, size, digest, bytes);
624
+ }
625
+ /**
626
+ * Turns bytes representation of multihash digest into an instance.
627
+ */
628
+ function decode(multihash) {
629
+ const bytes = coerce(multihash);
630
+ const [code, sizeOffset] = decode$1(bytes);
631
+ const [size, digestOffset] = decode$1(bytes.subarray(sizeOffset));
632
+ const digest = bytes.subarray(sizeOffset + digestOffset);
633
+ if (digest.byteLength !== size) {
634
+ throw new Error('Incorrect length');
635
+ }
636
+ return new Digest(code, size, digest, bytes);
637
+ }
638
+ function equals(a, b) {
639
+ if (a === b) {
640
+ return true;
641
+ }
642
+ else {
643
+ const data = b;
644
+ return (a.code === data.code &&
645
+ a.size === data.size &&
646
+ data.bytes instanceof Uint8Array &&
647
+ equals$1(a.bytes, data.bytes));
648
+ }
649
+ }
650
+ /**
651
+ * Represents a multihash digest which carries information about the
652
+ * hashing algorithm and an actual hash digest.
653
+ */
654
+ class Digest {
655
+ code;
656
+ size;
657
+ digest;
658
+ bytes;
659
+ /**
660
+ * Creates a multihash digest.
661
+ */
662
+ constructor(code, size, digest, bytes) {
663
+ this.code = code;
664
+ this.size = size;
665
+ this.digest = digest;
666
+ this.bytes = bytes;
667
+ }
668
+ }
669
+
670
+ function format(link, base) {
671
+ const { bytes, version } = link;
672
+ switch (version) {
673
+ case 0:
674
+ return toStringV0(bytes, baseCache(link), base ?? base58btc.encoder);
675
+ default:
676
+ return toStringV1(bytes, baseCache(link), (base ?? base32.encoder));
677
+ }
678
+ }
679
+ const cache = new WeakMap();
680
+ function baseCache(cid) {
681
+ const baseCache = cache.get(cid);
682
+ if (baseCache == null) {
683
+ const baseCache = new Map();
684
+ cache.set(cid, baseCache);
685
+ return baseCache;
686
+ }
687
+ return baseCache;
688
+ }
689
+ class CID {
690
+ code;
691
+ version;
692
+ multihash;
693
+ bytes;
694
+ '/';
695
+ /**
696
+ * @param version - Version of the CID
697
+ * @param code - Code of the codec content is encoded in, see https://github.com/multiformats/multicodec/blob/master/table.csv
698
+ * @param multihash - (Multi)hash of the of the content.
699
+ */
700
+ constructor(version, code, multihash, bytes) {
701
+ this.code = code;
702
+ this.version = version;
703
+ this.multihash = multihash;
704
+ this.bytes = bytes;
705
+ // flag to serializers that this is a CID and
706
+ // should be treated specially
707
+ this['/'] = bytes;
708
+ }
709
+ /**
710
+ * Signalling `cid.asCID === cid` has been replaced with `cid['/'] === cid.bytes`
711
+ * please either use `CID.asCID(cid)` or switch to new signalling mechanism
712
+ *
713
+ * @deprecated
714
+ */
715
+ get asCID() {
716
+ return this;
717
+ }
718
+ // ArrayBufferView
719
+ get byteOffset() {
720
+ return this.bytes.byteOffset;
721
+ }
722
+ // ArrayBufferView
723
+ get byteLength() {
724
+ return this.bytes.byteLength;
725
+ }
726
+ toV0() {
727
+ switch (this.version) {
728
+ case 0: {
729
+ return this;
730
+ }
731
+ case 1: {
732
+ const { code, multihash } = this;
733
+ if (code !== DAG_PB_CODE) {
734
+ throw new Error('Cannot convert a non dag-pb CID to CIDv0');
735
+ }
736
+ // sha2-256
737
+ if (multihash.code !== SHA_256_CODE) {
738
+ throw new Error('Cannot convert non sha2-256 multihash CID to CIDv0');
739
+ }
740
+ return (CID.createV0(multihash));
741
+ }
742
+ default: {
743
+ throw Error(`Can not convert CID version ${this.version} to version 0. This is a bug please report`);
744
+ }
745
+ }
746
+ }
747
+ toV1() {
748
+ switch (this.version) {
749
+ case 0: {
750
+ const { code, digest } = this.multihash;
751
+ const multihash = create(code, digest);
752
+ return (CID.createV1(this.code, multihash));
753
+ }
754
+ case 1: {
755
+ return this;
756
+ }
757
+ default: {
758
+ throw Error(`Can not convert CID version ${this.version} to version 1. This is a bug please report`);
759
+ }
760
+ }
761
+ }
762
+ equals(other) {
763
+ return CID.equals(this, other);
764
+ }
765
+ static equals(self, other) {
766
+ const unknown = other;
767
+ return (unknown != null &&
768
+ self.code === unknown.code &&
769
+ self.version === unknown.version &&
770
+ equals(self.multihash, unknown.multihash));
771
+ }
772
+ toString(base) {
773
+ return format(this, base);
774
+ }
775
+ toJSON() {
776
+ return { '/': format(this) };
777
+ }
778
+ link() {
779
+ return this;
780
+ }
781
+ [Symbol.toStringTag] = 'CID';
782
+ // Legacy
783
+ [Symbol.for('nodejs.util.inspect.custom')]() {
784
+ return `CID(${this.toString()})`;
785
+ }
786
+ /**
787
+ * Takes any input `value` and returns a `CID` instance if it was
788
+ * a `CID` otherwise returns `null`. If `value` is instanceof `CID`
789
+ * it will return value back. If `value` is not instance of this CID
790
+ * class, but is compatible CID it will return new instance of this
791
+ * `CID` class. Otherwise returns null.
792
+ *
793
+ * This allows two different incompatible versions of CID library to
794
+ * co-exist and interop as long as binary interface is compatible.
795
+ */
796
+ static asCID(input) {
797
+ if (input == null) {
798
+ return null;
799
+ }
800
+ const value = input;
801
+ if (value instanceof CID) {
802
+ // If value is instance of CID then we're all set.
803
+ return value;
804
+ }
805
+ else if ((value['/'] != null && value['/'] === value.bytes) || value.asCID === value) {
806
+ // If value isn't instance of this CID class but `this.asCID === this` or
807
+ // `value['/'] === value.bytes` is true it is CID instance coming from a
808
+ // different implementation (diff version or duplicate). In that case we
809
+ // rebase it to this `CID` implementation so caller is guaranteed to get
810
+ // instance with expected API.
811
+ const { version, code, multihash, bytes } = value;
812
+ return new CID(version, code, multihash, bytes ?? encodeCID(version, code, multihash.bytes));
813
+ }
814
+ else if (value[cidSymbol] === true) {
815
+ // If value is a CID from older implementation that used to be tagged via
816
+ // symbol we still rebase it to the this `CID` implementation by
817
+ // delegating that to a constructor.
818
+ const { version, multihash, code } = value;
819
+ const digest = decode(multihash);
820
+ return CID.create(version, code, digest);
821
+ }
822
+ else {
823
+ // Otherwise value is not a CID (or an incompatible version of it) in
824
+ // which case we return `null`.
825
+ return null;
826
+ }
827
+ }
828
+ /**
829
+ * @param version - Version of the CID
830
+ * @param code - Code of the codec content is encoded in, see https://github.com/multiformats/multicodec/blob/master/table.csv
831
+ * @param digest - (Multi)hash of the of the content.
832
+ */
833
+ static create(version, code, digest) {
834
+ if (typeof code !== 'number') {
835
+ throw new Error('String codecs are no longer supported');
836
+ }
837
+ if (!(digest.bytes instanceof Uint8Array)) {
838
+ throw new Error('Invalid digest');
839
+ }
840
+ switch (version) {
841
+ case 0: {
842
+ if (code !== DAG_PB_CODE) {
843
+ throw new Error(`Version 0 CID must use dag-pb (code: ${DAG_PB_CODE}) block encoding`);
844
+ }
845
+ else {
846
+ return new CID(version, code, digest, digest.bytes);
847
+ }
848
+ }
849
+ case 1: {
850
+ const bytes = encodeCID(version, code, digest.bytes);
851
+ return new CID(version, code, digest, bytes);
852
+ }
853
+ default: {
854
+ throw new Error('Invalid version');
855
+ }
856
+ }
857
+ }
858
+ /**
859
+ * Simplified version of `create` for CIDv0.
860
+ */
861
+ static createV0(digest) {
862
+ return CID.create(0, DAG_PB_CODE, digest);
863
+ }
864
+ /**
865
+ * Simplified version of `create` for CIDv1.
866
+ *
867
+ * @param code - Content encoding format code.
868
+ * @param digest - Multihash of the content.
869
+ */
870
+ static createV1(code, digest) {
871
+ return CID.create(1, code, digest);
872
+ }
873
+ /**
874
+ * Decoded a CID from its binary representation. The byte array must contain
875
+ * only the CID with no additional bytes.
876
+ *
877
+ * An error will be thrown if the bytes provided do not contain a valid
878
+ * binary representation of a CID.
879
+ */
880
+ static decode(bytes) {
881
+ const [cid, remainder] = CID.decodeFirst(bytes);
882
+ if (remainder.length !== 0) {
883
+ throw new Error('Incorrect length');
884
+ }
885
+ return cid;
886
+ }
887
+ /**
888
+ * Decoded a CID from its binary representation at the beginning of a byte
889
+ * array.
890
+ *
891
+ * Returns an array with the first element containing the CID and the second
892
+ * element containing the remainder of the original byte array. The remainder
893
+ * will be a zero-length byte array if the provided bytes only contained a
894
+ * binary CID representation.
895
+ */
896
+ static decodeFirst(bytes) {
897
+ const specs = CID.inspectBytes(bytes);
898
+ const prefixSize = specs.size - specs.multihashSize;
899
+ const multihashBytes = coerce(bytes.subarray(prefixSize, prefixSize + specs.multihashSize));
900
+ if (multihashBytes.byteLength !== specs.multihashSize) {
901
+ throw new Error('Incorrect length');
902
+ }
903
+ const digestBytes = multihashBytes.subarray(specs.multihashSize - specs.digestSize);
904
+ const digest = new Digest(specs.multihashCode, specs.digestSize, digestBytes, multihashBytes);
905
+ const cid = specs.version === 0
906
+ ? CID.createV0(digest)
907
+ : CID.createV1(specs.codec, digest);
908
+ return [cid, bytes.subarray(specs.size)];
909
+ }
910
+ /**
911
+ * Inspect the initial bytes of a CID to determine its properties.
912
+ *
913
+ * Involves decoding up to 4 varints. Typically this will require only 4 to 6
914
+ * bytes but for larger multicodec code values and larger multihash digest
915
+ * lengths these varints can be quite large. It is recommended that at least
916
+ * 10 bytes be made available in the `initialBytes` argument for a complete
917
+ * inspection.
918
+ */
919
+ static inspectBytes(initialBytes) {
920
+ let offset = 0;
921
+ const next = () => {
922
+ const [i, length] = decode$1(initialBytes.subarray(offset));
923
+ offset += length;
924
+ return i;
925
+ };
926
+ let version = next();
927
+ let codec = DAG_PB_CODE;
928
+ if (version === 18) {
929
+ // CIDv0
930
+ version = 0;
931
+ offset = 0;
932
+ }
933
+ else {
934
+ codec = next();
935
+ }
936
+ if (version !== 0 && version !== 1) {
937
+ throw new RangeError(`Invalid CID version ${version}`);
938
+ }
939
+ const prefixSize = offset;
940
+ const multihashCode = next(); // multihash code
941
+ const digestSize = next(); // multihash length
942
+ const size = offset + digestSize;
943
+ const multihashSize = size - prefixSize;
944
+ return { version, codec, multihashCode, digestSize, multihashSize, size };
945
+ }
946
+ /**
947
+ * Takes cid in a string representation and creates an instance. If `base`
948
+ * decoder is not provided will use a default from the configuration. It will
949
+ * throw an error if encoding of the CID is not compatible with supplied (or
950
+ * a default decoder).
951
+ */
952
+ static parse(source, base) {
953
+ const [prefix, bytes] = parseCIDtoBytes(source, base);
954
+ const cid = CID.decode(bytes);
955
+ if (cid.version === 0 && source[0] !== 'Q') {
956
+ throw Error('Version 0 CID string must not include multibase prefix');
957
+ }
958
+ // Cache string representation to avoid computing it on `this.toString()`
959
+ baseCache(cid).set(prefix, source);
960
+ return cid;
961
+ }
962
+ }
963
+ function parseCIDtoBytes(source, base) {
964
+ switch (source[0]) {
965
+ // CIDv0 is parsed differently
966
+ case 'Q': {
967
+ const decoder = base ?? base58btc;
968
+ return [
969
+ base58btc.prefix,
970
+ decoder.decode(`${base58btc.prefix}${source}`)
971
+ ];
972
+ }
973
+ case base58btc.prefix: {
974
+ const decoder = base ?? base58btc;
975
+ return [base58btc.prefix, decoder.decode(source)];
976
+ }
977
+ case base32.prefix: {
978
+ const decoder = base ?? base32;
979
+ return [base32.prefix, decoder.decode(source)];
980
+ }
981
+ case base36.prefix: {
982
+ const decoder = base ?? base36;
983
+ return [base36.prefix, decoder.decode(source)];
984
+ }
985
+ default: {
986
+ if (base == null) {
987
+ throw Error('To parse non base32, base36 or base58btc encoded CID multibase decoder must be provided');
988
+ }
989
+ return [source[0], base.decode(source)];
990
+ }
991
+ }
992
+ }
993
+ function toStringV0(bytes, cache, base) {
994
+ const { prefix } = base;
995
+ if (prefix !== base58btc.prefix) {
996
+ throw Error(`Cannot string encode V0 in ${base.name} encoding`);
997
+ }
998
+ const cid = cache.get(prefix);
999
+ if (cid == null) {
1000
+ const cid = base.encode(bytes).slice(1);
1001
+ cache.set(prefix, cid);
1002
+ return cid;
1003
+ }
1004
+ else {
1005
+ return cid;
1006
+ }
1007
+ }
1008
+ function toStringV1(bytes, cache, base) {
1009
+ const { prefix } = base;
1010
+ const cid = cache.get(prefix);
1011
+ if (cid == null) {
1012
+ const cid = base.encode(bytes);
1013
+ cache.set(prefix, cid);
1014
+ return cid;
1015
+ }
1016
+ else {
1017
+ return cid;
1018
+ }
1019
+ }
1020
+ const DAG_PB_CODE = 0x70;
1021
+ const SHA_256_CODE = 0x12;
1022
+ function encodeCID(version, code, multihash) {
1023
+ const codeOffset = encodingLength(version);
1024
+ const hashOffset = codeOffset + encodingLength(code);
1025
+ const bytes = new Uint8Array(hashOffset + multihash.byteLength);
1026
+ encodeTo(version, bytes, 0);
1027
+ encodeTo(code, bytes, codeOffset);
1028
+ bytes.set(multihash, hashOffset);
1029
+ return bytes;
1030
+ }
1031
+ const cidSymbol = Symbol.for('@ipld/js-cid/CID');
1032
+
1033
+ new TextEncoder();
1034
+ new TextDecoder();
1035
+
1036
+ function isValidCID(cid) {
1037
+ try {
1038
+ CID.parse(cid);
1039
+ return true;
1040
+ }
1041
+ catch (error) {
1042
+ return false;
1043
+ }
1044
+ }
1045
+ function isValidDID(did) {
1046
+ if (typeof did !== 'string') {
1047
+ return false;
1048
+ }
1049
+ if (!did.startsWith('did:')) {
1050
+ return false;
1051
+ }
1052
+ const parts = did.split(':');
1053
+ if (parts.length < 3) {
1054
+ return false;
1055
+ }
1056
+ const suffix = parts.pop();
1057
+ return isValidCID(suffix);
1058
+ }
1059
+
1060
+ const DefaultSchema = {
1061
+ "$schema": "http://json-schema.org/draft-07/schema#",
1062
+ "type": "object",
1063
+ "properties": {
1064
+ "propertyName": {
1065
+ "type": "string"
1066
+ }
1067
+ },
1068
+ "required": [
1069
+ "propertyName"
1070
+ ]
1071
+ };
1072
+ exports.DmailTags = void 0;
1073
+ (function (DmailTags) {
1074
+ DmailTags["DMAIL"] = "dmail";
1075
+ DmailTags["INBOX"] = "inbox";
1076
+ DmailTags["DRAFT"] = "draft";
1077
+ DmailTags["SENT"] = "sent";
1078
+ DmailTags["ARCHIVED"] = "archived";
1079
+ DmailTags["DELETED"] = "deleted";
1080
+ DmailTags["UNREAD"] = "unread";
1081
+ })(exports.DmailTags || (exports.DmailTags = {}));
1082
+ exports.NoticeTags = void 0;
1083
+ (function (NoticeTags) {
1084
+ NoticeTags["DMAIL"] = "dmail";
1085
+ NoticeTags["BALLOT"] = "ballot";
1086
+ NoticeTags["POLL"] = "poll";
1087
+ NoticeTags["CREDENTIAL"] = "credential";
1088
+ })(exports.NoticeTags || (exports.NoticeTags = {}));
1089
+ class Keymaster {
1090
+ passphrase;
1091
+ gatekeeper;
1092
+ db;
1093
+ cipher;
1094
+ searchEngine;
1095
+ defaultRegistry;
1096
+ ephemeralRegistry;
1097
+ maxNameLength;
1098
+ maxDataLength;
1099
+ _walletCache;
1100
+ _hdkeyCache;
1101
+ constructor(options) {
1102
+ if (!options || !options.gatekeeper || !options.gatekeeper.createDID) {
1103
+ throw new InvalidParameterError('options.gatekeeper');
1104
+ }
1105
+ if (!options.wallet || !options.wallet.loadWallet || !options.wallet.saveWallet) {
1106
+ throw new InvalidParameterError('options.wallet');
1107
+ }
1108
+ if (!options.cipher || !options.cipher.verifySig) {
1109
+ throw new InvalidParameterError('options.cipher');
1110
+ }
1111
+ if (options.search && !options.search.search) {
1112
+ throw new InvalidParameterError('options.search');
1113
+ }
1114
+ if (!options.passphrase) {
1115
+ throw new InvalidParameterError('options.passphrase');
1116
+ }
1117
+ this.passphrase = options.passphrase;
1118
+ this.gatekeeper = options.gatekeeper;
1119
+ this.db = options.wallet;
1120
+ this.cipher = options.cipher;
1121
+ this.searchEngine = options.search;
1122
+ this.defaultRegistry = options.defaultRegistry || 'hyperswarm';
1123
+ this.ephemeralRegistry = 'hyperswarm';
1124
+ this.maxNameLength = options.maxNameLength || 32;
1125
+ this.maxDataLength = 8 * 1024; // 8 KB max data to store in a JSON object
1126
+ }
1127
+ async listRegistries() {
1128
+ return this.gatekeeper.listRegistries();
1129
+ }
1130
+ async mutateWallet(mutator) {
1131
+ // Create wallet if none and make sure _walletCache is set
1132
+ if (!this._walletCache) {
1133
+ await this.loadWallet();
1134
+ }
1135
+ await this.db.updateWallet(async (stored) => {
1136
+ const decrypted = this._walletCache;
1137
+ const before = JSON.stringify(decrypted);
1138
+ await mutator(decrypted);
1139
+ const after = JSON.stringify(decrypted);
1140
+ if (before === after) {
1141
+ return;
1142
+ }
1143
+ const reenc = await this.encryptWalletForStorage(decrypted);
1144
+ Object.assign(stored, reenc);
1145
+ this._walletCache = decrypted;
1146
+ });
1147
+ }
1148
+ async loadWallet() {
1149
+ if (this._walletCache) {
1150
+ return this._walletCache;
1151
+ }
1152
+ let stored = await this.db.loadWallet();
1153
+ if (!stored) {
1154
+ stored = await this.newWallet();
1155
+ }
1156
+ const upgraded = await this.upgradeWallet(stored);
1157
+ this._walletCache = await this.decryptWallet(upgraded);
1158
+ return this._walletCache;
1159
+ }
1160
+ async saveWallet(wallet, overwrite = true) {
1161
+ let upgraded = await this.upgradeWallet(wallet);
1162
+ // Decrypt if encrypted to verify passphrase and get decrypted form
1163
+ const decrypted = await this.decryptWallet(upgraded);
1164
+ let toStore = await this.encryptWalletForStorage(decrypted);
1165
+ const ok = await this.db.saveWallet(toStore, overwrite);
1166
+ if (ok) {
1167
+ this._walletCache = decrypted;
1168
+ }
1169
+ return ok;
1170
+ }
1171
+ async newWallet(mnemonic, overwrite = false) {
1172
+ try {
1173
+ if (!mnemonic) {
1174
+ mnemonic = this.cipher.generateMnemonic();
1175
+ }
1176
+ this._hdkeyCache = this.cipher.generateHDKey(mnemonic);
1177
+ }
1178
+ catch (error) {
1179
+ throw new InvalidParameterError('mnemonic');
1180
+ }
1181
+ const mnemonicEnc = await encryption.encMnemonic(mnemonic, this.passphrase);
1182
+ const wallet = {
1183
+ version: 1,
1184
+ seed: { mnemonicEnc },
1185
+ counter: 0,
1186
+ ids: {}
1187
+ };
1188
+ const ok = await this.saveWallet(wallet, overwrite);
1189
+ if (!ok) {
1190
+ throw new KeymasterError('save wallet failed');
1191
+ }
1192
+ return wallet;
1193
+ }
1194
+ async decryptMnemonic() {
1195
+ const wallet = await this.loadWallet();
1196
+ return this.getMnemonicForDerivation(wallet);
1197
+ }
1198
+ async getMnemonicForDerivation(wallet) {
1199
+ return encryption.decMnemonic(wallet.seed.mnemonicEnc, this.passphrase);
1200
+ }
1201
+ async checkWallet() {
1202
+ const wallet = await this.loadWallet();
1203
+ let checked = 0;
1204
+ let invalid = 0;
1205
+ let deleted = 0;
1206
+ // Validate keys
1207
+ await this.resolveSeedBank();
1208
+ for (const name of Object.keys(wallet.ids)) {
1209
+ try {
1210
+ const doc = await this.resolveDID(wallet.ids[name].did);
1211
+ if (doc.didDocumentMetadata?.deactivated) {
1212
+ deleted += 1;
1213
+ }
1214
+ }
1215
+ catch (error) {
1216
+ invalid += 1;
1217
+ }
1218
+ checked += 1;
1219
+ }
1220
+ for (const id of Object.values(wallet.ids)) {
1221
+ if (id.owned) {
1222
+ for (const did of id.owned) {
1223
+ try {
1224
+ const doc = await this.resolveDID(did);
1225
+ if (doc.didDocumentMetadata?.deactivated) {
1226
+ deleted += 1;
1227
+ }
1228
+ }
1229
+ catch (error) {
1230
+ invalid += 1;
1231
+ }
1232
+ checked += 1;
1233
+ }
1234
+ }
1235
+ if (id.held) {
1236
+ for (const did of id.held) {
1237
+ try {
1238
+ const doc = await this.resolveDID(did);
1239
+ if (doc.didDocumentMetadata?.deactivated) {
1240
+ deleted += 1;
1241
+ }
1242
+ }
1243
+ catch (error) {
1244
+ invalid += 1;
1245
+ }
1246
+ checked += 1;
1247
+ }
1248
+ }
1249
+ }
1250
+ if (wallet.names) {
1251
+ for (const name of Object.keys(wallet.names)) {
1252
+ try {
1253
+ const doc = await this.resolveDID(wallet.names[name]);
1254
+ if (doc.didDocumentMetadata?.deactivated) {
1255
+ deleted += 1;
1256
+ }
1257
+ }
1258
+ catch (error) {
1259
+ invalid += 1;
1260
+ }
1261
+ checked += 1;
1262
+ }
1263
+ }
1264
+ return { checked, invalid, deleted };
1265
+ }
1266
+ async fixWallet() {
1267
+ let idsRemoved = 0;
1268
+ let ownedRemoved = 0;
1269
+ let heldRemoved = 0;
1270
+ let namesRemoved = 0;
1271
+ await this.mutateWallet(async (wallet) => {
1272
+ for (const name of Object.keys(wallet.ids)) {
1273
+ let remove = false;
1274
+ try {
1275
+ const doc = await this.resolveDID(wallet.ids[name].did);
1276
+ if (doc.didDocumentMetadata?.deactivated) {
1277
+ remove = true;
1278
+ }
1279
+ }
1280
+ catch {
1281
+ remove = true;
1282
+ }
1283
+ if (remove) {
1284
+ delete wallet.ids[name];
1285
+ idsRemoved++;
1286
+ }
1287
+ }
1288
+ for (const id of Object.values(wallet.ids)) {
1289
+ if (id.owned) {
1290
+ for (let i = 0; i < id.owned.length; i++) {
1291
+ let remove = false;
1292
+ try {
1293
+ const doc = await this.resolveDID(id.owned[i]);
1294
+ if (doc.didDocumentMetadata?.deactivated) {
1295
+ remove = true;
1296
+ }
1297
+ }
1298
+ catch {
1299
+ remove = true;
1300
+ }
1301
+ if (remove) {
1302
+ id.owned.splice(i, 1);
1303
+ i--;
1304
+ ownedRemoved++;
1305
+ }
1306
+ }
1307
+ }
1308
+ if (id.held) {
1309
+ for (let i = 0; i < id.held.length; i++) {
1310
+ let remove = false;
1311
+ try {
1312
+ const doc = await this.resolveDID(id.held[i]);
1313
+ if (doc.didDocumentMetadata?.deactivated) {
1314
+ remove = true;
1315
+ }
1316
+ }
1317
+ catch {
1318
+ remove = true;
1319
+ }
1320
+ if (remove) {
1321
+ id.held.splice(i, 1);
1322
+ i--;
1323
+ heldRemoved++;
1324
+ }
1325
+ }
1326
+ }
1327
+ }
1328
+ if (wallet.names) {
1329
+ for (const name of Object.keys(wallet.names)) {
1330
+ let remove = false;
1331
+ try {
1332
+ const doc = await this.resolveDID(wallet.names[name]);
1333
+ if (doc.didDocumentMetadata?.deactivated) {
1334
+ remove = true;
1335
+ }
1336
+ }
1337
+ catch {
1338
+ remove = true;
1339
+ }
1340
+ if (remove) {
1341
+ delete wallet.names[name];
1342
+ namesRemoved++;
1343
+ }
1344
+ }
1345
+ }
1346
+ });
1347
+ return { idsRemoved, ownedRemoved, heldRemoved, namesRemoved };
1348
+ }
1349
+ async resolveSeedBank() {
1350
+ const keypair = await this.hdKeyPair();
1351
+ const operation = {
1352
+ type: "create",
1353
+ created: new Date(0).toISOString(),
1354
+ registration: {
1355
+ version: 1,
1356
+ type: "agent",
1357
+ registry: this.defaultRegistry,
1358
+ },
1359
+ publicJwk: keypair.publicJwk,
1360
+ };
1361
+ const msgHash = this.cipher.hashJSON(operation);
1362
+ const signature = this.cipher.signHash(msgHash, keypair.privateJwk);
1363
+ const signed = {
1364
+ ...operation,
1365
+ signature: {
1366
+ signed: new Date(0).toISOString(),
1367
+ hash: msgHash,
1368
+ value: signature
1369
+ }
1370
+ };
1371
+ const did = await this.gatekeeper.createDID(signed);
1372
+ return this.gatekeeper.resolveDID(did);
1373
+ }
1374
+ async updateSeedBank(doc) {
1375
+ const keypair = await this.hdKeyPair();
1376
+ const did = doc.didDocument?.id;
1377
+ if (!did) {
1378
+ throw new InvalidParameterError('seed bank missing DID');
1379
+ }
1380
+ const current = await this.gatekeeper.resolveDID(did);
1381
+ const previd = current.didDocumentMetadata?.versionId;
1382
+ const operation = {
1383
+ type: "update",
1384
+ did,
1385
+ previd,
1386
+ doc,
1387
+ };
1388
+ const msgHash = this.cipher.hashJSON(operation);
1389
+ const signature = this.cipher.signHash(msgHash, keypair.privateJwk);
1390
+ const signed = {
1391
+ ...operation,
1392
+ signature: {
1393
+ signer: did,
1394
+ signed: new Date().toISOString(),
1395
+ hash: msgHash,
1396
+ value: signature,
1397
+ }
1398
+ };
1399
+ return await this.gatekeeper.updateDID(signed);
1400
+ }
1401
+ async backupWallet(registry = this.defaultRegistry, wallet) {
1402
+ if (!wallet) {
1403
+ wallet = await this.loadWallet();
1404
+ }
1405
+ const keypair = await this.hdKeyPair();
1406
+ const seedBank = await this.resolveSeedBank();
1407
+ const msg = JSON.stringify(wallet);
1408
+ const backup = this.cipher.encryptMessage(keypair.publicJwk, keypair.privateJwk, msg);
1409
+ const operation = {
1410
+ type: "create",
1411
+ created: new Date().toISOString(),
1412
+ registration: {
1413
+ version: 1,
1414
+ type: "asset",
1415
+ registry: registry,
1416
+ },
1417
+ controller: seedBank.didDocument?.id,
1418
+ data: { backup: backup },
1419
+ };
1420
+ const msgHash = this.cipher.hashJSON(operation);
1421
+ const signature = this.cipher.signHash(msgHash, keypair.privateJwk);
1422
+ const signed = {
1423
+ ...operation,
1424
+ signature: {
1425
+ signer: seedBank.didDocument?.id,
1426
+ signed: new Date().toISOString(),
1427
+ hash: msgHash,
1428
+ value: signature,
1429
+ }
1430
+ };
1431
+ const backupDID = await this.gatekeeper.createDID(signed);
1432
+ if (seedBank.didDocumentData && typeof seedBank.didDocumentData === 'object' && !Array.isArray(seedBank.didDocumentData)) {
1433
+ const data = seedBank.didDocumentData;
1434
+ data.wallet = backupDID;
1435
+ await this.updateSeedBank(seedBank);
1436
+ }
1437
+ return backupDID;
1438
+ }
1439
+ async recoverWallet(did) {
1440
+ try {
1441
+ if (!did) {
1442
+ const seedBank = await this.resolveSeedBank();
1443
+ if (seedBank.didDocumentData && typeof seedBank.didDocumentData === 'object' && !Array.isArray(seedBank.didDocumentData)) {
1444
+ const data = seedBank.didDocumentData;
1445
+ did = data.wallet;
1446
+ }
1447
+ if (!did) {
1448
+ throw new InvalidParameterError('No backup DID found');
1449
+ }
1450
+ }
1451
+ const keypair = await this.hdKeyPair();
1452
+ const data = await this.resolveAsset(did);
1453
+ if (!data) {
1454
+ throw new InvalidParameterError('No asset data found');
1455
+ }
1456
+ const castData = data;
1457
+ if (typeof castData.backup !== 'string') {
1458
+ throw new InvalidParameterError('Asset "backup" is missing or not a string');
1459
+ }
1460
+ const backup = this.cipher.decryptMessage(keypair.publicJwk, keypair.privateJwk, castData.backup);
1461
+ let wallet = JSON.parse(backup);
1462
+ if (db_typeGuards.isWalletFile(wallet)) {
1463
+ const mnemonic = await this.decryptMnemonic();
1464
+ // Backup might have a different mnemonic passphase so re-encrypt
1465
+ wallet.seed.mnemonicEnc = await encryption.encMnemonic(mnemonic, this.passphrase);
1466
+ }
1467
+ await this.mutateWallet(async (current) => {
1468
+ // Clear all existing properties from the current wallet
1469
+ // This ensures a clean slate before restoring the recovered wallet
1470
+ for (const k in current) {
1471
+ delete current[k];
1472
+ }
1473
+ // Upgrade the recovered wallet to the latest version if needed
1474
+ wallet = await this.upgradeWallet(wallet);
1475
+ // Decrypt the wallet if needed
1476
+ wallet = db_typeGuards.isWalletEncFile(wallet) ? await this.decryptWalletFromStorage(wallet) : wallet;
1477
+ // Copy all properties from the recovered wallet into the cleared current wallet
1478
+ // This effectively replaces the current wallet with the recovered one
1479
+ Object.assign(current, wallet);
1480
+ });
1481
+ return this.loadWallet();
1482
+ }
1483
+ catch (error) {
1484
+ // If we can't recover the wallet, just return the current one
1485
+ return this.loadWallet();
1486
+ }
1487
+ }
1488
+ async listIds() {
1489
+ const wallet = await this.loadWallet();
1490
+ return Object.keys(wallet.ids);
1491
+ }
1492
+ async getCurrentId() {
1493
+ const wallet = await this.loadWallet();
1494
+ return wallet.current;
1495
+ }
1496
+ async setCurrentId(name) {
1497
+ await this.mutateWallet((wallet) => {
1498
+ if (!(name in wallet.ids)) {
1499
+ throw new UnknownIDError();
1500
+ }
1501
+ wallet.current = name;
1502
+ });
1503
+ return true;
1504
+ }
1505
+ didMatch(did1, did2) {
1506
+ const suffix1 = did1.split(':').pop();
1507
+ const suffix2 = did2.split(':').pop();
1508
+ return (suffix1 === suffix2);
1509
+ }
1510
+ async fetchIdInfo(id, wallet) {
1511
+ // Callers should pass in the wallet if they are going to modify and save it later
1512
+ if (!wallet) {
1513
+ wallet = await this.loadWallet();
1514
+ }
1515
+ let idInfo = null;
1516
+ if (id) {
1517
+ if (id.startsWith('did')) {
1518
+ for (const name of Object.keys(wallet.ids)) {
1519
+ const info = wallet.ids[name];
1520
+ if (this.didMatch(id, info.did)) {
1521
+ idInfo = info;
1522
+ break;
1523
+ }
1524
+ }
1525
+ }
1526
+ else {
1527
+ idInfo = wallet.ids[id];
1528
+ }
1529
+ }
1530
+ else {
1531
+ if (!wallet.current) {
1532
+ throw new KeymasterError('No current ID');
1533
+ }
1534
+ idInfo = wallet.ids[wallet.current];
1535
+ }
1536
+ if (!idInfo) {
1537
+ throw new UnknownIDError();
1538
+ }
1539
+ return idInfo;
1540
+ }
1541
+ async hdKeyPair() {
1542
+ const wallet = await this.loadWallet();
1543
+ const hdkey = await this.getHDKeyFromCacheOrMnemonic(wallet);
1544
+ return this.cipher.generateJwk(hdkey.privateKey);
1545
+ }
1546
+ getPublicKeyJwk(doc) {
1547
+ // TBD Return the right public key, not just the first one
1548
+ if (!doc.didDocument) {
1549
+ throw new KeymasterError('Missing didDocument.');
1550
+ }
1551
+ const verificationMethods = doc.didDocument.verificationMethod;
1552
+ if (!verificationMethods || verificationMethods.length === 0) {
1553
+ throw new KeymasterError('The DID document does not contain any verification methods.');
1554
+ }
1555
+ const publicKeyJwk = verificationMethods[0].publicKeyJwk;
1556
+ if (!publicKeyJwk) {
1557
+ throw new KeymasterError('The publicKeyJwk is missing in the first verification method.');
1558
+ }
1559
+ return publicKeyJwk;
1560
+ }
1561
+ async fetchKeyPair(name) {
1562
+ const wallet = await this.loadWallet();
1563
+ const id = await this.fetchIdInfo(name);
1564
+ const hdkey = await this.getHDKeyFromCacheOrMnemonic(wallet);
1565
+ const doc = await this.resolveDID(id.did, { confirm: true });
1566
+ const confirmedPublicKeyJwk = this.getPublicKeyJwk(doc);
1567
+ for (let i = id.index; i >= 0; i--) {
1568
+ const path = `m/44'/0'/${id.account}'/0/${i}`;
1569
+ const didkey = hdkey.derive(path);
1570
+ const keypair = this.cipher.generateJwk(didkey.privateKey);
1571
+ if (keypair.publicJwk.x === confirmedPublicKeyJwk.x &&
1572
+ keypair.publicJwk.y === confirmedPublicKeyJwk.y) {
1573
+ return keypair;
1574
+ }
1575
+ }
1576
+ return null;
1577
+ }
1578
+ async createAsset(data, options = {}) {
1579
+ let { registry = this.defaultRegistry, controller, validUntil, name } = options;
1580
+ if (validUntil) {
1581
+ const validate = new Date(validUntil);
1582
+ if (isNaN(validate.getTime())) {
1583
+ throw new InvalidParameterError('options.validUntil');
1584
+ }
1585
+ }
1586
+ if (name) {
1587
+ const wallet = await this.loadWallet();
1588
+ this.validateName(name, wallet);
1589
+ }
1590
+ if (!data) {
1591
+ throw new InvalidParameterError('data');
1592
+ }
1593
+ const id = await this.fetchIdInfo(controller);
1594
+ const block = await this.gatekeeper.getBlock(registry);
1595
+ const blockid = block?.hash;
1596
+ const operation = {
1597
+ type: "create",
1598
+ created: new Date().toISOString(),
1599
+ blockid,
1600
+ registration: {
1601
+ version: 1,
1602
+ type: "asset",
1603
+ registry,
1604
+ validUntil
1605
+ },
1606
+ controller: id.did,
1607
+ data,
1608
+ };
1609
+ const signed = await this.addSignature(operation, controller);
1610
+ const did = await this.gatekeeper.createDID(signed);
1611
+ // Keep assets that will be garbage-collected out of the owned list
1612
+ if (!validUntil) {
1613
+ await this.addToOwned(did);
1614
+ }
1615
+ if (name) {
1616
+ await this.addName(name, did);
1617
+ }
1618
+ return did;
1619
+ }
1620
+ async cloneAsset(id, options = {}) {
1621
+ const assetDoc = await this.resolveDID(id);
1622
+ if (assetDoc.didDocumentRegistration?.type !== 'asset') {
1623
+ throw new InvalidParameterError('id');
1624
+ }
1625
+ const assetData = assetDoc.didDocumentData || {};
1626
+ const cloneData = { ...assetData, cloned: assetDoc.didDocument.id };
1627
+ return this.createAsset(cloneData, options);
1628
+ }
1629
+ async generateImageAsset(buffer) {
1630
+ let metadata;
1631
+ try {
1632
+ metadata = imageSize.imageSize(buffer);
1633
+ }
1634
+ catch (error) {
1635
+ throw new InvalidParameterError('buffer');
1636
+ }
1637
+ const cid = await this.gatekeeper.addData(buffer);
1638
+ const image = {
1639
+ cid,
1640
+ bytes: buffer.length,
1641
+ ...metadata,
1642
+ type: `image/${metadata.type}`
1643
+ };
1644
+ return image;
1645
+ }
1646
+ async createImage(buffer, options = {}) {
1647
+ const image = await this.generateImageAsset(buffer);
1648
+ return this.createAsset({ image }, options);
1649
+ }
1650
+ async updateImage(id, buffer) {
1651
+ const image = await this.generateImageAsset(buffer);
1652
+ return this.updateAsset(id, { image });
1653
+ }
1654
+ async getImage(id) {
1655
+ const asset = await this.resolveAsset(id);
1656
+ const image = asset.image;
1657
+ if (!image || !image.cid) {
1658
+ return null;
1659
+ }
1660
+ const buffer = await this.gatekeeper.getData(image.cid);
1661
+ if (buffer) {
1662
+ image.data = buffer;
1663
+ }
1664
+ return image;
1665
+ }
1666
+ async testImage(id) {
1667
+ try {
1668
+ const image = await this.getImage(id);
1669
+ return image !== null;
1670
+ }
1671
+ catch (error) {
1672
+ return false;
1673
+ }
1674
+ }
1675
+ async getMimeType(buffer) {
1676
+ // Try magic number detection
1677
+ const result = await fileType.fileTypeFromBuffer(buffer);
1678
+ if (result)
1679
+ return result.mime;
1680
+ // Convert to UTF-8 string if decodable
1681
+ const text = buffer.toString('utf8');
1682
+ // Check for JSON
1683
+ try {
1684
+ JSON.parse(text);
1685
+ return 'application/json';
1686
+ }
1687
+ catch { }
1688
+ // Default to plain text if printable ASCII
1689
+ // eslint-disable-next-line
1690
+ if (/^[\x09\x0A\x0D\x20-\x7E]*$/.test(text.replace(/\n/g, ''))) {
1691
+ return 'text/plain';
1692
+ }
1693
+ // Fallback
1694
+ return 'application/octet-stream';
1695
+ }
1696
+ async generateFileAsset(filename, buffer) {
1697
+ const cid = await this.gatekeeper.addData(buffer);
1698
+ const type = await this.getMimeType(buffer);
1699
+ const file = {
1700
+ cid,
1701
+ filename,
1702
+ type,
1703
+ bytes: buffer.length,
1704
+ };
1705
+ return file;
1706
+ }
1707
+ async createDocument(buffer, options = {}) {
1708
+ const filename = options.filename || 'document';
1709
+ const document = await this.generateFileAsset(filename, buffer);
1710
+ return this.createAsset({ document }, options);
1711
+ }
1712
+ async updateDocument(id, buffer, options = {}) {
1713
+ const filename = options.filename || 'document';
1714
+ const document = await this.generateFileAsset(filename, buffer);
1715
+ return this.updateAsset(id, { document });
1716
+ }
1717
+ async getDocument(id) {
1718
+ const asset = await this.resolveAsset(id);
1719
+ return asset.document ?? null;
1720
+ }
1721
+ async testDocument(id) {
1722
+ try {
1723
+ const document = await this.getDocument(id);
1724
+ return document !== null;
1725
+ }
1726
+ catch (error) {
1727
+ return false;
1728
+ }
1729
+ }
1730
+ async encryptMessage(msg, receiver, options = {}) {
1731
+ const { encryptForSender = true, includeHash = false, } = options;
1732
+ const id = await this.fetchIdInfo();
1733
+ const senderKeypair = await this.fetchKeyPair();
1734
+ if (!senderKeypair) {
1735
+ throw new KeymasterError('No valid sender keypair');
1736
+ }
1737
+ const doc = await this.resolveDID(receiver, { confirm: true });
1738
+ const receivePublicJwk = this.getPublicKeyJwk(doc);
1739
+ const cipher_sender = encryptForSender ? this.cipher.encryptMessage(senderKeypair.publicJwk, senderKeypair.privateJwk, msg) : null;
1740
+ const cipher_receiver = this.cipher.encryptMessage(receivePublicJwk, senderKeypair.privateJwk, msg);
1741
+ const cipher_hash = includeHash ? this.cipher.hashMessage(msg) : null;
1742
+ const encrypted = {
1743
+ sender: id.did,
1744
+ created: new Date().toISOString(),
1745
+ cipher_hash,
1746
+ cipher_sender,
1747
+ cipher_receiver,
1748
+ };
1749
+ return await this.createAsset({ encrypted }, options);
1750
+ }
1751
+ async decryptWithDerivedKeys(wallet, id, senderPublicJwk, ciphertext) {
1752
+ const hdkey = await this.getHDKeyFromCacheOrMnemonic(wallet);
1753
+ // Try all private keys for this ID, starting with the most recent and working backward
1754
+ let index = id.index;
1755
+ while (index >= 0) {
1756
+ const path = `m/44'/0'/${id.account}'/0/${index}`;
1757
+ const didkey = hdkey.derive(path);
1758
+ const receiverKeypair = this.cipher.generateJwk(didkey.privateKey);
1759
+ try {
1760
+ return this.cipher.decryptMessage(senderPublicJwk, receiverKeypair.privateJwk, ciphertext);
1761
+ }
1762
+ catch (error) {
1763
+ index -= 1;
1764
+ }
1765
+ }
1766
+ throw new KeymasterError("ID can't decrypt ciphertext");
1767
+ }
1768
+ async decryptMessage(did) {
1769
+ const wallet = await this.loadWallet();
1770
+ const id = await this.fetchIdInfo();
1771
+ const asset = await this.resolveAsset(did);
1772
+ if (!asset) {
1773
+ throw new InvalidParameterError('did not encrypted');
1774
+ }
1775
+ const castAsset = asset;
1776
+ if (!castAsset.encrypted && !castAsset.cipher_hash) {
1777
+ throw new InvalidParameterError('did not encrypted');
1778
+ }
1779
+ const crypt = (castAsset.encrypted ? castAsset.encrypted : castAsset);
1780
+ const doc = await this.resolveDID(crypt.sender, { confirm: true, versionTime: crypt.created });
1781
+ const senderPublicJwk = this.getPublicKeyJwk(doc);
1782
+ const ciphertext = (crypt.sender === id.did && crypt.cipher_sender) ? crypt.cipher_sender : crypt.cipher_receiver;
1783
+ return await this.decryptWithDerivedKeys(wallet, id, senderPublicJwk, ciphertext);
1784
+ }
1785
+ async encryptJSON(json, did, options = {}) {
1786
+ const plaintext = JSON.stringify(json);
1787
+ return this.encryptMessage(plaintext, did, options);
1788
+ }
1789
+ async decryptJSON(did) {
1790
+ const plaintext = await this.decryptMessage(did);
1791
+ try {
1792
+ return JSON.parse(plaintext);
1793
+ }
1794
+ catch (error) {
1795
+ throw new InvalidParameterError('did not encrypted JSON');
1796
+ }
1797
+ }
1798
+ async addSignature(obj, controller) {
1799
+ if (obj == null) {
1800
+ throw new InvalidParameterError('obj');
1801
+ }
1802
+ // Fetches current ID if name is missing
1803
+ const id = await this.fetchIdInfo(controller);
1804
+ const keypair = await this.fetchKeyPair(controller);
1805
+ if (!keypair) {
1806
+ throw new KeymasterError('addSignature: no keypair');
1807
+ }
1808
+ try {
1809
+ const msgHash = this.cipher.hashJSON(obj);
1810
+ const signature = this.cipher.signHash(msgHash, keypair.privateJwk);
1811
+ return {
1812
+ ...obj,
1813
+ signature: {
1814
+ signer: id.did,
1815
+ signed: new Date().toISOString(),
1816
+ hash: msgHash,
1817
+ value: signature,
1818
+ }
1819
+ };
1820
+ }
1821
+ catch (error) {
1822
+ throw new InvalidParameterError('obj');
1823
+ }
1824
+ }
1825
+ async verifySignature(obj) {
1826
+ if (!obj?.signature) {
1827
+ return false;
1828
+ }
1829
+ const { signature } = obj;
1830
+ if (!signature.signer) {
1831
+ return false;
1832
+ }
1833
+ const jsonCopy = JSON.parse(JSON.stringify(obj));
1834
+ delete jsonCopy.signature;
1835
+ const msgHash = this.cipher.hashJSON(jsonCopy);
1836
+ if (signature.hash && signature.hash !== msgHash) {
1837
+ return false;
1838
+ }
1839
+ const doc = await this.resolveDID(signature.signer, { versionTime: signature.signed });
1840
+ const publicJwk = this.getPublicKeyJwk(doc);
1841
+ try {
1842
+ return this.cipher.verifySig(msgHash, signature.value, publicJwk);
1843
+ }
1844
+ catch (error) {
1845
+ return false;
1846
+ }
1847
+ }
1848
+ async updateDID(id, doc) {
1849
+ const did = await this.lookupDID(id);
1850
+ const current = await this.resolveDID(did);
1851
+ const previd = current.didDocumentMetadata?.versionId;
1852
+ // Strip metadata fields from the update doc
1853
+ delete doc.didDocumentMetadata;
1854
+ delete doc.didResolutionMetadata;
1855
+ const block = await this.gatekeeper.getBlock(current.didDocumentRegistration.registry);
1856
+ const blockid = block?.hash;
1857
+ const operation = {
1858
+ type: "update",
1859
+ did,
1860
+ previd,
1861
+ blockid,
1862
+ doc,
1863
+ };
1864
+ let controller;
1865
+ if (current.didDocumentRegistration?.type === 'agent') {
1866
+ controller = current.didDocument?.id;
1867
+ }
1868
+ else if (current.didDocumentRegistration?.type === 'asset') {
1869
+ controller = current.didDocument?.controller;
1870
+ }
1871
+ const signed = await this.addSignature(operation, controller);
1872
+ return this.gatekeeper.updateDID(signed);
1873
+ }
1874
+ async revokeDID(id) {
1875
+ const did = await this.lookupDID(id);
1876
+ const current = await this.resolveDID(did);
1877
+ const previd = current.didDocumentMetadata?.versionId;
1878
+ const block = await this.gatekeeper.getBlock(current.didDocumentRegistration.registry);
1879
+ const blockid = block?.hash;
1880
+ const operation = {
1881
+ type: "delete",
1882
+ did,
1883
+ previd,
1884
+ blockid
1885
+ };
1886
+ let controller;
1887
+ if (current.didDocumentRegistration?.type === 'agent') {
1888
+ controller = current.didDocument?.id;
1889
+ }
1890
+ else if (current.didDocumentRegistration?.type === 'asset') {
1891
+ controller = current.didDocument?.controller;
1892
+ }
1893
+ const signed = await this.addSignature(operation, controller);
1894
+ const ok = await this.gatekeeper.deleteDID(signed);
1895
+ if (ok && current.didDocument?.controller) {
1896
+ await this.removeFromOwned(did, current.didDocument.controller);
1897
+ }
1898
+ return ok;
1899
+ }
1900
+ async addToOwned(did, owner) {
1901
+ await this.mutateWallet(async (wallet) => {
1902
+ const id = await this.fetchIdInfo(owner, wallet);
1903
+ const owned = new Set(id.owned);
1904
+ owned.add(did);
1905
+ id.owned = Array.from(owned);
1906
+ });
1907
+ return true;
1908
+ }
1909
+ async removeFromOwned(did, owner) {
1910
+ let ownerFound = false;
1911
+ await this.mutateWallet(async (wallet) => {
1912
+ const id = await this.fetchIdInfo(owner, wallet);
1913
+ if (!id.owned) {
1914
+ return;
1915
+ }
1916
+ ownerFound = true;
1917
+ id.owned = id.owned.filter(item => item !== did);
1918
+ });
1919
+ return ownerFound;
1920
+ }
1921
+ async addToHeld(did) {
1922
+ await this.mutateWallet((wallet) => {
1923
+ const id = wallet.ids[wallet.current];
1924
+ const held = new Set(id.held);
1925
+ held.add(did);
1926
+ id.held = Array.from(held);
1927
+ });
1928
+ return true;
1929
+ }
1930
+ async removeFromHeld(did) {
1931
+ let changed = false;
1932
+ await this.mutateWallet((wallet) => {
1933
+ const id = wallet.ids[wallet.current];
1934
+ const held = new Set(id.held);
1935
+ if (held.delete(did)) {
1936
+ id.held = Array.from(held);
1937
+ changed = true;
1938
+ }
1939
+ });
1940
+ return changed;
1941
+ }
1942
+ async lookupDID(name) {
1943
+ try {
1944
+ if (name.startsWith('did:')) {
1945
+ return name;
1946
+ }
1947
+ }
1948
+ catch {
1949
+ throw new InvalidDIDError();
1950
+ }
1951
+ const wallet = await this.loadWallet();
1952
+ if (wallet.names && name in wallet.names) {
1953
+ return wallet.names[name];
1954
+ }
1955
+ if (wallet.ids && name in wallet.ids) {
1956
+ return wallet.ids[name].did;
1957
+ }
1958
+ throw new UnknownIDError();
1959
+ }
1960
+ async resolveDID(did, options) {
1961
+ const actualDid = await this.lookupDID(did);
1962
+ const docs = await this.gatekeeper.resolveDID(actualDid, options);
1963
+ if (docs.didResolutionMetadata?.error) {
1964
+ if (docs.didResolutionMetadata.error === 'notFound') {
1965
+ throw new InvalidDIDError('unknown');
1966
+ }
1967
+ if (docs.didResolutionMetadata.error === 'invalidDid') {
1968
+ throw new InvalidDIDError('bad format');
1969
+ }
1970
+ }
1971
+ const controller = docs.didDocument?.controller || docs.didDocument?.id;
1972
+ const isOwned = await this.idInWallet(controller);
1973
+ // Augment the DID document metadata with the DID ownership status
1974
+ docs.didDocumentMetadata = {
1975
+ ...docs.didDocumentMetadata,
1976
+ isOwned,
1977
+ };
1978
+ return docs;
1979
+ }
1980
+ async idInWallet(did) {
1981
+ try {
1982
+ await this.fetchIdInfo(did);
1983
+ return true;
1984
+ }
1985
+ catch (error) {
1986
+ return false;
1987
+ }
1988
+ }
1989
+ async resolveAsset(did, options) {
1990
+ const doc = await this.resolveDID(did, options);
1991
+ if (!doc?.didDocument?.controller || !doc?.didDocumentData || doc.didDocumentMetadata?.deactivated) {
1992
+ return {};
1993
+ }
1994
+ return doc.didDocumentData;
1995
+ }
1996
+ async updateAsset(did, data) {
1997
+ const doc = await this.resolveDID(did);
1998
+ const currentData = doc.didDocumentData || {};
1999
+ const updatedData = {
2000
+ ...currentData,
2001
+ ...data
2002
+ };
2003
+ return this.updateDID(did, { didDocumentData: updatedData });
2004
+ }
2005
+ async transferAsset(id, controller) {
2006
+ const assetDoc = await this.resolveDID(id);
2007
+ if (assetDoc.didDocumentRegistration?.type !== 'asset') {
2008
+ throw new InvalidParameterError('id');
2009
+ }
2010
+ if (assetDoc.didDocument.controller === controller) {
2011
+ return true;
2012
+ }
2013
+ const agentDoc = await this.resolveDID(controller);
2014
+ if (agentDoc.didDocumentRegistration?.type !== 'agent') {
2015
+ throw new InvalidParameterError('controller');
2016
+ }
2017
+ const assetDID = assetDoc.didDocument.id;
2018
+ const prevOwner = assetDoc.didDocument.controller;
2019
+ const updatedDidDocument = {
2020
+ ...assetDoc.didDocument,
2021
+ controller: agentDoc.didDocument.id,
2022
+ };
2023
+ const ok = await this.updateDID(id, { didDocument: updatedDidDocument });
2024
+ if (ok && assetDID && prevOwner) {
2025
+ await this.removeFromOwned(assetDID, prevOwner);
2026
+ try {
2027
+ await this.addToOwned(assetDID, controller);
2028
+ }
2029
+ catch (error) {
2030
+ // New controller is not in our wallet
2031
+ }
2032
+ }
2033
+ return ok;
2034
+ }
2035
+ async listAssets(owner) {
2036
+ const id = await this.fetchIdInfo(owner);
2037
+ return id.owned || [];
2038
+ }
2039
+ validateName(name, wallet) {
2040
+ if (typeof name !== 'string' || !name.trim()) {
2041
+ throw new InvalidParameterError('name must be a non-empty string');
2042
+ }
2043
+ name = name.trim(); // Remove leading/trailing whitespace
2044
+ if (name.length > this.maxNameLength) {
2045
+ throw new InvalidParameterError(`name too long`);
2046
+ }
2047
+ if (/[^\P{Cc}]/u.test(name)) {
2048
+ throw new InvalidParameterError('name contains unprintable characters');
2049
+ }
2050
+ const alreadyUsedError = 'name already used';
2051
+ if (wallet && wallet.names && name in wallet.names) {
2052
+ throw new InvalidParameterError(alreadyUsedError);
2053
+ }
2054
+ if (wallet && wallet.ids && name in wallet.ids) {
2055
+ throw new InvalidParameterError(alreadyUsedError);
2056
+ }
2057
+ return name;
2058
+ }
2059
+ async createId(name, options = {}) {
2060
+ let did = '';
2061
+ await this.mutateWallet(async (wallet) => {
2062
+ const account = wallet.counter;
2063
+ const index = 0;
2064
+ const signed = await this.createIdOperation(name, account, options);
2065
+ did = await this.gatekeeper.createDID(signed);
2066
+ wallet.ids[name] = { did, account, index };
2067
+ wallet.counter += 1;
2068
+ wallet.current = name;
2069
+ });
2070
+ return did;
2071
+ }
2072
+ async createIdOperation(name, account = 0, options = {}) {
2073
+ const { registry = this.defaultRegistry } = options;
2074
+ const wallet = await this.loadWallet();
2075
+ name = this.validateName(name, wallet);
2076
+ const hdkey = await this.getHDKeyFromCacheOrMnemonic(wallet);
2077
+ const path = `m/44'/0'/${account}'/0/0`;
2078
+ const didkey = hdkey.derive(path);
2079
+ const keypair = this.cipher.generateJwk(didkey.privateKey);
2080
+ const block = await this.gatekeeper.getBlock(registry);
2081
+ const blockid = block?.hash;
2082
+ const operation = {
2083
+ type: 'create',
2084
+ created: new Date().toISOString(),
2085
+ blockid,
2086
+ registration: {
2087
+ version: 1,
2088
+ type: 'agent',
2089
+ registry
2090
+ },
2091
+ publicJwk: keypair.publicJwk,
2092
+ };
2093
+ const msgHash = this.cipher.hashJSON(operation);
2094
+ const signature = this.cipher.signHash(msgHash, keypair.privateJwk);
2095
+ const signed = {
2096
+ ...operation,
2097
+ signature: {
2098
+ signed: new Date().toISOString(),
2099
+ hash: msgHash,
2100
+ value: signature
2101
+ },
2102
+ };
2103
+ return signed;
2104
+ }
2105
+ async removeId(name) {
2106
+ await this.mutateWallet((wallet) => {
2107
+ if (!(name in wallet.ids)) {
2108
+ throw new UnknownIDError();
2109
+ }
2110
+ delete wallet.ids[name];
2111
+ if (wallet.current === name) {
2112
+ wallet.current = Object.keys(wallet.ids)[0] || '';
2113
+ }
2114
+ });
2115
+ return true;
2116
+ }
2117
+ async renameId(id, name) {
2118
+ await this.mutateWallet((wallet) => {
2119
+ name = this.validateName(name);
2120
+ if (!(id in wallet.ids)) {
2121
+ throw new UnknownIDError();
2122
+ }
2123
+ if (name in wallet.ids) {
2124
+ throw new InvalidParameterError('name already used');
2125
+ }
2126
+ wallet.ids[name] = wallet.ids[id];
2127
+ delete wallet.ids[id];
2128
+ if (wallet.current && wallet.current === id) {
2129
+ wallet.current = name;
2130
+ }
2131
+ });
2132
+ return true;
2133
+ }
2134
+ async backupId(id) {
2135
+ // Backs up current ID if id is not provided
2136
+ const wallet = await this.loadWallet();
2137
+ const name = id || wallet.current;
2138
+ if (!name) {
2139
+ throw new InvalidParameterError('no current ID');
2140
+ }
2141
+ const idInfo = await this.fetchIdInfo(name, wallet);
2142
+ const keypair = await this.hdKeyPair();
2143
+ const data = {
2144
+ name: name,
2145
+ id: idInfo,
2146
+ };
2147
+ const msg = JSON.stringify(data);
2148
+ const backup = this.cipher.encryptMessage(keypair.publicJwk, keypair.privateJwk, msg);
2149
+ const doc = await this.resolveDID(idInfo.did);
2150
+ const registry = doc.didDocumentRegistration?.registry;
2151
+ if (!registry) {
2152
+ throw new InvalidParameterError('no registry found for agent DID');
2153
+ }
2154
+ const vaultDid = await this.createAsset({ backup: backup }, { registry, controller: name });
2155
+ if (doc.didDocumentData) {
2156
+ const currentData = doc.didDocumentData;
2157
+ const updatedData = { ...currentData, vault: vaultDid };
2158
+ return this.updateDID(name, { didDocumentData: updatedData });
2159
+ }
2160
+ return false;
2161
+ }
2162
+ async recoverId(did) {
2163
+ try {
2164
+ const keypair = await this.hdKeyPair();
2165
+ const doc = await this.resolveDID(did);
2166
+ const docData = doc.didDocumentData;
2167
+ if (!docData.vault) {
2168
+ throw new InvalidDIDError('didDocumentData missing vault');
2169
+ }
2170
+ const vault = await this.resolveAsset(docData.vault);
2171
+ if (typeof vault.backup !== 'string') {
2172
+ throw new InvalidDIDError('backup not found in vault');
2173
+ }
2174
+ const backup = this.cipher.decryptMessage(keypair.publicJwk, keypair.privateJwk, vault.backup);
2175
+ const data = JSON.parse(backup);
2176
+ await this.mutateWallet((wallet) => {
2177
+ if (wallet.ids[data.name]) {
2178
+ throw new KeymasterError(`${data.name} already exists in wallet`);
2179
+ }
2180
+ wallet.ids[data.name] = data.id;
2181
+ wallet.current = data.name;
2182
+ wallet.counter += 1;
2183
+ });
2184
+ return data.name;
2185
+ }
2186
+ catch (error) {
2187
+ if (error.type === 'Keymaster') {
2188
+ throw error;
2189
+ }
2190
+ else {
2191
+ throw new InvalidDIDError();
2192
+ }
2193
+ }
2194
+ }
2195
+ async rotateKeys() {
2196
+ let ok = false;
2197
+ await this.mutateWallet(async (wallet) => {
2198
+ const id = wallet.ids[wallet.current];
2199
+ const nextIndex = id.index + 1;
2200
+ const hdkey = await this.getHDKeyFromCacheOrMnemonic(wallet);
2201
+ const path = `m/44'/0'/${id.account}'/0/${nextIndex}`;
2202
+ const didkey = hdkey.derive(path);
2203
+ const keypair = this.cipher.generateJwk(didkey.privateKey);
2204
+ const doc = await this.resolveDID(id.did);
2205
+ if (!doc.didDocumentMetadata?.confirmed) {
2206
+ throw new KeymasterError('Cannot rotate keys');
2207
+ }
2208
+ if (!doc.didDocument?.verificationMethod) {
2209
+ throw new KeymasterError('DID Document missing verificationMethod');
2210
+ }
2211
+ const vmethod = { ...doc.didDocument.verificationMethod[0] };
2212
+ vmethod.id = `#key-${nextIndex + 1}`;
2213
+ vmethod.publicKeyJwk = keypair.publicJwk;
2214
+ const updatedDidDocument = {
2215
+ ...doc.didDocument,
2216
+ verificationMethod: [vmethod],
2217
+ authentication: [vmethod.id],
2218
+ };
2219
+ ok = await this.updateDID(id.did, { didDocument: updatedDidDocument });
2220
+ if (!ok) {
2221
+ throw new KeymasterError('Cannot rotate keys');
2222
+ }
2223
+ id.index = nextIndex; // persist in same mutation
2224
+ });
2225
+ return ok;
2226
+ }
2227
+ async listNames(options = {}) {
2228
+ const { includeIDs = false } = options;
2229
+ const wallet = await this.loadWallet();
2230
+ const names = wallet.names || {};
2231
+ if (includeIDs) {
2232
+ for (const [name, id] of Object.entries(wallet.ids || {})) {
2233
+ names[name] = id.did;
2234
+ }
2235
+ }
2236
+ return names;
2237
+ }
2238
+ async addName(name, did) {
2239
+ await this.mutateWallet((wallet) => {
2240
+ if (!wallet.names) {
2241
+ wallet.names = {};
2242
+ }
2243
+ name = this.validateName(name, wallet);
2244
+ wallet.names[name] = did;
2245
+ });
2246
+ return true;
2247
+ }
2248
+ async getName(name) {
2249
+ const wallet = await this.loadWallet();
2250
+ if (wallet.names && name in wallet.names) {
2251
+ return wallet.names[name];
2252
+ }
2253
+ return null;
2254
+ }
2255
+ async removeName(name) {
2256
+ await this.mutateWallet((wallet) => {
2257
+ if (!wallet.names || !(name in wallet.names)) {
2258
+ return;
2259
+ }
2260
+ delete wallet.names[name];
2261
+ });
2262
+ return true;
2263
+ }
2264
+ async testAgent(id) {
2265
+ const doc = await this.resolveDID(id);
2266
+ return doc.didDocumentRegistration?.type === 'agent';
2267
+ }
2268
+ async bindCredential(schemaId, subjectId, options = {}) {
2269
+ let { validFrom, validUntil, credential } = options;
2270
+ if (!validFrom) {
2271
+ validFrom = new Date().toISOString();
2272
+ }
2273
+ const id = await this.fetchIdInfo();
2274
+ const type = await this.lookupDID(schemaId);
2275
+ const subjectDID = await this.lookupDID(subjectId);
2276
+ if (!credential) {
2277
+ const schema = await this.getSchema(type);
2278
+ credential = this.generateSchema(schema);
2279
+ }
2280
+ return {
2281
+ "@context": [
2282
+ "https://www.w3.org/ns/credentials/v2",
2283
+ "https://www.w3.org/ns/credentials/examples/v2"
2284
+ ],
2285
+ type: ["VerifiableCredential", type],
2286
+ issuer: id.did,
2287
+ validFrom,
2288
+ validUntil,
2289
+ credentialSubject: {
2290
+ id: subjectDID,
2291
+ },
2292
+ credential,
2293
+ };
2294
+ }
2295
+ async issueCredential(credential, options = {}) {
2296
+ const id = await this.fetchIdInfo();
2297
+ if (options.schema && options.subject) {
2298
+ credential = await this.bindCredential(options.schema, options.subject, { credential, ...options });
2299
+ }
2300
+ if (credential.issuer !== id.did) {
2301
+ throw new InvalidParameterError('credential.issuer');
2302
+ }
2303
+ const signed = await this.addSignature(credential);
2304
+ return this.encryptJSON(signed, credential.credentialSubject.id, { ...options, includeHash: true });
2305
+ }
2306
+ async sendCredential(did, options = {}) {
2307
+ const vc = await this.getCredential(did);
2308
+ if (!vc) {
2309
+ return null;
2310
+ }
2311
+ const registry = this.ephemeralRegistry;
2312
+ const validUntil = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); // Default to 7 days
2313
+ const message = {
2314
+ to: [vc.credentialSubject.id],
2315
+ dids: [did],
2316
+ };
2317
+ return this.createNotice(message, { registry, validUntil, ...options });
2318
+ }
2319
+ isVerifiableCredential(obj) {
2320
+ if (typeof obj !== 'object' || !obj) {
2321
+ return false;
2322
+ }
2323
+ const vc = obj;
2324
+ return !(!Array.isArray(vc["@context"]) || !Array.isArray(vc.type) || !vc.issuer || !vc.credentialSubject);
2325
+ }
2326
+ async updateCredential(did, credential) {
2327
+ did = await this.lookupDID(did);
2328
+ const originalVC = await this.decryptJSON(did);
2329
+ if (!this.isVerifiableCredential(originalVC)) {
2330
+ throw new InvalidParameterError("did is not a credential");
2331
+ }
2332
+ if (!credential ||
2333
+ !credential.credential ||
2334
+ !credential.credentialSubject ||
2335
+ !credential.credentialSubject.id) {
2336
+ throw new InvalidParameterError('credential');
2337
+ }
2338
+ delete credential.signature;
2339
+ const signed = await this.addSignature(credential);
2340
+ const msg = JSON.stringify(signed);
2341
+ const id = await this.fetchIdInfo();
2342
+ const senderKeypair = await this.fetchKeyPair();
2343
+ if (!senderKeypair) {
2344
+ throw new KeymasterError('No valid sender keypair');
2345
+ }
2346
+ const holder = credential.credentialSubject.id;
2347
+ const holderDoc = await this.resolveDID(holder, { confirm: true });
2348
+ const receivePublicJwk = this.getPublicKeyJwk(holderDoc);
2349
+ const cipher_sender = this.cipher.encryptMessage(senderKeypair.publicJwk, senderKeypair.privateJwk, msg);
2350
+ const cipher_receiver = this.cipher.encryptMessage(receivePublicJwk, senderKeypair.privateJwk, msg);
2351
+ const msgHash = this.cipher.hashMessage(msg);
2352
+ const encrypted = {
2353
+ sender: id.did,
2354
+ created: new Date().toISOString(),
2355
+ cipher_hash: msgHash,
2356
+ cipher_sender: cipher_sender,
2357
+ cipher_receiver: cipher_receiver,
2358
+ };
2359
+ return this.updateDID(did, { didDocumentData: { encrypted } });
2360
+ }
2361
+ async revokeCredential(credential) {
2362
+ const did = await this.lookupDID(credential);
2363
+ return this.revokeDID(did);
2364
+ }
2365
+ async listIssued(issuer) {
2366
+ const id = await this.fetchIdInfo(issuer);
2367
+ const issued = [];
2368
+ if (id.owned) {
2369
+ for (const did of id.owned) {
2370
+ try {
2371
+ const credential = await this.decryptJSON(did);
2372
+ if (this.isVerifiableCredential(credential) &&
2373
+ credential.issuer === id.did) {
2374
+ issued.push(did);
2375
+ }
2376
+ }
2377
+ catch (error) { }
2378
+ }
2379
+ }
2380
+ return issued;
2381
+ }
2382
+ async acceptCredential(did) {
2383
+ try {
2384
+ const id = await this.fetchIdInfo();
2385
+ const credential = await this.lookupDID(did);
2386
+ const vc = await this.decryptJSON(credential);
2387
+ if (this.isVerifiableCredential(vc) &&
2388
+ vc.credentialSubject?.id !== id.did) {
2389
+ return false;
2390
+ }
2391
+ return this.addToHeld(credential);
2392
+ }
2393
+ catch (error) {
2394
+ return false;
2395
+ }
2396
+ }
2397
+ async getCredential(id) {
2398
+ const did = await this.lookupDID(id);
2399
+ const vc = await this.decryptJSON(did);
2400
+ if (!this.isVerifiableCredential(vc)) {
2401
+ return null;
2402
+ }
2403
+ return vc;
2404
+ }
2405
+ async removeCredential(id) {
2406
+ const did = await this.lookupDID(id);
2407
+ return this.removeFromHeld(did);
2408
+ }
2409
+ async listCredentials(id) {
2410
+ const idInfo = await this.fetchIdInfo(id);
2411
+ return idInfo.held || [];
2412
+ }
2413
+ async publishCredential(did, options = {}) {
2414
+ const { reveal = false } = options;
2415
+ const id = await this.fetchIdInfo();
2416
+ const credential = await this.lookupDID(did);
2417
+ const vc = await this.decryptJSON(credential);
2418
+ if (!this.isVerifiableCredential(vc)) {
2419
+ throw new InvalidParameterError("did is not a credential");
2420
+ }
2421
+ if (vc.credentialSubject?.id !== id.did) {
2422
+ throw new InvalidParameterError('only subject can publish a credential');
2423
+ }
2424
+ const doc = await this.resolveDID(id.did);
2425
+ if (!doc.didDocumentData) {
2426
+ doc.didDocumentData = {};
2427
+ }
2428
+ const data = doc.didDocumentData;
2429
+ if (!data.manifest) {
2430
+ data.manifest = {};
2431
+ }
2432
+ if (!reveal) {
2433
+ // Remove the credential values
2434
+ vc.credential = null;
2435
+ }
2436
+ data.manifest[credential] = vc;
2437
+ const ok = await this.updateDID(id.did, { didDocumentData: doc.didDocumentData });
2438
+ if (ok) {
2439
+ return vc;
2440
+ }
2441
+ throw new KeymasterError('update DID failed');
2442
+ }
2443
+ async unpublishCredential(did) {
2444
+ const id = await this.fetchIdInfo();
2445
+ const doc = await this.resolveDID(id.did);
2446
+ const credential = await this.lookupDID(did);
2447
+ const data = doc.didDocumentData;
2448
+ if (credential && data.manifest && credential in data.manifest) {
2449
+ delete data.manifest[credential];
2450
+ await this.updateDID(id.did, { didDocumentData: doc.didDocumentData });
2451
+ return `OK credential ${did} removed from manifest`;
2452
+ }
2453
+ throw new InvalidParameterError('did');
2454
+ }
2455
+ async createChallenge(challenge = {}, options = {}) {
2456
+ if (!challenge || typeof challenge !== 'object' || Array.isArray(challenge)) {
2457
+ throw new InvalidParameterError('challenge');
2458
+ }
2459
+ if (challenge.credentials && !Array.isArray(challenge.credentials)) {
2460
+ throw new InvalidParameterError('challenge.credentials');
2461
+ // TBD validate each credential spec
2462
+ }
2463
+ if (!options.registry) {
2464
+ options.registry = this.ephemeralRegistry;
2465
+ }
2466
+ if (!options.validUntil) {
2467
+ const expires = new Date();
2468
+ expires.setHours(expires.getHours() + 1); // Add 1 hour
2469
+ options.validUntil = expires.toISOString();
2470
+ }
2471
+ return this.createAsset({ challenge }, options);
2472
+ }
2473
+ async findMatchingCredential(credential) {
2474
+ const id = await this.fetchIdInfo();
2475
+ if (!id.held) {
2476
+ return;
2477
+ }
2478
+ for (let did of id.held) {
2479
+ try {
2480
+ const doc = await this.decryptJSON(did);
2481
+ if (!this.isVerifiableCredential(doc)) {
2482
+ continue;
2483
+ }
2484
+ if (doc.credentialSubject?.id !== id.did) {
2485
+ // This VC is issued by the ID, not held
2486
+ continue;
2487
+ }
2488
+ if (credential.issuers && !credential.issuers.includes(doc.issuer)) {
2489
+ // Attestor not trusted by Verifier
2490
+ continue;
2491
+ }
2492
+ if (doc.type && !doc.type.includes(credential.schema)) {
2493
+ // Wrong type
2494
+ continue;
2495
+ }
2496
+ // TBD test for VC expiry too
2497
+ return did;
2498
+ }
2499
+ catch (error) {
2500
+ // Not encrypted, so can't be a VC
2501
+ }
2502
+ }
2503
+ }
2504
+ async createResponse(challengeDID, options = {}) {
2505
+ let { retries = 0, delay = 1000 } = options;
2506
+ if (!options.registry) {
2507
+ options.registry = this.ephemeralRegistry;
2508
+ }
2509
+ if (!options.validUntil) {
2510
+ const expires = new Date();
2511
+ expires.setHours(expires.getHours() + 1); // Add 1 hour
2512
+ options.validUntil = expires.toISOString();
2513
+ }
2514
+ let doc;
2515
+ while (retries >= 0) {
2516
+ try {
2517
+ doc = await this.resolveDID(challengeDID);
2518
+ break;
2519
+ }
2520
+ catch (error) {
2521
+ if (retries === 0)
2522
+ throw error; // If no retries left, throw the error
2523
+ retries--; // Decrease the retry count
2524
+ await new Promise(resolve => setTimeout(resolve, delay)); // Wait for delay milleseconds
2525
+ }
2526
+ }
2527
+ if (!doc) {
2528
+ throw new InvalidParameterError('challengeDID does not resolve');
2529
+ }
2530
+ const result = await this.resolveAsset(challengeDID);
2531
+ if (!result) {
2532
+ throw new InvalidParameterError('challengeDID');
2533
+ }
2534
+ const challenge = result.challenge;
2535
+ if (!challenge) {
2536
+ throw new InvalidParameterError('challengeDID');
2537
+ }
2538
+ const requestor = doc.didDocument?.controller;
2539
+ if (!requestor) {
2540
+ throw new InvalidParameterError('requestor undefined');
2541
+ }
2542
+ // TBD check challenge isValid for expired?
2543
+ const matches = [];
2544
+ if (challenge.credentials) {
2545
+ for (let credential of challenge.credentials) {
2546
+ const vc = await this.findMatchingCredential(credential);
2547
+ if (vc) {
2548
+ matches.push(vc);
2549
+ }
2550
+ }
2551
+ }
2552
+ const pairs = [];
2553
+ for (let vcDid of matches) {
2554
+ const plaintext = await this.decryptMessage(vcDid);
2555
+ const vpDid = await this.encryptMessage(plaintext, requestor, { ...options, includeHash: true });
2556
+ pairs.push({ vc: vcDid, vp: vpDid });
2557
+ }
2558
+ const requested = challenge.credentials?.length ?? 0;
2559
+ const fulfilled = matches.length;
2560
+ const match = (requested === fulfilled);
2561
+ const response = {
2562
+ challenge: challengeDID,
2563
+ credentials: pairs,
2564
+ requested: requested,
2565
+ fulfilled: fulfilled,
2566
+ match: match
2567
+ };
2568
+ return await this.encryptJSON({ response }, requestor, options);
2569
+ }
2570
+ async verifyResponse(responseDID, options = {}) {
2571
+ let { retries = 0, delay = 1000 } = options;
2572
+ let responseDoc;
2573
+ while (retries >= 0) {
2574
+ try {
2575
+ responseDoc = await this.resolveDID(responseDID);
2576
+ break;
2577
+ }
2578
+ catch (error) {
2579
+ if (retries === 0)
2580
+ throw error; // If no retries left, throw the error
2581
+ retries--; // Decrease the retry count
2582
+ await new Promise(resolve => setTimeout(resolve, delay)); // Wait for delay milliseconds
2583
+ }
2584
+ }
2585
+ if (!responseDoc) {
2586
+ throw new InvalidParameterError('responseDID does not resolve');
2587
+ }
2588
+ const wrapper = await this.decryptJSON(responseDID);
2589
+ if (typeof wrapper !== 'object' || !wrapper || !('response' in wrapper)) {
2590
+ throw new InvalidParameterError('responseDID not a valid challenge response');
2591
+ }
2592
+ const { response } = wrapper;
2593
+ const result = await this.resolveAsset(response.challenge);
2594
+ if (!result) {
2595
+ throw new InvalidParameterError('challenge not found');
2596
+ }
2597
+ const challenge = result.challenge;
2598
+ if (!challenge) {
2599
+ throw new InvalidParameterError('challengeDID');
2600
+ }
2601
+ const vps = [];
2602
+ for (let credential of response.credentials) {
2603
+ const vcData = await this.resolveAsset(credential.vc);
2604
+ const vpData = await this.resolveAsset(credential.vp);
2605
+ const castVCData = vcData;
2606
+ const castVPData = vpData;
2607
+ if (!vcData || !vpData || !castVCData.encrypted || !castVPData.encrypted) {
2608
+ // VC revoked
2609
+ continue;
2610
+ }
2611
+ const vcHash = castVCData.encrypted;
2612
+ const vpHash = castVPData.encrypted;
2613
+ if (vcHash.cipher_hash !== vpHash.cipher_hash) {
2614
+ // can't verify that the contents of VP match the VC
2615
+ continue;
2616
+ }
2617
+ const vp = await this.decryptJSON(credential.vp);
2618
+ const isValid = await this.verifySignature(vp);
2619
+ if (!isValid) {
2620
+ continue;
2621
+ }
2622
+ if (!vp.type || !Array.isArray(vp.type)) {
2623
+ continue;
2624
+ }
2625
+ // Check VP against VCs specified in challenge
2626
+ if (vp.type.length >= 2 && vp.type[1].startsWith('did:')) {
2627
+ const schema = vp.type[1];
2628
+ const credential = challenge.credentials?.find(item => item.schema === schema);
2629
+ if (!credential) {
2630
+ continue;
2631
+ }
2632
+ // Check if issuer of VP is in the trusted issuer list
2633
+ if (credential.issuers && credential.issuers.length > 0 && !credential.issuers.includes(vp.issuer)) {
2634
+ continue;
2635
+ }
2636
+ }
2637
+ vps.push(vp);
2638
+ }
2639
+ response.vps = vps;
2640
+ response.match = vps.length === (challenge.credentials?.length ?? 0);
2641
+ response.responder = responseDoc.didDocument?.controller;
2642
+ return response;
2643
+ }
2644
+ async createGroup(name, options = {}) {
2645
+ const group = {
2646
+ name: name,
2647
+ members: []
2648
+ };
2649
+ return this.createAsset({ group }, options);
2650
+ }
2651
+ async getGroup(id) {
2652
+ const asset = await this.resolveAsset(id);
2653
+ if (!asset) {
2654
+ return null;
2655
+ }
2656
+ // TEMP during did:cid, return old version groups
2657
+ const castOldAsset = asset;
2658
+ if (castOldAsset.members) {
2659
+ return castOldAsset;
2660
+ }
2661
+ const castAsset = asset;
2662
+ if (!castAsset.group) {
2663
+ return null;
2664
+ }
2665
+ return castAsset.group;
2666
+ }
2667
+ async addGroupMember(groupId, memberId) {
2668
+ const groupDID = await this.lookupDID(groupId);
2669
+ const memberDID = await this.lookupDID(memberId);
2670
+ // Can't add a group to itself
2671
+ if (memberDID === groupDID) {
2672
+ throw new InvalidParameterError("can't add a group to itself");
2673
+ }
2674
+ try {
2675
+ // test for valid member DID
2676
+ await this.resolveDID(memberDID);
2677
+ }
2678
+ catch {
2679
+ throw new InvalidParameterError('memberId');
2680
+ }
2681
+ const group = await this.getGroup(groupId);
2682
+ if (!group?.members) {
2683
+ throw new InvalidParameterError('groupId');
2684
+ }
2685
+ // If already a member, return immediately
2686
+ if (group.members.includes(memberDID)) {
2687
+ return true;
2688
+ }
2689
+ // Can't add a mutual membership relation
2690
+ const isMember = await this.testGroup(memberId, groupId);
2691
+ if (isMember) {
2692
+ throw new InvalidParameterError("can't create mutual membership");
2693
+ }
2694
+ const members = new Set(group.members);
2695
+ members.add(memberDID);
2696
+ group.members = Array.from(members);
2697
+ return this.updateAsset(groupDID, { group });
2698
+ }
2699
+ async removeGroupMember(groupId, memberId) {
2700
+ const groupDID = await this.lookupDID(groupId);
2701
+ const memberDID = await this.lookupDID(memberId);
2702
+ const group = await this.getGroup(groupDID);
2703
+ if (!group?.members) {
2704
+ throw new InvalidParameterError('groupId');
2705
+ }
2706
+ try {
2707
+ // test for valid member DID
2708
+ await this.resolveDID(memberDID);
2709
+ }
2710
+ catch {
2711
+ throw new InvalidParameterError('memberId');
2712
+ }
2713
+ // If not already a member, return immediately
2714
+ if (!group.members.includes(memberDID)) {
2715
+ return true;
2716
+ }
2717
+ const members = new Set(group.members);
2718
+ members.delete(memberDID);
2719
+ group.members = Array.from(members);
2720
+ return this.updateAsset(groupDID, { group });
2721
+ }
2722
+ async testGroup(groupId, memberId) {
2723
+ try {
2724
+ const group = await this.getGroup(groupId);
2725
+ if (!group) {
2726
+ return false;
2727
+ }
2728
+ if (!memberId) {
2729
+ return true;
2730
+ }
2731
+ const didMember = await this.lookupDID(memberId);
2732
+ let isMember = group.members.includes(didMember);
2733
+ if (!isMember) {
2734
+ for (const did of group.members) {
2735
+ isMember = await this.testGroup(did, didMember);
2736
+ if (isMember) {
2737
+ break;
2738
+ }
2739
+ }
2740
+ }
2741
+ return isMember;
2742
+ }
2743
+ catch (error) {
2744
+ return false;
2745
+ }
2746
+ }
2747
+ async listGroups(owner) {
2748
+ const assets = await this.listAssets(owner);
2749
+ const groups = [];
2750
+ for (const did of assets) {
2751
+ const isGroup = await this.testGroup(did);
2752
+ if (isGroup) {
2753
+ groups.push(did);
2754
+ }
2755
+ }
2756
+ return groups;
2757
+ }
2758
+ validateSchema(schema) {
2759
+ try {
2760
+ // Attempt to instantiate the schema
2761
+ this.generateSchema(schema);
2762
+ return true;
2763
+ }
2764
+ catch (error) {
2765
+ return false;
2766
+ }
2767
+ }
2768
+ generateSchema(schema) {
2769
+ if (typeof schema !== 'object' ||
2770
+ !schema ||
2771
+ !('$schema' in schema) ||
2772
+ !('properties' in schema)) {
2773
+ throw new InvalidParameterError('schema');
2774
+ }
2775
+ const template = {};
2776
+ const props = schema.properties;
2777
+ for (const property of Object.keys(props)) {
2778
+ template[property] = "TBD";
2779
+ }
2780
+ return template;
2781
+ }
2782
+ async createSchema(schema, options = {}) {
2783
+ if (!schema) {
2784
+ schema = DefaultSchema;
2785
+ }
2786
+ if (!this.validateSchema(schema)) {
2787
+ throw new InvalidParameterError('schema');
2788
+ }
2789
+ return this.createAsset({ schema }, options);
2790
+ }
2791
+ async getSchema(id) {
2792
+ const asset = await this.resolveAsset(id);
2793
+ if (!asset) {
2794
+ return null;
2795
+ }
2796
+ // TEMP during did:cid, return old version schemas
2797
+ const castOldAsset = asset;
2798
+ if (castOldAsset.properties) {
2799
+ return asset;
2800
+ }
2801
+ const castAsset = asset;
2802
+ if (!castAsset.schema) {
2803
+ return null;
2804
+ }
2805
+ return castAsset.schema;
2806
+ }
2807
+ async setSchema(id, schema) {
2808
+ if (!this.validateSchema(schema)) {
2809
+ throw new InvalidParameterError('schema');
2810
+ }
2811
+ return this.updateAsset(id, { schema });
2812
+ }
2813
+ // TBD add optional 2nd parameter that will validate JSON against the schema
2814
+ async testSchema(id) {
2815
+ try {
2816
+ const schema = await this.getSchema(id);
2817
+ // TBD Need a better way because any random object with keys can be a valid schema
2818
+ if (!schema || Object.keys(schema).length === 0) {
2819
+ return false;
2820
+ }
2821
+ return this.validateSchema(schema);
2822
+ }
2823
+ catch (error) {
2824
+ return false;
2825
+ }
2826
+ }
2827
+ async listSchemas(owner) {
2828
+ const assets = await this.listAssets(owner);
2829
+ const schemas = [];
2830
+ for (const did of assets) {
2831
+ const isSchema = await this.testSchema(did);
2832
+ if (isSchema) {
2833
+ schemas.push(did);
2834
+ }
2835
+ }
2836
+ return schemas;
2837
+ }
2838
+ async createTemplate(schemaId) {
2839
+ const isSchema = await this.testSchema(schemaId);
2840
+ if (!isSchema) {
2841
+ throw new InvalidParameterError('schemaId');
2842
+ }
2843
+ const schemaDID = await this.lookupDID(schemaId);
2844
+ const schema = await this.getSchema(schemaDID);
2845
+ const template = this.generateSchema(schema);
2846
+ template['$schema'] = schemaDID;
2847
+ return template;
2848
+ }
2849
+ async pollTemplate() {
2850
+ const now = new Date();
2851
+ const nextWeek = new Date();
2852
+ nextWeek.setDate(now.getDate() + 7);
2853
+ return {
2854
+ type: 'poll',
2855
+ version: 1,
2856
+ description: 'What is this poll about?',
2857
+ roster: 'DID of the eligible voter group',
2858
+ options: ['yes', 'no', 'abstain'],
2859
+ deadline: nextWeek.toISOString(),
2860
+ };
2861
+ }
2862
+ async createPoll(poll, options = {}) {
2863
+ if (poll.type !== 'poll') {
2864
+ throw new InvalidParameterError('poll');
2865
+ }
2866
+ if (poll.version !== 1) {
2867
+ throw new InvalidParameterError('poll.version');
2868
+ }
2869
+ if (!poll.description) {
2870
+ throw new InvalidParameterError('poll.description');
2871
+ }
2872
+ if (!poll.options || !Array.isArray(poll.options) || poll.options.length < 2 || poll.options.length > 10) {
2873
+ throw new InvalidParameterError('poll.options');
2874
+ }
2875
+ if (!poll.roster) {
2876
+ // eslint-disable-next-line
2877
+ throw new InvalidParameterError('poll.roster');
2878
+ }
2879
+ try {
2880
+ const isValidGroup = await this.testGroup(poll.roster);
2881
+ if (!isValidGroup) {
2882
+ throw new InvalidParameterError('poll.roster');
2883
+ }
2884
+ }
2885
+ catch {
2886
+ throw new InvalidParameterError('poll.roster');
2887
+ }
2888
+ if (!poll.deadline) {
2889
+ // eslint-disable-next-line
2890
+ throw new InvalidParameterError('poll.deadline');
2891
+ }
2892
+ const deadline = new Date(poll.deadline);
2893
+ if (isNaN(deadline.getTime())) {
2894
+ throw new InvalidParameterError('poll.deadline');
2895
+ }
2896
+ if (deadline < new Date()) {
2897
+ throw new InvalidParameterError('poll.deadline');
2898
+ }
2899
+ return this.createAsset({ poll }, options);
2900
+ }
2901
+ async getPoll(id) {
2902
+ const asset = await this.resolveAsset(id);
2903
+ // TEMP during did:cid, return old version poll
2904
+ const castOldAsset = asset;
2905
+ if (castOldAsset.options) {
2906
+ return castOldAsset;
2907
+ }
2908
+ const castAsset = asset;
2909
+ if (!castAsset.poll) {
2910
+ return null;
2911
+ }
2912
+ return castAsset.poll;
2913
+ }
2914
+ async testPoll(id) {
2915
+ try {
2916
+ const poll = await this.getPoll(id);
2917
+ return poll !== null;
2918
+ }
2919
+ catch (error) {
2920
+ return false;
2921
+ }
2922
+ }
2923
+ async listPolls(owner) {
2924
+ const assets = await this.listAssets(owner);
2925
+ const polls = [];
2926
+ for (const did of assets) {
2927
+ const isPoll = await this.testPoll(did);
2928
+ if (isPoll) {
2929
+ polls.push(did);
2930
+ }
2931
+ }
2932
+ return polls;
2933
+ }
2934
+ async viewPoll(pollId) {
2935
+ const id = await this.fetchIdInfo();
2936
+ const poll = await this.getPoll(pollId);
2937
+ if (!poll) {
2938
+ throw new InvalidParameterError('pollId');
2939
+ }
2940
+ let hasVoted = false;
2941
+ if (poll.ballots) {
2942
+ hasVoted = !!poll.ballots[id.did];
2943
+ }
2944
+ const voteExpired = Date.now() > new Date(poll.deadline).getTime();
2945
+ const isEligible = await this.testGroup(poll.roster, id.did);
2946
+ const doc = await this.resolveDID(pollId);
2947
+ const view = {
2948
+ description: poll.description,
2949
+ options: poll.options,
2950
+ deadline: poll.deadline,
2951
+ isOwner: (doc.didDocument?.controller === id.did),
2952
+ isEligible: isEligible,
2953
+ voteExpired: voteExpired,
2954
+ hasVoted: hasVoted,
2955
+ };
2956
+ if (id.did === doc.didDocument?.controller) {
2957
+ let voted = 0;
2958
+ const results = {
2959
+ tally: [],
2960
+ ballots: [],
2961
+ };
2962
+ results.tally.push({
2963
+ vote: 0,
2964
+ option: 'spoil',
2965
+ count: 0,
2966
+ });
2967
+ for (let i = 0; i < poll.options.length; i++) {
2968
+ results.tally.push({
2969
+ vote: i + 1,
2970
+ option: poll.options[i],
2971
+ count: 0,
2972
+ });
2973
+ }
2974
+ for (let voter in poll.ballots) {
2975
+ const ballot = poll.ballots[voter];
2976
+ const decrypted = await this.decryptJSON(ballot.ballot);
2977
+ const vote = decrypted.vote;
2978
+ if (results.ballots) {
2979
+ results.ballots.push({
2980
+ ...ballot,
2981
+ voter,
2982
+ vote,
2983
+ option: poll.options[vote - 1],
2984
+ });
2985
+ }
2986
+ voted += 1;
2987
+ results.tally[vote].count += 1;
2988
+ }
2989
+ const roster = await this.getGroup(poll.roster);
2990
+ const total = roster.members.length;
2991
+ results.votes = {
2992
+ eligible: total,
2993
+ received: voted,
2994
+ pending: total - voted,
2995
+ };
2996
+ results.final = voteExpired || (voted === total);
2997
+ view.results = results;
2998
+ }
2999
+ return view;
3000
+ }
3001
+ async votePoll(pollId, vote, options = {}) {
3002
+ const { spoil = false } = options;
3003
+ const id = await this.fetchIdInfo();
3004
+ const didPoll = await this.lookupDID(pollId);
3005
+ const doc = await this.resolveDID(didPoll);
3006
+ const poll = await this.getPoll(pollId);
3007
+ if (!poll) {
3008
+ throw new InvalidParameterError('pollId');
3009
+ }
3010
+ const eligible = await this.testGroup(poll.roster, id.did);
3011
+ const expired = Date.now() > new Date(poll.deadline).getTime();
3012
+ const owner = doc.didDocument?.controller;
3013
+ if (!owner) {
3014
+ throw new KeymasterError('owner mising from poll');
3015
+ }
3016
+ if (!eligible) {
3017
+ throw new InvalidParameterError('voter not in roster');
3018
+ }
3019
+ if (expired) {
3020
+ throw new InvalidParameterError('poll has expired');
3021
+ }
3022
+ let ballot;
3023
+ if (spoil) {
3024
+ ballot = {
3025
+ poll: didPoll,
3026
+ vote: 0,
3027
+ };
3028
+ }
3029
+ else {
3030
+ const max = poll.options.length;
3031
+ if (!Number.isInteger(vote) || vote < 1 || vote > max) {
3032
+ throw new InvalidParameterError('vote');
3033
+ }
3034
+ ballot = {
3035
+ poll: didPoll,
3036
+ vote: vote,
3037
+ };
3038
+ }
3039
+ // Encrypt for receiver only
3040
+ return await this.encryptJSON(ballot, owner, { ...options, encryptForSender: false });
3041
+ }
3042
+ async updatePoll(ballot) {
3043
+ const id = await this.fetchIdInfo();
3044
+ const didBallot = await this.lookupDID(ballot);
3045
+ const docBallot = await this.resolveDID(ballot);
3046
+ const didVoter = docBallot.didDocument.controller;
3047
+ let dataBallot;
3048
+ try {
3049
+ dataBallot = await this.decryptJSON(didBallot);
3050
+ if (!dataBallot.poll || !dataBallot.vote) {
3051
+ throw new InvalidParameterError('ballot');
3052
+ }
3053
+ }
3054
+ catch {
3055
+ throw new InvalidParameterError('ballot');
3056
+ }
3057
+ const didPoll = dataBallot.poll;
3058
+ const docPoll = await this.resolveDID(didPoll);
3059
+ const didOwner = docPoll.didDocument.controller;
3060
+ const poll = await this.getPoll(didPoll);
3061
+ if (!poll) {
3062
+ throw new KeymasterError('Cannot find poll related to ballot');
3063
+ }
3064
+ if (id.did !== didOwner) {
3065
+ throw new InvalidParameterError('only owner can update a poll');
3066
+ }
3067
+ const eligible = await this.testGroup(poll.roster, didVoter);
3068
+ if (!eligible) {
3069
+ throw new InvalidParameterError('voter not in roster');
3070
+ }
3071
+ const expired = Date.now() > new Date(poll.deadline).getTime();
3072
+ if (expired) {
3073
+ throw new InvalidParameterError('poll has expired');
3074
+ }
3075
+ const max = poll.options.length;
3076
+ const vote = dataBallot.vote;
3077
+ if (!vote || vote < 0 || vote > max) {
3078
+ throw new InvalidParameterError('ballot.vote');
3079
+ }
3080
+ if (!poll.ballots) {
3081
+ poll.ballots = {};
3082
+ }
3083
+ poll.ballots[didVoter] = {
3084
+ ballot: didBallot,
3085
+ received: new Date().toISOString(),
3086
+ };
3087
+ return this.updateAsset(didPoll, { poll });
3088
+ }
3089
+ async publishPoll(pollId, options = {}) {
3090
+ const { reveal = false } = options;
3091
+ const id = await this.fetchIdInfo();
3092
+ const doc = await this.resolveDID(pollId);
3093
+ const owner = doc.didDocument?.controller;
3094
+ if (id.did !== owner) {
3095
+ throw new InvalidParameterError('only owner can publish a poll');
3096
+ }
3097
+ const view = await this.viewPoll(pollId);
3098
+ if (!view.results?.final) {
3099
+ throw new InvalidParameterError('poll not final');
3100
+ }
3101
+ if (!reveal && view.results.ballots) {
3102
+ delete view.results.ballots;
3103
+ }
3104
+ const poll = await this.getPoll(pollId);
3105
+ if (!poll) {
3106
+ throw new InvalidParameterError(pollId);
3107
+ }
3108
+ poll.results = view.results;
3109
+ return this.updateAsset(pollId, { poll });
3110
+ }
3111
+ async unpublishPoll(pollId) {
3112
+ const id = await this.fetchIdInfo();
3113
+ const doc = await this.resolveDID(pollId);
3114
+ const owner = doc.didDocument?.controller;
3115
+ if (id.did !== owner) {
3116
+ throw new InvalidParameterError(pollId);
3117
+ }
3118
+ const poll = await this.getPoll(pollId);
3119
+ if (!poll) {
3120
+ throw new InvalidParameterError(pollId);
3121
+ }
3122
+ delete poll.results;
3123
+ return this.updateAsset(pollId, { poll });
3124
+ }
3125
+ async createGroupVault(options = {}) {
3126
+ const id = await this.fetchIdInfo();
3127
+ const idKeypair = await this.fetchKeyPair();
3128
+ // version defaults to 1. To make version undefined (unit testing), set options.version to 0
3129
+ const version = typeof options.version === 'undefined'
3130
+ ? 1
3131
+ : (typeof options.version === 'number' && options.version === 1 ? options.version : undefined);
3132
+ const salt = this.cipher.generateRandomSalt();
3133
+ const vaultKeypair = this.cipher.generateRandomJwk();
3134
+ const keys = {};
3135
+ const config = this.cipher.encryptMessage(idKeypair.publicJwk, vaultKeypair.privateJwk, JSON.stringify(options));
3136
+ const publicJwk = options.secretMembers ? idKeypair.publicJwk : vaultKeypair.publicJwk; // If secret, encrypt for the owner only
3137
+ const members = this.cipher.encryptMessage(publicJwk, vaultKeypair.privateJwk, JSON.stringify({}));
3138
+ const items = this.cipher.encryptMessage(vaultKeypair.publicJwk, vaultKeypair.privateJwk, JSON.stringify({}));
3139
+ const sha256 = this.cipher.hashJSON({});
3140
+ const groupVault = {
3141
+ version,
3142
+ publicJwk: vaultKeypair.publicJwk,
3143
+ salt,
3144
+ config,
3145
+ members,
3146
+ keys,
3147
+ items,
3148
+ sha256,
3149
+ };
3150
+ await this.addMemberKey(groupVault, id.did, vaultKeypair.privateJwk);
3151
+ return this.createAsset({ groupVault }, options);
3152
+ }
3153
+ async getGroupVault(groupVaultId, options) {
3154
+ const asset = await this.resolveAsset(groupVaultId, options);
3155
+ if (!asset.groupVault) {
3156
+ throw new InvalidParameterError('groupVaultId');
3157
+ }
3158
+ return asset.groupVault;
3159
+ }
3160
+ async testGroupVault(id, options) {
3161
+ try {
3162
+ const groupVault = await this.getGroupVault(id, options);
3163
+ return groupVault !== null;
3164
+ }
3165
+ catch (error) {
3166
+ return false;
3167
+ }
3168
+ }
3169
+ generateSaltedId(groupVault, memberDID) {
3170
+ if (!groupVault.version) {
3171
+ return this.cipher.hashMessage(groupVault.salt + memberDID);
3172
+ }
3173
+ const suffix = memberDID.split(':').pop();
3174
+ return this.cipher.hashMessage(groupVault.salt + suffix);
3175
+ }
3176
+ async decryptGroupVault(groupVault) {
3177
+ const wallet = await this.loadWallet();
3178
+ const id = await this.fetchIdInfo();
3179
+ const myMemberId = this.generateSaltedId(groupVault, id.did);
3180
+ const myVaultKey = groupVault.keys[myMemberId];
3181
+ if (!myVaultKey) {
3182
+ throw new KeymasterError('No access to group vault');
3183
+ }
3184
+ const privKeyJSON = await this.decryptWithDerivedKeys(wallet, id, groupVault.publicJwk, myVaultKey);
3185
+ const privateJwk = JSON.parse(privKeyJSON);
3186
+ let config = {};
3187
+ let isOwner = false;
3188
+ try {
3189
+ const configJSON = await this.decryptWithDerivedKeys(wallet, id, groupVault.publicJwk, groupVault.config);
3190
+ config = JSON.parse(configJSON);
3191
+ isOwner = true;
3192
+ }
3193
+ catch (error) {
3194
+ // Can't decrypt config if not the owner
3195
+ }
3196
+ let members = {};
3197
+ if (config.secretMembers) {
3198
+ try {
3199
+ const membersJSON = await this.decryptWithDerivedKeys(wallet, id, groupVault.publicJwk, groupVault.members);
3200
+ members = JSON.parse(membersJSON);
3201
+ }
3202
+ catch (error) {
3203
+ }
3204
+ }
3205
+ else {
3206
+ try {
3207
+ const membersJSON = this.cipher.decryptMessage(groupVault.publicJwk, privateJwk, groupVault.members);
3208
+ members = JSON.parse(membersJSON);
3209
+ }
3210
+ catch (error) {
3211
+ }
3212
+ }
3213
+ const itemsJSON = this.cipher.decryptMessage(groupVault.publicJwk, privateJwk, groupVault.items);
3214
+ const items = JSON.parse(itemsJSON);
3215
+ return {
3216
+ isOwner,
3217
+ privateJwk,
3218
+ config,
3219
+ members,
3220
+ items,
3221
+ };
3222
+ }
3223
+ async checkGroupVaultOwner(vaultId) {
3224
+ const id = await this.fetchIdInfo();
3225
+ const vaultDoc = await this.resolveDID(vaultId);
3226
+ const controller = vaultDoc.didDocument?.controller;
3227
+ if (controller !== id.did) {
3228
+ throw new KeymasterError('Only vault owner can modify the vault');
3229
+ }
3230
+ return controller;
3231
+ }
3232
+ async addMemberKey(groupVault, memberDID, privateJwk) {
3233
+ const memberDoc = await this.resolveDID(memberDID, { confirm: true });
3234
+ const memberPublicJwk = this.getPublicKeyJwk(memberDoc);
3235
+ const memberKey = this.cipher.encryptMessage(memberPublicJwk, privateJwk, JSON.stringify(privateJwk));
3236
+ const memberKeyId = this.generateSaltedId(groupVault, memberDID);
3237
+ groupVault.keys[memberKeyId] = memberKey;
3238
+ }
3239
+ async checkVaultVersion(vaultId, groupVault) {
3240
+ if (groupVault.version === 1) {
3241
+ return;
3242
+ }
3243
+ if (!groupVault.version) {
3244
+ const id = await this.fetchIdInfo();
3245
+ const { privateJwk, members } = await this.decryptGroupVault(groupVault);
3246
+ groupVault.version = 1;
3247
+ groupVault.keys = {};
3248
+ await this.addMemberKey(groupVault, id.did, privateJwk);
3249
+ for (const memberDID of Object.keys(members)) {
3250
+ await this.addMemberKey(groupVault, memberDID, privateJwk);
3251
+ }
3252
+ await this.updateAsset(vaultId, { groupVault });
3253
+ return;
3254
+ }
3255
+ throw new KeymasterError('Unsupported group vault version');
3256
+ }
3257
+ getAgentDID(doc) {
3258
+ if (doc.didDocumentRegistration?.type !== 'agent') {
3259
+ throw new KeymasterError('Document is not an agent');
3260
+ }
3261
+ const did = doc.didDocument?.id;
3262
+ if (!did) {
3263
+ throw new KeymasterError('Agent document does not have a DID');
3264
+ }
3265
+ return did;
3266
+ }
3267
+ async addGroupVaultMember(vaultId, memberId) {
3268
+ const owner = await this.checkGroupVaultOwner(vaultId);
3269
+ const idKeypair = await this.fetchKeyPair();
3270
+ const groupVault = await this.getGroupVault(vaultId);
3271
+ const { privateJwk, config, members } = await this.decryptGroupVault(groupVault);
3272
+ const memberDoc = await this.resolveDID(memberId, { confirm: true });
3273
+ const memberDID = this.getAgentDID(memberDoc);
3274
+ // Don't allow adding the vault owner
3275
+ if (owner === memberDID) {
3276
+ return false;
3277
+ }
3278
+ members[memberDID] = { added: new Date().toISOString() };
3279
+ const publicJwk = config.secretMembers ? idKeypair.publicJwk : groupVault.publicJwk;
3280
+ groupVault.members = this.cipher.encryptMessage(publicJwk, privateJwk, JSON.stringify(members));
3281
+ await this.addMemberKey(groupVault, memberDID, privateJwk);
3282
+ return this.updateAsset(vaultId, { groupVault });
3283
+ }
3284
+ async removeGroupVaultMember(vaultId, memberId) {
3285
+ const owner = await this.checkGroupVaultOwner(vaultId);
3286
+ const idKeypair = await this.fetchKeyPair();
3287
+ const groupVault = await this.getGroupVault(vaultId);
3288
+ const { privateJwk, config, members } = await this.decryptGroupVault(groupVault);
3289
+ const memberDoc = await this.resolveDID(memberId, { confirm: true });
3290
+ const memberDID = this.getAgentDID(memberDoc);
3291
+ // Don't allow removing the vault owner
3292
+ if (owner === memberDID) {
3293
+ return false;
3294
+ }
3295
+ delete members[memberDID];
3296
+ const publicJwk = config.secretMembers ? idKeypair.publicJwk : groupVault.publicJwk;
3297
+ groupVault.members = this.cipher.encryptMessage(publicJwk, privateJwk, JSON.stringify(members));
3298
+ const memberKeyId = this.generateSaltedId(groupVault, memberDID);
3299
+ delete groupVault.keys[memberKeyId];
3300
+ return this.updateAsset(vaultId, { groupVault });
3301
+ }
3302
+ async listGroupVaultMembers(vaultId) {
3303
+ const groupVault = await this.getGroupVault(vaultId);
3304
+ const { members, isOwner } = await this.decryptGroupVault(groupVault);
3305
+ if (isOwner) {
3306
+ await this.checkVaultVersion(vaultId, groupVault);
3307
+ }
3308
+ return members;
3309
+ }
3310
+ async addGroupVaultItem(vaultId, name, buffer) {
3311
+ await this.checkGroupVaultOwner(vaultId);
3312
+ const groupVault = await this.getGroupVault(vaultId);
3313
+ const { privateJwk, items } = await this.decryptGroupVault(groupVault);
3314
+ const validName = this.validateName(name);
3315
+ const encryptedData = this.cipher.encryptBytes(groupVault.publicJwk, privateJwk, buffer);
3316
+ const cid = await this.gatekeeper.addText(encryptedData);
3317
+ const sha256 = this.cipher.hashMessage(buffer);
3318
+ const type = await this.getMimeType(buffer);
3319
+ const data = encryptedData.length < this.maxDataLength ? encryptedData : undefined;
3320
+ items[validName] = {
3321
+ cid,
3322
+ sha256,
3323
+ bytes: buffer.length,
3324
+ type,
3325
+ added: new Date().toISOString(),
3326
+ data,
3327
+ };
3328
+ groupVault.items = this.cipher.encryptMessage(groupVault.publicJwk, privateJwk, JSON.stringify(items));
3329
+ groupVault.sha256 = this.cipher.hashJSON(items);
3330
+ return this.updateAsset(vaultId, { groupVault });
3331
+ }
3332
+ async removeGroupVaultItem(vaultId, name) {
3333
+ await this.checkGroupVaultOwner(vaultId);
3334
+ const groupVault = await this.getGroupVault(vaultId);
3335
+ const { privateJwk, items } = await this.decryptGroupVault(groupVault);
3336
+ delete items[name];
3337
+ groupVault.items = this.cipher.encryptMessage(groupVault.publicJwk, privateJwk, JSON.stringify(items));
3338
+ groupVault.sha256 = this.cipher.hashJSON(items);
3339
+ return this.updateAsset(vaultId, { groupVault });
3340
+ }
3341
+ async listGroupVaultItems(vaultId, options) {
3342
+ const groupVault = await this.getGroupVault(vaultId, options);
3343
+ const { items } = await this.decryptGroupVault(groupVault);
3344
+ return items;
3345
+ }
3346
+ async getGroupVaultItem(vaultId, name, options) {
3347
+ try {
3348
+ const groupVault = await this.getGroupVault(vaultId, options);
3349
+ const { privateJwk, items } = await this.decryptGroupVault(groupVault);
3350
+ if (items[name]) {
3351
+ const encryptedData = items[name].data || await this.gatekeeper.getText(items[name].cid);
3352
+ if (encryptedData) {
3353
+ const bytes = this.cipher.decryptBytes(groupVault.publicJwk, privateJwk, encryptedData);
3354
+ return Buffer.from(bytes);
3355
+ }
3356
+ }
3357
+ return null;
3358
+ }
3359
+ catch (error) {
3360
+ return null;
3361
+ }
3362
+ }
3363
+ async listDmail() {
3364
+ const wallet = await this.loadWallet();
3365
+ const id = await this.fetchIdInfo(undefined, wallet);
3366
+ const list = id.dmail || {};
3367
+ const dmailList = {};
3368
+ const nameList = await this.listNames({ includeIDs: true });
3369
+ const didToName = Object.entries(nameList).reduce((acc, [name, did]) => {
3370
+ acc[did] = name;
3371
+ return acc;
3372
+ }, {});
3373
+ for (const did of Object.keys(list)) {
3374
+ const message = await this.getDmailMessage(did);
3375
+ if (!message) {
3376
+ continue; // Skip if no dmail found for this DID
3377
+ }
3378
+ const tags = list[did].tags ?? [];
3379
+ const docs = await this.resolveDID(did);
3380
+ const controller = docs.didDocument?.controller ?? '';
3381
+ const sender = didToName[controller] ?? controller;
3382
+ const date = docs.didDocumentMetadata?.updated ?? '';
3383
+ const to = message.to.map(did => didToName[did] ?? did);
3384
+ const cc = message.cc.map(did => didToName[did] ?? did);
3385
+ const attachments = await this.listDmailAttachments(did);
3386
+ dmailList[did] = {
3387
+ message,
3388
+ to,
3389
+ cc,
3390
+ tags,
3391
+ sender,
3392
+ date,
3393
+ attachments,
3394
+ docs,
3395
+ };
3396
+ }
3397
+ return dmailList;
3398
+ }
3399
+ verifyTagList(tags) {
3400
+ if (!Array.isArray(tags)) {
3401
+ throw new InvalidParameterError('tags');
3402
+ }
3403
+ const tagSet = new Set();
3404
+ for (const tag of tags) {
3405
+ try {
3406
+ tagSet.add(this.validateName(tag));
3407
+ }
3408
+ catch (error) {
3409
+ throw new InvalidParameterError(`Invalid tag: '${tag}'`);
3410
+ }
3411
+ }
3412
+ return tagSet.size > 0 ? Array.from(tagSet) : [];
3413
+ }
3414
+ async fileDmail(did, tags) {
3415
+ const verifiedTags = this.verifyTagList(tags);
3416
+ await this.mutateWallet(async (wallet) => {
3417
+ const id = await this.fetchIdInfo(undefined, wallet);
3418
+ if (!id.dmail) {
3419
+ id.dmail = {};
3420
+ }
3421
+ id.dmail[did] = { tags: verifiedTags };
3422
+ });
3423
+ return true;
3424
+ }
3425
+ async removeDmail(did) {
3426
+ await this.mutateWallet(async (wallet) => {
3427
+ const id = await this.fetchIdInfo(undefined, wallet);
3428
+ if (!id.dmail || !id.dmail[did]) {
3429
+ return;
3430
+ }
3431
+ delete id.dmail[did];
3432
+ });
3433
+ return true;
3434
+ }
3435
+ async verifyRecipientList(list) {
3436
+ if (!Array.isArray(list)) {
3437
+ throw new InvalidParameterError('list');
3438
+ }
3439
+ const nameList = await this.listNames({ includeIDs: true });
3440
+ let newList = [];
3441
+ for (const id of list) {
3442
+ if (typeof id !== 'string') {
3443
+ throw new InvalidParameterError(`Invalid recipient type: ${typeof id}`);
3444
+ }
3445
+ if (id in nameList) {
3446
+ const did = nameList[id];
3447
+ const isAgent = await this.testAgent(did);
3448
+ if (isAgent) {
3449
+ newList.push(did);
3450
+ continue;
3451
+ }
3452
+ throw new InvalidParameterError(`Invalid recipient: ${id}`);
3453
+ }
3454
+ if (isValidDID(id)) {
3455
+ newList.push(id);
3456
+ continue;
3457
+ }
3458
+ throw new InvalidParameterError(`Invalid recipient: ${id}`);
3459
+ }
3460
+ return newList;
3461
+ }
3462
+ async verifyDmail(message) {
3463
+ const to = await this.verifyRecipientList(message.to);
3464
+ const cc = await this.verifyRecipientList(message.cc);
3465
+ if (to.length === 0) {
3466
+ throw new InvalidParameterError('dmail.to');
3467
+ }
3468
+ if (!message.subject || typeof message.subject !== 'string' || message.subject.trim() === '') {
3469
+ throw new InvalidParameterError('dmail.subject');
3470
+ }
3471
+ if (!message.body || typeof message.body !== 'string' || message.body.trim() === '') {
3472
+ throw new InvalidParameterError('dmail.body');
3473
+ }
3474
+ return {
3475
+ ...message,
3476
+ to,
3477
+ cc,
3478
+ };
3479
+ }
3480
+ async createDmail(message, options = {}) {
3481
+ const dmail = await this.verifyDmail(message);
3482
+ const did = await this.createGroupVault(options);
3483
+ for (const toDID of dmail.to) {
3484
+ await this.addGroupVaultMember(did, toDID);
3485
+ }
3486
+ for (const ccDID of dmail.cc) {
3487
+ await this.addGroupVaultMember(did, ccDID);
3488
+ }
3489
+ const buffer = Buffer.from(JSON.stringify({ dmail }), 'utf-8');
3490
+ await this.addGroupVaultItem(did, exports.DmailTags.DMAIL, buffer);
3491
+ await this.fileDmail(did, [exports.DmailTags.DRAFT]);
3492
+ return did;
3493
+ }
3494
+ async updateDmail(did, message) {
3495
+ const dmail = await this.verifyDmail(message);
3496
+ for (const toDID of dmail.to) {
3497
+ await this.addGroupVaultMember(did, toDID);
3498
+ }
3499
+ for (const ccDID of dmail.cc) {
3500
+ await this.addGroupVaultMember(did, ccDID);
3501
+ }
3502
+ const buffer = Buffer.from(JSON.stringify({ dmail }), 'utf-8');
3503
+ return this.addGroupVaultItem(did, exports.DmailTags.DMAIL, buffer);
3504
+ }
3505
+ async sendDmail(did) {
3506
+ const dmail = await this.getDmailMessage(did);
3507
+ if (!dmail) {
3508
+ return null;
3509
+ }
3510
+ const registry = this.ephemeralRegistry;
3511
+ const validUntil = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); // Default to 7 days
3512
+ const message = {
3513
+ to: [...dmail.to, ...dmail.cc],
3514
+ dids: [did],
3515
+ };
3516
+ const notice = await this.createNotice(message, { registry, validUntil });
3517
+ if (notice) {
3518
+ await this.fileDmail(did, [exports.DmailTags.SENT]);
3519
+ }
3520
+ return notice;
3521
+ }
3522
+ async getDmailMessage(did, options) {
3523
+ const isGroupVault = await this.testGroupVault(did, options);
3524
+ if (!isGroupVault) {
3525
+ return null;
3526
+ }
3527
+ const buffer = await this.getGroupVaultItem(did, exports.DmailTags.DMAIL, options);
3528
+ if (!buffer) {
3529
+ return null;
3530
+ }
3531
+ try {
3532
+ const data = JSON.parse(buffer.toString('utf-8'));
3533
+ return data.dmail;
3534
+ }
3535
+ catch (error) {
3536
+ return null;
3537
+ }
3538
+ }
3539
+ async listDmailAttachments(did, options) {
3540
+ let items = await this.listGroupVaultItems(did, options);
3541
+ delete items[exports.DmailTags.DMAIL]; // Remove the dmail item itself from attachments
3542
+ return items;
3543
+ }
3544
+ async addDmailAttachment(did, name, buffer) {
3545
+ if (name === exports.DmailTags.DMAIL) {
3546
+ throw new InvalidParameterError('Cannot add attachment with reserved name "dmail"');
3547
+ }
3548
+ return this.addGroupVaultItem(did, name, buffer);
3549
+ }
3550
+ async removeDmailAttachment(did, name) {
3551
+ if (name === exports.DmailTags.DMAIL) {
3552
+ throw new InvalidParameterError('Cannot remove attachment with reserved name "dmail"');
3553
+ }
3554
+ return this.removeGroupVaultItem(did, name);
3555
+ }
3556
+ async getDmailAttachment(did, name) {
3557
+ return this.getGroupVaultItem(did, name);
3558
+ }
3559
+ async importDmail(did) {
3560
+ const dmail = await this.getDmailMessage(did);
3561
+ if (!dmail) {
3562
+ return false;
3563
+ }
3564
+ return this.fileDmail(did, [exports.DmailTags.INBOX, exports.DmailTags.UNREAD]);
3565
+ }
3566
+ async verifyDIDList(didList) {
3567
+ if (!Array.isArray(didList)) {
3568
+ throw new InvalidParameterError('didList');
3569
+ }
3570
+ for (const did of didList) {
3571
+ if (!isValidDID(did)) {
3572
+ throw new InvalidParameterError(`Invalid DID: ${did}`);
3573
+ }
3574
+ }
3575
+ return didList;
3576
+ }
3577
+ async verifyNotice(notice) {
3578
+ const to = await this.verifyRecipientList(notice.to);
3579
+ const dids = await this.verifyDIDList(notice.dids);
3580
+ if (to.length === 0) {
3581
+ throw new InvalidParameterError('notice.to');
3582
+ }
3583
+ if (dids.length === 0) {
3584
+ throw new InvalidParameterError('notice.dids');
3585
+ }
3586
+ return { to, dids };
3587
+ }
3588
+ async createNotice(message, options = {}) {
3589
+ const notice = await this.verifyNotice(message);
3590
+ return this.createAsset({ notice }, options);
3591
+ }
3592
+ async updateNotice(id, message) {
3593
+ const notice = await this.verifyNotice(message);
3594
+ return this.updateAsset(id, { notice });
3595
+ }
3596
+ async addToNotices(did, tags) {
3597
+ const verifiedTags = this.verifyTagList(tags);
3598
+ await this.mutateWallet(async (wallet) => {
3599
+ const id = await this.fetchIdInfo(undefined, wallet);
3600
+ if (!id.notices)
3601
+ id.notices = {};
3602
+ id.notices[did] = { tags: verifiedTags };
3603
+ });
3604
+ return true;
3605
+ }
3606
+ async importNotice(did) {
3607
+ const wallet = await this.loadWallet();
3608
+ const id = await this.fetchIdInfo(undefined, wallet);
3609
+ if (id.notices && id.notices[did]) {
3610
+ return true; // Already imported
3611
+ }
3612
+ const asset = await this.resolveAsset(did);
3613
+ if (!asset || !asset.notice) {
3614
+ return false; // Not a notice
3615
+ }
3616
+ if (!asset.notice.to.includes(id.did)) {
3617
+ return false; // Not for this user
3618
+ }
3619
+ for (const noticeDID of asset.notice.dids) {
3620
+ const dmail = await this.getDmailMessage(noticeDID);
3621
+ if (dmail) {
3622
+ const imported = await this.importDmail(noticeDID);
3623
+ if (imported) {
3624
+ await this.addToNotices(did, [exports.NoticeTags.DMAIL]);
3625
+ }
3626
+ continue;
3627
+ }
3628
+ const isBallot = await this.isBallot(noticeDID);
3629
+ if (isBallot) {
3630
+ let imported = false;
3631
+ try {
3632
+ imported = await this.updatePoll(noticeDID);
3633
+ }
3634
+ catch { }
3635
+ if (imported) {
3636
+ await this.addToNotices(did, [exports.NoticeTags.BALLOT]);
3637
+ }
3638
+ continue;
3639
+ }
3640
+ const poll = await this.getPoll(noticeDID);
3641
+ if (poll) {
3642
+ const names = await this.listNames();
3643
+ if (!Object.values(names).includes(noticeDID)) {
3644
+ await this.addUnnamedPoll(noticeDID);
3645
+ }
3646
+ await this.addToNotices(did, [exports.NoticeTags.POLL]);
3647
+ continue;
3648
+ }
3649
+ const isCredential = await this.acceptCredential(noticeDID);
3650
+ if (isCredential) {
3651
+ await this.addToNotices(did, [exports.NoticeTags.CREDENTIAL]);
3652
+ continue;
3653
+ }
3654
+ return false;
3655
+ }
3656
+ return true;
3657
+ }
3658
+ async searchNotices() {
3659
+ if (!this.searchEngine) {
3660
+ return false; // Search engine not available
3661
+ }
3662
+ const id = await this.fetchIdInfo();
3663
+ if (!id.notices) {
3664
+ id.notices = {};
3665
+ }
3666
+ // Search for all notice DIDs sent to the current ID
3667
+ const where = {
3668
+ "didDocumentData.notice.to[*]": {
3669
+ "$in": [id.did]
3670
+ }
3671
+ };
3672
+ let notices;
3673
+ try {
3674
+ // TBD search engine should not return expired notices
3675
+ notices = await this.searchEngine.search({ where });
3676
+ }
3677
+ catch (error) {
3678
+ throw new KeymasterError('Failed to search for notices');
3679
+ }
3680
+ for (const notice of notices) {
3681
+ if (notice in id.notices) {
3682
+ continue; // Already imported
3683
+ }
3684
+ try {
3685
+ await this.importNotice(notice);
3686
+ }
3687
+ catch (error) {
3688
+ continue; // Skip if notice is expired or invalid
3689
+ }
3690
+ }
3691
+ return true;
3692
+ }
3693
+ async cleanupNotices() {
3694
+ await this.mutateWallet(async (wallet) => {
3695
+ const id = await this.fetchIdInfo(undefined, wallet);
3696
+ if (!id.notices) {
3697
+ return;
3698
+ }
3699
+ for (const nDid of Object.keys(id.notices)) {
3700
+ try {
3701
+ const asset = await this.resolveAsset(nDid);
3702
+ if (!asset || !asset.notice) {
3703
+ delete id.notices[nDid]; // revoked or invalid
3704
+ }
3705
+ }
3706
+ catch {
3707
+ delete id.notices[nDid]; // expired/unresolvable
3708
+ }
3709
+ }
3710
+ });
3711
+ return true;
3712
+ }
3713
+ async refreshNotices() {
3714
+ await this.searchNotices();
3715
+ return this.cleanupNotices();
3716
+ }
3717
+ async exportEncryptedWallet() {
3718
+ const wallet = await this.loadWallet();
3719
+ return this.encryptWalletForStorage(wallet);
3720
+ }
3721
+ async isBallot(ballotDid) {
3722
+ let payload;
3723
+ try {
3724
+ payload = await this.decryptJSON(ballotDid);
3725
+ }
3726
+ catch {
3727
+ return false;
3728
+ }
3729
+ return payload && typeof payload.poll === "string" && typeof payload.vote === "number";
3730
+ }
3731
+ async addUnnamedPoll(did) {
3732
+ const fallbackName = did.slice(-32);
3733
+ try {
3734
+ await this.addName(fallbackName, did);
3735
+ }
3736
+ catch { }
3737
+ }
3738
+ async getHDKeyFromCacheOrMnemonic(wallet) {
3739
+ if (this._hdkeyCache) {
3740
+ return this._hdkeyCache;
3741
+ }
3742
+ const mnemonic = await this.getMnemonicForDerivation(wallet);
3743
+ this._hdkeyCache = this.cipher.generateHDKey(mnemonic);
3744
+ return this._hdkeyCache;
3745
+ }
3746
+ async encryptWalletForStorage(decrypted) {
3747
+ const { version, seed, ...rest } = decrypted;
3748
+ const safeSeed = { mnemonicEnc: seed.mnemonicEnc };
3749
+ const hdkey = await this.getHDKeyFromCacheOrMnemonic(decrypted);
3750
+ const { publicJwk, privateJwk } = this.cipher.generateJwk(hdkey.privateKey);
3751
+ const plaintext = JSON.stringify(rest);
3752
+ const enc = this.cipher.encryptMessage(publicJwk, privateJwk, plaintext);
3753
+ return { version: version, seed: safeSeed, enc };
3754
+ }
3755
+ async decryptWalletFromStorage(stored) {
3756
+ let mnemonic;
3757
+ try {
3758
+ mnemonic = await encryption.decMnemonic(stored.seed.mnemonicEnc, this.passphrase);
3759
+ }
3760
+ catch {
3761
+ throw new KeymasterError('Incorrect passphrase.');
3762
+ }
3763
+ this._hdkeyCache = this.cipher.generateHDKey(mnemonic);
3764
+ const { publicJwk, privateJwk } = this.cipher.generateJwk(this._hdkeyCache.privateKey);
3765
+ const plaintext = this.cipher.decryptMessage(publicJwk, privateJwk, stored.enc);
3766
+ const data = JSON.parse(plaintext);
3767
+ const wallet = { version: stored.version, seed: stored.seed, ...data };
3768
+ return wallet;
3769
+ }
3770
+ async decryptWallet(wallet) {
3771
+ if (db_typeGuards.isWalletEncFile(wallet)) {
3772
+ wallet = await this.decryptWalletFromStorage(wallet);
3773
+ }
3774
+ if (!db_typeGuards.isWalletFile(wallet)) {
3775
+ throw new KeymasterError("Unsupported wallet version.");
3776
+ }
3777
+ return wallet;
3778
+ }
3779
+ async upgradeWallet(wallet) {
3780
+ if (wallet.version !== 1) {
3781
+ throw new KeymasterError("Unsupported wallet version.");
3782
+ }
3783
+ return wallet;
3784
+ }
3785
+ }
3786
+
3787
+ exports.default = Keymaster;