Skip to content

fix: EIP-1559 chainId hashing broken for chainId >= 256#427

Merged
pastaghost merged 4 commits into
keepkey:developfrom
BitHighlander:fix/eip1559-chainid-multibyte
May 23, 2026
Merged

fix: EIP-1559 chainId hashing broken for chainId >= 256#427
pastaghost merged 4 commits into
keepkey:developfrom
BitHighlander:fix/eip1559-chainid-multibyte

Conversation

@BitHighlander

Copy link
Copy Markdown
Collaborator

Problem

ethereum_signing_init() in lib/firmware/ethereum.c hashes the wrong pre-image for EIP-1559 transactions on chains with chainId >= 256 (Base=8453, Arbitrum=42161, Avalanche=43114).

The bug:

// WRONG — hashes only the LSB on little-endian ARM
hash_rlp_field((uint8_t*)(&chain_id), sizeof(uint8_t));

chain_id is a uint32_t. On ARM little-endian, (uint8_t*)&chain_id points to the least-significant byte. For Base (8453 = 0x2105), this feeds 0x05 to keccak instead of the correct 3-byte RLP encoding 0x82 0x21 0x05.

The RLP length calculation already used rlp_calculate_number_length(chain_id) correctly, so the list header was sized for 3 bytes but only 1 byte was hashed — corrupting the keccak pre-image. The resulting signature recovered to a random address with 0 ETH, causing every EIP-1559 transaction on those chains to fail with "insufficient funds".

The legacy EIP-155 signing path (send_signature()) already used hash_rlp_number(chain_id) correctly. Only the EIP-1559 path had this copy-paste bug.

Fix

// CORRECT — strips leading zeros, encodes all chain IDs correctly
hash_rlp_number(chain_id);

One line changed. hash_rlp_number() converts the uint32 to big-endian, strips leading zeros, and calls hash_rlp_field with the correct byte count — consistent with how the legacy path handles it.

Affected chains

Chain chainId Was broken?
Base 8453 Yes — only 0x05 was hashed
Arbitrum One 42161 Yes — only 0x11 was hashed
Avalanche C 43114 Yes — only 0x6a was hashed
Ethereum 1 No (single byte ≤ 0x7F)
Optimism 10 No
BSC 56 No
Polygon 137 No

Test vector (Base, chainId=8453)

Correct EIP-1559 signing pre-image for chainId=8453 must include RLP bytes 82 21 05 for the chain ID field. Before this fix the firmware fed only 05, producing a different keccak hash and a signature that recovers to a wrong address.

hash_rlp_field((uint8_t*)&chain_id, 1) only hashed the least-significant
byte of chain_id on little-endian ARM. For Base (8453=0x2105), this hashed
0x05 instead of the correct 3-byte RLP 0x82 0x21 0x05, producing a wrong
keccak pre-image — the signature recovered to a random address with 0 ETH.

The RLP length calculation already used rlp_calculate_number_length(chain_id)
correctly. The legacy EIP-155 path also used hash_rlp_number correctly.
Only the EIP-1559 hash step was wrong.

Fix: replace hash_rlp_field((uint8_t*)&chain_id, sizeof(uint8_t)) with
hash_rlp_number(chain_id) which strips leading zeros and encodes all
chain IDs correctly.

Affected chains (chainId >= 256): Base (8453), Arbitrum (42161), Avalanche (43114).
Unaffected: ETH (1), OP (10), BSC (56), Polygon (137).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes EIP-1559 transaction signing on chains with chainId >= 256 by ensuring the chain ID is RLP-encoded and hashed correctly in the signing preimage, matching the legacy EIP-155 path’s handling.

Changes:

  • Replace incorrect 1-byte hash_rlp_field((uint8_t*)&chain_id, 1) with hash_rlp_number(chain_id) for EIP-1559 chain ID hashing.
  • Remove outdated comments implying EIP-1559 chain IDs are “only one byte for now.”
  • Add an explanatory comment documenting the endianness/LSB bug and affected chain IDs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/firmware/ethereum.c Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Comment thread lib/firmware/ethereum.c Outdated
Comment on lines +863 to +868
// Arbitrum=42161). For chain_id == 0, hash_rlp_number is a no-op, so
// explicitly hash the single-byte RLP encoding for zero to keep the
// preimage consistent with rlp_calculate_number_length(chain_id).
if (chain_id == 0) {
uint8_t zero_rlp[1] = {0x80};
hash_data(zero_rlp, sizeof(zero_rlp));
Comment thread lib/firmware/ethereum.c Outdated
Comment on lines +861 to +871
// chain_id is a uint32; hash_rlp_number strips leading zeros and encodes
// correctly for all chain IDs including multi-byte ones (Base=8453,
// Arbitrum=42161). For chain_id == 0, hash_rlp_number is a no-op, so
// explicitly hash the single-byte RLP encoding for zero to keep the
// preimage consistent with rlp_calculate_number_length(chain_id).
if (chain_id == 0) {
uint8_t zero_rlp[1] = {0x80};
hash_data(zero_rlp, sizeof(zero_rlp));
} else {
hash_rlp_number(chain_id);
}
EIP-1559 requires a chain_id per the spec. Explicitly reject signing
when chain_id is absent/zero with a SyntaxError — this avoids the
preimage corruption that would result from hash_rlp_number() being a
no-op for 0, and removes the need for the raw 0x80 literal that
Copilot flagged as bypassing the RLP helpers.

After the guard, hash_rlp_number(chain_id) is safe for all valid
chain IDs (>= 1) and no special-casing is needed.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@pastaghost pastaghost merged commit bc8bd3c into keepkey:develop May 23, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants