@didcid/keymaster 0.3.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ import { base64url } from 'multiformats/bases/base64';
4
4
  import { InvalidDIDError, InvalidParameterError, KeymasterError, UnknownIDError } from '@didcid/common/errors';
5
5
  import { isWalletEncFile, isWalletFile } from './db/typeGuards.js';
6
6
  import { isValidDID } from '@didcid/ipfs/utils';
7
- import { decMnemonic, encMnemonic } from "./encryption.js";
7
+ import { decryptWithPassphrase, encryptWithPassphrase } from '@didcid/cipher/passphrase';
8
8
  function hexToBase64url(hex) {
9
9
  const bytes = Buffer.from(hex, 'hex');
10
10
  return base64url.baseEncode(bytes);
@@ -42,6 +42,11 @@ export var NoticeTags;
42
42
  NoticeTags["POLL"] = "poll";
43
43
  NoticeTags["CREDENTIAL"] = "credential";
44
44
  })(NoticeTags || (NoticeTags = {}));
45
+ export var PollItems;
46
+ (function (PollItems) {
47
+ PollItems["POLL"] = "poll";
48
+ PollItems["RESULTS"] = "results";
49
+ })(PollItems || (PollItems = {}));
45
50
  export default class Keymaster {
46
51
  passphrase;
47
52
  gatekeeper;
@@ -129,7 +134,7 @@ export default class Keymaster {
129
134
  catch (error) {
130
135
  throw new InvalidParameterError('mnemonic');
131
136
  }
132
- const mnemonicEnc = await encMnemonic(mnemonic, this.passphrase);
137
+ const mnemonicEnc = await encryptWithPassphrase(mnemonic, this.passphrase);
133
138
  const wallet = {
134
139
  version: 2,
135
140
  seed: { mnemonicEnc },
@@ -147,7 +152,7 @@ export default class Keymaster {
147
152
  return this.getMnemonicForDerivation(wallet);
148
153
  }
149
154
  async getMnemonicForDerivation(wallet) {
150
- return decMnemonic(wallet.seed.mnemonicEnc, this.passphrase);
155
+ return decryptWithPassphrase(wallet.seed.mnemonicEnc, this.passphrase);
151
156
  }
152
157
  async checkWallet() {
153
158
  const wallet = await this.loadWallet();
@@ -359,7 +364,7 @@ export default class Keymaster {
359
364
  const keypair = await this.hdKeyPair();
360
365
  const seedBank = await this.resolveSeedBank();
361
366
  const msg = JSON.stringify(wallet);
362
- const backup = this.cipher.encryptMessage(keypair.publicJwk, keypair.privateJwk, msg);
367
+ const backup = this.cipher.encryptMessage(keypair.publicJwk, msg);
363
368
  const operation = {
364
369
  type: "create",
365
370
  created: new Date().toISOString(),
@@ -412,12 +417,12 @@ export default class Keymaster {
412
417
  if (typeof castData.backup !== 'string') {
413
418
  throw new InvalidParameterError('Asset "backup" is missing or not a string');
414
419
  }
415
- const backup = this.cipher.decryptMessage(keypair.publicJwk, keypair.privateJwk, castData.backup);
420
+ const backup = this.cipher.decryptMessage(keypair.privateJwk, castData.backup, keypair.publicJwk);
416
421
  let wallet = JSON.parse(backup);
417
422
  if (isWalletFile(wallet)) {
418
423
  const mnemonic = await this.decryptMnemonic();
419
424
  // Backup might have a different mnemonic passphase so re-encrypt
420
- wallet.seed.mnemonicEnc = await encMnemonic(mnemonic, this.passphrase);
425
+ wallet.seed.mnemonicEnc = await encryptWithPassphrase(mnemonic, this.passphrase);
421
426
  }
422
427
  await this.mutateWallet(async (current) => {
423
428
  // Clear all existing properties from the current wallet
@@ -581,7 +586,7 @@ export default class Keymaster {
581
586
  const cloneData = { ...assetData, cloned: assetDoc.didDocument.id };
582
587
  return this.createAsset(cloneData, options);
583
588
  }
584
- async generateImageAsset(buffer) {
589
+ async generateImageAsset(filename, buffer) {
585
590
  let metadata;
586
591
  try {
587
592
  metadata = imageSize(buffer);
@@ -590,33 +595,38 @@ export default class Keymaster {
590
595
  throw new InvalidParameterError('buffer');
591
596
  }
592
597
  const cid = await this.gatekeeper.addData(buffer);
593
- const image = {
598
+ const file = {
594
599
  cid,
600
+ filename,
601
+ type: `image/${metadata.type}`,
595
602
  bytes: buffer.length,
596
- ...metadata,
597
- type: `image/${metadata.type}`
598
603
  };
599
- return image;
604
+ const image = {
605
+ width: metadata.width,
606
+ height: metadata.height,
607
+ };
608
+ return { file, image };
600
609
  }
601
610
  async createImage(buffer, options = {}) {
602
- const image = await this.generateImageAsset(buffer);
603
- return this.createAsset({ image }, options);
611
+ const filename = options.filename || 'image';
612
+ const { file, image } = await this.generateImageAsset(filename, buffer);
613
+ return this.createAsset({ file, image }, options);
604
614
  }
605
- async updateImage(id, buffer) {
606
- const image = await this.generateImageAsset(buffer);
607
- return this.mergeData(id, { image });
615
+ async updateImage(id, buffer, options = {}) {
616
+ const filename = options.filename || 'image';
617
+ const { file, image } = await this.generateImageAsset(filename, buffer);
618
+ return this.mergeData(id, { file, image });
608
619
  }
609
620
  async getImage(id) {
610
621
  const asset = await this.resolveAsset(id);
611
- const image = asset.image;
612
- if (!image || !image.cid) {
622
+ if (!asset.file || !asset.file.cid || !asset.image) {
613
623
  return null;
614
624
  }
615
- const buffer = await this.gatekeeper.getData(image.cid);
625
+ const buffer = await this.gatekeeper.getData(asset.file.cid);
616
626
  if (buffer) {
617
- image.data = buffer;
627
+ asset.file.data = buffer;
618
628
  }
619
- return image;
629
+ return { file: asset.file, image: asset.image };
620
630
  }
621
631
  async testImage(id) {
622
632
  try {
@@ -659,24 +669,32 @@ export default class Keymaster {
659
669
  };
660
670
  return file;
661
671
  }
662
- async createDocument(buffer, options = {}) {
663
- const filename = options.filename || 'document';
664
- const document = await this.generateFileAsset(filename, buffer);
665
- return this.createAsset({ document }, options);
672
+ async createFile(buffer, options = {}) {
673
+ const filename = options.filename || 'file';
674
+ const file = await this.generateFileAsset(filename, buffer);
675
+ return this.createAsset({ file }, options);
666
676
  }
667
- async updateDocument(id, buffer, options = {}) {
668
- const filename = options.filename || 'document';
669
- const document = await this.generateFileAsset(filename, buffer);
670
- return this.mergeData(id, { document });
677
+ async updateFile(id, buffer, options = {}) {
678
+ const filename = options.filename || 'file';
679
+ const file = await this.generateFileAsset(filename, buffer);
680
+ return this.mergeData(id, { file });
671
681
  }
672
- async getDocument(id) {
682
+ async getFile(id) {
673
683
  const asset = await this.resolveAsset(id);
674
- return asset.document ?? null;
684
+ const file = asset.file;
685
+ if (!file || !file.cid) {
686
+ return null;
687
+ }
688
+ const buffer = await this.gatekeeper.getData(file.cid);
689
+ if (buffer) {
690
+ file.data = buffer;
691
+ }
692
+ return file;
675
693
  }
676
- async testDocument(id) {
694
+ async testFile(id) {
677
695
  try {
678
- const document = await this.getDocument(id);
679
- return document !== null;
696
+ const file = await this.getFile(id);
697
+ return file !== null;
680
698
  }
681
699
  catch (error) {
682
700
  return false;
@@ -684,19 +702,16 @@ export default class Keymaster {
684
702
  }
685
703
  async encryptMessage(msg, receiver, options = {}) {
686
704
  const { encryptForSender = true, includeHash = false, } = options;
687
- const id = await this.fetchIdInfo();
688
705
  const senderKeypair = await this.fetchKeyPair();
689
706
  if (!senderKeypair) {
690
707
  throw new KeymasterError('No valid sender keypair');
691
708
  }
692
709
  const doc = await this.resolveDID(receiver, { confirm: true });
693
710
  const receivePublicJwk = this.getPublicKeyJwk(doc);
694
- const cipher_sender = encryptForSender ? this.cipher.encryptMessage(senderKeypair.publicJwk, senderKeypair.privateJwk, msg) : null;
695
- const cipher_receiver = this.cipher.encryptMessage(receivePublicJwk, senderKeypair.privateJwk, msg);
711
+ const cipher_sender = encryptForSender ? this.cipher.encryptMessage(senderKeypair.publicJwk, msg) : null;
712
+ const cipher_receiver = this.cipher.encryptMessage(receivePublicJwk, msg);
696
713
  const cipher_hash = includeHash ? this.cipher.hashMessage(msg) : null;
697
714
  const encrypted = {
698
- sender: id.did,
699
- created: new Date().toISOString(),
700
715
  cipher_hash,
701
716
  cipher_sender,
702
717
  cipher_receiver,
@@ -712,7 +727,7 @@ export default class Keymaster {
712
727
  const didkey = hdkey.derive(path);
713
728
  const receiverKeypair = this.cipher.generateJwk(didkey.privateKey);
714
729
  try {
715
- return this.cipher.decryptMessage(senderPublicJwk, receiverKeypair.privateJwk, ciphertext);
730
+ return this.cipher.decryptMessage(receiverKeypair.privateJwk, ciphertext, senderPublicJwk);
716
731
  }
717
732
  catch (error) {
718
733
  index -= 1;
@@ -723,7 +738,8 @@ export default class Keymaster {
723
738
  async decryptMessage(did) {
724
739
  const wallet = await this.loadWallet();
725
740
  const id = await this.fetchIdInfo();
726
- const asset = await this.resolveAsset(did);
741
+ const msgDoc = await this.resolveDID(did);
742
+ const asset = msgDoc.didDocumentData;
727
743
  if (!asset) {
728
744
  throw new InvalidParameterError('did not encrypted');
729
745
  }
@@ -732,9 +748,16 @@ export default class Keymaster {
732
748
  throw new InvalidParameterError('did not encrypted');
733
749
  }
734
750
  const crypt = (castAsset.encrypted ? castAsset.encrypted : castAsset);
735
- const doc = await this.resolveDID(crypt.sender, { confirm: true, versionTime: crypt.created });
736
- const senderPublicJwk = this.getPublicKeyJwk(doc);
737
- const ciphertext = (crypt.sender === id.did && crypt.cipher_sender) ? crypt.cipher_sender : crypt.cipher_receiver;
751
+ // Derive sender and created from the message DID document,
752
+ // falling back to fields in the asset for legacy messages
753
+ const sender = crypt.sender || msgDoc.didDocument?.controller;
754
+ const created = crypt.created || msgDoc.didDocumentMetadata?.created;
755
+ if (!sender) {
756
+ throw new InvalidParameterError('Sender DID could not be determined from message or DID document');
757
+ }
758
+ const senderDoc = await this.resolveDID(sender, { confirm: true, versionTime: created });
759
+ const senderPublicJwk = this.getPublicKeyJwk(senderDoc);
760
+ const ciphertext = (sender === id.did && crypt.cipher_sender) ? crypt.cipher_sender : crypt.cipher_receiver;
738
761
  return await this.decryptWithDerivedKeys(wallet, id, senderPublicJwk, ciphertext);
739
762
  }
740
763
  async encryptJSON(json, did, options = {}) {
@@ -1115,7 +1138,7 @@ export default class Keymaster {
1115
1138
  id: idInfo,
1116
1139
  };
1117
1140
  const msg = JSON.stringify(data);
1118
- const backup = this.cipher.encryptMessage(keypair.publicJwk, keypair.privateJwk, msg);
1141
+ const backup = this.cipher.encryptMessage(keypair.publicJwk, msg);
1119
1142
  const doc = await this.resolveDID(idInfo.did);
1120
1143
  const registry = doc.didDocumentRegistration?.registry;
1121
1144
  if (!registry) {
@@ -1141,7 +1164,7 @@ export default class Keymaster {
1141
1164
  if (typeof backupStore.backup !== 'string') {
1142
1165
  throw new InvalidDIDError('backup not found in backupStore');
1143
1166
  }
1144
- const backup = this.cipher.decryptMessage(keypair.publicJwk, keypair.privateJwk, backupStore.backup);
1167
+ const backup = this.cipher.decryptMessage(keypair.privateJwk, backupStore.backup, keypair.publicJwk);
1145
1168
  const data = JSON.parse(backup);
1146
1169
  await this.mutateWallet((wallet) => {
1147
1170
  if (wallet.ids[data.name]) {
@@ -1242,7 +1265,13 @@ export default class Keymaster {
1242
1265
  validFrom = new Date().toISOString();
1243
1266
  }
1244
1267
  const id = await this.fetchIdInfo();
1245
- const subjectDID = await this.lookupDID(subjectId);
1268
+ let subjectURI;
1269
+ try {
1270
+ subjectURI = await this.lookupDID(subjectId);
1271
+ }
1272
+ catch {
1273
+ subjectURI = subjectId;
1274
+ }
1246
1275
  const vc = {
1247
1276
  "@context": [
1248
1277
  "https://www.w3.org/ns/credentials/v2",
@@ -1253,7 +1282,7 @@ export default class Keymaster {
1253
1282
  validFrom,
1254
1283
  validUntil,
1255
1284
  credentialSubject: {
1256
- id: subjectDID,
1285
+ id: subjectURI,
1257
1286
  },
1258
1287
  };
1259
1288
  // If schema provided, add credentialSchema and generate claims from schema
@@ -1278,7 +1307,7 @@ export default class Keymaster {
1278
1307
  }
1279
1308
  if (claims && Object.keys(claims).length) {
1280
1309
  vc.credentialSubject = {
1281
- id: subjectDID,
1310
+ id: subjectURI,
1282
1311
  ...claims,
1283
1312
  };
1284
1313
  }
@@ -1293,21 +1322,32 @@ export default class Keymaster {
1293
1322
  throw new InvalidParameterError('credential.issuer');
1294
1323
  }
1295
1324
  const signed = await this.addProof(credential);
1296
- return this.encryptJSON(signed, credential.credentialSubject.id, { ...options, includeHash: true });
1325
+ const subjectId = credential.credentialSubject.id;
1326
+ if (this.isManagedDID(subjectId)) {
1327
+ return this.encryptJSON(signed, subjectId, { ...options, includeHash: true });
1328
+ }
1329
+ return this.encryptJSON(signed, id.did, { ...options, includeHash: true, encryptForSender: false });
1297
1330
  }
1298
1331
  async sendCredential(did, options = {}) {
1299
1332
  const vc = await this.getCredential(did);
1300
1333
  if (!vc) {
1301
1334
  return null;
1302
1335
  }
1336
+ const subjectId = vc.credentialSubject.id;
1337
+ if (!this.isManagedDID(subjectId)) {
1338
+ return null;
1339
+ }
1303
1340
  const registry = this.ephemeralRegistry;
1304
1341
  const validUntil = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); // Default to 7 days
1305
1342
  const message = {
1306
- to: [vc.credentialSubject.id],
1343
+ to: [subjectId],
1307
1344
  dids: [did],
1308
1345
  };
1309
1346
  return this.createNotice(message, { registry, validUntil, ...options });
1310
1347
  }
1348
+ isManagedDID(value) {
1349
+ return value.startsWith('did:cid:');
1350
+ }
1311
1351
  isVerifiableCredential(obj) {
1312
1352
  if (typeof obj !== 'object' || !obj) {
1313
1353
  return false;
@@ -1329,24 +1369,29 @@ export default class Keymaster {
1329
1369
  delete credential.proof;
1330
1370
  const signed = await this.addProof(credential);
1331
1371
  const msg = JSON.stringify(signed);
1332
- const id = await this.fetchIdInfo();
1333
1372
  const senderKeypair = await this.fetchKeyPair();
1334
1373
  if (!senderKeypair) {
1335
1374
  throw new KeymasterError('No valid sender keypair');
1336
1375
  }
1337
1376
  const holder = credential.credentialSubject.id;
1338
- const holderDoc = await this.resolveDID(holder, { confirm: true });
1339
- const receivePublicJwk = this.getPublicKeyJwk(holderDoc);
1340
- const cipher_sender = this.cipher.encryptMessage(senderKeypair.publicJwk, senderKeypair.privateJwk, msg);
1341
- const cipher_receiver = this.cipher.encryptMessage(receivePublicJwk, senderKeypair.privateJwk, msg);
1342
1377
  const msgHash = this.cipher.hashMessage(msg);
1343
- const encrypted = {
1344
- sender: id.did,
1345
- created: new Date().toISOString(),
1346
- cipher_hash: msgHash,
1347
- cipher_sender: cipher_sender,
1348
- cipher_receiver: cipher_receiver,
1349
- };
1378
+ let encrypted;
1379
+ if (this.isManagedDID(holder)) {
1380
+ const holderDoc = await this.resolveDID(holder, { confirm: true });
1381
+ const receivePublicJwk = this.getPublicKeyJwk(holderDoc);
1382
+ encrypted = {
1383
+ cipher_hash: msgHash,
1384
+ cipher_sender: this.cipher.encryptMessage(senderKeypair.publicJwk, msg),
1385
+ cipher_receiver: this.cipher.encryptMessage(receivePublicJwk, msg),
1386
+ };
1387
+ }
1388
+ else {
1389
+ encrypted = {
1390
+ cipher_hash: msgHash,
1391
+ cipher_sender: null,
1392
+ cipher_receiver: this.cipher.encryptMessage(senderKeypair.publicJwk, msg),
1393
+ };
1394
+ }
1350
1395
  return this.updateDID(did, { didDocumentData: { encrypted } });
1351
1396
  }
1352
1397
  async revokeCredential(credential) {
@@ -1842,72 +1887,64 @@ export default class Keymaster {
1842
1887
  const nextWeek = new Date();
1843
1888
  nextWeek.setDate(now.getDate() + 7);
1844
1889
  return {
1845
- type: 'poll',
1846
- version: 1,
1890
+ version: 2,
1891
+ name: 'poll-name',
1847
1892
  description: 'What is this poll about?',
1848
- roster: 'DID of the eligible voter group',
1849
1893
  options: ['yes', 'no', 'abstain'],
1850
1894
  deadline: nextWeek.toISOString(),
1851
1895
  };
1852
1896
  }
1853
- async createPoll(poll, options = {}) {
1854
- if (poll.type !== 'poll') {
1855
- throw new InvalidParameterError('poll');
1856
- }
1857
- if (poll.version !== 1) {
1897
+ async createPoll(config, options = {}) {
1898
+ if (config.version !== 2) {
1858
1899
  throw new InvalidParameterError('poll.version');
1859
1900
  }
1860
- if (!poll.description) {
1901
+ if (!config.name) {
1902
+ throw new InvalidParameterError('poll.name');
1903
+ }
1904
+ if (!config.description) {
1861
1905
  throw new InvalidParameterError('poll.description');
1862
1906
  }
1863
- if (!poll.options || !Array.isArray(poll.options) || poll.options.length < 2 || poll.options.length > 10) {
1907
+ if (!config.options || !Array.isArray(config.options) || config.options.length < 2 || config.options.length > 10) {
1864
1908
  throw new InvalidParameterError('poll.options');
1865
1909
  }
1866
- if (!poll.roster) {
1867
- // eslint-disable-next-line
1868
- throw new InvalidParameterError('poll.roster');
1869
- }
1870
- try {
1871
- const isValidGroup = await this.testGroup(poll.roster);
1872
- if (!isValidGroup) {
1873
- throw new InvalidParameterError('poll.roster');
1874
- }
1875
- }
1876
- catch {
1877
- throw new InvalidParameterError('poll.roster');
1878
- }
1879
- if (!poll.deadline) {
1880
- // eslint-disable-next-line
1910
+ if (!config.deadline) {
1881
1911
  throw new InvalidParameterError('poll.deadline');
1882
1912
  }
1883
- const deadline = new Date(poll.deadline);
1913
+ const deadline = new Date(config.deadline);
1884
1914
  if (isNaN(deadline.getTime())) {
1885
1915
  throw new InvalidParameterError('poll.deadline');
1886
1916
  }
1887
1917
  if (deadline < new Date()) {
1888
1918
  throw new InvalidParameterError('poll.deadline');
1889
1919
  }
1890
- return this.createAsset({ poll }, options);
1920
+ const vaultDid = await this.createVault(options);
1921
+ const buffer = Buffer.from(JSON.stringify(config), 'utf-8');
1922
+ await this.addVaultItem(vaultDid, PollItems.POLL, buffer);
1923
+ return vaultDid;
1891
1924
  }
1892
1925
  async getPoll(id) {
1893
- const asset = await this.resolveAsset(id);
1894
- // TEMP during did:cid, return old version poll
1895
- const castOldAsset = asset;
1896
- if (castOldAsset.options) {
1897
- return castOldAsset;
1926
+ const isVault = await this.testVault(id);
1927
+ if (!isVault) {
1928
+ return null;
1898
1929
  }
1899
- const castAsset = asset;
1900
- if (!castAsset.poll) {
1930
+ try {
1931
+ const buffer = await this.getVaultItem(id, PollItems.POLL);
1932
+ if (!buffer) {
1933
+ return null;
1934
+ }
1935
+ const config = JSON.parse(buffer.toString('utf-8'));
1936
+ return config;
1937
+ }
1938
+ catch {
1901
1939
  return null;
1902
1940
  }
1903
- return castAsset.poll;
1904
1941
  }
1905
1942
  async testPoll(id) {
1906
1943
  try {
1907
- const poll = await this.getPoll(id);
1908
- return poll !== null;
1944
+ const config = await this.getPoll(id);
1945
+ return config !== null;
1909
1946
  }
1910
- catch (error) {
1947
+ catch {
1911
1948
  return false;
1912
1949
  }
1913
1950
  }
@@ -1922,113 +1959,243 @@ export default class Keymaster {
1922
1959
  }
1923
1960
  return polls;
1924
1961
  }
1962
+ async addPollVoter(pollId, memberId) {
1963
+ const config = await this.getPoll(pollId);
1964
+ if (!config) {
1965
+ throw new InvalidParameterError('pollId');
1966
+ }
1967
+ return this.addVaultMember(pollId, memberId);
1968
+ }
1969
+ async removePollVoter(pollId, memberId) {
1970
+ const config = await this.getPoll(pollId);
1971
+ if (!config) {
1972
+ throw new InvalidParameterError('pollId');
1973
+ }
1974
+ return this.removeVaultMember(pollId, memberId);
1975
+ }
1976
+ async listPollVoters(pollId) {
1977
+ const config = await this.getPoll(pollId);
1978
+ if (!config) {
1979
+ throw new InvalidParameterError('pollId');
1980
+ }
1981
+ return this.listVaultMembers(pollId);
1982
+ }
1925
1983
  async viewPoll(pollId) {
1926
1984
  const id = await this.fetchIdInfo();
1927
- const poll = await this.getPoll(pollId);
1928
- if (!poll) {
1985
+ const config = await this.getPoll(pollId);
1986
+ if (!config) {
1929
1987
  throw new InvalidParameterError('pollId');
1930
1988
  }
1989
+ const doc = await this.resolveDID(pollId);
1990
+ const isOwner = (doc.didDocument?.controller === id.did);
1991
+ const voteExpired = Date.now() > new Date(config.deadline).getTime();
1992
+ let isEligible = false;
1931
1993
  let hasVoted = false;
1932
- if (poll.ballots) {
1933
- hasVoted = !!poll.ballots[id.did];
1994
+ const ballots = [];
1995
+ try {
1996
+ const vault = await this.getVault(pollId);
1997
+ const members = await this.listVaultMembers(pollId);
1998
+ isEligible = isOwner || !!members[id.did];
1999
+ const items = await this.listVaultItems(pollId);
2000
+ for (const itemName of Object.keys(items)) {
2001
+ if (itemName !== PollItems.POLL && itemName !== PollItems.RESULTS) {
2002
+ ballots.push(itemName);
2003
+ }
2004
+ }
2005
+ const myBallotKey = this.generateBallotKey(vault, id.did);
2006
+ hasVoted = ballots.includes(myBallotKey);
2007
+ }
2008
+ catch {
2009
+ isEligible = false;
1934
2010
  }
1935
- const voteExpired = Date.now() > new Date(poll.deadline).getTime();
1936
- const isEligible = await this.testGroup(poll.roster, id.did);
1937
- const doc = await this.resolveDID(pollId);
1938
2011
  const view = {
1939
- description: poll.description,
1940
- options: poll.options,
1941
- deadline: poll.deadline,
1942
- isOwner: (doc.didDocument?.controller === id.did),
1943
- isEligible: isEligible,
1944
- voteExpired: voteExpired,
1945
- hasVoted: hasVoted,
2012
+ description: config.description,
2013
+ options: config.options,
2014
+ deadline: config.deadline,
2015
+ isOwner,
2016
+ isEligible,
2017
+ voteExpired,
2018
+ hasVoted,
2019
+ ballots,
1946
2020
  };
1947
- if (id.did === doc.didDocument?.controller) {
1948
- let voted = 0;
1949
- const results = {
1950
- tally: [],
1951
- ballots: [],
1952
- };
1953
- results.tally.push({
1954
- vote: 0,
1955
- option: 'spoil',
1956
- count: 0,
1957
- });
1958
- for (let i = 0; i < poll.options.length; i++) {
1959
- results.tally.push({
1960
- vote: i + 1,
1961
- option: poll.options[i],
1962
- count: 0,
2021
+ if (isOwner) {
2022
+ view.results = await this.computePollResults(pollId, config);
2023
+ }
2024
+ else {
2025
+ try {
2026
+ const resultsBuffer = await this.getVaultItem(pollId, PollItems.RESULTS);
2027
+ if (resultsBuffer) {
2028
+ view.results = JSON.parse(resultsBuffer.toString('utf-8'));
2029
+ }
2030
+ }
2031
+ catch { }
2032
+ }
2033
+ return view;
2034
+ }
2035
+ async computePollResults(pollId, config) {
2036
+ const vault = await this.getVault(pollId);
2037
+ const members = await this.listVaultMembers(pollId);
2038
+ const items = await this.listVaultItems(pollId);
2039
+ const results = {
2040
+ tally: [],
2041
+ ballots: [],
2042
+ };
2043
+ results.tally.push({ vote: 0, option: 'spoil', count: 0 });
2044
+ for (let i = 0; i < config.options.length; i++) {
2045
+ results.tally.push({ vote: i + 1, option: config.options[i], count: 0 });
2046
+ }
2047
+ // Build ballotKey → memberDID mapping
2048
+ const keyToMember = {};
2049
+ for (const memberDID of Object.keys(members)) {
2050
+ const ballotKey = this.generateBallotKey(vault, memberDID);
2051
+ keyToMember[ballotKey] = memberDID;
2052
+ }
2053
+ // Include owner in mapping
2054
+ const id = await this.fetchIdInfo();
2055
+ const ownerKey = this.generateBallotKey(vault, id.did);
2056
+ keyToMember[ownerKey] = id.did;
2057
+ let voted = 0;
2058
+ for (const [itemName, itemMeta] of Object.entries(items)) {
2059
+ if (itemName === PollItems.POLL || itemName === PollItems.RESULTS) {
2060
+ continue;
2061
+ }
2062
+ const ballotBuffer = await this.getVaultItem(pollId, itemName);
2063
+ if (!ballotBuffer) {
2064
+ continue;
2065
+ }
2066
+ const ballotDid = ballotBuffer.toString('utf-8');
2067
+ const decrypted = await this.decryptJSON(ballotDid);
2068
+ const vote = decrypted.vote;
2069
+ const voterDID = keyToMember[itemName] || itemName;
2070
+ if (results.ballots) {
2071
+ results.ballots.push({
2072
+ voter: voterDID,
2073
+ vote,
2074
+ option: vote === 0 ? 'spoil' : config.options[vote - 1],
2075
+ received: itemMeta.added || '',
1963
2076
  });
1964
2077
  }
1965
- for (let voter in poll.ballots) {
1966
- const ballot = poll.ballots[voter];
1967
- const decrypted = await this.decryptJSON(ballot.ballot);
1968
- const vote = decrypted.vote;
1969
- if (results.ballots) {
1970
- results.ballots.push({
1971
- ...ballot,
1972
- voter,
1973
- vote,
1974
- option: poll.options[vote - 1],
1975
- });
1976
- }
1977
- voted += 1;
2078
+ voted += 1;
2079
+ if (vote >= 0 && vote < results.tally.length) {
1978
2080
  results.tally[vote].count += 1;
1979
2081
  }
1980
- const roster = await this.getGroup(poll.roster);
1981
- const total = roster.members.length;
1982
- results.votes = {
1983
- eligible: total,
1984
- received: voted,
1985
- pending: total - voted,
1986
- };
1987
- results.final = voteExpired || (voted === total);
1988
- view.results = results;
1989
2082
  }
1990
- return view;
2083
+ const total = Object.keys(members).length + 1; // +1 for owner
2084
+ const voteExpired = Date.now() > new Date(config.deadline).getTime();
2085
+ results.votes = {
2086
+ eligible: total,
2087
+ received: voted,
2088
+ pending: total - voted,
2089
+ };
2090
+ results.final = voteExpired || (voted === total);
2091
+ return results;
1991
2092
  }
1992
2093
  async votePoll(pollId, vote, options = {}) {
1993
- const { spoil = false } = options;
1994
2094
  const id = await this.fetchIdInfo();
1995
2095
  const didPoll = await this.lookupDID(pollId);
1996
2096
  const doc = await this.resolveDID(didPoll);
1997
- const poll = await this.getPoll(pollId);
1998
- if (!poll) {
2097
+ const config = await this.getPoll(pollId);
2098
+ if (!config) {
1999
2099
  throw new InvalidParameterError('pollId');
2000
2100
  }
2001
- const eligible = await this.testGroup(poll.roster, id.did);
2002
- const expired = Date.now() > new Date(poll.deadline).getTime();
2003
2101
  const owner = doc.didDocument?.controller;
2004
2102
  if (!owner) {
2005
- throw new KeymasterError('owner mising from poll');
2103
+ throw new KeymasterError('owner missing from poll');
2104
+ }
2105
+ // Check vault membership
2106
+ let isEligible = false;
2107
+ if (id.did === owner) {
2108
+ isEligible = true;
2006
2109
  }
2007
- if (!eligible) {
2008
- throw new InvalidParameterError('voter not in roster');
2110
+ else {
2111
+ try {
2112
+ const vault = await this.getVault(didPoll);
2113
+ await this.decryptVault(vault);
2114
+ isEligible = true;
2115
+ }
2116
+ catch {
2117
+ isEligible = false;
2118
+ }
2009
2119
  }
2120
+ if (!isEligible) {
2121
+ throw new InvalidParameterError('voter is not a poll member');
2122
+ }
2123
+ const expired = Date.now() > new Date(config.deadline).getTime();
2010
2124
  if (expired) {
2011
2125
  throw new InvalidParameterError('poll has expired');
2012
2126
  }
2013
- let ballot;
2014
- if (spoil) {
2015
- ballot = {
2016
- poll: didPoll,
2017
- vote: 0,
2018
- };
2127
+ const max = config.options.length;
2128
+ if (!Number.isInteger(vote) || vote < 0 || vote > max) {
2129
+ throw new InvalidParameterError('vote');
2019
2130
  }
2020
- else {
2021
- const max = poll.options.length;
2022
- if (!Number.isInteger(vote) || vote < 1 || vote > max) {
2023
- throw new InvalidParameterError('vote');
2131
+ const ballot = {
2132
+ poll: didPoll,
2133
+ vote: vote,
2134
+ };
2135
+ // Encrypt for owner and sender (voter can view their own ballot)
2136
+ return await this.encryptJSON(ballot, owner, options);
2137
+ }
2138
+ async sendPoll(pollId) {
2139
+ const didPoll = await this.lookupDID(pollId);
2140
+ const config = await this.getPoll(didPoll);
2141
+ if (!config) {
2142
+ throw new InvalidParameterError('pollId');
2143
+ }
2144
+ const members = await this.listVaultMembers(didPoll);
2145
+ const voters = Object.keys(members);
2146
+ if (voters.length === 0) {
2147
+ throw new KeymasterError('No poll voters found');
2148
+ }
2149
+ const registry = this.ephemeralRegistry;
2150
+ const validUntil = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();
2151
+ const message = {
2152
+ to: voters,
2153
+ dids: [didPoll],
2154
+ };
2155
+ return this.createNotice(message, { registry, validUntil });
2156
+ }
2157
+ async sendBallot(ballotDid, pollId) {
2158
+ const didPoll = await this.lookupDID(pollId);
2159
+ const config = await this.getPoll(didPoll);
2160
+ if (!config) {
2161
+ throw new InvalidParameterError('pollId is not a valid poll');
2162
+ }
2163
+ const pollDoc = await this.resolveDID(didPoll);
2164
+ const ownerDid = pollDoc.didDocument?.controller;
2165
+ if (!ownerDid) {
2166
+ throw new KeymasterError('poll owner not found');
2167
+ }
2168
+ const registry = this.ephemeralRegistry;
2169
+ const validUntil = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();
2170
+ const message = {
2171
+ to: [ownerDid],
2172
+ dids: [ballotDid],
2173
+ };
2174
+ return this.createNotice(message, { registry, validUntil });
2175
+ }
2176
+ async viewBallot(ballotDid) {
2177
+ const docBallot = await this.resolveDID(ballotDid);
2178
+ const voter = docBallot.didDocument?.controller;
2179
+ const result = {
2180
+ poll: '',
2181
+ voter: voter || undefined,
2182
+ };
2183
+ try {
2184
+ const data = await this.decryptJSON(ballotDid);
2185
+ result.poll = data.poll;
2186
+ result.vote = data.vote;
2187
+ const config = await this.getPoll(data.poll);
2188
+ if (config && data.vote > 0 && data.vote <= config.options.length) {
2189
+ result.option = config.options[data.vote - 1];
2024
2190
  }
2025
- ballot = {
2026
- poll: didPoll,
2027
- vote: vote,
2028
- };
2191
+ else if (data.vote === 0) {
2192
+ result.option = 'spoil';
2193
+ }
2194
+ }
2195
+ catch {
2196
+ // Caller cannot decrypt (not the owner) — return limited info
2029
2197
  }
2030
- // Encrypt for receiver only
2031
- return await this.encryptJSON(ballot, owner, { ...options, encryptForSender: false });
2198
+ return result;
2032
2199
  }
2033
2200
  async updatePoll(ballot) {
2034
2201
  const id = await this.fetchIdInfo();
@@ -2038,7 +2205,7 @@ export default class Keymaster {
2038
2205
  let dataBallot;
2039
2206
  try {
2040
2207
  dataBallot = await this.decryptJSON(didBallot);
2041
- if (!dataBallot.poll || !dataBallot.vote) {
2208
+ if (!dataBallot.poll || dataBallot.vote === undefined) {
2042
2209
  throw new InvalidParameterError('ballot');
2043
2210
  }
2044
2211
  }
@@ -2048,34 +2215,34 @@ export default class Keymaster {
2048
2215
  const didPoll = dataBallot.poll;
2049
2216
  const docPoll = await this.resolveDID(didPoll);
2050
2217
  const didOwner = docPoll.didDocument.controller;
2051
- const poll = await this.getPoll(didPoll);
2052
- if (!poll) {
2218
+ const config = await this.getPoll(didPoll);
2219
+ if (!config) {
2053
2220
  throw new KeymasterError('Cannot find poll related to ballot');
2054
2221
  }
2055
2222
  if (id.did !== didOwner) {
2056
2223
  throw new InvalidParameterError('only owner can update a poll');
2057
2224
  }
2058
- const eligible = await this.testGroup(poll.roster, didVoter);
2059
- if (!eligible) {
2060
- throw new InvalidParameterError('voter not in roster');
2225
+ // Check voter is a vault member
2226
+ const vault = await this.getVault(didPoll);
2227
+ const voterBallotKey = this.generateBallotKey(vault, didVoter);
2228
+ const members = await this.listVaultMembers(didPoll);
2229
+ const isMember = !!members[didVoter] || didVoter === id.did;
2230
+ if (!isMember) {
2231
+ throw new InvalidParameterError('voter is not a poll member');
2061
2232
  }
2062
- const expired = Date.now() > new Date(poll.deadline).getTime();
2233
+ const expired = Date.now() > new Date(config.deadline).getTime();
2063
2234
  if (expired) {
2064
2235
  throw new InvalidParameterError('poll has expired');
2065
2236
  }
2066
- const max = poll.options.length;
2237
+ const max = config.options.length;
2067
2238
  const vote = dataBallot.vote;
2068
- if (!vote || vote < 0 || vote > max) {
2239
+ if (vote < 0 || vote > max) {
2069
2240
  throw new InvalidParameterError('ballot.vote');
2070
2241
  }
2071
- if (!poll.ballots) {
2072
- poll.ballots = {};
2073
- }
2074
- poll.ballots[didVoter] = {
2075
- ballot: didBallot,
2076
- received: new Date().toISOString(),
2077
- };
2078
- return this.mergeData(didPoll, { poll });
2242
+ // Store ballot DID as vault item keyed by voter's ballot key
2243
+ const buffer = Buffer.from(didBallot, 'utf-8');
2244
+ await this.addVaultItem(didPoll, voterBallotKey, buffer);
2245
+ return true;
2079
2246
  }
2080
2247
  async publishPoll(pollId, options = {}) {
2081
2248
  const { reveal = false } = options;
@@ -2085,19 +2252,20 @@ export default class Keymaster {
2085
2252
  if (id.did !== owner) {
2086
2253
  throw new InvalidParameterError('only owner can publish a poll');
2087
2254
  }
2088
- const view = await this.viewPoll(pollId);
2089
- if (!view.results?.final) {
2090
- throw new InvalidParameterError('poll not final');
2255
+ const config = await this.getPoll(pollId);
2256
+ if (!config) {
2257
+ throw new InvalidParameterError(pollId);
2091
2258
  }
2092
- if (!reveal && view.results.ballots) {
2093
- delete view.results.ballots;
2259
+ const results = await this.computePollResults(pollId, config);
2260
+ if (!results.final) {
2261
+ throw new InvalidParameterError('poll not final');
2094
2262
  }
2095
- const poll = await this.getPoll(pollId);
2096
- if (!poll) {
2097
- throw new InvalidParameterError(pollId);
2263
+ if (!reveal) {
2264
+ delete results.ballots;
2098
2265
  }
2099
- poll.results = view.results;
2100
- return this.mergeData(pollId, { poll });
2266
+ const buffer = Buffer.from(JSON.stringify(results), 'utf-8');
2267
+ await this.addVaultItem(pollId, PollItems.RESULTS, buffer);
2268
+ return true;
2101
2269
  }
2102
2270
  async unpublishPoll(pollId) {
2103
2271
  const id = await this.fetchIdInfo();
@@ -2106,12 +2274,11 @@ export default class Keymaster {
2106
2274
  if (id.did !== owner) {
2107
2275
  throw new InvalidParameterError(pollId);
2108
2276
  }
2109
- const poll = await this.getPoll(pollId);
2110
- if (!poll) {
2277
+ const config = await this.getPoll(pollId);
2278
+ if (!config) {
2111
2279
  throw new InvalidParameterError(pollId);
2112
2280
  }
2113
- delete poll.results;
2114
- return this.mergeData(pollId, { poll });
2281
+ return this.removeVaultItem(pollId, PollItems.RESULTS);
2115
2282
  }
2116
2283
  async createVault(options = {}) {
2117
2284
  const id = await this.fetchIdInfo();
@@ -2123,10 +2290,10 @@ export default class Keymaster {
2123
2290
  const salt = this.cipher.generateRandomSalt();
2124
2291
  const vaultKeypair = this.cipher.generateRandomJwk();
2125
2292
  const keys = {};
2126
- const config = this.cipher.encryptMessage(idKeypair.publicJwk, vaultKeypair.privateJwk, JSON.stringify(options));
2293
+ const config = this.cipher.encryptMessage(idKeypair.publicJwk, JSON.stringify(options));
2127
2294
  const publicJwk = options.secretMembers ? idKeypair.publicJwk : vaultKeypair.publicJwk; // If secret, encrypt for the owner only
2128
- const members = this.cipher.encryptMessage(publicJwk, vaultKeypair.privateJwk, JSON.stringify({}));
2129
- const items = this.cipher.encryptMessage(vaultKeypair.publicJwk, vaultKeypair.privateJwk, JSON.stringify({}));
2295
+ const members = this.cipher.encryptMessage(publicJwk, JSON.stringify({}));
2296
+ const items = this.cipher.encryptMessage(vaultKeypair.publicJwk, JSON.stringify({}));
2130
2297
  const sha256 = this.cipher.hashJSON({});
2131
2298
  const vault = {
2132
2299
  version,
@@ -2157,6 +2324,9 @@ export default class Keymaster {
2157
2324
  return false;
2158
2325
  }
2159
2326
  }
2327
+ generateBallotKey(vault, memberDID) {
2328
+ return this.generateSaltedId(vault, memberDID).slice(0, this.maxAliasLength);
2329
+ }
2160
2330
  generateSaltedId(vault, memberDID) {
2161
2331
  if (!vault.version) {
2162
2332
  return this.cipher.hashMessage(vault.salt + memberDID);
@@ -2195,13 +2365,13 @@ export default class Keymaster {
2195
2365
  }
2196
2366
  else {
2197
2367
  try {
2198
- const membersJSON = this.cipher.decryptMessage(vault.publicJwk, privateJwk, vault.members);
2368
+ const membersJSON = this.cipher.decryptMessage(privateJwk, vault.members, vault.publicJwk);
2199
2369
  members = JSON.parse(membersJSON);
2200
2370
  }
2201
2371
  catch (error) {
2202
2372
  }
2203
2373
  }
2204
- const itemsJSON = this.cipher.decryptMessage(vault.publicJwk, privateJwk, vault.items);
2374
+ const itemsJSON = this.cipher.decryptMessage(privateJwk, vault.items, vault.publicJwk);
2205
2375
  const items = JSON.parse(itemsJSON);
2206
2376
  return {
2207
2377
  isOwner,
@@ -2223,7 +2393,7 @@ export default class Keymaster {
2223
2393
  async addMemberKey(vault, memberDID, privateJwk) {
2224
2394
  const memberDoc = await this.resolveDID(memberDID, { confirm: true });
2225
2395
  const memberPublicJwk = this.getPublicKeyJwk(memberDoc);
2226
- const memberKey = this.cipher.encryptMessage(memberPublicJwk, privateJwk, JSON.stringify(privateJwk));
2396
+ const memberKey = this.cipher.encryptMessage(memberPublicJwk, JSON.stringify(privateJwk));
2227
2397
  const memberKeyId = this.generateSaltedId(vault, memberDID);
2228
2398
  vault.keys[memberKeyId] = memberKey;
2229
2399
  }
@@ -2268,7 +2438,7 @@ export default class Keymaster {
2268
2438
  }
2269
2439
  members[memberDID] = { added: new Date().toISOString() };
2270
2440
  const publicJwk = config.secretMembers ? idKeypair.publicJwk : vault.publicJwk;
2271
- vault.members = this.cipher.encryptMessage(publicJwk, privateJwk, JSON.stringify(members));
2441
+ vault.members = this.cipher.encryptMessage(publicJwk, JSON.stringify(members));
2272
2442
  await this.addMemberKey(vault, memberDID, privateJwk);
2273
2443
  return this.mergeData(vaultId, { vault });
2274
2444
  }
@@ -2276,7 +2446,7 @@ export default class Keymaster {
2276
2446
  const owner = await this.checkVaultOwner(vaultId);
2277
2447
  const idKeypair = await this.fetchKeyPair();
2278
2448
  const vault = await this.getVault(vaultId);
2279
- const { privateJwk, config, members } = await this.decryptVault(vault);
2449
+ const { config, members } = await this.decryptVault(vault);
2280
2450
  const memberDoc = await this.resolveDID(memberId, { confirm: true });
2281
2451
  const memberDID = this.getAgentDID(memberDoc);
2282
2452
  // Don't allow removing the vault owner
@@ -2285,7 +2455,7 @@ export default class Keymaster {
2285
2455
  }
2286
2456
  delete members[memberDID];
2287
2457
  const publicJwk = config.secretMembers ? idKeypair.publicJwk : vault.publicJwk;
2288
- vault.members = this.cipher.encryptMessage(publicJwk, privateJwk, JSON.stringify(members));
2458
+ vault.members = this.cipher.encryptMessage(publicJwk, JSON.stringify(members));
2289
2459
  const memberKeyId = this.generateSaltedId(vault, memberDID);
2290
2460
  delete vault.keys[memberKeyId];
2291
2461
  return this.mergeData(vaultId, { vault });
@@ -2301,9 +2471,9 @@ export default class Keymaster {
2301
2471
  async addVaultItem(vaultId, name, buffer) {
2302
2472
  await this.checkVaultOwner(vaultId);
2303
2473
  const vault = await this.getVault(vaultId);
2304
- const { privateJwk, items } = await this.decryptVault(vault);
2474
+ const { items } = await this.decryptVault(vault);
2305
2475
  const validName = this.validateAlias(name);
2306
- const encryptedData = this.cipher.encryptBytes(vault.publicJwk, privateJwk, buffer);
2476
+ const encryptedData = this.cipher.encryptBytes(vault.publicJwk, buffer);
2307
2477
  const cid = await this.gatekeeper.addText(encryptedData);
2308
2478
  const sha256 = this.cipher.hashMessage(buffer);
2309
2479
  const type = await this.getMimeType(buffer);
@@ -2316,16 +2486,16 @@ export default class Keymaster {
2316
2486
  added: new Date().toISOString(),
2317
2487
  data,
2318
2488
  };
2319
- vault.items = this.cipher.encryptMessage(vault.publicJwk, privateJwk, JSON.stringify(items));
2489
+ vault.items = this.cipher.encryptMessage(vault.publicJwk, JSON.stringify(items));
2320
2490
  vault.sha256 = this.cipher.hashJSON(items);
2321
2491
  return this.mergeData(vaultId, { vault });
2322
2492
  }
2323
2493
  async removeVaultItem(vaultId, name) {
2324
2494
  await this.checkVaultOwner(vaultId);
2325
2495
  const vault = await this.getVault(vaultId);
2326
- const { privateJwk, items } = await this.decryptVault(vault);
2496
+ const { items } = await this.decryptVault(vault);
2327
2497
  delete items[name];
2328
- vault.items = this.cipher.encryptMessage(vault.publicJwk, privateJwk, JSON.stringify(items));
2498
+ vault.items = this.cipher.encryptMessage(vault.publicJwk, JSON.stringify(items));
2329
2499
  vault.sha256 = this.cipher.hashJSON(items);
2330
2500
  return this.mergeData(vaultId, { vault });
2331
2501
  }
@@ -2344,7 +2514,7 @@ export default class Keymaster {
2344
2514
  if (!encryptedData) {
2345
2515
  throw new KeymasterError(`Failed to retrieve data for item '${name}' (CID: ${items[name].cid})`);
2346
2516
  }
2347
- const bytes = this.cipher.decryptBytes(vault.publicJwk, privateJwk, encryptedData);
2517
+ const bytes = this.cipher.decryptBytes(privateJwk, encryptedData, vault.publicJwk);
2348
2518
  return Buffer.from(bytes);
2349
2519
  }
2350
2520
  async listDmail() {
@@ -2628,7 +2798,7 @@ export default class Keymaster {
2628
2798
  if (poll) {
2629
2799
  const names = await this.listAliases();
2630
2800
  if (!Object.values(names).includes(noticeDID)) {
2631
- await this.addUnaliasedPoll(noticeDID);
2801
+ await this.addUnaliasedPoll(noticeDID, poll.name);
2632
2802
  }
2633
2803
  await this.addToNotices(did, [NoticeTags.POLL]);
2634
2804
  continue;
@@ -2712,10 +2882,20 @@ export default class Keymaster {
2712
2882
  }
2713
2883
  return payload && typeof payload.poll === "string" && typeof payload.vote === "number";
2714
2884
  }
2715
- async addUnaliasedPoll(did) {
2716
- const fallbackName = did.slice(-32);
2885
+ async addUnaliasedPoll(did, name) {
2886
+ const baseName = name || did.slice(-32);
2887
+ const aliases = await this.listAliases();
2888
+ let candidate = baseName;
2889
+ let suffix = 2;
2890
+ while (candidate in aliases) {
2891
+ if (aliases[candidate] === did) {
2892
+ return; // Already aliased to this DID
2893
+ }
2894
+ candidate = `${baseName}-${suffix}`;
2895
+ suffix++;
2896
+ }
2717
2897
  try {
2718
- await this.addAlias(fallbackName, did);
2898
+ await this.addAlias(candidate, did);
2719
2899
  }
2720
2900
  catch { }
2721
2901
  }
@@ -2731,26 +2911,27 @@ export default class Keymaster {
2731
2911
  const { version, seed, ...rest } = decrypted;
2732
2912
  const safeSeed = { mnemonicEnc: seed.mnemonicEnc };
2733
2913
  const hdkey = await this.getHDKeyFromCacheOrMnemonic(decrypted);
2734
- const { publicJwk, privateJwk } = this.cipher.generateJwk(hdkey.privateKey);
2914
+ const { publicJwk } = this.cipher.generateJwk(hdkey.privateKey);
2735
2915
  const plaintext = JSON.stringify(rest);
2736
- const enc = this.cipher.encryptMessage(publicJwk, privateJwk, plaintext);
2916
+ const enc = this.cipher.encryptMessage(publicJwk, plaintext);
2737
2917
  return { version: version, seed: safeSeed, enc };
2738
2918
  }
2739
2919
  async decryptWalletFromStorage(stored) {
2740
2920
  let mnemonic;
2741
2921
  try {
2742
- mnemonic = await decMnemonic(stored.seed.mnemonicEnc, this.passphrase);
2922
+ mnemonic = await decryptWithPassphrase(stored.seed.mnemonicEnc, this.passphrase);
2743
2923
  }
2744
2924
  catch (error) {
2745
- // OperationError is thrown by crypto.subtle.decrypt when the passphrase is wrong
2746
- if (error?.name === 'OperationError') {
2925
+ const msg = error?.message || '';
2926
+ // OperationError: Web Crypto API (legacy); 'invalid ghash tag': @noble/ciphers
2927
+ if (error?.name === 'OperationError' || msg.includes('invalid ghash tag')) {
2747
2928
  throw new KeymasterError('Incorrect passphrase.');
2748
2929
  }
2749
2930
  throw error;
2750
2931
  }
2751
2932
  this._hdkeyCache = this.cipher.generateHDKey(mnemonic);
2752
2933
  const { publicJwk, privateJwk } = this.cipher.generateJwk(this._hdkeyCache.privateKey);
2753
- const plaintext = this.cipher.decryptMessage(publicJwk, privateJwk, stored.enc);
2934
+ const plaintext = this.cipher.decryptMessage(privateJwk, stored.enc, publicJwk);
2754
2935
  const data = JSON.parse(plaintext);
2755
2936
  const wallet = { version: stored.version, seed: stored.seed, ...data };
2756
2937
  return wallet;