@sidhujag/sysweb3-keyring 2.1.11 → 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.
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  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");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _KeyringCore_instances, _KeyringCore_deviceUnlockRetired, _KeyringCore_sessionOwnership, _KeyringCore_createDeviceUnlockControls, _KeyringCore_acceptSessionFrom, _KeyringCore_assertSessionOwner, _KeyringCore_captureSessionGuard, _KeyringCore_replaceSessionBuffers, _KeyringCore_finishUnlock, _KeyringCore_installVaultSession, _KeyringCore_recreateSessionFromVault;
12
+ 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;
13
13
  import ecc from '@bitcoinerlab/secp256k1';
14
14
  import { INetworkType, getNetworkConfig, } from '@sidhujag/sysweb3-network';
15
15
  import { BIP32Factory } from 'bip32';
@@ -31,10 +31,11 @@ const SLH_DSA_SETUP_SECRET_HEX_LENGTH = 64;
31
31
  const SLH_DSA_PARAMETER_SET = 'SLH-DSA-SHA2-128-24';
32
32
  import { currentVaultRefusals } from './current-vault-refusal';
33
33
  import { isHexString } from './ethers-v6';
34
- import { assertHardwareOperationCurrent } from './hardware-transaction-authority';
34
+ import { assertHardwareOperationCurrent, captureHardwareTransactionAuthority, } from './hardware-transaction-authority';
35
35
  import { initialActiveImportedAccountState, initialActiveLedgerAccountState, initialActiveTrezorAccountState, } from './initial-state';
36
36
  import { takeKeyringHostBinding } from './keyring-host';
37
- import { decryptPrivateRecordBytes, encryptPrivateRecordBytes, privateRecordProfileId, } from './private-records';
37
+ import { decryptPrivateRecordBytes, sealPublicRecordCheckpoint, restorePublicRecordCheckpoint, encryptPrivateRecordBytes, privateRecordProfileId, createPrivateRecordAccess, activePrivateRecordDomainSet, privateRecordDomainSet, privateRecordAccessCanTransfer, transferPrivateRecordAccess, } from './private-records';
38
+ import { capturePublicSessionRequest, capturePublicSessionCheckpoint, capturePublicSessionKey, publicSessionStateDigest, } from './public-session';
38
39
  import { getSyscoinSigners } from './signers';
39
40
  import { snapshotEvmHdIndex } from './software-hd-account';
40
41
  import { decryptCurrentVault, VaultAuthenticationError } from './storage-core';
@@ -160,9 +161,16 @@ export class KeyringCore {
160
161
  this.sessionMnemonic = null; // can be a mnemonic or a zprv, can be changed to a zprv when using an imported wallet
161
162
  this.privateRecordSessionRevision = 0;
162
163
  this.sessionLifetime = new AbortController();
164
+ this.publicSession = false;
165
+ this.publicSessionRevision = 0;
166
+ _KeyringCore_recordAccess.set(this, new Set());
167
+ _KeyringCore_recordAccessOwner.set(this, Object.freeze({}));
163
168
  _KeyringCore_deviceUnlockRetired.set(this, false);
164
169
  _KeyringCore_sessionOwnership.set(this, void 0);
165
- this.createSyscoinTransactions = () => new SyscoinTransactionsCore(this.getSigner, this.getReadOnlySigner, this.getAccountsState, this.syscoinHardwareSigners, this.capturePsbtSigningContext);
170
+ this.createSyscoinTransactions = () => new SyscoinTransactionsCore(this.getSigner, this.getReadOnlySigner, this.getAccountsState, this.syscoinHardwareSigners, this.capturePublicWalletContext, this.secretLifetime, () => {
171
+ if (this.secretLifetime === 'operation' && this.isUnlocked())
172
+ throw new Error('Prepare PSBT before the secret operation');
173
+ });
166
174
  this.requireLedgerSigner = () => {
167
175
  if (!this.ledgerSigner)
168
176
  throw createHardwareSignerUnavailableError('Ledger');
@@ -179,23 +187,25 @@ export class KeyringCore {
179
187
  ? KeyringAccountType.Imported
180
188
  : KeyringAccountType.HDAccount;
181
189
  };
182
- this.isUnlocked = () => !!this.sessionPassword && !this.sessionPassword.isCleared();
190
+ this.isUnlocked = () => !!this.sessionPassword &&
191
+ !this.sessionPassword.isCleared() &&
192
+ (this.secretLifetime === 'session' ||
193
+ (!!this.secretOperation?.active && this.secretOperation.working));
194
+ /** Public authenticated account context is independent of decrypted key availability. */
195
+ this.hasPublicSession = () => {
196
+ return this.secretLifetime === 'session'
197
+ ? this.isUnlocked()
198
+ : this.publicSession &&
199
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").tracker.current ===
200
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").token;
201
+ };
183
202
  this.lockWallet = async () => {
184
- this.retireSessionLifetime();
185
- // Clear secure session data
186
- if (this.sessionPassword) {
187
- this.sessionPassword.clear();
188
- this.sessionPassword = null;
189
- }
190
- if (this.sessionMnemonic) {
191
- this.sessionMnemonic.clear();
192
- this.sessionMnemonic = null;
193
- }
194
- const ownership = __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f");
195
- if (ownership.tracker.current === ownership.token) {
196
- ownership.tracker.current = undefined;
197
- ownership.tracker.epoch++;
198
- }
203
+ this.clearRecordAccess();
204
+ this.publicSession = false;
205
+ this.publicSessionRevision++;
206
+ if (this.secretOperation)
207
+ this.secretOperation.active = false;
208
+ this.clearSecretMaterial(false);
199
209
  // Clear transaction handlers that may hold HD signers
200
210
  if (this.syscoinTransaction) {
201
211
  // Replace with empty object to clear references
@@ -223,26 +233,35 @@ export class KeyringCore {
223
233
  };
224
234
  /** Retire hardware before preparing a synchronous, one-use session move. */
225
235
  this.prepareSessionTransfer = async (targetKeyring, assertCurrent) => {
226
- if (!this.isUnlocked()) {
236
+ const publicOnly = this.secretLifetime === 'operation';
237
+ if (publicOnly && this.secretOperation)
238
+ throw new Error('Secret operation is busy');
239
+ if (publicOnly ? !this.hasPublicSession() : !this.isUnlocked()) {
227
240
  throw new Error('Source keyring must be unlocked to transfer session');
228
241
  }
229
242
  // Cross-wallet transfers must fail before either keyring's buffers change.
230
243
  if (!(targetKeyring instanceof KeyringCore) ||
231
244
  targetKeyring.authority !== this.authority ||
245
+ targetKeyring.secretLifetime !== this.secretLifetime ||
232
246
  targetKeyring.storage !== this.storage ||
233
247
  targetKeyring.vaultPersistence !== this.vaultPersistence ||
234
248
  __classPrivateFieldGet(targetKeyring, _KeyringCore_sessionOwnership, "f").tracker !== __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").tracker) {
235
249
  throw new Error('Cannot transfer session between different wallet authorities');
236
250
  }
237
- const sourceGuard = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
251
+ const sourceGuard = publicOnly
252
+ ? __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this)
253
+ : __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
238
254
  const targetRevision = targetKeyring.privateRecordSessionRevision;
239
255
  const ledger = this.ledgerSigner;
240
256
  const trezor = this.trezorSigner;
241
257
  const retirementChecks = [];
242
258
  const checkOwnership = () => {
243
259
  sourceGuard();
244
- if (!this.isUnlocked() ||
245
- !this.sessionMnemonic ||
260
+ if ((publicOnly &&
261
+ (this.secretOperation || targetKeyring.secretOperation)) ||
262
+ (publicOnly
263
+ ? !this.hasPublicSession()
264
+ : !this.isUnlocked() || !this.sessionMnemonic) ||
246
265
  this.ledgerSigner !== ledger ||
247
266
  this.trezorSigner !== trezor ||
248
267
  __classPrivateFieldGet(targetKeyring, _KeyringCore_sessionOwnership, "f").destroyed ||
@@ -294,12 +313,39 @@ export class KeyringCore {
294
313
  check();
295
314
  if (targetKeyring === this)
296
315
  return;
297
- __classPrivateFieldGet(targetKeyring, _KeyringCore_instances, "m", _KeyringCore_acceptSessionFrom).call(targetKeyring, this);
298
- this.retireSessionLifetime();
316
+ if (publicOnly) {
317
+ const sourceRecords = [...__classPrivateFieldGet(this, _KeyringCore_recordAccess, "f")].filter((access) => privateRecordAccessCanTransfer(access, __classPrivateFieldGet(this, _KeyringCore_recordAccessOwner, "f")));
318
+ const retiringRecords = [...__classPrivateFieldGet(this, _KeyringCore_recordAccess, "f")].filter((access) => !sourceRecords.includes(access));
319
+ targetKeyring.clearRecordAccess();
320
+ // No facade is delivered until the synchronous authority move is complete.
321
+ const targetOwner = {};
322
+ const movedRecords = sourceRecords.map((access) => transferPrivateRecordAccess(access, __classPrivateFieldGet(this, _KeyringCore_recordAccessOwner, "f"), __classPrivateFieldGet(targetKeyring, _KeyringCore_recordAccessOwner, "f"), () => {
323
+ if (!targetOwner.guard)
324
+ throw new Error('Record access transfer is incomplete');
325
+ targetOwner.guard();
326
+ }));
327
+ for (const access of retiringRecords)
328
+ access.dispose();
329
+ __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f").clear();
330
+ this.publicSession = false;
331
+ this.publicSessionRevision++;
332
+ targetKeyring.publicSession = true;
333
+ targetKeyring.publicSessionRevision++;
334
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").tracker.current =
335
+ __classPrivateFieldGet(targetKeyring, _KeyringCore_sessionOwnership, "f").token;
336
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").tracker.epoch++;
337
+ targetOwner.guard = __classPrivateFieldGet(targetKeyring, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(targetKeyring);
338
+ for (const access of movedRecords)
339
+ __classPrivateFieldGet(targetKeyring, _KeyringCore_recordAccess, "f").add(access);
340
+ }
341
+ else {
342
+ __classPrivateFieldGet(targetKeyring, _KeyringCore_instances, "m", _KeyringCore_acceptSessionFrom).call(targetKeyring, this);
343
+ this.retireSessionLifetime();
344
+ // Move the original SecureBuffers without copying their contents.
345
+ this.sessionPassword = null;
346
+ this.sessionMnemonic = null;
347
+ }
299
348
  __classPrivateFieldSet(this, _KeyringCore_deviceUnlockRetired, true, "f");
300
- // Move the original SecureBuffers without copying their contents.
301
- this.sessionPassword = null;
302
- this.sessionMnemonic = null;
303
349
  },
304
350
  });
305
351
  };
@@ -350,7 +396,7 @@ export class KeyringCore {
350
396
  };
351
397
  };
352
398
  this.getActiveUtxoAddressAccount = (id, ownedRead) => {
353
- const captured = captureOwnedSyscoinRead(ownedRead, this.getAccountsState, this.capturePsbtSigningContext);
399
+ const captured = captureOwnedSyscoinRead(ownedRead, this.getAccountsState, this.capturePublicWalletContext);
354
400
  if (id !== captured.accountId)
355
401
  throw new Error('UTXO account is not active');
356
402
  const account = this.getVault().accounts[captured.accountType]?.[id];
@@ -729,8 +775,6 @@ export class KeyringCore {
729
775
  const isLedger = activeAccountType === KeyringAccountType.Ledger;
730
776
  const isTrezor = activeAccountType === KeyringAccountType.Trezor;
731
777
  const isHardwareWallet = isTrezor || isLedger;
732
- if (!this.sessionPassword)
733
- throw new Error('Wallet is locked cant proceed with transaction');
734
778
  const activeAccountData = accounts[activeAccountType][activeAccountId];
735
779
  if (!activeAccountData) {
736
780
  throw new Error(`Active account (${activeAccountType}:${activeAccountId}) not found. Account switching may be in progress.`);
@@ -741,11 +785,14 @@ export class KeyringCore {
741
785
  if (isTrezor || activeAccountData.isTrezorWallet)
742
786
  this.requireTrezorSigner();
743
787
  if (isHardwareWallet) {
788
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this)();
744
789
  return {
745
790
  address,
746
791
  decryptedPrivateKey: '',
747
792
  };
748
793
  }
794
+ if (!this.sessionPassword)
795
+ throw new Error('Wallet is locked cant proceed with transaction');
749
796
  if (!xprv) {
750
797
  throw new Error(`Private key not found for account ${activeAccountType}:${activeAccountId}. Account may not be fully initialized.`);
751
798
  }
@@ -796,7 +843,40 @@ export class KeyringCore {
796
843
  throw error;
797
844
  }
798
845
  };
846
+ this.captureEthereumSigningContext = () => {
847
+ const { activeAccount } = this.getVault();
848
+ if (activeAccount.type === KeyringAccountType.Ledger ||
849
+ activeAccount.type === KeyringAccountType.Trezor)
850
+ return this.capturePublicWalletContext();
851
+ return this.capturePsbtSigningContext();
852
+ };
853
+ this.capturePublicWalletContext = () => {
854
+ if (this.secretLifetime === 'session')
855
+ return this.capturePsbtSigningContext();
856
+ const publicGuard = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this);
857
+ const vault = this.getVault();
858
+ const selected = { ...vault.activeAccount };
859
+ const account = vault.accounts[selected.type]?.[selected.id];
860
+ const identity = { address: account?.address, xpub: account?.xpub };
861
+ const network = { ...vault.activeNetwork };
862
+ const check = () => {
863
+ publicGuard();
864
+ const current = this.getVault(), currentAccount = current.accounts[selected.type]?.[selected.id];
865
+ if (current.activeAccount.id !== selected.id ||
866
+ current.activeAccount.type !== selected.type ||
867
+ currentAccount?.address !== identity.address ||
868
+ currentAccount?.xpub !== identity.xpub ||
869
+ current.activeNetwork.kind !== network.kind ||
870
+ current.activeNetwork.chainId !== network.chainId ||
871
+ current.activeNetwork.slip44 !== network.slip44 ||
872
+ current.activeNetwork.url !== network.url)
873
+ throw new Error('Wallet public context changed');
874
+ };
875
+ check();
876
+ return check;
877
+ };
799
878
  this.capturePsbtSigningContext = (bindSessionRevision = true) => {
879
+ this.assertSecretAccess();
800
880
  const session = this.sessionPassword;
801
881
  const revision = this.privateRecordSessionRevision;
802
882
  const vault = this.getVault();
@@ -1127,6 +1207,7 @@ export class KeyringCore {
1127
1207
  };
1128
1208
  // NEW: Separate session initialization from account creation
1129
1209
  this.initializeSession = async (seedPhrase, password) => {
1210
+ this.assertSecretAccess();
1130
1211
  // Validate inputs first
1131
1212
  if (!BIP84.validateMnemonic(seedPhrase)) {
1132
1213
  throw new Error('Invalid Seed');
@@ -1232,12 +1313,41 @@ export class KeyringCore {
1232
1313
  }
1233
1314
  };
1234
1315
  this.initializeWalletSecurely = async (seedPhrase, password) => {
1235
- // Use new separated approach
1236
- await this.initializeSession(seedPhrase, password);
1237
- return await this.createFirstAccount();
1316
+ if (this.secretLifetime === 'session') {
1317
+ await this.initializeSession(seedPhrase, password);
1318
+ return await this.createFirstAccount();
1319
+ }
1320
+ if (this.secretOperation)
1321
+ throw new Error('Secret operation is busy');
1322
+ const operation = {
1323
+ active: true,
1324
+ working: true,
1325
+ assertCurrent: () => {
1326
+ if (!operation.active || this.secretOperation !== operation)
1327
+ throw new Error('Secret operation cancelled');
1328
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_assertSessionOwner).call(this);
1329
+ },
1330
+ };
1331
+ this.secretOperation = operation;
1332
+ try {
1333
+ await this.initializeSession(seedPhrase, password);
1334
+ operation.assertCurrent();
1335
+ const result = await this.createFirstAccount();
1336
+ operation.assertCurrent();
1337
+ this.publicSession = true;
1338
+ this.publicSessionRevision++;
1339
+ return result;
1340
+ }
1341
+ finally {
1342
+ operation.active = false;
1343
+ this.clearSecretMaterial(this.publicSession);
1344
+ if (this.secretOperation === operation)
1345
+ this.secretOperation = undefined;
1346
+ }
1238
1347
  };
1239
1348
  const dependencies = takeKeyringHostBinding(binding);
1240
1349
  this.authority = dependencies.authority;
1350
+ this.secretLifetime = dependencies.secretLifetime;
1241
1351
  __classPrivateFieldSet(this, _KeyringCore_sessionOwnership, {
1242
1352
  tracker: dependencies.sessionOwner,
1243
1353
  token: Symbol(),
@@ -1252,7 +1362,7 @@ export class KeyringCore {
1252
1362
  this.initialTrezorAccountState = initialActiveTrezorAccountState;
1253
1363
  this.initialLedgerAccountState = initialActiveLedgerAccountState;
1254
1364
  this.syscoinTransaction = this.createSyscoinTransactions();
1255
- this.ethereumTransaction = new EthereumTransactions(this.getNetwork, this.getDecryptedPrivateKey, this.getAccountsState, this.ledgerSigner, this.trezorSigner, this.capturePsbtSigningContext, dependencies.captureEvmSubmission);
1365
+ this.ethereumTransaction = new EthereumTransactions(this.getNetwork, this.getDecryptedPrivateKey, this.getAccountsState, this.ledgerSigner, this.trezorSigner, this.captureEthereumSigningContext, dependencies.captureEvmSubmission);
1256
1366
  if (dependencies.deviceUnlock) {
1257
1367
  const { port, receive } = dependencies.deviceUnlock;
1258
1368
  const readVaultPair = this.storage.readDeviceUnlockVaultPair;
@@ -1273,6 +1383,399 @@ export class KeyringCore {
1273
1383
  // No need to explicitly set active account - it's managed externally
1274
1384
  return account;
1275
1385
  }
1386
+ capturePublicSessionGuard() {
1387
+ return __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this);
1388
+ }
1389
+ assertSecretAccess() {
1390
+ if (this.secretLifetime === 'session')
1391
+ return;
1392
+ const operation = this.secretOperation;
1393
+ if (!operation?.active || !operation.working)
1394
+ throw new Error('A secret operation is required');
1395
+ operation.assertCurrent();
1396
+ }
1397
+ clearSecretMaterial(keepPublicSession) {
1398
+ this.retireSessionLifetime();
1399
+ this.sessionPassword?.clear();
1400
+ this.sessionPassword = null;
1401
+ this.sessionMnemonic?.clear();
1402
+ this.sessionMnemonic = null;
1403
+ const ownership = __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f");
1404
+ if (!keepPublicSession && ownership.tracker.current === ownership.token) {
1405
+ ownership.tracker.current = undefined;
1406
+ ownership.tracker.epoch++;
1407
+ }
1408
+ }
1409
+ /** The SDK returns only the work result, never its credential or secret lease. */
1410
+ async withSecretOperation(credential, options, work) {
1411
+ if (this.secretLifetime !== 'operation')
1412
+ throw new Error('Operation secret policy is required');
1413
+ if (this.secretOperation)
1414
+ throw new Error('Secret operation is busy');
1415
+ if (!credential ||
1416
+ typeof credential.withSecret !== 'function' ||
1417
+ typeof work !== 'function' ||
1418
+ !options ||
1419
+ typeof options.assertCurrent !== 'function' ||
1420
+ !options.signal ||
1421
+ typeof options.signal.addEventListener !== 'function')
1422
+ throw new TypeError('Invalid secret operation');
1423
+ let publicGuard = this.hasPublicSession()
1424
+ ? __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this)
1425
+ : undefined;
1426
+ const initialGuard = publicGuard ? undefined : __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
1427
+ const authority = captureHardwareTransactionAuthority(options);
1428
+ const withSecret = credential.withSecret.bind(credential);
1429
+ if (this.secretOperation)
1430
+ throw new Error('Secret operation is busy');
1431
+ const operation = {
1432
+ active: true,
1433
+ working: false,
1434
+ assertCurrent: () => {
1435
+ if (!operation.active ||
1436
+ this.secretOperation !== operation ||
1437
+ authority.signal.aborted)
1438
+ throw new Error('Secret operation cancelled');
1439
+ (publicGuard ?? initialGuard)();
1440
+ authority.assertCurrent();
1441
+ (publicGuard ?? initialGuard)();
1442
+ },
1443
+ };
1444
+ this.secretOperation = operation;
1445
+ let entered = false, completed = false, succeeded = false, createdPublicRevision, result;
1446
+ let pending;
1447
+ const retire = () => {
1448
+ operation.active = false;
1449
+ if (this.secretOperation === operation)
1450
+ this.clearSecretMaterial(this.hasPublicSession());
1451
+ };
1452
+ authority.signal.addEventListener('abort', retire, { once: true });
1453
+ try {
1454
+ operation.assertCurrent();
1455
+ await withSecret(operation.assertCurrent, (secret) => {
1456
+ if (entered) {
1457
+ const rejected = Promise.reject(new Error('Secret credential was already consumed'));
1458
+ void rejected.catch(() => undefined);
1459
+ return rejected;
1460
+ }
1461
+ entered = true;
1462
+ pending = (async () => {
1463
+ try {
1464
+ operation.assertCurrent();
1465
+ if (typeof secret !== 'string' || !secret)
1466
+ throw new TypeError('Invalid secret credential');
1467
+ const authenticated = await this.unlockSession(secret);
1468
+ if (!operation.active ||
1469
+ this.secretOperation !== operation ||
1470
+ authority.signal.aborted)
1471
+ throw new Error('Secret operation cancelled');
1472
+ authority.assertCurrent();
1473
+ if (!authenticated.canLogin)
1474
+ throw new Error('Invalid secret credential');
1475
+ if (!publicGuard) {
1476
+ __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_assertSessionOwner).call(this);
1477
+ if (!this.sessionPassword || this.sessionPassword.isCleared())
1478
+ throw new Error('Secret operation cancelled');
1479
+ this.publicSession = true;
1480
+ createdPublicRevision = ++this.publicSessionRevision;
1481
+ publicGuard = this.capturePublicSessionGuard();
1482
+ }
1483
+ operation.assertCurrent();
1484
+ operation.working = true;
1485
+ result = await work(secret);
1486
+ operation.assertCurrent();
1487
+ completed = true;
1488
+ }
1489
+ finally {
1490
+ operation.working = false;
1491
+ if (this.secretOperation === operation)
1492
+ this.clearSecretMaterial(this.hasPublicSession());
1493
+ }
1494
+ })();
1495
+ void pending.catch(() => undefined);
1496
+ return pending;
1497
+ });
1498
+ if (pending)
1499
+ await pending;
1500
+ operation.assertCurrent();
1501
+ if (!entered || !completed)
1502
+ throw new Error('Secret credential was not consumed');
1503
+ succeeded = true;
1504
+ return result;
1505
+ }
1506
+ finally {
1507
+ if (!succeeded &&
1508
+ createdPublicRevision !== undefined &&
1509
+ this.publicSessionRevision === createdPublicRevision &&
1510
+ this.hasPublicSession()) {
1511
+ this.clearRecordAccess();
1512
+ this.publicSession = false;
1513
+ this.publicSessionRevision++;
1514
+ }
1515
+ retire();
1516
+ authority.signal.removeEventListener('abort', retire);
1517
+ if (this.secretOperation === operation)
1518
+ this.secretOperation = undefined;
1519
+ }
1520
+ }
1521
+ /** Seal a host-validated immutable public manifest from a currently authorized secret session. */
1522
+ async createPublicSessionCheckpoint(input, wrappingKey, assertCurrent) {
1523
+ this.assertSecretAccess();
1524
+ const request = capturePublicSessionRequest(input);
1525
+ const session = this.privateRecordSession();
1526
+ const password = this.sessionPassword;
1527
+ const readPair = this.storage.readDeviceUnlockVaultPair;
1528
+ if (!password || typeof readPair !== 'function') {
1529
+ session.secret.fill(0);
1530
+ throw new Error('Public session requires atomic wallet storage');
1531
+ }
1532
+ const check = () => {
1533
+ this.assertSecretAccess();
1534
+ session.assertCurrent();
1535
+ assertHardwareOperationCurrent(assertCurrent);
1536
+ this.assertSecretAccess();
1537
+ session.assertCurrent();
1538
+ };
1539
+ let wrappingSecret, vaultKey, passwordKey;
1540
+ try {
1541
+ wrappingSecret = capturePublicSessionKey(wrappingKey);
1542
+ check();
1543
+ const pair = await readCurrentVaultPair(readPair.bind(this.storage));
1544
+ check();
1545
+ if (!pair)
1546
+ throw new Error('Public session wallet is unavailable');
1547
+ vaultKey = this.hexToBytes(password.toString());
1548
+ let difference = vaultKey.length ^ wrappingSecret.length;
1549
+ for (let index = 0; index < wrappingSecret.length; index++)
1550
+ difference |= wrappingSecret[index] ^ (vaultKey[index] ?? 0);
1551
+ if (difference === 0)
1552
+ throw new Error('Public session key must be independent of the vault key');
1553
+ // Catch direct reuse of a 32-byte UTF-8 wallet password as well. Random
1554
+ // wrapping bytes normally fail strict decoding and need no extra KDF.
1555
+ // This detects known credential reuse, not arbitrary key provenance.
1556
+ let candidatePassword;
1557
+ try {
1558
+ candidatePassword = new TextDecoder('utf-8', {
1559
+ fatal: true,
1560
+ ignoreBOM: true,
1561
+ }).decode(wrappingSecret);
1562
+ }
1563
+ catch {
1564
+ // A binary public-view key is expected and does not need decoding.
1565
+ }
1566
+ if (candidatePassword !== undefined) {
1567
+ passwordKey = this.hexToBytes(await this.deriveStoredSessionKey(candidatePassword, {
1568
+ salt: pair.salt,
1569
+ }));
1570
+ check();
1571
+ let passwordDifference = vaultKey.length ^ passwordKey.length;
1572
+ for (let index = 0; index < passwordKey.length; index++)
1573
+ passwordDifference |= passwordKey[index] ^ (vaultKey[index] ?? 0);
1574
+ passwordKey.fill(0);
1575
+ passwordKey = undefined;
1576
+ if (passwordDifference === 0)
1577
+ throw new Error('Public session key must be independent of the vault password');
1578
+ }
1579
+ vaultKey.fill(0);
1580
+ vaultKey = undefined;
1581
+ // A checkpoint must never attest a different pair using an old live root.
1582
+ const payload = await decryptCurrentVault(pair.vault, password.toString());
1583
+ check();
1584
+ if (!payload ||
1585
+ typeof payload !== 'object' ||
1586
+ Object.getOwnPropertyDescriptor(payload, 'mnemonic')?.value?.normalize('NFKD') !== new TextDecoder().decode(session.secret))
1587
+ throw new Error('Public session wallet changed');
1588
+ const digest = await publicSessionStateDigest(request.publicState, check);
1589
+ const checkpoint = await sealPublicRecordCheckpoint(session.secret, wrappingSecret, {
1590
+ walletBindingId: request.walletBindingId,
1591
+ pairFingerprint: pair.pairFingerprint,
1592
+ stateDigest: digest,
1593
+ domains: request.recordDomains,
1594
+ }, check);
1595
+ const current = await readCurrentVaultPair(readPair.bind(this.storage));
1596
+ check();
1597
+ if (!current ||
1598
+ current.vault !== pair.vault ||
1599
+ current.salt !== pair.salt ||
1600
+ current.pairFingerprint !== pair.pairFingerprint)
1601
+ throw new Error('Public session wallet changed');
1602
+ return checkpoint;
1603
+ }
1604
+ finally {
1605
+ session.secret.fill(0);
1606
+ wrappingSecret?.fill(0);
1607
+ vaultKey?.fill(0);
1608
+ passwordKey?.fill(0);
1609
+ request.publicState.fill(0);
1610
+ }
1611
+ }
1612
+ /** Restore only authenticated public state and record keys. Never decrypts the vault. */
1613
+ async restorePublicSession(credential, input, options, work) {
1614
+ if (this.secretLifetime !== 'operation')
1615
+ throw new Error('Operation secret policy is required');
1616
+ // Capture cancellation before any caller-controlled property or guard runs.
1617
+ const initialGuard = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
1618
+ initialGuard();
1619
+ if (this.secretOperation || this.hasPublicSession() || this.isUnlocked())
1620
+ throw new Error('Public session restore requires a locked wallet');
1621
+ if (!credential ||
1622
+ typeof credential.withKey !== 'function' ||
1623
+ typeof work !== 'function')
1624
+ throw new TypeError('Invalid public session restore');
1625
+ const readPair = this.storage.readDeviceUnlockVaultPair;
1626
+ if (typeof readPair !== 'function')
1627
+ throw new Error('Public session requires atomic wallet storage');
1628
+ const withKey = credential.withKey.bind(credential);
1629
+ const request = capturePublicSessionRequest(input);
1630
+ const checkpoint = capturePublicSessionCheckpoint(input);
1631
+ const authority = captureHardwareTransactionAuthority(options);
1632
+ initialGuard();
1633
+ // A reentrant host guard may have admitted a different operation.
1634
+ if (this.secretOperation || this.hasPublicSession() || this.isUnlocked())
1635
+ throw new Error('Public session restore requires a locked wallet');
1636
+ let publicGuard;
1637
+ const operation = {
1638
+ active: true,
1639
+ working: false,
1640
+ assertCurrent: () => {
1641
+ if (!operation.active ||
1642
+ this.secretOperation !== operation ||
1643
+ authority.signal.aborted)
1644
+ throw new Error('Public session restore cancelled');
1645
+ (publicGuard ?? initialGuard)();
1646
+ authority.assertCurrent();
1647
+ if (!operation.active ||
1648
+ this.secretOperation !== operation ||
1649
+ authority.signal.aborted)
1650
+ throw new Error('Public session restore cancelled');
1651
+ (publicGuard ?? initialGuard)();
1652
+ },
1653
+ };
1654
+ const check = operation.assertCurrent;
1655
+ let restored;
1656
+ let succeeded = false, entered = false, completed = false;
1657
+ let pending, result;
1658
+ const retire = () => {
1659
+ operation.active = false;
1660
+ if (this.secretOperation !== operation)
1661
+ return;
1662
+ this.clearRecordAccess();
1663
+ this.publicSession = false;
1664
+ this.publicSessionRevision++;
1665
+ this.clearSecretMaterial(false);
1666
+ };
1667
+ this.secretOperation = operation;
1668
+ authority.signal.addEventListener('abort', retire, { once: true });
1669
+ try {
1670
+ check();
1671
+ const pair = await readCurrentVaultPair(readPair.bind(this.storage));
1672
+ check();
1673
+ if (!pair)
1674
+ throw new Error('Public session wallet is unavailable');
1675
+ const checkPair = async () => {
1676
+ const current = await readCurrentVaultPair(readPair.bind(this.storage));
1677
+ check();
1678
+ if (!current ||
1679
+ current.vault !== pair.vault ||
1680
+ current.salt !== pair.salt ||
1681
+ current.pairFingerprint !== pair.pairFingerprint)
1682
+ throw new Error('Public session wallet changed');
1683
+ };
1684
+ const digest = await publicSessionStateDigest(request.publicState, check);
1685
+ await withKey(check, (key) => {
1686
+ if (entered) {
1687
+ const rejected = Promise.reject(new Error('Public session credential was already consumed'));
1688
+ void rejected.catch(() => undefined);
1689
+ return rejected;
1690
+ }
1691
+ entered = true;
1692
+ pending = (async () => {
1693
+ let wrappingSecret;
1694
+ try {
1695
+ wrappingSecret = capturePublicSessionKey(key);
1696
+ check();
1697
+ restored = await restorePublicRecordCheckpoint(wrappingSecret, checkpoint, {
1698
+ walletBindingId: request.walletBindingId,
1699
+ pairFingerprint: pair.pairFingerprint,
1700
+ stateDigest: digest,
1701
+ domains: request.recordDomains,
1702
+ }, () => (publicGuard ? publicGuard() : check()), __classPrivateFieldGet(this, _KeyringCore_recordAccessOwner, "f"));
1703
+ wrappingSecret.fill(0);
1704
+ wrappingSecret = undefined;
1705
+ await checkPair();
1706
+ this.clearRecordAccess();
1707
+ this.publicSession = true;
1708
+ this.publicSessionRevision++;
1709
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").tracker.current =
1710
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").token;
1711
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").tracker.epoch++;
1712
+ publicGuard = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this);
1713
+ __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f").add(restored);
1714
+ check();
1715
+ // Recreate only the public transaction wrapper retired by lockWallet.
1716
+ this.syscoinTransaction = this.createSyscoinTransactions();
1717
+ result = await work(request.publicState);
1718
+ check();
1719
+ await checkPair();
1720
+ completed = true;
1721
+ }
1722
+ finally {
1723
+ wrappingSecret?.fill(0);
1724
+ }
1725
+ })();
1726
+ void pending.catch(() => undefined);
1727
+ return pending;
1728
+ });
1729
+ if (pending)
1730
+ await pending;
1731
+ check();
1732
+ if (!entered || !completed)
1733
+ throw new Error('Public session credential was not consumed');
1734
+ succeeded = true;
1735
+ return result;
1736
+ }
1737
+ finally {
1738
+ authority.signal.removeEventListener('abort', retire);
1739
+ request.publicState.fill(0);
1740
+ if (!succeeded) {
1741
+ restored?.dispose();
1742
+ retire();
1743
+ }
1744
+ else {
1745
+ operation.active = false;
1746
+ this.clearSecretMaterial(true);
1747
+ }
1748
+ if (this.secretOperation === operation)
1749
+ this.secretOperation = undefined;
1750
+ }
1751
+ }
1752
+ async unlock(password) {
1753
+ if (this.secretLifetime === 'session')
1754
+ return this.unlockSession(password);
1755
+ if (this.secretOperation)
1756
+ throw new Error('Secret operation is busy');
1757
+ const operation = {
1758
+ active: true,
1759
+ working: false,
1760
+ assertCurrent: __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this),
1761
+ };
1762
+ this.secretOperation = operation;
1763
+ try {
1764
+ const result = await this.unlockSession(password);
1765
+ if (result.canLogin) {
1766
+ this.clearRecordAccess();
1767
+ this.publicSession = true;
1768
+ this.publicSessionRevision++;
1769
+ }
1770
+ return result;
1771
+ }
1772
+ finally {
1773
+ operation.active = false;
1774
+ this.clearSecretMaterial(this.publicSession);
1775
+ if (this.secretOperation === operation)
1776
+ this.secretOperation = undefined;
1777
+ }
1778
+ }
1276
1779
  /** Capture wallet-session authority without binding an account or network. */
1277
1780
  captureUnlockedSessionGuard() {
1278
1781
  const assertOwner = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
@@ -1294,7 +1797,7 @@ export class KeyringCore {
1294
1797
  * Storage, platform and session restoration failures reject so callers do
1295
1798
  * not charge a failed password attempt for an operational error.
1296
1799
  */
1297
- async unlock(password) {
1800
+ async unlockSession(password) {
1298
1801
  let assertCurrent = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
1299
1802
  let authenticated = false;
1300
1803
  try {
@@ -1513,6 +2016,51 @@ export class KeyringCore {
1513
2016
  throw new Error('Invalid private record root');
1514
2017
  return { secret: new TextEncoder().encode(root), assertCurrent };
1515
2018
  }
2019
+ clearRecordAccess() {
2020
+ for (const access of __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f"))
2021
+ access.dispose();
2022
+ __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f").clear();
2023
+ }
2024
+ /** Public transaction records may outlive a signer without retaining the spending root. */
2025
+ async createPrivateRecordAccess(domains, options) {
2026
+ this.assertSecretAccess();
2027
+ const session = this.privateRecordSession();
2028
+ const publicGuard = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this);
2029
+ for (const previous of __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f")) {
2030
+ if (activePrivateRecordDomainSet(previous, __classPrivateFieldGet(this, _KeyringCore_recordAccessOwner, "f")) === null)
2031
+ __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f").delete(previous);
2032
+ }
2033
+ let access;
2034
+ try {
2035
+ access = await createPrivateRecordAccess(session.secret, domains, options, publicGuard, __classPrivateFieldGet(this, _KeyringCore_recordAccessOwner, "f"));
2036
+ session.assertCurrent();
2037
+ publicGuard();
2038
+ __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f").add(access);
2039
+ return access;
2040
+ }
2041
+ catch (error) {
2042
+ access?.dispose();
2043
+ throw error;
2044
+ }
2045
+ finally {
2046
+ session.secret.fill(0);
2047
+ }
2048
+ }
2049
+ /** Retrieve only an exact domain set already derived under a secret operation. Never unlocks or derives. */
2050
+ getPrivateRecordAccess(domains) {
2051
+ const guard = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_capturePublicSessionGuard).call(this);
2052
+ const domainSet = privateRecordDomainSet(domains);
2053
+ guard();
2054
+ for (const access of __classPrivateFieldGet(this, _KeyringCore_recordAccess, "f")) {
2055
+ if (privateRecordAccessCanTransfer(access, __classPrivateFieldGet(this, _KeyringCore_recordAccessOwner, "f")) &&
2056
+ activePrivateRecordDomainSet(access, __classPrivateFieldGet(this, _KeyringCore_recordAccessOwner, "f")) ===
2057
+ domainSet) {
2058
+ guard();
2059
+ return access;
2060
+ }
2061
+ }
2062
+ throw new Error('Record access is not initialized for these domains');
2063
+ }
1516
2064
  /** Stable local profile identifier; never an account address or a key. */
1517
2065
  async getPrivateRecordProfileId() {
1518
2066
  const session = this.privateRecordSession();
@@ -2475,6 +3023,7 @@ export class KeyringCore {
2475
3023
  }
2476
3024
  // Helper methods for secure buffer operations
2477
3025
  getSessionPasswordString() {
3026
+ this.assertSecretAccess();
2478
3027
  if (!this.sessionPassword || this.sessionPassword.isCleared()) {
2479
3028
  throw new Error('Session password not available');
2480
3029
  }
@@ -2483,6 +3032,7 @@ export class KeyringCore {
2483
3032
  return this.sessionPassword.toString();
2484
3033
  }
2485
3034
  getSessionMnemonicString() {
3035
+ this.assertSecretAccess();
2486
3036
  if (!this.sessionMnemonic || this.sessionMnemonic.isCleared()) {
2487
3037
  throw new Error('Session mnemonic not available');
2488
3038
  }
@@ -2595,7 +3145,7 @@ export class KeyringCore {
2595
3145
  this.syscoinTransaction = {};
2596
3146
  }
2597
3147
  }
2598
- _KeyringCore_deviceUnlockRetired = new WeakMap(), _KeyringCore_sessionOwnership = new WeakMap(), _KeyringCore_instances = new WeakSet(), _KeyringCore_createDeviceUnlockControls = function _KeyringCore_createDeviceUnlockControls(port, readVaultPair) {
3148
+ _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) {
2599
3149
  const readPair = () => readCurrentVaultPair(readVaultPair);
2600
3150
  let pending = false;
2601
3151
  const run = async (request, assertCurrent) => {
@@ -2686,7 +3236,13 @@ _KeyringCore_deviceUnlockRetired = new WeakMap(), _KeyringCore_sessionOwnership
2686
3236
  else {
2687
3237
  if (__classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_finishUnlock).call(this).needsAccountCreation)
2688
3238
  return false;
3239
+ this.clearRecordAccess();
2689
3240
  __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_installVaultSession).call(this, mnemonic, keyHex, check);
3241
+ if (this.secretLifetime === 'operation') {
3242
+ this.publicSession = true;
3243
+ this.publicSessionRevision++;
3244
+ this.clearSecretMaterial(true);
3245
+ }
2690
3246
  }
2691
3247
  return true;
2692
3248
  }
@@ -2704,6 +3260,23 @@ _KeyringCore_deviceUnlockRetired = new WeakMap(), _KeyringCore_sessionOwnership
2704
3260
  },
2705
3261
  resume: (assertCurrent) => run(null, assertCurrent),
2706
3262
  });
3263
+ }, _KeyringCore_capturePublicSessionGuard = function _KeyringCore_capturePublicSessionGuard() {
3264
+ const assertOwner = __classPrivateFieldGet(this, _KeyringCore_instances, "m", _KeyringCore_captureSessionGuard).call(this);
3265
+ const revision = this.publicSessionRevision;
3266
+ const check = () => {
3267
+ if (this.secretLifetime === 'session') {
3268
+ assertOwner();
3269
+ if (!this.sessionPassword || this.sessionPassword.isCleared())
3270
+ throw new Error('Wallet unlock session changed');
3271
+ }
3272
+ else if (!this.publicSession ||
3273
+ this.publicSessionRevision !== revision ||
3274
+ __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").tracker.current !== __classPrivateFieldGet(this, _KeyringCore_sessionOwnership, "f").token) {
3275
+ throw new Error('Wallet public session changed');
3276
+ }
3277
+ };
3278
+ check();
3279
+ return check;
2707
3280
  }, _KeyringCore_acceptSessionFrom = function _KeyringCore_acceptSessionFrom(source) {
2708
3281
  if (source.authority !== this.authority ||
2709
3282
  source.storage !== this.storage ||