@sidhujag/sysweb3-keyring 2.1.2 → 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.
- package/README.md +8 -0
- package/cjs/current-vault-refusal.js +6 -0
- package/cjs/current-vault-refusal.js.map +1 -0
- package/cjs/evm-fees.js +29 -1
- package/cjs/evm-fees.js.map +1 -1
- package/cjs/keyring-core.js +349 -84
- package/cjs/keyring-core.js.map +1 -1
- package/cjs/keyring-manager.js +100 -4
- package/cjs/keyring-manager.js.map +1 -1
- package/cjs/software.js +14 -1
- package/cjs/software.js.map +1 -1
- package/cjs/storage-core.js +44 -4
- package/cjs/storage-core.js.map +1 -1
- package/cjs/transactions/ethereum.js +52 -65
- package/cjs/transactions/ethereum.js.map +1 -1
- package/cjs/transactions/evm-cancellation.js +43 -0
- package/cjs/transactions/evm-cancellation.js.map +1 -0
- package/esm/ledger/dmk-host.js +31 -0
- package/esm/ledger/dmk-host.js.map +1 -0
- package/esm/ledger/dmk-selected-host.js +6 -0
- package/esm/ledger/dmk-selected-host.js.map +1 -0
- package/esm/ledger/dmk-session.js +153 -34
- package/esm/ledger/dmk-session.js.map +1 -1
- package/esm/ledger-host.js +3 -27
- package/esm/ledger-host.js.map +1 -1
- package/package.json +13 -1
- package/types/current-vault-refusal.d.ts +2 -0
- package/types/evm-fees.d.ts +25 -0
- package/types/keyring-core.d.ts +36 -3
- package/types/keyring-manager.d.ts +15 -3
- package/types/ledger/dmk-host.d.ts +7 -0
- package/types/ledger/dmk-selected-host.d.ts +7 -0
- package/types/ledger/dmk-session.d.ts +10 -2
- package/types/ledger-host.d.ts +6 -2
- package/types/software.d.ts +4 -3
- package/types/storage-core.d.ts +3 -1
- package/types/storage.d.ts +1 -1
- package/types/transactions/evm-cancellation.d.ts +10 -0
package/cjs/keyring-core.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
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
|
-
|
|
1420
|
-
|
|
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
|