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