Skip to content

Scope: pin gas + expose pre-sign RLP hash for on-device match in blind-sign mode #37

Description

@szhygulin

Background

Today our prepare_* tools return a payloadHash over {chain, to, value, data} — a server-side integrity tag between prepare and send. Ledger's on-device hash display in blind-sign mode is over the full EIP-1559 RLP pre-sign hash, which includes nonce + maxFeePerGas + maxPriorityFeePerGas + gasLimit — fields that Ledger Live currently picks at send time. Those hashes never match, and we correctly dropped the "short hash must equal what Ledger shows" reminder in PR #35.

The consequence is that, in blind-sign mode, the only on-device checks available to the user are To and Value (which do round-trip into the RLP verbatim). Calldata integrity at the on-device boundary is unverifiable. This is a gap against a compromised-MCP-server threat model — our multi-channel cross-check (local ABI + 4byte.directory + user's own swiss-knife/agent decode) is the replacement, but none of those cross the Ledger device boundary.

Proposal

Pin all nonce+gas fields server-side so we can predict Ledger's on-device hash and restore the on-device hash-match check.

Flow:

  1. In src/signing/walletconnect.ts:requestSendTransaction, query eth_getTransactionCount(from, pending), eth_feeHistory, and eth_estimateGas at send time (NOT prepare time — gas conditions move).
  2. Compute the EIP-1559 RLP pre-sign hash from the full tuple.
  3. Return the expected hash in the send_transaction response as a new text content block: "Ledger will show this hash — reject if it doesn't match." Agent relays to user before they confirm on-device.
  4. Pass nonce, maxFeePerGas, maxPriorityFeePerGas, gasLimit in the WalletConnect eth_sendTransaction params.

Open questions / caveats

  • Does Ledger Live honor dApp-supplied fee fields? WalletConnect's eth_sendTransaction spec allows them, but Ledger Live treats them as hints and falls back to its own estimator when it judges them stale / too low. Needs empirical testing. Any silent override → false "DO NOT SEND" alerts.
  • Stale nonce races. If the user has a pending tx in mempool between our nonce read and the actual sign, our pin is stale. Ledger Live likely silently refetches.
  • User gas UI. Ledger Live's "Edit gas" button, and users will tap it when a tx stalls. We need explicit "if you edited gas in Ledger Live, this hash WILL NOT match — reject and retry" phrasing, and users must internalize it.
  • Fee volatility. Even a 30-second delay between send_transaction and user approving on the device can desync maxFeePerGas enough that Ledger Live refreshes.
  • Only helps against compromised MCP server. A compromised Ledger Live ignores the pin entirely. Defense-in-depth, not a full guarantee.

Prior context

  • PR Add verifiable transaction preview (swiss-knife + payload-hash proof) #35 dropped the hash-match reminder for EVM after confirming the mismatch on a real Compound supply tx. See feedback_verify_block_ux.md (auto-memory) and the renderAgentTaskBlock / server-level TRANSACTION VERIFICATION paragraph.
  • requestSendTransaction currently forwards only {from, to, data, value} + optional gas hint (src/signing/walletconnect.ts:254-268).

Acceptance criteria (if we decide to build this)

  • Send-time fee + nonce fetch in requestSendTransaction.
  • EIP-1559 RLP pre-sign hash computed and passed through WalletConnect.
  • Expected hash surfaced in the send_transaction response text block with "match this on Ledger" guidance.
  • Explicit "rejected because gas was edited" recovery path documented for the user.
  • Empirical test against a real Ledger Live: does it actually honor all four pinned fields? Capture findings in a decision note.
  • Decision note on whether the UX cost (edit-gas friction, stale-fee stalls) is worth the on-device hash-match.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions