@sidhujag/sysweb3-keyring 2.1.10 → 2.1.12

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 (43) hide show
  1. package/cjs/host-keyring.js +3 -1
  2. package/cjs/host-keyring.js.map +1 -1
  3. package/cjs/index.js +3 -1
  4. package/cjs/index.js.map +1 -1
  5. package/cjs/keyring-core.js +609 -36
  6. package/cjs/keyring-core.js.map +1 -1
  7. package/cjs/keyring-host.js +9 -2
  8. package/cjs/keyring-host.js.map +1 -1
  9. package/cjs/private-records.js +291 -9
  10. package/cjs/private-records.js.map +1 -1
  11. package/cjs/public-session.js +72 -0
  12. package/cjs/public-session.js.map +1 -0
  13. package/cjs/software.js +3 -1
  14. package/cjs/software.js.map +1 -1
  15. package/cjs/transactions/syscoin-core.js +159 -96
  16. package/cjs/transactions/syscoin-core.js.map +1 -1
  17. package/cjs/trezor/index.js +57 -42
  18. package/cjs/trezor/index.js.map +1 -1
  19. package/cjs/types.js.map +1 -1
  20. package/esm/keyring-core.js +611 -38
  21. package/esm/keyring-core.js.map +1 -1
  22. package/esm/keyring-host.js +9 -2
  23. package/esm/keyring-host.js.map +1 -1
  24. package/esm/private-records.js +284 -9
  25. package/esm/private-records.js.map +1 -1
  26. package/esm/public-session.js +65 -0
  27. package/esm/public-session.js.map +1 -0
  28. package/esm/transactions/syscoin-core.js +159 -96
  29. package/esm/transactions/syscoin-core.js.map +1 -1
  30. package/esm/trezor/index.js +57 -42
  31. package/esm/trezor/index.js.map +1 -1
  32. package/esm/types.js.map +1 -1
  33. package/package.json +1 -1
  34. package/types/host-keyring.d.ts +4 -0
  35. package/types/index.d.ts +3 -1
  36. package/types/keyring-core.d.ts +37 -5
  37. package/types/keyring-host.d.ts +4 -0
  38. package/types/private-records.d.ts +30 -0
  39. package/types/public-session.d.ts +19 -0
  40. package/types/software.d.ts +5 -0
  41. package/types/transactions/syscoin-core.d.ts +13 -1
  42. package/types/trezor/index.d.ts +0 -1
  43. package/types/types.d.ts +9 -1
@@ -46,7 +46,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
46
46
  var __importDefault = (this && this.__importDefault) || function (mod) {
47
47
  return (mod && mod.__esModule) ? mod : { "default": mod };
48
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;
49
+ var _KeyringCore_instances, _KeyringCore_recordAccess, _KeyringCore_recordAccessOwner, _KeyringCore_deviceUnlockRetired, _KeyringCore_sessionOwnership, _KeyringCore_createDeviceUnlockControls, _KeyringCore_capturePublicSessionGuard, _KeyringCore_acceptSessionFrom, _KeyringCore_assertSessionOwner, _KeyringCore_captureSessionGuard, _KeyringCore_replaceSessionBuffers, _KeyringCore_finishUnlock, _KeyringCore_installVaultSession, _KeyringCore_recreateSessionFromVault;
50
50
  Object.defineProperty(exports, "__esModule", { value: true });
51
51
  exports.KeyringCore = void 0;
52
52
  const secp256k1_1 = __importDefault(require("@bitcoinerlab/secp256k1"));
@@ -74,6 +74,7 @@ const hardware_transaction_authority_1 = require("./hardware-transaction-authori
74
74
  const initial_state_1 = require("./initial-state");
75
75
  const keyring_host_1 = require("./keyring-host");
76
76
  const private_records_1 = require("./private-records");
77
+ const public_session_1 = require("./public-session");
77
78
  const signers_1 = require("./signers");
78
79
  const software_hd_account_1 = require("./software-hd-account");
79
80
  const storage_core_1 = require("./storage-core");
@@ -199,9 +200,16 @@ class KeyringCore {
199
200
  this.sessionMnemonic = null; // can be a mnemonic or a zprv, can be changed to a zprv when using an imported wallet
200
201
  this.privateRecordSessionRevision = 0;
201
202
  this.sessionLifetime = new AbortController();
203
+ this.publicSession = false;
204
+ this.publicSessionRevision = 0;
205
+ _KeyringCore_recordAccess.set(this, new Set());
206
+ _KeyringCore_recordAccessOwner.set(this, Object.freeze({}));
202
207
  _KeyringCore_deviceUnlockRetired.set(this, false);
203
208
  _KeyringCore_sessionOwnership.set(this, void 0);
204
- this.createSyscoinTransactions = () => new syscoin_core_1.SyscoinTransactionsCore(this.getSigner, this.getReadOnlySigner, this.getAccountsState, this.syscoinHardwareSigners, this.capturePsbtSigningContext);
209
+ this.createSyscoinTransactions = () => new syscoin_core_1.SyscoinTransactionsCore(this.getSigner, this.getReadOnlySigner, this.getAccountsState, this.syscoinHardwareSigners, this.capturePublicWalletContext, this.secretLifetime, () => {
210
+ if (this.secretLifetime === 'operation' && this.isUnlocked())
211
+ throw new Error('Prepare PSBT before the secret operation');
212
+ });
205
213
  this.requireLedgerSigner = () => {
206
214
  if (!this.ledgerSigner)
207
215
  throw (0, syscoin_core_1.createHardwareSignerUnavailableError)('Ledger');
@@ -218,23 +226,25 @@ class KeyringCore {
218
226
  ? types_1.KeyringAccountType.Imported
219
227
  : types_1.KeyringAccountType.HDAccount;
220
228
  };
221
- this.isUnlocked = () => !!this.sessionPassword && !this.sessionPassword.isCleared();
229
+ this.isUnlocked = () => !!this.sessionPassword &&
230
+ !this.sessionPassword.isCleared() &&
231
+ (this.secretLifetime === 'session' ||
232
+ (!!this.secretOperation?.active && this.secretOperation.working));
233
+ /** Public authenticated account context is independent of decrypted key availability. */
234
+ this.hasPublicSession = () => {
235
+ return this.secretLifetime === 'session'
236
+ ? this.isUnlocked()
237
+ : this.publicSession &&
238
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").tracker.current ===
239
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").token;
240
+ };
222
241
  this.lockWallet = async () => {
223
- this.retireSessionLifetime();
224
- // Clear secure session data
225
- if (this.sessionPassword) {
226
- this.sessionPassword.clear();
227
- this.sessionPassword = null;
228
- }
229
- if (this.sessionMnemonic) {
230
- this.sessionMnemonic.clear();
231
- this.sessionMnemonic = null;
232
- }
233
- const ownership = __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f");
234
- if (ownership.tracker.current === ownership.token) {
235
- ownership.tracker.current = undefined;
236
- ownership.tracker.epoch++;
237
- }
242
+ this.clearRecordAccess();
243
+ this.publicSession = false;
244
+ this.publicSessionRevision++;
245
+ if (this.secretOperation)
246
+ this.secretOperation.active = false;
247
+ this.clearSecretMaterial(false);
238
248
  // Clear transaction handlers that may hold HD signers
239
249
  if (this.syscoinTransaction) {
240
250
  // Replace with empty object to clear references
@@ -262,26 +272,35 @@ class KeyringCore {
262
272
  };
263
273
  /** Retire hardware before preparing a synchronous, one-use session move. */
264
274
  this.prepareSessionTransfer = async (targetKeyring, assertCurrent) => {
265
- if (!this.isUnlocked()) {
275
+ const publicOnly = this.secretLifetime === 'operation';
276
+ if (publicOnly && this.secretOperation)
277
+ throw new Error('Secret operation is busy');
278
+ if (publicOnly ? !this.hasPublicSession() : !this.isUnlocked()) {
266
279
  throw new Error('Source keyring must be unlocked to transfer session');
267
280
  }
268
281
  // Cross-wallet transfers must fail before either keyring's buffers change.
269
282
  if (!(targetKeyring instanceof KeyringCore) ||
270
283
  targetKeyring.authority !== this.authority ||
284
+ targetKeyring.secretLifetime !== this.secretLifetime ||
271
285
  targetKeyring.storage !== this.storage ||
272
286
  targetKeyring.vaultPersistence !== this.vaultPersistence ||
273
287
  __classPrivateFieldGet(targetKeyring, _KeyringCore_sessionOwnership, "f").tracker !== __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").tracker) {
274
288
  throw new Error('Cannot transfer session between different wallet authorities');
275
289
  }
276
- const sourceGuard = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
290
+ const sourceGuard = publicOnly
291
+ ? __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this)
292
+ : __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
277
293
  const targetRevision = targetKeyring.privateRecordSessionRevision;
278
294
  const ledger = this.ledgerSigner;
279
295
  const trezor = this.trezorSigner;
280
296
  const retirementChecks = [];
281
297
  const checkOwnership = () => {
282
298
  sourceGuard();
283
- if (!this.isUnlocked() ||
284
- !this.sessionMnemonic ||
299
+ if ((publicOnly &&
300
+ (this.secretOperation || targetKeyring.secretOperation)) ||
301
+ (publicOnly
302
+ ? !this.hasPublicSession()
303
+ : !this.isUnlocked() || !this.sessionMnemonic) ||
285
304
  this.ledgerSigner !== ledger ||
286
305
  this.trezorSigner !== trezor ||
287
306
  __classPrivateFieldGet(targetKeyring, _KeyringCore_sessionOwnership, "f").destroyed ||
@@ -333,12 +352,39 @@ class KeyringCore {
333
352
  check();
334
353
  if (targetKeyring === this)
335
354
  return;
336
- __classPrivateFieldGet(targetKeyring, _KeyringCore_instances, "m", _KeyringCore_acceptSessionFrom).call(targetKeyring, this);
337
- this.retireSessionLifetime();
355
+ if (publicOnly) {
356
+ const sourceRecords = [...__classPrivateFieldGet(this, _KeyringCore_recordAccess, "f")].filter((access) => (0, private_records_1.privateRecordAccessCanTransfer)(access, __classPrivateFieldGet(this, _KeyringCore_recordAccessOwner, "f")));
357
+ const retiringRecords = [...__classPrivateFieldGet(this, _KeyringCore_recordAccess, "f")].filter((access) => !sourceRecords.includes(access));
358
+ targetKeyring.clearRecordAccess();
359
+ // No facade is delivered until the synchronous authority move is complete.
360
+ const targetOwner = {};
361
+ const movedRecords = sourceRecords.map((access) => (0, private_records_1.transferPrivateRecordAccess)(access, __classPrivateFieldGet(this, _KeyringCore_recordAccessOwner, "f"), __classPrivateFieldGet(targetKeyring, _KeyringCore_recordAccessOwner, "f"), () => {
362
+ if (!targetOwner.guard)
363
+ throw new Error('Record access transfer is incomplete');
364
+ targetOwner.guard();
365
+ }));
366
+ for (const access of retiringRecords)
367
+ access.dispose();
368
+ __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f").clear();
369
+ this.publicSession = false;
370
+ this.publicSessionRevision++;
371
+ targetKeyring.publicSession = true;
372
+ targetKeyring.publicSessionRevision++;
373
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").tracker.current =
374
+ __classPrivateFieldGet(targetKeyring, _KeyringCore_sessionOwnership, "f").token;
375
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").tracker.epoch++;
376
+ targetOwner.guard = __classPrivateFieldGet(targetKeyring, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(targetKeyring);
377
+ for (const access of movedRecords)
378
+ __classPrivateFieldGet(targetKeyring, _KeyringCore_recordAccess, "f").add(access);
379
+ }
380
+ else {
381
+ __classPrivateFieldGet(targetKeyring, _KeyringCore_instances, "m", _KeyringCore_acceptSessionFrom).call(targetKeyring, this);
382
+ this.retireSessionLifetime();
383
+ // Move the original SecureBuffers without copying their contents.
384
+ this.sessionPassword = null;
385
+ this.sessionMnemonic = null;
386
+ }
338
387
  __classPrivateFieldSet(this, _KeyringCore_deviceUnlockRetired, true, "f");
339
- // Move the original SecureBuffers without copying their contents.
340
- this.sessionPassword = null;
341
- this.sessionMnemonic = null;
342
388
  },
343
389
  });
344
390
  };
@@ -389,7 +435,7 @@ class KeyringCore {
389
435
  };
390
436
  };
391
437
  this.getActiveUtxoAddressAccount = (id, ownedRead) => {
392
- const captured = (0, syscoin_core_1.captureOwnedSyscoinRead)(ownedRead, this.getAccountsState, this.capturePsbtSigningContext);
438
+ const captured = (0, syscoin_core_1.captureOwnedSyscoinRead)(ownedRead, this.getAccountsState, this.capturePublicWalletContext);
393
439
  if (id !== captured.accountId)
394
440
  throw new Error('UTXO account is not active');
395
441
  const account = this.getVault().accounts[captured.accountType]?.[id];
@@ -768,8 +814,6 @@ class KeyringCore {
768
814
  const isLedger = activeAccountType === types_1.KeyringAccountType.Ledger;
769
815
  const isTrezor = activeAccountType === types_1.KeyringAccountType.Trezor;
770
816
  const isHardwareWallet = isTrezor || isLedger;
771
- if (!this.sessionPassword)
772
- throw new Error('Wallet is locked cant proceed with transaction');
773
817
  const activeAccountData = accounts[activeAccountType][activeAccountId];
774
818
  if (!activeAccountData) {
775
819
  throw new Error(`Active account (${activeAccountType}:${activeAccountId}) not found. Account switching may be in progress.`);
@@ -780,11 +824,14 @@ class KeyringCore {
780
824
  if (isTrezor || activeAccountData.isTrezorWallet)
781
825
  this.requireTrezorSigner();
782
826
  if (isHardwareWallet) {
827
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this)();
783
828
  return {
784
829
  address,
785
830
  decryptedPrivateKey: '',
786
831
  };
787
832
  }
833
+ if (!this.sessionPassword)
834
+ throw new Error('Wallet is locked cant proceed with transaction');
788
835
  if (!xprv) {
789
836
  throw new Error(`Private key not found for account ${activeAccountType}:${activeAccountId}. Account may not be fully initialized.`);
790
837
  }
@@ -835,7 +882,40 @@ class KeyringCore {
835
882
  throw error;
836
883
  }
837
884
  };
885
+ this.captureEthereumSigningContext = () => {
886
+ const { activeAccount } = this.getVault();
887
+ if (activeAccount.type === types_1.KeyringAccountType.Ledger ||
888
+ activeAccount.type === types_1.KeyringAccountType.Trezor)
889
+ return this.capturePublicWalletContext();
890
+ return this.capturePsbtSigningContext();
891
+ };
892
+ this.capturePublicWalletContext = () => {
893
+ if (this.secretLifetime === 'session')
894
+ return this.capturePsbtSigningContext();
895
+ const publicGuard = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this);
896
+ const vault = this.getVault();
897
+ const selected = { ...vault.activeAccount };
898
+ const account = vault.accounts[selected.type]?.[selected.id];
899
+ const identity = { address: account?.address, xpub: account?.xpub };
900
+ const network = { ...vault.activeNetwork };
901
+ const check = () => {
902
+ publicGuard();
903
+ const current = this.getVault(), currentAccount = current.accounts[selected.type]?.[selected.id];
904
+ if (current.activeAccount.id !== selected.id ||
905
+ current.activeAccount.type !== selected.type ||
906
+ currentAccount?.address !== identity.address ||
907
+ currentAccount?.xpub !== identity.xpub ||
908
+ current.activeNetwork.kind !== network.kind ||
909
+ current.activeNetwork.chainId !== network.chainId ||
910
+ current.activeNetwork.slip44 !== network.slip44 ||
911
+ current.activeNetwork.url !== network.url)
912
+ throw new Error('Wallet public context changed');
913
+ };
914
+ check();
915
+ return check;
916
+ };
838
917
  this.capturePsbtSigningContext = (bindSessionRevision = true) => {
918
+ this.assertSecretAccess();
839
919
  const session = this.sessionPassword;
840
920
  const revision = this.privateRecordSessionRevision;
841
921
  const vault = this.getVault();
@@ -1166,6 +1246,7 @@ class KeyringCore {
1166
1246
  };
1167
1247
  // NEW: Separate session initialization from account creation
1168
1248
  this.initializeSession = async (seedPhrase, password) => {
1249
+ this.assertSecretAccess();
1169
1250
  // Validate inputs first
1170
1251
  if (!BIP84.validateMnemonic(seedPhrase)) {
1171
1252
  throw new Error('Invalid Seed');
@@ -1271,12 +1352,41 @@ class KeyringCore {
1271
1352
  }
1272
1353
  };
1273
1354
  this.initializeWalletSecurely = async (seedPhrase, password) => {
1274
- // Use new separated approach
1275
- await this.initializeSession(seedPhrase, password);
1276
- return await this.createFirstAccount();
1355
+ if (this.secretLifetime === 'session') {
1356
+ await this.initializeSession(seedPhrase, password);
1357
+ return await this.createFirstAccount();
1358
+ }
1359
+ if (this.secretOperation)
1360
+ throw new Error('Secret operation is busy');
1361
+ const operation = {
1362
+ active: true,
1363
+ working: true,
1364
+ assertCurrent: () => {
1365
+ if (!operation.active || this.secretOperation !== operation)
1366
+ throw new Error('Secret operation cancelled');
1367
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_assertSessionOwner).call(this);
1368
+ },
1369
+ };
1370
+ this.secretOperation = operation;
1371
+ try {
1372
+ await this.initializeSession(seedPhrase, password);
1373
+ operation.assertCurrent();
1374
+ const result = await this.createFirstAccount();
1375
+ operation.assertCurrent();
1376
+ this.publicSession = true;
1377
+ this.publicSessionRevision++;
1378
+ return result;
1379
+ }
1380
+ finally {
1381
+ operation.active = false;
1382
+ this.clearSecretMaterial(this.publicSession);
1383
+ if (this.secretOperation === operation)
1384
+ this.secretOperation = undefined;
1385
+ }
1277
1386
  };
1278
1387
  const dependencies = (0, keyring_host_1.takeKeyringHostBinding)(binding);
1279
1388
  this.authority = dependencies.authority;
1389
+ this.secretLifetime = dependencies.secretLifetime;
1280
1390
  __classPrivateFieldSet(this, _KeyringCore_sessionOwnership, {
1281
1391
  tracker: dependencies.sessionOwner,
1282
1392
  token: Symbol(),
@@ -1291,7 +1401,7 @@ class KeyringCore {
1291
1401
  this.initialTrezorAccountState = initial_state_1.initialActiveTrezorAccountState;
1292
1402
  this.initialLedgerAccountState = initial_state_1.initialActiveLedgerAccountState;
1293
1403
  this.syscoinTransaction = this.createSyscoinTransactions();
1294
- this.ethereumTransaction = new ethereum_1.EthereumTransactions(this.getNetwork, this.getDecryptedPrivateKey, this.getAccountsState, this.ledgerSigner, this.trezorSigner, this.capturePsbtSigningContext, dependencies.captureEvmSubmission);
1404
+ this.ethereumTransaction = new ethereum_1.EthereumTransactions(this.getNetwork, this.getDecryptedPrivateKey, this.getAccountsState, this.ledgerSigner, this.trezorSigner, this.captureEthereumSigningContext, dependencies.captureEvmSubmission);
1295
1405
  if (dependencies.deviceUnlock) {
1296
1406
  const { port, receive } = dependencies.deviceUnlock;
1297
1407
  const readVaultPair = this.storage.readDeviceUnlockVaultPair;
@@ -1312,6 +1422,399 @@ class KeyringCore {
1312
1422
  // No need to explicitly set active account - it's managed externally
1313
1423
  return account;
1314
1424
  }
1425
+ capturePublicSessionGuard() {
1426
+ return __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this);
1427
+ }
1428
+ assertSecretAccess() {
1429
+ if (this.secretLifetime === 'session')
1430
+ return;
1431
+ const operation = this.secretOperation;
1432
+ if (!operation?.active || !operation.working)
1433
+ throw new Error('A secret operation is required');
1434
+ operation.assertCurrent();
1435
+ }
1436
+ clearSecretMaterial(keepPublicSession) {
1437
+ this.retireSessionLifetime();
1438
+ this.sessionPassword?.clear();
1439
+ this.sessionPassword = null;
1440
+ this.sessionMnemonic?.clear();
1441
+ this.sessionMnemonic = null;
1442
+ const ownership = __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f");
1443
+ if (!keepPublicSession && ownership.tracker.current === ownership.token) {
1444
+ ownership.tracker.current = undefined;
1445
+ ownership.tracker.epoch++;
1446
+ }
1447
+ }
1448
+ /** The SDK returns only the work result, never its credential or secret lease. */
1449
+ async withSecretOperation(credential, options, work) {
1450
+ if (this.secretLifetime !== 'operation')
1451
+ throw new Error('Operation secret policy is required');
1452
+ if (this.secretOperation)
1453
+ throw new Error('Secret operation is busy');
1454
+ if (!credential ||
1455
+ typeof credential.withSecret !== 'function' ||
1456
+ typeof work !== 'function' ||
1457
+ !options ||
1458
+ typeof options.assertCurrent !== 'function' ||
1459
+ !options.signal ||
1460
+ typeof options.signal.addEventListener !== 'function')
1461
+ throw new TypeError('Invalid secret operation');
1462
+ let publicGuard = this.hasPublicSession()
1463
+ ? __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this)
1464
+ : undefined;
1465
+ const initialGuard = publicGuard ? undefined : __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
1466
+ const authority = (0, hardware_transaction_authority_1.captureHardwareTransactionAuthority)(options);
1467
+ const withSecret = credential.withSecret.bind(credential);
1468
+ if (this.secretOperation)
1469
+ throw new Error('Secret operation is busy');
1470
+ const operation = {
1471
+ active: true,
1472
+ working: false,
1473
+ assertCurrent: () => {
1474
+ if (!operation.active ||
1475
+ this.secretOperation !== operation ||
1476
+ authority.signal.aborted)
1477
+ throw new Error('Secret operation cancelled');
1478
+ (publicGuard ?? initialGuard)();
1479
+ authority.assertCurrent();
1480
+ (publicGuard ?? initialGuard)();
1481
+ },
1482
+ };
1483
+ this.secretOperation = operation;
1484
+ let entered = false, completed = false, succeeded = false, createdPublicRevision, result;
1485
+ let pending;
1486
+ const retire = () => {
1487
+ operation.active = false;
1488
+ if (this.secretOperation === operation)
1489
+ this.clearSecretMaterial(this.hasPublicSession());
1490
+ };
1491
+ authority.signal.addEventListener('abort', retire, { once: true });
1492
+ try {
1493
+ operation.assertCurrent();
1494
+ await withSecret(operation.assertCurrent, (secret) => {
1495
+ if (entered) {
1496
+ const rejected = Promise.reject(new Error('Secret credential was already consumed'));
1497
+ void rejected.catch(() => undefined);
1498
+ return rejected;
1499
+ }
1500
+ entered = true;
1501
+ pending = (async () => {
1502
+ try {
1503
+ operation.assertCurrent();
1504
+ if (typeof secret !== 'string' || !secret)
1505
+ throw new TypeError('Invalid secret credential');
1506
+ const authenticated = await this.unlockSession(secret);
1507
+ if (!operation.active ||
1508
+ this.secretOperation !== operation ||
1509
+ authority.signal.aborted)
1510
+ throw new Error('Secret operation cancelled');
1511
+ authority.assertCurrent();
1512
+ if (!authenticated.canLogin)
1513
+ throw new Error('Invalid secret credential');
1514
+ if (!publicGuard) {
1515
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_assertSessionOwner).call(this);
1516
+ if (!this.sessionPassword || this.sessionPassword.isCleared())
1517
+ throw new Error('Secret operation cancelled');
1518
+ this.publicSession = true;
1519
+ createdPublicRevision = ++this.publicSessionRevision;
1520
+ publicGuard = this.capturePublicSessionGuard();
1521
+ }
1522
+ operation.assertCurrent();
1523
+ operation.working = true;
1524
+ result = await work(secret);
1525
+ operation.assertCurrent();
1526
+ completed = true;
1527
+ }
1528
+ finally {
1529
+ operation.working = false;
1530
+ if (this.secretOperation === operation)
1531
+ this.clearSecretMaterial(this.hasPublicSession());
1532
+ }
1533
+ })();
1534
+ void pending.catch(() => undefined);
1535
+ return pending;
1536
+ });
1537
+ if (pending)
1538
+ await pending;
1539
+ operation.assertCurrent();
1540
+ if (!entered || !completed)
1541
+ throw new Error('Secret credential was not consumed');
1542
+ succeeded = true;
1543
+ return result;
1544
+ }
1545
+ finally {
1546
+ if (!succeeded &&
1547
+ createdPublicRevision !== undefined &&
1548
+ this.publicSessionRevision === createdPublicRevision &&
1549
+ this.hasPublicSession()) {
1550
+ this.clearRecordAccess();
1551
+ this.publicSession = false;
1552
+ this.publicSessionRevision++;
1553
+ }
1554
+ retire();
1555
+ authority.signal.removeEventListener('abort', retire);
1556
+ if (this.secretOperation === operation)
1557
+ this.secretOperation = undefined;
1558
+ }
1559
+ }
1560
+ /** Seal a host-validated immutable public manifest from a currently authorized secret session. */
1561
+ async createPublicSessionCheckpoint(input, wrappingKey, assertCurrent) {
1562
+ this.assertSecretAccess();
1563
+ const request = (0, public_session_1.capturePublicSessionRequest)(input);
1564
+ const session = this.privateRecordSession();
1565
+ const password = this.sessionPassword;
1566
+ const readPair = this.storage.readDeviceUnlockVaultPair;
1567
+ if (!password || typeof readPair !== 'function') {
1568
+ session.secret.fill(0);
1569
+ throw new Error('Public session requires atomic wallet storage');
1570
+ }
1571
+ const check = () => {
1572
+ this.assertSecretAccess();
1573
+ session.assertCurrent();
1574
+ (0, hardware_transaction_authority_1.assertHardwareOperationCurrent)(assertCurrent);
1575
+ this.assertSecretAccess();
1576
+ session.assertCurrent();
1577
+ };
1578
+ let wrappingSecret, vaultKey, passwordKey;
1579
+ try {
1580
+ wrappingSecret = (0, public_session_1.capturePublicSessionKey)(wrappingKey);
1581
+ check();
1582
+ const pair = await readCurrentVaultPair(readPair.bind(this.storage));
1583
+ check();
1584
+ if (!pair)
1585
+ throw new Error('Public session wallet is unavailable');
1586
+ vaultKey = this.hexToBytes(password.toString());
1587
+ let difference = vaultKey.length ^ wrappingSecret.length;
1588
+ for (let index = 0; index < wrappingSecret.length; index++)
1589
+ difference |= wrappingSecret[index] ^ (vaultKey[index] ?? 0);
1590
+ if (difference === 0)
1591
+ throw new Error('Public session key must be independent of the vault key');
1592
+ // Catch direct reuse of a 32-byte UTF-8 wallet password as well. Random
1593
+ // wrapping bytes normally fail strict decoding and need no extra KDF.
1594
+ // This detects known credential reuse, not arbitrary key provenance.
1595
+ let candidatePassword;
1596
+ try {
1597
+ candidatePassword = new TextDecoder('utf-8', {
1598
+ fatal: true,
1599
+ ignoreBOM: true,
1600
+ }).decode(wrappingSecret);
1601
+ }
1602
+ catch {
1603
+ // A binary public-view key is expected and does not need decoding.
1604
+ }
1605
+ if (candidatePassword !== undefined) {
1606
+ passwordKey = this.hexToBytes(await this.deriveStoredSessionKey(candidatePassword, {
1607
+ salt: pair.salt,
1608
+ }));
1609
+ check();
1610
+ let passwordDifference = vaultKey.length ^ passwordKey.length;
1611
+ for (let index = 0; index < passwordKey.length; index++)
1612
+ passwordDifference |= passwordKey[index] ^ (vaultKey[index] ?? 0);
1613
+ passwordKey.fill(0);
1614
+ passwordKey = undefined;
1615
+ if (passwordDifference === 0)
1616
+ throw new Error('Public session key must be independent of the vault password');
1617
+ }
1618
+ vaultKey.fill(0);
1619
+ vaultKey = undefined;
1620
+ // A checkpoint must never attest a different pair using an old live root.
1621
+ const payload = await (0, storage_core_1.decryptCurrentVault)(pair.vault, password.toString());
1622
+ check();
1623
+ if (!payload ||
1624
+ typeof payload !== 'object' ||
1625
+ Object.getOwnPropertyDescriptor(payload, 'mnemonic')?.value?.normalize('NFKD') !== new TextDecoder().decode(session.secret))
1626
+ throw new Error('Public session wallet changed');
1627
+ const digest = await (0, public_session_1.publicSessionStateDigest)(request.publicState, check);
1628
+ const checkpoint = await (0, private_records_1.sealPublicRecordCheckpoint)(session.secret, wrappingSecret, {
1629
+ walletBindingId: request.walletBindingId,
1630
+ pairFingerprint: pair.pairFingerprint,
1631
+ stateDigest: digest,
1632
+ domains: request.recordDomains,
1633
+ }, check);
1634
+ const current = await readCurrentVaultPair(readPair.bind(this.storage));
1635
+ check();
1636
+ if (!current ||
1637
+ current.vault !== pair.vault ||
1638
+ current.salt !== pair.salt ||
1639
+ current.pairFingerprint !== pair.pairFingerprint)
1640
+ throw new Error('Public session wallet changed');
1641
+ return checkpoint;
1642
+ }
1643
+ finally {
1644
+ session.secret.fill(0);
1645
+ wrappingSecret?.fill(0);
1646
+ vaultKey?.fill(0);
1647
+ passwordKey?.fill(0);
1648
+ request.publicState.fill(0);
1649
+ }
1650
+ }
1651
+ /** Restore only authenticated public state and record keys. Never decrypts the vault. */
1652
+ async restorePublicSession(credential, input, options, work) {
1653
+ if (this.secretLifetime !== 'operation')
1654
+ throw new Error('Operation secret policy is required');
1655
+ // Capture cancellation before any caller-controlled property or guard runs.
1656
+ const initialGuard = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
1657
+ initialGuard();
1658
+ if (this.secretOperation || this.hasPublicSession() || this.isUnlocked())
1659
+ throw new Error('Public session restore requires a locked wallet');
1660
+ if (!credential ||
1661
+ typeof credential.withKey !== 'function' ||
1662
+ typeof work !== 'function')
1663
+ throw new TypeError('Invalid public session restore');
1664
+ const readPair = this.storage.readDeviceUnlockVaultPair;
1665
+ if (typeof readPair !== 'function')
1666
+ throw new Error('Public session requires atomic wallet storage');
1667
+ const withKey = credential.withKey.bind(credential);
1668
+ const request = (0, public_session_1.capturePublicSessionRequest)(input);
1669
+ const checkpoint = (0, public_session_1.capturePublicSessionCheckpoint)(input);
1670
+ const authority = (0, hardware_transaction_authority_1.captureHardwareTransactionAuthority)(options);
1671
+ initialGuard();
1672
+ // A reentrant host guard may have admitted a different operation.
1673
+ if (this.secretOperation || this.hasPublicSession() || this.isUnlocked())
1674
+ throw new Error('Public session restore requires a locked wallet');
1675
+ let publicGuard;
1676
+ const operation = {
1677
+ active: true,
1678
+ working: false,
1679
+ assertCurrent: () => {
1680
+ if (!operation.active ||
1681
+ this.secretOperation !== operation ||
1682
+ authority.signal.aborted)
1683
+ throw new Error('Public session restore cancelled');
1684
+ (publicGuard ?? initialGuard)();
1685
+ authority.assertCurrent();
1686
+ if (!operation.active ||
1687
+ this.secretOperation !== operation ||
1688
+ authority.signal.aborted)
1689
+ throw new Error('Public session restore cancelled');
1690
+ (publicGuard ?? initialGuard)();
1691
+ },
1692
+ };
1693
+ const check = operation.assertCurrent;
1694
+ let restored;
1695
+ let succeeded = false, entered = false, completed = false;
1696
+ let pending, result;
1697
+ const retire = () => {
1698
+ operation.active = false;
1699
+ if (this.secretOperation !== operation)
1700
+ return;
1701
+ this.clearRecordAccess();
1702
+ this.publicSession = false;
1703
+ this.publicSessionRevision++;
1704
+ this.clearSecretMaterial(false);
1705
+ };
1706
+ this.secretOperation = operation;
1707
+ authority.signal.addEventListener('abort', retire, { once: true });
1708
+ try {
1709
+ check();
1710
+ const pair = await readCurrentVaultPair(readPair.bind(this.storage));
1711
+ check();
1712
+ if (!pair)
1713
+ throw new Error('Public session wallet is unavailable');
1714
+ const checkPair = async () => {
1715
+ const current = await readCurrentVaultPair(readPair.bind(this.storage));
1716
+ check();
1717
+ if (!current ||
1718
+ current.vault !== pair.vault ||
1719
+ current.salt !== pair.salt ||
1720
+ current.pairFingerprint !== pair.pairFingerprint)
1721
+ throw new Error('Public session wallet changed');
1722
+ };
1723
+ const digest = await (0, public_session_1.publicSessionStateDigest)(request.publicState, check);
1724
+ await withKey(check, (key) => {
1725
+ if (entered) {
1726
+ const rejected = Promise.reject(new Error('Public session credential was already consumed'));
1727
+ void rejected.catch(() => undefined);
1728
+ return rejected;
1729
+ }
1730
+ entered = true;
1731
+ pending = (async () => {
1732
+ let wrappingSecret;
1733
+ try {
1734
+ wrappingSecret = (0, public_session_1.capturePublicSessionKey)(key);
1735
+ check();
1736
+ restored = await (0, private_records_1.restorePublicRecordCheckpoint)(wrappingSecret, checkpoint, {
1737
+ walletBindingId: request.walletBindingId,
1738
+ pairFingerprint: pair.pairFingerprint,
1739
+ stateDigest: digest,
1740
+ domains: request.recordDomains,
1741
+ }, () => (publicGuard ? publicGuard() : check()), __classPrivateFieldGet(this, _KeyringCore_recordAccessOwner, "f"));
1742
+ wrappingSecret.fill(0);
1743
+ wrappingSecret = undefined;
1744
+ await checkPair();
1745
+ this.clearRecordAccess();
1746
+ this.publicSession = true;
1747
+ this.publicSessionRevision++;
1748
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").tracker.current =
1749
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").token;
1750
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").tracker.epoch++;
1751
+ publicGuard = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this);
1752
+ __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f").add(restored);
1753
+ check();
1754
+ // Recreate only the public transaction wrapper retired by lockWallet.
1755
+ this.syscoinTransaction = this.createSyscoinTransactions();
1756
+ result = await work(request.publicState);
1757
+ check();
1758
+ await checkPair();
1759
+ completed = true;
1760
+ }
1761
+ finally {
1762
+ wrappingSecret?.fill(0);
1763
+ }
1764
+ })();
1765
+ void pending.catch(() => undefined);
1766
+ return pending;
1767
+ });
1768
+ if (pending)
1769
+ await pending;
1770
+ check();
1771
+ if (!entered || !completed)
1772
+ throw new Error('Public session credential was not consumed');
1773
+ succeeded = true;
1774
+ return result;
1775
+ }
1776
+ finally {
1777
+ authority.signal.removeEventListener('abort', retire);
1778
+ request.publicState.fill(0);
1779
+ if (!succeeded) {
1780
+ restored?.dispose();
1781
+ retire();
1782
+ }
1783
+ else {
1784
+ operation.active = false;
1785
+ this.clearSecretMaterial(true);
1786
+ }
1787
+ if (this.secretOperation === operation)
1788
+ this.secretOperation = undefined;
1789
+ }
1790
+ }
1791
+ async unlock(password) {
1792
+ if (this.secretLifetime === 'session')
1793
+ return this.unlockSession(password);
1794
+ if (this.secretOperation)
1795
+ throw new Error('Secret operation is busy');
1796
+ const operation = {
1797
+ active: true,
1798
+ working: false,
1799
+ assertCurrent: __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this),
1800
+ };
1801
+ this.secretOperation = operation;
1802
+ try {
1803
+ const result = await this.unlockSession(password);
1804
+ if (result.canLogin) {
1805
+ this.clearRecordAccess();
1806
+ this.publicSession = true;
1807
+ this.publicSessionRevision++;
1808
+ }
1809
+ return result;
1810
+ }
1811
+ finally {
1812
+ operation.active = false;
1813
+ this.clearSecretMaterial(this.publicSession);
1814
+ if (this.secretOperation === operation)
1815
+ this.secretOperation = undefined;
1816
+ }
1817
+ }
1315
1818
  /** Capture wallet-session authority without binding an account or network. */
1316
1819
  captureUnlockedSessionGuard() {
1317
1820
  const assertOwner = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
@@ -1333,7 +1836,7 @@ class KeyringCore {
1333
1836
  * Storage, platform and session restoration failures reject so callers do
1334
1837
  * not charge a failed password attempt for an operational error.
1335
1838
  */
1336
- async unlock(password) {
1839
+ async unlockSession(password) {
1337
1840
  let assertCurrent = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
1338
1841
  let authenticated = false;
1339
1842
  try {
@@ -1552,6 +2055,51 @@ class KeyringCore {
1552
2055
  throw new Error('Invalid private record root');
1553
2056
  return { secret: new TextEncoder().encode(root), assertCurrent };
1554
2057
  }
2058
+ clearRecordAccess() {
2059
+ for (const access of __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f"))
2060
+ access.dispose();
2061
+ __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f").clear();
2062
+ }
2063
+ /** Public transaction records may outlive a signer without retaining the spending root. */
2064
+ async createPrivateRecordAccess(domains, options) {
2065
+ this.assertSecretAccess();
2066
+ const session = this.privateRecordSession();
2067
+ const publicGuard = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this);
2068
+ for (const previous of __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f")) {
2069
+ if ((0, private_records_1.activePrivateRecordDomainSet)(previous, __classPrivateFieldGet(this, _KeyringCore_recordAccessOwner, "f")) === null)
2070
+ __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f").delete(previous);
2071
+ }
2072
+ let access;
2073
+ try {
2074
+ access = await (0, private_records_1.createPrivateRecordAccess)(session.secret, domains, options, publicGuard, __classPrivateFieldGet(this, _KeyringCore_recordAccessOwner, "f"));
2075
+ session.assertCurrent();
2076
+ publicGuard();
2077
+ __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f").add(access);
2078
+ return access;
2079
+ }
2080
+ catch (error) {
2081
+ access?.dispose();
2082
+ throw error;
2083
+ }
2084
+ finally {
2085
+ session.secret.fill(0);
2086
+ }
2087
+ }
2088
+ /** Retrieve only an exact domain set already derived under a secret operation. Never unlocks or derives. */
2089
+ getPrivateRecordAccess(domains) {
2090
+ const guard = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this);
2091
+ const domainSet = (0, private_records_1.privateRecordDomainSet)(domains);
2092
+ guard();
2093
+ for (const access of __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f")) {
2094
+ if ((0, private_records_1.privateRecordAccessCanTransfer)(access, __classPrivateFieldGet(this, _KeyringCore_recordAccessOwner, "f")) &&
2095
+ (0, private_records_1.activePrivateRecordDomainSet)(access, __classPrivateFieldGet(this, _KeyringCore_recordAccessOwner, "f")) ===
2096
+ domainSet) {
2097
+ guard();
2098
+ return access;
2099
+ }
2100
+ }
2101
+ throw new Error('Record access is not initialized for these domains');
2102
+ }
1555
2103
  /** Stable local profile identifier; never an account address or a key. */
1556
2104
  async getPrivateRecordProfileId() {
1557
2105
  const session = this.privateRecordSession();
@@ -2514,6 +3062,7 @@ class KeyringCore {
2514
3062
  }
2515
3063
  // Helper methods for secure buffer operations
2516
3064
  getSessionPasswordString() {
3065
+ this.assertSecretAccess();
2517
3066
  if (!this.sessionPassword || this.sessionPassword.isCleared()) {
2518
3067
  throw new Error('Session password not available');
2519
3068
  }
@@ -2522,6 +3071,7 @@ class KeyringCore {
2522
3071
  return this.sessionPassword.toString();
2523
3072
  }
2524
3073
  getSessionMnemonicString() {
3074
+ this.assertSecretAccess();
2525
3075
  if (!this.sessionMnemonic || this.sessionMnemonic.isCleared()) {
2526
3076
  throw new Error('Session mnemonic not available');
2527
3077
  }
@@ -2635,7 +3185,7 @@ class KeyringCore {
2635
3185
  }
2636
3186
  }
2637
3187
  exports.KeyringCore = KeyringCore;
2638
- _KeyringCore_deviceUnlockRetired = new WeakMap(), _KeyringCore_sessionOwnership = new WeakMap(), _KeyringCore_instances = new WeakSet(), _KeyringCore_createDeviceUnlockControls = function _KeyringCore_createDeviceUnlockControls(port, readVaultPair) {
3188
+ _KeyringCore_recordAccess = new WeakMap(), _KeyringCore_recordAccessOwner = new WeakMap(), _KeyringCore_deviceUnlockRetired = new WeakMap(), _KeyringCore_sessionOwnership = new WeakMap(), _KeyringCore_instances = new WeakSet(), _KeyringCore_createDeviceUnlockControls = function _KeyringCore_createDeviceUnlockControls(port, readVaultPair) {
2639
3189
  const readPair = () => readCurrentVaultPair(readVaultPair);
2640
3190
  let pending = false;
2641
3191
  const run = async (request, assertCurrent) => {
@@ -2726,7 +3276,13 @@ _KeyringCore_deviceUnlockRetired = new WeakMap(), _KeyringCore_sessionOwnership
2726
3276
  else {
2727
3277
  if (__classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_finishUnlock).call(this).needsAccountCreation)
2728
3278
  return false;
3279
+ this.clearRecordAccess();
2729
3280
  __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_installVaultSession).call(this, mnemonic, keyHex, check);
3281
+ if (this.secretLifetime === 'operation') {
3282
+ this.publicSession = true;
3283
+ this.publicSessionRevision++;
3284
+ this.clearSecretMaterial(true);
3285
+ }
2730
3286
  }
2731
3287
  return true;
2732
3288
  }
@@ -2744,6 +3300,23 @@ _KeyringCore_deviceUnlockRetired = new WeakMap(), _KeyringCore_sessionOwnership
2744
3300
  },
2745
3301
  resume: (assertCurrent) => run(null, assertCurrent),
2746
3302
  });
3303
+ }, _KeyringCore_capturePublicSessionGuard = function _KeyringCore_capturePublicSessionGuard() {
3304
+ const assertOwner = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
3305
+ const revision = this.publicSessionRevision;
3306
+ const check = () => {
3307
+ if (this.secretLifetime === 'session') {
3308
+ assertOwner();
3309
+ if (!this.sessionPassword || this.sessionPassword.isCleared())
3310
+ throw new Error('Wallet unlock session changed');
3311
+ }
3312
+ else if (!this.publicSession ||
3313
+ this.publicSessionRevision !== revision ||
3314
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").tracker.current !== __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").token) {
3315
+ throw new Error('Wallet public session changed');
3316
+ }
3317
+ };
3318
+ check();
3319
+ return check;
2747
3320
  }, _KeyringCore_acceptSessionFrom = function _KeyringCore_acceptSessionFrom(source) {
2748
3321
  if (source.authority !== this.authority ||
2749
3322
  source.storage !== this.storage ||