Skip to content

feat(7.14.2): XRP THORChain memo + EVM depositWithExpiry tests#18

Merged
BitHighlander merged 8 commits into
masterfrom
release/7.14.2-python-keepkey
May 15, 2026
Merged

feat(7.14.2): XRP THORChain memo + EVM depositWithExpiry tests#18
BitHighlander merged 8 commits into
masterfrom
release/7.14.2-python-keepkey

Conversation

@BitHighlander

Copy link
Copy Markdown
Owner

Summary

  • Updates messages_ripple_pb2.py to add memo field (field 7) to RippleSignTx — old-format protobuf descriptor compatible with protobuf==3.20.3
  • Bumps device-protocol submodule to 8f80bcd which adds optional string memo = 7 to the proto
  • Adds test_sign_with_thorchain_memo in test_msg_ripple_sign_tx.py: signs an XRP payment with a THORChain routing memo and verifies the serialized XRPL tx ends with the canonical Memos binary encoding (F9 EA 7D <len> <utf8-memo> E1 F1)
  • Adds test_msg_ethereum_thorchain_deposit.py with three tests for the EVM depositWithExpiry() fix:
    1. Legacy deposit() selector 0x1fece7b4 recognized without AdvancedMode (≥7.5.0)
    2. Modern depositWithExpiry() selector 0x44bc937b recognized without AdvancedMode (≥7.14.2)
    3. Non-THORChain address with depositWithExpiry() selector is still blocked

Context

Part of firmware release 7.14.2:

  • XRP fix: THORChain swap memos must appear in the XRPL Memos array. Without the proto memo field, swaps from XRP to other chains were completely broken.
  • EVM fix: All modern THORChain routers use depositWithExpiry() (selector 0x44bc937b). Firmware pre-7.14.2 only recognized the legacy deposit() selector, causing every EVM→THORChain swap to fail unless AdvancedMode was enabled.

Test plan

  • python -m pytest tests/test_msg_ripple_sign_tx.py::TestMsgRippleSignTx::test_sign_with_thorchain_memo passes on 7.14.2 device
  • python -m pytest tests/test_msg_ethereum_thorchain_deposit.py all 3 tests pass
  • Legacy test test_sign still passes (no regression)
  • PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python python -c "from keepkeylib import messages_ripple_pb2; m=messages_ripple_pb2.RippleSignTx(); m.memo='test'; assert m.memo=='test'" passes

BitHighlander and others added 8 commits May 15, 2026 16:03
… ≤ 7.14.0)

Pairs the device, signs a 1550-byte EIP-1559 transaction with the
all-all-all test mnemonic, and asserts that ECDSA recovery against the
canonical type-2 pre-image yields the device's own address.

Catches a firmware/ethereum.c ordering bug present in 7.x.0 .. 7.14.0
where the empty access-list byte (0xC0) — which closes the EIP-1559 RLP
body and must be the last byte fed to keccak before signing — was being
hashed inside ethereum_signing_init() right after the initial 1024-byte
data chunk, BEFORE the host had a chance to send the remaining
EthereumTxAck frames. For any tx whose data exceeded the single-chunk
threshold, the resulting pre-image was:

  keccak( ...header...
          || data_len_prefix
          || data[0..1024]
          || 0xC0           (bug: should be after ALL data)
          || data[1024..end] )

The signature was mathematically valid for that mangled hash so RPCs
accepted the broadcast, but the recovered signer was a wrong-but-
deterministic address. The mempool dropped the tx because the recovered
"from" had no balance / wrong nonce. Production symptom: every Uniswap
Universal Router swap, Permit2 batch, and large multicall hung at
"Confirm in wallet."

Single-chunk transactions (<= 1024 bytes) escaped the bug only by
accident — the misplaced 0xC0 happened to land at the end anyway.

Recovery-based assertion (eth-keys, eth-utils.keccak) — works on any
seed, no golden vectors to capture, the test asserts the actual
invariant: "signature recovers to the signer." Fails on broken
firmware, passes on 7.14.1+.

CI: eth-keys added to the existing pip install line; ships a pure-Python
keccak via eth-utils so no native deps are required.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
requires_message("EthereumTxAck") sends an empty EthereumTxAck as a
discovery probe. The firmware (correctly) rejects that with
Failure_UnexpectedMessage because we're not mid-sign, which skips the
test before the actual assertion runs.

requires_firmware("7.2.1") is sufficient — EthereumTxAck has been part
of the protocol since EIP-1559 support landed in 7.2.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
eth-utils ships keccak via the eth-hash adapter, which auto-selects
between pycryptodome and pysha3 at import time. Without either backend
installed, importing keccak raises:

  ImportError: None of these hashing backends are installed:
  ['pycryptodome', 'pysha3'].

The new EIP-1559 chunked-data regression test imports keccak from
eth_utils to build the canonical type-2 pre-image, so it failed at
import rather than at the recovery assertion. Adding pycryptodome to
the existing pip-install line fixes it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
KeepKeyTest overrides unittest's assertEqual with a 2-arg version
(common.py:104) that doesn't accept the optional msg parameter — passing
one raises:

  TypeError: KeepKeyTest.assertEqual() takes 3 positional arguments
  but 4 were given

Print the regression diagnostic before asserting instead. Pytest captures
stdout on failure, so the divergence (expected vs recovered, canonical
hash, sig values) still surfaces in the failure report.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Upstreaming this test as a permanent regression guard rather than a
one-shot bug catcher. Bumping requires_firmware from 7.2.1 (the version
where EIP-1559 support originally landed) to 7.14.1 (the first version
where the access-list ordering bug is fixed) so CI on broken builds
skips this test instead of flagging a known-broken state as a new
regression.

The header comment already documents the affected range
(7.x.0 .. 7.14.0) and the fix landing in 7.14.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gnition

- Bump device-protocol submodule to 8f80bcd (adds memo field to RippleSignTx)
- Update messages_ripple_pb2.py with memo field (field 7, optional string)
  compatible with protobuf==3.20.3 (old-format serialized_pb descriptor)
- Add test_sign_with_thorchain_memo in test_msg_ripple_sign_tx.py:
  verifies serialized XRPL tx ends with canonical Memos array binary
  (F9 EA 7D <len> <memo> E1 F1), requires firmware 7.14.2
- Add test_msg_ethereum_thorchain_deposit.py:
  covers legacy deposit() 0x1fece7b4 selector, new depositWithExpiry()
  0x44bc937b selector (requires 7.14.2, no AdvancedMode), and verifies
  non-THORChain addresses are still blocked without AdvancedMode
@BitHighlander
BitHighlander force-pushed the release/7.14.2-python-keepkey branch from 8159a7f to 297cba3 Compare May 15, 2026 19:04
@BitHighlander
BitHighlander merged commit bf870e6 into master May 15, 2026
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.

1 participant