Skip to content

precompiles: Implement expmod with GMP #1198

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,10 @@ To build the evmone EVMC module (shared library), test, and benchmark:

### Precompiles

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

However, there are options to enable limited precompiles support for testing.

1. For precompiles with missing implementation stubs are enabled by default.
They will correctly respond to known inputs.
2. The CMake option `EVMONE_PRECOMPILES_SILKPRE=1` enables building of
the [silkpre] third party library with the implementation of the precompiles.
This library also requires [GMP] (e.g. libgmp-dev) library for building and execution.
1. The `ecrecover` is implemented directly by evmone and has degraded performance.
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.

### Tools

Expand Down
24 changes: 24 additions & 0 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,29 @@ jobs:
- upload_coverage:
flags: ethereum_tests

precompiles-gmp:
executor: linux-gcc-latest
environment:
BUILD_TYPE: Coverage
CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-Og -DEVMONE_PRECOMPILES_GMP=1
steps:
- run:
name: "Install GMP"
command: sudo apt-get -q update && sudo apt-get -qy install libgmp-dev
- build
- download_execution_spec_tests:
release: v4.1.0
fixtures_suffix: develop
- run:
name: "Execution spec tests (state_tests)"
working_directory: ~/spec-tests/fixtures/state_tests
command: >
~/build/bin/evmone-statetest ~/spec-tests/fixtures/state_tests
- collect_coverage_gcc
- upload_coverage:
flags: eest_gmp


precompiles-silkpre:
executor: linux-gcc-latest
environment:
Expand Down Expand Up @@ -675,6 +698,7 @@ workflows:
- execution-spec-tests
- eof-execution-spec-tests
- ethereum-tests
- precompiles-gmp
- precompiles-silkpre
- cmake-min
- gcc-min
Expand Down
25 changes: 25 additions & 0 deletions cmake/FindGMP.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# evmone: Fast Ethereum Virtual Machine implementation
# Copyright 2025 The evmone Authors.
# SPDX-License-Identifier: Apache-2.0

# Finds the GMP or MPIR library and its include directories.

find_library(GMP_LIBRARY NAMES gmp mpir DOC "GMP/MPIR library")
find_path(GMP_INCLUDE_DIR NAMES gmp.h DOC "GMP header")

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
GMP
REQUIRED_VARS GMP_LIBRARY GMP_INCLUDE_DIR
)

if(GMP_FOUND)
if(NOT TARGET GMP::gmp)
add_library(GMP::gmp UNKNOWN IMPORTED)
set_target_properties(GMP::gmp PROPERTIES
IMPORTED_LOCATION ${GMP_LIBRARY}
IMPORTED_LINK_INTERFACE_LANGUAGES C
INTERFACE_INCLUDE_DIRECTORIES ${GMP_INCLUDE_DIR}
)
endif()
endif()
15 changes: 14 additions & 1 deletion test/state/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ target_sources(
transaction.cpp
)

option(EVMONE_PRECOMPILES_SILKPRE "Enable precompiles support via silkpre library" OFF)
option(EVMONE_PRECOMPILES_SILKPRE "Enable precompiles from silkpre library (for benchmarking only)" OFF)
if(EVMONE_PRECOMPILES_SILKPRE)
include(FetchContent)
FetchContent_Declare(
Expand Down Expand Up @@ -72,3 +72,16 @@ if(EVMONE_PRECOMPILES_SILKPRE)
precompiles_silkpre.cpp
)
endif()

# This is done after Silkpre because it also tries to find GMP.
option(EVMONE_PRECOMPILES_GMP "Enable precompiles implementations via the GMP/MPIR library" OFF)
if(EVMONE_PRECOMPILES_GMP)
find_package(GMP REQUIRED)
target_link_libraries(evmone-state PRIVATE GMP::gmp)
target_compile_definitions(evmone-state PRIVATE EVMONE_PRECOMPILES_GMP=1)
target_sources(
evmone-state PRIVATE
precompiles_gmp.hpp
precompiles_gmp.cpp
)
endif()
14 changes: 5 additions & 9 deletions test/state/precompiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
#include <limits>
#include <span>

#ifdef EVMONE_PRECOMPILES_SILKPRE
#include "precompiles_silkpre.hpp"
#ifdef EVMONE_PRECOMPILES_GMP
#include "precompiles_gmp.hpp"
#endif

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

#ifdef EVMONE_PRECOMPILES_SILKPRE
(void)base;
(void)exp;
(void)mod;
// For Silkpre use the raw input for compatibility.
return silkpre_expmod_execute(input, input_size, output, output_size);
#ifdef EVMONE_PRECOMPILES_GMP
expmod_gmp(base, exp, mod, output);
#else
expmod_stub(base, exp, mod, output);
return {EVMC_SUCCESS, mod.size()};
#endif
return {EVMC_SUCCESS, mod.size()};
}

ExecutionResult ecadd_execute(const uint8_t* input, size_t input_size, uint8_t* output,
Expand Down
30 changes: 30 additions & 0 deletions test/state/precompiles_gmp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2025 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include "precompiles_gmp.hpp"
#include <gmp.h>
#include <cassert>

namespace evmone::state
{
void expmod_gmp(bytes_view base, bytes_view exp, bytes_view mod, uint8_t* output) noexcept
{
mpz_t b, e, m, r; // NOLINT(*-isolate-declaration)
mpz_inits(b, e, m, r, nullptr);
mpz_import(b, base.size(), 1, 1, 0, 0, base.data());
mpz_import(e, exp.size(), 1, 1, 0, 0, exp.data());
mpz_import(m, mod.size(), 1, 1, 0, 0, mod.data());
assert(mpz_sgn(m) != 0);

mpz_powm(r, b, e, m);

size_t export_size = 0;
mpz_export(output, &export_size, 1, 1, 0, 0, r);
assert(export_size <= mod.size());
mpz_clears(b, e, m, r, nullptr);

std::copy_backward(output, output + export_size, output + mod.size());
std::fill_n(output, mod.size() - export_size, 0);
}
} // namespace evmone::state
16 changes: 16 additions & 0 deletions test/state/precompiles_gmp.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2025 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include <evmc/evmc.hpp>

namespace evmone::state
{
using evmc::bytes_view;

/// Executes the expmod precompile using the GMP library.
///
/// Requires mod not to be zero (having at least one non-zero byte).
void expmod_gmp(bytes_view base, bytes_view exp, bytes_view mod, uint8_t* output) noexcept;
} // namespace evmone::state