@onekeyfe/hwk-keystone-adapter 1.2.2-alpha.100
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 +468 -0
- package/dist/index.d.ts +468 -0
- package/dist/index.js +1578 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1565 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +67 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1578 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
KeystoneAdapter: () => KeystoneAdapter,
|
|
34
|
+
KeystoneUrEngine: () => KeystoneUrEngine,
|
|
35
|
+
QR_CONNECT_ID_PREFIX: () => QR_CONNECT_ID_PREFIX,
|
|
36
|
+
accountKey: () => accountKey,
|
|
37
|
+
btcScriptTypeFromPath: () => btcScriptTypeFromPath,
|
|
38
|
+
createDeviceRecord: () => createDeviceRecord,
|
|
39
|
+
normalizePath: () => normalizePath,
|
|
40
|
+
placeholderDeviceInfo: () => placeholderDeviceInfo,
|
|
41
|
+
qrConnectId: () => qrConnectId,
|
|
42
|
+
splitAccountPath: () => splitAccountPath,
|
|
43
|
+
toDeviceInfo: () => toDeviceInfo
|
|
44
|
+
});
|
|
45
|
+
module.exports = __toCommonJS(index_exports);
|
|
46
|
+
|
|
47
|
+
// src/urEngine/KeystoneUrEngine.ts
|
|
48
|
+
var import_bc_ur_registry_eth = require("@keystonehq/bc-ur-registry-eth");
|
|
49
|
+
var import_keystone_sdk = require("@keystonehq/keystone-sdk");
|
|
50
|
+
var import_bs58check = __toESM(require("bs58check"));
|
|
51
|
+
var bitcoin = __toESM(require("bitcoinjs-lib"));
|
|
52
|
+
var import_hdkey = __toESM(require("hdkey"));
|
|
53
|
+
var import_uuid = require("uuid");
|
|
54
|
+
|
|
55
|
+
// src/urEngine/TronSignRequest.ts
|
|
56
|
+
var import_bc_ur_registry = require("@keystonehq/bc-ur-registry");
|
|
57
|
+
var { decodeToDataItem } = import_bc_ur_registry.extend;
|
|
58
|
+
var TRON_SIGN_REQUEST_TYPE = new import_bc_ur_registry.RegistryType("tron-sign-request", 5201);
|
|
59
|
+
var TronSignRequest = class _TronSignRequest extends import_bc_ur_registry.RegistryItem {
|
|
60
|
+
constructor(args) {
|
|
61
|
+
super();
|
|
62
|
+
this.getRegistryType = () => TRON_SIGN_REQUEST_TYPE;
|
|
63
|
+
this.getRequestId = () => this.requestId;
|
|
64
|
+
this.getSignData = () => this.signData;
|
|
65
|
+
this.getSignType = () => this.signType;
|
|
66
|
+
this.getDerivationPath = () => this.derivationPath.getPath();
|
|
67
|
+
this.toDataItem = () => {
|
|
68
|
+
const map = {};
|
|
69
|
+
if (this.requestId) {
|
|
70
|
+
map[1 /* requestId */] = new import_bc_ur_registry.DataItem(this.requestId, import_bc_ur_registry.RegistryTypes.UUID.getTag());
|
|
71
|
+
}
|
|
72
|
+
if (this.address) map[4 /* address */] = this.address;
|
|
73
|
+
if (this.origin) map[5 /* origin */] = this.origin;
|
|
74
|
+
map[2 /* signData */] = this.signData;
|
|
75
|
+
map[6 /* signType */] = this.signType;
|
|
76
|
+
const keyPath = this.derivationPath.toDataItem();
|
|
77
|
+
keyPath.setTag(this.derivationPath.getRegistryType().getTag());
|
|
78
|
+
map[3 /* derivationPath */] = keyPath;
|
|
79
|
+
return new import_bc_ur_registry.DataItem(map);
|
|
80
|
+
};
|
|
81
|
+
this.requestId = args.requestId;
|
|
82
|
+
this.signData = args.signData;
|
|
83
|
+
this.derivationPath = args.derivationPath;
|
|
84
|
+
this.address = args.address;
|
|
85
|
+
this.origin = args.origin;
|
|
86
|
+
this.signType = args.signType;
|
|
87
|
+
}
|
|
88
|
+
static fromDataItem(dataItem) {
|
|
89
|
+
const map = dataItem.getData();
|
|
90
|
+
const signData = map[2 /* signData */];
|
|
91
|
+
const derivationPath = import_bc_ur_registry.CryptoKeypath.fromDataItem(map[3 /* derivationPath */]);
|
|
92
|
+
const address = map[4 /* address */];
|
|
93
|
+
const requestIdItem = map[1 /* requestId */];
|
|
94
|
+
const requestId = requestIdItem?.getData();
|
|
95
|
+
const origin = map[5 /* origin */];
|
|
96
|
+
const signType = map[6 /* signType */];
|
|
97
|
+
return new _TronSignRequest({ requestId, signData, derivationPath, address, origin, signType });
|
|
98
|
+
}
|
|
99
|
+
static fromCBOR(cborPayload) {
|
|
100
|
+
return _TronSignRequest.fromDataItem(decodeToDataItem(cborPayload));
|
|
101
|
+
}
|
|
102
|
+
static parsePath(path, xfp) {
|
|
103
|
+
const segments = path.replace(/^[mM]\//, "").split("/");
|
|
104
|
+
const components = segments.map((segment) => {
|
|
105
|
+
const hardened = segment.endsWith("'");
|
|
106
|
+
return new import_bc_ur_registry.PathComponent({
|
|
107
|
+
index: parseInt(hardened ? segment.slice(0, -1) : segment, 10),
|
|
108
|
+
hardened
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
return new import_bc_ur_registry.CryptoKeypath(components, Buffer.from(xfp, "hex"));
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// src/urEngine/TronSignature.ts
|
|
116
|
+
var import_bc_ur_registry2 = require("@keystonehq/bc-ur-registry");
|
|
117
|
+
var { decodeToDataItem: decodeToDataItem2 } = import_bc_ur_registry2.extend;
|
|
118
|
+
var TRON_SIGNATURE_TYPE = new import_bc_ur_registry2.RegistryType("tron-signature", 5202);
|
|
119
|
+
var TronSignature = class _TronSignature extends import_bc_ur_registry2.RegistryItem {
|
|
120
|
+
constructor(signature, requestId) {
|
|
121
|
+
super();
|
|
122
|
+
this.getRegistryType = () => TRON_SIGNATURE_TYPE;
|
|
123
|
+
this.getRequestId = () => this.requestId;
|
|
124
|
+
this.getSignature = () => this.signatureBytes;
|
|
125
|
+
this.toDataItem = () => {
|
|
126
|
+
const map = {};
|
|
127
|
+
if (this.requestId) {
|
|
128
|
+
map[1 /* requestId */] = new import_bc_ur_registry2.DataItem(this.requestId, import_bc_ur_registry2.RegistryTypes.UUID.getTag());
|
|
129
|
+
}
|
|
130
|
+
map[2 /* signature */] = this.signatureBytes;
|
|
131
|
+
return new import_bc_ur_registry2.DataItem(map);
|
|
132
|
+
};
|
|
133
|
+
this.signatureBytes = signature;
|
|
134
|
+
this.requestId = requestId;
|
|
135
|
+
}
|
|
136
|
+
static fromDataItem(dataItem) {
|
|
137
|
+
const map = dataItem.getData();
|
|
138
|
+
const signature = map[2 /* signature */];
|
|
139
|
+
const requestIdItem = map[1 /* requestId */];
|
|
140
|
+
const requestId = requestIdItem?.getData();
|
|
141
|
+
return new _TronSignature(signature, requestId);
|
|
142
|
+
}
|
|
143
|
+
static fromCBOR(cborPayload) {
|
|
144
|
+
return _TronSignature.fromDataItem(decodeToDataItem2(cborPayload));
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// src/urEngine/KeystoneUrEngine.ts
|
|
149
|
+
var TRON_ADDRESS_PREFIX = 65;
|
|
150
|
+
function stripHexPrefix(hex) {
|
|
151
|
+
return hex.replace(/^0x/i, "");
|
|
152
|
+
}
|
|
153
|
+
var ETH_DATA_TYPE = {
|
|
154
|
+
transaction: 1,
|
|
155
|
+
typedData: 2,
|
|
156
|
+
personalMessage: 3,
|
|
157
|
+
typedTransaction: 4
|
|
158
|
+
};
|
|
159
|
+
var DERIVATION_CURVE = {
|
|
160
|
+
secp256k1: import_keystone_sdk.Curve.secp256k1,
|
|
161
|
+
ed25519: import_keystone_sdk.Curve.ed25519
|
|
162
|
+
};
|
|
163
|
+
function toSdkUr(ur) {
|
|
164
|
+
return new import_keystone_sdk.UR(Buffer.from(ur.urData, "hex"), ur.urType);
|
|
165
|
+
}
|
|
166
|
+
function fromSdkUr(ur) {
|
|
167
|
+
return { urType: ur.type, urData: ur.cbor.toString("hex") };
|
|
168
|
+
}
|
|
169
|
+
function splitSignature65(hex) {
|
|
170
|
+
return { r: hex.slice(0, 64), s: hex.slice(64, 128), v: hex.slice(128) };
|
|
171
|
+
}
|
|
172
|
+
var KeystoneUrEngine = class {
|
|
173
|
+
constructor(origin = "OneKey") {
|
|
174
|
+
this.sdk = new import_keystone_sdk.KeystoneSDK({ origin });
|
|
175
|
+
}
|
|
176
|
+
// --- Account sync (works for both a device-initiated QR export and a
|
|
177
|
+
// wallet-initiated KeyDerivation request/response over either channel) ---
|
|
178
|
+
parseMultiAccounts(ur) {
|
|
179
|
+
const parsed = this.sdk.parseMultiAccounts(toSdkUr(ur));
|
|
180
|
+
return {
|
|
181
|
+
masterFingerprint: parsed.masterFingerprint.toLowerCase(),
|
|
182
|
+
device: parsed.device,
|
|
183
|
+
deviceId: parsed.deviceId,
|
|
184
|
+
deviceVersion: parsed.deviceVersion,
|
|
185
|
+
accounts: parsed.keys.map(
|
|
186
|
+
(key) => ({
|
|
187
|
+
chain: key.chain,
|
|
188
|
+
path: key.path,
|
|
189
|
+
publicKey: key.publicKey,
|
|
190
|
+
extendedPublicKey: key.extendedPublicKey,
|
|
191
|
+
xfp: key.xfp,
|
|
192
|
+
name: key.name
|
|
193
|
+
})
|
|
194
|
+
)
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
parseHDKey(ur) {
|
|
198
|
+
const key = this.sdk.parseHDKey(toSdkUr(ur));
|
|
199
|
+
return {
|
|
200
|
+
chain: key.chain,
|
|
201
|
+
path: key.path,
|
|
202
|
+
publicKey: key.publicKey,
|
|
203
|
+
extendedPublicKey: key.extendedPublicKey,
|
|
204
|
+
xfp: key.xfp,
|
|
205
|
+
name: key.name
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Build a `qr-hardware-call` (KeyDerivation) request: the host asks for
|
|
210
|
+
* specific paths instead of waiting for whatever the device happens to be
|
|
211
|
+
* showing. The device replies with a `crypto-multi-accounts` UR — parse it
|
|
212
|
+
* with `parseMultiAccounts`. Used for the implicit "sync this wallet's xfp
|
|
213
|
+
* before the first sign" round trip as well as an explicit account import.
|
|
214
|
+
*
|
|
215
|
+
* `version: V1` is required — verified against real Keystone hardware and
|
|
216
|
+
* `keystone3-firmware`'s `CheckHardwareCallRequestIsLegal` source: an
|
|
217
|
+
* unversioned/V0 request is validated as a legacy Cardano-only request
|
|
218
|
+
* (`m/1852'/1815'/...`) and firmware rejects every other chain's path with
|
|
219
|
+
* `PRS_PARSING_ERROR` (device-shown message: "路径不受支持" / "path not
|
|
220
|
+
* supported"), regardless of the path's shape. V1 is what actually enables
|
|
221
|
+
* the general per-chain path whitelist (includes `m/44'/60'` for ETH, the
|
|
222
|
+
* standard BTC purposes, etc.). The SDK itself defaults to V0 unless a
|
|
223
|
+
* truthy `version` is passed — omitting this silently produces a request
|
|
224
|
+
* every non-Cardano device rejects.
|
|
225
|
+
*/
|
|
226
|
+
buildKeyDerivationRequest(input) {
|
|
227
|
+
const ur = this.sdk.generateKeyDerivationCall({
|
|
228
|
+
schemas: input.schemas.map((schema) => ({
|
|
229
|
+
path: schema.path,
|
|
230
|
+
curve: DERIVATION_CURVE[schema.curve ?? "secp256k1"]
|
|
231
|
+
})),
|
|
232
|
+
origin: input.origin,
|
|
233
|
+
version: import_keystone_sdk.QRHardwareCallVersion.V1
|
|
234
|
+
});
|
|
235
|
+
return fromSdkUr(ur);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Parse the response to a KeyDerivation request (or a device-initiated
|
|
239
|
+
* account export): `crypto-multi-accounts` for a multi-schema request,
|
|
240
|
+
* `crypto-hdkey` for a single-key response some firmware paths use instead.
|
|
241
|
+
* Both are normalized to the same `KeystoneParsedMultiAccounts` shape — a
|
|
242
|
+
* single `crypto-hdkey` becomes a one-entry account list, with its own
|
|
243
|
+
* `origin.sourceFingerprint` promoted to `masterFingerprint` (correct for a
|
|
244
|
+
* key derived directly from the seed, which every request this engine
|
|
245
|
+
* builds asks for).
|
|
246
|
+
*/
|
|
247
|
+
parseAccountResponse(ur) {
|
|
248
|
+
if (ur.urType === "crypto-hdkey") {
|
|
249
|
+
const account = this.parseHDKey(ur);
|
|
250
|
+
if (!account.xfp) {
|
|
251
|
+
throw new Error("Keystone crypto-hdkey response is missing its source fingerprint");
|
|
252
|
+
}
|
|
253
|
+
return { masterFingerprint: account.xfp.toLowerCase(), accounts: [account] };
|
|
254
|
+
}
|
|
255
|
+
return this.parseMultiAccounts(ur);
|
|
256
|
+
}
|
|
257
|
+
// --- EVM ---
|
|
258
|
+
buildEthSignRequest(input) {
|
|
259
|
+
const ur = this.sdk.eth.generateSignRequest({
|
|
260
|
+
requestId: input.requestId,
|
|
261
|
+
signData: input.unsignedTxHex,
|
|
262
|
+
dataType: ETH_DATA_TYPE[input.dataType],
|
|
263
|
+
path: input.path,
|
|
264
|
+
xfp: input.xfp,
|
|
265
|
+
chainId: input.chainId,
|
|
266
|
+
address: input.address,
|
|
267
|
+
origin: input.origin
|
|
268
|
+
});
|
|
269
|
+
return fromSdkUr(ur);
|
|
270
|
+
}
|
|
271
|
+
parseEthSignature(ur) {
|
|
272
|
+
const signature = this.sdk.eth.parseSignature(toSdkUr(ur));
|
|
273
|
+
return { requestId: signature.requestId, ...splitSignature65(signature.signature) };
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Derive one EVM address offline from an already-synced account xpub —
|
|
277
|
+
* verified against the same `@keystonehq/bc-ur-registry-eth` helper the
|
|
278
|
+
* Keystone-based OneKey air-gap demo uses in production
|
|
279
|
+
* (`generateAddressFromXpub`), so no unverified assumption about what a
|
|
280
|
+
* leaf-path KeyDerivation request would return. `relativeDerivePath` is
|
|
281
|
+
* relative to the xpub's own depth, e.g. `'0/0'` for an account xpub.
|
|
282
|
+
*/
|
|
283
|
+
deriveEvmAddressFromXpub(xpub, relativeDerivePath) {
|
|
284
|
+
return (0, import_bc_ur_registry_eth.generateAddressFromXpub)(xpub, `m/${relativeDerivePath.replace(/^m\//i, "")}`);
|
|
285
|
+
}
|
|
286
|
+
// --- BTC (PSBT transaction signing + plain message signing) ---
|
|
287
|
+
/**
|
|
288
|
+
* Derive one BTC address offline from an already-synced account xpub, the
|
|
289
|
+
* same way `deriveEvmAddressFromXpub` does. Keystone's `CryptoHDKey`
|
|
290
|
+
* always emits standard mainnet-xpub version bytes (`0488B21E`) regardless
|
|
291
|
+
* of the account's purpose/script type (verified against
|
|
292
|
+
* `@keystonehq/bc-ur-registry`'s `CryptoHDKey.getBip32Key`, which hardcodes
|
|
293
|
+
* that version rather than switching to a SLIP-132 ypub/zpub prefix per
|
|
294
|
+
* script type) — so `hdkey.fromExtendedKey` parses it correctly for every
|
|
295
|
+
* `scriptType` without needing custom version bytes configured.
|
|
296
|
+
*
|
|
297
|
+
* `p2tr` is deliberately not handled: taproot output-key tweaking (BIP-341)
|
|
298
|
+
* needs an elliptic-curve library wired via bitcoinjs-lib's `initEccLib`,
|
|
299
|
+
* which this package doesn't set up yet — every other payment function
|
|
300
|
+
* here needs no such library.
|
|
301
|
+
*/
|
|
302
|
+
deriveBtcAddressFromXpub(xpub, relativeDerivePath, scriptType) {
|
|
303
|
+
const node = import_hdkey.default.fromExtendedKey(xpub).derive(`m/${relativeDerivePath.replace(/^m\//i, "")}`);
|
|
304
|
+
if (!node.publicKey) throw new Error("HDKey derivation did not produce a public key");
|
|
305
|
+
const pubkey = Buffer.from(node.publicKey);
|
|
306
|
+
const network = bitcoin.networks.bitcoin;
|
|
307
|
+
switch (scriptType) {
|
|
308
|
+
case "p2pkh": {
|
|
309
|
+
const { address } = bitcoin.payments.p2pkh({ pubkey, network });
|
|
310
|
+
if (!address) throw new Error("Failed to derive a P2PKH address from this xpub");
|
|
311
|
+
return address;
|
|
312
|
+
}
|
|
313
|
+
case "p2sh-p2wpkh": {
|
|
314
|
+
const { address } = bitcoin.payments.p2sh({
|
|
315
|
+
redeem: bitcoin.payments.p2wpkh({ pubkey, network }),
|
|
316
|
+
network
|
|
317
|
+
});
|
|
318
|
+
if (!address) throw new Error("Failed to derive a P2SH-P2WPKH address from this xpub");
|
|
319
|
+
return address;
|
|
320
|
+
}
|
|
321
|
+
case "p2wpkh": {
|
|
322
|
+
const { address } = bitcoin.payments.p2wpkh({ pubkey, network });
|
|
323
|
+
if (!address) throw new Error("Failed to derive a P2WPKH address from this xpub");
|
|
324
|
+
return address;
|
|
325
|
+
}
|
|
326
|
+
case "p2tr":
|
|
327
|
+
throw new Error(
|
|
328
|
+
"BTC P2TR (taproot) address derivation is not supported yet \u2014 needs an elliptic-curve library for BIP-341 tweaking"
|
|
329
|
+
);
|
|
330
|
+
default: {
|
|
331
|
+
const exhaustive = scriptType;
|
|
332
|
+
throw new Error(`Unsupported BTC script type: ${String(exhaustive)}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
buildBtcPsbtRequest(unsignedPsbtHex) {
|
|
337
|
+
const ur = this.sdk.btc.generatePSBT(Buffer.from(unsignedPsbtHex, "hex"));
|
|
338
|
+
return fromSdkUr(ur);
|
|
339
|
+
}
|
|
340
|
+
/** Returns the hex-encoded (possibly still-unsigned-in-part) PSBT the device replied with. */
|
|
341
|
+
parseBtcPsbt(ur) {
|
|
342
|
+
return this.sdk.btc.parsePSBT(toSdkUr(ur));
|
|
343
|
+
}
|
|
344
|
+
buildBtcMessageSignRequest(params) {
|
|
345
|
+
const ur = this.sdk.btc.generateSignRequest({
|
|
346
|
+
requestId: params.requestId,
|
|
347
|
+
signData: params.messageHex,
|
|
348
|
+
dataType: 1,
|
|
349
|
+
// BtcSignRequest.DataType.message — PSBT signing never goes through this path.
|
|
350
|
+
accounts: params.accounts,
|
|
351
|
+
origin: params.origin
|
|
352
|
+
});
|
|
353
|
+
return fromSdkUr(ur);
|
|
354
|
+
}
|
|
355
|
+
parseBtcSignature(ur) {
|
|
356
|
+
const signature = this.sdk.btc.parseSignature(toSdkUr(ur));
|
|
357
|
+
return {
|
|
358
|
+
requestId: signature.requestId,
|
|
359
|
+
publicKey: signature.publicKey,
|
|
360
|
+
signature: signature.signature
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
// --- SOL ---
|
|
364
|
+
buildSolSignRequest(input) {
|
|
365
|
+
const ur = this.sdk.sol.generateSignRequest({
|
|
366
|
+
requestId: input.requestId,
|
|
367
|
+
signData: input.unsignedPayloadHex,
|
|
368
|
+
dataType: input.dataType === "transaction" ? 1 : 2,
|
|
369
|
+
path: input.path,
|
|
370
|
+
xfp: input.xfp,
|
|
371
|
+
address: input.address,
|
|
372
|
+
origin: input.origin
|
|
373
|
+
});
|
|
374
|
+
return fromSdkUr(ur);
|
|
375
|
+
}
|
|
376
|
+
parseSolSignature(ur) {
|
|
377
|
+
const signature = this.sdk.sol.parseSignature(toSdkUr(ur));
|
|
378
|
+
return { requestId: signature.requestId, signature: signature.signature };
|
|
379
|
+
}
|
|
380
|
+
// --- TRON ---
|
|
381
|
+
/**
|
|
382
|
+
* `@keystonehq/keystone-sdk`'s own bundled `sdk.tron` module is
|
|
383
|
+
* deliberately NOT used here — see `TronSignRequest.ts`'s doc comment for
|
|
384
|
+
* why: it's a different (gzip/protobuf) protocol with response semantics
|
|
385
|
+
* this package has no way to verify, whereas `TronSignRequest`/
|
|
386
|
+
* `TronSignature` are a direct port of OneKey's own already-proven
|
|
387
|
+
* production QR-wallet TRON implementation (a plain CBOR-native
|
|
388
|
+
* sign-request/signature pair, same shape as eth/sol). The device decodes
|
|
389
|
+
* `rawTxHex` itself — no client-side contract-type pre-parsing or
|
|
390
|
+
* `tokenInfo` needed, unlike the public SDK's module.
|
|
391
|
+
*/
|
|
392
|
+
buildTronSignRequest(input) {
|
|
393
|
+
const request = new TronSignRequest({
|
|
394
|
+
requestId: Buffer.from((0, import_uuid.parse)(input.requestId)),
|
|
395
|
+
signData: Buffer.from(stripHexPrefix(input.rawTxHex), "hex"),
|
|
396
|
+
signType: 0 /* Transaction */,
|
|
397
|
+
derivationPath: TronSignRequest.parsePath(input.path, input.xfp),
|
|
398
|
+
origin: input.origin
|
|
399
|
+
});
|
|
400
|
+
return fromSdkUr(request.toUR());
|
|
401
|
+
}
|
|
402
|
+
parseTronSignature(ur) {
|
|
403
|
+
if (ur.urType !== "tron-signature") {
|
|
404
|
+
throw new Error(`Expected a tron-signature UR, got ${ur.urType}`);
|
|
405
|
+
}
|
|
406
|
+
const signature = TronSignature.fromCBOR(Buffer.from(ur.urData, "hex"));
|
|
407
|
+
const requestId = signature.getRequestId();
|
|
408
|
+
return {
|
|
409
|
+
requestId: requestId ? (0, import_uuid.stringify)(requestId) : void 0,
|
|
410
|
+
signature: signature.getSignature().toString("hex")
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Derive one TRON address offline from an already-synced account xpub.
|
|
415
|
+
* TRON reuses EVM's exact secp256k1-pubkey → keccak256 → last-20-bytes
|
|
416
|
+
* derivation (verified against Keystone's own `formatAddress()` in
|
|
417
|
+
* `keystone-sdk`'s TRON chain source) — only the final text encoding
|
|
418
|
+
* differs (base58check with a `0x41` version byte, not checksummed hex).
|
|
419
|
+
* Reusing `generateAddressFromXpub` here means no new hashing dependency:
|
|
420
|
+
* strip its "0x" and re-encode the same 20 bytes.
|
|
421
|
+
*/
|
|
422
|
+
deriveTronAddressFromXpub(xpub, relativeDerivePath) {
|
|
423
|
+
const evmStyleHex = (0, import_bc_ur_registry_eth.generateAddressFromXpub)(
|
|
424
|
+
xpub,
|
|
425
|
+
`m/${relativeDerivePath.replace(/^m\//i, "")}`
|
|
426
|
+
);
|
|
427
|
+
const addressBytes = Buffer.concat([
|
|
428
|
+
Buffer.from([TRON_ADDRESS_PREFIX]),
|
|
429
|
+
Buffer.from(evmStyleHex.replace(/^0x/i, ""), "hex")
|
|
430
|
+
]);
|
|
431
|
+
return import_bs58check.default.encode(addressBytes);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
// src/adapter/KeystoneAdapter.ts
|
|
436
|
+
var import_bs58 = __toESM(require("bs58"));
|
|
437
|
+
var import_uuid2 = require("uuid");
|
|
438
|
+
var import_hwk_adapter_core = require("@onekeyfe/hwk-adapter-core");
|
|
439
|
+
|
|
440
|
+
// src/adapter/pathUtils.ts
|
|
441
|
+
function normalizePath(path) {
|
|
442
|
+
const trimmed = path.trim();
|
|
443
|
+
return /^m\//i.test(trimmed) ? `m/${trimmed.slice(2)}` : `m/${trimmed}`;
|
|
444
|
+
}
|
|
445
|
+
function splitAccountPath(path) {
|
|
446
|
+
const normalized = normalizePath(path);
|
|
447
|
+
const segments = normalized.slice(2).split("/");
|
|
448
|
+
if (segments.length <= 3) {
|
|
449
|
+
return { accountPath: normalized, relativeDerivePath: "" };
|
|
450
|
+
}
|
|
451
|
+
const accountSegments = segments.slice(0, segments.length - 2);
|
|
452
|
+
const relativeSegments = segments.slice(segments.length - 2);
|
|
453
|
+
return {
|
|
454
|
+
accountPath: `m/${accountSegments.join("/")}`,
|
|
455
|
+
relativeDerivePath: relativeSegments.join("/")
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
function btcScriptTypeFromPath(path) {
|
|
459
|
+
const match = normalizePath(path).match(/^m\/(\d+)'/);
|
|
460
|
+
if (!match) return void 0;
|
|
461
|
+
switch (Number(match[1])) {
|
|
462
|
+
case 44:
|
|
463
|
+
return "p2pkh";
|
|
464
|
+
case 49:
|
|
465
|
+
return "p2sh-p2wpkh";
|
|
466
|
+
case 84:
|
|
467
|
+
return "p2wpkh";
|
|
468
|
+
case 86:
|
|
469
|
+
return "p2tr";
|
|
470
|
+
default:
|
|
471
|
+
return void 0;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// src/adapter/deviceTable.ts
|
|
476
|
+
var QR_CONNECT_ID_PREFIX = "keystone-qr:";
|
|
477
|
+
function qrConnectId(masterFingerprint) {
|
|
478
|
+
return `${QR_CONNECT_ID_PREFIX}${masterFingerprint}`;
|
|
479
|
+
}
|
|
480
|
+
function accountKey(hwkChain, path) {
|
|
481
|
+
return `${hwkChain}:${normalizePath(path)}`;
|
|
482
|
+
}
|
|
483
|
+
function createDeviceRecord(masterFingerprint) {
|
|
484
|
+
return {
|
|
485
|
+
masterFingerprint,
|
|
486
|
+
connectId: qrConnectId(masterFingerprint),
|
|
487
|
+
accounts: /* @__PURE__ */ new Map(),
|
|
488
|
+
importedAt: Date.now()
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
var CAPABILITIES = { persistentDeviceIdentity: true };
|
|
492
|
+
function toDeviceInfo(record) {
|
|
493
|
+
let availableChannels = ["qr"];
|
|
494
|
+
if (record.usbSessionId) {
|
|
495
|
+
availableChannels = record.qrSynced ? ["qr", "usb"] : ["usb"];
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
vendor: "keystone",
|
|
499
|
+
model: record.model ?? "unknown",
|
|
500
|
+
modelName: record.model,
|
|
501
|
+
firmwareVersion: record.deviceVersion ?? "0.0.0",
|
|
502
|
+
deviceId: record.masterFingerprint,
|
|
503
|
+
connectId: record.connectId,
|
|
504
|
+
connectionType: record.usbSessionId ? "usb" : "qr",
|
|
505
|
+
capabilities: CAPABILITIES,
|
|
506
|
+
raw: { availableChannels }
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function placeholderDeviceInfo() {
|
|
510
|
+
return {
|
|
511
|
+
vendor: "keystone",
|
|
512
|
+
model: "unknown",
|
|
513
|
+
firmwareVersion: "0.0.0",
|
|
514
|
+
deviceId: "",
|
|
515
|
+
connectId: "",
|
|
516
|
+
connectionType: "qr",
|
|
517
|
+
capabilities: CAPABILITIES
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/adapter/KeystoneAdapter.ts
|
|
522
|
+
var COLD_START_JOB_LABEL = "keystone-cold-start";
|
|
523
|
+
var BIP44_COIN_TYPE_TO_CHAIN = {
|
|
524
|
+
60: "evm",
|
|
525
|
+
0: "btc",
|
|
526
|
+
501: "sol",
|
|
527
|
+
195: "tron"
|
|
528
|
+
};
|
|
529
|
+
function inferHwkChainFromPath(path) {
|
|
530
|
+
const match = normalizePath(path).match(/^m\/\d+'\/(\d+)'/);
|
|
531
|
+
return match ? BIP44_COIN_TYPE_TO_CHAIN[Number(match[1])] : void 0;
|
|
532
|
+
}
|
|
533
|
+
var DEFAULT_IMPORT_SCHEMAS = [
|
|
534
|
+
{ hwkChain: "evm", path: import_hwk_adapter_core.CHAIN_FINGERPRINT_PATHS.evm.replace(/\/0\/0$/, "") },
|
|
535
|
+
// The 3 BTC purposes this package can actually derive addresses for
|
|
536
|
+
// (btcScriptTypeFromPath: 44'→P2PKH, 49'→P2SH-P2WPKH, 84'→P2WPKH) —
|
|
537
|
+
// explicitly requested, not left to chance on whether the device
|
|
538
|
+
// volunteers the others unprompted. Still one combined round trip: these
|
|
539
|
+
// are 3 more entries in the same qr-hardware-call, not 3 more scans.
|
|
540
|
+
// 86' (P2TR/taproot) deliberately excluded — `deriveBtcAddressFromXpub`
|
|
541
|
+
// can't use it yet (needs an elliptic-curve library not wired in), and
|
|
542
|
+
// unlike app-monorepo's own sync-all bundle (which also requests 86', but
|
|
543
|
+
// to compute a "full xfp" — a need this adapter doesn't have, since its
|
|
544
|
+
// mfp already comes for free from the response envelope), there's no
|
|
545
|
+
// other use for that xpub here — so it stays out until P2TR is real.
|
|
546
|
+
{ hwkChain: "btc", path: import_hwk_adapter_core.CHAIN_FINGERPRINT_PATHS.btc },
|
|
547
|
+
{ hwkChain: "btc", path: "m/49'/0'/0'" },
|
|
548
|
+
{ hwkChain: "btc", path: "m/84'/0'/0'" },
|
|
549
|
+
{ hwkChain: "sol", path: import_hwk_adapter_core.CHAIN_FINGERPRINT_PATHS.sol },
|
|
550
|
+
{ hwkChain: "tron", path: import_hwk_adapter_core.CHAIN_FINGERPRINT_PATHS.tron.replace(/\/0\/0$/, "") }
|
|
551
|
+
];
|
|
552
|
+
var KeystoneAdapter = class _KeystoneAdapter {
|
|
553
|
+
constructor(options) {
|
|
554
|
+
this.vendor = "keystone";
|
|
555
|
+
this.emitter = new import_hwk_adapter_core.TypedEventEmitter();
|
|
556
|
+
this._uiRegistry = new import_hwk_adapter_core.UiRequestRegistry();
|
|
557
|
+
this._devices = /* @__PURE__ */ new Map();
|
|
558
|
+
// ---------------------------------------------------------------------------
|
|
559
|
+
// All-network bundle — dispatches to the same per-chain methods below, so
|
|
560
|
+
// there's exactly one implementation of each chain's address logic.
|
|
561
|
+
// ---------------------------------------------------------------------------
|
|
562
|
+
this.allNetworkGetAddress = async (connectId, deviceId, params) => {
|
|
563
|
+
try {
|
|
564
|
+
return await (0, import_hwk_adapter_core.runAllNetworkGetAddress)({
|
|
565
|
+
connectId,
|
|
566
|
+
deviceId,
|
|
567
|
+
params,
|
|
568
|
+
callItem: async ({ chain, item }) => {
|
|
569
|
+
const commonArgs = { path: item.path, showOnDevice: item.showOnDevice };
|
|
570
|
+
switch (chain) {
|
|
571
|
+
case "evm":
|
|
572
|
+
return this.evmGetAddress(connectId, deviceId, commonArgs);
|
|
573
|
+
case "btc":
|
|
574
|
+
return this.btcGetAddress(connectId, deviceId, commonArgs);
|
|
575
|
+
case "sol":
|
|
576
|
+
return this.solGetAddress(connectId, deviceId, commonArgs);
|
|
577
|
+
case "tron":
|
|
578
|
+
return this.tronGetAddress(connectId, deviceId, commonArgs);
|
|
579
|
+
default:
|
|
580
|
+
return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.MethodNotSupported, `Unsupported chain: ${chain}`);
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
attachIdentity: (context) => {
|
|
584
|
+
const mfp = deviceId ? deviceId.toLowerCase() : void 0;
|
|
585
|
+
return Promise.resolve({
|
|
586
|
+
...context.item,
|
|
587
|
+
success: true,
|
|
588
|
+
payload: {
|
|
589
|
+
...context.payload,
|
|
590
|
+
deviceIdentity: mfp ? { vendor: "keystone", type: "masterFingerprint", value: mfp } : void 0
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
} catch (err) {
|
|
596
|
+
return this._errorToFailure(err);
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
this._origin = options?.origin ?? "OneKey";
|
|
600
|
+
this._qrTimeoutMs = options?.qrTimeoutMs;
|
|
601
|
+
this.urEngine = new KeystoneUrEngine(this._origin);
|
|
602
|
+
this._jobQueue = new import_hwk_adapter_core.DeviceJobQueue();
|
|
603
|
+
this._usbConnector = options?.usbConnector;
|
|
604
|
+
}
|
|
605
|
+
// ---------------------------------------------------------------------------
|
|
606
|
+
// Lifecycle / transport
|
|
607
|
+
// ---------------------------------------------------------------------------
|
|
608
|
+
get activeTransport() {
|
|
609
|
+
if (this._devices.size === 0) return null;
|
|
610
|
+
const hasUsbSession = Array.from(this._devices.values()).some((r) => r.usbSessionId);
|
|
611
|
+
return hasUsbSession ? "usb" : "qr";
|
|
612
|
+
}
|
|
613
|
+
getAvailableTransports() {
|
|
614
|
+
return this._usbConnector ? ["qr", "usb"] : ["qr"];
|
|
615
|
+
}
|
|
616
|
+
/** Pins subsequent calls to 'qr' or 'usb' (routing otherwise defaults to "USB when the target wallet has a live session, else QR" — see `_resolveUr`). Any other value clears the pin back to auto. */
|
|
617
|
+
switchTransport(type) {
|
|
618
|
+
this._forcedTransport = type === "qr" || type === "usb" ? type : void 0;
|
|
619
|
+
return Promise.resolve();
|
|
620
|
+
}
|
|
621
|
+
init(_config) {
|
|
622
|
+
return Promise.resolve();
|
|
623
|
+
}
|
|
624
|
+
dispose() {
|
|
625
|
+
this._uiRegistry.cancel();
|
|
626
|
+
this._jobQueue.clear();
|
|
627
|
+
this.emitter.removeAllListeners();
|
|
628
|
+
this._devices.clear();
|
|
629
|
+
this._usbConnector?.reset();
|
|
630
|
+
return Promise.resolve();
|
|
631
|
+
}
|
|
632
|
+
// ---------------------------------------------------------------------------
|
|
633
|
+
// Device table
|
|
634
|
+
// ---------------------------------------------------------------------------
|
|
635
|
+
/**
|
|
636
|
+
* QR-synced wallets are always included (this instance's own state — no
|
|
637
|
+
* enumeration exists for QR). When a USB connector is configured, its raw
|
|
638
|
+
* scan results are appended as-is: a USB descriptor has no mfp until
|
|
639
|
+
* `connectDevice()` actually opens+claims it (see
|
|
640
|
+
* `KeystoneUsbConnectorBase.searchDevices`), so these entries carry an
|
|
641
|
+
* empty `deviceId` and exist purely so a host can list "plugged in, click
|
|
642
|
+
* to connect" candidates.
|
|
643
|
+
*/
|
|
644
|
+
async searchDevices(_options) {
|
|
645
|
+
const known = Array.from(this._devices.values()).map(toDeviceInfo);
|
|
646
|
+
if (!this._usbConnector) return known;
|
|
647
|
+
let usbDevices;
|
|
648
|
+
try {
|
|
649
|
+
usbDevices = await this._usbConnector.searchDevices();
|
|
650
|
+
} catch {
|
|
651
|
+
return known;
|
|
652
|
+
}
|
|
653
|
+
const placeholders = usbDevices.map((d) => ({
|
|
654
|
+
vendor: "keystone",
|
|
655
|
+
model: d.model ?? "unknown",
|
|
656
|
+
modelName: d.modelName,
|
|
657
|
+
firmwareVersion: "0.0.0",
|
|
658
|
+
deviceId: d.deviceId,
|
|
659
|
+
connectId: d.connectId,
|
|
660
|
+
connectionType: "usb",
|
|
661
|
+
capabilities: d.capabilities ?? { persistentDeviceIdentity: true }
|
|
662
|
+
}));
|
|
663
|
+
return [...known, ...placeholders];
|
|
664
|
+
}
|
|
665
|
+
async connectDevice(connectId) {
|
|
666
|
+
const existing = this._findByConnectId(connectId);
|
|
667
|
+
if (existing) return (0, import_hwk_adapter_core.success)(existing.connectId);
|
|
668
|
+
if (!this._usbConnector) {
|
|
669
|
+
return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.DeviceNotFound, `Unknown Keystone connectId: ${connectId}`);
|
|
670
|
+
}
|
|
671
|
+
const result = await this._connectUsb();
|
|
672
|
+
if (!result.success) return result;
|
|
673
|
+
return (0, import_hwk_adapter_core.success)(result.payload.connectId);
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* QR has no persistent connection to tear down — the account cache
|
|
677
|
+
* survives so a later call resumes without re-syncing. For a USB session,
|
|
678
|
+
* this closes the connector session and either demotes the record back to
|
|
679
|
+
* QR-only (if it was ever QR-synced) or removes it entirely (pure-USB
|
|
680
|
+
* wallet that was never seen over QR) — see §4.2 of the design doc.
|
|
681
|
+
*/
|
|
682
|
+
async disconnectDevice(connectId) {
|
|
683
|
+
const record = this._findByConnectId(connectId);
|
|
684
|
+
if (!record?.usbSessionId) return;
|
|
685
|
+
if (this._usbConnector) {
|
|
686
|
+
try {
|
|
687
|
+
await this._usbConnector.disconnect(record.usbSessionId);
|
|
688
|
+
} catch {
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
record.usbSessionId = void 0;
|
|
692
|
+
if (record.qrSynced) {
|
|
693
|
+
const info = toDeviceInfo(record);
|
|
694
|
+
this.emitter.emit(import_hwk_adapter_core.DEVICE.CHANGED, { type: import_hwk_adapter_core.DEVICE.CHANGED, payload: info });
|
|
695
|
+
} else {
|
|
696
|
+
this._devices.delete(record.masterFingerprint);
|
|
697
|
+
const info = toDeviceInfo(record);
|
|
698
|
+
this.emitter.emit(import_hwk_adapter_core.DEVICE.DISCONNECT, { type: import_hwk_adapter_core.DEVICE.DISCONNECT, payload: info });
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
getDeviceInfo(connectId, deviceId) {
|
|
702
|
+
const record = this._findByConnectId(connectId) ?? this._devices.get(deviceId.toLowerCase());
|
|
703
|
+
if (!record) {
|
|
704
|
+
return Promise.resolve(
|
|
705
|
+
(0, import_hwk_adapter_core.failure)(
|
|
706
|
+
import_hwk_adapter_core.HardwareErrorCode.DeviceNotFound,
|
|
707
|
+
`Unknown Keystone device: ${connectId || deviceId}`
|
|
708
|
+
)
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
return Promise.resolve((0, import_hwk_adapter_core.success)(toDeviceInfo(record)));
|
|
712
|
+
}
|
|
713
|
+
getSupportedChains() {
|
|
714
|
+
return ["evm", "btc", "sol", "tron"];
|
|
715
|
+
}
|
|
716
|
+
cancel(connectId) {
|
|
717
|
+
const reason = (0, import_hwk_adapter_core.createHwkError)({
|
|
718
|
+
code: import_hwk_adapter_core.HardwareErrorCode.UserAborted,
|
|
719
|
+
message: "User aborted operation"
|
|
720
|
+
});
|
|
721
|
+
this._uiRegistry.cancel();
|
|
722
|
+
this._jobQueue.cancelActiveAndPending(connectId, reason);
|
|
723
|
+
}
|
|
724
|
+
getChainFingerprint(connectId, deviceId, chain) {
|
|
725
|
+
const mfp = deviceId ? deviceId.toLowerCase() : this._mfpFromConnectId(connectId);
|
|
726
|
+
if (!mfp) {
|
|
727
|
+
return Promise.resolve((0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.DeviceNotFound, "Unknown Keystone device"));
|
|
728
|
+
}
|
|
729
|
+
return Promise.resolve((0, import_hwk_adapter_core.success)((0, import_hwk_adapter_core.deriveDeviceFingerprint)(`keystone:${chain}:${mfp}`)));
|
|
730
|
+
}
|
|
731
|
+
uiResponse(response) {
|
|
732
|
+
if (response.type === import_hwk_adapter_core.UI_RESPONSE.CANCEL) {
|
|
733
|
+
this.cancel();
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
this._uiRegistry.resolve(response.type, response.payload);
|
|
737
|
+
}
|
|
738
|
+
on(event, listener) {
|
|
739
|
+
this.emitter.on(event, listener);
|
|
740
|
+
}
|
|
741
|
+
off(event, listener) {
|
|
742
|
+
this.emitter.off(event, listener);
|
|
743
|
+
}
|
|
744
|
+
// ---------------------------------------------------------------------------
|
|
745
|
+
// Explicit account import — the recommended entry point before signing, so
|
|
746
|
+
// signing calls don't each pay for their own cold-sync round trip.
|
|
747
|
+
// ---------------------------------------------------------------------------
|
|
748
|
+
async importFromQr(options = {}) {
|
|
749
|
+
try {
|
|
750
|
+
return await this._jobQueue.enqueue(COLD_START_JOB_LABEL, async (signal) => {
|
|
751
|
+
const displayDevice = placeholderDeviceInfo();
|
|
752
|
+
let responseUr;
|
|
753
|
+
let requestedChains;
|
|
754
|
+
if (options.mode === "scan") {
|
|
755
|
+
responseUr = await this._requestQrScanAndAwaitResponse(displayDevice);
|
|
756
|
+
} else {
|
|
757
|
+
const schemas = options.paths?.length ? options.paths : DEFAULT_IMPORT_SCHEMAS;
|
|
758
|
+
requestedChains = schemas.map((s) => s.hwkChain);
|
|
759
|
+
const requestUr = this.urEngine.buildKeyDerivationRequest({
|
|
760
|
+
schemas: schemas.map((s) => ({
|
|
761
|
+
path: s.path,
|
|
762
|
+
curve: s.hwkChain === "sol" ? "ed25519" : "secp256k1"
|
|
763
|
+
})),
|
|
764
|
+
origin: this._origin
|
|
765
|
+
});
|
|
766
|
+
responseUr = await this._requestQrDisplayAndAwaitResponse(displayDevice, {
|
|
767
|
+
...requestUr,
|
|
768
|
+
animated: false
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
772
|
+
const parsed = this.urEngine.parseAccountResponse(responseUr);
|
|
773
|
+
const record = this._upsertDeviceRecord(parsed);
|
|
774
|
+
for (const account of parsed.accounts) {
|
|
775
|
+
const hwkChain = requestedChains?.length === 1 ? requestedChains[0] : inferHwkChainFromPath(account.path);
|
|
776
|
+
if (hwkChain) {
|
|
777
|
+
const entry = { ...account, hwkChain };
|
|
778
|
+
record.accounts.set(accountKey(hwkChain, account.path), entry);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return (0, import_hwk_adapter_core.success)(toDeviceInfo(record));
|
|
782
|
+
});
|
|
783
|
+
} catch (err) {
|
|
784
|
+
return this._errorToFailure(err);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
// ---------------------------------------------------------------------------
|
|
788
|
+
// EVM
|
|
789
|
+
// ---------------------------------------------------------------------------
|
|
790
|
+
async evmGetAddress(connectIdArg, deviceIdArg, paramsArg) {
|
|
791
|
+
const connectId = connectIdArg ?? void 0;
|
|
792
|
+
const deviceId = deviceIdArg ?? void 0;
|
|
793
|
+
const params = paramsArg;
|
|
794
|
+
if (!params) return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "evmGetAddress requires params");
|
|
795
|
+
if (!params.path)
|
|
796
|
+
return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "evmGetAddress requires params.path");
|
|
797
|
+
const { accountPath, relativeDerivePath } = splitAccountPath(params.path);
|
|
798
|
+
if (!relativeDerivePath) {
|
|
799
|
+
return (0, import_hwk_adapter_core.failure)(
|
|
800
|
+
import_hwk_adapter_core.HardwareErrorCode.InvalidParams,
|
|
801
|
+
"evmGetAddress requires a full leaf path, e.g. m/44'/60'/0'/0/0"
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
try {
|
|
805
|
+
return await this._jobQueue.enqueue(
|
|
806
|
+
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
807
|
+
async (signal) => {
|
|
808
|
+
const { account } = await this._ensureAccountSynced(
|
|
809
|
+
connectId,
|
|
810
|
+
deviceId,
|
|
811
|
+
"evm",
|
|
812
|
+
accountPath,
|
|
813
|
+
signal
|
|
814
|
+
);
|
|
815
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
816
|
+
if (!account.extendedPublicKey) {
|
|
817
|
+
throw (0, import_hwk_adapter_core.createHwkError)({
|
|
818
|
+
code: import_hwk_adapter_core.HardwareErrorCode.MethodNotSupported,
|
|
819
|
+
message: "Keystone did not return an extended public key for this account path"
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
const address = this.urEngine.deriveEvmAddressFromXpub(
|
|
823
|
+
account.extendedPublicKey,
|
|
824
|
+
relativeDerivePath
|
|
825
|
+
);
|
|
826
|
+
return (0, import_hwk_adapter_core.success)({ address, path: normalizePath(params.path) });
|
|
827
|
+
}
|
|
828
|
+
);
|
|
829
|
+
} catch (err) {
|
|
830
|
+
return this._errorToFailure(err);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
async evmSignTransaction(connectIdArg, deviceIdArg, paramsArg) {
|
|
834
|
+
const connectId = connectIdArg ?? void 0;
|
|
835
|
+
const deviceId = deviceIdArg ?? void 0;
|
|
836
|
+
const params = paramsArg;
|
|
837
|
+
if (!params)
|
|
838
|
+
return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "evmSignTransaction requires params");
|
|
839
|
+
if (!params.path) {
|
|
840
|
+
return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "evmSignTransaction requires params.path");
|
|
841
|
+
}
|
|
842
|
+
if (!("serializedTx" in params) || typeof params.serializedTx !== "string" || !params.serializedTx) {
|
|
843
|
+
return (0, import_hwk_adapter_core.failure)(
|
|
844
|
+
import_hwk_adapter_core.HardwareErrorCode.MethodNotSupported,
|
|
845
|
+
"Keystone only signs a fully RLP-serialized transaction (params.serializedTx) \u2014 structured-field signing is not supported"
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
const rawTxHex = (0, import_hwk_adapter_core.stripHex)(params.serializedTx);
|
|
849
|
+
const path = normalizePath(params.path);
|
|
850
|
+
try {
|
|
851
|
+
return await this._jobQueue.enqueue(
|
|
852
|
+
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
853
|
+
async (signal) => {
|
|
854
|
+
const { record } = await this._ensureMfpKnown(connectId, deviceId, "evm", signal);
|
|
855
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
856
|
+
const requestId = (0, import_uuid2.v4)();
|
|
857
|
+
const dataType = parseInt(rawTxHex.slice(0, 2), 16) < 192 ? "typedTransaction" : "transaction";
|
|
858
|
+
const requestUr = this.urEngine.buildEthSignRequest({
|
|
859
|
+
requestId,
|
|
860
|
+
unsignedTxHex: rawTxHex,
|
|
861
|
+
dataType,
|
|
862
|
+
path,
|
|
863
|
+
xfp: record.masterFingerprint,
|
|
864
|
+
chainId: params.chainId
|
|
865
|
+
});
|
|
866
|
+
const responseUr = await this._resolveUr(record, requestUr, true);
|
|
867
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
868
|
+
const sig = this.urEngine.parseEthSignature(responseUr);
|
|
869
|
+
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
870
|
+
return (0, import_hwk_adapter_core.success)({
|
|
871
|
+
v: (0, import_hwk_adapter_core.ensure0x)(sig.v),
|
|
872
|
+
r: (0, import_hwk_adapter_core.ensure0x)(sig.r),
|
|
873
|
+
s: (0, import_hwk_adapter_core.ensure0x)(sig.s)
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
);
|
|
877
|
+
} catch (err) {
|
|
878
|
+
return this._errorToFailure(err);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
async evmSignMessage(connectIdArg, deviceIdArg, paramsArg) {
|
|
882
|
+
const connectId = connectIdArg ?? void 0;
|
|
883
|
+
const deviceId = deviceIdArg ?? void 0;
|
|
884
|
+
const params = paramsArg;
|
|
885
|
+
if (!params) return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "evmSignMessage requires params");
|
|
886
|
+
if (!params.path || params.message === void 0) {
|
|
887
|
+
return (0, import_hwk_adapter_core.failure)(
|
|
888
|
+
import_hwk_adapter_core.HardwareErrorCode.InvalidParams,
|
|
889
|
+
"evmSignMessage requires params.path and params.message"
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
const messageHex = params.hex ? (0, import_hwk_adapter_core.stripHex)(params.message) : Buffer.from(params.message, "utf8").toString("hex");
|
|
893
|
+
const path = normalizePath(params.path);
|
|
894
|
+
try {
|
|
895
|
+
return await this._jobQueue.enqueue(
|
|
896
|
+
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
897
|
+
async (signal) => {
|
|
898
|
+
const { record } = await this._ensureMfpKnown(connectId, deviceId, "evm", signal);
|
|
899
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
900
|
+
const requestId = (0, import_uuid2.v4)();
|
|
901
|
+
const requestUr = this.urEngine.buildEthSignRequest({
|
|
902
|
+
requestId,
|
|
903
|
+
unsignedTxHex: messageHex,
|
|
904
|
+
dataType: "personalMessage",
|
|
905
|
+
path,
|
|
906
|
+
xfp: record.masterFingerprint,
|
|
907
|
+
chainId: params.chainId
|
|
908
|
+
});
|
|
909
|
+
const responseUr = await this._resolveUr(record, requestUr, true);
|
|
910
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
911
|
+
const sig = this.urEngine.parseEthSignature(responseUr);
|
|
912
|
+
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
913
|
+
return (0, import_hwk_adapter_core.success)({ signature: (0, import_hwk_adapter_core.ensure0x)(sig.r + sig.s + sig.v) });
|
|
914
|
+
}
|
|
915
|
+
);
|
|
916
|
+
} catch (err) {
|
|
917
|
+
return this._errorToFailure(err);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
async evmSignTypedData(connectIdArg, deviceIdArg, paramsArg) {
|
|
921
|
+
const connectId = connectIdArg ?? void 0;
|
|
922
|
+
const deviceId = deviceIdArg ?? void 0;
|
|
923
|
+
const params = paramsArg;
|
|
924
|
+
if (!params)
|
|
925
|
+
return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "evmSignTypedData requires params");
|
|
926
|
+
if (!params.path) {
|
|
927
|
+
return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "evmSignTypedData requires params.path");
|
|
928
|
+
}
|
|
929
|
+
if (params.mode === "hash") {
|
|
930
|
+
return (0, import_hwk_adapter_core.failure)(
|
|
931
|
+
import_hwk_adapter_core.HardwareErrorCode.MethodNotSupported,
|
|
932
|
+
"Keystone always displays the full EIP-712 payload for on-device review \u2014 pre-hashed signing is not supported"
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
const signDataHex = Buffer.from(JSON.stringify(params.data), "utf8").toString("hex");
|
|
936
|
+
const path = normalizePath(params.path);
|
|
937
|
+
try {
|
|
938
|
+
return await this._jobQueue.enqueue(
|
|
939
|
+
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
940
|
+
async (signal) => {
|
|
941
|
+
const { record } = await this._ensureMfpKnown(connectId, deviceId, "evm", signal);
|
|
942
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
943
|
+
const requestId = (0, import_uuid2.v4)();
|
|
944
|
+
const requestUr = this.urEngine.buildEthSignRequest({
|
|
945
|
+
requestId,
|
|
946
|
+
unsignedTxHex: signDataHex,
|
|
947
|
+
dataType: "typedData",
|
|
948
|
+
path,
|
|
949
|
+
xfp: record.masterFingerprint,
|
|
950
|
+
chainId: params.chainId
|
|
951
|
+
});
|
|
952
|
+
const responseUr = await this._resolveUr(record, requestUr, true);
|
|
953
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
954
|
+
const sig = this.urEngine.parseEthSignature(responseUr);
|
|
955
|
+
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
956
|
+
return (0, import_hwk_adapter_core.success)({ signature: (0, import_hwk_adapter_core.ensure0x)(sig.r + sig.s + sig.v) });
|
|
957
|
+
}
|
|
958
|
+
);
|
|
959
|
+
} catch (err) {
|
|
960
|
+
return this._errorToFailure(err);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
// ---------------------------------------------------------------------------
|
|
964
|
+
// BTC — PSBT signing and message signing only for now. Address/pubkey
|
|
965
|
+
// derivation needs script-type-aware xpub decoding (P2WPKH/P2TR/…) this
|
|
966
|
+
// phase doesn't wire in yet; structured-field tx signing needs host-side
|
|
967
|
+
// PSBT construction. Both are real, bounded follow-ups, not silent gaps.
|
|
968
|
+
// ---------------------------------------------------------------------------
|
|
969
|
+
async btcGetAddress(connectIdArg, deviceIdArg, paramsArg) {
|
|
970
|
+
const connectId = connectIdArg ?? void 0;
|
|
971
|
+
const deviceId = deviceIdArg ?? void 0;
|
|
972
|
+
const params = paramsArg;
|
|
973
|
+
if (!params) return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "btcGetAddress requires params");
|
|
974
|
+
if (!params.path)
|
|
975
|
+
return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "btcGetAddress requires params.path");
|
|
976
|
+
const scriptType = btcScriptTypeFromPath(params.path);
|
|
977
|
+
if (!scriptType) {
|
|
978
|
+
return (0, import_hwk_adapter_core.failure)(
|
|
979
|
+
import_hwk_adapter_core.HardwareErrorCode.InvalidParams,
|
|
980
|
+
"btcGetAddress requires a path whose purpose is 44'/49'/84'/86' (P2PKH/P2SH-P2WPKH/P2WPKH/P2TR)"
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
if (scriptType === "p2tr") {
|
|
984
|
+
return this._unsupported(
|
|
985
|
+
"btcGetAddress",
|
|
986
|
+
"P2TR (purpose 86') needs an elliptic-curve library for BIP-341 tweaking, not yet wired in \u2014 44'/49'/84' work"
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
const { accountPath, relativeDerivePath } = splitAccountPath(params.path);
|
|
990
|
+
if (!relativeDerivePath) {
|
|
991
|
+
return (0, import_hwk_adapter_core.failure)(
|
|
992
|
+
import_hwk_adapter_core.HardwareErrorCode.InvalidParams,
|
|
993
|
+
"btcGetAddress requires a full leaf path, e.g. m/84'/0'/0'/0/0"
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
try {
|
|
997
|
+
return await this._jobQueue.enqueue(
|
|
998
|
+
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
999
|
+
async (signal) => {
|
|
1000
|
+
const { account } = await this._ensureAccountSynced(
|
|
1001
|
+
connectId,
|
|
1002
|
+
deviceId,
|
|
1003
|
+
"btc",
|
|
1004
|
+
accountPath,
|
|
1005
|
+
signal
|
|
1006
|
+
);
|
|
1007
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1008
|
+
if (!account.extendedPublicKey) {
|
|
1009
|
+
throw (0, import_hwk_adapter_core.createHwkError)({
|
|
1010
|
+
code: import_hwk_adapter_core.HardwareErrorCode.MethodNotSupported,
|
|
1011
|
+
message: "Keystone did not return an extended public key for this account path"
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
const address = this.urEngine.deriveBtcAddressFromXpub(
|
|
1015
|
+
account.extendedPublicKey,
|
|
1016
|
+
relativeDerivePath,
|
|
1017
|
+
scriptType
|
|
1018
|
+
);
|
|
1019
|
+
return (0, import_hwk_adapter_core.success)({ address, path: normalizePath(params.path) });
|
|
1020
|
+
}
|
|
1021
|
+
);
|
|
1022
|
+
} catch (err) {
|
|
1023
|
+
return this._errorToFailure(err);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
async btcGetPublicKey(_connectId, _deviceId, _params) {
|
|
1027
|
+
return this._unsupported(
|
|
1028
|
+
"btcGetPublicKey",
|
|
1029
|
+
"not yet wired \u2014 the underlying synced xpub/chainCode data already exists in the device table"
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
async btcSignTransaction(_connectId, _deviceId, _params) {
|
|
1033
|
+
return this._unsupported(
|
|
1034
|
+
"btcSignTransaction",
|
|
1035
|
+
"use btcSignPsbt \u2014 structured input/output signing needs host-side PSBT construction, not yet wired in"
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
async btcSignPsbt(connectIdArg, deviceIdArg, paramsArg) {
|
|
1039
|
+
const connectId = connectIdArg ?? void 0;
|
|
1040
|
+
const deviceId = deviceIdArg ?? void 0;
|
|
1041
|
+
const params = paramsArg;
|
|
1042
|
+
if (!params) return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "btcSignPsbt requires params");
|
|
1043
|
+
if (!params.psbt)
|
|
1044
|
+
return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "btcSignPsbt requires params.psbt");
|
|
1045
|
+
try {
|
|
1046
|
+
return await this._jobQueue.enqueue(
|
|
1047
|
+
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1048
|
+
async (signal) => {
|
|
1049
|
+
const { record } = await this._ensureMfpKnown(connectId, deviceId, "btc", signal);
|
|
1050
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1051
|
+
const requestUr = this.urEngine.buildBtcPsbtRequest((0, import_hwk_adapter_core.stripHex)(params.psbt));
|
|
1052
|
+
const responseUr = await this._resolveUr(record, requestUr, true);
|
|
1053
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1054
|
+
const signedPsbt = this.urEngine.parseBtcPsbt(responseUr);
|
|
1055
|
+
return (0, import_hwk_adapter_core.success)({ signedPsbt });
|
|
1056
|
+
}
|
|
1057
|
+
);
|
|
1058
|
+
} catch (err) {
|
|
1059
|
+
return this._errorToFailure(err);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
async btcSignMessage(connectIdArg, deviceIdArg, paramsArg) {
|
|
1063
|
+
const connectId = connectIdArg ?? void 0;
|
|
1064
|
+
const deviceId = deviceIdArg ?? void 0;
|
|
1065
|
+
const params = paramsArg;
|
|
1066
|
+
if (!params) return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "btcSignMessage requires params");
|
|
1067
|
+
if (!params.path || params.message === void 0) {
|
|
1068
|
+
return (0, import_hwk_adapter_core.failure)(
|
|
1069
|
+
import_hwk_adapter_core.HardwareErrorCode.InvalidParams,
|
|
1070
|
+
"btcSignMessage requires params.path and params.message"
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
const messageHex = params.hex ? (0, import_hwk_adapter_core.stripHex)(params.message) : Buffer.from(params.message, "utf8").toString("hex");
|
|
1074
|
+
const path = normalizePath(params.path);
|
|
1075
|
+
try {
|
|
1076
|
+
return await this._jobQueue.enqueue(
|
|
1077
|
+
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1078
|
+
async (signal) => {
|
|
1079
|
+
const { record } = await this._ensureMfpKnown(connectId, deviceId, "btc", signal);
|
|
1080
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1081
|
+
const requestId = (0, import_uuid2.v4)();
|
|
1082
|
+
const requestUr = this.urEngine.buildBtcMessageSignRequest({
|
|
1083
|
+
requestId,
|
|
1084
|
+
messageHex,
|
|
1085
|
+
accounts: [{ path, xfp: record.masterFingerprint }]
|
|
1086
|
+
});
|
|
1087
|
+
const responseUr = await this._resolveUr(record, requestUr, true);
|
|
1088
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1089
|
+
const sig = this.urEngine.parseBtcSignature(responseUr);
|
|
1090
|
+
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1091
|
+
return (0, import_hwk_adapter_core.success)({ signature: sig.signature });
|
|
1092
|
+
}
|
|
1093
|
+
);
|
|
1094
|
+
} catch (err) {
|
|
1095
|
+
return this._errorToFailure(err);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
async btcGetMasterFingerprint(connectIdArg, deviceIdArg) {
|
|
1099
|
+
const connectId = connectIdArg ?? void 0;
|
|
1100
|
+
const deviceId = deviceIdArg ?? void 0;
|
|
1101
|
+
try {
|
|
1102
|
+
return await this._jobQueue.enqueue(
|
|
1103
|
+
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1104
|
+
async (signal) => {
|
|
1105
|
+
const { record } = await this._ensureMfpKnown(connectId, deviceId, "btc", signal);
|
|
1106
|
+
return (0, import_hwk_adapter_core.success)({ masterFingerprint: record.masterFingerprint });
|
|
1107
|
+
}
|
|
1108
|
+
);
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
return this._errorToFailure(err);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
// ---------------------------------------------------------------------------
|
|
1114
|
+
// SOL
|
|
1115
|
+
// ---------------------------------------------------------------------------
|
|
1116
|
+
async solGetAddress(connectIdArg, deviceIdArg, paramsArg) {
|
|
1117
|
+
const connectId = connectIdArg ?? void 0;
|
|
1118
|
+
const deviceId = deviceIdArg ?? void 0;
|
|
1119
|
+
const params = paramsArg;
|
|
1120
|
+
if (!params) return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "solGetAddress requires params");
|
|
1121
|
+
if (!params.path)
|
|
1122
|
+
return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "solGetAddress requires params.path");
|
|
1123
|
+
const path = normalizePath(params.path);
|
|
1124
|
+
try {
|
|
1125
|
+
return await this._jobQueue.enqueue(
|
|
1126
|
+
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1127
|
+
async (signal) => {
|
|
1128
|
+
const { account } = await this._ensureAccountSynced(
|
|
1129
|
+
connectId,
|
|
1130
|
+
deviceId,
|
|
1131
|
+
"sol",
|
|
1132
|
+
path,
|
|
1133
|
+
signal
|
|
1134
|
+
);
|
|
1135
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1136
|
+
const address = import_bs58.default.encode(Buffer.from(account.publicKey, "hex"));
|
|
1137
|
+
return (0, import_hwk_adapter_core.success)({ address, path });
|
|
1138
|
+
}
|
|
1139
|
+
);
|
|
1140
|
+
} catch (err) {
|
|
1141
|
+
return this._errorToFailure(err);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
async solSignTransaction(connectIdArg, deviceIdArg, paramsArg) {
|
|
1145
|
+
const connectId = connectIdArg ?? void 0;
|
|
1146
|
+
const deviceId = deviceIdArg ?? void 0;
|
|
1147
|
+
const params = paramsArg;
|
|
1148
|
+
if (!params)
|
|
1149
|
+
return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "solSignTransaction requires params");
|
|
1150
|
+
if (!params.path || !params.serializedTx) {
|
|
1151
|
+
return (0, import_hwk_adapter_core.failure)(
|
|
1152
|
+
import_hwk_adapter_core.HardwareErrorCode.InvalidParams,
|
|
1153
|
+
"solSignTransaction requires params.path and params.serializedTx"
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
const path = normalizePath(params.path);
|
|
1157
|
+
try {
|
|
1158
|
+
return await this._jobQueue.enqueue(
|
|
1159
|
+
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1160
|
+
async (signal) => {
|
|
1161
|
+
const { record } = await this._ensureMfpKnown(connectId, deviceId, "sol", signal);
|
|
1162
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1163
|
+
const requestId = (0, import_uuid2.v4)();
|
|
1164
|
+
const requestUr = this.urEngine.buildSolSignRequest({
|
|
1165
|
+
requestId,
|
|
1166
|
+
unsignedPayloadHex: (0, import_hwk_adapter_core.stripHex)(params.serializedTx),
|
|
1167
|
+
dataType: "transaction",
|
|
1168
|
+
path,
|
|
1169
|
+
xfp: record.masterFingerprint
|
|
1170
|
+
});
|
|
1171
|
+
const responseUr = await this._resolveUr(record, requestUr, true);
|
|
1172
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1173
|
+
const sig = this.urEngine.parseSolSignature(responseUr);
|
|
1174
|
+
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1175
|
+
return (0, import_hwk_adapter_core.success)({ signature: sig.signature });
|
|
1176
|
+
}
|
|
1177
|
+
);
|
|
1178
|
+
} catch (err) {
|
|
1179
|
+
return this._errorToFailure(err);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
async solSignMessage(connectIdArg, deviceIdArg, paramsArg) {
|
|
1183
|
+
const connectId = connectIdArg ?? void 0;
|
|
1184
|
+
const deviceId = deviceIdArg ?? void 0;
|
|
1185
|
+
const params = paramsArg;
|
|
1186
|
+
if (!params) return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "solSignMessage requires params");
|
|
1187
|
+
if (!params.path || !params.message) {
|
|
1188
|
+
return (0, import_hwk_adapter_core.failure)(
|
|
1189
|
+
import_hwk_adapter_core.HardwareErrorCode.InvalidParams,
|
|
1190
|
+
"solSignMessage requires params.path and params.message"
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
const path = normalizePath(params.path);
|
|
1194
|
+
try {
|
|
1195
|
+
return await this._jobQueue.enqueue(
|
|
1196
|
+
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1197
|
+
async (signal) => {
|
|
1198
|
+
const { record } = await this._ensureMfpKnown(connectId, deviceId, "sol", signal);
|
|
1199
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1200
|
+
const requestId = (0, import_uuid2.v4)();
|
|
1201
|
+
const requestUr = this.urEngine.buildSolSignRequest({
|
|
1202
|
+
requestId,
|
|
1203
|
+
unsignedPayloadHex: (0, import_hwk_adapter_core.stripHex)(params.message),
|
|
1204
|
+
dataType: "message",
|
|
1205
|
+
path,
|
|
1206
|
+
xfp: record.masterFingerprint
|
|
1207
|
+
});
|
|
1208
|
+
const responseUr = await this._resolveUr(record, requestUr, true);
|
|
1209
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1210
|
+
const sig = this.urEngine.parseSolSignature(responseUr);
|
|
1211
|
+
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1212
|
+
return (0, import_hwk_adapter_core.success)({ signature: sig.signature });
|
|
1213
|
+
}
|
|
1214
|
+
);
|
|
1215
|
+
} catch (err) {
|
|
1216
|
+
return this._errorToFailure(err);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
// ---------------------------------------------------------------------------
|
|
1220
|
+
// TRON — routed through `TronSignRequest`/`TronSignature` (see
|
|
1221
|
+
// urEngine/TronSignRequest.ts), a port of OneKey's own already-proven
|
|
1222
|
+
// production TRON QR-wallet implementation — NOT keystone-sdk's own
|
|
1223
|
+
// bundled `sdk.tron` module (different, protobuf-based protocol with
|
|
1224
|
+
// unverified response semantics).
|
|
1225
|
+
// ---------------------------------------------------------------------------
|
|
1226
|
+
async tronGetAddress(connectIdArg, deviceIdArg, paramsArg) {
|
|
1227
|
+
const connectId = connectIdArg ?? void 0;
|
|
1228
|
+
const deviceId = deviceIdArg ?? void 0;
|
|
1229
|
+
const params = paramsArg;
|
|
1230
|
+
if (!params) return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "tronGetAddress requires params");
|
|
1231
|
+
if (!params.path)
|
|
1232
|
+
return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "tronGetAddress requires params.path");
|
|
1233
|
+
const { accountPath, relativeDerivePath } = splitAccountPath(params.path);
|
|
1234
|
+
if (!relativeDerivePath) {
|
|
1235
|
+
return (0, import_hwk_adapter_core.failure)(
|
|
1236
|
+
import_hwk_adapter_core.HardwareErrorCode.InvalidParams,
|
|
1237
|
+
"tronGetAddress requires a full leaf path, e.g. m/44'/195'/0'/0/0"
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
try {
|
|
1241
|
+
return await this._jobQueue.enqueue(
|
|
1242
|
+
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1243
|
+
async (signal) => {
|
|
1244
|
+
const { account } = await this._ensureAccountSynced(
|
|
1245
|
+
connectId,
|
|
1246
|
+
deviceId,
|
|
1247
|
+
"tron",
|
|
1248
|
+
accountPath,
|
|
1249
|
+
signal
|
|
1250
|
+
);
|
|
1251
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1252
|
+
if (!account.extendedPublicKey) {
|
|
1253
|
+
throw (0, import_hwk_adapter_core.createHwkError)({
|
|
1254
|
+
code: import_hwk_adapter_core.HardwareErrorCode.MethodNotSupported,
|
|
1255
|
+
message: "Keystone did not return an extended public key for this account path"
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
const address = this.urEngine.deriveTronAddressFromXpub(
|
|
1259
|
+
account.extendedPublicKey,
|
|
1260
|
+
relativeDerivePath
|
|
1261
|
+
);
|
|
1262
|
+
return (0, import_hwk_adapter_core.success)({ address, path: normalizePath(params.path) });
|
|
1263
|
+
}
|
|
1264
|
+
);
|
|
1265
|
+
} catch (err) {
|
|
1266
|
+
return this._errorToFailure(err);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
async tronSignTransaction(connectIdArg, deviceIdArg, paramsArg) {
|
|
1270
|
+
const connectId = connectIdArg ?? void 0;
|
|
1271
|
+
const deviceId = deviceIdArg ?? void 0;
|
|
1272
|
+
const params = paramsArg;
|
|
1273
|
+
if (!params)
|
|
1274
|
+
return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "tronSignTransaction requires params");
|
|
1275
|
+
if (!params.path) {
|
|
1276
|
+
return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.InvalidParams, "tronSignTransaction requires params.path");
|
|
1277
|
+
}
|
|
1278
|
+
if (!params.rawTxHex) {
|
|
1279
|
+
return (0, import_hwk_adapter_core.failure)(
|
|
1280
|
+
import_hwk_adapter_core.HardwareErrorCode.InvalidParams,
|
|
1281
|
+
"Keystone only signs a fully protobuf-serialized TRON transaction (params.rawTxHex) \u2014 the Trezor-style structured contract fields have no equivalent here"
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
const rawTxHex = (0, import_hwk_adapter_core.stripHex)(params.rawTxHex);
|
|
1285
|
+
const path = normalizePath(params.path);
|
|
1286
|
+
try {
|
|
1287
|
+
return await this._jobQueue.enqueue(
|
|
1288
|
+
deviceId ?? connectId ?? COLD_START_JOB_LABEL,
|
|
1289
|
+
async (signal) => {
|
|
1290
|
+
const { record } = await this._ensureMfpKnown(connectId, deviceId, "tron", signal);
|
|
1291
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1292
|
+
const requestId = (0, import_uuid2.v4)();
|
|
1293
|
+
const requestUr = this.urEngine.buildTronSignRequest({
|
|
1294
|
+
requestId,
|
|
1295
|
+
rawTxHex,
|
|
1296
|
+
path,
|
|
1297
|
+
xfp: record.masterFingerprint
|
|
1298
|
+
});
|
|
1299
|
+
const responseUr = await this._resolveUr(record, requestUr, true);
|
|
1300
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1301
|
+
const sig = this.urEngine.parseTronSignature(responseUr);
|
|
1302
|
+
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1303
|
+
return (0, import_hwk_adapter_core.success)({ signature: sig.signature });
|
|
1304
|
+
}
|
|
1305
|
+
);
|
|
1306
|
+
} catch (err) {
|
|
1307
|
+
return this._errorToFailure(err);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
async tronSignMessage(_connectId, _deviceId, _params) {
|
|
1311
|
+
return this._unsupported(
|
|
1312
|
+
"tronSignMessage",
|
|
1313
|
+
"Keystone TRON message signing needs signType V1-vs-V2 verified against real hardware first \u2014 not wired in"
|
|
1314
|
+
);
|
|
1315
|
+
}
|
|
1316
|
+
// ---------------------------------------------------------------------------
|
|
1317
|
+
// Internals
|
|
1318
|
+
// ---------------------------------------------------------------------------
|
|
1319
|
+
_mfpFromConnectId(connectId) {
|
|
1320
|
+
return connectId?.startsWith(QR_CONNECT_ID_PREFIX) ? connectId.slice(QR_CONNECT_ID_PREFIX.length) : void 0;
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Handles both connectId shapes a caller might hand back: the QR-style
|
|
1324
|
+
* `keystone-qr:<mfp>` prefix, and a bare mfp — which is exactly what a
|
|
1325
|
+
* USB session's `sessionId`/`connectId` is (see `KeystoneUsbConnectorBase`
|
|
1326
|
+
* and `_connectUsb`).
|
|
1327
|
+
*/
|
|
1328
|
+
_findByConnectId(connectId) {
|
|
1329
|
+
const mfp = this._mfpFromConnectId(connectId) ?? connectId?.toLowerCase();
|
|
1330
|
+
return mfp ? this._devices.get(mfp) : void 0;
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Folds a parsed account-response UR into the device table. `viaUsb`
|
|
1334
|
+
* (defaults false) says which channel actually carried this round trip —
|
|
1335
|
+
* `_resolveUr` routes a KeyDerivation sync over USB when the target record
|
|
1336
|
+
* already has a live session, so this must NOT unconditionally mark
|
|
1337
|
+
* `qrSynced`, or a USB-only wallet would wrongly survive a later USB
|
|
1338
|
+
* disconnect as a "QR-synced, demote to QR-only" entry instead of being
|
|
1339
|
+
* dropped outright (see `disconnectDevice`).
|
|
1340
|
+
*/
|
|
1341
|
+
_upsertDeviceRecord(parsed, options) {
|
|
1342
|
+
const mfp = parsed.masterFingerprint;
|
|
1343
|
+
let record = this._devices.get(mfp);
|
|
1344
|
+
const isNew = !record;
|
|
1345
|
+
if (!record) {
|
|
1346
|
+
record = createDeviceRecord(mfp);
|
|
1347
|
+
this._devices.set(mfp, record);
|
|
1348
|
+
}
|
|
1349
|
+
record.model = parsed.device ?? record.model;
|
|
1350
|
+
record.deviceVersion = parsed.deviceVersion ?? record.deviceVersion;
|
|
1351
|
+
if (!options?.viaUsb) record.qrSynced = true;
|
|
1352
|
+
const info = toDeviceInfo(record);
|
|
1353
|
+
const eventType = isNew ? import_hwk_adapter_core.DEVICE.CONNECT : import_hwk_adapter_core.DEVICE.CHANGED;
|
|
1354
|
+
this.emitter.emit(eventType, { type: eventType, payload: info });
|
|
1355
|
+
return record;
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Opens+claims whatever Keystone the USB connector currently has
|
|
1359
|
+
* permission for, learns its mfp via `getAppConfig`, and merges it into
|
|
1360
|
+
* the device table by that mfp — a QR-synced entry becomes
|
|
1361
|
+
* `{qr, usb}`-capable in place (one `device-changed`, not a second
|
|
1362
|
+
* `device-connect`); a wallet never seen before becomes a new USB-only
|
|
1363
|
+
* entry. See §4.2 of the design doc.
|
|
1364
|
+
*/
|
|
1365
|
+
async _connectUsb() {
|
|
1366
|
+
if (!this._usbConnector) {
|
|
1367
|
+
return (0, import_hwk_adapter_core.failure)(
|
|
1368
|
+
import_hwk_adapter_core.HardwareErrorCode.TransportNotAvailable,
|
|
1369
|
+
"No USB connector configured for this Keystone adapter"
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
try {
|
|
1373
|
+
const session = await this._usbConnector.connect();
|
|
1374
|
+
const mfp = session.deviceInfo.deviceId.toLowerCase();
|
|
1375
|
+
let record = this._devices.get(mfp);
|
|
1376
|
+
const isNew = !record;
|
|
1377
|
+
if (!record) {
|
|
1378
|
+
record = createDeviceRecord(mfp);
|
|
1379
|
+
this._devices.set(mfp, record);
|
|
1380
|
+
}
|
|
1381
|
+
record.model = session.deviceInfo.modelName ?? session.deviceInfo.model ?? record.model;
|
|
1382
|
+
record.deviceVersion = session.deviceInfo.firmwareVersion ?? record.deviceVersion;
|
|
1383
|
+
record.usbSessionId = session.sessionId;
|
|
1384
|
+
const info = toDeviceInfo(record);
|
|
1385
|
+
const eventType = isNew ? import_hwk_adapter_core.DEVICE.CONNECT : import_hwk_adapter_core.DEVICE.CHANGED;
|
|
1386
|
+
this.emitter.emit(eventType, { type: eventType, payload: info });
|
|
1387
|
+
return (0, import_hwk_adapter_core.success)(info);
|
|
1388
|
+
} catch (err) {
|
|
1389
|
+
return this._errorToFailure(err);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* The one place that decides QR vs. USB for a UR round trip and carries it
|
|
1394
|
+
* out. `record` is the (possibly not-yet-existing, for a true cold start)
|
|
1395
|
+
* device row for the target wallet — USB is only used when `record`
|
|
1396
|
+
* already has a live `usbSessionId` (a session comes from an explicit
|
|
1397
|
+
* `connectDevice()`, never conjured mid-call — see the class doc). A
|
|
1398
|
+
* `switchTransport('qr')` pin forces QR even for a USB-attached wallet;
|
|
1399
|
+
* `switchTransport('usb')` on a wallet with no live USB session fails
|
|
1400
|
+
* closed rather than silently falling back to QR.
|
|
1401
|
+
*/
|
|
1402
|
+
async _resolveUr(record, requestUr, animated) {
|
|
1403
|
+
const wantUsb = this._forcedTransport === "usb" || this._forcedTransport !== "qr" && Boolean(record?.usbSessionId);
|
|
1404
|
+
if (wantUsb) {
|
|
1405
|
+
if (!record?.usbSessionId || !this._usbConnector) {
|
|
1406
|
+
throw (0, import_hwk_adapter_core.createHwkError)({
|
|
1407
|
+
code: import_hwk_adapter_core.HardwareErrorCode.TransportNotAvailable,
|
|
1408
|
+
message: "USB channel is not connected for this Keystone wallet"
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
const result = await this._usbConnector.call(record.usbSessionId, "resolveUr", requestUr);
|
|
1412
|
+
if (!result.success) throw (0, import_hwk_adapter_core.rehydrateConnectorError)(result.error);
|
|
1413
|
+
return result.payload;
|
|
1414
|
+
}
|
|
1415
|
+
const displayDevice = record ? toDeviceInfo(record) : placeholderDeviceInfo();
|
|
1416
|
+
return this._requestQrDisplayAndAwaitResponse(displayDevice, { ...requestUr, animated });
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Resolve (syncing over QR if needed) the account cached for `hwkChain` at
|
|
1420
|
+
* `syncPath`. Drives the "implicit account sync, then the real request" two
|
|
1421
|
+
* hop flow the first time a wallet/path pair is seen; a cache hit skips
|
|
1422
|
+
* straight to the caller's own round trip.
|
|
1423
|
+
*/
|
|
1424
|
+
async _ensureAccountSynced(connectId, deviceId, hwkChain, syncPath, signal) {
|
|
1425
|
+
const wantMfp = deviceId ? deviceId.toLowerCase() : this._mfpFromConnectId(connectId ?? "");
|
|
1426
|
+
const key = accountKey(hwkChain, syncPath);
|
|
1427
|
+
const existingRecord = wantMfp ? this._devices.get(wantMfp) : void 0;
|
|
1428
|
+
const cached = existingRecord?.accounts.get(key);
|
|
1429
|
+
if (existingRecord && cached) {
|
|
1430
|
+
return { record: existingRecord, account: cached };
|
|
1431
|
+
}
|
|
1432
|
+
const requestUr = this.urEngine.buildKeyDerivationRequest({
|
|
1433
|
+
schemas: [{ path: syncPath, curve: hwkChain === "sol" ? "ed25519" : "secp256k1" }],
|
|
1434
|
+
origin: this._origin
|
|
1435
|
+
});
|
|
1436
|
+
const responseUr = await this._resolveUr(existingRecord, requestUr, false);
|
|
1437
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1438
|
+
const parsed = this.urEngine.parseAccountResponse(responseUr);
|
|
1439
|
+
if (wantMfp && parsed.masterFingerprint !== wantMfp) {
|
|
1440
|
+
throw (0, import_hwk_adapter_core.createHwkError)({
|
|
1441
|
+
code: import_hwk_adapter_core.HardwareErrorCode.DeviceMismatch,
|
|
1442
|
+
message: `Scanned Keystone wallet (mfp ${parsed.masterFingerprint}) does not match the requested device (${wantMfp})`
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
const record = this._upsertDeviceRecord(parsed, {
|
|
1446
|
+
viaUsb: Boolean(existingRecord?.usbSessionId)
|
|
1447
|
+
});
|
|
1448
|
+
const account = parsed.accounts.find((a) => normalizePath(a.path) === syncPath);
|
|
1449
|
+
if (!account) {
|
|
1450
|
+
throw (0, import_hwk_adapter_core.createHwkError)({
|
|
1451
|
+
code: import_hwk_adapter_core.HardwareErrorCode.DeviceMismatch,
|
|
1452
|
+
message: `Keystone did not return the requested derivation path (${syncPath})`
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
const entry = { ...account, hwkChain };
|
|
1456
|
+
record.accounts.set(key, entry);
|
|
1457
|
+
return { record, account: entry };
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Like `_ensureAccountSynced`, but for operations (PSBT signing, master
|
|
1461
|
+
* fingerprint) that only need to know WHICH wallet is attached, not a
|
|
1462
|
+
* specific cached path. Syncs the account-level path for `chain` as a
|
|
1463
|
+
* throwaway probe when the mfp isn't already known.
|
|
1464
|
+
*
|
|
1465
|
+
* `CHAIN_FINGERPRINT_PATHS[chain]` is a 5-segment LEAF path for `evm`
|
|
1466
|
+
* (`m/44'/60'/0'/0/0`) — sending that verbatim as a KeyDerivation request
|
|
1467
|
+
* asks Keystone for a non-standard path. Keystone's own docs
|
|
1468
|
+
* (dev.keyst.one's multichain KeyDerivation example) show the ETH
|
|
1469
|
+
* account-level path as `m/44'/60'/0'` (3 segments), same as what
|
|
1470
|
+
* `DEFAULT_IMPORT_SCHEMAS`/`_ensureAccountSynced` already request — so
|
|
1471
|
+
* truncate through `splitAccountPath` here too instead of using the raw
|
|
1472
|
+
* fingerprint leaf path. `btc`/`sol` are already 3-segment account paths
|
|
1473
|
+
* and pass through unchanged.
|
|
1474
|
+
*/
|
|
1475
|
+
async _ensureMfpKnown(connectId, deviceId, chain, signal) {
|
|
1476
|
+
const wantMfp = deviceId ? deviceId.toLowerCase() : this._mfpFromConnectId(connectId ?? "");
|
|
1477
|
+
const existing = wantMfp ? this._devices.get(wantMfp) : void 0;
|
|
1478
|
+
if (existing) return { record: existing };
|
|
1479
|
+
const { accountPath } = splitAccountPath(import_hwk_adapter_core.CHAIN_FINGERPRINT_PATHS[chain]);
|
|
1480
|
+
const requestUr = this.urEngine.buildKeyDerivationRequest({
|
|
1481
|
+
schemas: [{ path: accountPath, curve: chain === "sol" ? "ed25519" : "secp256k1" }],
|
|
1482
|
+
origin: this._origin
|
|
1483
|
+
});
|
|
1484
|
+
const responseUr = await this._resolveUr(void 0, requestUr, false);
|
|
1485
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1486
|
+
const parsed = this.urEngine.parseAccountResponse(responseUr);
|
|
1487
|
+
if (wantMfp && parsed.masterFingerprint !== wantMfp) {
|
|
1488
|
+
throw (0, import_hwk_adapter_core.createHwkError)({
|
|
1489
|
+
code: import_hwk_adapter_core.HardwareErrorCode.DeviceMismatch,
|
|
1490
|
+
message: `Scanned Keystone wallet (mfp ${parsed.masterFingerprint}) does not match the requested device (${wantMfp})`
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
return { record: this._upsertDeviceRecord(parsed) };
|
|
1494
|
+
}
|
|
1495
|
+
async _requestQrDisplayAndAwaitResponse(device, data) {
|
|
1496
|
+
const waitPromise = this._uiRegistry.wait(
|
|
1497
|
+
import_hwk_adapter_core.UI_REQUEST.REQUEST_QR_DISPLAY,
|
|
1498
|
+
{ timeoutMs: this._qrTimeoutMs }
|
|
1499
|
+
);
|
|
1500
|
+
this.emitter.emit(import_hwk_adapter_core.UI_REQUEST.REQUEST_QR_DISPLAY, {
|
|
1501
|
+
type: import_hwk_adapter_core.UI_REQUEST.REQUEST_QR_DISPLAY,
|
|
1502
|
+
payload: { device, data }
|
|
1503
|
+
});
|
|
1504
|
+
const response = await waitPromise;
|
|
1505
|
+
return { urType: response.urType, urData: response.urData };
|
|
1506
|
+
}
|
|
1507
|
+
async _requestQrScanAndAwaitResponse(device) {
|
|
1508
|
+
const waitPromise = this._uiRegistry.wait(
|
|
1509
|
+
import_hwk_adapter_core.UI_REQUEST.REQUEST_QR_SCAN,
|
|
1510
|
+
{ timeoutMs: this._qrTimeoutMs }
|
|
1511
|
+
);
|
|
1512
|
+
this.emitter.emit(import_hwk_adapter_core.UI_REQUEST.REQUEST_QR_SCAN, {
|
|
1513
|
+
type: import_hwk_adapter_core.UI_REQUEST.REQUEST_QR_SCAN,
|
|
1514
|
+
payload: { device }
|
|
1515
|
+
});
|
|
1516
|
+
const response = await waitPromise;
|
|
1517
|
+
return { urType: response.urType, urData: response.urData };
|
|
1518
|
+
}
|
|
1519
|
+
static _throwIfAborted(signal) {
|
|
1520
|
+
if (signal.aborted) {
|
|
1521
|
+
throw signal.reason instanceof Error ? signal.reason : new Error("Operation aborted");
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
static _assertRequestIdMatches(expected, actual) {
|
|
1525
|
+
if (actual && actual.toLowerCase() !== expected.toLowerCase()) {
|
|
1526
|
+
throw (0, import_hwk_adapter_core.createHwkError)({
|
|
1527
|
+
code: import_hwk_adapter_core.HardwareErrorCode.DeviceMismatch,
|
|
1528
|
+
message: "Keystone response requestId does not match the pending request \u2014 discarding a stale or unrelated scan"
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
_unsupported(method, reason) {
|
|
1533
|
+
return Promise.resolve(
|
|
1534
|
+
(0, import_hwk_adapter_core.failure)(
|
|
1535
|
+
import_hwk_adapter_core.HardwareErrorCode.MethodNotSupported,
|
|
1536
|
+
`KeystoneAdapter.${method} is not implemented yet: ${reason}`
|
|
1537
|
+
)
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
_errorToFailure(err) {
|
|
1541
|
+
if (err && typeof err === "object") {
|
|
1542
|
+
const e = err;
|
|
1543
|
+
if (typeof e.code === "number") {
|
|
1544
|
+
return (0, import_hwk_adapter_core.failure)(e.code, e.message ?? "Unknown error", e.params);
|
|
1545
|
+
}
|
|
1546
|
+
if (e._tag === import_hwk_adapter_core.UI_REQUEST_CANCELLED_TAG || e._tag === import_hwk_adapter_core.UI_REQUEST_PREEMPTED_TAG) {
|
|
1547
|
+
return (0, import_hwk_adapter_core.failure)(
|
|
1548
|
+
import_hwk_adapter_core.HardwareErrorCode.UserAborted,
|
|
1549
|
+
e.message ?? "Keystone QR interaction was cancelled"
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
if (e._tag === import_hwk_adapter_core.UI_REQUEST_TIMEOUT_TAG) {
|
|
1553
|
+
return (0, import_hwk_adapter_core.failure)(
|
|
1554
|
+
import_hwk_adapter_core.HardwareErrorCode.OperationTimeout,
|
|
1555
|
+
e.message ?? "Keystone QR interaction timed out"
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1560
|
+
const params = err instanceof Error && err.stack ? { stack: err.stack } : void 0;
|
|
1561
|
+
return (0, import_hwk_adapter_core.failure)(import_hwk_adapter_core.HardwareErrorCode.UnknownError, message, params);
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
1564
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1565
|
+
0 && (module.exports = {
|
|
1566
|
+
KeystoneAdapter,
|
|
1567
|
+
KeystoneUrEngine,
|
|
1568
|
+
QR_CONNECT_ID_PREFIX,
|
|
1569
|
+
accountKey,
|
|
1570
|
+
btcScriptTypeFromPath,
|
|
1571
|
+
createDeviceRecord,
|
|
1572
|
+
normalizePath,
|
|
1573
|
+
placeholderDeviceInfo,
|
|
1574
|
+
qrConnectId,
|
|
1575
|
+
splitAccountPath,
|
|
1576
|
+
toDeviceInfo
|
|
1577
|
+
});
|
|
1578
|
+
//# sourceMappingURL=index.js.map
|