@onekeyfe/hwk-keystone-adapter 1.2.2-alpha.100 → 1.2.3-alpha.1
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/dist/index.d.mts +176 -122
- package/dist/index.d.ts +176 -122
- package/dist/index.js +1446 -379
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1348 -272
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -30,17 +30,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
KEYSTONE_WALLET_CONNECT_ID_PREFIX: () => KEYSTONE_WALLET_CONNECT_ID_PREFIX,
|
|
34
|
+
KEYSTONE_WALLET_ID_PATH: () => KEYSTONE_WALLET_ID_PATH,
|
|
33
35
|
KeystoneAdapter: () => KeystoneAdapter,
|
|
34
36
|
KeystoneUrEngine: () => KeystoneUrEngine,
|
|
35
|
-
QR_CONNECT_ID_PREFIX: () => QR_CONNECT_ID_PREFIX,
|
|
36
37
|
accountKey: () => accountKey,
|
|
37
38
|
btcScriptTypeFromPath: () => btcScriptTypeFromPath,
|
|
38
39
|
createDeviceRecord: () => createDeviceRecord,
|
|
40
|
+
deriveKeystoneWalletId: () => deriveKeystoneWalletId,
|
|
39
41
|
normalizePath: () => normalizePath,
|
|
40
42
|
placeholderDeviceInfo: () => placeholderDeviceInfo,
|
|
41
|
-
qrConnectId: () => qrConnectId,
|
|
42
43
|
splitAccountPath: () => splitAccountPath,
|
|
43
|
-
toDeviceInfo: () => toDeviceInfo
|
|
44
|
+
toDeviceInfo: () => toDeviceInfo,
|
|
45
|
+
walletConnectId: () => walletConnectId
|
|
44
46
|
});
|
|
45
47
|
module.exports = __toCommonJS(index_exports);
|
|
46
48
|
|
|
@@ -51,6 +53,7 @@ var import_bs58check = __toESM(require("bs58check"));
|
|
|
51
53
|
var bitcoin = __toESM(require("bitcoinjs-lib"));
|
|
52
54
|
var import_hdkey = __toESM(require("hdkey"));
|
|
53
55
|
var import_uuid = require("uuid");
|
|
56
|
+
var import_hwk_adapter_core = require("@onekeyfe/hwk-adapter-core");
|
|
54
57
|
|
|
55
58
|
// src/urEngine/TronSignRequest.ts
|
|
56
59
|
var import_bc_ur_registry = require("@keystonehq/bc-ur-registry");
|
|
@@ -69,13 +72,13 @@ var TronSignRequest = class _TronSignRequest extends import_bc_ur_registry.Regis
|
|
|
69
72
|
if (this.requestId) {
|
|
70
73
|
map[1 /* requestId */] = new import_bc_ur_registry.DataItem(this.requestId, import_bc_ur_registry.RegistryTypes.UUID.getTag());
|
|
71
74
|
}
|
|
72
|
-
if (this.address) map[
|
|
73
|
-
if (this.origin) map[
|
|
75
|
+
if (this.address) map[5 /* address */] = this.address;
|
|
76
|
+
if (this.origin) map[6 /* origin */] = this.origin;
|
|
74
77
|
map[2 /* signData */] = this.signData;
|
|
75
|
-
map[
|
|
78
|
+
map[3 /* dataType */] = this.signType;
|
|
76
79
|
const keyPath = this.derivationPath.toDataItem();
|
|
77
80
|
keyPath.setTag(this.derivationPath.getRegistryType().getTag());
|
|
78
|
-
map[
|
|
81
|
+
map[4 /* derivationPath */] = keyPath;
|
|
79
82
|
return new import_bc_ur_registry.DataItem(map);
|
|
80
83
|
};
|
|
81
84
|
this.requestId = args.requestId;
|
|
@@ -88,12 +91,12 @@ var TronSignRequest = class _TronSignRequest extends import_bc_ur_registry.Regis
|
|
|
88
91
|
static fromDataItem(dataItem) {
|
|
89
92
|
const map = dataItem.getData();
|
|
90
93
|
const signData = map[2 /* signData */];
|
|
91
|
-
const derivationPath = import_bc_ur_registry.CryptoKeypath.fromDataItem(map[
|
|
92
|
-
const address = map[
|
|
94
|
+
const derivationPath = import_bc_ur_registry.CryptoKeypath.fromDataItem(map[4 /* derivationPath */]);
|
|
95
|
+
const address = map[5 /* address */];
|
|
93
96
|
const requestIdItem = map[1 /* requestId */];
|
|
94
97
|
const requestId = requestIdItem?.getData();
|
|
95
|
-
const origin = map[
|
|
96
|
-
const signType = map[
|
|
98
|
+
const origin = map[6 /* origin */];
|
|
99
|
+
const signType = map[3 /* dataType */];
|
|
97
100
|
return new _TronSignRequest({ requestId, signData, derivationPath, address, origin, signType });
|
|
98
101
|
}
|
|
99
102
|
static fromCBOR(cborPayload) {
|
|
@@ -169,6 +172,13 @@ function fromSdkUr(ur) {
|
|
|
169
172
|
function splitSignature65(hex) {
|
|
170
173
|
return { r: hex.slice(0, 64), s: hex.slice(64, 128), v: hex.slice(128) };
|
|
171
174
|
}
|
|
175
|
+
function requireBip32MasterFingerprint(value) {
|
|
176
|
+
const fingerprint = (0, import_hwk_adapter_core.parseBip32MasterFingerprint)(value);
|
|
177
|
+
if (!fingerprint) {
|
|
178
|
+
throw new Error("Keystone master fingerprint must be exactly 4 bytes (8 hex characters)");
|
|
179
|
+
}
|
|
180
|
+
return fingerprint;
|
|
181
|
+
}
|
|
172
182
|
var KeystoneUrEngine = class {
|
|
173
183
|
constructor(origin = "OneKey") {
|
|
174
184
|
this.sdk = new import_keystone_sdk.KeystoneSDK({ origin });
|
|
@@ -178,7 +188,7 @@ var KeystoneUrEngine = class {
|
|
|
178
188
|
parseMultiAccounts(ur) {
|
|
179
189
|
const parsed = this.sdk.parseMultiAccounts(toSdkUr(ur));
|
|
180
190
|
return {
|
|
181
|
-
masterFingerprint: parsed.masterFingerprint
|
|
191
|
+
masterFingerprint: requireBip32MasterFingerprint(parsed.masterFingerprint),
|
|
182
192
|
device: parsed.device,
|
|
183
193
|
deviceId: parsed.deviceId,
|
|
184
194
|
deviceVersion: parsed.deviceVersion,
|
|
@@ -188,7 +198,7 @@ var KeystoneUrEngine = class {
|
|
|
188
198
|
path: key.path,
|
|
189
199
|
publicKey: key.publicKey,
|
|
190
200
|
extendedPublicKey: key.extendedPublicKey,
|
|
191
|
-
xfp: key.xfp,
|
|
201
|
+
xfp: key.xfp ? requireBip32MasterFingerprint(key.xfp) : void 0,
|
|
192
202
|
name: key.name
|
|
193
203
|
})
|
|
194
204
|
)
|
|
@@ -201,7 +211,7 @@ var KeystoneUrEngine = class {
|
|
|
201
211
|
path: key.path,
|
|
202
212
|
publicKey: key.publicKey,
|
|
203
213
|
extendedPublicKey: key.extendedPublicKey,
|
|
204
|
-
xfp: key.xfp,
|
|
214
|
+
xfp: key.xfp ? requireBip32MasterFingerprint(key.xfp) : void 0,
|
|
205
215
|
name: key.name
|
|
206
216
|
};
|
|
207
217
|
}
|
|
@@ -250,7 +260,10 @@ var KeystoneUrEngine = class {
|
|
|
250
260
|
if (!account.xfp) {
|
|
251
261
|
throw new Error("Keystone crypto-hdkey response is missing its source fingerprint");
|
|
252
262
|
}
|
|
253
|
-
return {
|
|
263
|
+
return {
|
|
264
|
+
masterFingerprint: requireBip32MasterFingerprint(account.xfp),
|
|
265
|
+
accounts: [account]
|
|
266
|
+
};
|
|
254
267
|
}
|
|
255
268
|
return this.parseMultiAccounts(ur);
|
|
256
269
|
}
|
|
@@ -302,12 +315,18 @@ var KeystoneUrEngine = class {
|
|
|
302
315
|
deriveBtcAddressFromXpub(xpub, relativeDerivePath, scriptType) {
|
|
303
316
|
const node = import_hdkey.default.fromExtendedKey(xpub).derive(`m/${relativeDerivePath.replace(/^m\//i, "")}`);
|
|
304
317
|
if (!node.publicKey) throw new Error("HDKey derivation did not produce a public key");
|
|
305
|
-
|
|
318
|
+
return this.deriveBtcAddressFromPublicKey(
|
|
319
|
+
Buffer.from(node.publicKey).toString("hex"),
|
|
320
|
+
scriptType
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
deriveBtcAddressFromPublicKey(publicKeyHex, scriptType) {
|
|
324
|
+
const pubkey = Buffer.from(stripHexPrefix(publicKeyHex), "hex");
|
|
306
325
|
const network = bitcoin.networks.bitcoin;
|
|
307
326
|
switch (scriptType) {
|
|
308
327
|
case "p2pkh": {
|
|
309
328
|
const { address } = bitcoin.payments.p2pkh({ pubkey, network });
|
|
310
|
-
if (!address) throw new Error("Failed to derive a P2PKH address from this
|
|
329
|
+
if (!address) throw new Error("Failed to derive a P2PKH address from this public key");
|
|
311
330
|
return address;
|
|
312
331
|
}
|
|
313
332
|
case "p2sh-p2wpkh": {
|
|
@@ -315,12 +334,13 @@ var KeystoneUrEngine = class {
|
|
|
315
334
|
redeem: bitcoin.payments.p2wpkh({ pubkey, network }),
|
|
316
335
|
network
|
|
317
336
|
});
|
|
318
|
-
if (!address)
|
|
337
|
+
if (!address)
|
|
338
|
+
throw new Error("Failed to derive a P2SH-P2WPKH address from this public key");
|
|
319
339
|
return address;
|
|
320
340
|
}
|
|
321
341
|
case "p2wpkh": {
|
|
322
342
|
const { address } = bitcoin.payments.p2wpkh({ pubkey, network });
|
|
323
|
-
if (!address) throw new Error("Failed to derive a P2WPKH address from this
|
|
343
|
+
if (!address) throw new Error("Failed to derive a P2WPKH address from this public key");
|
|
324
344
|
return address;
|
|
325
345
|
}
|
|
326
346
|
case "p2tr":
|
|
@@ -379,21 +399,14 @@ var KeystoneUrEngine = class {
|
|
|
379
399
|
}
|
|
380
400
|
// --- TRON ---
|
|
381
401
|
/**
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
* why: it's a different (gzip/protobuf) protocol with response semantics
|
|
385
|
-
* this package has no way to verify, whereas `TronSignRequest`/
|
|
386
|
-
* `TronSignature` are a direct port of OneKey's own already-proven
|
|
387
|
-
* production QR-wallet TRON implementation (a plain CBOR-native
|
|
388
|
-
* sign-request/signature pair, same shape as eth/sol). The device decodes
|
|
389
|
-
* `rawTxHex` itself — no client-side contract-type pre-parsing or
|
|
390
|
-
* `tokenInfo` needed, unlike the public SDK's module.
|
|
402
|
+
* Keystone's native tron-sign-request/tron-signature pair (bare signature).
|
|
403
|
+
* `sdk.tron` from keystone-sdk is the older protobuf envelope; not used.
|
|
391
404
|
*/
|
|
392
405
|
buildTronSignRequest(input) {
|
|
393
406
|
const request = new TronSignRequest({
|
|
394
407
|
requestId: Buffer.from((0, import_uuid.parse)(input.requestId)),
|
|
395
408
|
signData: Buffer.from(stripHexPrefix(input.rawTxHex), "hex"),
|
|
396
|
-
signType:
|
|
409
|
+
signType: input.signType ?? 1 /* Transaction */,
|
|
397
410
|
derivationPath: TronSignRequest.parsePath(input.path, input.xfp),
|
|
398
411
|
origin: input.origin
|
|
399
412
|
});
|
|
@@ -430,12 +443,35 @@ var KeystoneUrEngine = class {
|
|
|
430
443
|
]);
|
|
431
444
|
return import_bs58check.default.encode(addressBytes);
|
|
432
445
|
}
|
|
446
|
+
/**
|
|
447
|
+
* Split an account-level xpub back into the BIP-32 fields a host needs to
|
|
448
|
+
* treat it as a real extended key (`BtcPublicKey`). Everything here is
|
|
449
|
+
* carried inside the xpub's own serialization — depth, the PARENT key
|
|
450
|
+
* fingerprint (not the seed's master fingerprint), the chain code and the
|
|
451
|
+
* compressed public key — so this is a pure decode with no device round
|
|
452
|
+
* trip; the xpub itself was already device-verified when it was synced.
|
|
453
|
+
*/
|
|
454
|
+
parseXpubMeta(xpub) {
|
|
455
|
+
const raw = Buffer.from(import_bs58check.default.decode(xpub));
|
|
456
|
+
if (raw.length !== 78) {
|
|
457
|
+
throw new Error(`Keystone xpub did not decode to a 78-byte BIP-32 key (got ${raw.length})`);
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
publicKey: raw.subarray(45, 78).toString("hex"),
|
|
461
|
+
chainCode: raw.subarray(13, 45).toString("hex"),
|
|
462
|
+
depth: raw.readUInt8(4),
|
|
463
|
+
parentFingerprint: raw.readUInt32BE(5)
|
|
464
|
+
};
|
|
465
|
+
}
|
|
433
466
|
};
|
|
434
467
|
|
|
435
468
|
// src/adapter/KeystoneAdapter.ts
|
|
436
469
|
var import_bs58 = __toESM(require("bs58"));
|
|
437
470
|
var import_uuid2 = require("uuid");
|
|
438
|
-
var
|
|
471
|
+
var import_hwk_adapter_core3 = require("@onekeyfe/hwk-adapter-core");
|
|
472
|
+
|
|
473
|
+
// src/adapter/deviceTable.ts
|
|
474
|
+
var import_hwk_adapter_core2 = require("@onekeyfe/hwk-adapter-core");
|
|
439
475
|
|
|
440
476
|
// src/adapter/pathUtils.ts
|
|
441
477
|
function normalizePath(path) {
|
|
@@ -471,20 +507,41 @@ function btcScriptTypeFromPath(path) {
|
|
|
471
507
|
return void 0;
|
|
472
508
|
}
|
|
473
509
|
}
|
|
510
|
+
function isKeystoneSignableBtcAccountPath(accountPath) {
|
|
511
|
+
const segments = normalizePath(accountPath).slice(2).split("/");
|
|
512
|
+
if (segments.length !== 3) return false;
|
|
513
|
+
const [purpose, coin, account] = segments;
|
|
514
|
+
return ["44'", "49'", "84'", "86'"].includes(purpose) && coin === "0'" && account === "0'";
|
|
515
|
+
}
|
|
516
|
+
var KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE = "Keystone BTC is limited to mainnet account 0 (m/44'|49'|84'|86'/0'/0')";
|
|
474
517
|
|
|
475
518
|
// src/adapter/deviceTable.ts
|
|
476
|
-
var
|
|
477
|
-
|
|
478
|
-
|
|
519
|
+
var KEYSTONE_WALLET_CONNECT_ID_PREFIX = "keystone-wallet:";
|
|
520
|
+
var KEYSTONE_WALLET_ID_PATH = "m/44'/60'/0'";
|
|
521
|
+
function walletConnectId(walletId) {
|
|
522
|
+
return `${KEYSTONE_WALLET_CONNECT_ID_PREFIX}${walletId}`;
|
|
479
523
|
}
|
|
480
524
|
function accountKey(hwkChain, path) {
|
|
481
525
|
return `${hwkChain}:${normalizePath(path)}`;
|
|
482
526
|
}
|
|
483
|
-
function
|
|
527
|
+
function deriveKeystoneWalletId(accounts) {
|
|
528
|
+
const identityAccount = accounts.find(
|
|
529
|
+
(account) => normalizePath(account.path) === KEYSTONE_WALLET_ID_PATH
|
|
530
|
+
);
|
|
531
|
+
if (!identityAccount?.extendedPublicKey) {
|
|
532
|
+
throw new Error(
|
|
533
|
+
`Keystone identity response is missing the extended public key at ${KEYSTONE_WALLET_ID_PATH}`
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
return (0, import_hwk_adapter_core2.deriveWalletId)(
|
|
537
|
+
`keystone:secp256k1:${KEYSTONE_WALLET_ID_PATH}:${identityAccount.extendedPublicKey.trim()}`
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
function createDeviceRecord(walletId, masterFingerprint) {
|
|
484
541
|
return {
|
|
542
|
+
walletId,
|
|
485
543
|
masterFingerprint,
|
|
486
|
-
connectId:
|
|
487
|
-
accounts: /* @__PURE__ */ new Map(),
|
|
544
|
+
connectId: walletConnectId(walletId),
|
|
488
545
|
importedAt: Date.now()
|
|
489
546
|
};
|
|
490
547
|
}
|
|
@@ -499,11 +556,15 @@ function toDeviceInfo(record) {
|
|
|
499
556
|
model: record.model ?? "unknown",
|
|
500
557
|
modelName: record.model,
|
|
501
558
|
firmwareVersion: record.deviceVersion ?? "0.0.0",
|
|
502
|
-
deviceId: record.
|
|
559
|
+
deviceId: record.walletId,
|
|
503
560
|
connectId: record.connectId,
|
|
504
561
|
connectionType: record.usbSessionId ? "usb" : "qr",
|
|
505
562
|
capabilities: CAPABILITIES,
|
|
506
|
-
raw: {
|
|
563
|
+
raw: {
|
|
564
|
+
availableChannels,
|
|
565
|
+
masterFingerprint: record.masterFingerprint,
|
|
566
|
+
hardwareDeviceId: record.hardwareDeviceId
|
|
567
|
+
}
|
|
507
568
|
};
|
|
508
569
|
}
|
|
509
570
|
function placeholderDeviceInfo() {
|
|
@@ -520,6 +581,26 @@ function placeholderDeviceInfo() {
|
|
|
520
581
|
|
|
521
582
|
// src/adapter/KeystoneAdapter.ts
|
|
522
583
|
var COLD_START_JOB_LABEL = "keystone-cold-start";
|
|
584
|
+
var KEYSTONE_USB_REATTACH_PROBE_ATTEMPTS = 4;
|
|
585
|
+
var KEYSTONE_USB_REATTACH_PROBE_INTERVAL_MS = 500;
|
|
586
|
+
function resolveHardwareOperationTarget(positionalTargetId, interactionId) {
|
|
587
|
+
const result = (0, import_hwk_adapter_core3.resolveHardwareOperationTarget)(
|
|
588
|
+
positionalTargetId,
|
|
589
|
+
interactionId,
|
|
590
|
+
"keystone"
|
|
591
|
+
);
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
function waitForKeystoneUsbReattachProbe() {
|
|
595
|
+
return new Promise((resolve) => {
|
|
596
|
+
setTimeout(resolve, KEYSTONE_USB_REATTACH_PROBE_INTERVAL_MS);
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
function isKeystoneUsbReconnectableError(error) {
|
|
600
|
+
if (!error || typeof error !== "object") return false;
|
|
601
|
+
const { code } = error;
|
|
602
|
+
return code === import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound || code === import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected;
|
|
603
|
+
}
|
|
523
604
|
var BIP44_COIN_TYPE_TO_CHAIN = {
|
|
524
605
|
60: "evm",
|
|
525
606
|
0: "btc",
|
|
@@ -530,64 +611,111 @@ function inferHwkChainFromPath(path) {
|
|
|
530
611
|
const match = normalizePath(path).match(/^m\/\d+'\/(\d+)'/);
|
|
531
612
|
return match ? BIP44_COIN_TYPE_TO_CHAIN[Number(match[1])] : void 0;
|
|
532
613
|
}
|
|
533
|
-
var DEFAULT_IMPORT_SCHEMAS = [
|
|
534
|
-
{ hwkChain: "evm", path: import_hwk_adapter_core.CHAIN_FINGERPRINT_PATHS.evm.replace(/\/0\/0$/, "") },
|
|
535
|
-
// The 3 BTC purposes this package can actually derive addresses for
|
|
536
|
-
// (btcScriptTypeFromPath: 44'→P2PKH, 49'→P2SH-P2WPKH, 84'→P2WPKH) —
|
|
537
|
-
// explicitly requested, not left to chance on whether the device
|
|
538
|
-
// volunteers the others unprompted. Still one combined round trip: these
|
|
539
|
-
// are 3 more entries in the same qr-hardware-call, not 3 more scans.
|
|
540
|
-
// 86' (P2TR/taproot) deliberately excluded — `deriveBtcAddressFromXpub`
|
|
541
|
-
// can't use it yet (needs an elliptic-curve library not wired in), and
|
|
542
|
-
// unlike app-monorepo's own sync-all bundle (which also requests 86', but
|
|
543
|
-
// to compute a "full xfp" — a need this adapter doesn't have, since its
|
|
544
|
-
// mfp already comes for free from the response envelope), there's no
|
|
545
|
-
// other use for that xpub here — so it stays out until P2TR is real.
|
|
546
|
-
{ hwkChain: "btc", path: import_hwk_adapter_core.CHAIN_FINGERPRINT_PATHS.btc },
|
|
547
|
-
{ hwkChain: "btc", path: "m/49'/0'/0'" },
|
|
548
|
-
{ hwkChain: "btc", path: "m/84'/0'/0'" },
|
|
549
|
-
{ hwkChain: "sol", path: import_hwk_adapter_core.CHAIN_FINGERPRINT_PATHS.sol },
|
|
550
|
-
{ hwkChain: "tron", path: import_hwk_adapter_core.CHAIN_FINGERPRINT_PATHS.tron.replace(/\/0\/0$/, "") }
|
|
551
|
-
];
|
|
552
614
|
var KeystoneAdapter = class _KeystoneAdapter {
|
|
553
615
|
constructor(options) {
|
|
554
616
|
this.vendor = "keystone";
|
|
555
|
-
this.emitter = new
|
|
556
|
-
this.
|
|
617
|
+
this.emitter = new import_hwk_adapter_core3.TypedEventEmitter();
|
|
618
|
+
this._interactionRoutes = /* @__PURE__ */ new Map();
|
|
619
|
+
this._interactions = new import_hwk_adapter_core3.InteractionRegistry({
|
|
620
|
+
vendor: "keystone",
|
|
621
|
+
onEnded: (interaction, reason) => {
|
|
622
|
+
const route = this._interactionRoutes.get(interaction.connectId);
|
|
623
|
+
if (route?.interactionId === interaction.interactionId) {
|
|
624
|
+
this._interactionRoutes.delete(interaction.connectId);
|
|
625
|
+
}
|
|
626
|
+
this.emitter.emit(import_hwk_adapter_core3.SDK.INTERACTION_ENDED, {
|
|
627
|
+
type: import_hwk_adapter_core3.SDK.INTERACTION_ENDED,
|
|
628
|
+
payload: { interactionId: interaction.interactionId, reason }
|
|
629
|
+
});
|
|
630
|
+
if (reason === "timeout") {
|
|
631
|
+
this._releaseInteractionConnection(interaction).catch(() => void 0);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
this._uiRegistry = new import_hwk_adapter_core3.UiRequestRegistry();
|
|
557
636
|
this._devices = /* @__PURE__ */ new Map();
|
|
637
|
+
this._searchDeviceTargets = /* @__PURE__ */ new Map();
|
|
638
|
+
/** Serializes USB open/identity handshakes that run outside the job queue. */
|
|
639
|
+
this._usbConnectTail = Promise.resolve();
|
|
640
|
+
this._unsettledUsbOperations = /* @__PURE__ */ new Map();
|
|
641
|
+
this._usbIdleWaiters = /* @__PURE__ */ new Set();
|
|
642
|
+
this._usbTeardownTail = Promise.resolve();
|
|
643
|
+
this._pendingUsbTeardowns = 0;
|
|
644
|
+
this._handleUsbDisconnect = ({ connectId }) => {
|
|
645
|
+
const record = Array.from(this._devices.values()).find((item) => item.usbSessionId === connectId);
|
|
646
|
+
if (!record) return;
|
|
647
|
+
this._interactions.endByConnectionKey(connectId, "disconnect");
|
|
648
|
+
this._interactions.endByConnectionKey(record.connectId, "disconnect");
|
|
649
|
+
record.usbSessionId = void 0;
|
|
650
|
+
const info = toDeviceInfo(record);
|
|
651
|
+
if (record.qrSynced) {
|
|
652
|
+
this.emitter.emit(import_hwk_adapter_core3.DEVICE.CHANGED, { type: import_hwk_adapter_core3.DEVICE.CHANGED, payload: info });
|
|
653
|
+
} else {
|
|
654
|
+
this._devices.delete(record.walletId);
|
|
655
|
+
this.emitter.emit(import_hwk_adapter_core3.DEVICE.DISCONNECT, { type: import_hwk_adapter_core3.DEVICE.DISCONNECT, payload: info });
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
this._handleUsbUiEvent = (event) => {
|
|
659
|
+
this.emitter.emit("ui-event", event);
|
|
660
|
+
};
|
|
558
661
|
// ---------------------------------------------------------------------------
|
|
559
662
|
// All-network bundle — dispatches to the same per-chain methods below, so
|
|
560
663
|
// there's exactly one implementation of each chain's address logic.
|
|
561
664
|
// ---------------------------------------------------------------------------
|
|
562
665
|
this.allNetworkGetAddress = async (connectId, deviceId, params) => {
|
|
666
|
+
const operationTarget = resolveHardwareOperationTarget(connectId, params.interactionId);
|
|
667
|
+
if (!operationTarget.success) return operationTarget;
|
|
668
|
+
const effectiveConnectId = operationTarget.payload.targetId ?? "";
|
|
669
|
+
const { interactionId } = operationTarget.payload;
|
|
563
670
|
try {
|
|
564
|
-
|
|
565
|
-
|
|
671
|
+
const prefetched = await this._prefetchAllNetworkAccounts(
|
|
672
|
+
effectiveConnectId,
|
|
566
673
|
deviceId,
|
|
674
|
+
params
|
|
675
|
+
);
|
|
676
|
+
const { book } = prefetched;
|
|
677
|
+
const itemDeviceId = deviceId || prefetched.walletId || "";
|
|
678
|
+
return await (0, import_hwk_adapter_core3.runAllNetworkGetAddress)({
|
|
679
|
+
connectId: effectiveConnectId,
|
|
680
|
+
deviceId: itemDeviceId,
|
|
567
681
|
params,
|
|
568
|
-
callItem: async ({
|
|
569
|
-
const commonArgs = {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
case "
|
|
576
|
-
return this.
|
|
577
|
-
case "
|
|
578
|
-
return this.
|
|
682
|
+
callItem: async ({ method, item }) => {
|
|
683
|
+
const commonArgs = {
|
|
684
|
+
path: item.path,
|
|
685
|
+
showOnDevice: item.showOnDevice,
|
|
686
|
+
interactionId
|
|
687
|
+
};
|
|
688
|
+
switch (method) {
|
|
689
|
+
case "evmGetAddress":
|
|
690
|
+
return this.evmGetAddress(effectiveConnectId, itemDeviceId, commonArgs, book);
|
|
691
|
+
case "btcGetAddress":
|
|
692
|
+
return this.btcGetAddress(effectiveConnectId, itemDeviceId, commonArgs, book);
|
|
693
|
+
case "btcGetPublicKey":
|
|
694
|
+
return this.btcGetPublicKey(effectiveConnectId, itemDeviceId, commonArgs, book);
|
|
695
|
+
case "solGetAddress":
|
|
696
|
+
return this.solGetAddress(effectiveConnectId, itemDeviceId, commonArgs, book);
|
|
697
|
+
case "tronGetAddress":
|
|
698
|
+
return this.tronGetAddress(effectiveConnectId, itemDeviceId, commonArgs, book);
|
|
579
699
|
default:
|
|
580
|
-
return (0,
|
|
700
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
701
|
+
import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
|
|
702
|
+
`Unsupported method: ${String(method)}`
|
|
703
|
+
);
|
|
581
704
|
}
|
|
582
705
|
},
|
|
583
706
|
attachIdentity: (context) => {
|
|
584
|
-
const
|
|
707
|
+
const { record } = this._resolveTarget(effectiveConnectId, itemDeviceId);
|
|
585
708
|
return Promise.resolve({
|
|
586
709
|
...context.item,
|
|
587
710
|
success: true,
|
|
588
711
|
payload: {
|
|
589
712
|
...context.payload,
|
|
590
|
-
|
|
713
|
+
rootFingerprint: record ? Number.parseInt(record.masterFingerprint, 16) : void 0,
|
|
714
|
+
deviceIdentity: record ? {
|
|
715
|
+
vendor: "keystone",
|
|
716
|
+
type: "walletId",
|
|
717
|
+
value: record.walletId
|
|
718
|
+
} : void 0
|
|
591
719
|
}
|
|
592
720
|
});
|
|
593
721
|
}
|
|
@@ -599,8 +727,10 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
599
727
|
this._origin = options?.origin ?? "OneKey";
|
|
600
728
|
this._qrTimeoutMs = options?.qrTimeoutMs;
|
|
601
729
|
this.urEngine = new KeystoneUrEngine(this._origin);
|
|
602
|
-
this._jobQueue = new
|
|
730
|
+
this._jobQueue = new import_hwk_adapter_core3.DeviceJobQueue();
|
|
603
731
|
this._usbConnector = options?.usbConnector;
|
|
732
|
+
this._usbConnector?.on("device-disconnect", this._handleUsbDisconnect);
|
|
733
|
+
this._usbConnector?.on("ui-event", this._handleUsbUiEvent);
|
|
604
734
|
}
|
|
605
735
|
// ---------------------------------------------------------------------------
|
|
606
736
|
// Lifecycle / transport
|
|
@@ -621,56 +751,187 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
621
751
|
init(_config) {
|
|
622
752
|
return Promise.resolve();
|
|
623
753
|
}
|
|
624
|
-
dispose() {
|
|
754
|
+
async dispose() {
|
|
755
|
+
this._interactions.endAll("runtime-reset");
|
|
625
756
|
this._uiRegistry.cancel();
|
|
626
757
|
this._jobQueue.clear();
|
|
627
|
-
this.
|
|
628
|
-
this.
|
|
758
|
+
this._searchDeviceTargets.clear();
|
|
759
|
+
await this._resetUsbSessions();
|
|
760
|
+
this._usbConnector?.off("device-disconnect", this._handleUsbDisconnect);
|
|
761
|
+
this._usbConnector?.off("ui-event", this._handleUsbUiEvent);
|
|
629
762
|
this._usbConnector?.reset();
|
|
630
|
-
|
|
763
|
+
this._devices.clear();
|
|
764
|
+
this.emitter.removeAllListeners();
|
|
631
765
|
}
|
|
632
766
|
// ---------------------------------------------------------------------------
|
|
633
767
|
// Device table
|
|
634
768
|
// ---------------------------------------------------------------------------
|
|
635
769
|
/**
|
|
636
|
-
* QR-synced wallets
|
|
637
|
-
*
|
|
638
|
-
*
|
|
639
|
-
*
|
|
770
|
+
* Unscoped discovery includes QR-synced wallets already known to this
|
|
771
|
+
* adapter. Explicit QR discovery returns a virtual connection target because
|
|
772
|
+
* there is no physical descriptor to enumerate. When a USB connector is
|
|
773
|
+
* configured, its raw scan results are appended as-is: a USB descriptor has
|
|
774
|
+
* no mfp until `connectDevice()` actually opens+claims it (see
|
|
640
775
|
* `KeystoneUsbConnectorBase.searchDevices`), so these entries carry an
|
|
641
776
|
* empty `deviceId` and exist purely so a host can list "plugged in, click
|
|
642
777
|
* to connect" candidates.
|
|
643
778
|
*/
|
|
644
|
-
async searchDevices(
|
|
779
|
+
async searchDevices(options) {
|
|
780
|
+
const searchGeneration = /* @__PURE__ */ Symbol("keystone-device-search");
|
|
781
|
+
this._activeSearchGeneration = searchGeneration;
|
|
782
|
+
this._searchDeviceTargets.clear();
|
|
783
|
+
if (options?.resetSession) {
|
|
784
|
+
await this._resetUsbSessions();
|
|
785
|
+
} else {
|
|
786
|
+
await this._usbTeardownTail;
|
|
787
|
+
}
|
|
645
788
|
const known = Array.from(this._devices.values()).map(toDeviceInfo);
|
|
646
|
-
if (
|
|
789
|
+
if (options?.transportType === "qr") {
|
|
790
|
+
const searchTargetId = (0, import_hwk_adapter_core3.createHardwareSearchTargetId)({
|
|
791
|
+
vendor: "keystone",
|
|
792
|
+
connectionType: "qr"
|
|
793
|
+
});
|
|
794
|
+
this._searchDeviceTargets.set(searchTargetId, "qr");
|
|
795
|
+
return [
|
|
796
|
+
{
|
|
797
|
+
...placeholderDeviceInfo(),
|
|
798
|
+
model: "",
|
|
799
|
+
connectId: searchTargetId,
|
|
800
|
+
capabilities: { persistentDeviceIdentity: false }
|
|
801
|
+
}
|
|
802
|
+
];
|
|
803
|
+
}
|
|
804
|
+
if (options?.transportType === "ble") return [];
|
|
805
|
+
if (!this._usbConnector) return options?.transportType === "usb" ? [] : known;
|
|
647
806
|
let usbDevices;
|
|
648
807
|
try {
|
|
649
808
|
usbDevices = await this._usbConnector.searchDevices();
|
|
650
|
-
} catch {
|
|
809
|
+
} catch (error) {
|
|
810
|
+
if (options?.transportType === "usb") throw error;
|
|
651
811
|
return known;
|
|
652
812
|
}
|
|
813
|
+
if (this._activeSearchGeneration !== searchGeneration) return [];
|
|
653
814
|
const placeholders = usbDevices.map((d) => ({
|
|
654
815
|
vendor: "keystone",
|
|
655
|
-
|
|
816
|
+
// Empty, not 'unknown': enumeration genuinely cannot tell the model
|
|
817
|
+
// apart (every Keystone shares one vid/pid), and a literal placeholder
|
|
818
|
+
// string would beat the host's own default-name fallback and surface as
|
|
819
|
+
// a device called "unknown".
|
|
820
|
+
model: d.model ?? "",
|
|
656
821
|
modelName: d.modelName,
|
|
822
|
+
// Placeholders — neither is knowable until the device is opened.
|
|
657
823
|
firmwareVersion: "0.0.0",
|
|
658
824
|
deviceId: d.deviceId,
|
|
659
825
|
connectId: d.connectId,
|
|
660
826
|
connectionType: "usb",
|
|
661
|
-
|
|
827
|
+
// The USB product name, which IS readable at enumeration time.
|
|
828
|
+
label: d.name,
|
|
829
|
+
serialNumber: d.serialNumber,
|
|
830
|
+
capabilities: d.capabilities ?? { persistentDeviceIdentity: false },
|
|
831
|
+
raw: d.raw
|
|
662
832
|
}));
|
|
663
|
-
|
|
833
|
+
for (const placeholder of placeholders) {
|
|
834
|
+
this._searchDeviceTargets.set(placeholder.connectId, "usb");
|
|
835
|
+
}
|
|
836
|
+
return options?.transportType === "usb" ? placeholders : [...known, ...placeholders];
|
|
837
|
+
}
|
|
838
|
+
async _resetUsbSessions() {
|
|
839
|
+
await this._runUsbTeardown(async () => {
|
|
840
|
+
await this._usbConnectTail;
|
|
841
|
+
this._interactions.endAll("runtime-reset");
|
|
842
|
+
if (!this._usbConnector) return;
|
|
843
|
+
const sessionIds = new Set(
|
|
844
|
+
Array.from(this._devices.values()).map((record) => record.usbSessionId).filter((sessionId) => Boolean(sessionId))
|
|
845
|
+
);
|
|
846
|
+
for (const sessionId of sessionIds) {
|
|
847
|
+
try {
|
|
848
|
+
await this._usbConnector.disconnect(sessionId);
|
|
849
|
+
} catch {
|
|
850
|
+
}
|
|
851
|
+
this._handleUsbDisconnect({ connectId: sessionId });
|
|
852
|
+
}
|
|
853
|
+
});
|
|
664
854
|
}
|
|
665
|
-
async
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
855
|
+
async searchDeviceTargets(options) {
|
|
856
|
+
const devices = await this.searchDevices(options);
|
|
857
|
+
return devices.map((device) => ({
|
|
858
|
+
searchTargetId: device.connectId,
|
|
859
|
+
// Keystone's wallet id is stable only after the USB/QR handshake. The
|
|
860
|
+
// selectable handle remains owned by the current discovery snapshot.
|
|
861
|
+
searchTargetReusePolicy: "current-discovery",
|
|
862
|
+
vendor: device.vendor,
|
|
863
|
+
connectionType: device.connectionType,
|
|
864
|
+
kind: device.connectionType === "qr" ? "interactive" : "physical",
|
|
865
|
+
label: device.label,
|
|
866
|
+
model: device.model,
|
|
867
|
+
modelName: device.modelName,
|
|
868
|
+
serialNumber: device.serialNumber
|
|
869
|
+
}));
|
|
870
|
+
}
|
|
871
|
+
async listConnectionTargets(options) {
|
|
872
|
+
const targets = await this.searchDeviceTargets(options);
|
|
873
|
+
return targets.map(({ searchTargetId, ...target }) => ({
|
|
874
|
+
...target,
|
|
875
|
+
targetId: searchTargetId
|
|
876
|
+
}));
|
|
877
|
+
}
|
|
878
|
+
async connectDevice(searchTargetId) {
|
|
879
|
+
try {
|
|
880
|
+
let connected;
|
|
881
|
+
let selectedConnectionType;
|
|
882
|
+
const discoveredConnectionType = this._searchDeviceTargets.get(searchTargetId);
|
|
883
|
+
if (discoveredConnectionType === "qr") {
|
|
884
|
+
selectedConnectionType = "qr";
|
|
885
|
+
connected = await this.importFromQr();
|
|
886
|
+
} else if (discoveredConnectionType === "usb") {
|
|
887
|
+
selectedConnectionType = "usb";
|
|
888
|
+
if (!this._usbConnector) {
|
|
889
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
890
|
+
import_hwk_adapter_core3.HardwareErrorCode.TransportNotAvailable,
|
|
891
|
+
"No USB connector configured for this Keystone adapter"
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
connected = await this._connectUsb({}, searchTargetId);
|
|
895
|
+
} else if ((0, import_hwk_adapter_core3.hasHardwareRuntimeIdPrefix)(searchTargetId)) {
|
|
896
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
897
|
+
import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
|
|
898
|
+
"Keystone search target has expired; search for devices again",
|
|
899
|
+
void 0,
|
|
900
|
+
void 0,
|
|
901
|
+
{ scope: "search-target" }
|
|
902
|
+
);
|
|
903
|
+
} else {
|
|
904
|
+
const target = this._resolveTarget(searchTargetId);
|
|
905
|
+
if (target.record) {
|
|
906
|
+
selectedConnectionType = target.record.usbSessionId ? "usb" : "qr";
|
|
907
|
+
connected = (0, import_hwk_adapter_core3.success)(toDeviceInfo(target.record));
|
|
908
|
+
} else if (!this._usbConnector) {
|
|
909
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
910
|
+
import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
|
|
911
|
+
`Unknown Keystone device search target: ${searchTargetId}`
|
|
912
|
+
);
|
|
913
|
+
} else {
|
|
914
|
+
selectedConnectionType = "usb";
|
|
915
|
+
connected = await this._connectUsb(target);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (!connected.success) return connected;
|
|
919
|
+
const record = this._devices.get(connected.payload.deviceId);
|
|
920
|
+
this._interactions.endByConnectionKey(connected.payload.connectId, "explicit");
|
|
921
|
+
const interaction = this._interactions.create({
|
|
922
|
+
searchTargetId,
|
|
923
|
+
connectId: connected.payload.connectId,
|
|
924
|
+
device: connected.payload,
|
|
925
|
+
connectionKeys: selectedConnectionType === "usb" && record?.usbSessionId ? [record.usbSessionId] : []
|
|
926
|
+
});
|
|
927
|
+
this._interactionRoutes.set(connected.payload.connectId, {
|
|
928
|
+
interactionId: interaction.interactionId,
|
|
929
|
+
connectionType: selectedConnectionType
|
|
930
|
+
});
|
|
931
|
+
return (0, import_hwk_adapter_core3.success)(interaction.interactionId);
|
|
932
|
+
} catch (err) {
|
|
933
|
+
return this._errorToFailure(err);
|
|
670
934
|
}
|
|
671
|
-
const result = await this._connectUsb();
|
|
672
|
-
if (!result.success) return result;
|
|
673
|
-
return (0, import_hwk_adapter_core.success)(result.payload.connectId);
|
|
674
935
|
}
|
|
675
936
|
/**
|
|
676
937
|
* QR has no persistent connection to tear down — the account cache
|
|
@@ -679,57 +940,87 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
679
940
|
* QR-only (if it was ever QR-synced) or removes it entirely (pure-USB
|
|
680
941
|
* wallet that was never seen over QR) — see §4.2 of the design doc.
|
|
681
942
|
*/
|
|
682
|
-
async
|
|
683
|
-
const
|
|
684
|
-
if (!
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
await this._usbConnector.disconnect(record.usbSessionId);
|
|
688
|
-
} catch {
|
|
689
|
-
}
|
|
943
|
+
async releaseInteraction(interactionId) {
|
|
944
|
+
const interaction = this._interactions.find(interactionId);
|
|
945
|
+
if (!interaction) {
|
|
946
|
+
this._interactions.resolve(interactionId);
|
|
947
|
+
return;
|
|
690
948
|
}
|
|
691
|
-
|
|
692
|
-
if (
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
949
|
+
const endedInteraction = this._interactions.end(interactionId, "explicit");
|
|
950
|
+
if (!endedInteraction) return;
|
|
951
|
+
await this._releaseInteractionConnection(endedInteraction);
|
|
952
|
+
}
|
|
953
|
+
async _releaseInteractionConnection(interaction) {
|
|
954
|
+
const { connectId } = interaction;
|
|
955
|
+
let record;
|
|
956
|
+
try {
|
|
957
|
+
record = this._resolveTarget(connectId).record;
|
|
958
|
+
} catch {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
if (!record?.usbSessionId || !interaction.connectionKeys.includes(record.usbSessionId)) {
|
|
962
|
+
return;
|
|
699
963
|
}
|
|
964
|
+
const { usbSessionId } = record;
|
|
965
|
+
await this._runUsbTeardown(async () => {
|
|
966
|
+
if (this._usbConnector) {
|
|
967
|
+
try {
|
|
968
|
+
await this._usbConnector.disconnect(usbSessionId);
|
|
969
|
+
} catch {
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
this._handleUsbDisconnect({ connectId: usbSessionId });
|
|
973
|
+
});
|
|
700
974
|
}
|
|
701
|
-
getDeviceInfo(
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
975
|
+
getDeviceInfo(connectIdOrInteractionId, deviceId) {
|
|
976
|
+
try {
|
|
977
|
+
const connectId = (0, import_hwk_adapter_core3.isHardwareInteractionId)(connectIdOrInteractionId) ? this._interactions.resolve(connectIdOrInteractionId).connectId : connectIdOrInteractionId;
|
|
978
|
+
const { record } = this._resolveTarget(connectId, deviceId);
|
|
979
|
+
if (!record) {
|
|
980
|
+
return Promise.resolve(
|
|
981
|
+
(0, import_hwk_adapter_core3.failure)(
|
|
982
|
+
import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
|
|
983
|
+
`Unknown Keystone device: ${connectId || deviceId}`
|
|
984
|
+
)
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
return Promise.resolve((0, import_hwk_adapter_core3.success)(toDeviceInfo(record)));
|
|
988
|
+
} catch (err) {
|
|
989
|
+
return Promise.resolve(this._errorToFailure(err));
|
|
710
990
|
}
|
|
711
|
-
return Promise.resolve((0, import_hwk_adapter_core.success)(toDeviceInfo(record)));
|
|
712
991
|
}
|
|
713
992
|
getSupportedChains() {
|
|
714
993
|
return ["evm", "btc", "sol", "tron"];
|
|
715
994
|
}
|
|
716
995
|
cancel(connectId) {
|
|
717
|
-
const reason = (0,
|
|
718
|
-
code:
|
|
996
|
+
const reason = (0, import_hwk_adapter_core3.createHwkError)({
|
|
997
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.UserAborted,
|
|
719
998
|
message: "User aborted operation"
|
|
720
999
|
});
|
|
721
1000
|
this._uiRegistry.cancel();
|
|
722
|
-
this._jobQueue.
|
|
1001
|
+
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
1002
|
+
const targetId = connectId ?? activeJobId;
|
|
1003
|
+
this._jobQueue.cancelActiveAndPending(
|
|
1004
|
+
targetId && (0, import_hwk_adapter_core3.isHardwareInteractionId)(targetId) ? activeJobId ?? targetId : targetId,
|
|
1005
|
+
reason
|
|
1006
|
+
);
|
|
723
1007
|
}
|
|
724
1008
|
getChainFingerprint(connectId, deviceId, chain) {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
1009
|
+
try {
|
|
1010
|
+
const target = this._resolveTarget(connectId, deviceId);
|
|
1011
|
+
const walletId = target.record?.walletId ?? target.expectedWalletId;
|
|
1012
|
+
if (!walletId) {
|
|
1013
|
+
return Promise.resolve(
|
|
1014
|
+
(0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound, "Unknown Keystone wallet identity")
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
return Promise.resolve((0, import_hwk_adapter_core3.success)((0, import_hwk_adapter_core3.deriveDeviceFingerprint)(`keystone:${chain}:${walletId}`)));
|
|
1018
|
+
} catch (err) {
|
|
1019
|
+
return Promise.resolve(this._errorToFailure(err));
|
|
728
1020
|
}
|
|
729
|
-
return Promise.resolve((0, import_hwk_adapter_core.success)((0, import_hwk_adapter_core.deriveDeviceFingerprint)(`keystone:${chain}:${mfp}`)));
|
|
730
1021
|
}
|
|
731
1022
|
uiResponse(response) {
|
|
732
|
-
if (response.type ===
|
|
1023
|
+
if (response.type === import_hwk_adapter_core3.UI_RESPONSE.CANCEL) {
|
|
733
1024
|
this.cancel();
|
|
734
1025
|
return;
|
|
735
1026
|
}
|
|
@@ -750,17 +1041,11 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
750
1041
|
return await this._jobQueue.enqueue(COLD_START_JOB_LABEL, async (signal) => {
|
|
751
1042
|
const displayDevice = placeholderDeviceInfo();
|
|
752
1043
|
let responseUr;
|
|
753
|
-
let requestedChains;
|
|
754
1044
|
if (options.mode === "scan") {
|
|
755
1045
|
responseUr = await this._requestQrScanAndAwaitResponse(displayDevice);
|
|
756
1046
|
} else {
|
|
757
|
-
const schemas = options.paths?.length ? options.paths : DEFAULT_IMPORT_SCHEMAS;
|
|
758
|
-
requestedChains = schemas.map((s) => s.hwkChain);
|
|
759
1047
|
const requestUr = this.urEngine.buildKeyDerivationRequest({
|
|
760
|
-
schemas:
|
|
761
|
-
path: s.path,
|
|
762
|
-
curve: s.hwkChain === "sol" ? "ed25519" : "secp256k1"
|
|
763
|
-
})),
|
|
1048
|
+
schemas: [{ path: KEYSTONE_WALLET_ID_PATH, curve: "secp256k1" }],
|
|
764
1049
|
origin: this._origin
|
|
765
1050
|
});
|
|
766
1051
|
responseUr = await this._requestQrDisplayAndAwaitResponse(displayDevice, {
|
|
@@ -771,14 +1056,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
771
1056
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
772
1057
|
const parsed = this.urEngine.parseAccountResponse(responseUr);
|
|
773
1058
|
const record = this._upsertDeviceRecord(parsed);
|
|
774
|
-
|
|
775
|
-
const hwkChain = requestedChains?.length === 1 ? requestedChains[0] : inferHwkChainFromPath(account.path);
|
|
776
|
-
if (hwkChain) {
|
|
777
|
-
const entry = { ...account, hwkChain };
|
|
778
|
-
record.accounts.set(accountKey(hwkChain, account.path), entry);
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
return (0, import_hwk_adapter_core.success)(toDeviceInfo(record));
|
|
1059
|
+
return (0, import_hwk_adapter_core3.success)(toDeviceInfo(record));
|
|
782
1060
|
});
|
|
783
1061
|
} catch (err) {
|
|
784
1062
|
return this._errorToFailure(err);
|
|
@@ -787,17 +1065,19 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
787
1065
|
// ---------------------------------------------------------------------------
|
|
788
1066
|
// EVM
|
|
789
1067
|
// ---------------------------------------------------------------------------
|
|
790
|
-
async evmGetAddress(connectIdArg, deviceIdArg, paramsArg) {
|
|
791
|
-
const
|
|
1068
|
+
async evmGetAddress(connectIdArg, deviceIdArg, paramsArg, book) {
|
|
1069
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.interactionId);
|
|
1070
|
+
if (!operationTarget.success) return operationTarget;
|
|
1071
|
+
const connectId = operationTarget.payload.targetId;
|
|
792
1072
|
const deviceId = deviceIdArg ?? void 0;
|
|
793
1073
|
const params = paramsArg;
|
|
794
|
-
if (!params) return (0,
|
|
1074
|
+
if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "evmGetAddress requires params");
|
|
795
1075
|
if (!params.path)
|
|
796
|
-
return (0,
|
|
1076
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "evmGetAddress requires params.path");
|
|
797
1077
|
const { accountPath, relativeDerivePath } = splitAccountPath(params.path);
|
|
798
1078
|
if (!relativeDerivePath) {
|
|
799
|
-
return (0,
|
|
800
|
-
|
|
1079
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1080
|
+
import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
|
|
801
1081
|
"evmGetAddress requires a full leaf path, e.g. m/44'/60'/0'/0/0"
|
|
802
1082
|
);
|
|
803
1083
|
}
|
|
@@ -805,17 +1085,18 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
805
1085
|
return await this._jobQueue.enqueue(
|
|
806
1086
|
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
807
1087
|
async (signal) => {
|
|
808
|
-
const { account } = await this.
|
|
1088
|
+
const { account } = await this._fetchAccount(
|
|
809
1089
|
connectId,
|
|
810
1090
|
deviceId,
|
|
811
1091
|
"evm",
|
|
812
1092
|
accountPath,
|
|
813
|
-
signal
|
|
1093
|
+
signal,
|
|
1094
|
+
{ book }
|
|
814
1095
|
);
|
|
815
1096
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
816
1097
|
if (!account.extendedPublicKey) {
|
|
817
|
-
throw (0,
|
|
818
|
-
code:
|
|
1098
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1099
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
|
|
819
1100
|
message: "Keystone did not return an extended public key for this account path"
|
|
820
1101
|
});
|
|
821
1102
|
}
|
|
@@ -823,7 +1104,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
823
1104
|
account.extendedPublicKey,
|
|
824
1105
|
relativeDerivePath
|
|
825
1106
|
);
|
|
826
|
-
return (0,
|
|
1107
|
+
return (0, import_hwk_adapter_core3.success)({ address, path: normalizePath(params.path) });
|
|
827
1108
|
}
|
|
828
1109
|
);
|
|
829
1110
|
} catch (err) {
|
|
@@ -831,27 +1112,29 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
831
1112
|
}
|
|
832
1113
|
}
|
|
833
1114
|
async evmSignTransaction(connectIdArg, deviceIdArg, paramsArg) {
|
|
834
|
-
const
|
|
1115
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.interactionId);
|
|
1116
|
+
if (!operationTarget.success) return operationTarget;
|
|
1117
|
+
const connectId = operationTarget.payload.targetId;
|
|
835
1118
|
const deviceId = deviceIdArg ?? void 0;
|
|
836
1119
|
const params = paramsArg;
|
|
837
1120
|
if (!params)
|
|
838
|
-
return (0,
|
|
1121
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "evmSignTransaction requires params");
|
|
839
1122
|
if (!params.path) {
|
|
840
|
-
return (0,
|
|
1123
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "evmSignTransaction requires params.path");
|
|
841
1124
|
}
|
|
842
1125
|
if (!("serializedTx" in params) || typeof params.serializedTx !== "string" || !params.serializedTx) {
|
|
843
|
-
return (0,
|
|
844
|
-
|
|
1126
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1127
|
+
import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
|
|
845
1128
|
"Keystone only signs a fully RLP-serialized transaction (params.serializedTx) \u2014 structured-field signing is not supported"
|
|
846
1129
|
);
|
|
847
1130
|
}
|
|
848
|
-
const rawTxHex = (0,
|
|
1131
|
+
const rawTxHex = (0, import_hwk_adapter_core3.stripHex)(params.serializedTx);
|
|
849
1132
|
const path = normalizePath(params.path);
|
|
850
1133
|
try {
|
|
851
1134
|
return await this._jobQueue.enqueue(
|
|
852
1135
|
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
853
1136
|
async (signal) => {
|
|
854
|
-
const { record } = await this.
|
|
1137
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, "evm", signal);
|
|
855
1138
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
856
1139
|
const requestId = (0, import_uuid2.v4)();
|
|
857
1140
|
const dataType = parseInt(rawTxHex.slice(0, 2), 16) < 192 ? "typedTransaction" : "transaction";
|
|
@@ -863,14 +1146,21 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
863
1146
|
xfp: record.masterFingerprint,
|
|
864
1147
|
chainId: params.chainId
|
|
865
1148
|
});
|
|
866
|
-
const responseUr = await this._resolveUr(
|
|
1149
|
+
const responseUr = await this._resolveUr(
|
|
1150
|
+
record,
|
|
1151
|
+
requestUr,
|
|
1152
|
+
true,
|
|
1153
|
+
connectId,
|
|
1154
|
+
signal,
|
|
1155
|
+
"evmSignTransaction"
|
|
1156
|
+
);
|
|
867
1157
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
868
1158
|
const sig = this.urEngine.parseEthSignature(responseUr);
|
|
869
1159
|
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
870
|
-
return (0,
|
|
871
|
-
v: (0,
|
|
872
|
-
r: (0,
|
|
873
|
-
s: (0,
|
|
1160
|
+
return (0, import_hwk_adapter_core3.success)({
|
|
1161
|
+
v: (0, import_hwk_adapter_core3.ensure0x)(sig.v),
|
|
1162
|
+
r: (0, import_hwk_adapter_core3.ensure0x)(sig.r),
|
|
1163
|
+
s: (0, import_hwk_adapter_core3.ensure0x)(sig.s)
|
|
874
1164
|
});
|
|
875
1165
|
}
|
|
876
1166
|
);
|
|
@@ -879,23 +1169,25 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
879
1169
|
}
|
|
880
1170
|
}
|
|
881
1171
|
async evmSignMessage(connectIdArg, deviceIdArg, paramsArg) {
|
|
882
|
-
const
|
|
1172
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.interactionId);
|
|
1173
|
+
if (!operationTarget.success) return operationTarget;
|
|
1174
|
+
const connectId = operationTarget.payload.targetId;
|
|
883
1175
|
const deviceId = deviceIdArg ?? void 0;
|
|
884
1176
|
const params = paramsArg;
|
|
885
|
-
if (!params) return (0,
|
|
1177
|
+
if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "evmSignMessage requires params");
|
|
886
1178
|
if (!params.path || params.message === void 0) {
|
|
887
|
-
return (0,
|
|
888
|
-
|
|
1179
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1180
|
+
import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
|
|
889
1181
|
"evmSignMessage requires params.path and params.message"
|
|
890
1182
|
);
|
|
891
1183
|
}
|
|
892
|
-
const messageHex = params.hex ? (0,
|
|
1184
|
+
const messageHex = params.hex ? (0, import_hwk_adapter_core3.stripHex)(params.message) : Buffer.from(params.message, "utf8").toString("hex");
|
|
893
1185
|
const path = normalizePath(params.path);
|
|
894
1186
|
try {
|
|
895
1187
|
return await this._jobQueue.enqueue(
|
|
896
1188
|
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
897
1189
|
async (signal) => {
|
|
898
|
-
const { record } = await this.
|
|
1190
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, "evm", signal);
|
|
899
1191
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
900
1192
|
const requestId = (0, import_uuid2.v4)();
|
|
901
1193
|
const requestUr = this.urEngine.buildEthSignRequest({
|
|
@@ -906,11 +1198,18 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
906
1198
|
xfp: record.masterFingerprint,
|
|
907
1199
|
chainId: params.chainId
|
|
908
1200
|
});
|
|
909
|
-
const responseUr = await this._resolveUr(
|
|
1201
|
+
const responseUr = await this._resolveUr(
|
|
1202
|
+
record,
|
|
1203
|
+
requestUr,
|
|
1204
|
+
true,
|
|
1205
|
+
connectId,
|
|
1206
|
+
signal,
|
|
1207
|
+
"evmSignMessage"
|
|
1208
|
+
);
|
|
910
1209
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
911
1210
|
const sig = this.urEngine.parseEthSignature(responseUr);
|
|
912
1211
|
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
913
|
-
return (0,
|
|
1212
|
+
return (0, import_hwk_adapter_core3.success)({ signature: (0, import_hwk_adapter_core3.ensure0x)(sig.r + sig.s + sig.v) });
|
|
914
1213
|
}
|
|
915
1214
|
);
|
|
916
1215
|
} catch (err) {
|
|
@@ -918,17 +1217,19 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
918
1217
|
}
|
|
919
1218
|
}
|
|
920
1219
|
async evmSignTypedData(connectIdArg, deviceIdArg, paramsArg) {
|
|
921
|
-
const
|
|
1220
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.interactionId);
|
|
1221
|
+
if (!operationTarget.success) return operationTarget;
|
|
1222
|
+
const connectId = operationTarget.payload.targetId;
|
|
922
1223
|
const deviceId = deviceIdArg ?? void 0;
|
|
923
1224
|
const params = paramsArg;
|
|
924
1225
|
if (!params)
|
|
925
|
-
return (0,
|
|
1226
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "evmSignTypedData requires params");
|
|
926
1227
|
if (!params.path) {
|
|
927
|
-
return (0,
|
|
1228
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "evmSignTypedData requires params.path");
|
|
928
1229
|
}
|
|
929
1230
|
if (params.mode === "hash") {
|
|
930
|
-
return (0,
|
|
931
|
-
|
|
1231
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1232
|
+
import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
|
|
932
1233
|
"Keystone always displays the full EIP-712 payload for on-device review \u2014 pre-hashed signing is not supported"
|
|
933
1234
|
);
|
|
934
1235
|
}
|
|
@@ -938,7 +1239,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
938
1239
|
return await this._jobQueue.enqueue(
|
|
939
1240
|
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
940
1241
|
async (signal) => {
|
|
941
|
-
const { record } = await this.
|
|
1242
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, "evm", signal);
|
|
942
1243
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
943
1244
|
const requestId = (0, import_uuid2.v4)();
|
|
944
1245
|
const requestUr = this.urEngine.buildEthSignRequest({
|
|
@@ -949,11 +1250,18 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
949
1250
|
xfp: record.masterFingerprint,
|
|
950
1251
|
chainId: params.chainId
|
|
951
1252
|
});
|
|
952
|
-
const responseUr = await this._resolveUr(
|
|
1253
|
+
const responseUr = await this._resolveUr(
|
|
1254
|
+
record,
|
|
1255
|
+
requestUr,
|
|
1256
|
+
true,
|
|
1257
|
+
connectId,
|
|
1258
|
+
signal,
|
|
1259
|
+
"evmSignTypedData"
|
|
1260
|
+
);
|
|
953
1261
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
954
1262
|
const sig = this.urEngine.parseEthSignature(responseUr);
|
|
955
1263
|
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
956
|
-
return (0,
|
|
1264
|
+
return (0, import_hwk_adapter_core3.success)({ signature: (0, import_hwk_adapter_core3.ensure0x)(sig.r + sig.s + sig.v) });
|
|
957
1265
|
}
|
|
958
1266
|
);
|
|
959
1267
|
} catch (err) {
|
|
@@ -966,17 +1274,19 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
966
1274
|
// phase doesn't wire in yet; structured-field tx signing needs host-side
|
|
967
1275
|
// PSBT construction. Both are real, bounded follow-ups, not silent gaps.
|
|
968
1276
|
// ---------------------------------------------------------------------------
|
|
969
|
-
async btcGetAddress(connectIdArg, deviceIdArg, paramsArg) {
|
|
970
|
-
const
|
|
1277
|
+
async btcGetAddress(connectIdArg, deviceIdArg, paramsArg, book) {
|
|
1278
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.interactionId);
|
|
1279
|
+
if (!operationTarget.success) return operationTarget;
|
|
1280
|
+
const connectId = operationTarget.payload.targetId;
|
|
971
1281
|
const deviceId = deviceIdArg ?? void 0;
|
|
972
1282
|
const params = paramsArg;
|
|
973
|
-
if (!params) return (0,
|
|
1283
|
+
if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "btcGetAddress requires params");
|
|
974
1284
|
if (!params.path)
|
|
975
|
-
return (0,
|
|
1285
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "btcGetAddress requires params.path");
|
|
976
1286
|
const scriptType = btcScriptTypeFromPath(params.path);
|
|
977
1287
|
if (!scriptType) {
|
|
978
|
-
return (0,
|
|
979
|
-
|
|
1288
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1289
|
+
import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
|
|
980
1290
|
"btcGetAddress requires a path whose purpose is 44'/49'/84'/86' (P2PKH/P2SH-P2WPKH/P2WPKH/P2TR)"
|
|
981
1291
|
);
|
|
982
1292
|
}
|
|
@@ -986,28 +1296,33 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
986
1296
|
"P2TR (purpose 86') needs an elliptic-curve library for BIP-341 tweaking, not yet wired in \u2014 44'/49'/84' work"
|
|
987
1297
|
);
|
|
988
1298
|
}
|
|
989
|
-
const
|
|
1299
|
+
const normalizedPath = normalizePath(params.path);
|
|
1300
|
+
const { accountPath, relativeDerivePath } = splitAccountPath(normalizedPath);
|
|
990
1301
|
if (!relativeDerivePath) {
|
|
991
|
-
return (0,
|
|
992
|
-
|
|
1302
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1303
|
+
import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
|
|
993
1304
|
"btcGetAddress requires a full leaf path, e.g. m/84'/0'/0'/0/0"
|
|
994
1305
|
);
|
|
995
1306
|
}
|
|
1307
|
+
if (!isKeystoneSignableBtcAccountPath(accountPath)) {
|
|
1308
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
|
|
1309
|
+
}
|
|
996
1310
|
try {
|
|
997
1311
|
return await this._jobQueue.enqueue(
|
|
998
1312
|
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
999
1313
|
async (signal) => {
|
|
1000
|
-
const { account } = await this.
|
|
1314
|
+
const { account } = await this._fetchAccount(
|
|
1001
1315
|
connectId,
|
|
1002
1316
|
deviceId,
|
|
1003
1317
|
"btc",
|
|
1004
1318
|
accountPath,
|
|
1005
|
-
signal
|
|
1319
|
+
signal,
|
|
1320
|
+
{ book }
|
|
1006
1321
|
);
|
|
1007
1322
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
1008
1323
|
if (!account.extendedPublicKey) {
|
|
1009
|
-
throw (0,
|
|
1010
|
-
code:
|
|
1324
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1325
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
|
|
1011
1326
|
message: "Keystone did not return an extended public key for this account path"
|
|
1012
1327
|
});
|
|
1013
1328
|
}
|
|
@@ -1016,18 +1331,74 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1016
1331
|
relativeDerivePath,
|
|
1017
1332
|
scriptType
|
|
1018
1333
|
);
|
|
1019
|
-
return (0,
|
|
1334
|
+
return (0, import_hwk_adapter_core3.success)({ address, path: normalizedPath });
|
|
1020
1335
|
}
|
|
1021
1336
|
);
|
|
1022
1337
|
} catch (err) {
|
|
1023
1338
|
return this._errorToFailure(err);
|
|
1024
1339
|
}
|
|
1025
1340
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1341
|
+
/**
|
|
1342
|
+
* Returns the account-level extended public key. `params.path` must be an
|
|
1343
|
+
* ACCOUNT path (`m/84'/0'/0'`), not a leaf — that is the level Keystone
|
|
1344
|
+
* actually syncs, and it is what a host needs to derive a whole account's
|
|
1345
|
+
* addresses offline. Unlike `btcGetAddress` this has no script-type
|
|
1346
|
+
* restriction: an xpub is script-type agnostic, so `86'` (taproot) works
|
|
1347
|
+
* here even though deriving a taproot ADDRESS still needs an EC library
|
|
1348
|
+
* this package doesn't wire in.
|
|
1349
|
+
*/
|
|
1350
|
+
async btcGetPublicKey(connectIdArg, deviceIdArg, paramsArg, book) {
|
|
1351
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.interactionId);
|
|
1352
|
+
if (!operationTarget.success) return operationTarget;
|
|
1353
|
+
const connectId = operationTarget.payload.targetId;
|
|
1354
|
+
const deviceId = deviceIdArg ?? void 0;
|
|
1355
|
+
const params = paramsArg;
|
|
1356
|
+
if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "btcGetPublicKey requires params");
|
|
1357
|
+
if (!params.path)
|
|
1358
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "btcGetPublicKey requires params.path");
|
|
1359
|
+
const { accountPath, relativeDerivePath } = splitAccountPath(params.path);
|
|
1360
|
+
if (relativeDerivePath) {
|
|
1361
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1362
|
+
import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
|
|
1363
|
+
"btcGetPublicKey requires an account path, e.g. m/84'/0'/0' (not a leaf path)"
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
if (!isKeystoneSignableBtcAccountPath(accountPath)) {
|
|
1367
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
|
|
1368
|
+
}
|
|
1369
|
+
try {
|
|
1370
|
+
return await this._jobQueue.enqueue(
|
|
1371
|
+
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1372
|
+
async (signal) => {
|
|
1373
|
+
const { account } = await this._fetchAccount(
|
|
1374
|
+
connectId,
|
|
1375
|
+
deviceId,
|
|
1376
|
+
"btc",
|
|
1377
|
+
accountPath,
|
|
1378
|
+
signal,
|
|
1379
|
+
{ book }
|
|
1380
|
+
);
|
|
1381
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1382
|
+
if (!account.extendedPublicKey) {
|
|
1383
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1384
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
|
|
1385
|
+
message: "Keystone did not return an extended public key for this account path"
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
const meta = this.urEngine.parseXpubMeta(account.extendedPublicKey);
|
|
1389
|
+
return (0, import_hwk_adapter_core3.success)({
|
|
1390
|
+
xpub: account.extendedPublicKey,
|
|
1391
|
+
publicKey: meta.publicKey,
|
|
1392
|
+
chainCode: meta.chainCode,
|
|
1393
|
+
depth: meta.depth,
|
|
1394
|
+
fingerprint: meta.parentFingerprint,
|
|
1395
|
+
path: normalizePath(accountPath)
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
);
|
|
1399
|
+
} catch (err) {
|
|
1400
|
+
return this._errorToFailure(err);
|
|
1401
|
+
}
|
|
1031
1402
|
}
|
|
1032
1403
|
async btcSignTransaction(_connectId, _deviceId, _params) {
|
|
1033
1404
|
return this._unsupported(
|
|
@@ -1036,23 +1407,35 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1036
1407
|
);
|
|
1037
1408
|
}
|
|
1038
1409
|
async btcSignPsbt(connectIdArg, deviceIdArg, paramsArg) {
|
|
1039
|
-
const
|
|
1410
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.interactionId);
|
|
1411
|
+
if (!operationTarget.success) return operationTarget;
|
|
1412
|
+
const connectId = operationTarget.payload.targetId;
|
|
1040
1413
|
const deviceId = deviceIdArg ?? void 0;
|
|
1041
1414
|
const params = paramsArg;
|
|
1042
|
-
if (!params) return (0,
|
|
1415
|
+
if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "btcSignPsbt requires params");
|
|
1043
1416
|
if (!params.psbt)
|
|
1044
|
-
return (0,
|
|
1417
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "btcSignPsbt requires params.psbt");
|
|
1418
|
+
if (params.path && !isKeystoneSignableBtcAccountPath(splitAccountPath(params.path).accountPath)) {
|
|
1419
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
|
|
1420
|
+
}
|
|
1045
1421
|
try {
|
|
1046
1422
|
return await this._jobQueue.enqueue(
|
|
1047
1423
|
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1048
1424
|
async (signal) => {
|
|
1049
|
-
const { record } = await this.
|
|
1425
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, "btc", signal);
|
|
1050
1426
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
1051
|
-
const requestUr = this.urEngine.buildBtcPsbtRequest((0,
|
|
1052
|
-
const responseUr = await this._resolveUr(
|
|
1427
|
+
const requestUr = this.urEngine.buildBtcPsbtRequest((0, import_hwk_adapter_core3.stripHex)(params.psbt));
|
|
1428
|
+
const responseUr = await this._resolveUr(
|
|
1429
|
+
record,
|
|
1430
|
+
requestUr,
|
|
1431
|
+
true,
|
|
1432
|
+
connectId,
|
|
1433
|
+
signal,
|
|
1434
|
+
"btcSignPsbt"
|
|
1435
|
+
);
|
|
1053
1436
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
1054
1437
|
const signedPsbt = this.urEngine.parseBtcPsbt(responseUr);
|
|
1055
|
-
return (0,
|
|
1438
|
+
return (0, import_hwk_adapter_core3.success)({ signedPsbt });
|
|
1056
1439
|
}
|
|
1057
1440
|
);
|
|
1058
1441
|
} catch (err) {
|
|
@@ -1060,23 +1443,28 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1060
1443
|
}
|
|
1061
1444
|
}
|
|
1062
1445
|
async btcSignMessage(connectIdArg, deviceIdArg, paramsArg) {
|
|
1063
|
-
const
|
|
1446
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.interactionId);
|
|
1447
|
+
if (!operationTarget.success) return operationTarget;
|
|
1448
|
+
const connectId = operationTarget.payload.targetId;
|
|
1064
1449
|
const deviceId = deviceIdArg ?? void 0;
|
|
1065
1450
|
const params = paramsArg;
|
|
1066
|
-
if (!params) return (0,
|
|
1451
|
+
if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "btcSignMessage requires params");
|
|
1067
1452
|
if (!params.path || params.message === void 0) {
|
|
1068
|
-
return (0,
|
|
1069
|
-
|
|
1453
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1454
|
+
import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
|
|
1070
1455
|
"btcSignMessage requires params.path and params.message"
|
|
1071
1456
|
);
|
|
1072
1457
|
}
|
|
1073
|
-
const messageHex = params.hex ? (0,
|
|
1458
|
+
const messageHex = params.hex ? (0, import_hwk_adapter_core3.stripHex)(params.message) : Buffer.from(params.message, "utf8").toString("hex");
|
|
1074
1459
|
const path = normalizePath(params.path);
|
|
1460
|
+
if (!isKeystoneSignableBtcAccountPath(splitAccountPath(path).accountPath)) {
|
|
1461
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
|
|
1462
|
+
}
|
|
1075
1463
|
try {
|
|
1076
1464
|
return await this._jobQueue.enqueue(
|
|
1077
1465
|
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1078
1466
|
async (signal) => {
|
|
1079
|
-
const { record } = await this.
|
|
1467
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, "btc", signal);
|
|
1080
1468
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
1081
1469
|
const requestId = (0, import_uuid2.v4)();
|
|
1082
1470
|
const requestUr = this.urEngine.buildBtcMessageSignRequest({
|
|
@@ -1084,26 +1472,35 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1084
1472
|
messageHex,
|
|
1085
1473
|
accounts: [{ path, xfp: record.masterFingerprint }]
|
|
1086
1474
|
});
|
|
1087
|
-
const responseUr = await this._resolveUr(
|
|
1475
|
+
const responseUr = await this._resolveUr(
|
|
1476
|
+
record,
|
|
1477
|
+
requestUr,
|
|
1478
|
+
true,
|
|
1479
|
+
connectId,
|
|
1480
|
+
signal,
|
|
1481
|
+
"btcSignMessage"
|
|
1482
|
+
);
|
|
1088
1483
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
1089
1484
|
const sig = this.urEngine.parseBtcSignature(responseUr);
|
|
1090
1485
|
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1091
|
-
return (0,
|
|
1486
|
+
return (0, import_hwk_adapter_core3.success)({ signature: sig.signature });
|
|
1092
1487
|
}
|
|
1093
1488
|
);
|
|
1094
1489
|
} catch (err) {
|
|
1095
1490
|
return this._errorToFailure(err);
|
|
1096
1491
|
}
|
|
1097
1492
|
}
|
|
1098
|
-
async btcGetMasterFingerprint(connectIdArg, deviceIdArg) {
|
|
1099
|
-
const
|
|
1493
|
+
async btcGetMasterFingerprint(connectIdArg, deviceIdArg, paramsArg) {
|
|
1494
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.interactionId);
|
|
1495
|
+
if (!operationTarget.success) return operationTarget;
|
|
1496
|
+
const connectId = operationTarget.payload.targetId;
|
|
1100
1497
|
const deviceId = deviceIdArg ?? void 0;
|
|
1101
1498
|
try {
|
|
1102
1499
|
return await this._jobQueue.enqueue(
|
|
1103
1500
|
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1104
1501
|
async (signal) => {
|
|
1105
|
-
const { record } = await this.
|
|
1106
|
-
return (0,
|
|
1502
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, "btc", signal);
|
|
1503
|
+
return (0, import_hwk_adapter_core3.success)({ masterFingerprint: record.masterFingerprint });
|
|
1107
1504
|
}
|
|
1108
1505
|
);
|
|
1109
1506
|
} catch (err) {
|
|
@@ -1113,28 +1510,26 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1113
1510
|
// ---------------------------------------------------------------------------
|
|
1114
1511
|
// SOL
|
|
1115
1512
|
// ---------------------------------------------------------------------------
|
|
1116
|
-
async solGetAddress(connectIdArg, deviceIdArg, paramsArg) {
|
|
1117
|
-
const
|
|
1513
|
+
async solGetAddress(connectIdArg, deviceIdArg, paramsArg, book) {
|
|
1514
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.interactionId);
|
|
1515
|
+
if (!operationTarget.success) return operationTarget;
|
|
1516
|
+
const connectId = operationTarget.payload.targetId;
|
|
1118
1517
|
const deviceId = deviceIdArg ?? void 0;
|
|
1119
1518
|
const params = paramsArg;
|
|
1120
|
-
if (!params) return (0,
|
|
1519
|
+
if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "solGetAddress requires params");
|
|
1121
1520
|
if (!params.path)
|
|
1122
|
-
return (0,
|
|
1521
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "solGetAddress requires params.path");
|
|
1123
1522
|
const path = normalizePath(params.path);
|
|
1124
1523
|
try {
|
|
1125
1524
|
return await this._jobQueue.enqueue(
|
|
1126
1525
|
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1127
1526
|
async (signal) => {
|
|
1128
|
-
const { account } = await this.
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
"sol",
|
|
1132
|
-
path,
|
|
1133
|
-
signal
|
|
1134
|
-
);
|
|
1527
|
+
const { account } = await this._fetchAccount(connectId, deviceId, "sol", path, signal, {
|
|
1528
|
+
book
|
|
1529
|
+
});
|
|
1135
1530
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
1136
1531
|
const address = import_bs58.default.encode(Buffer.from(account.publicKey, "hex"));
|
|
1137
|
-
return (0,
|
|
1532
|
+
return (0, import_hwk_adapter_core3.success)({ address, path });
|
|
1138
1533
|
}
|
|
1139
1534
|
);
|
|
1140
1535
|
} catch (err) {
|
|
@@ -1142,14 +1537,16 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1142
1537
|
}
|
|
1143
1538
|
}
|
|
1144
1539
|
async solSignTransaction(connectIdArg, deviceIdArg, paramsArg) {
|
|
1145
|
-
const
|
|
1540
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.interactionId);
|
|
1541
|
+
if (!operationTarget.success) return operationTarget;
|
|
1542
|
+
const connectId = operationTarget.payload.targetId;
|
|
1146
1543
|
const deviceId = deviceIdArg ?? void 0;
|
|
1147
1544
|
const params = paramsArg;
|
|
1148
1545
|
if (!params)
|
|
1149
|
-
return (0,
|
|
1546
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "solSignTransaction requires params");
|
|
1150
1547
|
if (!params.path || !params.serializedTx) {
|
|
1151
|
-
return (0,
|
|
1152
|
-
|
|
1548
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1549
|
+
import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
|
|
1153
1550
|
"solSignTransaction requires params.path and params.serializedTx"
|
|
1154
1551
|
);
|
|
1155
1552
|
}
|
|
@@ -1158,21 +1555,28 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1158
1555
|
return await this._jobQueue.enqueue(
|
|
1159
1556
|
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1160
1557
|
async (signal) => {
|
|
1161
|
-
const { record } = await this.
|
|
1558
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, "sol", signal);
|
|
1162
1559
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
1163
1560
|
const requestId = (0, import_uuid2.v4)();
|
|
1164
1561
|
const requestUr = this.urEngine.buildSolSignRequest({
|
|
1165
1562
|
requestId,
|
|
1166
|
-
unsignedPayloadHex: (0,
|
|
1563
|
+
unsignedPayloadHex: (0, import_hwk_adapter_core3.stripHex)(params.serializedTx),
|
|
1167
1564
|
dataType: "transaction",
|
|
1168
1565
|
path,
|
|
1169
1566
|
xfp: record.masterFingerprint
|
|
1170
1567
|
});
|
|
1171
|
-
const responseUr = await this._resolveUr(
|
|
1568
|
+
const responseUr = await this._resolveUr(
|
|
1569
|
+
record,
|
|
1570
|
+
requestUr,
|
|
1571
|
+
true,
|
|
1572
|
+
connectId,
|
|
1573
|
+
signal,
|
|
1574
|
+
"solSignTransaction"
|
|
1575
|
+
);
|
|
1172
1576
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
1173
1577
|
const sig = this.urEngine.parseSolSignature(responseUr);
|
|
1174
1578
|
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1175
|
-
return (0,
|
|
1579
|
+
return (0, import_hwk_adapter_core3.success)({ signature: sig.signature });
|
|
1176
1580
|
}
|
|
1177
1581
|
);
|
|
1178
1582
|
} catch (err) {
|
|
@@ -1180,13 +1584,15 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1180
1584
|
}
|
|
1181
1585
|
}
|
|
1182
1586
|
async solSignMessage(connectIdArg, deviceIdArg, paramsArg) {
|
|
1183
|
-
const
|
|
1587
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.interactionId);
|
|
1588
|
+
if (!operationTarget.success) return operationTarget;
|
|
1589
|
+
const connectId = operationTarget.payload.targetId;
|
|
1184
1590
|
const deviceId = deviceIdArg ?? void 0;
|
|
1185
1591
|
const params = paramsArg;
|
|
1186
|
-
if (!params) return (0,
|
|
1592
|
+
if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "solSignMessage requires params");
|
|
1187
1593
|
if (!params.path || !params.message) {
|
|
1188
|
-
return (0,
|
|
1189
|
-
|
|
1594
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1595
|
+
import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
|
|
1190
1596
|
"solSignMessage requires params.path and params.message"
|
|
1191
1597
|
);
|
|
1192
1598
|
}
|
|
@@ -1195,21 +1601,28 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1195
1601
|
return await this._jobQueue.enqueue(
|
|
1196
1602
|
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1197
1603
|
async (signal) => {
|
|
1198
|
-
const { record } = await this.
|
|
1604
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, "sol", signal);
|
|
1199
1605
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
1200
1606
|
const requestId = (0, import_uuid2.v4)();
|
|
1201
1607
|
const requestUr = this.urEngine.buildSolSignRequest({
|
|
1202
1608
|
requestId,
|
|
1203
|
-
unsignedPayloadHex: (0,
|
|
1609
|
+
unsignedPayloadHex: (0, import_hwk_adapter_core3.stripHex)(params.message),
|
|
1204
1610
|
dataType: "message",
|
|
1205
1611
|
path,
|
|
1206
1612
|
xfp: record.masterFingerprint
|
|
1207
1613
|
});
|
|
1208
|
-
const responseUr = await this._resolveUr(
|
|
1614
|
+
const responseUr = await this._resolveUr(
|
|
1615
|
+
record,
|
|
1616
|
+
requestUr,
|
|
1617
|
+
true,
|
|
1618
|
+
connectId,
|
|
1619
|
+
signal,
|
|
1620
|
+
"solSignMessage"
|
|
1621
|
+
);
|
|
1209
1622
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
1210
1623
|
const sig = this.urEngine.parseSolSignature(responseUr);
|
|
1211
1624
|
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1212
|
-
return (0,
|
|
1625
|
+
return (0, import_hwk_adapter_core3.success)({ signature: sig.signature });
|
|
1213
1626
|
}
|
|
1214
1627
|
);
|
|
1215
1628
|
} catch (err) {
|
|
@@ -1223,17 +1636,19 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1223
1636
|
// bundled `sdk.tron` module (different, protobuf-based protocol with
|
|
1224
1637
|
// unverified response semantics).
|
|
1225
1638
|
// ---------------------------------------------------------------------------
|
|
1226
|
-
async tronGetAddress(connectIdArg, deviceIdArg, paramsArg) {
|
|
1227
|
-
const
|
|
1639
|
+
async tronGetAddress(connectIdArg, deviceIdArg, paramsArg, book) {
|
|
1640
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.interactionId);
|
|
1641
|
+
if (!operationTarget.success) return operationTarget;
|
|
1642
|
+
const connectId = operationTarget.payload.targetId;
|
|
1228
1643
|
const deviceId = deviceIdArg ?? void 0;
|
|
1229
1644
|
const params = paramsArg;
|
|
1230
|
-
if (!params) return (0,
|
|
1645
|
+
if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "tronGetAddress requires params");
|
|
1231
1646
|
if (!params.path)
|
|
1232
|
-
return (0,
|
|
1647
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "tronGetAddress requires params.path");
|
|
1233
1648
|
const { accountPath, relativeDerivePath } = splitAccountPath(params.path);
|
|
1234
1649
|
if (!relativeDerivePath) {
|
|
1235
|
-
return (0,
|
|
1236
|
-
|
|
1650
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1651
|
+
import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
|
|
1237
1652
|
"tronGetAddress requires a full leaf path, e.g. m/44'/195'/0'/0/0"
|
|
1238
1653
|
);
|
|
1239
1654
|
}
|
|
@@ -1241,17 +1656,18 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1241
1656
|
return await this._jobQueue.enqueue(
|
|
1242
1657
|
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1243
1658
|
async (signal) => {
|
|
1244
|
-
const { account } = await this.
|
|
1659
|
+
const { account } = await this._fetchAccount(
|
|
1245
1660
|
connectId,
|
|
1246
1661
|
deviceId,
|
|
1247
1662
|
"tron",
|
|
1248
1663
|
accountPath,
|
|
1249
|
-
signal
|
|
1664
|
+
signal,
|
|
1665
|
+
{ book }
|
|
1250
1666
|
);
|
|
1251
1667
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
1252
1668
|
if (!account.extendedPublicKey) {
|
|
1253
|
-
throw (0,
|
|
1254
|
-
code:
|
|
1669
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1670
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
|
|
1255
1671
|
message: "Keystone did not return an extended public key for this account path"
|
|
1256
1672
|
});
|
|
1257
1673
|
}
|
|
@@ -1259,7 +1675,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1259
1675
|
account.extendedPublicKey,
|
|
1260
1676
|
relativeDerivePath
|
|
1261
1677
|
);
|
|
1262
|
-
return (0,
|
|
1678
|
+
return (0, import_hwk_adapter_core3.success)({ address, path: normalizePath(params.path) });
|
|
1263
1679
|
}
|
|
1264
1680
|
);
|
|
1265
1681
|
} catch (err) {
|
|
@@ -1267,27 +1683,29 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1267
1683
|
}
|
|
1268
1684
|
}
|
|
1269
1685
|
async tronSignTransaction(connectIdArg, deviceIdArg, paramsArg) {
|
|
1270
|
-
const
|
|
1686
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.interactionId);
|
|
1687
|
+
if (!operationTarget.success) return operationTarget;
|
|
1688
|
+
const connectId = operationTarget.payload.targetId;
|
|
1271
1689
|
const deviceId = deviceIdArg ?? void 0;
|
|
1272
1690
|
const params = paramsArg;
|
|
1273
1691
|
if (!params)
|
|
1274
|
-
return (0,
|
|
1692
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "tronSignTransaction requires params");
|
|
1275
1693
|
if (!params.path) {
|
|
1276
|
-
return (0,
|
|
1694
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "tronSignTransaction requires params.path");
|
|
1277
1695
|
}
|
|
1278
1696
|
if (!params.rawTxHex) {
|
|
1279
|
-
return (0,
|
|
1280
|
-
|
|
1697
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1698
|
+
import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
|
|
1281
1699
|
"Keystone only signs a fully protobuf-serialized TRON transaction (params.rawTxHex) \u2014 the Trezor-style structured contract fields have no equivalent here"
|
|
1282
1700
|
);
|
|
1283
1701
|
}
|
|
1284
|
-
const rawTxHex = (0,
|
|
1702
|
+
const rawTxHex = (0, import_hwk_adapter_core3.stripHex)(params.rawTxHex);
|
|
1285
1703
|
const path = normalizePath(params.path);
|
|
1286
1704
|
try {
|
|
1287
1705
|
return await this._jobQueue.enqueue(
|
|
1288
1706
|
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1289
1707
|
async (signal) => {
|
|
1290
|
-
const { record } = await this.
|
|
1708
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, "tron", signal);
|
|
1291
1709
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
1292
1710
|
const requestId = (0, import_uuid2.v4)();
|
|
1293
1711
|
const requestUr = this.urEngine.buildTronSignRequest({
|
|
@@ -1296,38 +1714,262 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1296
1714
|
path,
|
|
1297
1715
|
xfp: record.masterFingerprint
|
|
1298
1716
|
});
|
|
1299
|
-
const responseUr = await this._resolveUr(
|
|
1717
|
+
const responseUr = await this._resolveUr(
|
|
1718
|
+
record,
|
|
1719
|
+
requestUr,
|
|
1720
|
+
true,
|
|
1721
|
+
connectId,
|
|
1722
|
+
signal,
|
|
1723
|
+
"tronSignTransaction"
|
|
1724
|
+
);
|
|
1300
1725
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
1301
1726
|
const sig = this.urEngine.parseTronSignature(responseUr);
|
|
1302
1727
|
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1303
|
-
return (0,
|
|
1728
|
+
return (0, import_hwk_adapter_core3.success)({ signature: sig.signature });
|
|
1304
1729
|
}
|
|
1305
1730
|
);
|
|
1306
1731
|
} catch (err) {
|
|
1307
1732
|
return this._errorToFailure(err);
|
|
1308
1733
|
}
|
|
1309
1734
|
}
|
|
1310
|
-
async tronSignMessage(
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1735
|
+
async tronSignMessage(connectIdArg, deviceIdArg, paramsArg) {
|
|
1736
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.interactionId);
|
|
1737
|
+
if (!operationTarget.success) return operationTarget;
|
|
1738
|
+
const connectId = operationTarget.payload.targetId;
|
|
1739
|
+
const deviceId = deviceIdArg ?? void 0;
|
|
1740
|
+
const params = paramsArg;
|
|
1741
|
+
if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "tronSignMessage requires params");
|
|
1742
|
+
if (!params.path) {
|
|
1743
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "tronSignMessage requires params.path");
|
|
1744
|
+
}
|
|
1745
|
+
if (!params.messageHex) {
|
|
1746
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "tronSignMessage requires params.messageHex");
|
|
1747
|
+
}
|
|
1748
|
+
if (params.messageType !== "V2") {
|
|
1749
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1750
|
+
import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
|
|
1751
|
+
'Keystone signs TRON messages as TIP-191 signMessageV2 only (messageType: "V2")'
|
|
1752
|
+
);
|
|
1753
|
+
}
|
|
1754
|
+
const messageHex = (0, import_hwk_adapter_core3.stripHex)(params.messageHex);
|
|
1755
|
+
const path = normalizePath(params.path);
|
|
1756
|
+
try {
|
|
1757
|
+
return await this._jobQueue.enqueue(
|
|
1758
|
+
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1759
|
+
async (signal) => {
|
|
1760
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, "tron", signal);
|
|
1761
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1762
|
+
const requestId = (0, import_uuid2.v4)();
|
|
1763
|
+
const requestUr = this.urEngine.buildTronSignRequest({
|
|
1764
|
+
requestId,
|
|
1765
|
+
rawTxHex: messageHex,
|
|
1766
|
+
path,
|
|
1767
|
+
xfp: record.masterFingerprint,
|
|
1768
|
+
signType: 2 /* PersonalMessage */
|
|
1769
|
+
});
|
|
1770
|
+
const responseUr = await this._resolveUr(
|
|
1771
|
+
record,
|
|
1772
|
+
requestUr,
|
|
1773
|
+
true,
|
|
1774
|
+
connectId,
|
|
1775
|
+
signal,
|
|
1776
|
+
"tronSignMessage"
|
|
1777
|
+
);
|
|
1778
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1779
|
+
const sig = this.urEngine.parseTronSignature(responseUr);
|
|
1780
|
+
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1781
|
+
return (0, import_hwk_adapter_core3.success)({ signature: sig.signature });
|
|
1782
|
+
}
|
|
1783
|
+
);
|
|
1784
|
+
} catch (err) {
|
|
1785
|
+
return this._errorToFailure(err);
|
|
1786
|
+
}
|
|
1315
1787
|
}
|
|
1316
1788
|
// ---------------------------------------------------------------------------
|
|
1317
1789
|
// Internals
|
|
1318
1790
|
// ---------------------------------------------------------------------------
|
|
1319
|
-
|
|
1320
|
-
|
|
1791
|
+
_walletIdFromIdentifier(identifier) {
|
|
1792
|
+
const value = identifier.startsWith(KEYSTONE_WALLET_CONNECT_ID_PREFIX) ? identifier.slice(KEYSTONE_WALLET_CONNECT_ID_PREFIX.length) : identifier;
|
|
1793
|
+
return /^[0-9a-f]{64}$/i.test(value) ? value.toLowerCase() : void 0;
|
|
1794
|
+
}
|
|
1795
|
+
_allNetworkSyncSchema(item) {
|
|
1796
|
+
if (!(0, import_hwk_adapter_core3.isAllNetworkMethodName)(String(item.methodName)) || !item.path) return void 0;
|
|
1797
|
+
const method = item.methodName;
|
|
1798
|
+
const hwkChain = (0, import_hwk_adapter_core3.getAllNetworkMethodChain)(method);
|
|
1799
|
+
const path = normalizePath(item.path);
|
|
1800
|
+
if (hwkChain === "btc" && !isKeystoneSignableBtcAccountPath(splitAccountPath(path).accountPath)) {
|
|
1801
|
+
return void 0;
|
|
1802
|
+
}
|
|
1803
|
+
switch (method) {
|
|
1804
|
+
case "evmGetAddress":
|
|
1805
|
+
case "btcGetAddress":
|
|
1806
|
+
case "tronGetAddress":
|
|
1807
|
+
return { hwkChain, path: splitAccountPath(path).accountPath };
|
|
1808
|
+
case "btcGetPublicKey":
|
|
1809
|
+
case "solGetAddress":
|
|
1810
|
+
return { hwkChain, path };
|
|
1811
|
+
default:
|
|
1812
|
+
return void 0;
|
|
1813
|
+
}
|
|
1321
1814
|
}
|
|
1322
1815
|
/**
|
|
1323
|
-
*
|
|
1324
|
-
*
|
|
1325
|
-
*
|
|
1326
|
-
*
|
|
1816
|
+
* QR is an interactive batch transport: one account-creation action must
|
|
1817
|
+
* produce one request QR and one response scan, regardless of how many
|
|
1818
|
+
* chains or derivation paths the host bundled. USB keeps its proven
|
|
1819
|
+
* one-path-at-a-time export flow because the device limits that channel.
|
|
1327
1820
|
*/
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1821
|
+
async _prefetchAllNetworkAccounts(connectId, deviceId, params) {
|
|
1822
|
+
return this._jobQueue.enqueue(deviceId || connectId || COLD_START_JOB_LABEL, async (signal) => {
|
|
1823
|
+
const target = this._resolveTarget(connectId, deviceId);
|
|
1824
|
+
const { record: targetRecord } = target;
|
|
1825
|
+
let record = targetRecord;
|
|
1826
|
+
const requestedByKey = /* @__PURE__ */ new Map();
|
|
1827
|
+
for (const item of params.bundle) {
|
|
1828
|
+
const schema = this._allNetworkSyncSchema(item);
|
|
1829
|
+
if (schema) requestedByKey.set(accountKey(schema.hwkChain, schema.path), schema);
|
|
1830
|
+
}
|
|
1831
|
+
const book = /* @__PURE__ */ new Map();
|
|
1832
|
+
if (!requestedByKey.size) {
|
|
1833
|
+
return { book };
|
|
1834
|
+
}
|
|
1835
|
+
if (this._forcedTransport !== "qr" && !record?.usbSessionId && this._usbConnector && target.expectedWalletId) {
|
|
1836
|
+
record = await this._tryUsbAttach(
|
|
1837
|
+
target.expectedWalletId,
|
|
1838
|
+
record?.masterFingerprint ?? target.expectedMasterFingerprint
|
|
1839
|
+
) ?? record;
|
|
1840
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1841
|
+
}
|
|
1842
|
+
if (this._forcedTransport !== "qr" && record?.usbSessionId) {
|
|
1843
|
+
const missingSchemas2 = Array.from(requestedByKey.values());
|
|
1844
|
+
for (const [index, schema] of missingSchemas2.entries()) {
|
|
1845
|
+
if (!book.has(accountKey(schema.hwkChain, schema.path))) {
|
|
1846
|
+
const synced = await this._fetchAccount(
|
|
1847
|
+
connectId,
|
|
1848
|
+
deviceId,
|
|
1849
|
+
schema.hwkChain,
|
|
1850
|
+
schema.path,
|
|
1851
|
+
signal,
|
|
1852
|
+
{ qrFallbackSchemaPaths: missingSchemas2.slice(index), book }
|
|
1853
|
+
);
|
|
1854
|
+
record = synced.record;
|
|
1855
|
+
if (!record.usbSessionId) break;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
for (const schema of missingSchemas2) {
|
|
1859
|
+
if (!book.has(accountKey(schema.hwkChain, schema.path))) {
|
|
1860
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1861
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
1862
|
+
message: `Keystone did not return the requested derivation path (${schema.path})`
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
return { book, walletId: record.walletId };
|
|
1867
|
+
}
|
|
1868
|
+
if (this._forcedTransport === "usb") {
|
|
1869
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1870
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.TransportNotAvailable,
|
|
1871
|
+
message: "USB channel is not connected for this Keystone wallet"
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
const missingSchemas = Array.from(requestedByKey.values());
|
|
1875
|
+
const schemas = [
|
|
1876
|
+
{ hwkChain: "evm", path: KEYSTONE_WALLET_ID_PATH },
|
|
1877
|
+
...missingSchemas.filter((schema) => normalizePath(schema.path) !== KEYSTONE_WALLET_ID_PATH)
|
|
1878
|
+
];
|
|
1879
|
+
const requestUr = this.urEngine.buildKeyDerivationRequest({
|
|
1880
|
+
schemas: schemas.map((schema) => ({
|
|
1881
|
+
path: schema.path,
|
|
1882
|
+
curve: schema.hwkChain === "sol" ? "ed25519" : "secp256k1"
|
|
1883
|
+
})),
|
|
1884
|
+
origin: this._origin
|
|
1885
|
+
});
|
|
1886
|
+
const responseUr = await this._requestQrDisplayAndAwaitResponse(
|
|
1887
|
+
record ? toDeviceInfo(record) : placeholderDeviceInfo(),
|
|
1888
|
+
{ ...requestUr, animated: false }
|
|
1889
|
+
);
|
|
1890
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1891
|
+
const parsed = this.urEngine.parseAccountResponse(responseUr);
|
|
1892
|
+
this._assertParsedIdentity(parsed, target);
|
|
1893
|
+
record = this._upsertDeviceRecord(parsed);
|
|
1894
|
+
const requestedChainByPath = new Map(
|
|
1895
|
+
schemas.map((schema) => [normalizePath(schema.path), schema.hwkChain])
|
|
1896
|
+
);
|
|
1897
|
+
for (const account of parsed.accounts) {
|
|
1898
|
+
const normalizedPath = normalizePath(account.path);
|
|
1899
|
+
const hwkChain = requestedChainByPath.get(normalizedPath) ?? inferHwkChainFromPath(normalizedPath);
|
|
1900
|
+
if (hwkChain) {
|
|
1901
|
+
book.set(accountKey(hwkChain, normalizedPath), {
|
|
1902
|
+
...account,
|
|
1903
|
+
hwkChain
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
for (const schema of missingSchemas) {
|
|
1908
|
+
if (!book.has(accountKey(schema.hwkChain, schema.path))) {
|
|
1909
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1910
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
1911
|
+
message: `Keystone did not return the requested derivation path (${schema.path})`
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
return { book, walletId: record.walletId };
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
/**
|
|
1919
|
+
* Resolve only the stable 64-hex wallet identity. The 8-hex BIP32 master
|
|
1920
|
+
* fingerprint is protocol metadata and is never accepted as a lookup key.
|
|
1921
|
+
*/
|
|
1922
|
+
_resolveTarget(connectId, deviceId) {
|
|
1923
|
+
let resolvedConnectId = connectId;
|
|
1924
|
+
if ((0, import_hwk_adapter_core3.isHardwareInteractionId)(connectId)) {
|
|
1925
|
+
resolvedConnectId = this._interactions.resolve(connectId).connectId;
|
|
1926
|
+
}
|
|
1927
|
+
const identifiers = [deviceId, resolvedConnectId].filter(
|
|
1928
|
+
(identifier) => Boolean(identifier)
|
|
1929
|
+
);
|
|
1930
|
+
if (!identifiers.length) return {};
|
|
1931
|
+
const walletIds = identifiers.map((identifier) => this._walletIdFromIdentifier(identifier)).filter((walletId) => Boolean(walletId));
|
|
1932
|
+
if (!walletIds.length) {
|
|
1933
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1934
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
|
|
1935
|
+
message: "Keystone device lookup requires a 64-hex wallet identity"
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
const expectedWalletId = walletIds[0];
|
|
1939
|
+
if (walletIds.some((walletId) => walletId !== expectedWalletId)) {
|
|
1940
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1941
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
1942
|
+
message: "Keystone connectId and deviceId identify different wallets"
|
|
1943
|
+
});
|
|
1944
|
+
}
|
|
1945
|
+
if (identifiers.some((identifier) => !this._walletIdFromIdentifier(identifier))) {
|
|
1946
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1947
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
1948
|
+
message: "Keystone connectId and deviceId are inconsistent"
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1951
|
+
const record = this._devices.get(expectedWalletId);
|
|
1952
|
+
return record ? {
|
|
1953
|
+
record,
|
|
1954
|
+
expectedWalletId,
|
|
1955
|
+
expectedMasterFingerprint: record.masterFingerprint
|
|
1956
|
+
} : { expectedWalletId };
|
|
1957
|
+
}
|
|
1958
|
+
_assertParsedIdentity(parsed, expected) {
|
|
1959
|
+
const walletId = deriveKeystoneWalletId(parsed.accounts);
|
|
1960
|
+
if (expected.expectedMasterFingerprint && parsed.masterFingerprint !== expected.expectedMasterFingerprint) {
|
|
1961
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1962
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
1963
|
+
message: `Connected Keystone wallet (mfp ${parsed.masterFingerprint}) does not match the requested wallet fingerprint (${expected.expectedMasterFingerprint})`
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
if (expected.expectedWalletId && walletId !== expected.expectedWalletId) {
|
|
1967
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1968
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
1969
|
+
message: "Connected Keystone wallet does not match the requested wallet identity"
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
return walletId;
|
|
1331
1973
|
}
|
|
1332
1974
|
/**
|
|
1333
1975
|
* Folds a parsed account-response UR into the device table. `viaUsb`
|
|
@@ -1336,169 +1978,498 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1336
1978
|
* already has a live session, so this must NOT unconditionally mark
|
|
1337
1979
|
* `qrSynced`, or a USB-only wallet would wrongly survive a later USB
|
|
1338
1980
|
* disconnect as a "QR-synced, demote to QR-only" entry instead of being
|
|
1339
|
-
* dropped outright (see `
|
|
1981
|
+
* dropped outright (see `releaseInteraction`).
|
|
1340
1982
|
*/
|
|
1341
1983
|
_upsertDeviceRecord(parsed, options) {
|
|
1342
|
-
const
|
|
1343
|
-
let record = this._devices.get(
|
|
1984
|
+
const walletId = deriveKeystoneWalletId(parsed.accounts);
|
|
1985
|
+
let record = this._devices.get(walletId);
|
|
1344
1986
|
const isNew = !record;
|
|
1345
1987
|
if (!record) {
|
|
1346
|
-
record = createDeviceRecord(
|
|
1347
|
-
this._devices.set(
|
|
1988
|
+
record = createDeviceRecord(walletId, parsed.masterFingerprint);
|
|
1989
|
+
this._devices.set(walletId, record);
|
|
1990
|
+
} else if (record.masterFingerprint !== parsed.masterFingerprint) {
|
|
1991
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1992
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
1993
|
+
message: "Keystone wallet identity returned an inconsistent master fingerprint"
|
|
1994
|
+
});
|
|
1348
1995
|
}
|
|
1349
1996
|
record.model = parsed.device ?? record.model;
|
|
1350
1997
|
record.deviceVersion = parsed.deviceVersion ?? record.deviceVersion;
|
|
1998
|
+
record.hardwareDeviceId = parsed.deviceId ?? record.hardwareDeviceId;
|
|
1999
|
+
record.usbSessionId = options?.usbSessionId ?? record.usbSessionId;
|
|
1351
2000
|
if (!options?.viaUsb) record.qrSynced = true;
|
|
1352
2001
|
const info = toDeviceInfo(record);
|
|
1353
|
-
const eventType = isNew ?
|
|
2002
|
+
const eventType = isNew ? import_hwk_adapter_core3.DEVICE.CONNECT : import_hwk_adapter_core3.DEVICE.CHANGED;
|
|
1354
2003
|
this.emitter.emit(eventType, { type: eventType, payload: info });
|
|
1355
2004
|
return record;
|
|
1356
2005
|
}
|
|
1357
2006
|
/**
|
|
1358
2007
|
* Opens+claims whatever Keystone the USB connector currently has
|
|
1359
|
-
* permission for, learns its mfp via `getAppConfig`,
|
|
1360
|
-
*
|
|
2008
|
+
* permission for, learns its protocol mfp via `getAppConfig`, then requests
|
|
2009
|
+
* one fixed account-level xpub and derives the stable wallet id. A
|
|
2010
|
+
* QR-synced entry becomes
|
|
1361
2011
|
* `{qr, usb}`-capable in place (one `device-changed`, not a second
|
|
1362
2012
|
* `device-connect`); a wallet never seen before becomes a new USB-only
|
|
1363
2013
|
* entry. See §4.2 of the design doc.
|
|
1364
2014
|
*/
|
|
1365
|
-
async _connectUsb() {
|
|
2015
|
+
async _connectUsb(expected, searchTargetId) {
|
|
2016
|
+
if (this._pendingUsbTeardowns > 0) {
|
|
2017
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
2018
|
+
import_hwk_adapter_core3.HardwareErrorCode.DeviceBusyInternal,
|
|
2019
|
+
"Keystone USB session is being released"
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
const previous = this._usbConnectTail;
|
|
2023
|
+
let release;
|
|
2024
|
+
this._usbConnectTail = new Promise((resolve) => {
|
|
2025
|
+
release = resolve;
|
|
2026
|
+
});
|
|
2027
|
+
await previous;
|
|
2028
|
+
try {
|
|
2029
|
+
return await this._connectUsbExclusive(expected, searchTargetId);
|
|
2030
|
+
} finally {
|
|
2031
|
+
release?.();
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
async _connectUsbExclusive(expected, searchTargetId) {
|
|
1366
2035
|
if (!this._usbConnector) {
|
|
1367
|
-
return (0,
|
|
1368
|
-
|
|
2036
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
2037
|
+
import_hwk_adapter_core3.HardwareErrorCode.TransportNotAvailable,
|
|
1369
2038
|
"No USB connector configured for this Keystone adapter"
|
|
1370
2039
|
);
|
|
1371
2040
|
}
|
|
2041
|
+
let sessionId;
|
|
1372
2042
|
try {
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
2043
|
+
let connectTarget = { type: "default" };
|
|
2044
|
+
if (searchTargetId) {
|
|
2045
|
+
connectTarget = { type: "search-target", searchTargetId };
|
|
2046
|
+
} else if (expected.expectedMasterFingerprint) {
|
|
2047
|
+
connectTarget = {
|
|
2048
|
+
type: "expected-device-identity",
|
|
2049
|
+
deviceIdentity: expected.expectedMasterFingerprint
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
const connector = this._usbConnector;
|
|
2053
|
+
const session = await (connector.connectTarget ? connector.connectTarget(connectTarget) : connector.connect(searchTargetId ?? expected.expectedMasterFingerprint));
|
|
2054
|
+
sessionId = session.sessionId;
|
|
2055
|
+
const raw = session.deviceInfo.raw;
|
|
2056
|
+
const mfpValue = (0, import_hwk_adapter_core3.parseBip32MasterFingerprint)(raw?.masterFingerprint);
|
|
2057
|
+
if (!mfpValue) {
|
|
2058
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2059
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
2060
|
+
message: "Keystone USB did not report a master fingerprint"
|
|
2061
|
+
});
|
|
1380
2062
|
}
|
|
2063
|
+
const masterFingerprint = mfpValue.toLowerCase();
|
|
2064
|
+
if (expected.expectedMasterFingerprint && masterFingerprint !== expected.expectedMasterFingerprint) {
|
|
2065
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2066
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
2067
|
+
message: `Connected Keystone wallet (mfp ${masterFingerprint}) does not match the requested wallet fingerprint (${expected.expectedMasterFingerprint})`
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
2070
|
+
const identityRequest = this.urEngine.buildKeyDerivationRequest({
|
|
2071
|
+
schemas: [{ path: KEYSTONE_WALLET_ID_PATH, curve: "secp256k1" }],
|
|
2072
|
+
origin: this._origin
|
|
2073
|
+
});
|
|
2074
|
+
const identityResult = await this._callUsbConnector(
|
|
2075
|
+
session.sessionId,
|
|
2076
|
+
"resolveUr",
|
|
2077
|
+
identityRequest
|
|
2078
|
+
);
|
|
2079
|
+
if (!identityResult.success) {
|
|
2080
|
+
throw (0, import_hwk_adapter_core3.rehydrateConnectorError)(identityResult.error);
|
|
2081
|
+
}
|
|
2082
|
+
const parsed = this.urEngine.parseAccountResponse(identityResult.payload);
|
|
2083
|
+
this._assertParsedIdentity(parsed, {
|
|
2084
|
+
...expected,
|
|
2085
|
+
expectedMasterFingerprint: expected.expectedMasterFingerprint ?? masterFingerprint
|
|
2086
|
+
});
|
|
2087
|
+
const walletId = deriveKeystoneWalletId(parsed.accounts);
|
|
2088
|
+
const previousUsbSessionId = this._devices.get(walletId)?.usbSessionId;
|
|
2089
|
+
const record = this._upsertDeviceRecord(parsed, {
|
|
2090
|
+
viaUsb: true,
|
|
2091
|
+
usbSessionId: session.sessionId
|
|
2092
|
+
});
|
|
2093
|
+
if (previousUsbSessionId && previousUsbSessionId !== session.sessionId) {
|
|
2094
|
+
try {
|
|
2095
|
+
await this._usbConnector.disconnect(previousUsbSessionId);
|
|
2096
|
+
} catch {
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
record.hadUsbSession = true;
|
|
1381
2100
|
record.model = session.deviceInfo.modelName ?? session.deviceInfo.model ?? record.model;
|
|
1382
2101
|
record.deviceVersion = session.deviceInfo.firmwareVersion ?? record.deviceVersion;
|
|
1383
|
-
record.usbSessionId = session.sessionId;
|
|
1384
2102
|
const info = toDeviceInfo(record);
|
|
1385
|
-
|
|
1386
|
-
this.emitter.emit(eventType, { type: eventType, payload: info });
|
|
1387
|
-
return (0, import_hwk_adapter_core.success)(info);
|
|
2103
|
+
return (0, import_hwk_adapter_core3.success)(info);
|
|
1388
2104
|
} catch (err) {
|
|
2105
|
+
if (sessionId) {
|
|
2106
|
+
try {
|
|
2107
|
+
await this._usbConnector.disconnect(sessionId);
|
|
2108
|
+
} catch {
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
1389
2111
|
return this._errorToFailure(err);
|
|
1390
2112
|
}
|
|
1391
2113
|
}
|
|
2114
|
+
/**
|
|
2115
|
+
* Probe already-authorized USB devices without opening a permission picker,
|
|
2116
|
+
* then attach and verify the requested wallet when one is present. This is
|
|
2117
|
+
* used both after an adapter restart and after a QR-only period, so plugging
|
|
2118
|
+
* USB back in affects the next business call without changing an in-flight
|
|
2119
|
+
* QR interaction.
|
|
2120
|
+
*/
|
|
2121
|
+
async _tryUsbAttach(expectedWalletId, expectedMasterFingerprint, options) {
|
|
2122
|
+
if (!expectedWalletId || this._forcedTransport === "qr" || !this._usbConnector) {
|
|
2123
|
+
return void 0;
|
|
2124
|
+
}
|
|
2125
|
+
const maxAttempts = options?.waitForReenumeration ? KEYSTONE_USB_REATTACH_PROBE_ATTEMPTS : 1;
|
|
2126
|
+
let availableDevices = [];
|
|
2127
|
+
let lastSearchError;
|
|
2128
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
2129
|
+
try {
|
|
2130
|
+
availableDevices = await this._usbConnector.searchDevices({ purpose: "availability" });
|
|
2131
|
+
lastSearchError = void 0;
|
|
2132
|
+
} catch (error) {
|
|
2133
|
+
lastSearchError = error;
|
|
2134
|
+
}
|
|
2135
|
+
if (availableDevices.length) {
|
|
2136
|
+
break;
|
|
2137
|
+
}
|
|
2138
|
+
if (attempt < maxAttempts) {
|
|
2139
|
+
await waitForKeystoneUsbReattachProbe();
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
if (!availableDevices.length) {
|
|
2143
|
+
if (this._forcedTransport === "usb" && lastSearchError) {
|
|
2144
|
+
throw lastSearchError instanceof Error ? lastSearchError : new Error(String(lastSearchError));
|
|
2145
|
+
}
|
|
2146
|
+
return void 0;
|
|
2147
|
+
}
|
|
2148
|
+
let attached;
|
|
2149
|
+
let record;
|
|
2150
|
+
const candidates = expectedMasterFingerprint ? [void 0] : availableDevices;
|
|
2151
|
+
for (const device of candidates) {
|
|
2152
|
+
attached = await this._connectUsb(
|
|
2153
|
+
{ expectedWalletId, expectedMasterFingerprint },
|
|
2154
|
+
device?.connectId
|
|
2155
|
+
);
|
|
2156
|
+
if (attached.success) {
|
|
2157
|
+
record = this._devices.get(expectedWalletId);
|
|
2158
|
+
if (record) break;
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
return record;
|
|
2162
|
+
}
|
|
1392
2163
|
/**
|
|
1393
2164
|
* The one place that decides QR vs. USB for a UR round trip and carries it
|
|
1394
2165
|
* out. `record` is the (possibly not-yet-existing, for a true cold start)
|
|
1395
|
-
* device row for the target wallet
|
|
1396
|
-
*
|
|
1397
|
-
*
|
|
2166
|
+
* device row for the target wallet. USB is the preferred channel: a known
|
|
2167
|
+
* wallet record with no live session gets ONE best-effort re-attach here,
|
|
2168
|
+
* and anything that goes wrong just leaves the call on QR. A
|
|
1398
2169
|
* `switchTransport('qr')` pin forces QR even for a USB-attached wallet;
|
|
1399
2170
|
* `switchTransport('usb')` on a wallet with no live USB session fails
|
|
1400
2171
|
* closed rather than silently falling back to QR.
|
|
1401
2172
|
*/
|
|
1402
|
-
async _resolveUr(record, requestUr, animated) {
|
|
1403
|
-
const
|
|
2173
|
+
async _resolveUr(record, requestUr, animated, interactionId, signal, operationName) {
|
|
2174
|
+
const releaseInteractionRetention = (0, import_hwk_adapter_core3.isHardwareInteractionId)(interactionId) ? this._interactions.retain(interactionId) : void 0;
|
|
2175
|
+
try {
|
|
2176
|
+
return await this._resolveUrWithRoute(
|
|
2177
|
+
record,
|
|
2178
|
+
requestUr,
|
|
2179
|
+
animated,
|
|
2180
|
+
interactionId,
|
|
2181
|
+
signal,
|
|
2182
|
+
operationName
|
|
2183
|
+
);
|
|
2184
|
+
} finally {
|
|
2185
|
+
releaseInteractionRetention?.();
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
async _resolveUrWithRoute(record, requestUr, animated, interactionId, signal, operationName) {
|
|
2189
|
+
let interactionRoute;
|
|
2190
|
+
if ((0, import_hwk_adapter_core3.isHardwareInteractionId)(interactionId)) {
|
|
2191
|
+
const interaction = this._interactions.resolve(interactionId);
|
|
2192
|
+
const route = this._interactionRoutes.get(interaction.connectId);
|
|
2193
|
+
if (!route || route.interactionId !== interactionId) {
|
|
2194
|
+
this._interactions.end(interactionId, "disconnect");
|
|
2195
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2196
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.InteractionEnded,
|
|
2197
|
+
message: "Keystone hardware interaction is no longer connected",
|
|
2198
|
+
params: { interactionId, reason: "disconnect" }
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
interactionRoute = route;
|
|
2202
|
+
}
|
|
2203
|
+
if (!interactionRoute && !this._forcedTransport && record && !record.usbSessionId && this._usbConnector) {
|
|
2204
|
+
await this._tryUsbAttach(record.walletId, record.masterFingerprint, {
|
|
2205
|
+
waitForReenumeration: record.hadUsbSession
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
const wantUsb = interactionRoute ? interactionRoute.connectionType === "usb" : this._forcedTransport === "usb" || this._forcedTransport !== "qr" && Boolean(record?.usbSessionId);
|
|
1404
2209
|
if (wantUsb) {
|
|
1405
2210
|
if (!record?.usbSessionId || !this._usbConnector) {
|
|
1406
|
-
|
|
1407
|
-
|
|
2211
|
+
if (interactionRoute) {
|
|
2212
|
+
this._interactions.end(interactionRoute.interactionId, "disconnect");
|
|
2213
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2214
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.InteractionEnded,
|
|
2215
|
+
message: "Keystone interaction USB connection was lost",
|
|
2216
|
+
params: {
|
|
2217
|
+
interactionId: interactionRoute.interactionId,
|
|
2218
|
+
reason: "disconnect"
|
|
2219
|
+
}
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2223
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.TransportNotAvailable,
|
|
1408
2224
|
message: "USB channel is not connected for this Keystone wallet"
|
|
1409
2225
|
});
|
|
1410
2226
|
}
|
|
1411
|
-
const result = await this.
|
|
1412
|
-
|
|
1413
|
-
|
|
2227
|
+
const result = await this._callUsbConnector(
|
|
2228
|
+
record.usbSessionId,
|
|
2229
|
+
"resolveUr",
|
|
2230
|
+
requestUr,
|
|
2231
|
+
signal
|
|
2232
|
+
);
|
|
2233
|
+
if (result.success) return result.payload;
|
|
2234
|
+
const usbError = (0, import_hwk_adapter_core3.rehydrateConnectorError)(result.error);
|
|
2235
|
+
const usbErrorOrigin = usbError.origin;
|
|
2236
|
+
const usbErrorCode = usbError.code;
|
|
2237
|
+
if (!interactionRoute && !this._forcedTransport && usbErrorCode === import_hwk_adapter_core3.HardwareErrorCode.PayloadTooLarge && usbErrorOrigin !== "device") {
|
|
2238
|
+
const displayDevice2 = toDeviceInfo(record);
|
|
2239
|
+
return this._requestQrDisplayAndAwaitResponse(displayDevice2, {
|
|
2240
|
+
...requestUr,
|
|
2241
|
+
animated
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
const deviceAnswered = usbErrorOrigin !== void 0 ? usbErrorOrigin === "device" : usbErrorCode === import_hwk_adapter_core3.HardwareErrorCode.UserRejected || usbErrorCode === import_hwk_adapter_core3.HardwareErrorCode.UserAborted || usbErrorCode === import_hwk_adapter_core3.HardwareErrorCode.DeviceLocked || usbErrorCode === import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch;
|
|
2245
|
+
if (!deviceAnswered) {
|
|
2246
|
+
const lostSessionId = record.usbSessionId;
|
|
2247
|
+
if (lostSessionId) {
|
|
2248
|
+
this._usbConnector?.disconnect(lostSessionId).catch(() => void 0);
|
|
2249
|
+
}
|
|
2250
|
+
record.usbSessionId = void 0;
|
|
2251
|
+
if (interactionRoute) {
|
|
2252
|
+
this._interactions.end(interactionRoute.interactionId, "disconnect");
|
|
2253
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2254
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.InteractionEnded,
|
|
2255
|
+
message: operationName ? `Keystone ${operationName} may have completed before the USB connection was lost` : "Keystone interaction USB connection was lost",
|
|
2256
|
+
recovery: operationName ? { scope: "unknown" } : void 0,
|
|
2257
|
+
params: operationName ? (0, import_hwk_adapter_core3.operationMayHaveCompletedParams)(operationName, {
|
|
2258
|
+
interactionId: interactionRoute.interactionId,
|
|
2259
|
+
reason: "disconnect"
|
|
2260
|
+
}) : {
|
|
2261
|
+
interactionId: interactionRoute.interactionId,
|
|
2262
|
+
reason: "disconnect"
|
|
2263
|
+
}
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2266
|
+
if (operationName) {
|
|
2267
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2268
|
+
code: usbErrorCode ?? import_hwk_adapter_core3.HardwareErrorCode.TransportError,
|
|
2269
|
+
message: `Keystone ${operationName} may have completed before the USB connection was lost`,
|
|
2270
|
+
origin: "transport",
|
|
2271
|
+
recovery: { scope: "unknown" },
|
|
2272
|
+
params: (0, import_hwk_adapter_core3.operationMayHaveCompletedParams)(operationName)
|
|
2273
|
+
});
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
throw usbError;
|
|
1414
2277
|
}
|
|
1415
2278
|
const displayDevice = record ? toDeviceInfo(record) : placeholderDeviceInfo();
|
|
1416
|
-
|
|
2279
|
+
try {
|
|
2280
|
+
return await this._requestQrDisplayAndAwaitResponse(displayDevice, {
|
|
2281
|
+
...requestUr,
|
|
2282
|
+
animated
|
|
2283
|
+
});
|
|
2284
|
+
} catch (error) {
|
|
2285
|
+
const { code, _tag: tag } = error;
|
|
2286
|
+
if (operationName && (code === import_hwk_adapter_core3.HardwareErrorCode.OperationTimeout || tag === import_hwk_adapter_core3.UI_REQUEST_TIMEOUT_TAG)) {
|
|
2287
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2288
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.OperationTimeout,
|
|
2289
|
+
message: `Keystone ${operationName} may have completed before the QR response timed out`,
|
|
2290
|
+
recovery: { scope: "unknown" },
|
|
2291
|
+
params: (0, import_hwk_adapter_core3.operationMayHaveCompletedParams)(operationName)
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
throw error;
|
|
2295
|
+
}
|
|
1417
2296
|
}
|
|
1418
2297
|
/**
|
|
1419
|
-
*
|
|
1420
|
-
* `
|
|
1421
|
-
*
|
|
1422
|
-
*
|
|
2298
|
+
* Fetch one account's key material from the device for this operation only.
|
|
2299
|
+
* `book` lets earlier fetches of the same operation be reused; nothing is
|
|
2300
|
+
* retained on the record. On a USB drop the remaining paths fall back to one
|
|
2301
|
+
* QR request.
|
|
1423
2302
|
*/
|
|
1424
|
-
async
|
|
1425
|
-
const
|
|
2303
|
+
async _fetchAccount(connectId, deviceId, hwkChain, syncPath, signal, options) {
|
|
2304
|
+
const target = this._resolveTarget(connectId, deviceId);
|
|
1426
2305
|
const key = accountKey(hwkChain, syncPath);
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
2306
|
+
let existingRecord = target.record;
|
|
2307
|
+
if (!existingRecord) {
|
|
2308
|
+
const attached = await this._tryUsbAttach(target.expectedWalletId);
|
|
2309
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2310
|
+
existingRecord = attached;
|
|
2311
|
+
}
|
|
2312
|
+
const book = options?.book;
|
|
2313
|
+
const booked = book?.get(key);
|
|
2314
|
+
if (existingRecord && booked) {
|
|
2315
|
+
return { record: existingRecord, account: booked };
|
|
2316
|
+
}
|
|
2317
|
+
if (existingRecord && !existingRecord.usbSessionId && this._forcedTransport === "usb") {
|
|
2318
|
+
await this._tryUsbAttach(existingRecord.walletId, existingRecord.masterFingerprint, {
|
|
2319
|
+
waitForReenumeration: existingRecord.hadUsbSession
|
|
2320
|
+
});
|
|
2321
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2322
|
+
existingRecord = this._devices.get(existingRecord.walletId) ?? existingRecord;
|
|
1431
2323
|
}
|
|
2324
|
+
const useSinglePathUsbExport = this._forcedTransport !== "qr" && Boolean(existingRecord?.usbSessionId);
|
|
2325
|
+
const requestedSchemaPaths = [
|
|
2326
|
+
{ hwkChain, path: syncPath }
|
|
2327
|
+
];
|
|
2328
|
+
const schemaPaths = useSinglePathUsbExport || requestedSchemaPaths.some((schema) => normalizePath(schema.path) === KEYSTONE_WALLET_ID_PATH) ? requestedSchemaPaths : [{ hwkChain: "evm", path: KEYSTONE_WALLET_ID_PATH }, ...requestedSchemaPaths];
|
|
1432
2329
|
const requestUr = this.urEngine.buildKeyDerivationRequest({
|
|
1433
|
-
schemas:
|
|
2330
|
+
schemas: schemaPaths.map((s) => ({
|
|
2331
|
+
path: s.path,
|
|
2332
|
+
curve: s.hwkChain === "sol" ? "ed25519" : "secp256k1"
|
|
2333
|
+
})),
|
|
1434
2334
|
origin: this._origin
|
|
1435
2335
|
});
|
|
1436
|
-
|
|
2336
|
+
let resolvedViaUsb = useSinglePathUsbExport;
|
|
2337
|
+
let responseUr;
|
|
2338
|
+
try {
|
|
2339
|
+
responseUr = await this._resolveUr(existingRecord, requestUr, false, connectId, signal);
|
|
2340
|
+
} catch (error) {
|
|
2341
|
+
if ((0, import_hwk_adapter_core3.isHardwareInteractionId)(connectId) || !useSinglePathUsbExport || !isKeystoneUsbReconnectableError(error)) {
|
|
2342
|
+
throw error;
|
|
2343
|
+
}
|
|
2344
|
+
await waitForKeystoneUsbReattachProbe();
|
|
2345
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2346
|
+
const recoveredRecord = await this._tryUsbAttach(
|
|
2347
|
+
existingRecord?.walletId,
|
|
2348
|
+
existingRecord?.masterFingerprint,
|
|
2349
|
+
{ waitForReenumeration: true }
|
|
2350
|
+
);
|
|
2351
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2352
|
+
if (recoveredRecord?.usbSessionId) {
|
|
2353
|
+
try {
|
|
2354
|
+
responseUr = await this._resolveUr(recoveredRecord, requestUr, false, connectId, signal);
|
|
2355
|
+
existingRecord = recoveredRecord;
|
|
2356
|
+
} catch (retryError) {
|
|
2357
|
+
if (!isKeystoneUsbReconnectableError(retryError)) throw retryError;
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
if (!responseUr) {
|
|
2361
|
+
if (this._forcedTransport === "usb") throw error;
|
|
2362
|
+
const fallbackSchemaPaths = options?.qrFallbackSchemaPaths ?? [
|
|
2363
|
+
{ hwkChain, path: syncPath }
|
|
2364
|
+
];
|
|
2365
|
+
const qrFallbackSchemas = [
|
|
2366
|
+
{ hwkChain: "evm", path: KEYSTONE_WALLET_ID_PATH },
|
|
2367
|
+
...fallbackSchemaPaths.filter(
|
|
2368
|
+
(schema) => normalizePath(schema.path) !== KEYSTONE_WALLET_ID_PATH
|
|
2369
|
+
)
|
|
2370
|
+
].map((schema) => ({
|
|
2371
|
+
path: schema.path,
|
|
2372
|
+
curve: schema.hwkChain === "sol" ? "ed25519" : "secp256k1"
|
|
2373
|
+
}));
|
|
2374
|
+
const qrRequestUr = this.urEngine.buildKeyDerivationRequest({
|
|
2375
|
+
schemas: qrFallbackSchemas,
|
|
2376
|
+
origin: this._origin
|
|
2377
|
+
});
|
|
2378
|
+
const displayDevice = existingRecord ? toDeviceInfo(existingRecord) : placeholderDeviceInfo();
|
|
2379
|
+
responseUr = await this._requestQrDisplayAndAwaitResponse(displayDevice, {
|
|
2380
|
+
...qrRequestUr,
|
|
2381
|
+
animated: false
|
|
2382
|
+
});
|
|
2383
|
+
resolvedViaUsb = false;
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
1437
2386
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
1438
2387
|
const parsed = this.urEngine.parseAccountResponse(responseUr);
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
2388
|
+
let record;
|
|
2389
|
+
if (resolvedViaUsb && existingRecord) {
|
|
2390
|
+
if (parsed.masterFingerprint !== existingRecord.masterFingerprint) {
|
|
2391
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2392
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
2393
|
+
message: "Keystone USB account export returned a different master fingerprint"
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
record = existingRecord;
|
|
2397
|
+
} else {
|
|
2398
|
+
this._assertParsedIdentity(parsed, target);
|
|
2399
|
+
record = this._upsertDeviceRecord(parsed, {
|
|
2400
|
+
viaUsb: Boolean(existingRecord?.usbSessionId)
|
|
1443
2401
|
});
|
|
1444
2402
|
}
|
|
1445
|
-
const record = this._upsertDeviceRecord(parsed, {
|
|
1446
|
-
viaUsb: Boolean(existingRecord?.usbSessionId)
|
|
1447
|
-
});
|
|
1448
2403
|
const account = parsed.accounts.find((a) => normalizePath(a.path) === syncPath);
|
|
1449
2404
|
if (!account) {
|
|
1450
|
-
throw (0,
|
|
1451
|
-
code:
|
|
2405
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2406
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
1452
2407
|
message: `Keystone did not return the requested derivation path (${syncPath})`
|
|
1453
2408
|
});
|
|
1454
2409
|
}
|
|
1455
2410
|
const entry = { ...account, hwkChain };
|
|
1456
|
-
|
|
2411
|
+
if (book) {
|
|
2412
|
+
for (const returned of parsed.accounts) {
|
|
2413
|
+
const returnedChain = inferHwkChainFromPath(returned.path);
|
|
2414
|
+
if (returnedChain) {
|
|
2415
|
+
book.set(accountKey(returnedChain, returned.path), {
|
|
2416
|
+
...returned,
|
|
2417
|
+
hwkChain: returnedChain
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
book.set(key, entry);
|
|
2422
|
+
}
|
|
1457
2423
|
return { record, account: entry };
|
|
1458
2424
|
}
|
|
1459
2425
|
/**
|
|
1460
|
-
* Like `
|
|
2426
|
+
* Like `_fetchAccount`, but for operations (PSBT signing, master
|
|
1461
2427
|
* fingerprint) that only need to know WHICH wallet is attached, not a
|
|
1462
2428
|
* specific cached path. Syncs the account-level path for `chain` as a
|
|
1463
|
-
* throwaway probe when the
|
|
2429
|
+
* throwaway probe when the wallet record isn't already known.
|
|
1464
2430
|
*
|
|
1465
2431
|
* `CHAIN_FINGERPRINT_PATHS[chain]` is a 5-segment LEAF path for `evm`
|
|
1466
2432
|
* (`m/44'/60'/0'/0/0`) — sending that verbatim as a KeyDerivation request
|
|
1467
2433
|
* asks Keystone for a non-standard path. Keystone's own docs
|
|
1468
2434
|
* (dev.keyst.one's multichain KeyDerivation example) show the ETH
|
|
1469
2435
|
* account-level path as `m/44'/60'/0'` (3 segments), same as what
|
|
1470
|
-
* `
|
|
2436
|
+
* `_fetchAccount` already requests — so
|
|
1471
2437
|
* truncate through `splitAccountPath` here too instead of using the raw
|
|
1472
2438
|
* fingerprint leaf path. `btc`/`sol` are already 3-segment account paths
|
|
1473
2439
|
* and pass through unchanged.
|
|
1474
2440
|
*/
|
|
1475
|
-
async
|
|
1476
|
-
const
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
2441
|
+
async _ensureWalletKnown(connectId, deviceId, chain, signal) {
|
|
2442
|
+
const target = this._resolveTarget(connectId, deviceId);
|
|
2443
|
+
if (target.record) return { record: target.record };
|
|
2444
|
+
const attached = await this._tryUsbAttach(target.expectedWalletId);
|
|
2445
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2446
|
+
if (attached) return { record: attached };
|
|
2447
|
+
const { accountPath } = splitAccountPath(import_hwk_adapter_core3.CHAIN_FINGERPRINT_PATHS[chain]);
|
|
2448
|
+
const schemas = accountPath === KEYSTONE_WALLET_ID_PATH ? [{ path: KEYSTONE_WALLET_ID_PATH, curve: "secp256k1" }] : [
|
|
2449
|
+
{ path: KEYSTONE_WALLET_ID_PATH, curve: "secp256k1" },
|
|
2450
|
+
{
|
|
2451
|
+
path: accountPath,
|
|
2452
|
+
curve: chain === "sol" ? "ed25519" : "secp256k1"
|
|
2453
|
+
}
|
|
2454
|
+
];
|
|
1480
2455
|
const requestUr = this.urEngine.buildKeyDerivationRequest({
|
|
1481
|
-
schemas
|
|
2456
|
+
schemas,
|
|
1482
2457
|
origin: this._origin
|
|
1483
2458
|
});
|
|
1484
|
-
const responseUr = await this._resolveUr(void 0, requestUr, false);
|
|
2459
|
+
const responseUr = await this._resolveUr(void 0, requestUr, false, connectId, signal);
|
|
1485
2460
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
1486
2461
|
const parsed = this.urEngine.parseAccountResponse(responseUr);
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
message: `Scanned Keystone wallet (mfp ${parsed.masterFingerprint}) does not match the requested device (${wantMfp})`
|
|
1491
|
-
});
|
|
1492
|
-
}
|
|
1493
|
-
return { record: this._upsertDeviceRecord(parsed) };
|
|
2462
|
+
this._assertParsedIdentity(parsed, target);
|
|
2463
|
+
const record = this._upsertDeviceRecord(parsed);
|
|
2464
|
+
return { record };
|
|
1494
2465
|
}
|
|
1495
2466
|
async _requestQrDisplayAndAwaitResponse(device, data) {
|
|
1496
2467
|
const waitPromise = this._uiRegistry.wait(
|
|
1497
|
-
|
|
2468
|
+
import_hwk_adapter_core3.UI_REQUEST.REQUEST_QR_DISPLAY,
|
|
1498
2469
|
{ timeoutMs: this._qrTimeoutMs }
|
|
1499
2470
|
);
|
|
1500
|
-
this.emitter.emit(
|
|
1501
|
-
type:
|
|
2471
|
+
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_QR_DISPLAY, {
|
|
2472
|
+
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_QR_DISPLAY,
|
|
1502
2473
|
payload: { device, data }
|
|
1503
2474
|
});
|
|
1504
2475
|
const response = await waitPromise;
|
|
@@ -1506,16 +2477,103 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1506
2477
|
}
|
|
1507
2478
|
async _requestQrScanAndAwaitResponse(device) {
|
|
1508
2479
|
const waitPromise = this._uiRegistry.wait(
|
|
1509
|
-
|
|
2480
|
+
import_hwk_adapter_core3.UI_REQUEST.REQUEST_QR_SCAN,
|
|
1510
2481
|
{ timeoutMs: this._qrTimeoutMs }
|
|
1511
2482
|
);
|
|
1512
|
-
this.emitter.emit(
|
|
1513
|
-
type:
|
|
2483
|
+
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_QR_SCAN, {
|
|
2484
|
+
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_QR_SCAN,
|
|
1514
2485
|
payload: { device }
|
|
1515
2486
|
});
|
|
1516
2487
|
const response = await waitPromise;
|
|
1517
2488
|
return { urType: response.urType, urData: response.urData };
|
|
1518
2489
|
}
|
|
2490
|
+
async _callUsbConnector(sessionId, method, params, signal) {
|
|
2491
|
+
if (!this._usbConnector) {
|
|
2492
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2493
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.TransportNotAvailable,
|
|
2494
|
+
message: "No USB connector configured for this Keystone adapter"
|
|
2495
|
+
});
|
|
2496
|
+
}
|
|
2497
|
+
if (this._pendingUsbTeardowns > 0 || this._unsettledUsbOperations.size > 0) {
|
|
2498
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2499
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceBusyInternal,
|
|
2500
|
+
message: `Keystone USB is busy while calling ${method}`
|
|
2501
|
+
});
|
|
2502
|
+
}
|
|
2503
|
+
const releaseOperation = this._retainUsbOperation(`call:${sessionId}`);
|
|
2504
|
+
let rawCall;
|
|
2505
|
+
try {
|
|
2506
|
+
const connector = this._usbConnector;
|
|
2507
|
+
rawCall = connector.call(sessionId, method, params).finally(releaseOperation);
|
|
2508
|
+
} catch (error) {
|
|
2509
|
+
releaseOperation();
|
|
2510
|
+
throw error;
|
|
2511
|
+
}
|
|
2512
|
+
return signal ? _KeystoneAdapter._abortable(signal, rawCall) : rawCall;
|
|
2513
|
+
}
|
|
2514
|
+
_runUsbTeardown(task) {
|
|
2515
|
+
const previous = this._usbTeardownTail;
|
|
2516
|
+
let releaseTail = () => void 0;
|
|
2517
|
+
this._usbTeardownTail = new Promise((resolve) => {
|
|
2518
|
+
releaseTail = resolve;
|
|
2519
|
+
});
|
|
2520
|
+
this._pendingUsbTeardowns += 1;
|
|
2521
|
+
return (async () => {
|
|
2522
|
+
try {
|
|
2523
|
+
await previous;
|
|
2524
|
+
await this._waitForUsbOperationsToDrain();
|
|
2525
|
+
await task();
|
|
2526
|
+
} finally {
|
|
2527
|
+
this._pendingUsbTeardowns -= 1;
|
|
2528
|
+
releaseTail();
|
|
2529
|
+
}
|
|
2530
|
+
})();
|
|
2531
|
+
}
|
|
2532
|
+
_waitForUsbOperationsToDrain() {
|
|
2533
|
+
if (this._unsettledUsbOperations.size === 0) return Promise.resolve();
|
|
2534
|
+
return new Promise((resolve) => {
|
|
2535
|
+
this._usbIdleWaiters.add(resolve);
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2538
|
+
_retainUsbOperation(key) {
|
|
2539
|
+
this._unsettledUsbOperations.set(key, (this._unsettledUsbOperations.get(key) ?? 0) + 1);
|
|
2540
|
+
let released = false;
|
|
2541
|
+
return () => {
|
|
2542
|
+
if (released) return;
|
|
2543
|
+
released = true;
|
|
2544
|
+
const remaining = (this._unsettledUsbOperations.get(key) ?? 1) - 1;
|
|
2545
|
+
if (remaining > 0) {
|
|
2546
|
+
this._unsettledUsbOperations.set(key, remaining);
|
|
2547
|
+
} else {
|
|
2548
|
+
this._unsettledUsbOperations.delete(key);
|
|
2549
|
+
}
|
|
2550
|
+
if (this._unsettledUsbOperations.size === 0) {
|
|
2551
|
+
for (const resolve of this._usbIdleWaiters) resolve();
|
|
2552
|
+
this._usbIdleWaiters.clear();
|
|
2553
|
+
}
|
|
2554
|
+
};
|
|
2555
|
+
}
|
|
2556
|
+
static _abortable(signal, promise) {
|
|
2557
|
+
if (signal.aborted) {
|
|
2558
|
+
return Promise.reject(signal.reason instanceof Error ? signal.reason : new Error("Aborted"));
|
|
2559
|
+
}
|
|
2560
|
+
return new Promise((resolve, reject) => {
|
|
2561
|
+
const onAbort = () => {
|
|
2562
|
+
reject(signal.reason instanceof Error ? signal.reason : new Error("Aborted"));
|
|
2563
|
+
};
|
|
2564
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2565
|
+
promise.then(
|
|
2566
|
+
(value) => {
|
|
2567
|
+
signal.removeEventListener("abort", onAbort);
|
|
2568
|
+
resolve(value);
|
|
2569
|
+
},
|
|
2570
|
+
(error) => {
|
|
2571
|
+
signal.removeEventListener("abort", onAbort);
|
|
2572
|
+
reject(error);
|
|
2573
|
+
}
|
|
2574
|
+
);
|
|
2575
|
+
});
|
|
2576
|
+
}
|
|
1519
2577
|
static _throwIfAborted(signal) {
|
|
1520
2578
|
if (signal.aborted) {
|
|
1521
2579
|
throw signal.reason instanceof Error ? signal.reason : new Error("Operation aborted");
|
|
@@ -1523,16 +2581,16 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1523
2581
|
}
|
|
1524
2582
|
static _assertRequestIdMatches(expected, actual) {
|
|
1525
2583
|
if (actual && actual.toLowerCase() !== expected.toLowerCase()) {
|
|
1526
|
-
throw (0,
|
|
1527
|
-
code:
|
|
2584
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2585
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
1528
2586
|
message: "Keystone response requestId does not match the pending request \u2014 discarding a stale or unrelated scan"
|
|
1529
2587
|
});
|
|
1530
2588
|
}
|
|
1531
2589
|
}
|
|
1532
2590
|
_unsupported(method, reason) {
|
|
1533
2591
|
return Promise.resolve(
|
|
1534
|
-
(0,
|
|
1535
|
-
|
|
2592
|
+
(0, import_hwk_adapter_core3.failure)(
|
|
2593
|
+
import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
|
|
1536
2594
|
`KeystoneAdapter.${method} is not implemented yet: ${reason}`
|
|
1537
2595
|
)
|
|
1538
2596
|
);
|
|
@@ -1541,38 +2599,47 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1541
2599
|
if (err && typeof err === "object") {
|
|
1542
2600
|
const e = err;
|
|
1543
2601
|
if (typeof e.code === "number") {
|
|
1544
|
-
|
|
2602
|
+
const { origin } = e;
|
|
2603
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
2604
|
+
e.code,
|
|
2605
|
+
e.message ?? "Unknown error",
|
|
2606
|
+
e.params,
|
|
2607
|
+
origin === "device" || origin === "transport" || origin === "host" ? origin : void 0,
|
|
2608
|
+
(0, import_hwk_adapter_core3.isHwkRecoveryHint)(e.recovery) ? e.recovery : void 0
|
|
2609
|
+
);
|
|
1545
2610
|
}
|
|
1546
|
-
if (e._tag ===
|
|
1547
|
-
return (0,
|
|
1548
|
-
|
|
2611
|
+
if (e._tag === import_hwk_adapter_core3.UI_REQUEST_CANCELLED_TAG || e._tag === import_hwk_adapter_core3.UI_REQUEST_PREEMPTED_TAG) {
|
|
2612
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
2613
|
+
import_hwk_adapter_core3.HardwareErrorCode.UserAborted,
|
|
1549
2614
|
e.message ?? "Keystone QR interaction was cancelled"
|
|
1550
2615
|
);
|
|
1551
2616
|
}
|
|
1552
|
-
if (e._tag ===
|
|
1553
|
-
return (0,
|
|
1554
|
-
|
|
2617
|
+
if (e._tag === import_hwk_adapter_core3.UI_REQUEST_TIMEOUT_TAG) {
|
|
2618
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
2619
|
+
import_hwk_adapter_core3.HardwareErrorCode.OperationTimeout,
|
|
1555
2620
|
e.message ?? "Keystone QR interaction timed out"
|
|
1556
2621
|
);
|
|
1557
2622
|
}
|
|
1558
2623
|
}
|
|
1559
2624
|
const message = err instanceof Error ? err.message : String(err);
|
|
1560
2625
|
const params = err instanceof Error && err.stack ? { stack: err.stack } : void 0;
|
|
1561
|
-
return (0,
|
|
2626
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.UnknownError, message, params);
|
|
1562
2627
|
}
|
|
1563
2628
|
};
|
|
1564
2629
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1565
2630
|
0 && (module.exports = {
|
|
2631
|
+
KEYSTONE_WALLET_CONNECT_ID_PREFIX,
|
|
2632
|
+
KEYSTONE_WALLET_ID_PATH,
|
|
1566
2633
|
KeystoneAdapter,
|
|
1567
2634
|
KeystoneUrEngine,
|
|
1568
|
-
QR_CONNECT_ID_PREFIX,
|
|
1569
2635
|
accountKey,
|
|
1570
2636
|
btcScriptTypeFromPath,
|
|
1571
2637
|
createDeviceRecord,
|
|
2638
|
+
deriveKeystoneWalletId,
|
|
1572
2639
|
normalizePath,
|
|
1573
2640
|
placeholderDeviceInfo,
|
|
1574
|
-
qrConnectId,
|
|
1575
2641
|
splitAccountPath,
|
|
1576
|
-
toDeviceInfo
|
|
2642
|
+
toDeviceInfo,
|
|
2643
|
+
walletConnectId
|
|
1577
2644
|
});
|
|
1578
2645
|
//# sourceMappingURL=index.js.map
|