From bedb2944eae9dacbf201ab65dc6dafceb9ed6e23 Mon Sep 17 00:00:00 2001 From: highlander Date: Fri, 8 May 2026 13:02:26 -0300 Subject: [PATCH 1/2] feat(keepkey): expose firmware-reported pre-image hash on EthSignTx response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EthereumSignedTx (KeepKey custom proto) carries an optional `hash` field — the keccak256 pre-image the firmware actually signed. Until now the SDK ignored it, so the only way to verify which message bytes the device hashed was to recover the signer client-side and compare to the expected address. Surface the hash as `deviceSignedHash` on the returned signed tx so callers can pin the exact firmware behavior in tests/diagnostics. Older firmware doesn't populate the field; absence is non-fatal (try/catch, optional). Motivating use case: EIP-1559 tx hash regression where ethers' Transaction.from() reconstruction recovers the wrong signer — having the firmware-reported hash narrows the divergence to RLP construction vs. signing, instead of guessing. Non-breaking: purely additive. Existing consumers see the same shape. --- packages/hdwallet-keepkey/src/ethereum.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/hdwallet-keepkey/src/ethereum.ts b/packages/hdwallet-keepkey/src/ethereum.ts index 051a2fef..1a8a46b2 100644 --- a/packages/hdwallet-keepkey/src/ethereum.ts +++ b/packages/hdwallet-keepkey/src/ethereum.ts @@ -386,6 +386,20 @@ export async function ethSignTx(transport: Transport, msg: core.ETHSignTx): Prom const v = core.mustBeDefined(response.getSignatureV()); const v2 = "0x" + v.toString(16); + // Capture the firmware-reported pre-image hash (KeepKey custom field). + // Older firmware doesn't populate this; absence is non-fatal. + let deviceSignedHash: string | undefined; + try { + if (response.hasHash && response.hasHash() && response.getHash_asU8) { + const h = response.getHash_asU8(); + if (h && h.length === 32) { + deviceSignedHash = "0x" + core.toHexString(h); + } + } + } catch { + // ignore — older firmware/proto may not support `hash` + } + const common = Common.custom({ chainId: msg.chainId }); const tx = msg.maxFeePerGas ? FeeMarketEIP1559Transaction.fromTxData({ @@ -403,7 +417,8 @@ export async function ethSignTx(transport: Transport, msg: core.ETHSignTx): Prom s, v, serialized: "0x" + core.toHexString(tx.serialize()), - }; + ...(deviceSignedHash ? { deviceSignedHash } : {}), + } as core.ETHSignedTx & { deviceSignedHash?: string }; }); } From 8026f0a16d498dae493e957f20f0e7037021695b Mon Sep 17 00:00:00 2001 From: highlander Date: Fri, 8 May 2026 13:08:58 -0300 Subject: [PATCH 2/2] fix(core): declare deviceSignedHash on ETHSignedTx so callers can read it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review feedback on PR #40: the previous commit attached deviceSignedHash via a local cast in hdwallet-keepkey, but ETHSignedTx in hdwallet-core didn't declare the field — TypeScript callers couldn't read it without their own cast, which defeated the diagnostic purpose. Add deviceSignedHash?: string to ETHSignedTx (optional, KeepKey-only) and drop the local cast. The shape is identical at runtime; this just makes the field visible to consumers. --- packages/hdwallet-core/src/ethereum.ts | 4 ++++ packages/hdwallet-keepkey/src/ethereum.ts | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/hdwallet-core/src/ethereum.ts b/packages/hdwallet-core/src/ethereum.ts index 213b9565..5204d82f 100644 --- a/packages/hdwallet-core/src/ethereum.ts +++ b/packages/hdwallet-core/src/ethereum.ts @@ -103,6 +103,10 @@ export interface ETHSignedTx { s: string; /** big-endian hex, prefixed with '0x' */ serialized: string; + /** KeepKey-only: keccak256 pre-image the firmware actually signed (32-byte hex). + * Optional — older firmware doesn't populate it. Useful for diagnostics where + * the caller needs to verify which bytes the device hashed. */ + deviceSignedHash?: string; } export interface ETHSignMessage { diff --git a/packages/hdwallet-keepkey/src/ethereum.ts b/packages/hdwallet-keepkey/src/ethereum.ts index 1a8a46b2..68a7c5bd 100644 --- a/packages/hdwallet-keepkey/src/ethereum.ts +++ b/packages/hdwallet-keepkey/src/ethereum.ts @@ -412,13 +412,14 @@ export async function ethSignTx(transport: Transport, msg: core.ETHSignTx): Prom }) : Transaction.fromTxData({ ...utxBase, gasPrice: msg.gasPrice, r: r, s: s, v: v2 }, { common }); - return { + const result: core.ETHSignedTx = { r, s, v, serialized: "0x" + core.toHexString(tx.serialize()), - ...(deviceSignedHash ? { deviceSignedHash } : {}), - } as core.ETHSignedTx & { deviceSignedHash?: string }; + }; + if (deviceSignedHash) result.deviceSignedHash = deviceSignedHash; + return result; }); }