@sidhujag/sysweb3-keyring 2.1.1 → 2.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 (46) hide show
  1. package/CHANGELOG.md +5 -2
  2. package/README.md +8 -0
  3. package/cjs/current-vault-refusal.js +6 -0
  4. package/cjs/current-vault-refusal.js.map +1 -0
  5. package/cjs/evm-fees.js +29 -1
  6. package/cjs/evm-fees.js.map +1 -1
  7. package/cjs/keyring-core.js +349 -84
  8. package/cjs/keyring-core.js.map +1 -1
  9. package/cjs/keyring-manager.js +100 -4
  10. package/cjs/keyring-manager.js.map +1 -1
  11. package/cjs/software.js +14 -1
  12. package/cjs/software.js.map +1 -1
  13. package/cjs/storage-core.js +44 -4
  14. package/cjs/storage-core.js.map +1 -1
  15. package/cjs/transactions/ethereum.js +138 -82
  16. package/cjs/transactions/ethereum.js.map +1 -1
  17. package/cjs/transactions/evm-cancellation.js +43 -0
  18. package/cjs/transactions/evm-cancellation.js.map +1 -0
  19. package/cjs/transactions/evm-local-signer.js +4 -2
  20. package/cjs/transactions/evm-local-signer.js.map +1 -1
  21. package/cjs/transactions/evm-submission.js +23 -2
  22. package/cjs/transactions/evm-submission.js.map +1 -1
  23. package/cjs/transactions/prepared-evm.js +3 -6
  24. package/cjs/transactions/prepared-evm.js.map +1 -1
  25. package/esm/ledger/dmk-host.js +31 -0
  26. package/esm/ledger/dmk-host.js.map +1 -0
  27. package/esm/ledger/dmk-selected-host.js +6 -0
  28. package/esm/ledger/dmk-selected-host.js.map +1 -0
  29. package/esm/ledger/dmk-session.js +153 -34
  30. package/esm/ledger/dmk-session.js.map +1 -1
  31. package/esm/ledger-host.js +3 -27
  32. package/esm/ledger-host.js.map +1 -1
  33. package/package.json +13 -1
  34. package/types/current-vault-refusal.d.ts +2 -0
  35. package/types/evm-fees.d.ts +25 -0
  36. package/types/keyring-core.d.ts +36 -3
  37. package/types/keyring-manager.d.ts +15 -3
  38. package/types/ledger/dmk-host.d.ts +7 -0
  39. package/types/ledger/dmk-selected-host.d.ts +7 -0
  40. package/types/ledger/dmk-session.d.ts +10 -2
  41. package/types/ledger-host.d.ts +6 -2
  42. package/types/software.d.ts +4 -3
  43. package/types/storage-core.d.ts +3 -1
  44. package/types/storage.d.ts +1 -1
  45. package/types/transactions/evm-cancellation.d.ts +10 -0
  46. package/types/transactions/evm-submission.d.ts +4 -1
@@ -32,9 +32,21 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
36
+ if (kind === "m") throw new TypeError("Private method is not writable");
37
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
38
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
39
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
40
+ };
41
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
42
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
43
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
44
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
45
+ };
35
46
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
47
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
48
  };
49
+ var _KeyringCore_instances, _KeyringCore_deviceUnlockRetired, _KeyringCore_sessionOwnership, _KeyringCore_createDeviceUnlockControls, _KeyringCore_acceptSessionFrom, _KeyringCore_assertSessionOwner, _KeyringCore_captureSessionGuard, _KeyringCore_replaceSessionBuffers, _KeyringCore_finishUnlock, _KeyringCore_installVaultSession, _KeyringCore_recreateSessionFromVault;
38
50
  Object.defineProperty(exports, "__esModule", { value: true });
39
51
  exports.KeyringCore = void 0;
40
52
  const secp256k1_1 = __importDefault(require("@bitcoinerlab/secp256k1"));
@@ -56,6 +68,7 @@ const SLH_DSA_DERIVATION_VERSION = 1;
56
68
  // Pali expands this setup secret into the 48-byte SLH skSeed/skPrf/pkSeed tuple.
57
69
  const SLH_DSA_SETUP_SECRET_HEX_LENGTH = 64;
58
70
  const SLH_DSA_PARAMETER_SET = 'SLH-DSA-SHA2-128-24';
71
+ const current_vault_refusal_1 = require("./current-vault-refusal");
59
72
  const ethers_v6_1 = require("./ethers-v6");
60
73
  const initial_state_1 = require("./initial-state");
61
74
  const private_records_1 = require("./private-records");
@@ -71,6 +84,28 @@ const blockbook_cache_1 = require("./utils/blockbook-cache");
71
84
  const derivation_paths_1 = require("./utils/derivation-paths");
72
85
  const psbt_account_scope_1 = require("./utils/psbt-account-scope");
73
86
  const watch_descriptor_1 = require("./utils/watch-descriptor");
87
+ const currentVaultSalt = (keys) => {
88
+ const salt = keys && typeof keys === 'object' && Reflect.ownKeys(keys).length === 1
89
+ ? Object.getOwnPropertyDescriptor(keys, 'salt')?.value
90
+ : undefined;
91
+ return typeof salt === 'string' && /^[0-9a-f]{32}$/.test(salt)
92
+ ? salt
93
+ : undefined;
94
+ };
95
+ const readCurrentVaultPair = async (readPair) => {
96
+ const pair = await readPair();
97
+ if (pair === null)
98
+ return null;
99
+ const { vault, keys, pairFingerprint } = pair;
100
+ const salt = currentVaultSalt(keys);
101
+ if (typeof vault !== 'string' ||
102
+ salt === undefined ||
103
+ typeof pairFingerprint !== 'string' ||
104
+ !/^[0-9a-f]{64}$/.test(pairFingerprint)) {
105
+ throw new Error('Invalid device unlock vault pair');
106
+ }
107
+ return { vault, salt, pairFingerprint };
108
+ };
74
109
  // Dynamic ETH HD path generation - will be computed as needed
75
110
  /**
76
111
  * Secure Buffer implementation for sensitive data
@@ -112,6 +147,7 @@ class SecureBuffer {
112
147
  }
113
148
  class KeyringCore {
114
149
  constructor(dependencies) {
150
+ _KeyringCore_instances.add(this);
115
151
  // Store getter function for accessing Redux state
116
152
  this.getVaultState = null;
117
153
  // Method to inject store getter from Pali side
@@ -156,6 +192,8 @@ class KeyringCore {
156
192
  this.sessionPassword = null;
157
193
  this.sessionMnemonic = null; // can be a mnemonic or a zprv, can be changed to a zprv when using an imported wallet
158
194
  this.privateRecordSessionRevision = 0;
195
+ _KeyringCore_deviceUnlockRetired.set(this, false);
196
+ _KeyringCore_sessionOwnership.set(this, void 0);
159
197
  this.createSyscoinTransactions = () => new syscoin_core_1.SyscoinTransactionsCore(this.getSigner, this.getReadOnlySigner, this.getAccountsState, this.getAddress, this.syscoinHardwareSigners, this.capturePsbtSigningContext);
160
198
  this.requireLedgerSigner = () => {
161
199
  if (!this.ledgerSigner)
@@ -191,6 +229,11 @@ class KeyringCore {
191
229
  this.sessionMnemonic.clear();
192
230
  this.sessionMnemonic = null;
193
231
  }
232
+ const ownership = __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f");
233
+ if (ownership && ownership.tracker.current === ownership.token) {
234
+ ownership.tracker.current = undefined;
235
+ ownership.tracker.epoch++;
236
+ }
194
237
  // Clear transaction handlers that may hold HD signers
195
238
  if (this.syscoinTransaction) {
196
239
  // Replace with empty object to clear references
@@ -234,40 +277,15 @@ class KeyringCore {
234
277
  if (!this.sessionPassword || !this.sessionMnemonic) {
235
278
  throw new Error('Session data is missing during transfer');
236
279
  }
280
+ __classPrivateFieldGet(targetKeyring, _KeyringCore_instances, "m", _KeyringCore_acceptSessionFrom).call(targetKeyring, this);
237
281
  this.privateRecordSessionRevision++;
238
- targetKeyring.acceptSessionFrom(this);
282
+ __classPrivateFieldSet(this, _KeyringCore_deviceUnlockRetired, true, "f");
239
283
  // Null out our references (do NOT clear buffers - target owns them now)
240
284
  this.sessionPassword = null;
241
285
  this.sessionMnemonic = null;
242
286
  };
243
- // The checked transfer path never dispatches through a public host receiver.
244
- this.acceptSessionFrom = (source) => {
245
- if (source.authority !== this.authority ||
246
- source.storage !== this.storage ||
247
- source.vaultPersistence !== this.vaultPersistence) {
248
- throw new Error('Cannot transfer session between different wallet authorities');
249
- }
250
- if (!source.sessionPassword || !source.sessionMnemonic) {
251
- throw new Error('Session data is missing during transfer');
252
- }
253
- this.replaceSessionBuffers(source.sessionPassword, source.sessionMnemonic);
254
- };
255
287
  // Legacy trusted-host API. SoftwareKeyring disables direct buffer injection.
256
- this.receiveSessionOwnership = (sessionPassword, sessionMnemonic) => this.replaceSessionBuffers(sessionPassword, sessionMnemonic);
257
- this.replaceSessionBuffers = (sessionPassword, sessionMnemonic) => {
258
- this.privateRecordSessionRevision++;
259
- // Clear any existing data first
260
- if (this.sessionPassword) {
261
- this.sessionPassword.clear();
262
- }
263
- if (this.sessionMnemonic) {
264
- this.sessionMnemonic.clear();
265
- }
266
- // Take ownership of the actual SecureBuffer objects
267
- // No copying - these are the original objects
268
- this.sessionPassword = sessionPassword;
269
- this.sessionMnemonic = sessionMnemonic;
270
- };
288
+ this.receiveSessionOwnership = (sessionPassword, sessionMnemonic) => __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_replaceSessionBuffers).call(this, sessionPassword, sessionMnemonic);
271
289
  this.addNewAccount = async (label) => {
272
290
  // Check if wallet is unlocked
273
291
  if (!this.isUnlocked()) {
@@ -1198,7 +1216,7 @@ class KeyringCore {
1198
1216
  },
1199
1217
  // v4 vault is encrypted with the derived session password key (PBKDF2 output)
1200
1218
  sessionPasswordSaltedHash, { salt });
1201
- await this.recreateSessionFromVault(sessionPasswordSaltedHash);
1219
+ await __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_recreateSessionFromVault).call(this, sessionPasswordSaltedHash);
1202
1220
  };
1203
1221
  // NEW: Create first account without signer setup
1204
1222
  this.createFirstAccount = async (label) => {
@@ -1247,6 +1265,12 @@ class KeyringCore {
1247
1265
  throw new TypeError('Keyring core requires bound vault persistence');
1248
1266
  }
1249
1267
  this.authority = dependencies.authority;
1268
+ if (dependencies.sessionOwner) {
1269
+ __classPrivateFieldSet(this, _KeyringCore_sessionOwnership, {
1270
+ tracker: dependencies.sessionOwner,
1271
+ token: Symbol(),
1272
+ }, "f");
1273
+ }
1250
1274
  this.storage = dependencies.storage;
1251
1275
  this.vaultPersistence = dependencies.vaultPersistence;
1252
1276
  this.ledgerSigner = dependencies.ledgerSigner;
@@ -1258,6 +1282,15 @@ class KeyringCore {
1258
1282
  this.initialLedgerAccountState = initial_state_1.initialActiveLedgerAccountState;
1259
1283
  this.syscoinTransaction = this.createSyscoinTransactions();
1260
1284
  this.ethereumTransaction = new ethereum_1.EthereumTransactions(this.getNetwork, this.getDecryptedPrivateKey, this.getAccountsState, this.ledgerSigner, this.trezorSigner, this.capturePsbtSigningContext, dependencies.captureEvmSubmission);
1285
+ if (dependencies.deviceUnlock) {
1286
+ if (!__classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f"))
1287
+ throw new TypeError('Device unlock requires a session owner');
1288
+ const { port, receive } = dependencies.deviceUnlock;
1289
+ const readVaultPair = this.storage.readDeviceUnlockVaultPair;
1290
+ if (typeof readVaultPair !== 'function')
1291
+ throw new TypeError('Device unlock requires an atomic vault pair reader');
1292
+ receive(__classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_createDeviceUnlockControls).call(this, port, readVaultPair.bind(this.storage)));
1293
+ }
1261
1294
  }
1262
1295
  // Convenience method for complete setup after construction
1263
1296
  async initialize(seed, password, network) {
@@ -1277,18 +1310,28 @@ class KeyringCore {
1277
1310
  * not charge a failed password attempt for an operational error.
1278
1311
  */
1279
1312
  async unlock(password) {
1313
+ let assertCurrent = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
1280
1314
  let authenticated = false;
1281
1315
  try {
1316
+ assertCurrent?.();
1282
1317
  const vaultKeys = await this.storage.get('vault-keys');
1318
+ assertCurrent?.();
1283
1319
  if (!vaultKeys) {
1284
1320
  throw new Error('Vault keys not found. Reload the wallet and try again.');
1285
1321
  }
1286
1322
  // v4: Validate password by deriving the session key once and decrypting the vault with it.
1287
1323
  // This avoids an extra KDF pass just for a stored verifier.
1288
1324
  const { salt } = vaultKeys;
1325
+ const readVaultPair = assertCurrent
1326
+ ? this.storage.readDeviceUnlockVaultPair
1327
+ : undefined;
1328
+ const accountingSalt = readVaultPair
1329
+ ? currentVaultSalt(vaultKeys)
1330
+ : undefined;
1289
1331
  // Derive session encryption key (in-memory only) used to encrypt/decrypt xprv fields
1290
1332
  // IMPORTANT: This MUST NOT be the same value as the stored verifier above.
1291
- let sessionPasswordKey = await this.deriveStoredSessionKey(password, vaultKeys);
1333
+ let sessionPasswordKey = await this.deriveStoredSessionKey(password, accountingSalt === undefined ? vaultKeys : { salt: accountingSalt });
1334
+ assertCurrent?.();
1292
1335
  // Handle migration from old vault format with currentSessionSalt
1293
1336
  if (vaultKeys.currentSessionSalt) {
1294
1337
  console.log('[KeyringManager] Detected old vault format, handling session migration...');
@@ -1299,6 +1342,7 @@ class KeyringCore {
1299
1342
  // derived key. Selecting by stored format also permits a safe retry
1300
1343
  // if the vault write succeeded but the metadata write was interrupted.
1301
1344
  const { mnemonic } = await this.vaultPersistence.getDecryptedVault(sessionPasswordKey, password);
1345
+ assertCurrent?.();
1302
1346
  authenticated = true;
1303
1347
  const isValidSecret = (value) => {
1304
1348
  if (typeof value !== 'string' || !value)
@@ -1338,13 +1382,16 @@ class KeyringCore {
1338
1382
  throw new Error('Legacy vault secret could not be recovered. Existing vault preserved.');
1339
1383
  }
1340
1384
  // Do not change either stored record until a valid secret is recovered.
1341
- await this.vaultPersistence.setEncryptedVault({ mnemonic: decryptedMnemonic }, sessionPasswordKey);
1385
+ await this.vaultPersistence.setEncryptedVault({ mnemonic: decryptedMnemonic }, sessionPasswordKey, undefined, assertCurrent);
1386
+ assertCurrent?.();
1342
1387
  console.log('[KeyringManager] Vault mnemonic format normalized');
1343
1388
  // Remove currentSessionSalt from vault-keys (v4 keeps only the salt on disk)
1344
1389
  const migratedVaultKeys = {
1345
1390
  salt: vaultKeys.salt,
1346
1391
  };
1392
+ assertCurrent?.();
1347
1393
  await this.storage.set('vault-keys', migratedVaultKeys);
1394
+ assertCurrent?.();
1348
1395
  console.log('[KeyringManager] Old vault format migration completed');
1349
1396
  }
1350
1397
  // v4 vault is encrypted with the derived session password key (PBKDF2 output).
@@ -1355,6 +1402,25 @@ class KeyringCore {
1355
1402
  decryptedVault = await this.vaultPersistence.getDecryptedVault(sessionPasswordKey);
1356
1403
  }
1357
1404
  catch (error) {
1405
+ const matchesRefusedVault = accountingSalt === undefined
1406
+ ? undefined
1407
+ : current_vault_refusal_1.currentVaultRefusals.get(error);
1408
+ if (matchesRefusedVault && readVaultPair) {
1409
+ const pair = await readCurrentVaultPair(readVaultPair.bind(this.storage)).catch((readError) => {
1410
+ // A storage rejection cannot attest to password authentication.
1411
+ if (readError instanceof storage_core_1.VaultAuthenticationError)
1412
+ throw new Error('Wallet unlock vault read failed');
1413
+ throw readError;
1414
+ });
1415
+ if (!pair ||
1416
+ !matchesRefusedVault(pair.vault) ||
1417
+ pair.salt !== accountingSalt)
1418
+ throw new Error('Wallet unlock vault changed');
1419
+ // This exact negative result has no session authority. Keep it
1420
+ // available for the host counter even if its owner just retired.
1421
+ return { canLogin: false };
1422
+ }
1423
+ assertCurrent?.();
1358
1424
  // Only the historical CBC fallback used the 20k key without metadata.
1359
1425
  // Never try it for GCM, malformed records, or platform/storage failures.
1360
1426
  const rawVault = error instanceof storage_core_1.VaultAuthenticationError &&
@@ -1362,65 +1428,56 @@ class KeyringCore {
1362
1428
  !vaultKeys.currentSessionSalt
1363
1429
  ? await this.storage.get('vault')
1364
1430
  : undefined;
1431
+ assertCurrent?.();
1365
1432
  if (typeof rawVault !== 'string' || rawVault.trim().startsWith('{')) {
1366
1433
  throw error;
1367
1434
  }
1368
1435
  const legacyKey = await this.deriveLegacySessionKey(password, salt);
1436
+ assertCurrent?.();
1369
1437
  decryptedVault = await this.vaultPersistence.getDecryptedVault(legacyKey);
1438
+ assertCurrent?.();
1370
1439
  authenticated = true;
1371
1440
  sessionPasswordKey = legacyKey;
1372
1441
  // Keep the historical account wrapping key. Persist its profile before
1373
1442
  // GCM rewrapping so either interrupted write remains recoverable.
1374
- await this.storage.set('vault-keys', {
1443
+ const migratedVaultKeys = {
1375
1444
  ...vaultKeys,
1376
1445
  keyDerivation: LEGACY_ENCRYPTION_KDF,
1377
- });
1446
+ };
1447
+ assertCurrent?.();
1448
+ await this.storage.set('vault-keys', migratedVaultKeys);
1449
+ assertCurrent?.();
1378
1450
  legacyProfile = true;
1379
1451
  }
1452
+ assertCurrent?.();
1380
1453
  authenticated = true;
1381
1454
  if (legacyProfile && this.hasWebCrypto()) {
1382
1455
  const storedVault = await this.storage.get('vault');
1456
+ assertCurrent?.();
1383
1457
  if (typeof storedVault === 'string' &&
1384
1458
  !storedVault.trim().startsWith('{')) {
1385
1459
  // A failed earlier rewrap can leave the authenticated profile plus
1386
1460
  // CBC. Retry that write without changing the account wrapping key.
1387
- await this.vaultPersistence.setEncryptedVault(decryptedVault, sessionPasswordKey);
1461
+ await this.vaultPersistence.setEncryptedVault(decryptedVault, sessionPasswordKey, undefined, assertCurrent);
1462
+ assertCurrent?.();
1388
1463
  }
1389
1464
  }
1390
1465
  // If session data missing or corrupted, recreate from vault
1391
1466
  if (!this.sessionMnemonic) {
1392
- await this.recreateSessionFromVault(sessionPasswordKey);
1393
- }
1394
- // Re-initialize syscoin transaction handler if it was cleared on lock.
1395
- // `lockWallet()` intentionally replaces it with a plain object to drop signer references,
1396
- // so we must restore the real class instance on unlock.
1397
- if (!this.syscoinTransaction ||
1398
- typeof this.syscoinTransaction.getRecommendedFee !== 'function') {
1399
- this.syscoinTransaction = this.createSyscoinTransactions();
1400
- }
1401
- // NOTE: Active account management is now handled by vault state/Redux
1402
- // No need to explicitly set active account after unlock - it's managed externally
1403
- const vault = this.getVault();
1404
- if (vault.activeAccount?.id !== undefined && vault.activeAccount?.type) {
1405
- // Check if the active account actually exists in the accounts map
1406
- const accountType = vault.activeAccount.type;
1407
- const accountId = vault.activeAccount.id;
1408
- const accountExists = vault.accounts?.[accountType]?.[accountId];
1409
- if (!accountExists) {
1410
- console.log(`[KeyringManager] Active account ${accountType}:${accountId} not found in accounts map. This may indicate a migration from old vault format.`);
1411
- // Signal that accounts need to be created after migration
1412
- return {
1413
- canLogin: true,
1414
- needsAccountCreation: true,
1415
- };
1467
+ assertCurrent = await __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_recreateSessionFromVault).call(this, sessionPasswordKey, assertCurrent);
1468
+ if (assertCurrent) {
1469
+ assertCurrent();
1470
+ console.log('[KeyringManager] Session data recreated from vault');
1416
1471
  }
1417
- console.log(`[KeyringManager] Active account ${vault.activeAccount.id} available after unlock`);
1418
1472
  }
1419
- return {
1420
- canLogin: true,
1421
- };
1473
+ assertCurrent?.();
1474
+ const result = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_finishUnlock).call(this);
1475
+ assertCurrent?.();
1476
+ return result;
1422
1477
  }
1423
1478
  catch (error) {
1479
+ // A superseded attempt must not report authentication or clear its winner.
1480
+ assertCurrent?.();
1424
1481
  if (!authenticated && error instanceof storage_core_1.VaultAuthenticationError) {
1425
1482
  return { canLogin: false };
1426
1483
  }
@@ -2169,28 +2226,6 @@ class KeyringCore {
2169
2226
  // No gaps found, return next sequential ID
2170
2227
  return existingIds.length;
2171
2228
  }
2172
- async recreateSessionFromVault(saltedHashPassword) {
2173
- try {
2174
- // v4 vault is encrypted with the derived session password key (PBKDF2 output)
2175
- // `saltedHashPassword` is the derived session password key passed by caller.
2176
- const { mnemonic } = await this.vaultPersistence.getDecryptedVault(saltedHashPassword);
2177
- if (!mnemonic) {
2178
- throw new Error('Mnemonic not found in vault');
2179
- }
2180
- // Encrypt session data with sessionPassword hash for consistency
2181
- // This allows keyring manager to decrypt with this.sessionPassword
2182
- this.privateRecordSessionRevision++;
2183
- this.sessionPassword = new SecureBuffer(saltedHashPassword);
2184
- // Encrypt the mnemonic with session password for consistency with the rest of the code
2185
- const encryptedMnemonic = crypto_js_1.default.AES.encrypt(mnemonic, saltedHashPassword).toString();
2186
- this.sessionMnemonic = new SecureBuffer(encryptedMnemonic);
2187
- console.log('[KeyringManager] Session data recreated from vault');
2188
- }
2189
- catch (error) {
2190
- console.error('ERROR recreateSessionFromVault', { error });
2191
- throw error;
2192
- }
2193
- }
2194
2229
  async _getPrivateKeyAccountInfos(privKey, label, options) {
2195
2230
  const vault = this.getVault();
2196
2231
  const { accounts } = vault;
@@ -2595,6 +2630,9 @@ class KeyringCore {
2595
2630
  * Clean up all resources
2596
2631
  */
2597
2632
  async destroy() {
2633
+ __classPrivateFieldSet(this, _KeyringCore_deviceUnlockRetired, true, "f");
2634
+ if (__classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f"))
2635
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").destroyed = true;
2598
2636
  await this.lockWallet();
2599
2637
  // Clear any remaining references
2600
2638
  this.ethereumTransaction = {};
@@ -2602,4 +2640,231 @@ class KeyringCore {
2602
2640
  }
2603
2641
  }
2604
2642
  exports.KeyringCore = KeyringCore;
2643
+ _KeyringCore_deviceUnlockRetired = new WeakMap(), _KeyringCore_sessionOwnership = new WeakMap(), _KeyringCore_instances = new WeakSet(), _KeyringCore_createDeviceUnlockControls = function _KeyringCore_createDeviceUnlockControls(port, readVaultPair) {
2644
+ const readPair = () => readCurrentVaultPair(readVaultPair);
2645
+ let pending = false;
2646
+ const run = async (request, assertCurrent) => {
2647
+ if (pending)
2648
+ throw new Error('Device unlock operation is busy');
2649
+ pending = true;
2650
+ const assertOwnership = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this, 'Device unlock session changed');
2651
+ const issuing = request !== null;
2652
+ const check = () => {
2653
+ const result = assertCurrent();
2654
+ if (result !== undefined) {
2655
+ // Reject async guards and consume a possible rejected promise.
2656
+ if (result &&
2657
+ (typeof result === 'object' || typeof result === 'function'))
2658
+ void Promise.resolve(result).catch(() => undefined);
2659
+ throw new TypeError('Device unlock guard must return undefined synchronously');
2660
+ }
2661
+ if (__classPrivateFieldGet(this, _KeyringCore_deviceUnlockRetired, "f") ||
2662
+ (!issuing && (this.sessionPassword || this.sessionMnemonic))) {
2663
+ throw new Error('Device unlock session changed');
2664
+ }
2665
+ assertOwnership?.();
2666
+ };
2667
+ let key;
2668
+ try {
2669
+ check();
2670
+ const pair = await readPair();
2671
+ check();
2672
+ if (!pair)
2673
+ return false;
2674
+ const checkPair = async (requireLiveOwner = true) => {
2675
+ const current = await readPair();
2676
+ if (requireLiveOwner)
2677
+ check();
2678
+ if (current?.pairFingerprint !== pair.pairFingerprint ||
2679
+ current.vault !== pair.vault ||
2680
+ current.salt !== pair.salt) {
2681
+ throw new Error('Device unlock vault changed');
2682
+ }
2683
+ };
2684
+ if (request) {
2685
+ key = this.hexToBytes(await this.deriveStoredSessionKey(request.password, {
2686
+ salt: pair.salt,
2687
+ }));
2688
+ }
2689
+ else {
2690
+ const credential = await port.readCredential(pair.pairFingerprint);
2691
+ key = credential?.key;
2692
+ check();
2693
+ if (credential?.pairFingerprint !== pair.pairFingerprint)
2694
+ return false;
2695
+ }
2696
+ check();
2697
+ if (!(key instanceof Uint8Array) || key.length !== 32)
2698
+ return false;
2699
+ const keyHex = Buffer.from(key.buffer, key.byteOffset, key.byteLength).toString('hex');
2700
+ let payload;
2701
+ try {
2702
+ payload = await (0, storage_core_1.decryptCurrentVault)(pair.vault, keyHex);
2703
+ }
2704
+ catch (error) {
2705
+ if (!(error instanceof storage_core_1.VaultAuthenticationError))
2706
+ throw error;
2707
+ // Preserve a definitive wrong-password result for host accounting
2708
+ // even if enrollment was cancelled. The pair must still match and
2709
+ // this branch never installs a session or writes a credential.
2710
+ await checkPair(!issuing);
2711
+ if (!issuing)
2712
+ check();
2713
+ return false;
2714
+ }
2715
+ check();
2716
+ const mnemonic = payload && typeof payload === 'object'
2717
+ ? Object.getOwnPropertyDescriptor(payload, 'mnemonic')?.value
2718
+ : undefined;
2719
+ if (typeof mnemonic !== 'string' || !this.isSeedValid(mnemonic)) {
2720
+ throw new Error('Invalid device unlock vault payload');
2721
+ }
2722
+ await checkPair();
2723
+ check();
2724
+ if (request) {
2725
+ const result = await port.writeCredential(Object.freeze({ pairFingerprint: pair.pairFingerprint, key }), check);
2726
+ if (result !== undefined)
2727
+ throw new Error('Device unlock credential write was not acknowledged');
2728
+ await checkPair();
2729
+ check();
2730
+ }
2731
+ else {
2732
+ if (__classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_finishUnlock).call(this).needsAccountCreation)
2733
+ return false;
2734
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_installVaultSession).call(this, mnemonic, keyHex, check);
2735
+ }
2736
+ return true;
2737
+ }
2738
+ finally {
2739
+ if (key instanceof Uint8Array)
2740
+ key.fill(0);
2741
+ pending = false;
2742
+ }
2743
+ };
2744
+ return Object.freeze({
2745
+ issue: (password, assertCurrent) => {
2746
+ if (typeof password !== 'string' || !password)
2747
+ return Promise.reject(new TypeError('Wallet password is required'));
2748
+ return run({ password }, assertCurrent);
2749
+ },
2750
+ resume: (assertCurrent) => run(null, assertCurrent),
2751
+ });
2752
+ }, _KeyringCore_acceptSessionFrom = function _KeyringCore_acceptSessionFrom(source) {
2753
+ if (source.authority !== this.authority ||
2754
+ source.storage !== this.storage ||
2755
+ source.vaultPersistence !== this.vaultPersistence ||
2756
+ __classPrivateFieldGet(source, _KeyringCore_sessionOwnership, "f")?.tracker !== __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f")?.tracker) {
2757
+ throw new Error('Cannot transfer session between different wallet authorities');
2758
+ }
2759
+ if (!source.sessionPassword || !source.sessionMnemonic) {
2760
+ throw new Error('Session data is missing during transfer');
2761
+ }
2762
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_replaceSessionBuffers).call(this, source.sessionPassword, source.sessionMnemonic, __classPrivateFieldGet(source, _KeyringCore_sessionOwnership, "f")?.token);
2763
+ }, _KeyringCore_assertSessionOwner = function _KeyringCore_assertSessionOwner(sourceToken) {
2764
+ const ownership = __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f");
2765
+ if (!ownership)
2766
+ return;
2767
+ if (ownership.destroyed)
2768
+ throw new Error('Wallet keyring was destroyed');
2769
+ const owner = ownership.tracker.current;
2770
+ if (sourceToken === undefined
2771
+ ? owner !== undefined && owner !== ownership.token
2772
+ : owner !== sourceToken) {
2773
+ throw new Error('Wallet session is owned by another keyring');
2774
+ }
2775
+ }, _KeyringCore_captureSessionGuard = function _KeyringCore_captureSessionGuard(message = 'Wallet unlock session changed') {
2776
+ const ownership = __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f");
2777
+ if (!ownership)
2778
+ return undefined;
2779
+ const revision = this.privateRecordSessionRevision;
2780
+ const epoch = ownership.tracker.epoch;
2781
+ return () => {
2782
+ if (this.privateRecordSessionRevision !== revision)
2783
+ throw new Error(message);
2784
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_assertSessionOwner).call(this);
2785
+ if (ownership.tracker.epoch !== epoch)
2786
+ throw new Error(message);
2787
+ };
2788
+ }, _KeyringCore_replaceSessionBuffers = function _KeyringCore_replaceSessionBuffers(sessionPassword, sessionMnemonic, sourceToken) {
2789
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_assertSessionOwner).call(this, sourceToken);
2790
+ this.privateRecordSessionRevision++;
2791
+ // Clear any existing data first
2792
+ if (this.sessionPassword) {
2793
+ this.sessionPassword.clear();
2794
+ }
2795
+ if (this.sessionMnemonic) {
2796
+ this.sessionMnemonic.clear();
2797
+ }
2798
+ // Take ownership of the actual SecureBuffer objects
2799
+ // No copying - these are the original objects
2800
+ this.sessionPassword = sessionPassword;
2801
+ this.sessionMnemonic = sessionMnemonic;
2802
+ const ownership = __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f");
2803
+ if (ownership) {
2804
+ ownership.tracker.current = ownership.token;
2805
+ ownership.tracker.epoch++;
2806
+ }
2807
+ }, _KeyringCore_finishUnlock = function _KeyringCore_finishUnlock() {
2808
+ // Re-initialize syscoin transaction handler if it was cleared on lock.
2809
+ // `lockWallet()` intentionally replaces it with a plain object to drop signer references,
2810
+ // so we must restore the real class instance on unlock.
2811
+ if (!this.syscoinTransaction ||
2812
+ typeof this.syscoinTransaction.getRecommendedFee !== 'function') {
2813
+ this.syscoinTransaction = this.createSyscoinTransactions();
2814
+ }
2815
+ // NOTE: Active account management is now handled by vault state/Redux
2816
+ // No need to explicitly set active account after unlock - it's managed externally
2817
+ const vault = this.getVault();
2818
+ if (vault.activeAccount?.id !== undefined && vault.activeAccount?.type) {
2819
+ // Check if the active account actually exists in the accounts map
2820
+ const accountType = vault.activeAccount.type;
2821
+ const accountId = vault.activeAccount.id;
2822
+ const accountExists = vault.accounts?.[accountType]?.[accountId];
2823
+ if (!accountExists) {
2824
+ console.log(`[KeyringManager] Active account ${accountType}:${accountId} not found in accounts map. This may indicate a migration from old vault format.`);
2825
+ // Signal that accounts need to be created after migration
2826
+ return {
2827
+ canLogin: true,
2828
+ needsAccountCreation: true,
2829
+ };
2830
+ }
2831
+ console.log(`[KeyringManager] Active account ${vault.activeAccount.id} available after unlock`);
2832
+ }
2833
+ return {
2834
+ canLogin: true,
2835
+ };
2836
+ }, _KeyringCore_installVaultSession = function _KeyringCore_installVaultSession(mnemonic, key, assertCurrent) {
2837
+ const ciphertext = crypto_js_1.default.AES.encrypt(mnemonic, key).toString();
2838
+ const password = new SecureBuffer(key);
2839
+ const encryptedMnemonic = new SecureBuffer(ciphertext);
2840
+ try {
2841
+ assertCurrent?.();
2842
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_replaceSessionBuffers).call(this, password, encryptedMnemonic);
2843
+ }
2844
+ catch (error) {
2845
+ password.clear();
2846
+ encryptedMnemonic.clear();
2847
+ throw error;
2848
+ }
2849
+ }, _KeyringCore_recreateSessionFromVault = async function _KeyringCore_recreateSessionFromVault(saltedHashPassword, assertCurrent) {
2850
+ try {
2851
+ // v4 vault is encrypted with the derived session password key (PBKDF2 output)
2852
+ // `saltedHashPassword` is the derived session password key passed by caller.
2853
+ const { mnemonic } = await this.vaultPersistence.getDecryptedVault(saltedHashPassword);
2854
+ assertCurrent?.();
2855
+ if (!mnemonic) {
2856
+ throw new Error('Mnemonic not found in vault');
2857
+ }
2858
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_installVaultSession).call(this, mnemonic, saltedHashPassword, assertCurrent);
2859
+ // Capture the installed revision before returning to the async caller.
2860
+ if (assertCurrent)
2861
+ return __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
2862
+ console.log('[KeyringManager] Session data recreated from vault');
2863
+ return undefined;
2864
+ }
2865
+ catch (error) {
2866
+ console.error('ERROR recreateSessionFromVault', { error });
2867
+ throw error;
2868
+ }
2869
+ };
2605
2870
  //# sourceMappingURL=keyring-core.js.map