Identify EVM revisions by name rather than by number#1586
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates evmone-cli to accept EVM revision names (e.g. London, Osaka) instead of enum-backed numeric values for run --rev, aligning it with the existing t8n --state.fork approach and improving UX stability across newly inserted revisions.
Changes:
- Switch
evmone-cli run --revfromevmc_revision(numeric parsing) to a string resolved viaevmone::test::to_rev(). - Update CLI help text to clarify
--revexpects a revision name. - Remove explicit numeric initializers from
evmc_revisionenumerators.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| tools/evmone/main.cpp | Makes run --rev accept revision names (string) and resolves them via to_rev(). |
| evmc/include/evmc/evmc.h | Removes explicit numeric initializers from evmc_revision enumerators. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1586 +/- ##
=======================================
Coverage 97.41% 97.41%
=======================================
Files 163 163
Lines 14677 14681 +4
Branches 3404 3404
=======================================
+ Hits 14297 14301 +4
Misses 280 280
Partials 100 100
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
evmone-cli's `run --rev` was bound to the evmc_revision enum, so CLI11 parsed it as the underlying integer (e.g. `--rev 15` for Amsterdam) — an opaque number that also shifts as revisions are inserted. Bind --rev to a string resolved through the existing evmone::test::to_rev() helper, mirroring the t8n `--state.fork` path; the default comes from evmc::to_string() and prints as [Osaka], and an unknown name now fails with "unknown revision: <name>". Drop the explicit = 0..16 values from the evmc_revision enum so revisions are no longer advertised by number. The enumerators keep their order, leaving the values and ABI unchanged and EVMC_MAX_REVISION / EVMC_LATEST_STABLE_REVISION symbolic. Claude-Session: https://claude.ai/code/session_0124TpiAtCnqTGx6uannKYcZ
c233a9a to
d8bd426
Compare
chfast
added a commit
that referenced
this pull request
Jul 14, 2026
The create address was computed host-side (Host::prepare_message), one
EVMC crossing too late: msg.recipient traveled down empty and was
filled midway, the EIP-2681 nonce-overflow light-fail lived in the host
while the depth and balance light-fails lived in the VM, the EIP-2929
warming happened host-side where EELS does it in the creating frame,
and the parent could learn what it created only from
evmc_result.create_address.
Compute the address in create_impl instead: read the sender's nonce via
get_nonce (unifying the EIP-2681 light-fail with depth/balance), derive
the CREATE/CREATE2 address, warm it in the creating frame (EELS:
generic_create), and pass it down in msg.recipient - the same
VM-resolved-message pattern as code_address for EIP-7702 delegations.
This leaves evmc_result.create_address and evmc_message.create2_salt
without readers: drop both and bump EVMC_ABI_VERSION to 16.
Host::prepare_message dissolves. The creator-nonce bump stays in
Host::call before the state checkpoint: per the Yellow Paper it
survives a failed creation. At depth 0, build_message computes the
deployment address from tx.nonce.
Behavior-identical: the EEST glamsterdam-devnet@v7.2.0 state-test
failure set is bit-identical to master's.
Claude-Session: https://claude.ai/code/session_01R8uyb9z8VwgRrEKRanDPP8
Move MAX_NONCE constant to constants.hpp (#1592)
Move the create address computation to lib/evmone (#1591)
compute_create_address() and compute_create2_address() are defined in
the testing host (test/state/host.{hpp,cpp}) although they are not
Host-specific, and the VM itself will need them once it computes the
create address instead of the Host (#1589).
Move them to the new TU lib/evmone/create_address.{hpp,cpp} in the
evmone namespace. The implementations are unchanged except the
keccak256() host-utils wrapper being replaced by the direct
ethash::keccak256() call. The functions are marked EVMC_EXPORT so the
testing code can still reach them across the libevmone shared-library
boundary.
state: Simplify `compute_create_address()` (#1590)
state: Don't journal warm storage accesses (#1585)
`access_storage()` journaled every storage access, including warm ones
where nothing changes. Warm SLOADs are among the most frequent
operations and each appended a ~90-byte journal entry, growing the
journal by a large number of no-op entries per storage-heavy
transaction, all replayed on rollback.
Journal in `access_storage()` only when the slot status actually changes
(cold access) and journal the value change in `set_storage()` instead
(previously pre-Berlin only).
evmc: Add get_nonce() to the host interface (#1588)
Upcoming work computes the CREATE address in the VM, making the creating
frame the first VM-side consumer of the sender's nonce. A message field
cannot carry it - a reentrant frame creating from the same account bumps
the nonce mid-frame - so it is a live query like get_balance.
Bump EVMC_ABI_VERSION to 15.
evmc: Remove the Constantinople revision (#1587)
It never activated on Mainnet (superseded by Petersburg after the
EIP-1283 issue) and no live testnet runs it any more. Bump
EVMC_ABI_VERSION and retarget its opcodes to Petersburg. Strip the lone
Constantinople case from the legacy blockchain-test fixtures in CI.
Identify EVM revisions by name rather than by number (#1586)
evmone-cli's `run --rev` was bound to the evmc_revision enum, so CLI11
parsed it as the underlying integer (e.g. `--rev 15` for Amsterdam) — an
opaque number that also shifts as revisions are inserted. Bind --rev to
a string resolved through the existing evmone::test::to_rev() helper,
mirroring the t8n `--state.fork` path; the default comes from
evmc::to_string() and prints as [Osaka], and an unknown name now fails
with "unknown revision: <name>".
Drop the explicit = 0..16 values from the evmc_revision enum so
revisions are no longer advertised by number. The enumerators keep their
order, leaving the values and ABI unchanged and EVMC_MAX_REVISION /
EVMC_LATEST_STABLE_REVISION symbolic.
test: Name the shared RLP framing constants (#1580)
Introduce the RLP prefix bases (0x80 for strings, 0xc0 for lists) and
the short-form length limit (Yellow Paper Appendix B) as named
constants, replacing the magic numbers in the length encoder. A short
item's prefix is base + length, matching the encoder's ShortBase
template parameter and the spec's "value 0x80 plus the length" wording;
the long-form base (base + SHORT_LENGTH_LIMIT) is derived where used, so
only the two short bases need naming. The transaction decoder added next
reuses these.
The constants live in test/state/rlp_common.hpp because later we will
introduce RLP decoder there.
Extract shared frame-exit result construction (#1578)
Use shared procedure for frame exit in Baseline and Advanced.
ci: Switch the EEST devnet fixtures to glamsterdam-devnet (#1577)
The bal@v5.6.1 fixtures are superseded by the glamsterdam-devnet
release, which is published in ethereum/execution-specs instead of
ethereum/execution-spec-tests. Point the existing run at
tests-glamsterdam-devnet@v6.1.0 and add a repo parameter to
run_execution_spec_tests to select the source repository.
for_amsterdam/* and for_bpo2toamsterdamattime15k/* stay filtered out
(Amsterdam is not implemented yet); the rest pass on master: 8069 state
and 8518 blockchain tests.
test: Change spelling to "TangerineWhistle" and "SpuriousDragon" (#1576)
Spell fork names without space to match execution-specs:
- "Tangerine Whistle" → "TangerineWhistle",
- "Spurious Dragon" → "SpuriousDragon".
This allows us to execute new test suite for these forks in next release
of tests in execution-specs. Also handy for `evmone t8n`.
We decided not to support old spelling as this fork name is old and not
very user-facing.
Implement EIP-7708: "ETH transfers emit a log" (#1573)
In all cases where ETH transfer happens in EVM emit LOG3 "Transfer"
matching smart contract ABI. These cases are:
- CALL with value,
- CREATE with endowment,
- SELFDESTRUCT sending all to beneficiary. The log is omitted when the
transfer value is zero or the recipient is the sender.
test: Add a logs expectation to the state_transition fixture (#1574)
The state_transition fixture could assert state, gas, and trace, but not
emitted logs. Add an optional Expectation::logs, compared field-by-field
(count, address, data, topics, and order) in the runner, with a LOG1
smoke test pinning the mechanism.
Implement EIP-8246: "Remove SELFDESTRUCT Burn" (#1572)
This EIP removes the "burn" feature of SELFDESTRUCT. If a contract tries
to burn ETH the balance is preserved.
https://eips.ethereum.org/EIPS/eip-8246
test: Load transaction signature v as uint64 (#1571)
A legacy EIP-155 transaction encodes `v` as chainId*2 + 35 + parity, so
`v` exceeds 0xff for any chainId >= 110 (e.g. chainId 300 gives
v=0x27c). The state-test loader narrowed `v` to `uint8_t` and
`Transaction::v` was `uint8_t`, so `from_json<uint8_t>` threw `value >
0xFF` and `evmone t8n` rejected such transactions while geth `evm t8n`
executes them.
Widen `Transaction::v` and its loader to `uint64_t`, matching the
`chainId` fix (#1570). `v` is only RLP-encoded for the transaction hash,
so the wider type is encoded correctly with no other behavior change.
Fixes #1487.
test: Load transaction chainId as uint64 (#1570)
from_json_tx_common parsed the transaction "chainId" with
from_json<uint8_t>, throwing `from_json<uint8_t>: value > 0xFF` for any
chain ID above 255. This crashed `evmone t8n` on common networks such as
Sepolia (11155111) and Arbitrum One (42161): the transaction is parsed
before t8n overrides chain_id from its args, so the parse failed first
and no output was produced.
Parse it as uint64_t, matching the Transaction::chain_id field type.
Fixes #1569.
evmone 0.22.0
Bump version: 0.21.0 -> 0.22.0
changelog: List changes for 0.22 release (#1568)
evmc: Bump ABI version to 13 (#1567)
The ABI changed incompatibly since EVMC 12 (evmone 0.21.0) while the
version number stayed at 12, so a host built against ABI-12 headers
would load the new VM cleanly and then read structs at wrong offsets:
- evmc_tx_context layout changed: the TXCREATE initcodes fields were
removed (#1514) and block_slot_number was added for EIP-7843 (#1517).
- evmc_result lost the reserved optional-data storage (#1529).
- EVMC_EOFCREATE was removed from evmc_call_kind, freeing value 5 for
future reuse (#1515).
- The EVMC_AMSTERDAM revision was added (#1508).
This is the first ABI bump since EVMC was merged into evmone, so the
version no longer tracks the standalone EVMC project's major version;
the doc comment is updated accordingly.
crypto: Migrate ExtPoint to AffinePoint<E2> (#1547)
Use `AffinePoint<ExtFieldElem>` instead of `ExtPoint` (now removed).
This consolidates and de-duplicates code.
crypto: Use hex literals in bn254 FROBENIUS_COEFFS and make it constexpr (#1565)
Hex _u256 literals load directly instead of the costly compile-time
decimal parse, halving pairing.cpp compile cost (~35G->15.5G
instructions, ~4.5s->2.2s). This also fits the array under
-fconstexpr-steps, so promote it to `inline constexpr` (was `static
inline const` + consteval make_fq2).
ci: Upgrade codecov orb to 6.0.0 (#1566)
The 5.4.3 orb fetched the Codecov PGP public key from Keybase to
validate the CLI binary; Keybase is gone, so the "(Codecov) Validate
CLI" step failed with "gpg: no valid OpenPGP data found" and broke every
coverage-uploading job. Orb 6.0.0 ships wrapper 0.2.9, which embeds the
PGP key instead of fetching it. Upload parameters are unchanged (6.0.0
only adds a backward-compatible cli_type, default codecov-cli).
test/bench: Avoid -Winvalid-constexpr on gcc-16 in bench helpers (#1563)
Workaround -Winvalid-constexpr false-positive in GCC 16 builds.
crypto: Use local accumulator t[] in mul_amm_256 to avoid aliasing (#1477)
Use a local array t[4] instead of writing directly to the output span r,
which may alias the inputs x or y. This allows the compiler to keep the
accumulator in registers without reloading after stores. The result is
copied to r at the end.
~6% speedup on 256-bit modexp benchmarks (25846 → 24256 cycles).
test: Support matching multiple tx-level exception names in blockchaintest (#1561)
ci: Upgrade macOS executors (#1562)
Upgrade "latest" Xcode to 26.5 (now m4pro executors are available).
Upgrade "min" Xcode to 16.4.0 (16.3.0 is not available any more).
test: Extract shared apply_block from blockchaintest and t8n (#1558)
The blockchain test runner and the t8n tool each carried their own block
transition loop. Move the runner's `apply_block` into common
gtest-free `test/utils/block_transition.{hpp,cpp}` used by both.
test: Match block requests and transaction exceptions in blockchaintest (#1560)
The invalid-block branch silently accepted any transition failure
without checking it against the fixture's `expectException`. Verify
those reasons:
- Requests collection failures map to distinct exceptions
(`INVALID_DEPOSIT_EVENT_LAYOUT`, `SYSTEM_CONTRACT_EMPTY`,
`SYSTEM_CONTRACT_CALL_FAILED`); `system_call_block_end` now returns the
specific error code, which t8n also reports.
- A rejected transaction is required to match a `TransactionException.*`
fixture (it stops block processing immediately).
test: Fix block_reward type to optional<uint64_t> (#1559)
test: Match blockchain test exceptions (#1556)
The blockchain test runner treated block validity as a bool: for a block
expected to be invalid it accepted ANY rejection and never checked that
the reason matched the fixture's `expectException`. So a block rejected
for the wrong reason still passed.
Make `validate_block` return a `std::error_code` instead of `bool`, with
new block-level entries appended (at the back, keeping existing values
stable) to `state::ErrorCode`. Each entry's message is the matching
execution-spec-tests `BlockException` constant (INVALID_GASLIMIT,
INVALID_BASEFEE_PER_GAS, INCORRECT_EXCESS_BLOB_GAS,
RLP_BLOCK_LIMIT_EXCEEDED, INVALID_BLOCK_TIMESTAMP_OLDER_THAN_PARENT,
INCORRECT_BLOCK_FORMAT for the rest). The loader captures the
`expectException` string; the runner substring-matches the actual
error's constant against it, which also handles the fixtures'
`|`-separated alternatives.
Legacy ethereum/tests fixtures use older, sometimes finer-grained
exception names (e.g. uncles after Paris, a number gap, a wrong base
fee). The loader maps those (many-to-one) onto the `BlockException`
constant evmone reports, so they match by reason as well.
test: Set blob gas budget from the protocol max (#1555)
So far we were pre-validating the block header blob_gas_used against the
protocol max. This was correct, but hid expected per-tx blob gas limit
exceeded errors.
Changed behavior: start with the protocol max and validate each tx.
Compare the block header value at the end. The block validity outcome is
unchanged; only the reported error differs (a per-tx blob gas error
instead of a block-level one). Matches EELS and other implementations.
tools: Fold evmone-t8n into the evmone CLI as a subcommand (#1551)
Replace the standalone evmone-t8n binary with `evmone t8n`. The
standalone executable is removed.
The command line options are the same, but now parsed by CLI11,
which makes validation strict at parse time:
- --state.reward range-checked via CLI::Range(-1, INT64_MAX); -1
remains the "emit pre-state only" sentinel.
- --input.alloc/env/txs/blobParams must exist (CLI::ExistingFile);
previously the standalone binary would open and read an empty/
failed stream.
- Unknown options now error; previously the hand-rolled if-else
chain silently ignored them.
tracing: Keep copy of output path in InstructionCounter (#1554)
Previously used string_view for output path causes lifetime issues for
users: the tracer outputs to file during destruction.
tooling: Restore vm param in t8n() (#1553)
This is useful for later unification of the evmone CLI commands.
state: Check deploy prefix 0xEF before gas cost (#1552)
When checking new code deployment requirements, check the forbidden 0xEF
prefix before checking the by-byte deployment cost. This help with
future EIP-8037 implementation.
test: Add recent Mainnet ecpairing samples to bench (#1550)
Live mainnet at block ~25,185,275 (May 2026) is overwhelmingly 2-pair
PLONK-style verifier calls — sampling 15 distinct inputs across a
26-block window gave 14 × 2-pair + 1 × 4-pair (~93/7). The existing
bench was 4 × 2-pair + 6 × 4-pair, which over-weighted the rarer case.
Append 10 recent on-chain samples (9 × 2-pair + 1 × 4-pair) to balance
the input set. Source: debug_traceBlockByNumber via drpc.org; per-input
txhash and block height recorded in code comments. Total inputs: 20 (13
× 2-pair + 7 × 4-pair = 54 pairs).
crypto: Precompute Miller-loop lines for G2 generator in KZG verify (#1549)
It turned out the `blst_aggregated_in_g1` doesn't use precomputed lines
for G2 generator point. Do it ourselves.
crypto: Fix incorrect docstrings in BN254 and KZG pairing helpers (#1548)
- pairing.cpp: the sparse Fq12 line value matrix had [1][1] = t[0], but
the body of multiply_by_lin_func_value uses t[2]. Symbolic expansion
(treating v = t[0]·y + t[1]·x·w + t[2]·v·w) confirms t[2] is correct.
- kzg.cpp: blst_aggregated_in_g1 was claimed to use precomputed
Miller-loop lines for the G2 generator. It does not: it calls
miller_loop_n(ret, &BLS12_381_G2, sig, 1) on the fly. Reword the comment
and add a TODO to precompute G2_GEN lines, matching the existing [s]₂
path.
crypto: Migrate BN254 pairing internals to AffinePoint<Curve> (#1546)
The pairing module's affine point arguments used the legacy untyped
ecc::Point<T> while the rest of the precompile already uses the
curve-parameterised ecc::AffinePoint<Curve>. Migrate Point<> to
AffinePoint<>.
crypto: Replace JacPoint with ProjPoint (#1545)
Remove the duplicate JacPoint<ValueT> type in favor of ProjPoint<Curve>,
which has a richer API (zero comparison, to_affine conversion,
AffinePoint constructor).
To support the BN254 G2 pairing code (which operated on JacPoint<Fq2>),
introduce TwistCurve — a minimal curve definition with Fp=Fq2 and A=0 —
so ProjPoint<TwistCurve> replaces JacPoint<Fq2>.
Also change ProjPoint's default y initializer and constructor from FE{1}
to FE::one(), which works for both prime field FieldElement and
extension field ExtFieldElem types.
Also add ProjPoint::from(const Point<FE>&) as the JacPoint::from analog,
used at the call sites that still hold legacy Point<T>-typed inputs.
This factory becomes redundant once those call sites migrate to
AffinePoint<Curve> (separate follow-up).
crypto: Optimize line evaluation in BN254 pairings (#1541)
Algebraic simplifications in the Jacobian add + line evaluation,
all recovering an intermediate reuse that was implicit in the original
reference but lost when the procedure was re-derived to fix the paper's
typos.
crypto: Trivial reuse of computed values in pairing helpers (#1542)
This reuses already computed terms in trivial cases in pairing helpers.
91.4 → 92.2 (+0.83%) Mgas/s
precompiles: Add Fq2 sqr() and use it in BN254 pairing helpers (#1539)
precompiles: Precompute Miller loop lines for KZG trusted setup point (#1537)
Precompute the Miller-loop lines for the trusted-setup point [s]₂
and use these (instead of the point) for faster KZG pairing check.
The precomputation has been done outside and only the result is stored.
The static data size is ~19 KB.
build: Fix missing intx::intx dependencies (#1540)
crypto: Refactor pairing Fp coefficients unpacking (#1538)
precompiles: Optimize KZG point evaluation via joint G1 MSM (#1536)
Rearrange the standard KZG verification equation
e(C - [y]₁, [1]₂) =? e(π, [s - z]₂)
into
e(C + [z]π - [y]₁, [1]₂) =? e(π, [s]₂)
by moving the [z] factor from G2 to G1 via the pairing's bilinearity.
The new form:
- eliminates the G2 scalar multiplication entirely (was [z](-G2_gen)),
- eliminates the G2 point addition (was [s]G2 - [z]G2),
- merges the two remaining G1 scalar multiplications ([z]π and [y]₁)
into a single 2-point Pippenger MSM,
- keeps both G2 pairing arguments as fixed constants ([1]₂ via
blst_aggregated_in_g1's precomputed lines and the [s]₂ point from
the trusted setup).
G2_GENERATOR_NEGATIVE and the blst_p2 mult/add helpers are no longer
needed. G1_GENERATOR_NEGATIVE is redeclared as blst_p1_affine since
the Pippenger MSM consumes affine points.
test/utils: Extract evmone::tooling::t8n() from evmone-t8n main (#1535)
Move the state-transition body of evmone-t8n's main() into a reusable
evmone::tooling::t8n() helper next to run.cpp in the testutils library.
The function operates only on std::istream/std::ostream and a small
config struct; opening files and assembling the per-tx trace filename
remain main()'s responsibility.
T8NArgs collects all configuration and I/O in one place:
- non-owning raw std::istream*/std::ostream* for inputs and outputs
(TODO(C++26): switch to std::optional<stream&>),
- an open_trace callback (std::function<std::ostream&(size_t, const
evmc::bytes32&)>) invoked once per executed transaction so the caller
controls the per-tx trace sink without t8n() touching the filesystem,
- opcode_count_file kept as std::string until a follow-up makes the
VM-side option filesystem-free.
A small StreamRedirect RAII guard scopes the std::clog rdbuf swap to
each transaction's execution so the original buffer is restored on
exception as well as normal return. evmone-t8n's main shrinks from 326
to 127 lines and constructs the VM itself; testutils therefore does not
pick up a link dependency on evmone.
Add an integration test (cancun_create_tx_trace) exercising --trace,
asserting both the original trace-<i>-0x<hash>.jsonl filename and the
recorded opcode sequence.
precompiles: Upgrade blst to v0.3.16 (#1534)
BLST v0.3.16 now tolerates points-at-infinity in multi-scalar muls.
test/utils: Rename evmone::test::run to evmone::tooling::run (#1533)
The run() helper is the body of the evmone-cli `run` command, not test
infrastructure. Move it into a dedicated evmone::tooling namespace so
the CLI-tool helpers are separated from evmone::test (test fixtures,
loaders, bytecode DSL). Sets the stage for extracting t8n() next to it.
Only one call site (tools/evmone/main.cpp) needed updating; the unit
test keeps `using namespace evmone::test` for the bytecode DSL and adds
`using namespace evmone::tooling` alongside.
tools: Replace evmc::tooling with evmone::test::run (#1531)
evmone-cli's `run` subcommand was the sole evmone consumer of
evmc::tooling::run from the vendored EVMC. Port the helper into
evmone::testutils as evmone::test::run so it lives alongside the rest of
evmone's test infrastructure, then drop the now-unused evmc::tooling
target (tooling.hpp, run.cpp, lib/CMakeLists.txt hook) entirely.
Same MockedHost + optional --create / --bench flow, just relocated. The
implementation is kept near-byte-identical to the upstream EVMC copy
(with `using namespace evmc;` substituting for the implicit visibility
of `evmc::` names inside `evmc::tooling`) so git diff detects it as a
rename. evmone.testutils gains a PRIVATE dep on evmc::mocked_host (used
only inside run.cpp; no testutils header exposes it), and tools/evmone
now links evmone::testutils instead of evmc::tooling.
build: Move evmone::testutils under EVMONE_TOOLS (#1532)
evmone::testutils (and its dependency evmone::state) lived under the
EVMONE_TESTING umbrella, but evmone-cli — a tool installed as the
`evmone` binary — will start linking testutils so it can reuse the
ported MockedHost-based `run` helper. The current default
EVMONE_TOOLS=ON / EVMONE_TESTING=OFF top-level build would then fail to
resolve the testutils target.
Hoist add_subdirectory(test/state), add_subdirectory(test/utils), and
the nlohmann_json hunter package from test/ to the top-level
`if(EVMONE_TOOLS)` block, and remove the corresponding lines from
test/CMakeLists.txt. EVMONE_TESTING now implicitly requires EVMONE_TOOLS
— the explicit combo `-DEVMONE_TESTING=ON -DEVMONE_TOOLS=OFF` fails at
configure time because the test targets reference evmone::testutils
which is no longer in scope. The common case is unaffected (top-level
dev clones default EVMONE_TOOLS to ON via PROJECT_IS_TOP_LEVEL).
test/blockchaintest: Assert lastblockhash matches canonical chain tip (#1530)
The blockchain-test fixture's top-level `lastblockhash` field was loaded
into `BlockchainTest::Expectation::last_block_hash` but never compared
against the runner's computed canonical chain tip. A runner that picks
the wrong block as canonical (e.g. wrong total_difficulty comparison on
an ommer-heavy chain) would silently pass.
Track the expected hash of the canonical tip alongside the existing
`canonical_state` pointer (initialized from the genesis hash, updated
when total_difficulty advances) and assert it equals
`expectation.last_block_hash` after the block loop.
evmc: Drop evmc_result optional storage (#1529)
Remove the API for evmc_result optional storage feature. EVM could use
this to pass a handle to internal objects from internal calls, but this
is currently unused by evmone.
ci: Tune sanitizers (#1528)
test: Refactor create-tx with EOF validation test (#1527)
Implement EIP-7778: Block Gas Accounting without Refunds (#1526)
Per EIP-7778, the block-level `gas_used` accumulator is the gas spent
*before* refunds (still floored), while the user keeps paying the
post-refund amount:
user_pays = max(tx_gas_used - refund, min_cost) // unchanged
block.gas_used += max(tx_gas_used, min_cost) // EIP-7778
https://eips.ethereum.org/EIPS/eip-7778
`TransactionReceipt` gains a `gas_refund` defined as the difference
between the EIP-7778 block-level contribution and `gas_used`, so the
runner can derive the contribution as `gas_used + gas_refund` without
needing the per-tx floor. The non-trivial part of the definition is the
floor edge case: when the floor binds upward (pre-refund < floor) or
sideways (refund pushes user cost below floor), `gas_refund` is clipped
so the identity still holds.
The blockchain-test runner uses that contribution for `block_gas_left`
and the header `gas_used`. Receipt `cumulative_gas_used` keeps tracking
the user cost (matching geth/erigon receipt semantics post-EIP-7778).
build: Add PCH for evmone-unittests (#1525)
The unittests target re-parses the same external headers in 70+ TUs.
Cumulative parse time across the target was ~780s (gtest 183s, evmc/evmc
101s, evmc/hex 71s, intx 70s).
Precompile the four external headers used in nearly every TU. Clean
rebuild measured on this machine (-j32, GCC 14, Release):
- baseline: 24.1s wall, 308s CPU
- with PCH: 20.0s wall, 233s CPU (-17% wall, -24% CPU)
Wider PCH sets (adding mocked_host, gmock, nlohmann/json, bytecode.hpp)
were tested but regressed GCC: the .gch file grows past 100MB and the
per-TU load cost outweighs the parse savings on TUs that do not need
those headers. The minimal external-only set is the sweet spot.
Implement EIP-7981: Increase Access List Cost (#1524)
Charge a flat surcharge of 64 gas per access-list byte on top of the
existing EIP-2930 access-list cost.
The surcharge applies from Amsterdam and is added to both the regular
intrinsic gas and the EIP-7976 floor, preventing access lists from being
used to circumvent the EIP-7623 floor pricing.
https://eips.ethereum.org/EIPS/eip-7981
state: Refactor tx intrinsic gas cost computation (#1523)
build: Upgrade google/benchmark to v1.9.5 (#1522)
build,ci: Enable _LIBCPP_REMOVE_TRANSITIVE_INCLUDES for libc++ (#1521)
Implement EIP-7976: Increase Calldata Floor Cost (#1519)
Raise TOTAL_COST_FLOOR_PER_TOKEN from 10 to 16, and count every calldata
byte (including zero bytes) as 4 tokens in the floor calculation. This
yields a 64-gas floor cost per calldata byte. Regular intrinsic gas is
unchanged (zero bytes still cost 4 gas via DATA_TOKEN_COST * 1 token).
https://eips.ethereum.org/EIPS/eip-7976
test: Add blob params for Amsterdam (#1520)
Unify the test::get_blob_params() to return reasonable value for each
revision. Useful for new unit tests.
Implement EIP-7843: SLOTNUM opcode (#1517)
Add SLOTNUM instruction (opcode 0x4b) that returns the beacon chain slot
number. Gas cost: 2. Available since Amsterdam.
https://eips.ethereum.org/EIPS/eip-7843
ci: Run clang-format check on evmc/ (#1518)
The find roots in the format check excluded evmc/, so formatting issues
there went undetected. Add evmc/ to the list and reformat existing
offenders.
CodeAnalysis: merge raw_code/executable_code into code() (#1516)
evmc: Drop EOFCREATE (EOF leftover) (#1515)
evmc: Drop tx initcodes (TXCREATE leftover) (#1514)
ci: Don't run legacy state tests (#1512)
Don't run legacy state tests because we later want to use some new JSON
test features available only in EEST files.
The lost coverage is minimal and also covered by internal unit tests.
ci: set LLVM_PROFILE_FILE per run in ethereum-tests job (#1513)
The job ran evmone-statetest and two evmone-blockchaintest invocations
back-to-back without setting LLVM_PROFILE_FILE. Each instrumented binary
wrote to the default location (default.profraw), so every subsequent run
truncated the previous one. By the time collect_coverage_clang merged
*.profraw, only the last invocation's profile remained — the State tests
coverage was silently lost.
Give each run its own profile file so all three survive the merge.
Add missing inline keyword to always_inline functions (#1510)
GCC requires both [[gnu::always_inline]] and the inline keyword,
otherwise it warns "function might not be inlinable". The functions were
already being inlined.
This was silenced by the -Wno-attributes warning, now tuned to ignore
only attributes with known vendor prefixes.
crypto: Restrict conjugate() to degree-2 extension fields (#1485)
The conjugate operation (negating the second coefficient) is only
mathematically correct for degree-2 extensions. Add a requires clause to
prevent misuse on degree-3+ types where it would produce incorrect
results silently.
Implement DUPN/SWAPN/EXCHANGE (EIP-8024) (#1429)
Implements the DUPN (`0xe6`), SWAPN (`0xe7`), and EXCHANGE (`0xe8`)
stack instructions for the Amsterdam revision as specified in
[EIP-8024](https://eips.ethereum.org/EIPS/eip-8024)
([`c0aa8d7`](https://github.com/ethereum/EIPs/commit/c0aa8d79038e0bf60ab8f15ff32ef17ba3b80882)).
Co-authored-by: Paweł Bylica <pawel@hepcolgum.band>
Extract SWAP instruction helper (#1509)
Add Amsterdam EVM revision (#1508)
Register EVMC_AMSTERDAM for the Amsterdam revision (EIP-7773).
https://eips.ethereum.org/EIPS/eip-7773
ci: Run EEST "develop" (#1507)
Add another EEST run in CircleCI. It reuses previous scripting. This
time we take the latest BAL fixture and run all tests not for Amsterdam.
test: Add push1_stack_overflow test (#1506)
Verify that 1024 PUSH1 instructions fill the stack successfully and 1025
PUSH1 instructions trigger EVMC_STACK_OVERFLOW. This is a regression
test for a bug where instructions with immediates (like PUSH1) could
bypass the stack overflow check.
evmone 0.21.0
Bump version: 0.20.0 -> 0.21.0
evmc: Remove redundant build configuration (#1505)
Now that EVMC is part of the evmone project, its CMakeLists.txt no
longer needs its own project(), compiler settings, Cable includes, or
build type configuration — all provided by the root CMakeLists.txt.
evmc: Remove loader library and old evmc tool (#1504)
The EVMC loader (dynamic VM loading) has no remaining users after
evmone-bench dropped external VM support and evmone-cli replaced the
evmc tool with direct linking.
tools: Remove external EVMC support in evmone-bench (#1503)
Drop the EVMC loader dependency and the 3-argument CLI mode that loaded
an external EVMC VM. The benchmarks now only use the built-in VMs.
tools: Add evmone CLI tool (#1502)
Replace the evmc tool with evmone-cli that links evmone directly instead
of loading it dynamically via the EVMC loader. The tool is guarded by
the new EVMONE_TOOLS option (default: PROJECT_IS_TOP_LEVEL).
The --vm option is removed; --trace and --histogram flags are added as
direct replacements for the loader's comma-separated option syntax.
CLI11 dependency is moved from the root CMakeLists.txt into tools/ and
test/ subdirectories.
build: Bump minimum CMake version to 3.22 (#1501)
This enables built-in PROJECT_IS_TOP_LEVEL (since 3.21), removing the
manual fallback in CableCompilerSettings. Remove redundant
cmake_minimum_required from the vendored evmc.
Merge EVMC git submodule into evmone project (#1499)
The [EVMC] is not maintained any more and will not have support for
future EVM revisions. In evmone however we want to keep evolving parts
of it to have future EVM features and revisions. Later, it may
completely blend with evmone, but for now keep it as is to limit
disturbance for users.
This replaces the evmc git submodule with the subset of its files
and their history used by evmone.
The EVMC_INSTALL cmake option has been removed.
[EVMC]: https://github.com/ethereum/evmc
Remove unused build configuration
Remove Hunter package manager, install targets, examples/tests options,
and instructions library not used when EVMC is embedded in evmone.
This is in preparation for merging the EVMC project into evmone.
The EVMC_INSTALL cmake option has been removed.
build: Add explicit CLI11 dependency (#1500)
CLI11 was previously fetched transitively by evmc's Hunter setup. Move
hunter_add_package(CLI11) to evmone's root CMakeLists (before evmc) and
link CLI11::CLI11 to test targets that use it.
test: Don't test evmone with evmc-vmtester (#1498)
docs,build: Remove outdated mention of evm-test (#1497)
evmone 0.20.0
Bump version: 0.19.0 -> 0.20.0
crypto: Hardening asserts, noexcept fixes, and safety checks (#1491)
- ecc: Assert 2-torsion (y=0) is unreachable in add_affine() for
prime-order curves.
- bls: Remove incorrect noexcept from g1_msm/g2_msm (they allocate via
std::vector/make_unique which may throw std::bad_alloc). Document with
@throws in docstrings. Add TODOs at call sites.
- modexp: Add static_assert for little-endian host requirement in
store(). Document and assert mod_tz overflow safety.
- state: Add null check for precompile output buffer allocation.
crypto: Document preconditions and edge cases in ECC/pairing code (#1481)
- ModArith::inv(): returns 0 for non-invertible input (including 0)
- FieldElement division/inv: returns 0 when divisor is 0
- mul(), msm(): not constant-time, safe for public EVM calldata only
- BN254 Jacobian add(): must not receive infinity, equal, or negated
points
- BLS g2_add(): fix comment to match actual checks (field + on-curve,
not subgroup)
crypto: Make FROBENIUS_COEFFS const (#1479)
Add const qualifier to prevent accidental mutation of the BN254
Frobenius endomorphism coefficients, which would silently corrupt
pairing results.
build: Add option to force assertions (#1474)
Add CMake option `-DASSERTIONS=ON` to force-enable assertions in code.
Use this for CI sanitizer builds.
crypto: Use actual mod_size instead of declared byte-padded size (#1475)
The modexp function was using mod_size derived from the byte length of
the input (declared_mod_size), which includes leading zero padding. This
caused the CRT combine's mul(result, mod_odd, y) to receive an oversized
result buffer, violating the mul() assert that r.size() <= x.size() +
y.size().
Return the actual trimmed mod_size from load_mod and use it for the CRT
mul/add operations. The result buffer is still allocated at
declared_mod_size (needed for store()), but CRT operations use the
trimmed mod_size.
Add regression test: 3^3 mod 12 encoded as 32 bytes (4 words padded, but
value only needs 1 word).
crypto: Specialize mul_amm for 256-bit modulus (#1473)
Template mul_amm on span extent N, defaulting to dynamic_extent. Add
explicit specialization for N=4 (256-bit / 32-byte modulus) that
delegates to mul_amm_256 in a separate translation unit
(mulmod.cpp/hpp), enabling independent optimization.
The N=4 specialization:
- Peels the first CIOS iteration (r is uninitialized, uses mul instead
of addmul, saving the zero-fill)
- Uses #pragma GCC unroll 3 for the remaining iterations
- Uses [[gnu::always_inline]] so the exp loop calls mul_amm_256 directly
with no wrapper overhead
The exp loop dispatches n==4 to the specialized path. Shared helpers
(sub, mul, addmul) are moved to mulmod.hpp to avoid duplication between
modexp.cpp and mulmod.cpp.
crypto: Optimize first iteration of multi-word multiplications (#1472)
Add mul(r, x, y) — multiply multi-word x by single word y, returning the
carry. Use it in:
- mul(r, x, y[]): first iteration of multi-word multiply, replacing
addmul on zero-filled r and eliminating the fill entirely.
- mul_amm: first CIOS iteration where t[] is zero, peeled out of the
main loop.
crypto: Refactor mul_amm to separate output from inputs (#1471)
Change mul_amm signature so that the output r is separate from the const
inputs x and y (r must not alias either). This removes the internal t
buffer parameter — mul_amm now writes directly to r.
The caller uses a double-buffer technique: r_cur holds the current
value, r_tmp receives the next mul_amm result. Squaring always writes to
r_tmp; if a multiply follows, it writes back to r_cur directly,
otherwise the spans are swapped. This eliminates the per-call copy from
the old implementation.
Also trim the result span passed to modexp_odd to match mod size
exactly, and remove the trailing-zero fill (the caller pre-zeros the
full result buffer).
crypto: Simplify modinv_pow2 Newton-Raphson iteration (#1470)
Rearrange the Newton-Raphson formula from inv * (2 - x * inv) to 2 * inv
- x * inv^2. This eliminates the neg_add2 helper (single-use x = 2 - x
operation) and computes the result directly into r, removing the
per-iteration copy.
Also return carry from add() to place the doubling overflow directly
into the next word.
crypto: Optimize scratch space in modexp_odd (#1469)
Overlap t and rem_scratch buffers in modexp_odd which have exclusive
lifetimes: rem_scratch is only used during the rem() call, t is only
used in the AMM loop after rem() returns.
Also simplify mask_pow2 to a branchless expression.
Reduces modexp_odd scratch from 5n+3b+2 to 4n+3b+2 words. Stack buffer
capacity drops from 4b+8m+4 to 4b+7m+4 (11 KB for 1024-byte inputs, down
from 12 KB).
Depends on #1467.
crypto: Unify memory allocation for modexp execution (#1467)
Replace per-function heap allocations in modexp helpers (rem,
modexp_odd, modexp_pow2, modinv_pow2) with caller-provided scratch
spans. The top-level modexp() uses a pmr::monotonic_buffer_resource
backed by a stack buffer for inputs up to the EIP-7823 limit (1024
bytes), with automatic heap fallback for larger inputs.
Each buffer (base, mod, result, op_scratch, CRT intermediates) is
allocated individually from the bump allocator — no manual pointer
arithmetic. The capacity formula 4b+8m+4 is tight: verified that
1024-byte inputs fit on the stack and 1025-byte CRT inputs overflow to
heap.
test: Simplify and parametrize by implementation modexp tests (#1468)
Introduce a parameterized test fixture that runs test_vectors and
large_inputs against both the native evmone and GMP implementations.
Add make_val helper to construct large values programmatically,
replacing ~220 lines of hex literals with compact one-liners. Move large
test vectors (AMM at various sizes, dense (2^N-2)^2, full-width
normalization, large exponent) from test_vectors to large_inputs.
Add large_inputs test covering 1024-byte (EIP-7823 stack limit) and
1025-byte (heap fallback) inputs across odd, power-of-two, and even
modulus types.
crypto: Unify modexp into single CRT-based flow (#1466)
Remove the modexp_even() helper and unify the odd, power-of-two, and
even modulus cases into a single CRT-based flow in modexp(). The
sub-parts are conditionally skipped when trivial (e.g. odd modulus skips
the power-of-two computation). The CRT combine only runs when both parts
are non-trivial.
crypto: Use exact size for x1 in modexp_even CRT (#1465)
Shrink the x1 (odd-part result) buffer from r.size() to mod_odd.size()
words, reducing temporary storage. Relax add/sub to handle shorter y
operand with carry/borrow propagation through remaining high words.
crypto: Load and split modulus in modexp in the same pass (#1464)
Fuse even-part detection into modulus loading via `load_mod()`, which
loads big-endian bytes, finds trailing zero bits, and right-shifts
in-place to extract the odd part — all in one pass. This replaces
separate `ctz()`, `is_pow2()`, and `shr()` calls in the dispatcher.
- Add `load_mod()` returning `{mod_odd, mod_tz}` (trimmed odd part +
trailing zero count)
- `load()` now returns a trimmed span; base and mod use independent
storage sizes
- Remove `ctz()` and `is_pow2()` (no remaining callers)
- `shr()` returns a trimmed span; fix aliased self-copy UB when
word_shift == 0
- `mul()` supports wider output (`r.size() >= max(x.size(), y.size())`)
- `modinv_pow2()` handles x shorter than r (high words implicitly zero)
- `modexp_pow2()` handles base shorter than num_pow2_words with
zero-fill
- Storage reduced from 4w to `base_size + 2 * mod_size` words
- Assert trimmed inputs in `modexp_odd`, `modexp_pow2`, `modexp_even`,
`mul_amm`
- Move base==0 early exit to main dispatcher
- Add test cases for base/mod size mismatches and small base with
multi-word modulus
evmone 0.19.0
Bump version: 0.18.0 -> 0.19.0
changelog: List changes for 0.19 release (#1463)
crypto: Use dynamic spans in modexp_odd and mul_amm (#1462)
Replace fixed-size template dispatch with runtime-sized std::span
parameters. This merges modexp_odd_fixed_size() into modexp_odd() and
simplifies mul_amm() to work with dynamic spans, eliminating the
per-size template instantiations.
crypto: Convert modexp_odd_fixed_size() to std::span params (#1461)
Same pattern as mul_amm: template on size_t N with static_assert, void
return with result as the first span argument. compute_mont_mod_inv(mod)
is replaced by -evmmax::modinv(mod[0]) since only mod[0] is used.
Extract the LE word-array comparison into a less() helper using
std::ranges::lexicographical_compare with std::views::reverse.
In the modexp_odd() dispatch lambda the dynamic-extent base_mont/mod
spans are zero-extended into N-word std::array buffers before calling
the fixed-size function, and the result words are copied back.
crypto: Compute modexp base_mont using var-length division (#1457)
Replace fixed-width intx::udivrem with span-based rem() for computing
the Montgomery form of the base in modexp_odd. The rem() function reuses
intx's internal division primitives operating on dynamic word spans.
crypto: Refactor modexp/mul_amm to use std::span params (#1460)
precompiles: Tests for huge inputs to expmod (#1459)
precompiles: Remove optional "silkpre" backend (#1456)
Drop the CMake EVMONE_PRECOMPILES_SILKPRE option. Silkpre is outdated
and it only provided useful (not slower than evmone) implementations for
ecrecover and modexp. These are now covered directly by libsecp256k1 and
GMP options.
precompiles: Split "evmone" and "gmp" modexp implementations (#1455)
precompiles: Add option to use libsecp256k1 (#1454)
Add CMake option `EVMONE_PRECOMPILES_LIBSECP256K1` to fetch and build
libsecp256k1 and use it for the ecrecover precompile implementation.
This also enables benchmark comparison between evmone and libsecp256k1.
precompiles: Remove modexp stubs, use local impl for all inputs (#1453)
crypto: Update modexp to use variadic-length numbers (except odd) (#1452)
Switch modexp input handling from fixed-width intx types to dynamic
uint64 word spans.
Add dynamic helpers for BE load/store and modulus classification
(ctz/is_pow2/shr), and remove fixed-size template dispatch while keeping
the odd-modulus arithmetic path unchanged.
crypto: Wrap modexp_odd with variadic-length API (#1446)
Rename the fixed-size modexp_odd<UIntT>() to modexp_odd_fixed_size() and
introduce a new modexp_odd() accepting std::span<uint64_t> parameters.
The new function dispatches to the appropriately sized fixed-size
implementation based on the operand word count.
Additionally, convert modexp_even() from a template to a span-based
function and adjust the callers in modexp_impl() to pass word spans via
as_words().
Fix missing EIP-7702 y_parity validation (#1450)
Expand the shortcut authorization validation in testing to check the range of the y_parity values.
Fixes https://github.com/ipsilon/evmone/issues/1444.
test: Add modexp test cases for modulus 0–1 (#1445)
crypto: Rewrite modexp_even to use variadic-length numbers (#1443)
Extends the variadic-length span approach to modexp_even, replacing
fixed-width UIntT operations with span-based arithmetic over only the
⌈k/64⌉ words needed.
New add and sub helpers are introduced. The mul function is relaxed to
auto-swap operands instead of requiring `x.size() >= y.size()`.
crypto: Use variadic-length for mod 2^k part in modexp (#1442)
Convert the `modexp_pow2` to take variadic-length numbers as arguments.
crypto: Use variadic-length modinv in modexp (#1434)
Replace the modular inversion with mod 2ᵏ which uses fixed size
`intx::uint` to one which uses variadic-length numbers represented by
`std::span<uint64_t>`.
This improves performance in pathological cases when the 2ᵏ part of the
modexp modulus is significant shorter than the modulus itself.
ci: Upgrade compilers and build images (#1441)
- clang 20 → 21
- GCC 14 → 15
- Use docker images chfast/cpp-build-env:25
crypto: Fix formatting with clang-format-21 (#1440)
cmake: Set CMake policy version to 4.2 (#1439)
test: Suppress buffer overflow warning from GCC 15 (#1438)
Fix missing #include <evmc/evmc.hpp> (#1437)
crypto: Extract `addmul()` helper in modexp (#1436)
crypto: Optimize carry handling in AMM for modexp (#1435)
```
│ old │ new │
│ gas/s │ gas/s vs base │
modexp<expmod_execute>/mod_len:8/mod_tz:8/exp_bits:33-14 921.9M ± 0% 932.6M ± 0% +1.16% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:16/mod_tz:8/exp_bits:33-14 919.6M ± 0% 930.9M ± 0% +1.23% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:24/mod_tz:8/exp_bits:33-14 232.7M ± 1% 250.0M ± 0% +7.43% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:32/mod_tz:0/exp_bits:33-14 254.3M ± 1% 273.8M ± 0% +7.67% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:32/mod_tz:1/exp_bits:33-14 234.6M ± 1% 251.3M ± 0% +7.08% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:32/mod_tz:8/exp_bits:33-14 233.3M ± 1% 250.3M ± 0% +7.28% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:32/mod_tz:127/exp_bits:33-14 232.6M ± 0% 249.1M ± 0% +7.11% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:32/mod_tz:254/exp_bits:33-14 233.1M ± 1% 249.0M ± 0% +6.85% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:32/mod_tz:0/exp_bits:256-14 266.4M ± 0% 287.2M ± 0% +7.81% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:32/mod_tz:8/exp_bits:256-14 246.0M ± 0% 264.5M ± 0% +7.50% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:32/mod_tz:127/exp_bits:256-14 245.4M ± 0% 263.6M ± 0% +7.43% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:32/mod_tz:254/exp_bits:256-14 245.6M ± 0% 263.5M ± 0% +7.29% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:32/mod_tz:8/exp_bits:8192-14 487.6M ± 0% 523.6M ± 0% +7.39% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:40/mod_tz:8/exp_bits:11-14 206.8M ± 0% 213.0M ± 0% +2.96% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:48/mod_tz:8/exp_bits:8-14 281.2M ± 1% 289.5M ± 0% +2.97% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:48/mod_tz:8/exp_bits:256-14 341.5M ± 0% 354.3M ± 0% +3.75% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:56/mod_tz:8/exp_bits:6-14 361.7M ± 0% 372.5M ± 1% +2.98% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:64/mod_tz:8/exp_bits:5-14 435.3M ± 0% 448.6M ± 0% +3.05% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:72/mod_tz:8/exp_bits:4-14 163.9M ± 0% 165.8M ± 0% +1.17% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:112/mod_tz:8/exp_bits:4-14 376.1M ± 0% 381.2M ± 1% +1.36% (p=0.002 n=11+9)
modexp<expmod_execute>/mod_len:136/mod_tz:8/exp_bits:3-14 132.4M ± 0% 133.6M ± 0% +0.96% (p=0.000 n=11+9)
modexp<expmod_execute>/mod_len:192/mod_tz:8/exp_bits:2-14 186.4M ± 0% 187.3M ± 1% +0.49% (p=0.038 n=11+9)
modexp<expmod_execute>/mod_len:504/mod_tz:0/exp_bits:2-14 135.9M ± 0% 135.8M ± 0% ~ (p=0.412 n=11+9)
modexp<expmod_execute>/mod_len:504/mod_tz:8/exp_bits:2-14 81.93M ± 0% 81.89M ± 0% ~ (p=0.412 n=11+9)
modexp<expmod_execute>/mod_len:504/mod_tz:1000/exp_bits:2-14 54.27M ± 0% 54.30M ± 0% ~ (p=0.710 n=11+9)
modexp<expmod_execute>/mod_len:504/mod_tz:4000/exp_bits:2-14 47.77M ± 1% 47.67M ± 1% ~ (p=1.000 n=11+9)
modexp<expmod_execute>/mod_len:504/mod_tz:0/exp_bits:255-14 234.6M ± 0% 234.5M ± 0% ~ (p=0.412 n=11+9)
modexp<expmod_execute>/mod_len:504/mod_tz:8/exp_bits:255-14 170.2M ± 1% 170.6M ± 0% +0.23% (p=0.010 n=11+9)
modexp<expmod_execute>/mod_len:504/mod_tz:1000/exp_bits:255-14 169.3M ± 1% 169.8M ± 0% +0.33% (p=0.020 n=11+9)
modexp<expmod_execute>/mod_len:504/mod_tz:4000/exp_bits:255-14 169.0M ± 0% 169.6M ± 0% +0.36% (p=0.006 n=11+9)
modexp<expmod_execute>/mod_len:512/mod_tz:8/exp_bits:2-14 84.42M ± 0% 84.43M ± 0% ~ (p=0.710 n=11+9)
modexp<expmod_execute>/mod_len:512/mod_tz:8/exp_bits:8192-14 347.3M ± 0% 347.0M ± 0% ~ (p=0.201 n=11+9)
modexp<expmod_execute>/mod_len:520/mod_tz:8/exp_bits:2-14 86.91M ± 1% 86.88M ± 0% ~ (p=0.710 n=11+9)
modexp<expmod_execute>/mod_len:1016/mod_tz:8/exp_bits:2-14 309.7M ± 1% 310.0M ± 0% ~ (p=0.412 n=11+9)
modexp<expmod_execute>/mod_len:1024/mod_tz:8/exp_bits:2-14 314.4M ± 0% 315.4M ± 1% ~ (p=0.112 n=11+9)
modexp<expmod_execute>/mod_len:1024/mod_tz:8/exp_bits:256-14 701.6M ± 1% 701.3M ± 0% ~ (p=0.824 n=11+9)
modexp<expmod_execute>/mod_len:1024/mod_tz:8/exp_bits:2048-14 1.322G ± 0% 1.323G ± 0% ~ (p=0.656 n=11+9)
modexp<expmod_execute>/mod_len:1024/mod_tz:4096/exp_bits:2048-14 1.323G ± 0% 1.321G ± 1% ~ (p=0.269 n=11+9)
modexp<expmod_execute>/mod_len:1024/mod_tz:8190/exp_bits:2048-14 1.324G ± 0% 1.321G ± 0% ~ (p=0.056 n=11+9)
geomean 261.1M 267.9M +2.61%
```
crypto: Use Almost Montgomery Multiplication in modexp (#1427)
Use the relaxed version of the Montgomery multiplication called Almost
Montgomery Multiplication from
"Efficient Software Implementations of Modular Exponentiation"
(https://eprint.iacr.org/2011/239.pdf).
```
│ old │ new │
│ gas/s │ gas/s vs base │
modexp<expmod_execute>/mod_len:8/exp_bits:33-14 902.5M ± 0% 941.3M ± 0% +4.29% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:16/exp_bits:33-14 896.6M ± 1% 931.5M ± 0% +3.88% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:24/exp_bits:33-14 233.0M ± 1% 241.2M ± 0% +3.53% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:32/exp_bits:33-14 233.9M ± 1% 242.0M ± 0% +3.43% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:32/exp_bits:256-14 245.3M ± 1% 254.7M ± 0% +3.83% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:32/exp_bits:8192-14 488.2M ± 0% 505.2M ± 0% +3.48% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:40/exp_bits:11-14 204.1M ± 0% 212.3M ± 1% +3.99% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:48/exp_bits:8-14 277.2M ± 0% 288.6M ± 0% +4.14% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:48/exp_bits:256-14 338.4M ± 0% 351.9M ± 0% +4.01% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:56/exp_bits:6-14 359.1M ± 0% 371.5M ± 0% +3.46% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:64/exp_bits:5-14 434.1M ± 0% 446.5M ± 0% +2.87% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:72/exp_bits:4-14 160.5M ± 1% 161.3M ± 0% +0.48% (p=0.001 n=11)
modexp<expmod_execute>/mod_len:112/exp_bits:4-14 368.1M ± 0% 369.0M ± 0% +0.24% (p=0.002 n=11)
modexp<expmod_execute>/mod_len:136/exp_bits:3-14 132.1M ± 0% 133.2M ± 1% +0.79% (p=0.001 n=11)
modexp<expmod_execute>/mod_len:192/exp_bits:2-14 186.7M ± 1% 186.7M ± 2% ~ (p=0.199 n=11)
modexp<expmod_execute>/mod_len:504/exp_bits:2-14 82.04M ± 0% 81.86M ± 1% -0.21% (p=0.010 n=11)
modexp<expmod_execute>/mod_len:512/exp_bits:2-14 84.51M ± 1% 84.42M ± 0% ~ (p=0.270 n=11)
modexp<expmod_execute>/mod_len:512/exp_bits:8192-14 348.0M ± 0% 348.4M ± 0% +0.12% (p=0.034 n=11)
modexp<expmod_execute>/mod_len:520/exp_bits:2-14 87.13M ± 0% 87.00M ± 0% ~ (p=0.300 n=11)
modexp<expmod_execute>/mod_len:1016/exp_bits:2-14 310.5M ± 1% 309.7M ± 0% ~ (p=0.171 n=11)
modexp<expmod_execute>/mod_len:1024/exp_bits:2-14 315.2M ± 0% 314.7M ± 0% ~ (p=0.151 n=11)
modexp<expmod_execute>/mod_len:1024/exp_bits:256-14 704.2M ± 0% 705.1M ± 0% ~ (p=0.217 n=11)
modexp<expmod_execute>/mod_len:1024/exp_bits:2048-14 1.327G ± 0% 1.325G ± 0% ~ (p=0.519 n=11)
geomean 290.3M 295.5M +1.79%
```
test: Extend modexp benchmarks (#1433)
- Add option to set the amount of trailing zero bits of modexp modulus.
- Add more benchmark cases for modexp.
- Sync revision to Osaka for all precompile benchmarks.
crypto: Improvements to `modinv()` / `inv_mod()` (#1432)
- Rename `inv_mod()` → `modinv()`.
- Add optimized `modinv(uint32_t).
- Change `modinv(uint64_t)` to `modinv(uint32_t)` + one iteration.
Codegen comparison: https://godbolt.org/z/6eT9rTfcf
build: Upgrade intx to 0.15.0 and use new stuff (#1430)
intx 0.15.0 release notes:
https://github.com/chfast/intx/releases/tag/v0.15.0
tidy: Make bool → int conversion explicit (#1431)
This fixes incoming clang-tidy warning.
Also disable one more clang-tidy warning.
crypto: Optimized copy of Montgomery multiplication for modexp (#1426)
- Make a copy of the core Montgomery multiplication from ModArith for
modexp_odd implementation. Independence will be used later.
- Don't compute R^2 % mod. Instead, do one-off to Montgomery conversion.
```
│ old │ new │
│ gas/s │ gas/s vs base │
modexp<expmod_execute>/mod_len:8/exp_bits:33-14 891.3M ± 0% 902.5M ± 0% +1.26% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:16/exp_bits:33-14 879.5M ± 1% 896.6M ± 1% +1.95% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:24/exp_bits:33-14 223.4M ± 1% 233.0M ± 1% +4.30% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:32/exp_bits:33-14 225.1M ± 0% 233.9M ± 1% +3.93% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:32/exp_bits:256-14 239.4M ± 0% 245.3M ± 1% +2.46% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:32/exp_bits:8192-14 475.7M ± 0% 488.2M ± 0% +2.62% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:40/exp_bits:11-14 196.2M ± 0% 204.1M ± 0% +4.03% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:48/exp_bits:8-14 264.9M ± 1% 277.2M ± 0% +4.62% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:48/exp_bits:256-14 337.7M ± 0% 338.4M ± 0% +0.19% (p=0.023 n=11)
modexp<expmod_execute>/mod_len:56/exp_bits:6-14 338.8M ± 0% 359.1M ± 0% +6.00% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:64/exp_bits:5-14 405.7M ± 0% 434.1M ± 0% +6.99% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:72/exp_bits:4-14 144.0M ± 1% 160.5M ± 1% +11.45% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:112/exp_bits:4-14 336.3M ± 1% 368.1M ± 0% +9.46% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:136/exp_bits:3-14 114.2M ± 0% 132.1M ± 0% +15.67% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:192/exp_bits:2-14 151.6M ± 1% 186.7M ± 1% +23.18% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:504/exp_bits:2-14 67.33M ± 1% 82.04M ± 0% +21.84% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:512/exp_bits:2-14 69.46M ± 1% 84.51M ± 1% +21.67% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:512/exp_bits:8192-14 347.7M ± 0% 348.0M ± 0% ~ (p=0.401 n=11)
modexp<expmod_execute>/mod_len:520/exp_bits:2-14 71.23M ± 1% 87.13M ± 0% +22.33% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:1016/exp_bits:2-14 264.7M ± 0% 310.5M ± 1% +17.29% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:1024/exp_bits:2-14 269.2M ± 1% 315.2M ± 0% +17.07% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:1024/exp_bits:256-14 700.6M ± 1% 704.2M ± 0% +0.51% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:1024/exp_bits:2048-14 1.321G ± 0% 1.327G ± 0% +0.43% (p=0.001 n=11)
geomean 267.9M 290.3M +8.38%
```
crypto: Extract some Montgomery helpers out of struct scope (#1425)
crypto: Handle modexp with exponent of 1 separately (#1424)
This allows optimizing the main loop and avoid 2 Montgomery
multiplications.
```
│ old │ new │
│ gas/s │ gas/s vs base │
modexp<expmod_execute>/mod_len:8/exp_bits:33-14 875.8M ± 7% 891.7M ± 0% +1.81% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:16/exp_bits:33-14 868.9M ± 0% 877.8M ± 1% +1.02% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:24/exp_bits:33-14 217.1M ± 0% 223.9M ± 0% +3.14% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:32/exp_bits:33-14 218.0M ± 0% 225.0M ± 0% +3.24% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:32/exp_bits:256-14 237.4M ± 0% 239.4M ± 0% +0.87% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:32/exp_bits:8192-14 472.1M ± 0% 475.8M ± 0% +0.77% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:40/exp_bits:11-14 182.7M ± 0% 196.5M ± 1% +7.52% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:48/exp_bits:8-14 239.6M ± 0% 265.1M ± 2% +10.63% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:48/exp_bits:256-14 336.5M ± 0% 337.5M ± 0% +0.30% (p=0.001 n=11)
modexp<expmod_execute>/mod_len:56/exp_bits:6-14 297.9M ± 0% 339.2M ± 0% +13.87% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:64/exp_bits:5-14 348.9M ± 1% 407.0M ± 0% +16.65% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:72/exp_bits:4-14 116.0M ± 2% 144.1M ± 0% +24.21% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:112/exp_bits:4-14 271.0M ± 0% 335.1M ± 1% +23.66% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:136/exp_bits:3-14 86.38M ± 0% 114.13M ± 0% +32.12% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:192/exp_bits:2-14 107.2M ± 1% 153.1M ± 1% +42.91% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:504/exp_bits:2-14 47.21M ± 1% 67.21M ± 1% +42.37% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:512/exp_bits:2-14 48.79M ± 0% 69.45M ± 1% +42.34% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:512/exp_bits:8192-14 348.0M ± 0% 347.5M ± 1% ~ (p=0.478 n=11)
modexp<expmod_execute>/mod_len:520/exp_bits:2-14 50.24M ± 0% 71.75M ± 1% +42.81% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:1016/exp_bits:2-14 188.3M ± 0% 267.9M ± 0% +42.25% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:1024/exp_bits:2-14 191.5M ± 0% 271.7M ± 1% +41.93% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:1024/exp_bits:256-14 700.3M ± 0% 700.7M ± 0% ~ (p=0.748 n=11)
modexp<expmod_execute>/mod_len:1024/exp_bits:2048-14 1.327G ± 0% 1.320G ± 1% -0.52% (p=0.003 n=11)
geomean 231.5M 268.3M +15.89%
```
bench: Adjust modexp benchmark inputs and params (#1423)
cmake: Add EVMONE_INTX_DIR to point to local intx source (#1422)
crypto: Use Newton–Raphson for modinv in modexp (#1420)
Use the Newton–Raphson method to compute the modular inverse of the odd
part of the modulus in the modexp for even modulus. This reuses the
inv_mod() procedure from ModArith to compute initial 64-bit value of the
inversion.
Benchmarks of modified modexp cases with big `k` in the decomposition to
`mod_odd * 2^k`:
```
│ before │ after │
│ gas/s │ gas/s vs base │
modexp<expmod_execute>/mod_len:8/exp_bits:33-14 722.7M ± 0% 870.1M ± 0% +20.39% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:16/exp_bits:33-14 863.9M ± 0% 868.3M ± 0% +0.50% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:24/exp_bits:33-14 206.1M ± 0% 217.0M ± 0% +5.24% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:32/exp_bits:33-14 217.7M ± 0% 218.1M ± 0% +0.16% (p=0.034 n=11)
modexp<expmod_execute>/mod_len:40/exp_bits:11-14 136.5M ± 0% 181.0M ± 0% +32.65% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:48/exp_bits:8-14 211.5M ± 0% 237.4M ± 0% +12.24% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:56/exp_bits:6-14 210.2M ± 0% 296.2M ± 0% +40.89% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:64/exp_bits:5-14 33.74M ± 0% 324.07M ± 0% +860.53% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:72/exp_bits:4-14 76.66M ± 0% 116.36M ± 0% +51.79% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:80/exp_bits:4-14 91.70M ± 0% 139.26M ± 0% +51.87% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:88/exp_bits:4-14 70.52M ± 0% 167.91M ± 0% +138.11% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:96/exp_bits:4-14 171.1M ± 0% 192.2M ± 0% +12.30% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:112/exp_bits:4-14 106.2M ± 0% 239.8M ± 16% +125.91% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:136/exp_bits:3-14 53.91M ± 0% 86.57M ± 0% +60.58% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:192/exp_bits:2-14 83.53M ± 0% 107.25M ± 0% +28.39% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:200/exp_bits:2-14 2.302M ± 0% 95.886M ± 0% +4065.86% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:256/exp_bits:2-14 50.46M ± 0% 180.23M ± 1% +257.16% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:264/exp_bits:2-14 3.710M ± 0% 12.260M ± 1% +230.45% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:504/exp_bits:2-14 10.74M ± 0% 43.81M ± 1% +307.90% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:512/exp_bits:2-14 25.28M ± 0% 48.86M ± 1% +93.28% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:520/exp_bits:2-14 11.43M ± 0% 46.62M ± 0% +307.95% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:1016/exp_bits:2-14 43.47M ± 0% 175.31M ± 0% +303.26% (p=0.000 n=11)
modexp<expmod_execute>/mod_len:1024/exp_bits:2-14 145.6M ± 81% 190.8M ± 12% +31.10% (p=0.000 n=11)
geomean 69.00M 150.5M +118.12%
```
test: Add modexp test vectors for even modulus and edge cases (#1421)
Add test vectors for:
- Small even modulus: 12, 20, 24 (single-byte results)
- Power-of-two modulus larger than 64 bits
- Large even modulus: (2^320-1)*2^64 and (2^768-1)*2^320
- Large base (48 bytes) with small even modulus (16 bytes)
These cases exercise:
- Even modulus handling via Chinese Remainder Theorem
- modinv_pow2 Newton-Raphson iteration
- Montgomery multiplication with base >= mod
AGENTS: add building and testing info
crypto: Replace BaseFieldElem with FieldElement (#1417)
ci: Upgrade riscv toolchain and harden curl downloads (#1418)
crypto: Hide FieldElement implementation details (#1416)
Refactors the `FieldElement` class to improve encapsulation by
converting it from a `struct` to a `class` and hiding implementation
details. The internal state (`value_`) and the helper method (`wrap()`)
are now properly encapsulated as private members, while maintaining the
public API.
crypto: Rework prime/order specification for FieldElement (#1415)
Decouple the `Curve::FIELD_PRIME` from the prime/order specification for
the `FieldElement`. This allows at least multiple different
FieldElements for the same curve.
crypto: Optimize computing mod inversion for Montgomery (#1359)
Compute the regular modular inverse for 2⁶⁴ using the Newton–Raphson numeric method. Then transform the result to the Montgomery modular multiplication requirements.
crypto: Use Montgomery form to reduce hash in ecrecover (#1409)
In numeric value of the ecrecover input hash can be reduced to be within
the group order by converting it to the Montgomery form (needed later).
crypto: Check point-at-infinity in non-affine in ecrecover (#1408)
In ecrecover, when checking if the final point (public key) is not the
point-at-infinity use the non-affine coordinates. This check is cheaper
(only checks the z coordinate).
crypto: Extract modexp_even() (#1413)
ci: Bump Xcode to 16.3.0 in macos-xcode-min (#1414)
The previous version 16.2.0 has been deprecated on Circle CI.
Add AGENTS.md with review guidelines (#1412)
tests: Update modexp benchmarks to Osaka gas prices (#1410)
crypto: Pass arguments to ecrecover as byte spans (#1407)
This leaves the full decision how to parse and validate these bytes by
the ecrecover implementation.
tests: Rework ecrecover unit tests (#1406)
crypto: Rename ecc::add() → add_affine() (#1404)
Make it explicit in the prodedure name that this addition includes a
finite field inversion on the way to produce the result in affine
coordinates.
tests: Remove unit tests fo…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
evmone-cli's
run --revwas bound to the evmc_revision enum, so CLI11 parsed it as the underlying integer (e.g.--rev 15for Amsterdam) — an opaque number that also shifts as revisions are inserted. Bind --rev to a string resolved through the existing evmone::test::to_rev() helper, mirroring the t8n--state.forkpath; the default comes from evmc::to_string() and prints as [Osaka], and an unknown name now fails with "unknown revision: ".Drop the explicit = 0..16 values from the evmc_revision enum so revisions are no longer advertised by number. The enumerators keep their order, leaving the values and ABI unchanged and EVMC_MAX_REVISION / EVMC_LATEST_STABLE_REVISION symbolic.