@onekeyfe/hwk-keystone-adapter 1.2.3-alpha.8 → 1.2.5-alpha.0
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 +88 -222
- package/dist/index.d.ts +88 -222
- package/dist/index.js +547 -795
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +547 -795
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -140,6 +140,9 @@ var TronSignature = class _TronSignature extends import_bc_ur_registry2.Registry
|
|
|
140
140
|
static fromDataItem(dataItem) {
|
|
141
141
|
const map = dataItem.getData();
|
|
142
142
|
const signature = map[2 /* signature */];
|
|
143
|
+
if (!Buffer.isBuffer(signature)) {
|
|
144
|
+
throw new Error("Keystone tron-signature UR is missing its signature bytes");
|
|
145
|
+
}
|
|
143
146
|
const requestIdItem = map[1 /* requestId */];
|
|
144
147
|
const requestId = requestIdItem?.getData();
|
|
145
148
|
return new _TronSignature(signature, requestId);
|
|
@@ -170,14 +173,22 @@ function toSdkUr(ur) {
|
|
|
170
173
|
function fromSdkUr(ur) {
|
|
171
174
|
return { urType: ur.type, urData: ur.cbor.toString("hex") };
|
|
172
175
|
}
|
|
173
|
-
function
|
|
174
|
-
if (hex.length !==
|
|
176
|
+
function splitEvmSignature(hex) {
|
|
177
|
+
if (hex.length < 130 || hex.length % 2 !== 0) {
|
|
175
178
|
throw new Error(
|
|
176
|
-
`Keystone returned a ${hex.length / 2}-byte signature; expected 65 bytes (r|s|v)`
|
|
179
|
+
`Keystone returned a ${hex.length / 2}-byte signature; expected at least 65 bytes (r|s|v)`
|
|
177
180
|
);
|
|
178
181
|
}
|
|
179
182
|
return { r: hex.slice(0, 64), s: hex.slice(64, 128), v: hex.slice(128) };
|
|
180
183
|
}
|
|
184
|
+
function requireSignatureBytes(hex, expectedBytes, chain) {
|
|
185
|
+
if (hex.length !== expectedBytes * 2) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Keystone returned a ${hex.length / 2}-byte ${chain} signature; expected ${expectedBytes} bytes`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
return hex;
|
|
191
|
+
}
|
|
181
192
|
function requireBip32MasterFingerprint(value) {
|
|
182
193
|
const fingerprint = (0, import_hwk_adapter_core.parseBip32MasterFingerprint)(value);
|
|
183
194
|
if (!fingerprint) {
|
|
@@ -185,12 +196,24 @@ function requireBip32MasterFingerprint(value) {
|
|
|
185
196
|
}
|
|
186
197
|
return fingerprint;
|
|
187
198
|
}
|
|
199
|
+
function toParsedAccount(key) {
|
|
200
|
+
return {
|
|
201
|
+
chain: key.chain,
|
|
202
|
+
path: key.path,
|
|
203
|
+
publicKey: key.publicKey,
|
|
204
|
+
extendedPublicKey: key.extendedPublicKey,
|
|
205
|
+
xfp: key.xfp ? requireBip32MasterFingerprint(key.xfp) : void 0,
|
|
206
|
+
name: key.name
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function relativeHdPath(relativeDerivePath) {
|
|
210
|
+
return `m/${relativeDerivePath.replace(/^m\//i, "")}`;
|
|
211
|
+
}
|
|
188
212
|
var KeystoneUrEngine = class {
|
|
189
213
|
constructor(origin = "OneKey") {
|
|
190
214
|
this.sdk = new import_keystone_sdk.KeystoneSDK({ origin });
|
|
191
215
|
}
|
|
192
|
-
// --- Account sync (
|
|
193
|
-
// wallet-initiated KeyDerivation request/response over either channel) ---
|
|
216
|
+
// --- Account sync (device-initiated export or KeyDerivation response) ---
|
|
194
217
|
parseMultiAccounts(ur) {
|
|
195
218
|
const parsed = this.sdk.parseMultiAccounts(toSdkUr(ur));
|
|
196
219
|
return {
|
|
@@ -198,46 +221,15 @@ var KeystoneUrEngine = class {
|
|
|
198
221
|
device: parsed.device,
|
|
199
222
|
deviceId: parsed.deviceId,
|
|
200
223
|
deviceVersion: parsed.deviceVersion,
|
|
201
|
-
accounts: parsed.keys.map(
|
|
202
|
-
(key) => ({
|
|
203
|
-
chain: key.chain,
|
|
204
|
-
path: key.path,
|
|
205
|
-
publicKey: key.publicKey,
|
|
206
|
-
extendedPublicKey: key.extendedPublicKey,
|
|
207
|
-
xfp: key.xfp ? requireBip32MasterFingerprint(key.xfp) : void 0,
|
|
208
|
-
name: key.name
|
|
209
|
-
})
|
|
210
|
-
)
|
|
224
|
+
accounts: parsed.keys.map(toParsedAccount)
|
|
211
225
|
};
|
|
212
226
|
}
|
|
213
227
|
parseHDKey(ur) {
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
chain: key.chain,
|
|
217
|
-
path: key.path,
|
|
218
|
-
publicKey: key.publicKey,
|
|
219
|
-
extendedPublicKey: key.extendedPublicKey,
|
|
220
|
-
xfp: key.xfp ? requireBip32MasterFingerprint(key.xfp) : void 0,
|
|
221
|
-
name: key.name
|
|
222
|
-
};
|
|
228
|
+
return toParsedAccount(this.sdk.parseHDKey(toSdkUr(ur)));
|
|
223
229
|
}
|
|
224
230
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* showing. The device replies with a `crypto-multi-accounts` UR — parse it
|
|
228
|
-
* with `parseMultiAccounts`. Used for the implicit "sync this wallet's xfp
|
|
229
|
-
* before the first sign" round trip as well as an explicit account import.
|
|
230
|
-
*
|
|
231
|
-
* `version: V1` is required — verified against real Keystone hardware and
|
|
232
|
-
* `keystone3-firmware`'s `CheckHardwareCallRequestIsLegal` source: an
|
|
233
|
-
* unversioned/V0 request is validated as a legacy Cardano-only request
|
|
234
|
-
* (`m/1852'/1815'/...`) and firmware rejects every other chain's path with
|
|
235
|
-
* `PRS_PARSING_ERROR` (device-shown message: "路径不受支持" / "path not
|
|
236
|
-
* supported"), regardless of the path's shape. V1 is what actually enables
|
|
237
|
-
* the general per-chain path whitelist (includes `m/44'/60'` for ETH, the
|
|
238
|
-
* standard BTC purposes, etc.). The SDK itself defaults to V0 unless a
|
|
239
|
-
* truthy `version` is passed — omitting this silently produces a request
|
|
240
|
-
* every non-Cardano device rejects.
|
|
231
|
+
* Builds a KeyDerivation request. `version: V1` is required: firmware validates V0 (the SDK
|
|
232
|
+
* default) as Cardano-only and rejects other paths with `PRS_PARSING_ERROR`.
|
|
241
233
|
*/
|
|
242
234
|
buildKeyDerivationRequest(input) {
|
|
243
235
|
const ur = this.sdk.generateKeyDerivationCall({
|
|
@@ -251,14 +243,8 @@ var KeystoneUrEngine = class {
|
|
|
251
243
|
return fromSdkUr(ur);
|
|
252
244
|
}
|
|
253
245
|
/**
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
* `crypto-hdkey` for a single-key response some firmware paths use instead.
|
|
257
|
-
* Both are normalized to the same `KeystoneParsedMultiAccounts` shape — a
|
|
258
|
-
* single `crypto-hdkey` becomes a one-entry account list, with its own
|
|
259
|
-
* `origin.sourceFingerprint` promoted to `masterFingerprint` (correct for a
|
|
260
|
-
* key derived directly from the seed, which every request this engine
|
|
261
|
-
* builds asks for).
|
|
246
|
+
* Normalizes `crypto-multi-accounts` and the single-key `crypto-hdkey` some firmware returns. The
|
|
247
|
+
* hdkey source fingerprint is the mfp only because every request here derives from the seed.
|
|
262
248
|
*/
|
|
263
249
|
parseAccountResponse(ur) {
|
|
264
250
|
if (ur.urType === "crypto-hdkey") {
|
|
@@ -289,37 +275,19 @@ var KeystoneUrEngine = class {
|
|
|
289
275
|
}
|
|
290
276
|
parseEthSignature(ur) {
|
|
291
277
|
const signature = this.sdk.eth.parseSignature(toSdkUr(ur));
|
|
292
|
-
return { requestId: signature.requestId, ...
|
|
278
|
+
return { requestId: signature.requestId, ...splitEvmSignature(signature.signature) };
|
|
293
279
|
}
|
|
294
|
-
/**
|
|
295
|
-
* Derive one EVM address offline from an already-synced account xpub —
|
|
296
|
-
* verified against the same `@keystonehq/bc-ur-registry-eth` helper the
|
|
297
|
-
* Keystone-based OneKey air-gap demo uses in production
|
|
298
|
-
* (`generateAddressFromXpub`), so no unverified assumption about what a
|
|
299
|
-
* leaf-path KeyDerivation request would return. `relativeDerivePath` is
|
|
300
|
-
* relative to the xpub's own depth, e.g. `'0/0'` for an account xpub.
|
|
301
|
-
*/
|
|
280
|
+
/** Derives an EVM address offline; `relativeDerivePath` is relative to the xpub, e.g. `'0/0'`. */
|
|
302
281
|
deriveEvmAddressFromXpub(xpub, relativeDerivePath) {
|
|
303
|
-
return (0, import_bc_ur_registry_eth.generateAddressFromXpub)(xpub,
|
|
282
|
+
return (0, import_bc_ur_registry_eth.generateAddressFromXpub)(xpub, relativeHdPath(relativeDerivePath));
|
|
304
283
|
}
|
|
305
284
|
// --- BTC (PSBT transaction signing + plain message signing) ---
|
|
306
285
|
/**
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
* always emits standard mainnet-xpub version bytes (`0488B21E`) regardless
|
|
310
|
-
* of the account's purpose/script type (verified against
|
|
311
|
-
* `@keystonehq/bc-ur-registry`'s `CryptoHDKey.getBip32Key`, which hardcodes
|
|
312
|
-
* that version rather than switching to a SLIP-132 ypub/zpub prefix per
|
|
313
|
-
* script type) — so `hdkey.fromExtendedKey` parses it correctly for every
|
|
314
|
-
* `scriptType` without needing custom version bytes configured.
|
|
315
|
-
*
|
|
316
|
-
* `p2tr` is deliberately not handled: taproot output-key tweaking (BIP-341)
|
|
317
|
-
* needs an elliptic-curve library wired via bitcoinjs-lib's `initEccLib`,
|
|
318
|
-
* which this package doesn't set up yet — every other payment function
|
|
319
|
-
* here needs no such library.
|
|
286
|
+
* `CryptoHDKey` always emits mainnet xpub version bytes (`0488B21E`), so `hdkey` parses it as is.
|
|
287
|
+
* `p2tr` needs an ECC lib for BIP-341 tweaking, not wired here.
|
|
320
288
|
*/
|
|
321
289
|
deriveBtcAddressFromXpub(xpub, relativeDerivePath, scriptType) {
|
|
322
|
-
const node = import_hdkey.default.fromExtendedKey(xpub).derive(
|
|
290
|
+
const node = import_hdkey.default.fromExtendedKey(xpub).derive(relativeHdPath(relativeDerivePath));
|
|
323
291
|
if (!node.publicKey) throw new Error("HDKey derivation did not produce a public key");
|
|
324
292
|
return this.deriveBtcAddressFromPublicKey(
|
|
325
293
|
Buffer.from(node.publicKey).toString("hex"),
|
|
@@ -372,7 +340,7 @@ var KeystoneUrEngine = class {
|
|
|
372
340
|
requestId: params.requestId,
|
|
373
341
|
signData: params.messageHex,
|
|
374
342
|
dataType: 1,
|
|
375
|
-
// BtcSignRequest.DataType.message
|
|
343
|
+
// BtcSignRequest.DataType.message; PSBT signing never goes through this path.
|
|
376
344
|
accounts: params.accounts,
|
|
377
345
|
origin: params.origin
|
|
378
346
|
});
|
|
@@ -401,7 +369,10 @@ var KeystoneUrEngine = class {
|
|
|
401
369
|
}
|
|
402
370
|
parseSolSignature(ur) {
|
|
403
371
|
const signature = this.sdk.sol.parseSignature(toSdkUr(ur));
|
|
404
|
-
return {
|
|
372
|
+
return {
|
|
373
|
+
requestId: signature.requestId,
|
|
374
|
+
signature: requireSignatureBytes(signature.signature, 64, "SOL")
|
|
375
|
+
};
|
|
405
376
|
}
|
|
406
377
|
// --- TRON ---
|
|
407
378
|
/**
|
|
@@ -426,37 +397,22 @@ var KeystoneUrEngine = class {
|
|
|
426
397
|
const requestId = signature.getRequestId();
|
|
427
398
|
return {
|
|
428
399
|
requestId: requestId ? (0, import_uuid.stringify)(requestId) : void 0,
|
|
429
|
-
signature: signature.getSignature().toString("hex")
|
|
400
|
+
signature: requireSignatureBytes(signature.getSignature().toString("hex"), 65, "TRON")
|
|
430
401
|
};
|
|
431
402
|
}
|
|
432
403
|
/**
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
* derivation (verified against Keystone's own `formatAddress()` in
|
|
436
|
-
* `keystone-sdk`'s TRON chain source) — only the final text encoding
|
|
437
|
-
* differs (base58check with a `0x41` version byte, not checksummed hex).
|
|
438
|
-
* Reusing `generateAddressFromXpub` here means no new hashing dependency:
|
|
439
|
-
* strip its "0x" and re-encode the same 20 bytes.
|
|
404
|
+
* TRON uses the same 20 address bytes as EVM (Keystone's `formatAddress()`), re-encoded as
|
|
405
|
+
* base58check with the `0x41` version byte.
|
|
440
406
|
*/
|
|
441
407
|
deriveTronAddressFromXpub(xpub, relativeDerivePath) {
|
|
442
|
-
const evmStyleHex = (0, import_bc_ur_registry_eth.generateAddressFromXpub)(
|
|
443
|
-
xpub,
|
|
444
|
-
`m/${relativeDerivePath.replace(/^m\//i, "")}`
|
|
445
|
-
);
|
|
408
|
+
const evmStyleHex = (0, import_bc_ur_registry_eth.generateAddressFromXpub)(xpub, relativeHdPath(relativeDerivePath));
|
|
446
409
|
const addressBytes = Buffer.concat([
|
|
447
410
|
Buffer.from([TRON_ADDRESS_PREFIX]),
|
|
448
|
-
Buffer.from(evmStyleHex
|
|
411
|
+
Buffer.from(stripHexPrefix(evmStyleHex), "hex")
|
|
449
412
|
]);
|
|
450
413
|
return import_bs58check.default.encode(addressBytes);
|
|
451
414
|
}
|
|
452
|
-
/**
|
|
453
|
-
* Split an account-level xpub back into the BIP-32 fields a host needs to
|
|
454
|
-
* treat it as a real extended key (`BtcPublicKey`). Everything here is
|
|
455
|
-
* carried inside the xpub's own serialization — depth, the PARENT key
|
|
456
|
-
* fingerprint (not the seed's master fingerprint), the chain code and the
|
|
457
|
-
* compressed public key — so this is a pure decode with no device round
|
|
458
|
-
* trip; the xpub itself was already device-verified when it was synced.
|
|
459
|
-
*/
|
|
415
|
+
/** Decodes an xpub's BIP-32 fields; `parentFingerprint` is the parent key's, not the mfp. */
|
|
460
416
|
parseXpubMeta(xpub) {
|
|
461
417
|
const raw = Buffer.from(import_bs58check.default.decode(xpub));
|
|
462
418
|
if (raw.length !== 78) {
|
|
@@ -611,8 +567,10 @@ function keystoneCancelQueueKeys(identifiers) {
|
|
|
611
567
|
var KEYSTONE_USB_REATTACH_PROBE_ATTEMPTS = 4;
|
|
612
568
|
var KEYSTONE_USB_REATTACH_PROBE_INTERVAL_MS = 500;
|
|
613
569
|
function resolveHardwareOperationTarget(positionalTargetId, operationId) {
|
|
614
|
-
|
|
615
|
-
|
|
570
|
+
return (0, import_hwk_adapter_core4.resolveHardwareOperationTarget)(positionalTargetId, operationId, "keystone");
|
|
571
|
+
}
|
|
572
|
+
function keySchema(chain, path) {
|
|
573
|
+
return { path, curve: chain === "sol" ? "ed25519" : "secp256k1" };
|
|
616
574
|
}
|
|
617
575
|
function waitForKeystoneUsbReattachProbe() {
|
|
618
576
|
return new Promise((resolve) => {
|
|
@@ -666,13 +624,13 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
666
624
|
this._usbConnectTail = Promise.resolve();
|
|
667
625
|
this._unsettledUsbOperations = /* @__PURE__ */ new Map();
|
|
668
626
|
this._usbIdleWaiters = /* @__PURE__ */ new Set();
|
|
627
|
+
this._abandonedUsbConnects = 0;
|
|
669
628
|
this._usbTeardownTail = Promise.resolve();
|
|
670
629
|
this._pendingUsbTeardowns = 0;
|
|
671
630
|
this._handleUsbDisconnect = ({ connectId }) => {
|
|
672
631
|
const record = Array.from(this._devices.values()).find((item) => item.usbSessionId === connectId);
|
|
673
632
|
if (!record) return;
|
|
674
633
|
this._operations.endByConnectionKey(connectId, "disconnect");
|
|
675
|
-
this._operations.endByConnectionKey(record.connectId, "disconnect");
|
|
676
634
|
record.usbSessionId = void 0;
|
|
677
635
|
const info = toDeviceInfo(record);
|
|
678
636
|
if (record.qrSynced) {
|
|
@@ -686,8 +644,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
686
644
|
this.emitter.emit("ui-event", event);
|
|
687
645
|
};
|
|
688
646
|
// ---------------------------------------------------------------------------
|
|
689
|
-
// All-network bundle
|
|
690
|
-
// there's exactly one implementation of each chain's address logic.
|
|
647
|
+
// All-network bundle, dispatched to the per-chain methods below.
|
|
691
648
|
// ---------------------------------------------------------------------------
|
|
692
649
|
this.allNetworkGetAddress = async (connectId, deviceId, params) => {
|
|
693
650
|
const operationTarget = resolveHardwareOperationTarget(connectId, params.operationId);
|
|
@@ -696,11 +653,10 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
696
653
|
const { operationId } = operationTarget.payload;
|
|
697
654
|
let cancelScope;
|
|
698
655
|
try {
|
|
699
|
-
const prefetched = await this._prefetchAllNetworkAccounts(
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
);
|
|
656
|
+
const prefetched = await this._prefetchAllNetworkAccounts(effectiveConnectId, deviceId, {
|
|
657
|
+
...params,
|
|
658
|
+
operationId
|
|
659
|
+
});
|
|
704
660
|
const { book } = prefetched;
|
|
705
661
|
const itemDeviceId = deviceId || prefetched.walletId || "";
|
|
706
662
|
const scope = this._jobQueue.createCancelScope(
|
|
@@ -756,9 +712,8 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
756
712
|
}
|
|
757
713
|
});
|
|
758
714
|
},
|
|
759
|
-
//
|
|
760
|
-
//
|
|
761
|
-
// existing "carry on with the next chain" behaviour.
|
|
715
|
+
// Cancel, refusal or a lost session ends the batch; other per-item
|
|
716
|
+
// failures carry on with the next chain.
|
|
762
717
|
shouldAbortBundle: (response, { index }) => {
|
|
763
718
|
const { code } = response.payload ?? {};
|
|
764
719
|
return cancelledIndexes.has(index) || (0, import_hwk_adapter_core4.isUserRefusal)(code) || (0, import_hwk_adapter_core4.isConnectionLost)(code);
|
|
@@ -789,7 +744,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
789
744
|
getAvailableTransports() {
|
|
790
745
|
return this._usbConnector ? ["qr", "usb"] : ["qr"];
|
|
791
746
|
}
|
|
792
|
-
/** Pins
|
|
747
|
+
/** Pins calls to 'qr' or 'usb'; any other value clears the pin back to auto routing. */
|
|
793
748
|
switchTransport(type) {
|
|
794
749
|
this._forcedTransport = type === "qr" || type === "usb" ? type : void 0;
|
|
795
750
|
return Promise.resolve();
|
|
@@ -802,6 +757,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
802
757
|
this._uiRegistry.cancel();
|
|
803
758
|
this._jobQueue.clear();
|
|
804
759
|
this._searchDeviceTargets.clear();
|
|
760
|
+
await this._usbConnectTail;
|
|
805
761
|
await this._resetUsbSessions();
|
|
806
762
|
this._usbConnector?.off("device-disconnect", this._handleUsbDisconnect);
|
|
807
763
|
this._usbConnector?.off("ui-event", this._handleUsbUiEvent);
|
|
@@ -813,14 +769,8 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
813
769
|
// Device table
|
|
814
770
|
// ---------------------------------------------------------------------------
|
|
815
771
|
/**
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
* there is no physical descriptor to enumerate. When a USB connector is
|
|
819
|
-
* configured, its raw scan results are appended as-is: a USB descriptor has
|
|
820
|
-
* no mfp until `connectDevice()` actually opens+claims it (see
|
|
821
|
-
* `KeystoneUsbConnectorBase.searchDevices`), so these entries carry an
|
|
822
|
-
* empty `deviceId` and exist purely so a host can list "plugged in, click
|
|
823
|
-
* to connect" candidates.
|
|
772
|
+
* Returns known wallets plus USB scan results; USB entries have no mfp until `connectDevice()`
|
|
773
|
+
* claims them. Explicit QR discovery returns one virtual target.
|
|
824
774
|
*/
|
|
825
775
|
async searchDevices(options) {
|
|
826
776
|
const searchGeneration = /* @__PURE__ */ Symbol("keystone-device-search");
|
|
@@ -856,18 +806,15 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
856
806
|
if (this._activeSearchGeneration !== searchGeneration) return [];
|
|
857
807
|
const placeholders = usbDevices.map((d) => ({
|
|
858
808
|
vendor: "keystone",
|
|
859
|
-
// Empty, not 'unknown':
|
|
860
|
-
//
|
|
861
|
-
// string would beat the host's own default-name fallback and surface as
|
|
862
|
-
// a device called "unknown".
|
|
809
|
+
// Empty, not 'unknown': all models share one vid/pid, and a placeholder
|
|
810
|
+
// string would beat the host's default-name fallback.
|
|
863
811
|
model: d.model ?? "",
|
|
864
812
|
modelName: d.modelName,
|
|
865
|
-
//
|
|
813
|
+
// Unknown until the device is opened.
|
|
866
814
|
firmwareVersion: "0.0.0",
|
|
867
815
|
deviceId: d.deviceId,
|
|
868
816
|
connectId: d.connectId,
|
|
869
817
|
connectionType: "usb",
|
|
870
|
-
// The USB product name, which IS readable at enumeration time.
|
|
871
818
|
label: d.name,
|
|
872
819
|
serialNumber: d.serialNumber,
|
|
873
820
|
capabilities: d.capabilities ?? { persistentDeviceIdentity: false },
|
|
@@ -887,20 +834,25 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
887
834
|
Array.from(this._devices.values()).map((record) => record.usbSessionId).filter((sessionId) => Boolean(sessionId))
|
|
888
835
|
);
|
|
889
836
|
for (const sessionId of sessionIds) {
|
|
890
|
-
|
|
891
|
-
await this._usbConnector.disconnect(sessionId);
|
|
892
|
-
} catch {
|
|
893
|
-
}
|
|
894
|
-
this._handleUsbDisconnect({ connectId: sessionId });
|
|
837
|
+
await this._retireUsbSession(sessionId);
|
|
895
838
|
}
|
|
896
839
|
});
|
|
897
840
|
}
|
|
841
|
+
/** Best-effort disconnect; the session is retired either way so it cannot be selected again. */
|
|
842
|
+
async _retireUsbSession(sessionId) {
|
|
843
|
+
if (this._usbConnector) {
|
|
844
|
+
try {
|
|
845
|
+
await this._usbConnector.disconnect(sessionId);
|
|
846
|
+
} catch {
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
this._handleUsbDisconnect({ connectId: sessionId });
|
|
850
|
+
}
|
|
898
851
|
async searchDeviceTargets(options) {
|
|
899
852
|
const devices = await this.searchDevices(options);
|
|
900
853
|
return devices.map((device) => ({
|
|
901
854
|
searchTargetId: device.connectId,
|
|
902
|
-
//
|
|
903
|
-
// selectable handle remains owned by the current discovery snapshot.
|
|
855
|
+
// The wallet id is stable only after the USB/QR handshake.
|
|
904
856
|
searchTargetReusePolicy: "current-discovery",
|
|
905
857
|
vendor: device.vendor,
|
|
906
858
|
connectionType: device.connectionType,
|
|
@@ -919,13 +871,23 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
919
871
|
}));
|
|
920
872
|
}
|
|
921
873
|
async connectDevice(searchTargetId) {
|
|
874
|
+
const scope = this._jobQueue.createCancelScope(searchTargetId);
|
|
922
875
|
try {
|
|
923
876
|
let connected;
|
|
924
877
|
let selectedConnectionType;
|
|
925
878
|
const discoveredConnectionType = this._searchDeviceTargets.get(searchTargetId);
|
|
926
879
|
if (discoveredConnectionType === "qr") {
|
|
927
880
|
selectedConnectionType = "qr";
|
|
928
|
-
|
|
881
|
+
const abortColdStart = () => this._jobQueue.cancelActiveAndPending(
|
|
882
|
+
KEYSTONE_COLD_START_JOB_LABEL,
|
|
883
|
+
scope.signal.reason
|
|
884
|
+
);
|
|
885
|
+
scope.signal.addEventListener("abort", abortColdStart, { once: true });
|
|
886
|
+
try {
|
|
887
|
+
connected = await this.importFromQr();
|
|
888
|
+
} finally {
|
|
889
|
+
scope.signal.removeEventListener("abort", abortColdStart);
|
|
890
|
+
}
|
|
929
891
|
} else if (discoveredConnectionType === "usb") {
|
|
930
892
|
selectedConnectionType = "usb";
|
|
931
893
|
if (!this._usbConnector) {
|
|
@@ -934,7 +896,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
934
896
|
"No USB connector configured for this Keystone adapter"
|
|
935
897
|
);
|
|
936
898
|
}
|
|
937
|
-
connected = await this._connectUsb({}, searchTargetId);
|
|
899
|
+
connected = await this._connectUsb({}, searchTargetId, scope.signal);
|
|
938
900
|
} else if ((0, import_hwk_adapter_core4.hasHardwareRuntimeIdPrefix)(searchTargetId)) {
|
|
939
901
|
return (0, import_hwk_adapter_core4.failure)(
|
|
940
902
|
import_hwk_adapter_core4.HardwareErrorCode.DeviceNotFound,
|
|
@@ -955,9 +917,10 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
955
917
|
);
|
|
956
918
|
} else {
|
|
957
919
|
selectedConnectionType = "usb";
|
|
958
|
-
connected = await this._connectUsb(target);
|
|
920
|
+
connected = await this._connectUsb(target, void 0, scope.signal);
|
|
959
921
|
}
|
|
960
922
|
}
|
|
923
|
+
_KeystoneAdapter._throwIfAborted(scope.signal);
|
|
961
924
|
if (!connected.success) return connected;
|
|
962
925
|
const record = this._devices.get(connected.payload.deviceId);
|
|
963
926
|
this._operations.endByConnectionKey(connected.payload.connectId, "explicit");
|
|
@@ -975,14 +938,13 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
975
938
|
return (0, import_hwk_adapter_core4.success)(operation.operationId);
|
|
976
939
|
} catch (err) {
|
|
977
940
|
return this._errorToFailure(err);
|
|
941
|
+
} finally {
|
|
942
|
+
scope.release();
|
|
978
943
|
}
|
|
979
944
|
}
|
|
980
945
|
/**
|
|
981
|
-
* QR has
|
|
982
|
-
*
|
|
983
|
-
* this closes the connector session and either demotes the record back to
|
|
984
|
-
* QR-only (if it was ever QR-synced) or removes it entirely (pure-USB
|
|
985
|
-
* wallet that was never seen over QR) — see §4.2 of the design doc.
|
|
946
|
+
* QR has nothing to tear down. A USB session is closed, and the record is demoted to QR-only or
|
|
947
|
+
* removed if it was never QR-synced.
|
|
986
948
|
*/
|
|
987
949
|
async releaseOperation(operationId) {
|
|
988
950
|
const operation = this._operations.find(operationId);
|
|
@@ -1006,15 +968,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1006
968
|
return;
|
|
1007
969
|
}
|
|
1008
970
|
const { usbSessionId } = record;
|
|
1009
|
-
await this._runUsbTeardown(
|
|
1010
|
-
if (this._usbConnector) {
|
|
1011
|
-
try {
|
|
1012
|
-
await this._usbConnector.disconnect(usbSessionId);
|
|
1013
|
-
} catch {
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
this._handleUsbDisconnect({ connectId: usbSessionId });
|
|
1017
|
-
});
|
|
971
|
+
await this._runUsbTeardown(() => this._retireUsbSession(usbSessionId));
|
|
1018
972
|
}
|
|
1019
973
|
getDeviceInfo(connectIdOrOperationId, deviceId) {
|
|
1020
974
|
try {
|
|
@@ -1092,8 +1046,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1092
1046
|
this.emitter.off(event, listener);
|
|
1093
1047
|
}
|
|
1094
1048
|
// ---------------------------------------------------------------------------
|
|
1095
|
-
// Explicit account import
|
|
1096
|
-
// signing calls don't each pay for their own cold-sync round trip.
|
|
1049
|
+
// Explicit account import: call before signing to avoid a cold sync per call.
|
|
1097
1050
|
// ---------------------------------------------------------------------------
|
|
1098
1051
|
async importFromQr(options = {}) {
|
|
1099
1052
|
try {
|
|
@@ -1104,7 +1057,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1104
1057
|
responseUr = await this._requestQrScanAndAwaitResponse(displayDevice, signal);
|
|
1105
1058
|
} else {
|
|
1106
1059
|
const requestUr = this.urEngine.buildKeyDerivationRequest({
|
|
1107
|
-
schemas: [
|
|
1060
|
+
schemas: [keySchema("evm", KEYSTONE_WALLET_ID_PATH)],
|
|
1108
1061
|
origin: this._origin
|
|
1109
1062
|
});
|
|
1110
1063
|
responseUr = await this._requestQrDisplayAndAwaitResponse(
|
|
@@ -1125,12 +1078,11 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1125
1078
|
// ---------------------------------------------------------------------------
|
|
1126
1079
|
// EVM
|
|
1127
1080
|
// ---------------------------------------------------------------------------
|
|
1128
|
-
async evmGetAddress(connectIdArg, deviceIdArg,
|
|
1129
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1081
|
+
async evmGetAddress(connectIdArg, deviceIdArg, params, book) {
|
|
1082
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1130
1083
|
if (!operationTarget.success) return operationTarget;
|
|
1131
1084
|
const connectId = operationTarget.payload.targetId;
|
|
1132
1085
|
const deviceId = deviceIdArg ?? void 0;
|
|
1133
|
-
const params = paramsArg;
|
|
1134
1086
|
if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "evmGetAddress requires params");
|
|
1135
1087
|
if (!params.path)
|
|
1136
1088
|
return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "evmGetAddress requires params.path");
|
|
@@ -1141,39 +1093,24 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1141
1093
|
"evmGetAddress requires a full leaf path, e.g. m/44'/60'/0'/0/0"
|
|
1142
1094
|
);
|
|
1143
1095
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
throw (0, import_hwk_adapter_core4.createHwkError)({
|
|
1157
|
-
code: import_hwk_adapter_core4.HardwareErrorCode.MethodNotSupported,
|
|
1158
|
-
message: "Keystone did not return an extended public key for this account path"
|
|
1159
|
-
});
|
|
1160
|
-
}
|
|
1161
|
-
const address = this.urEngine.deriveEvmAddressFromXpub(
|
|
1162
|
-
account.extendedPublicKey,
|
|
1163
|
-
relativeDerivePath
|
|
1164
|
-
);
|
|
1165
|
-
return (0, import_hwk_adapter_core4.success)({ address, path: normalizePath(params.path) });
|
|
1166
|
-
});
|
|
1167
|
-
} catch (err) {
|
|
1168
|
-
return this._errorToFailure(err);
|
|
1169
|
-
}
|
|
1096
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1097
|
+
const xpub = await this._fetchAccountXpub(
|
|
1098
|
+
connectId,
|
|
1099
|
+
deviceId,
|
|
1100
|
+
"evm",
|
|
1101
|
+
accountPath,
|
|
1102
|
+
signal,
|
|
1103
|
+
book
|
|
1104
|
+
);
|
|
1105
|
+
const address = this.urEngine.deriveEvmAddressFromXpub(xpub, relativeDerivePath);
|
|
1106
|
+
return (0, import_hwk_adapter_core4.success)({ address, path: normalizePath(params.path) });
|
|
1107
|
+
});
|
|
1170
1108
|
}
|
|
1171
|
-
async evmSignTransaction(connectIdArg, deviceIdArg,
|
|
1172
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1109
|
+
async evmSignTransaction(connectIdArg, deviceIdArg, params) {
|
|
1110
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1173
1111
|
if (!operationTarget.success) return operationTarget;
|
|
1174
1112
|
const connectId = operationTarget.payload.targetId;
|
|
1175
1113
|
const deviceId = deviceIdArg ?? void 0;
|
|
1176
|
-
const params = paramsArg;
|
|
1177
1114
|
if (!params)
|
|
1178
1115
|
return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "evmSignTransaction requires params");
|
|
1179
1116
|
if (!params.path) {
|
|
@@ -1187,48 +1124,34 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1187
1124
|
}
|
|
1188
1125
|
const rawTxHex = (0, import_hwk_adapter_core4.stripHex)(params.serializedTx);
|
|
1189
1126
|
const path = normalizePath(params.path);
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
);
|
|
1213
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1214
|
-
const sig = this.urEngine.parseEthSignature(responseUr);
|
|
1215
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1216
|
-
return (0, import_hwk_adapter_core4.success)({
|
|
1217
|
-
v: (0, import_hwk_adapter_core4.ensure0x)(sig.v),
|
|
1218
|
-
r: (0, import_hwk_adapter_core4.ensure0x)(sig.r),
|
|
1219
|
-
s: (0, import_hwk_adapter_core4.ensure0x)(sig.s)
|
|
1220
|
-
});
|
|
1221
|
-
});
|
|
1222
|
-
} catch (err) {
|
|
1223
|
-
return this._errorToFailure(err);
|
|
1224
|
-
}
|
|
1127
|
+
const dataType = parseInt(rawTxHex.slice(0, 2), 16) < 192 ? "typedTransaction" : "transaction";
|
|
1128
|
+
return this._signWithWallet({
|
|
1129
|
+
connectId,
|
|
1130
|
+
deviceId,
|
|
1131
|
+
chain: "evm",
|
|
1132
|
+
operationName: "evmSignTransaction",
|
|
1133
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildEthSignRequest({
|
|
1134
|
+
requestId,
|
|
1135
|
+
unsignedTxHex: rawTxHex,
|
|
1136
|
+
dataType,
|
|
1137
|
+
path,
|
|
1138
|
+
xfp,
|
|
1139
|
+
chainId: params.chainId,
|
|
1140
|
+
origin: this._origin
|
|
1141
|
+
}),
|
|
1142
|
+
parseResponse: (ur) => this.urEngine.parseEthSignature(ur),
|
|
1143
|
+
toPayload: (sig) => ({
|
|
1144
|
+
v: (0, import_hwk_adapter_core4.ensure0x)(sig.v),
|
|
1145
|
+
r: (0, import_hwk_adapter_core4.ensure0x)(sig.r),
|
|
1146
|
+
s: (0, import_hwk_adapter_core4.ensure0x)(sig.s)
|
|
1147
|
+
})
|
|
1148
|
+
});
|
|
1225
1149
|
}
|
|
1226
|
-
async evmSignMessage(connectIdArg, deviceIdArg,
|
|
1227
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1150
|
+
async evmSignMessage(connectIdArg, deviceIdArg, params) {
|
|
1151
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1228
1152
|
if (!operationTarget.success) return operationTarget;
|
|
1229
1153
|
const connectId = operationTarget.payload.targetId;
|
|
1230
1154
|
const deviceId = deviceIdArg ?? void 0;
|
|
1231
|
-
const params = paramsArg;
|
|
1232
1155
|
if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "evmSignMessage requires params");
|
|
1233
1156
|
if (!params.path || params.message === void 0) {
|
|
1234
1157
|
return (0, import_hwk_adapter_core4.failure)(
|
|
@@ -1238,43 +1161,29 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1238
1161
|
}
|
|
1239
1162
|
const messageHex = params.hex ? (0, import_hwk_adapter_core4.stripHex)(params.message) : Buffer.from(params.message, "utf8").toString("hex");
|
|
1240
1163
|
const path = normalizePath(params.path);
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
true,
|
|
1259
|
-
connectId,
|
|
1260
|
-
signal,
|
|
1261
|
-
"evmSignMessage"
|
|
1262
|
-
);
|
|
1263
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1264
|
-
const sig = this.urEngine.parseEthSignature(responseUr);
|
|
1265
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1266
|
-
return (0, import_hwk_adapter_core4.success)({ signature: (0, import_hwk_adapter_core4.ensure0x)(sig.r + sig.s + sig.v) });
|
|
1267
|
-
});
|
|
1268
|
-
} catch (err) {
|
|
1269
|
-
return this._errorToFailure(err);
|
|
1270
|
-
}
|
|
1164
|
+
return this._signWithWallet({
|
|
1165
|
+
connectId,
|
|
1166
|
+
deviceId,
|
|
1167
|
+
chain: "evm",
|
|
1168
|
+
operationName: "evmSignMessage",
|
|
1169
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildEthSignRequest({
|
|
1170
|
+
requestId,
|
|
1171
|
+
unsignedTxHex: messageHex,
|
|
1172
|
+
dataType: "personalMessage",
|
|
1173
|
+
path,
|
|
1174
|
+
xfp,
|
|
1175
|
+
chainId: params.chainId,
|
|
1176
|
+
origin: this._origin
|
|
1177
|
+
}),
|
|
1178
|
+
parseResponse: (ur) => this.urEngine.parseEthSignature(ur),
|
|
1179
|
+
toPayload: (sig) => ({ signature: (0, import_hwk_adapter_core4.ensure0x)(sig.r + sig.s + sig.v) })
|
|
1180
|
+
});
|
|
1271
1181
|
}
|
|
1272
|
-
async evmSignTypedData(connectIdArg, deviceIdArg,
|
|
1273
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1182
|
+
async evmSignTypedData(connectIdArg, deviceIdArg, params) {
|
|
1183
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1274
1184
|
if (!operationTarget.success) return operationTarget;
|
|
1275
1185
|
const connectId = operationTarget.payload.targetId;
|
|
1276
1186
|
const deviceId = deviceIdArg ?? void 0;
|
|
1277
|
-
const params = paramsArg;
|
|
1278
1187
|
if (!params)
|
|
1279
1188
|
return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "evmSignTypedData requires params");
|
|
1280
1189
|
if (!params.path) {
|
|
@@ -1291,49 +1200,33 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1291
1200
|
"utf8"
|
|
1292
1201
|
).toString("hex");
|
|
1293
1202
|
const path = normalizePath(params.path);
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
true,
|
|
1312
|
-
connectId,
|
|
1313
|
-
signal,
|
|
1314
|
-
"evmSignTypedData"
|
|
1315
|
-
);
|
|
1316
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1317
|
-
const sig = this.urEngine.parseEthSignature(responseUr);
|
|
1318
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1319
|
-
return (0, import_hwk_adapter_core4.success)({ signature: (0, import_hwk_adapter_core4.ensure0x)(sig.r + sig.s + sig.v) });
|
|
1320
|
-
});
|
|
1321
|
-
} catch (err) {
|
|
1322
|
-
return this._errorToFailure(err);
|
|
1323
|
-
}
|
|
1203
|
+
return this._signWithWallet({
|
|
1204
|
+
connectId,
|
|
1205
|
+
deviceId,
|
|
1206
|
+
chain: "evm",
|
|
1207
|
+
operationName: "evmSignTypedData",
|
|
1208
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildEthSignRequest({
|
|
1209
|
+
requestId,
|
|
1210
|
+
unsignedTxHex: signDataHex,
|
|
1211
|
+
dataType: "typedData",
|
|
1212
|
+
path,
|
|
1213
|
+
xfp,
|
|
1214
|
+
chainId: params.chainId,
|
|
1215
|
+
origin: this._origin
|
|
1216
|
+
}),
|
|
1217
|
+
parseResponse: (ur) => this.urEngine.parseEthSignature(ur),
|
|
1218
|
+
toPayload: (sig) => ({ signature: (0, import_hwk_adapter_core4.ensure0x)(sig.r + sig.s + sig.v) })
|
|
1219
|
+
});
|
|
1324
1220
|
}
|
|
1325
1221
|
// ---------------------------------------------------------------------------
|
|
1326
|
-
// BTC
|
|
1327
|
-
//
|
|
1328
|
-
// phase doesn't wire in yet; structured-field tx signing needs host-side
|
|
1329
|
-
// PSBT construction. Both are real, bounded follow-ups, not silent gaps.
|
|
1222
|
+
// BTC: 44'/49'/84' addresses derive offline from the account xpub; P2TR addresses
|
|
1223
|
+
// and structured-field tx signing are unsupported (the host builds a PSBT instead).
|
|
1330
1224
|
// ---------------------------------------------------------------------------
|
|
1331
|
-
async btcGetAddress(connectIdArg, deviceIdArg,
|
|
1332
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1225
|
+
async btcGetAddress(connectIdArg, deviceIdArg, params, book) {
|
|
1226
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1333
1227
|
if (!operationTarget.success) return operationTarget;
|
|
1334
1228
|
const connectId = operationTarget.payload.targetId;
|
|
1335
1229
|
const deviceId = deviceIdArg ?? void 0;
|
|
1336
|
-
const params = paramsArg;
|
|
1337
1230
|
if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "btcGetAddress requires params");
|
|
1338
1231
|
if (!params.path)
|
|
1339
1232
|
return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "btcGetAddress requires params.path");
|
|
@@ -1361,49 +1254,28 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1361
1254
|
if (!isKeystoneSignableBtcAccountPath(accountPath)) {
|
|
1362
1255
|
return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
|
|
1363
1256
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
throw (0, import_hwk_adapter_core4.createHwkError)({
|
|
1377
|
-
code: import_hwk_adapter_core4.HardwareErrorCode.MethodNotSupported,
|
|
1378
|
-
message: "Keystone did not return an extended public key for this account path"
|
|
1379
|
-
});
|
|
1380
|
-
}
|
|
1381
|
-
const address = this.urEngine.deriveBtcAddressFromXpub(
|
|
1382
|
-
account.extendedPublicKey,
|
|
1383
|
-
relativeDerivePath,
|
|
1384
|
-
scriptType
|
|
1385
|
-
);
|
|
1386
|
-
return (0, import_hwk_adapter_core4.success)({ address, path: normalizedPath });
|
|
1387
|
-
});
|
|
1388
|
-
} catch (err) {
|
|
1389
|
-
return this._errorToFailure(err);
|
|
1390
|
-
}
|
|
1257
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1258
|
+
const xpub = await this._fetchAccountXpub(
|
|
1259
|
+
connectId,
|
|
1260
|
+
deviceId,
|
|
1261
|
+
"btc",
|
|
1262
|
+
accountPath,
|
|
1263
|
+
signal,
|
|
1264
|
+
book
|
|
1265
|
+
);
|
|
1266
|
+
const address = this.urEngine.deriveBtcAddressFromXpub(xpub, relativeDerivePath, scriptType);
|
|
1267
|
+
return (0, import_hwk_adapter_core4.success)({ address, path: normalizedPath });
|
|
1268
|
+
});
|
|
1391
1269
|
}
|
|
1392
1270
|
/**
|
|
1393
|
-
*
|
|
1394
|
-
*
|
|
1395
|
-
* actually syncs, and it is what a host needs to derive a whole account's
|
|
1396
|
-
* addresses offline. Unlike `btcGetAddress` this has no script-type
|
|
1397
|
-
* restriction: an xpub is script-type agnostic, so `86'` (taproot) works
|
|
1398
|
-
* here even though deriving a taproot ADDRESS still needs an EC library
|
|
1399
|
-
* this package doesn't wire in.
|
|
1271
|
+
* `params.path` must be an account path (`m/84'/0'/0'`), the level Keystone syncs. An xpub is
|
|
1272
|
+
* script-type agnostic, so `86'` works here even though `btcGetAddress` rejects it.
|
|
1400
1273
|
*/
|
|
1401
|
-
async btcGetPublicKey(connectIdArg, deviceIdArg,
|
|
1402
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1274
|
+
async btcGetPublicKey(connectIdArg, deviceIdArg, params, book) {
|
|
1275
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1403
1276
|
if (!operationTarget.success) return operationTarget;
|
|
1404
1277
|
const connectId = operationTarget.payload.targetId;
|
|
1405
1278
|
const deviceId = deviceIdArg ?? void 0;
|
|
1406
|
-
const params = paramsArg;
|
|
1407
1279
|
if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "btcGetPublicKey requires params");
|
|
1408
1280
|
if (!params.path)
|
|
1409
1281
|
return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "btcGetPublicKey requires params.path");
|
|
@@ -1417,36 +1289,25 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1417
1289
|
if (!isKeystoneSignableBtcAccountPath(accountPath)) {
|
|
1418
1290
|
return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
|
|
1419
1291
|
}
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
const meta = this.urEngine.parseXpubMeta(account.extendedPublicKey);
|
|
1438
|
-
return (0, import_hwk_adapter_core4.success)({
|
|
1439
|
-
xpub: account.extendedPublicKey,
|
|
1440
|
-
publicKey: meta.publicKey,
|
|
1441
|
-
chainCode: meta.chainCode,
|
|
1442
|
-
depth: meta.depth,
|
|
1443
|
-
fingerprint: meta.parentFingerprint,
|
|
1444
|
-
path: normalizePath(accountPath)
|
|
1445
|
-
});
|
|
1292
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1293
|
+
const xpub = await this._fetchAccountXpub(
|
|
1294
|
+
connectId,
|
|
1295
|
+
deviceId,
|
|
1296
|
+
"btc",
|
|
1297
|
+
accountPath,
|
|
1298
|
+
signal,
|
|
1299
|
+
book
|
|
1300
|
+
);
|
|
1301
|
+
const meta = this.urEngine.parseXpubMeta(xpub);
|
|
1302
|
+
return (0, import_hwk_adapter_core4.success)({
|
|
1303
|
+
xpub,
|
|
1304
|
+
publicKey: meta.publicKey,
|
|
1305
|
+
chainCode: meta.chainCode,
|
|
1306
|
+
depth: meta.depth,
|
|
1307
|
+
fingerprint: meta.parentFingerprint,
|
|
1308
|
+
path: normalizePath(accountPath)
|
|
1446
1309
|
});
|
|
1447
|
-
}
|
|
1448
|
-
return this._errorToFailure(err);
|
|
1449
|
-
}
|
|
1310
|
+
});
|
|
1450
1311
|
}
|
|
1451
1312
|
async btcSignTransaction(_connectId, _deviceId, _params) {
|
|
1452
1313
|
return this._unsupported(
|
|
@@ -1454,12 +1315,11 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1454
1315
|
"use btcSignPsbt \u2014 structured input/output signing needs host-side PSBT construction, not yet wired in"
|
|
1455
1316
|
);
|
|
1456
1317
|
}
|
|
1457
|
-
async btcSignPsbt(connectIdArg, deviceIdArg,
|
|
1458
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1318
|
+
async btcSignPsbt(connectIdArg, deviceIdArg, params) {
|
|
1319
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1459
1320
|
if (!operationTarget.success) return operationTarget;
|
|
1460
1321
|
const connectId = operationTarget.payload.targetId;
|
|
1461
1322
|
const deviceId = deviceIdArg ?? void 0;
|
|
1462
|
-
const params = paramsArg;
|
|
1463
1323
|
if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "btcSignPsbt requires params");
|
|
1464
1324
|
if (!params.psbt)
|
|
1465
1325
|
return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "btcSignPsbt requires params.psbt");
|
|
@@ -1468,33 +1328,28 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1468
1328
|
if (!isKeystoneSignableBtcAccountPath(splitAccountPath(params.path).accountPath)) {
|
|
1469
1329
|
return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
|
|
1470
1330
|
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
});
|
|
1488
|
-
} catch (err) {
|
|
1489
|
-
return this._errorToFailure(err);
|
|
1490
|
-
}
|
|
1331
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1332
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, "btc", signal);
|
|
1333
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1334
|
+
const requestUr = this.urEngine.buildBtcPsbtRequest((0, import_hwk_adapter_core4.stripHex)(params.psbt));
|
|
1335
|
+
const responseUr = await this._resolveUr(
|
|
1336
|
+
record,
|
|
1337
|
+
requestUr,
|
|
1338
|
+
true,
|
|
1339
|
+
connectId,
|
|
1340
|
+
signal,
|
|
1341
|
+
"btcSignPsbt"
|
|
1342
|
+
);
|
|
1343
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1344
|
+
const signedPsbt = this.urEngine.parseBtcPsbt(responseUr);
|
|
1345
|
+
return (0, import_hwk_adapter_core4.success)({ signedPsbt });
|
|
1346
|
+
});
|
|
1491
1347
|
}
|
|
1492
|
-
async btcSignMessage(connectIdArg, deviceIdArg,
|
|
1493
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1348
|
+
async btcSignMessage(connectIdArg, deviceIdArg, params) {
|
|
1349
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1494
1350
|
if (!operationTarget.success) return operationTarget;
|
|
1495
1351
|
const connectId = operationTarget.payload.targetId;
|
|
1496
1352
|
const deviceId = deviceIdArg ?? void 0;
|
|
1497
|
-
const params = paramsArg;
|
|
1498
1353
|
if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "btcSignMessage requires params");
|
|
1499
1354
|
if (!params.path || params.message === void 0) {
|
|
1500
1355
|
return (0, import_hwk_adapter_core4.failure)(
|
|
@@ -1507,80 +1362,55 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1507
1362
|
if (!isKeystoneSignableBtcAccountPath(splitAccountPath(path).accountPath)) {
|
|
1508
1363
|
return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
|
|
1509
1364
|
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
true,
|
|
1525
|
-
connectId,
|
|
1526
|
-
signal,
|
|
1527
|
-
"btcSignMessage"
|
|
1528
|
-
);
|
|
1529
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1530
|
-
const sig = this.urEngine.parseBtcSignature(responseUr);
|
|
1531
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1532
|
-
return (0, import_hwk_adapter_core4.success)({ signature: sig.signature });
|
|
1533
|
-
});
|
|
1534
|
-
} catch (err) {
|
|
1535
|
-
return this._errorToFailure(err);
|
|
1536
|
-
}
|
|
1365
|
+
return this._signWithWallet({
|
|
1366
|
+
connectId,
|
|
1367
|
+
deviceId,
|
|
1368
|
+
chain: "btc",
|
|
1369
|
+
operationName: "btcSignMessage",
|
|
1370
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildBtcMessageSignRequest({
|
|
1371
|
+
requestId,
|
|
1372
|
+
messageHex,
|
|
1373
|
+
accounts: [{ path, xfp }],
|
|
1374
|
+
origin: this._origin
|
|
1375
|
+
}),
|
|
1376
|
+
parseResponse: (ur) => this.urEngine.parseBtcSignature(ur),
|
|
1377
|
+
toPayload: (sig) => ({ signature: sig.signature })
|
|
1378
|
+
});
|
|
1537
1379
|
}
|
|
1538
1380
|
async btcGetMasterFingerprint(connectIdArg, deviceIdArg, paramsArg) {
|
|
1539
1381
|
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
|
|
1540
1382
|
if (!operationTarget.success) return operationTarget;
|
|
1541
1383
|
const connectId = operationTarget.payload.targetId;
|
|
1542
1384
|
const deviceId = deviceIdArg ?? void 0;
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
});
|
|
1548
|
-
} catch (err) {
|
|
1549
|
-
return this._errorToFailure(err);
|
|
1550
|
-
}
|
|
1385
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1386
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, "btc", signal);
|
|
1387
|
+
return (0, import_hwk_adapter_core4.success)({ masterFingerprint: record.masterFingerprint });
|
|
1388
|
+
});
|
|
1551
1389
|
}
|
|
1552
1390
|
// ---------------------------------------------------------------------------
|
|
1553
1391
|
// SOL
|
|
1554
1392
|
// ---------------------------------------------------------------------------
|
|
1555
|
-
async solGetAddress(connectIdArg, deviceIdArg,
|
|
1556
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1393
|
+
async solGetAddress(connectIdArg, deviceIdArg, params, book) {
|
|
1394
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1557
1395
|
if (!operationTarget.success) return operationTarget;
|
|
1558
1396
|
const connectId = operationTarget.payload.targetId;
|
|
1559
1397
|
const deviceId = deviceIdArg ?? void 0;
|
|
1560
|
-
const params = paramsArg;
|
|
1561
1398
|
if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "solGetAddress requires params");
|
|
1562
1399
|
if (!params.path)
|
|
1563
1400
|
return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "solGetAddress requires params.path");
|
|
1564
1401
|
const path = normalizePath(params.path);
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
const address = import_bs58.default.encode(Buffer.from(account.publicKey, "hex"));
|
|
1572
|
-
return (0, import_hwk_adapter_core4.success)({ address, path });
|
|
1573
|
-
});
|
|
1574
|
-
} catch (err) {
|
|
1575
|
-
return this._errorToFailure(err);
|
|
1576
|
-
}
|
|
1402
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1403
|
+
const { account } = await this._fetchAccount(connectId, deviceId, "sol", path, signal, book);
|
|
1404
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1405
|
+
const address = import_bs58.default.encode(Buffer.from(account.publicKey, "hex"));
|
|
1406
|
+
return (0, import_hwk_adapter_core4.success)({ address, path });
|
|
1407
|
+
});
|
|
1577
1408
|
}
|
|
1578
|
-
async solSignTransaction(connectIdArg, deviceIdArg,
|
|
1579
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1409
|
+
async solSignTransaction(connectIdArg, deviceIdArg, params) {
|
|
1410
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1580
1411
|
if (!operationTarget.success) return operationTarget;
|
|
1581
1412
|
const connectId = operationTarget.payload.targetId;
|
|
1582
1413
|
const deviceId = deviceIdArg ?? void 0;
|
|
1583
|
-
const params = paramsArg;
|
|
1584
1414
|
if (!params)
|
|
1585
1415
|
return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "solSignTransaction requires params");
|
|
1586
1416
|
if (!params.path || !params.serializedTx) {
|
|
@@ -1590,42 +1420,28 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1590
1420
|
);
|
|
1591
1421
|
}
|
|
1592
1422
|
const path = normalizePath(params.path);
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
true,
|
|
1610
|
-
connectId,
|
|
1611
|
-
signal,
|
|
1612
|
-
"solSignTransaction"
|
|
1613
|
-
);
|
|
1614
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1615
|
-
const sig = this.urEngine.parseSolSignature(responseUr);
|
|
1616
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1617
|
-
return (0, import_hwk_adapter_core4.success)({ signature: sig.signature });
|
|
1618
|
-
});
|
|
1619
|
-
} catch (err) {
|
|
1620
|
-
return this._errorToFailure(err);
|
|
1621
|
-
}
|
|
1423
|
+
return this._signWithWallet({
|
|
1424
|
+
connectId,
|
|
1425
|
+
deviceId,
|
|
1426
|
+
chain: "sol",
|
|
1427
|
+
operationName: "solSignTransaction",
|
|
1428
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildSolSignRequest({
|
|
1429
|
+
requestId,
|
|
1430
|
+
unsignedPayloadHex: (0, import_hwk_adapter_core4.stripHex)(params.serializedTx),
|
|
1431
|
+
dataType: "transaction",
|
|
1432
|
+
path,
|
|
1433
|
+
xfp,
|
|
1434
|
+
origin: this._origin
|
|
1435
|
+
}),
|
|
1436
|
+
parseResponse: (ur) => this.urEngine.parseSolSignature(ur),
|
|
1437
|
+
toPayload: (sig) => ({ signature: sig.signature })
|
|
1438
|
+
});
|
|
1622
1439
|
}
|
|
1623
|
-
async solSignMessage(connectIdArg, deviceIdArg,
|
|
1624
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1440
|
+
async solSignMessage(connectIdArg, deviceIdArg, params) {
|
|
1441
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1625
1442
|
if (!operationTarget.success) return operationTarget;
|
|
1626
1443
|
const connectId = operationTarget.payload.targetId;
|
|
1627
1444
|
const deviceId = deviceIdArg ?? void 0;
|
|
1628
|
-
const params = paramsArg;
|
|
1629
1445
|
if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "solSignMessage requires params");
|
|
1630
1446
|
if (!params.path || !params.message) {
|
|
1631
1447
|
return (0, import_hwk_adapter_core4.failure)(
|
|
@@ -1634,50 +1450,32 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1634
1450
|
);
|
|
1635
1451
|
}
|
|
1636
1452
|
const path = normalizePath(params.path);
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
true,
|
|
1654
|
-
connectId,
|
|
1655
|
-
signal,
|
|
1656
|
-
"solSignMessage"
|
|
1657
|
-
);
|
|
1658
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1659
|
-
const sig = this.urEngine.parseSolSignature(responseUr);
|
|
1660
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1661
|
-
return (0, import_hwk_adapter_core4.success)({ signature: sig.signature });
|
|
1662
|
-
});
|
|
1663
|
-
} catch (err) {
|
|
1664
|
-
return this._errorToFailure(err);
|
|
1665
|
-
}
|
|
1453
|
+
return this._signWithWallet({
|
|
1454
|
+
connectId,
|
|
1455
|
+
deviceId,
|
|
1456
|
+
chain: "sol",
|
|
1457
|
+
operationName: "solSignMessage",
|
|
1458
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildSolSignRequest({
|
|
1459
|
+
requestId,
|
|
1460
|
+
unsignedPayloadHex: (0, import_hwk_adapter_core4.stripHex)(params.message),
|
|
1461
|
+
dataType: "message",
|
|
1462
|
+
path,
|
|
1463
|
+
xfp,
|
|
1464
|
+
origin: this._origin
|
|
1465
|
+
}),
|
|
1466
|
+
parseResponse: (ur) => this.urEngine.parseSolSignature(ur),
|
|
1467
|
+
toPayload: (sig) => ({ signature: sig.signature })
|
|
1468
|
+
});
|
|
1666
1469
|
}
|
|
1667
1470
|
// ---------------------------------------------------------------------------
|
|
1668
|
-
// TRON
|
|
1669
|
-
//
|
|
1670
|
-
// (tags 5201/5202, firmware >= 2.5.0). The OneKey Pro air-gap encoding under
|
|
1671
|
-
// the same tag is a different layout the firmware rejects. Not keystone-sdk's
|
|
1672
|
-
// bundled `sdk.tron` module either (protobuf-based, unverified response
|
|
1673
|
-
// semantics).
|
|
1471
|
+
// TRON: keystone-sdk-rust layout (tags 5201/5202, firmware >= 2.5.0), not sdk.tron's
|
|
1472
|
+
// protobuf module; OneKey Pro's encoding under the same tag is rejected by the firmware.
|
|
1674
1473
|
// ---------------------------------------------------------------------------
|
|
1675
|
-
async tronGetAddress(connectIdArg, deviceIdArg,
|
|
1676
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1474
|
+
async tronGetAddress(connectIdArg, deviceIdArg, params, book) {
|
|
1475
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1677
1476
|
if (!operationTarget.success) return operationTarget;
|
|
1678
1477
|
const connectId = operationTarget.payload.targetId;
|
|
1679
1478
|
const deviceId = deviceIdArg ?? void 0;
|
|
1680
|
-
const params = paramsArg;
|
|
1681
1479
|
if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "tronGetAddress requires params");
|
|
1682
1480
|
if (!params.path)
|
|
1683
1481
|
return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "tronGetAddress requires params.path");
|
|
@@ -1688,39 +1486,24 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1688
1486
|
"tronGetAddress requires a full leaf path, e.g. m/44'/195'/0'/0/0"
|
|
1689
1487
|
);
|
|
1690
1488
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
throw (0, import_hwk_adapter_core4.createHwkError)({
|
|
1704
|
-
code: import_hwk_adapter_core4.HardwareErrorCode.MethodNotSupported,
|
|
1705
|
-
message: "Keystone did not return an extended public key for this account path"
|
|
1706
|
-
});
|
|
1707
|
-
}
|
|
1708
|
-
const address = this.urEngine.deriveTronAddressFromXpub(
|
|
1709
|
-
account.extendedPublicKey,
|
|
1710
|
-
relativeDerivePath
|
|
1711
|
-
);
|
|
1712
|
-
return (0, import_hwk_adapter_core4.success)({ address, path: normalizePath(params.path) });
|
|
1713
|
-
});
|
|
1714
|
-
} catch (err) {
|
|
1715
|
-
return this._errorToFailure(err);
|
|
1716
|
-
}
|
|
1489
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1490
|
+
const xpub = await this._fetchAccountXpub(
|
|
1491
|
+
connectId,
|
|
1492
|
+
deviceId,
|
|
1493
|
+
"tron",
|
|
1494
|
+
accountPath,
|
|
1495
|
+
signal,
|
|
1496
|
+
book
|
|
1497
|
+
);
|
|
1498
|
+
const address = this.urEngine.deriveTronAddressFromXpub(xpub, relativeDerivePath);
|
|
1499
|
+
return (0, import_hwk_adapter_core4.success)({ address, path: normalizePath(params.path) });
|
|
1500
|
+
});
|
|
1717
1501
|
}
|
|
1718
|
-
async tronSignTransaction(connectIdArg, deviceIdArg,
|
|
1719
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1502
|
+
async tronSignTransaction(connectIdArg, deviceIdArg, params) {
|
|
1503
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1720
1504
|
if (!operationTarget.success) return operationTarget;
|
|
1721
1505
|
const connectId = operationTarget.payload.targetId;
|
|
1722
1506
|
const deviceId = deviceIdArg ?? void 0;
|
|
1723
|
-
const params = paramsArg;
|
|
1724
1507
|
if (!params)
|
|
1725
1508
|
return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "tronSignTransaction requires params");
|
|
1726
1509
|
if (!params.path) {
|
|
@@ -1734,41 +1517,28 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1734
1517
|
}
|
|
1735
1518
|
const rawTxHex = (0, import_hwk_adapter_core4.stripHex)(params.rawTxHex);
|
|
1736
1519
|
const path = normalizePath(params.path);
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
connectId,
|
|
1754
|
-
signal,
|
|
1755
|
-
"tronSignTransaction"
|
|
1756
|
-
);
|
|
1757
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1758
|
-
const sig = this.urEngine.parseTronSignature(responseUr);
|
|
1759
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1760
|
-
return (0, import_hwk_adapter_core4.success)({ signature: sig.signature });
|
|
1761
|
-
});
|
|
1762
|
-
} catch (err) {
|
|
1763
|
-
return this._errorToFailure(err);
|
|
1764
|
-
}
|
|
1520
|
+
return this._signWithWallet({
|
|
1521
|
+
connectId,
|
|
1522
|
+
deviceId,
|
|
1523
|
+
chain: "tron",
|
|
1524
|
+
operationName: "tronSignTransaction",
|
|
1525
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildTronSignRequest({
|
|
1526
|
+
requestId,
|
|
1527
|
+
rawTxHex,
|
|
1528
|
+
path,
|
|
1529
|
+
xfp,
|
|
1530
|
+
origin: this._origin
|
|
1531
|
+
}),
|
|
1532
|
+
parseResponse: (ur) => this.urEngine.parseTronSignature(ur),
|
|
1533
|
+
// Keystone signs rawTxHex as given and returns only the signature, so no serializedTx.
|
|
1534
|
+
toPayload: (sig) => ({ signature: sig.signature })
|
|
1535
|
+
});
|
|
1765
1536
|
}
|
|
1766
|
-
async tronSignMessage(connectIdArg, deviceIdArg,
|
|
1767
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1537
|
+
async tronSignMessage(connectIdArg, deviceIdArg, params) {
|
|
1538
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1768
1539
|
if (!operationTarget.success) return operationTarget;
|
|
1769
1540
|
const connectId = operationTarget.payload.targetId;
|
|
1770
1541
|
const deviceId = deviceIdArg ?? void 0;
|
|
1771
|
-
const params = paramsArg;
|
|
1772
1542
|
if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "tronSignMessage requires params");
|
|
1773
1543
|
if (!params.path) {
|
|
1774
1544
|
return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "tronSignMessage requires params.path");
|
|
@@ -1784,41 +1554,75 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1784
1554
|
}
|
|
1785
1555
|
const messageHex = (0, import_hwk_adapter_core4.stripHex)(params.messageHex);
|
|
1786
1556
|
const path = normalizePath(params.path);
|
|
1557
|
+
return this._signWithWallet({
|
|
1558
|
+
connectId,
|
|
1559
|
+
deviceId,
|
|
1560
|
+
chain: "tron",
|
|
1561
|
+
operationName: "tronSignMessage",
|
|
1562
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildTronSignRequest({
|
|
1563
|
+
requestId,
|
|
1564
|
+
rawTxHex: messageHex,
|
|
1565
|
+
path,
|
|
1566
|
+
xfp,
|
|
1567
|
+
signType: 2 /* PersonalMessage */,
|
|
1568
|
+
origin: this._origin
|
|
1569
|
+
}),
|
|
1570
|
+
parseResponse: (ur) => this.urEngine.parseTronSignature(ur),
|
|
1571
|
+
toPayload: (sig) => ({ signature: sig.signature })
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
// ---------------------------------------------------------------------------
|
|
1575
|
+
// Internals
|
|
1576
|
+
// ---------------------------------------------------------------------------
|
|
1577
|
+
async _runJob(connectId, deviceId, job) {
|
|
1787
1578
|
try {
|
|
1788
|
-
return await this._jobQueue.enqueue(keystoneQueueKey(connectId, deviceId),
|
|
1789
|
-
const { record } = await this._ensureWalletKnown(connectId, deviceId, "tron", signal);
|
|
1790
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1791
|
-
const requestId = (0, import_uuid2.v4)();
|
|
1792
|
-
const requestUr = this.urEngine.buildTronSignRequest({
|
|
1793
|
-
requestId,
|
|
1794
|
-
rawTxHex: messageHex,
|
|
1795
|
-
path,
|
|
1796
|
-
xfp: record.masterFingerprint,
|
|
1797
|
-
signType: 2 /* PersonalMessage */,
|
|
1798
|
-
origin: this._origin
|
|
1799
|
-
});
|
|
1800
|
-
const responseUr = await this._resolveUr(
|
|
1801
|
-
record,
|
|
1802
|
-
requestUr,
|
|
1803
|
-
true,
|
|
1804
|
-
connectId,
|
|
1805
|
-
signal,
|
|
1806
|
-
"tronSignMessage"
|
|
1807
|
-
);
|
|
1808
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1809
|
-
const sig = this.urEngine.parseTronSignature(responseUr);
|
|
1810
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1811
|
-
return (0, import_hwk_adapter_core4.success)({ signature: sig.signature });
|
|
1812
|
-
});
|
|
1579
|
+
return await this._jobQueue.enqueue(keystoneQueueKey(connectId, deviceId), job);
|
|
1813
1580
|
} catch (err) {
|
|
1814
1581
|
return this._errorToFailure(err);
|
|
1815
1582
|
}
|
|
1816
1583
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1584
|
+
async _fetchAccountXpub(connectId, deviceId, hwkChain, accountPath, signal, book) {
|
|
1585
|
+
const { account } = await this._fetchAccount(
|
|
1586
|
+
connectId,
|
|
1587
|
+
deviceId,
|
|
1588
|
+
hwkChain,
|
|
1589
|
+
accountPath,
|
|
1590
|
+
signal,
|
|
1591
|
+
book
|
|
1592
|
+
);
|
|
1593
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1594
|
+
if (!account.extendedPublicKey) {
|
|
1595
|
+
throw (0, import_hwk_adapter_core4.createHwkError)({
|
|
1596
|
+
code: import_hwk_adapter_core4.HardwareErrorCode.MethodNotSupported,
|
|
1597
|
+
message: "Keystone did not return an extended public key for this account path"
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
return account.extendedPublicKey;
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Signing needs only the wallet's mfp: the device re-derives the key from path+xfp, so no
|
|
1604
|
+
* per-path account lookup happens here.
|
|
1605
|
+
*/
|
|
1606
|
+
_signWithWallet(args) {
|
|
1607
|
+
const { connectId, deviceId } = args;
|
|
1608
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1609
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, args.chain, signal);
|
|
1610
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1611
|
+
const requestId = (0, import_uuid2.v4)();
|
|
1612
|
+
const requestUr = args.buildRequest(requestId, record.masterFingerprint);
|
|
1613
|
+
const responseUr = await this._resolveUr(
|
|
1614
|
+
record,
|
|
1615
|
+
requestUr,
|
|
1616
|
+
true,
|
|
1617
|
+
connectId,
|
|
1618
|
+
signal,
|
|
1619
|
+
args.operationName
|
|
1620
|
+
);
|
|
1621
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1622
|
+
const signature = args.parseResponse(responseUr);
|
|
1623
|
+
_KeystoneAdapter._assertRequestIdMatches(requestId, signature.requestId);
|
|
1624
|
+
return (0, import_hwk_adapter_core4.success)(args.toPayload(signature));
|
|
1625
|
+
});
|
|
1822
1626
|
}
|
|
1823
1627
|
_allNetworkSyncSchema(item) {
|
|
1824
1628
|
if (!(0, import_hwk_adapter_core4.isAllNetworkMethodName)(String(item.methodName)) || !item.path) return void 0;
|
|
@@ -1841,16 +1645,13 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1841
1645
|
}
|
|
1842
1646
|
}
|
|
1843
1647
|
/**
|
|
1844
|
-
* QR
|
|
1845
|
-
*
|
|
1846
|
-
* chains or derivation paths the host bundled. USB keeps its proven
|
|
1847
|
-
* one-path-at-a-time export flow because the device limits that channel.
|
|
1648
|
+
* QR answers the whole bundle with one request QR and one scan. USB exports one path per
|
|
1649
|
+
* request because the device limits that channel.
|
|
1848
1650
|
*/
|
|
1849
1651
|
async _prefetchAllNetworkAccounts(connectId, deviceId, params) {
|
|
1850
1652
|
return this._jobQueue.enqueue(keystoneQueueKey(connectId, deviceId), async (signal) => {
|
|
1851
1653
|
const target = this._resolveTarget(connectId, deviceId);
|
|
1852
|
-
|
|
1853
|
-
let record = targetRecord;
|
|
1654
|
+
let { record } = target;
|
|
1854
1655
|
const requestedByKey = /* @__PURE__ */ new Map();
|
|
1855
1656
|
for (const item of params.bundle) {
|
|
1856
1657
|
const schema = this._allNetworkSyncSchema(item);
|
|
@@ -1860,11 +1661,23 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1860
1661
|
if (!requestedByKey.size) {
|
|
1861
1662
|
return { book };
|
|
1862
1663
|
}
|
|
1664
|
+
const requestedSchemas = Array.from(requestedByKey.values());
|
|
1665
|
+
const assertAllBooked = () => {
|
|
1666
|
+
for (const schema of requestedSchemas) {
|
|
1667
|
+
if (!book.has(accountKey(schema.hwkChain, schema.path))) {
|
|
1668
|
+
throw (0, import_hwk_adapter_core4.createHwkError)({
|
|
1669
|
+
code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
|
|
1670
|
+
message: `Keystone did not return the requested derivation path (${schema.path})`
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
};
|
|
1863
1675
|
const operation = (0, import_hwk_adapter_core4.isHardwareOperationId)(params.operationId) ? this._operations.find(params.operationId) : void 0;
|
|
1864
1676
|
const operationRoute = operation ? this._operationRoutes.get(operation.connectId) : void 0;
|
|
1865
1677
|
const routedToQr = operationRoute?.operationId === operation?.operationId && operationRoute?.connectionType === "qr";
|
|
1866
1678
|
if (this._forcedTransport !== "qr" && !routedToQr && !record?.usbSessionId && this._usbConnector && target.expectedWalletId) {
|
|
1867
1679
|
record = await this._tryUsbAttach(
|
|
1680
|
+
signal,
|
|
1868
1681
|
target.expectedWalletId,
|
|
1869
1682
|
record?.masterFingerprint ?? target.expectedMasterFingerprint
|
|
1870
1683
|
) ?? record;
|
|
@@ -1880,8 +1693,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1880
1693
|
});
|
|
1881
1694
|
}
|
|
1882
1695
|
};
|
|
1883
|
-
const
|
|
1884
|
-
for (const schema of missingSchemas2) {
|
|
1696
|
+
for (const schema of requestedSchemas) {
|
|
1885
1697
|
if (!book.has(accountKey(schema.hwkChain, schema.path))) {
|
|
1886
1698
|
assertBundleSession();
|
|
1887
1699
|
const synced = await this._fetchAccount(
|
|
@@ -1890,20 +1702,13 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1890
1702
|
schema.hwkChain,
|
|
1891
1703
|
schema.path,
|
|
1892
1704
|
signal,
|
|
1893
|
-
|
|
1705
|
+
book
|
|
1894
1706
|
);
|
|
1895
1707
|
record = synced.record;
|
|
1896
1708
|
assertBundleSession();
|
|
1897
1709
|
}
|
|
1898
1710
|
}
|
|
1899
|
-
|
|
1900
|
-
if (!book.has(accountKey(schema.hwkChain, schema.path))) {
|
|
1901
|
-
throw (0, import_hwk_adapter_core4.createHwkError)({
|
|
1902
|
-
code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
|
|
1903
|
-
message: `Keystone did not return the requested derivation path (${schema.path})`
|
|
1904
|
-
});
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1711
|
+
assertAllBooked();
|
|
1907
1712
|
return { book, walletId: record.walletId };
|
|
1908
1713
|
}
|
|
1909
1714
|
if (this._forcedTransport === "usb") {
|
|
@@ -1912,16 +1717,14 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1912
1717
|
message: "USB channel is not connected for this Keystone wallet"
|
|
1913
1718
|
});
|
|
1914
1719
|
}
|
|
1915
|
-
const missingSchemas = Array.from(requestedByKey.values());
|
|
1916
1720
|
const schemas = [
|
|
1917
1721
|
{ hwkChain: "evm", path: KEYSTONE_WALLET_ID_PATH },
|
|
1918
|
-
...
|
|
1722
|
+
...requestedSchemas.filter(
|
|
1723
|
+
(schema) => normalizePath(schema.path) !== KEYSTONE_WALLET_ID_PATH
|
|
1724
|
+
)
|
|
1919
1725
|
];
|
|
1920
1726
|
const requestUr = this.urEngine.buildKeyDerivationRequest({
|
|
1921
|
-
schemas: schemas.map((schema) => (
|
|
1922
|
-
path: schema.path,
|
|
1923
|
-
curve: schema.hwkChain === "sol" ? "ed25519" : "secp256k1"
|
|
1924
|
-
})),
|
|
1727
|
+
schemas: schemas.map((schema) => keySchema(schema.hwkChain, schema.path)),
|
|
1925
1728
|
origin: this._origin
|
|
1926
1729
|
});
|
|
1927
1730
|
const responseUr = await this._requestQrDisplayAndAwaitResponse(
|
|
@@ -1940,20 +1743,10 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1940
1743
|
const normalizedPath = normalizePath(account.path);
|
|
1941
1744
|
const hwkChain = requestedChainByPath.get(normalizedPath) ?? inferHwkChainFromPath(normalizedPath);
|
|
1942
1745
|
if (hwkChain) {
|
|
1943
|
-
book.set(accountKey(hwkChain, normalizedPath), {
|
|
1944
|
-
...account,
|
|
1945
|
-
hwkChain
|
|
1946
|
-
});
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
for (const schema of missingSchemas) {
|
|
1950
|
-
if (!book.has(accountKey(schema.hwkChain, schema.path))) {
|
|
1951
|
-
throw (0, import_hwk_adapter_core4.createHwkError)({
|
|
1952
|
-
code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
|
|
1953
|
-
message: `Keystone did not return the requested derivation path (${schema.path})`
|
|
1954
|
-
});
|
|
1746
|
+
book.set(accountKey(hwkChain, normalizedPath), { ...account, hwkChain });
|
|
1955
1747
|
}
|
|
1956
1748
|
}
|
|
1749
|
+
assertAllBooked();
|
|
1957
1750
|
return { book, walletId: record.walletId };
|
|
1958
1751
|
});
|
|
1959
1752
|
}
|
|
@@ -1962,15 +1755,12 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1962
1755
|
* fingerprint is protocol metadata and is never accepted as a lookup key.
|
|
1963
1756
|
*/
|
|
1964
1757
|
_resolveTarget(connectId, deviceId) {
|
|
1965
|
-
|
|
1966
|
-
if ((0, import_hwk_adapter_core4.isHardwareOperationId)(connectId)) {
|
|
1967
|
-
resolvedConnectId = this._operations.resolve(connectId).connectId;
|
|
1968
|
-
}
|
|
1758
|
+
const resolvedConnectId = (0, import_hwk_adapter_core4.isHardwareOperationId)(connectId) ? this._operations.resolve(connectId).connectId : connectId;
|
|
1969
1759
|
const identifiers = [deviceId, resolvedConnectId].filter(
|
|
1970
1760
|
(identifier) => Boolean(identifier)
|
|
1971
1761
|
);
|
|
1972
1762
|
if (!identifiers.length) return {};
|
|
1973
|
-
const walletIds = identifiers.map((identifier) =>
|
|
1763
|
+
const walletIds = identifiers.map((identifier) => keystoneWalletIdFromIdentifier(identifier)).filter((walletId) => Boolean(walletId));
|
|
1974
1764
|
if (!walletIds.length) {
|
|
1975
1765
|
throw (0, import_hwk_adapter_core4.createHwkError)({
|
|
1976
1766
|
code: import_hwk_adapter_core4.HardwareErrorCode.DeviceNotFound,
|
|
@@ -1984,7 +1774,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1984
1774
|
message: "Keystone connectId and deviceId identify different wallets"
|
|
1985
1775
|
});
|
|
1986
1776
|
}
|
|
1987
|
-
if (identifiers.some((identifier) => !
|
|
1777
|
+
if (identifiers.some((identifier) => !keystoneWalletIdFromIdentifier(identifier))) {
|
|
1988
1778
|
throw (0, import_hwk_adapter_core4.createHwkError)({
|
|
1989
1779
|
code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
|
|
1990
1780
|
message: "Keystone connectId and deviceId are inconsistent"
|
|
@@ -2014,13 +1804,8 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2014
1804
|
return walletId;
|
|
2015
1805
|
}
|
|
2016
1806
|
/**
|
|
2017
|
-
* Folds a parsed account
|
|
2018
|
-
*
|
|
2019
|
-
* `_resolveUr` routes a KeyDerivation sync over USB when the target record
|
|
2020
|
-
* already has a live session, so this must NOT unconditionally mark
|
|
2021
|
-
* `qrSynced`, or a USB-only wallet would wrongly survive a later USB
|
|
2022
|
-
* disconnect as a "QR-synced, demote to QR-only" entry instead of being
|
|
2023
|
-
* dropped outright (see `releaseOperation`).
|
|
1807
|
+
* Folds a parsed account response into the device table. Only non-USB round trips mark
|
|
1808
|
+
* `qrSynced`, so a USB-only wallet is dropped on disconnect.
|
|
2024
1809
|
*/
|
|
2025
1810
|
_upsertDeviceRecord(parsed, options) {
|
|
2026
1811
|
const walletId = deriveKeystoneWalletId(parsed.accounts);
|
|
@@ -2046,34 +1831,38 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2046
1831
|
return record;
|
|
2047
1832
|
}
|
|
2048
1833
|
/**
|
|
2049
|
-
* Opens
|
|
2050
|
-
*
|
|
2051
|
-
* one fixed account-level xpub and derives the stable wallet id. A
|
|
2052
|
-
* QR-synced entry becomes
|
|
2053
|
-
* `{qr, usb}`-capable in place (one `device-changed`, not a second
|
|
2054
|
-
* `device-connect`); a wallet never seen before becomes a new USB-only
|
|
2055
|
-
* entry. See §4.2 of the design doc.
|
|
1834
|
+
* Opens a permitted USB Keystone, reads its mfp, and derives the wallet id from the identity
|
|
1835
|
+
* xpub. A known wallet gains USB in place (`device-changed`); an unseen one becomes USB-only.
|
|
2056
1836
|
*/
|
|
2057
|
-
async _connectUsb(expected, searchTargetId) {
|
|
2058
|
-
if (this._pendingUsbTeardowns > 0) {
|
|
1837
|
+
async _connectUsb(expected, searchTargetId, signal) {
|
|
1838
|
+
if (this._pendingUsbTeardowns > 0 || this._abandonedUsbConnects > 0) {
|
|
2059
1839
|
return (0, import_hwk_adapter_core4.failure)(
|
|
2060
1840
|
import_hwk_adapter_core4.HardwareErrorCode.DeviceBusyInternal,
|
|
2061
1841
|
"Keystone USB session is being released"
|
|
2062
1842
|
);
|
|
2063
1843
|
}
|
|
2064
|
-
|
|
2065
|
-
let
|
|
2066
|
-
|
|
2067
|
-
|
|
1844
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1845
|
+
let abandoned = false;
|
|
1846
|
+
const onAbort = () => {
|
|
1847
|
+
abandoned = true;
|
|
1848
|
+
this._abandonedUsbConnects += 1;
|
|
1849
|
+
};
|
|
1850
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1851
|
+
const pending = this._usbConnectTail.then(async () => {
|
|
1852
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1853
|
+
return this._connectUsbExclusive(expected, searchTargetId, signal);
|
|
2068
1854
|
});
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
}
|
|
2073
|
-
|
|
2074
|
-
|
|
1855
|
+
const settled = pending.finally(() => {
|
|
1856
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1857
|
+
if (abandoned) this._abandonedUsbConnects -= 1;
|
|
1858
|
+
});
|
|
1859
|
+
this._usbConnectTail = settled.then(
|
|
1860
|
+
() => void 0,
|
|
1861
|
+
() => void 0
|
|
1862
|
+
);
|
|
1863
|
+
return signal ? _KeystoneAdapter._abortable(signal, settled) : settled;
|
|
2075
1864
|
}
|
|
2076
|
-
async _connectUsbExclusive(expected, searchTargetId) {
|
|
1865
|
+
async _connectUsbExclusive(expected, searchTargetId, signal) {
|
|
2077
1866
|
if (!this._usbConnector) {
|
|
2078
1867
|
return (0, import_hwk_adapter_core4.failure)(
|
|
2079
1868
|
import_hwk_adapter_core4.HardwareErrorCode.TransportNotAvailable,
|
|
@@ -2094,6 +1883,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2094
1883
|
const connector = this._usbConnector;
|
|
2095
1884
|
const session = await (connector.connectTarget ? connector.connectTarget(connectTarget) : connector.connect(searchTargetId ?? expected.expectedMasterFingerprint));
|
|
2096
1885
|
sessionId = session.sessionId;
|
|
1886
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2097
1887
|
const raw = session.deviceInfo.raw;
|
|
2098
1888
|
const mfpValue = (0, import_hwk_adapter_core4.parseBip32MasterFingerprint)(raw?.masterFingerprint);
|
|
2099
1889
|
if (!mfpValue) {
|
|
@@ -2110,7 +1900,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2110
1900
|
});
|
|
2111
1901
|
}
|
|
2112
1902
|
const identityRequest = this.urEngine.buildKeyDerivationRequest({
|
|
2113
|
-
schemas: [
|
|
1903
|
+
schemas: [keySchema("evm", KEYSTONE_WALLET_ID_PATH)],
|
|
2114
1904
|
origin: this._origin
|
|
2115
1905
|
});
|
|
2116
1906
|
const identityResult = await this._callUsbConnector(
|
|
@@ -2118,6 +1908,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2118
1908
|
"resolveUr",
|
|
2119
1909
|
identityRequest
|
|
2120
1910
|
);
|
|
1911
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2121
1912
|
if (!identityResult.success) {
|
|
2122
1913
|
throw (0, import_hwk_adapter_core4.rehydrateConnectorError)(identityResult.error);
|
|
2123
1914
|
}
|
|
@@ -2128,21 +1919,22 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2128
1919
|
});
|
|
2129
1920
|
const walletId = deriveKeystoneWalletId(parsed.accounts);
|
|
2130
1921
|
const previousUsbSessionId = this._devices.get(walletId)?.usbSessionId;
|
|
2131
|
-
const record = this._upsertDeviceRecord(parsed, {
|
|
2132
|
-
viaUsb: true,
|
|
2133
|
-
usbSessionId: session.sessionId
|
|
2134
|
-
});
|
|
2135
1922
|
if (previousUsbSessionId && previousUsbSessionId !== session.sessionId) {
|
|
2136
1923
|
try {
|
|
2137
1924
|
await this._usbConnector.disconnect(previousUsbSessionId);
|
|
2138
1925
|
} catch {
|
|
2139
1926
|
}
|
|
2140
1927
|
}
|
|
1928
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1929
|
+
const record = this._upsertDeviceRecord(parsed, {
|
|
1930
|
+
viaUsb: true,
|
|
1931
|
+
usbSessionId: session.sessionId
|
|
1932
|
+
});
|
|
1933
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2141
1934
|
record.hadUsbSession = true;
|
|
2142
1935
|
record.model = session.deviceInfo.modelName ?? session.deviceInfo.model ?? record.model;
|
|
2143
1936
|
record.deviceVersion = session.deviceInfo.firmwareVersion ?? record.deviceVersion;
|
|
2144
|
-
|
|
2145
|
-
return (0, import_hwk_adapter_core4.success)(info);
|
|
1937
|
+
return (0, import_hwk_adapter_core4.success)(toDeviceInfo(record));
|
|
2146
1938
|
} catch (err) {
|
|
2147
1939
|
if (sessionId) {
|
|
2148
1940
|
try {
|
|
@@ -2154,13 +1946,11 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2154
1946
|
}
|
|
2155
1947
|
}
|
|
2156
1948
|
/**
|
|
2157
|
-
*
|
|
2158
|
-
*
|
|
2159
|
-
* used both after an adapter restart and after a QR-only period, so plugging
|
|
2160
|
-
* USB back in affects the next business call without changing an in-flight
|
|
2161
|
-
* QR operation.
|
|
1949
|
+
* Probes already-authorized USB devices without a permission picker, then attaches and verifies
|
|
1950
|
+
* the requested wallet if present.
|
|
2162
1951
|
*/
|
|
2163
|
-
async _tryUsbAttach(expectedWalletId, expectedMasterFingerprint, options) {
|
|
1952
|
+
async _tryUsbAttach(signal, expectedWalletId, expectedMasterFingerprint, options) {
|
|
1953
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2164
1954
|
if (!expectedWalletId || this._forcedTransport === "qr" || !this._usbConnector) {
|
|
2165
1955
|
return void 0;
|
|
2166
1956
|
}
|
|
@@ -2168,17 +1958,22 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2168
1958
|
let availableDevices = [];
|
|
2169
1959
|
let lastSearchError;
|
|
2170
1960
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
1961
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2171
1962
|
try {
|
|
2172
|
-
availableDevices = await
|
|
1963
|
+
availableDevices = await _KeystoneAdapter._abortable(
|
|
1964
|
+
signal,
|
|
1965
|
+
this._usbConnector.searchDevices({ purpose: "availability" })
|
|
1966
|
+
);
|
|
2173
1967
|
lastSearchError = void 0;
|
|
2174
1968
|
} catch (error) {
|
|
2175
1969
|
lastSearchError = error;
|
|
2176
1970
|
}
|
|
1971
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2177
1972
|
if (availableDevices.length) {
|
|
2178
1973
|
break;
|
|
2179
1974
|
}
|
|
2180
1975
|
if (attempt < maxAttempts) {
|
|
2181
|
-
await waitForKeystoneUsbReattachProbe();
|
|
1976
|
+
await _KeystoneAdapter._abortable(signal, waitForKeystoneUsbReattachProbe());
|
|
2182
1977
|
}
|
|
2183
1978
|
}
|
|
2184
1979
|
if (!availableDevices.length) {
|
|
@@ -2193,8 +1988,16 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2193
1988
|
for (const device of candidates) {
|
|
2194
1989
|
attached = await this._connectUsb(
|
|
2195
1990
|
{ expectedWalletId, expectedMasterFingerprint },
|
|
2196
|
-
device?.connectId
|
|
1991
|
+
device?.connectId,
|
|
1992
|
+
signal
|
|
2197
1993
|
);
|
|
1994
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1995
|
+
if (!attached.success && attached.payload.code === import_hwk_adapter_core4.HardwareErrorCode.DeviceBusyInternal) {
|
|
1996
|
+
throw (0, import_hwk_adapter_core4.createHwkError)({
|
|
1997
|
+
...attached.payload,
|
|
1998
|
+
message: attached.payload.error
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2198
2001
|
if (attached.success) {
|
|
2199
2002
|
record = this._devices.get(expectedWalletId);
|
|
2200
2003
|
if (record) break;
|
|
@@ -2203,14 +2006,8 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2203
2006
|
return record;
|
|
2204
2007
|
}
|
|
2205
2008
|
/**
|
|
2206
|
-
*
|
|
2207
|
-
*
|
|
2208
|
-
* device row for the target wallet. USB is the preferred channel: a known
|
|
2209
|
-
* wallet record with no live session gets ONE best-effort re-attach here,
|
|
2210
|
-
* and anything that goes wrong just leaves the call on QR. A
|
|
2211
|
-
* `switchTransport('qr')` pin forces QR even for a USB-attached wallet;
|
|
2212
|
-
* `switchTransport('usb')` on a wallet with no live USB session fails
|
|
2213
|
-
* closed rather than silently falling back to QR.
|
|
2009
|
+
* Decides QR vs USB for one UR round trip. A pinned 'usb' with no live session fails closed;
|
|
2010
|
+
* otherwise a failed USB reattach leaves the call on QR.
|
|
2214
2011
|
*/
|
|
2215
2012
|
async _resolveUr(record, requestUr, animated, operationId, signal, operationName) {
|
|
2216
2013
|
const releaseOperationRetention = (0, import_hwk_adapter_core4.isHardwareOperationId)(operationId) ? this._operations.retain(operationId) : void 0;
|
|
@@ -2233,17 +2030,15 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2233
2030
|
const operation = this._operations.resolve(operationId);
|
|
2234
2031
|
const route = this._operationRoutes.get(operation.connectId);
|
|
2235
2032
|
if (!route || route.operationId !== operationId) {
|
|
2236
|
-
this.
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
params: { operationId, reason: "disconnect" }
|
|
2241
|
-
});
|
|
2033
|
+
throw this._endDisconnectedOperation(
|
|
2034
|
+
operationId,
|
|
2035
|
+
"Keystone hardware operation is no longer connected"
|
|
2036
|
+
);
|
|
2242
2037
|
}
|
|
2243
2038
|
interactionRoute = route;
|
|
2244
2039
|
}
|
|
2245
2040
|
if (!interactionRoute && !this._forcedTransport && record && !record.usbSessionId && this._usbConnector) {
|
|
2246
|
-
await this._tryUsbAttach(record.walletId, record.masterFingerprint, {
|
|
2041
|
+
await this._tryUsbAttach(signal, record.walletId, record.masterFingerprint, {
|
|
2247
2042
|
waitForReenumeration: record.hadUsbSession
|
|
2248
2043
|
});
|
|
2249
2044
|
}
|
|
@@ -2251,15 +2046,10 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2251
2046
|
if (wantUsb) {
|
|
2252
2047
|
if (!record?.usbSessionId || !this._usbConnector) {
|
|
2253
2048
|
if (interactionRoute) {
|
|
2254
|
-
this.
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
params: {
|
|
2259
|
-
operationId: interactionRoute.operationId,
|
|
2260
|
-
reason: "disconnect"
|
|
2261
|
-
}
|
|
2262
|
-
});
|
|
2049
|
+
throw this._endDisconnectedOperation(
|
|
2050
|
+
interactionRoute.operationId,
|
|
2051
|
+
"Keystone operation USB connection was lost"
|
|
2052
|
+
);
|
|
2263
2053
|
}
|
|
2264
2054
|
throw (0, import_hwk_adapter_core4.createHwkError)({
|
|
2265
2055
|
code: import_hwk_adapter_core4.HardwareErrorCode.TransportNotAvailable,
|
|
@@ -2274,15 +2064,17 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2274
2064
|
);
|
|
2275
2065
|
if (result.success) return result.payload;
|
|
2276
2066
|
const usbError = (0, import_hwk_adapter_core4.rehydrateConnectorError)(result.error);
|
|
2277
|
-
const usbErrorOrigin = usbError
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2067
|
+
const { origin: usbErrorOrigin, code: usbErrorCode } = usbError;
|
|
2068
|
+
if (usbErrorCode === import_hwk_adapter_core4.HardwareErrorCode.PayloadTooLarge && usbErrorOrigin !== "device") {
|
|
2069
|
+
if (!interactionRoute && !this._forcedTransport) {
|
|
2070
|
+
const displayDevice2 = toDeviceInfo(record);
|
|
2071
|
+
return this._requestQrDisplayAndAwaitResponse(
|
|
2072
|
+
displayDevice2,
|
|
2073
|
+
{ ...requestUr, animated },
|
|
2074
|
+
signal
|
|
2075
|
+
);
|
|
2076
|
+
}
|
|
2077
|
+
throw usbError;
|
|
2286
2078
|
}
|
|
2287
2079
|
const deviceAnswered = usbErrorOrigin !== void 0 ? usbErrorOrigin === "device" : usbErrorCode === import_hwk_adapter_core4.HardwareErrorCode.UserRejected || usbErrorCode === import_hwk_adapter_core4.HardwareErrorCode.UserAborted || usbErrorCode === import_hwk_adapter_core4.HardwareErrorCode.DeviceLocked || usbErrorCode === import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch;
|
|
2288
2080
|
if (!deviceAnswered) {
|
|
@@ -2292,19 +2084,11 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2292
2084
|
}
|
|
2293
2085
|
record.usbSessionId = void 0;
|
|
2294
2086
|
if (interactionRoute) {
|
|
2295
|
-
this.
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
params: operationName ? (0, import_hwk_adapter_core4.operationMayHaveCompletedParams)(operationName, {
|
|
2301
|
-
operationId: interactionRoute.operationId,
|
|
2302
|
-
reason: "disconnect"
|
|
2303
|
-
}) : {
|
|
2304
|
-
operationId: interactionRoute.operationId,
|
|
2305
|
-
reason: "disconnect"
|
|
2306
|
-
}
|
|
2307
|
-
});
|
|
2087
|
+
throw this._endDisconnectedOperation(
|
|
2088
|
+
interactionRoute.operationId,
|
|
2089
|
+
operationName ? `Keystone ${operationName} may have completed before the USB connection was lost` : "Keystone operation USB connection was lost",
|
|
2090
|
+
operationName
|
|
2091
|
+
);
|
|
2308
2092
|
}
|
|
2309
2093
|
if (operationName) {
|
|
2310
2094
|
throw (0, import_hwk_adapter_core4.createHwkError)({
|
|
@@ -2338,51 +2122,49 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2338
2122
|
throw error;
|
|
2339
2123
|
}
|
|
2340
2124
|
}
|
|
2125
|
+
/** Ends the operation as disconnected and returns the error to throw. */
|
|
2126
|
+
_endDisconnectedOperation(operationId, message, operationName) {
|
|
2127
|
+
this._operations.end(operationId, "disconnect");
|
|
2128
|
+
const params = { operationId, reason: "disconnect" };
|
|
2129
|
+
return (0, import_hwk_adapter_core4.createHwkError)({
|
|
2130
|
+
code: import_hwk_adapter_core4.HardwareErrorCode.OperationEnded,
|
|
2131
|
+
message,
|
|
2132
|
+
recovery: operationName ? { scope: "unknown" } : void 0,
|
|
2133
|
+
params: operationName ? (0, import_hwk_adapter_core4.operationMayHaveCompletedParams)(operationName, params) : params
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2341
2136
|
/**
|
|
2342
|
-
*
|
|
2343
|
-
*
|
|
2344
|
-
* retained on the record. A failed USB export ends this operation without
|
|
2345
|
-
* replaying the request or switching transports.
|
|
2137
|
+
* Fetches one account's key material for this operation only; `book` reuses earlier fetches. A
|
|
2138
|
+
* failed USB export ends the operation without replaying or switching transports.
|
|
2346
2139
|
*/
|
|
2347
|
-
async _fetchAccount(connectId, deviceId, hwkChain, syncPath, signal,
|
|
2140
|
+
async _fetchAccount(connectId, deviceId, hwkChain, syncPath, signal, book) {
|
|
2348
2141
|
const target = this._resolveTarget(connectId, deviceId);
|
|
2142
|
+
const operationConnectionType = (0, import_hwk_adapter_core4.isHardwareOperationId)(connectId) ? this._operations.resolve(connectId).connectionType : void 0;
|
|
2349
2143
|
const key = accountKey(hwkChain, syncPath);
|
|
2350
2144
|
let existingRecord = target.record;
|
|
2351
2145
|
if (!existingRecord) {
|
|
2352
|
-
|
|
2146
|
+
existingRecord = await this._tryUsbAttach(signal, target.expectedWalletId);
|
|
2353
2147
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
2354
|
-
existingRecord = attached;
|
|
2355
2148
|
}
|
|
2356
|
-
const book = options?.book;
|
|
2357
2149
|
const booked = book?.get(key);
|
|
2358
2150
|
if (existingRecord && booked) {
|
|
2359
2151
|
return { record: existingRecord, account: booked };
|
|
2360
2152
|
}
|
|
2361
|
-
if (existingRecord && !existingRecord.usbSessionId && this._forcedTransport === "usb") {
|
|
2362
|
-
await this._tryUsbAttach(existingRecord.walletId, existingRecord.masterFingerprint, {
|
|
2153
|
+
if (!operationConnectionType && existingRecord && !existingRecord.usbSessionId && this._forcedTransport === "usb") {
|
|
2154
|
+
await this._tryUsbAttach(signal, existingRecord.walletId, existingRecord.masterFingerprint, {
|
|
2363
2155
|
waitForReenumeration: existingRecord.hadUsbSession
|
|
2364
2156
|
});
|
|
2365
2157
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
2366
2158
|
existingRecord = this._devices.get(existingRecord.walletId) ?? existingRecord;
|
|
2367
2159
|
}
|
|
2368
|
-
const useSinglePathUsbExport = this._forcedTransport !== "qr" && Boolean(existingRecord?.usbSessionId);
|
|
2369
|
-
const
|
|
2370
|
-
|
|
2371
|
-
];
|
|
2372
|
-
const schemaPaths = useSinglePathUsbExport || requestedSchemaPaths.some((schema) => normalizePath(schema.path) === KEYSTONE_WALLET_ID_PATH) ? requestedSchemaPaths : [{ hwkChain: "evm", path: KEYSTONE_WALLET_ID_PATH }, ...requestedSchemaPaths];
|
|
2373
|
-
const requestUr = this.urEngine.buildKeyDerivationRequest({
|
|
2374
|
-
schemas: schemaPaths.map((s) => ({
|
|
2375
|
-
path: s.path,
|
|
2376
|
-
curve: s.hwkChain === "sol" ? "ed25519" : "secp256k1"
|
|
2377
|
-
})),
|
|
2378
|
-
origin: this._origin
|
|
2379
|
-
});
|
|
2380
|
-
const resolvedViaUsb = useSinglePathUsbExport;
|
|
2160
|
+
const useSinglePathUsbExport = operationConnectionType ? operationConnectionType === "usb" : this._forcedTransport !== "qr" && Boolean(existingRecord?.usbSessionId);
|
|
2161
|
+
const schemas = useSinglePathUsbExport || normalizePath(syncPath) === KEYSTONE_WALLET_ID_PATH ? [keySchema(hwkChain, syncPath)] : [keySchema("evm", KEYSTONE_WALLET_ID_PATH), keySchema(hwkChain, syncPath)];
|
|
2162
|
+
const requestUr = this.urEngine.buildKeyDerivationRequest({ schemas, origin: this._origin });
|
|
2381
2163
|
const responseUr = await this._resolveUr(existingRecord, requestUr, false, connectId, signal);
|
|
2382
2164
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
2383
2165
|
const parsed = this.urEngine.parseAccountResponse(responseUr);
|
|
2384
2166
|
let record;
|
|
2385
|
-
if (
|
|
2167
|
+
if (useSinglePathUsbExport && existingRecord) {
|
|
2386
2168
|
if (parsed.masterFingerprint !== existingRecord.masterFingerprint) {
|
|
2387
2169
|
throw (0, import_hwk_adapter_core4.createHwkError)({
|
|
2388
2170
|
code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
|
|
@@ -2393,7 +2175,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2393
2175
|
} else {
|
|
2394
2176
|
this._assertParsedIdentity(parsed, target);
|
|
2395
2177
|
record = this._upsertDeviceRecord(parsed, {
|
|
2396
|
-
viaUsb: Boolean(existingRecord?.usbSessionId)
|
|
2178
|
+
viaUsb: operationConnectionType !== "qr" && Boolean(existingRecord?.usbSessionId)
|
|
2397
2179
|
});
|
|
2398
2180
|
}
|
|
2399
2181
|
const account = parsed.accounts.find((a) => normalizePath(a.path) === syncPath);
|
|
@@ -2419,52 +2201,27 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2419
2201
|
return { record, account: entry };
|
|
2420
2202
|
}
|
|
2421
2203
|
/**
|
|
2422
|
-
*
|
|
2423
|
-
*
|
|
2424
|
-
* specific cached path. Syncs the account-level path for `chain` as a
|
|
2425
|
-
* throwaway probe when the wallet record isn't already known.
|
|
2426
|
-
*
|
|
2427
|
-
* `CHAIN_FINGERPRINT_PATHS[chain]` is a 5-segment LEAF path for `evm`
|
|
2428
|
-
* (`m/44'/60'/0'/0/0`) — sending that verbatim as a KeyDerivation request
|
|
2429
|
-
* asks Keystone for a non-standard path. Keystone's own docs
|
|
2430
|
-
* (dev.keyst.one's multichain KeyDerivation example) show the ETH
|
|
2431
|
-
* account-level path as `m/44'/60'/0'` (3 segments), same as what
|
|
2432
|
-
* `_fetchAccount` already requests — so
|
|
2433
|
-
* truncate through `splitAccountPath` here too instead of using the raw
|
|
2434
|
-
* fingerprint leaf path. `btc`/`sol` are already 3-segment account paths
|
|
2435
|
-
* and pass through unchanged.
|
|
2204
|
+
* Resolves which wallet is attached, without a per-path account. `CHAIN_FINGERPRINT_PATHS.evm` is
|
|
2205
|
+
* a leaf path, so it is cut to the account path Keystone expects.
|
|
2436
2206
|
*/
|
|
2437
2207
|
async _ensureWalletKnown(connectId, deviceId, chain, signal) {
|
|
2438
2208
|
const target = this._resolveTarget(connectId, deviceId);
|
|
2439
2209
|
if (target.record) return { record: target.record };
|
|
2440
|
-
const attached = await this._tryUsbAttach(target.expectedWalletId);
|
|
2210
|
+
const attached = await this._tryUsbAttach(signal, target.expectedWalletId);
|
|
2441
2211
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
2442
2212
|
if (attached) return { record: attached };
|
|
2443
2213
|
const { accountPath } = splitAccountPath(import_hwk_adapter_core4.CHAIN_FINGERPRINT_PATHS[chain]);
|
|
2444
|
-
const schemas = accountPath === KEYSTONE_WALLET_ID_PATH ? [
|
|
2445
|
-
|
|
2446
|
-
{
|
|
2447
|
-
path: accountPath,
|
|
2448
|
-
curve: chain === "sol" ? "ed25519" : "secp256k1"
|
|
2449
|
-
}
|
|
2450
|
-
];
|
|
2451
|
-
const requestUr = this.urEngine.buildKeyDerivationRequest({
|
|
2452
|
-
schemas,
|
|
2453
|
-
origin: this._origin
|
|
2454
|
-
});
|
|
2214
|
+
const schemas = accountPath === KEYSTONE_WALLET_ID_PATH ? [keySchema("evm", KEYSTONE_WALLET_ID_PATH)] : [keySchema("evm", KEYSTONE_WALLET_ID_PATH), keySchema(chain, accountPath)];
|
|
2215
|
+
const requestUr = this.urEngine.buildKeyDerivationRequest({ schemas, origin: this._origin });
|
|
2455
2216
|
const responseUr = await this._resolveUr(void 0, requestUr, false, connectId, signal);
|
|
2456
2217
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
2457
2218
|
const parsed = this.urEngine.parseAccountResponse(responseUr);
|
|
2458
2219
|
this._assertParsedIdentity(parsed, target);
|
|
2459
|
-
|
|
2460
|
-
return { record };
|
|
2220
|
+
return { record: this._upsertDeviceRecord(parsed) };
|
|
2461
2221
|
}
|
|
2462
2222
|
/**
|
|
2463
|
-
* The operation the running
|
|
2464
|
-
*
|
|
2465
|
-
* (`keystoneQueueKey`); a call without one queues under the wallet id or the
|
|
2466
|
-
* wallet connectId, and the live operation routed to that connection is still
|
|
2467
|
-
* the owner. Only a cold start comes back undefined.
|
|
2223
|
+
* The operation owning the running job, so a mid-call UI request can name it. Unpinned jobs map
|
|
2224
|
+
* through the operation route; a cold start returns undefined.
|
|
2468
2225
|
*/
|
|
2469
2226
|
_activeOperationId() {
|
|
2470
2227
|
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
@@ -2482,9 +2239,8 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2482
2239
|
return walletId ? this._operationRoutes.get(walletConnectId(walletId)) : void 0;
|
|
2483
2240
|
}
|
|
2484
2241
|
/**
|
|
2485
|
-
* A QR prompt has no transport to interrupt
|
|
2486
|
-
*
|
|
2487
|
-
* cancel reach the wait at all.
|
|
2242
|
+
* A QR prompt has no transport to interrupt; releasing the registry slot on abort is what lets
|
|
2243
|
+
* a cancel reach the wait before the QR timeout.
|
|
2488
2244
|
*/
|
|
2489
2245
|
async _awaitQrResponse(requestType, pending, signal) {
|
|
2490
2246
|
if (!signal) return pending;
|
|
@@ -2553,8 +2309,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2553
2309
|
const releaseOperation = this._retainUsbOperation(`call:${sessionId}`);
|
|
2554
2310
|
let rawCall;
|
|
2555
2311
|
try {
|
|
2556
|
-
|
|
2557
|
-
rawCall = connector.call(sessionId, method, params).finally(releaseOperation);
|
|
2312
|
+
rawCall = this._usbConnector.call(sessionId, method, params).finally(releaseOperation);
|
|
2558
2313
|
} catch (error) {
|
|
2559
2314
|
releaseOperation();
|
|
2560
2315
|
throw error;
|
|
@@ -2604,13 +2359,10 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2604
2359
|
};
|
|
2605
2360
|
}
|
|
2606
2361
|
static _abortable(signal, promise) {
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
}
|
|
2362
|
+
const abortReason = () => signal.reason instanceof Error ? signal.reason : new Error("Aborted");
|
|
2363
|
+
if (signal.aborted) return Promise.reject(abortReason());
|
|
2610
2364
|
return new Promise((resolve, reject) => {
|
|
2611
|
-
const onAbort = () =>
|
|
2612
|
-
reject(signal.reason instanceof Error ? signal.reason : new Error("Aborted"));
|
|
2613
|
-
};
|
|
2365
|
+
const onAbort = () => reject(abortReason());
|
|
2614
2366
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
2615
2367
|
promise.then(
|
|
2616
2368
|
(value) => {
|
|
@@ -2625,7 +2377,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2625
2377
|
});
|
|
2626
2378
|
}
|
|
2627
2379
|
static _throwIfAborted(signal) {
|
|
2628
|
-
if (signal
|
|
2380
|
+
if (signal?.aborted) {
|
|
2629
2381
|
throw signal.reason instanceof Error ? signal.reason : new Error("Operation aborted");
|
|
2630
2382
|
}
|
|
2631
2383
|
}
|