Skip to content

t8n: Recover transaction senders from their signatures - #1711

Open
chfast wants to merge 1 commit into
masterfrom
t8n/recover-sender
Open

chfast wants to merge 1 commit into
masterfrom
t8n/recover-sender

Conversation

@chfast

@chfast chfast commented Sep 14, 2026

Copy link
Copy Markdown
Member

evmone t8n took each transaction's sender from the JSON "sender" field and never validated the signature: from_json_tx_common reads it, and validate_transaction never touches v, r or s. A transaction whose signature recovers no public key executed as the sender the input named, so no invalid-signature test could be filled with --evm-bin evmone. recover_sender() existed but was wired only to the state test runner ("Recover the signer, as a node does, instead of taking it from JSON") and the blockchain test RLP path.

sender is not part of the t8n input schema: geth's txWithKey adds only secretKey and protected to types.Transaction, whose JSON has no sender key. Checked on one txs.json, the execution specs, geth, erigon, besu, nimbus and ethereumjs all recover; evmone was the only one taking the field. It inherited the read from the GeneralStateTest loader, where transaction.sender is legitimate.

Recovery moves into apply_block(), which already encodes each transaction for its hash. The blockchain test runner opts out, for the reason its own TODO gives.

Two things had to follow:

  • recover_sender() now rejects a v no signature has (a typed v above 1, a legacy one other than 27, 28 or EIP-155's 35 + 2 * chain_id + y_parity). The RLP decoder bounds v already, so this was unreachable before; a transaction read from JSON carries whatever v the input names, and v = 34 recovered a garbage address instead of failing.
  • A legacy transaction's chain id is read from its v instead of --state.chainid. Overwriting it forced tx.chain_id == block.chain_id, making the EIP-155 check in validate_transaction vacuous, and built the wrong signing preimage for a transaction meant for another chain.

The t8n fixtures carried signatures belonging to no sender they name: the pasted (0x468a915f…, 0x5cedae08…) pair recovers to three different addresses over the three payloads it appears on, none of them the 0xa94f5374… they claim. They are re-signed with that account's key, so the allocs are untouched. max_v now uses a valid v = 0xffffffffffffffff (chain id 0x7fffffffffffffee, y parity 0) rather than one that was silently accepted.

Verified: ctest 1307/1307 (the bench cases need the evm-benchmarks submodule). Against EEST's fill at Prague, this branch vs master over 47,425 tests: 0 regressions, 69 fixed, exactly test_bad_v_r_s (60) and the new test_unrecoverable_signature (9) from ethereum/execution-specs#3573. Consumer side unchanged: evmone test passes all 4,898 filled fixture files. EEST needs a matching EvmoneExceptionMapper entry, which is ethereum/execution-specs#3573's to carry.

https://claude.ai/code/session_019wquSXUuvWyx3N6zwNXV2v

Recover each transaction's sender in apply_block(), the way the state test
runner already does, and ignore the JSON "sender" field. A transaction whose
signature recovers no sender is now rejected as
TransactionException.INVALID_SIGNATURE_VRS instead of executing as the sender
the input names. evmone was the only t8n taking that field: the execution
specs, geth, erigon, besu, nimbus and ethereumjs all recover. The blockchain
test runner opts out, for the reason its own TODO gives.

recover_sender() rejects a v no signature has as well (a typed v above 1, a
legacy one other than 27, 28 or EIP-155's 35 + 2 * chain_id + y_parity). The
RLP decoder bounds v already, but a transaction read from JSON carries
whatever v the input names.

A legacy transaction's chain id is read from its v rather than taken from
--state.chainid. Overwriting it made the EIP-155 chain id check vacuous and
built the signing preimage of a transaction meant for another chain wrong.

The t8n fixtures carried signatures belonging to no sender they name, so they
are re-signed with the key of the account funding them.

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.

🟡 Changes recommended

Sender recovery currently applies post-fork signature rules to historical revisions where they are not valid.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Updates evmone t8n to recover transaction senders from signatures instead of trusting JSON input.

Changes:

  • Recovers senders and rejects invalid signatures during block application.
  • Derives legacy chain IDs from EIP-155 v.
  • Adds regression tests and re-signs affected fixtures.
File summaries
File Description
test/utils/t8n.cpp Derives legacy chain IDs from v.
test/utils/blockchaintest_runner.cpp Disables recovery for blockchain fixtures.
test/utils/block_transition.hpp Adds the sender-recovery option.
test/utils/block_transition.cpp Recovers senders before execution.
test/unittests/tooling_t8n_test.cpp Adds signature and chain-ID tests.
test/state/transaction.hpp Documents stricter recovery behavior.
test/state/transaction.cpp Validates v during recovery.
test/integration/evmone-cli/t8n/prague_pre_state_only/txs.json Re-signs the fixture transaction.
test/integration/evmone-cli/t8n/prague_empty_requests/txs.json Re-signs the fixture transaction.
test/integration/evmone-cli/t8n/osaka_blob_schedule/txs.json Re-signs the fixture transaction.
test/integration/evmone-cli/t8n/CMakeLists.txt Updates expected hashes and RLP.
test/integration/evmone-cli/t8n/cancun_create_tx/txs.json Re-signs the fixture transaction.
Review details
  • Files reviewed: 12/12 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.


if (opts.recover_senders)
{
const auto sender = state::recover_sender(tx, txbytes);
@codspeed

codspeed Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 129 untouched benchmarks


Comparing t8n/recover-sender (9add03b) with master (f40021b)

Open in CodSpeed

@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.87234% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.02%. Comparing base (f40021b) to head (9add03b).

Files with missing lines Patch % Lines
test/unittests/tooling_t8n_test.cpp 97.36% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1711      +/-   ##
==========================================
- Coverage   98.02%   98.02%   -0.01%     
==========================================
  Files         179      179              
  Lines       16357    16430      +73     
  Branches     3763     3773      +10     
==========================================
+ Hits        16034    16105      +71     
- Misses        243      244       +1     
- Partials       80       81       +1     
Flag Coverage Δ
eest-develop 82.68% <38.88%> (-0.13%) ⬇️
eest-develop-gmp 26.09% <0.00%> (-0.12%) ⬇️
eest-legacy 17.14% <7.44%> (-0.07%) ⬇️
eest-libsecp256k1 28.35% <0.00%> (-0.13%) ⬇️
eest-stable 82.64% <38.88%> (-0.13%) ⬇️
evmone-unittests 94.35% <97.87%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
core 96.20% <100.00%> (+<0.01%) ⬆️
tooling 94.09% <100.00%> (+0.03%) ⬆️
tests 99.79% <97.36%> (-0.03%) ⬇️
Files with missing lines Coverage Δ
test/state/transaction.cpp 100.00% <100.00%> (ø)
test/state/transaction.hpp 100.00% <ø> (ø)
test/utils/block_transition.cpp 100.00% <100.00%> (ø)
test/utils/blockchaintest_runner.cpp 89.75% <100.00%> (+0.03%) ⬆️
test/utils/t8n.cpp 97.63% <100.00%> (+0.01%) ⬆️
test/unittests/tooling_t8n_test.cpp 99.37% <97.36%> (-0.63%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

2 participants