Skip to content

Commit 62ac627

Browse files
authored
precompiles: Implement expmod with GMP (#1198)
Adds expmod implementation using GMP library. This must be enabled with `-DEVMONE_PRECOMPILES_GMP=1` CMake option.
1 parent 48c2d63 commit 62ac627

File tree

7 files changed

+117
-18
lines changed

7 files changed

+117
-18
lines changed

README.md

+3-8
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,10 @@ To build the evmone EVMC module (shared library), test, and benchmark:
9191

9292
### Precompiles
9393

94-
Ethereum Precompiled Contracts (_precompiles_ for short) are only partly supported by evmone.
94+
Ethereum Precompiled Contracts (_precompiles_ for short) are supported by evmone with some exceptions:
9595

96-
However, there are options to enable limited precompiles support for testing.
97-
98-
1. For precompiles with missing implementation stubs are enabled by default.
99-
They will correctly respond to known inputs.
100-
2. The CMake option `EVMONE_PRECOMPILES_SILKPRE=1` enables building of
101-
the [silkpre] third party library with the implementation of the precompiles.
102-
This library also requires [GMP] (e.g. libgmp-dev) library for building and execution.
96+
1. The `ecrecover` is implemented directly by evmone and has degraded performance.
97+
2. For `expmod` stubs are enabled by default — they will correctly respond to known inputs. The CMake option `EVMONE_PRECOMPILES_GMP=1` enables full implementation but this requires [GMP] (e.g. libgmp-dev) library at build and execution time.
10398

10499
### Tools
105100

circle.yml

+24
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,29 @@ jobs:
450450
- upload_coverage:
451451
flags: ethereum_tests
452452

453+
precompiles-gmp:
454+
executor: linux-gcc-latest
455+
environment:
456+
BUILD_TYPE: Coverage
457+
CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-Og -DEVMONE_PRECOMPILES_GMP=1
458+
steps:
459+
- run:
460+
name: "Install GMP"
461+
command: sudo apt-get -q update && sudo apt-get -qy install libgmp-dev
462+
- build
463+
- download_execution_spec_tests:
464+
release: v4.1.0
465+
fixtures_suffix: develop
466+
- run:
467+
name: "Execution spec tests (state_tests)"
468+
working_directory: ~/spec-tests/fixtures/state_tests
469+
command: >
470+
~/build/bin/evmone-statetest ~/spec-tests/fixtures/state_tests
471+
- collect_coverage_gcc
472+
- upload_coverage:
473+
flags: eest_gmp
474+
475+
453476
precompiles-silkpre:
454477
executor: linux-gcc-latest
455478
environment:
@@ -675,6 +698,7 @@ workflows:
675698
- execution-spec-tests
676699
- eof-execution-spec-tests
677700
- ethereum-tests
701+
- precompiles-gmp
678702
- precompiles-silkpre
679703
- cmake-min
680704
- gcc-min

cmake/FindGMP.cmake

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# evmone: Fast Ethereum Virtual Machine implementation
2+
# Copyright 2025 The evmone Authors.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
# Finds the GMP or MPIR library and its include directories.
6+
7+
find_library(GMP_LIBRARY NAMES gmp mpir DOC "GMP/MPIR library")
8+
find_path(GMP_INCLUDE_DIR NAMES gmp.h DOC "GMP header")
9+
10+
include(FindPackageHandleStandardArgs)
11+
find_package_handle_standard_args(
12+
GMP
13+
REQUIRED_VARS GMP_LIBRARY GMP_INCLUDE_DIR
14+
)
15+
16+
if(GMP_FOUND)
17+
if(NOT TARGET GMP::gmp)
18+
add_library(GMP::gmp UNKNOWN IMPORTED)
19+
set_target_properties(GMP::gmp PROPERTIES
20+
IMPORTED_LOCATION ${GMP_LIBRARY}
21+
IMPORTED_LINK_INTERFACE_LANGUAGES C
22+
INTERFACE_INCLUDE_DIRECTORIES ${GMP_INCLUDE_DIR}
23+
)
24+
endif()
25+
endif()

test/state/CMakeLists.txt

+14-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ target_sources(
4343
transaction.cpp
4444
)
4545

46-
option(EVMONE_PRECOMPILES_SILKPRE "Enable precompiles support via silkpre library" OFF)
46+
option(EVMONE_PRECOMPILES_SILKPRE "Enable precompiles from silkpre library (for benchmarking only)" OFF)
4747
if(EVMONE_PRECOMPILES_SILKPRE)
4848
include(FetchContent)
4949
FetchContent_Declare(
@@ -72,3 +72,16 @@ if(EVMONE_PRECOMPILES_SILKPRE)
7272
precompiles_silkpre.cpp
7373
)
7474
endif()
75+
76+
# This is done after Silkpre because it also tries to find GMP.
77+
option(EVMONE_PRECOMPILES_GMP "Enable precompiles implementations via the GMP/MPIR library" OFF)
78+
if(EVMONE_PRECOMPILES_GMP)
79+
find_package(GMP REQUIRED)
80+
target_link_libraries(evmone-state PRIVATE GMP::gmp)
81+
target_compile_definitions(evmone-state PRIVATE EVMONE_PRECOMPILES_GMP=1)
82+
target_sources(
83+
evmone-state PRIVATE
84+
precompiles_gmp.hpp
85+
precompiles_gmp.cpp
86+
)
87+
endif()

test/state/precompiles.cpp

+5-9
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
#include <limits>
2020
#include <span>
2121

22-
#ifdef EVMONE_PRECOMPILES_SILKPRE
23-
#include "precompiles_silkpre.hpp"
22+
#ifdef EVMONE_PRECOMPILES_GMP
23+
#include "precompiles_gmp.hpp"
2424
#endif
2525

2626
namespace evmone::state
@@ -363,16 +363,12 @@ ExecutionResult expmod_execute(
363363
const auto exp = payload.substr(base_len, exp_len);
364364
const auto mod = mod_requires_padding ? bytes_view{output, mod_len} : mod_explicit;
365365

366-
#ifdef EVMONE_PRECOMPILES_SILKPRE
367-
(void)base;
368-
(void)exp;
369-
(void)mod;
370-
// For Silkpre use the raw input for compatibility.
371-
return silkpre_expmod_execute(input, input_size, output, output_size);
366+
#ifdef EVMONE_PRECOMPILES_GMP
367+
expmod_gmp(base, exp, mod, output);
372368
#else
373369
expmod_stub(base, exp, mod, output);
374-
return {EVMC_SUCCESS, mod.size()};
375370
#endif
371+
return {EVMC_SUCCESS, mod.size()};
376372
}
377373

378374
ExecutionResult ecadd_execute(const uint8_t* input, size_t input_size, uint8_t* output,

test/state/precompiles_gmp.cpp

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// evmone: Fast Ethereum Virtual Machine implementation
2+
// Copyright 2025 The evmone Authors.
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
#include "precompiles_gmp.hpp"
6+
#include <gmp.h>
7+
#include <cassert>
8+
9+
namespace evmone::state
10+
{
11+
void expmod_gmp(bytes_view base, bytes_view exp, bytes_view mod, uint8_t* output) noexcept
12+
{
13+
mpz_t b, e, m, r; // NOLINT(*-isolate-declaration)
14+
mpz_inits(b, e, m, r, nullptr);
15+
mpz_import(b, base.size(), 1, 1, 0, 0, base.data());
16+
mpz_import(e, exp.size(), 1, 1, 0, 0, exp.data());
17+
mpz_import(m, mod.size(), 1, 1, 0, 0, mod.data());
18+
assert(mpz_sgn(m) != 0);
19+
20+
mpz_powm(r, b, e, m);
21+
22+
size_t export_size = 0;
23+
mpz_export(output, &export_size, 1, 1, 0, 0, r);
24+
assert(export_size <= mod.size());
25+
mpz_clears(b, e, m, r, nullptr);
26+
27+
std::copy_backward(output, output + export_size, output + mod.size());
28+
std::fill_n(output, mod.size() - export_size, 0);
29+
}
30+
} // namespace evmone::state

test/state/precompiles_gmp.hpp

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// evmone: Fast Ethereum Virtual Machine implementation
2+
// Copyright 2025 The evmone Authors.
3+
// SPDX-License-Identifier: Apache-2.0
4+
#pragma once
5+
6+
#include <evmc/evmc.hpp>
7+
8+
namespace evmone::state
9+
{
10+
using evmc::bytes_view;
11+
12+
/// Executes the expmod precompile using the GMP library.
13+
///
14+
/// Requires mod not to be zero (having at least one non-zero byte).
15+
void expmod_gmp(bytes_view base, bytes_view exp, bytes_view mod, uint8_t* output) noexcept;
16+
} // namespace evmone::state

0 commit comments

Comments
 (0)