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