Skip to content

evmmax: Implement modexp #1135

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

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions lib/evmone_precompiles/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ target_sources(
pairing/bn254/pairing.cpp
pairing/bn254/utils.hpp
pairing/field_template.hpp
modexp.hpp
modexp.cpp
ripemd160.hpp
ripemd160.cpp
secp256k1.hpp
Expand Down
227 changes: 227 additions & 0 deletions lib/evmone_precompiles/modexp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
#include "modexp.hpp"
#include <evmmax/evmmax.hpp>

#include <bit>

using namespace intx;

namespace
{
template <typename UIntT>
UIntT modexp_odd(const UIntT& base, const evmc::bytes_view& exp, const UIntT& mod)
{
const evmmax::ModArith<UIntT> arith(mod);

UIntT ret = arith.to_mont(UIntT{1});
const auto base_mont = arith.to_mont(base);
const auto base2 = arith.mul(base_mont, base_mont);
const auto base3 = arith.mul(base_mont, base2);

for (const auto e : exp)
{
for (size_t i = 8; i != 0; i -= 2)
{
ret = arith.mul(ret, ret);
const auto bits = e >> (i - 2) & 0b11;

const auto& x = bits == 0b11 ? base3 : base_mont;

switch (bits)
{
case 0b00:
ret = arith.mul(ret, ret);
break;
case 0b01:
ret = arith.mul(ret, ret);
ret = arith.mul(ret, x);
break;
case 0b10:
ret = arith.mul(ret, x);
ret = arith.mul(ret, ret);
break;
case 0b11:
ret = arith.mul(ret, ret);
ret = arith.mul(ret, x);
break;
default:
__builtin_unreachable();

Check warning on line 47 in lib/evmone_precompiles/modexp.cpp

View check run for this annotation

Codecov / codecov/patch

lib/evmone_precompiles/modexp.cpp#L46-L47

Added lines #L46 - L47 were not covered by tests
}
}
}

return arith.from_mont(ret);
}

template <typename UIntT>
UIntT modexp_pow_of_two(const UIntT& base, const evmc::bytes_view& exp, const UIntT& mod)
{
const auto nlz = clz(mod);

const UIntT mod_mask = std::numeric_limits<UIntT>::max() >> (nlz + 1);
UIntT ret = UIntT{1};
for (auto e : exp)
{
unsigned char mask = 0x80;
while (mask != 0)
{
ret = ret * ret;
ret &= mod_mask;
if ((mask & e) != 0)
{
ret = ret * base;
ret &= mod_mask;
}

mask >>= 1;
}
}

return ret;
}

template <typename UIntT>
size_t ctz(const UIntT& value)
{
size_t mod_tailing_zeros = 0;
for (size_t i = 0; i < value.num_words; ++i)
{
if (value[i] == 0)
{
mod_tailing_zeros += value.word_num_bits;
continue;
}
else
{
mod_tailing_zeros += static_cast<size_t>(std::countr_zero(value[i]));
break;
}
}

return mod_tailing_zeros;
}

template <typename UIntT>
UIntT modinv_2k(const UIntT& x, size_t k)
{
UIntT b{1};
UIntT res;
for (size_t i = 0; i < k; ++i)
{
UIntT t = b & UIntT{1};
b = (b - x * t) >> 1;
res += t << i;
}

return res;
}

template <typename UIntT>
UIntT modexp_impl(const UIntT& base, const evmc::bytes_view& exp, const UIntT& mod)
{
// is odd
if ((mod & UIntT{1}) == UIntT{1})
{
return modexp_odd(base, exp, mod);
}
else if ((mod << (clz(mod) + 1)) == 0) // is power of 2
{
return modexp_pow_of_two(base, exp, mod);
}
else // is even
{
const auto mod_tailing_zeros = ctz(mod);

auto const N = mod >> mod_tailing_zeros;
const UIntT K = UIntT{1} << mod_tailing_zeros;

const auto x1 = modexp_odd(base, exp, N);
const auto x2 = modexp_pow_of_two(base, exp, K);

const auto N_inv = modinv_2k(N, mod_tailing_zeros);

return x1 + (((x2 - x1) * N_inv) % K) * N;
}
}

template <typename UIntT>
UIntT load_from_bytes(const evmc::bytes_view& data)
{
constexpr auto num_bytes = UIntT::num_words * sizeof(typename UIntT::word_type);
assert(data.size() <= num_bytes);
if (data.size() == num_bytes)
{
return intx::be::unsafe::load<UIntT>(data.data());
}
else
{
evmc::bytes tmp;
tmp.resize(num_bytes);
std::memcpy(&tmp[num_bytes - data.size()], data.data(), data.size());
return intx::be::unsafe::load<UIntT>(tmp.data());
}
}

} // namespace

namespace evmone::crypto
{
bool modexp(uint8_t* output, size_t output_size, const evmc::bytes_view& base,
const evmc::bytes_view& exp, const evmc::bytes_view& mod)
{
constexpr auto MAX_INPUT_SIZE = 1024;
if (base.size() > MAX_INPUT_SIZE || exp.size() > MAX_INPUT_SIZE || mod.size() > MAX_INPUT_SIZE)
return false;

// mod is zero
if (mod.find_first_not_of(uint8_t{0}) == std::string::npos)
{
memset(output, 0, output_size);
return true;
}

const auto size = std::max(mod.size(), base.size());

assert(output_size >= mod.size());

evmc::bytes res_bytes;
if (size <= 32)
{
res_bytes.resize(32);
intx::be::unsafe::store(res_bytes.data(),
modexp_impl(load_from_bytes<uint256>(base), exp, load_from_bytes<uint256>(mod)));
}
else if (size <= 64)
{
res_bytes.resize(64);
intx::be::unsafe::store(res_bytes.data(),
modexp_impl(load_from_bytes<uint512>(base), exp, load_from_bytes<uint512>(mod)));
}
else if (size <= 128)
{
res_bytes.resize(128);
intx::be::unsafe::store(
res_bytes.data(), modexp_impl(load_from_bytes<intx::uint<1024>>(base), exp,
load_from_bytes<intx::uint<1024>>(mod)));
}
else if (size <= 256)
{
res_bytes.resize(256);
intx::be::unsafe::store(
res_bytes.data(), modexp_impl(load_from_bytes<intx::uint<2048>>(base), exp,
load_from_bytes<intx::uint<2048>>(mod)));
}
else
{
assert(output_size <= 1024);
res_bytes.resize(1024);
intx::be::unsafe::store(
res_bytes.data(), modexp_impl(load_from_bytes<intx::uint<8192>>(base), exp,
load_from_bytes<intx::uint<8192>>(mod)));
}

memcpy(output, &res_bytes[res_bytes.size() - output_size], output_size);
return true;
}


} // namespace evmone::crypto
13 changes: 13 additions & 0 deletions lib/evmone_precompiles/modexp.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2025 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#pragma once
#include <evmc/evmc.hpp>
#include <intx/intx.hpp>

namespace evmone::crypto
{
bool modexp(uint8_t* output, size_t output_size, const evmc::bytes_view& base,
const evmc::bytes_view& exp, const evmc::bytes_view& mod);
}
36 changes: 36 additions & 0 deletions test/precompiles_bench/precompiles_bench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
#include <array>
#include <memory>

#ifdef EVMONE_PRECOMPILES_OPENSSL
#include <state/precompiles_openssl.hpp>
#endif

#ifdef EVMONE_PRECOMPILES_SILKPRE
#include <state/precompiles_silkpre.hpp>
#endif
Expand All @@ -28,6 +32,8 @@
template <>
constexpr auto analyze<PrecompileId::ecrecover> = ecrecover_analyze;
template <>
constexpr auto analyze<PrecompileId::expmod> = expmod_analyze;
template <>
constexpr auto analyze<PrecompileId::ecadd> = ecadd_analyze;
template <>
constexpr auto analyze<PrecompileId::ecmul> = ecmul_analyze;
Expand Down Expand Up @@ -65,6 +71,22 @@

};

template <>
const inline std::array inputs<PrecompileId::expmod>{
"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020 3038f57abc91abff7dcbc5b55cd6aa454503850ce62e941f81273ba6008e82a5 30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffffff 30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001"_hex,
"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020 239ea33f993130afd678f591cd1685d44779b7717156f2a715220955e10a2348 30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffffff 30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001"_hex,
"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020 3914aeddf3af39bd5e0e231d09a8438cf38c7d0274a5aa61bd9a2bbcd77fd2c7 ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254f ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"_hex,
"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020 27c816cec3d048eaae22d948c42f9d803d765d0827a7b49e01273031566eed92 ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254f ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"_hex,
"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020 0cf7d1e109b5b8f6a682be51e152d3668acc1b4d762ecbf5cdca7a807cc5fe4c ffffffff00000001000000000000000000000000fffffffffffffffffffffffd ffffffff00000001000000000000000000000000ffffffffffffffffffffffff"_hex,
"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020 3a122fd889a09d3a19c587ab486d5a6a2b39836d6d665c6ebad2a68a615e6736 ffffffff00000001000000000000000000000000fffffffffffffffffffffffd ffffffff00000001000000000000000000000000ffffffffffffffffffffffff"_hex,
"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020 161455250ee1d7ff76e4eb2f6a16a636a4bc69dd20416f6ea5647b7fb311e932 0000000000000000000000000000000000000000000000000000000000ffffff 30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001"_hex,
"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020 0d96428856df2f1b728ce955f28f50befa405a61e8984acbaf678af9be0e0dfd 0000000000000000000000000000000000000000000000000000000001000000 30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001"_hex,
"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020 2369f74b39dddb516bb17d20f24ba72c8f0ec09bea7d7f38ad54dbc3cfd4b476 0c19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52 30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47"_hex,
"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020 19b01e88eb4a62d7df2da453deb859f6113004675370b6f54ff9f20499cfc4cf 0c19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52 30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47"_hex,
"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020 c943d0f2b95b88d8a3ad7782221659f05cb697047a9793a65ec7db59439afb0a 3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"_hex,
"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020 307c38188f3b804fc399b7195579269fd60fddbe7e6d8174b3ad38b64b1bb2fa 3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"_hex,
};

template <>
const inline std::array inputs<PrecompileId::ecadd>{
"0f25929bcb43d5a57391564615c9e70a992b10eafa4db109709649cf48c50dd2"
Expand Down Expand Up @@ -184,6 +206,20 @@
#endif
} // namespace bench_ecrecovery

namespace bench_expmod
{
constexpr auto evmmax_cpp = expmod_execute;

Check warning on line 211 in test/precompiles_bench/precompiles_bench.cpp

View check run for this annotation

Codecov / codecov/patch

test/precompiles_bench/precompiles_bench.cpp#L211

Added line #L211 was not covered by tests
BENCHMARK_TEMPLATE(precompile, PrecompileId::expmod, evmmax_cpp);
#ifdef EVMONE_PRECOMPILES_OPENSSL
constexpr auto openssl = openssl_expmod_execute;
BENCHMARK_TEMPLATE(precompile, PrecompileId::expmod, openssl);
#endif
#ifdef EVMONE_PRECOMPILES_SILKPRE
constexpr auto gmp = silkpre_expmod_execute;

Check warning on line 218 in test/precompiles_bench/precompiles_bench.cpp

View check run for this annotation

Codecov / codecov/patch

test/precompiles_bench/precompiles_bench.cpp#L218

Added line #L218 was not covered by tests
BENCHMARK_TEMPLATE(precompile, PrecompileId::expmod, gmp);
#endif
} // namespace bench_ecadd

namespace bench_ecadd
{
constexpr auto evmmax_cpp = ecadd_execute;
Expand Down
13 changes: 11 additions & 2 deletions test/state/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ target_sources(
precompiles.hpp
precompiles.cpp
precompiles_internal.hpp
precompiles_stubs.hpp
precompiles_stubs.cpp
requests.hpp
requests.cpp
rlp.hpp
Expand All @@ -43,6 +41,17 @@ target_sources(
transaction.cpp
)

find_package(OpenSSL COMPONENTS Crypto)
if(OPENSSL_FOUND)
target_compile_definitions(evmone-state PUBLIC EVMONE_PRECOMPILES_OPENSSL=1)
target_link_libraries(evmone-state PRIVATE OpenSSL::Crypto)
target_sources(
evmone-state PRIVATE
precompiles_openssl.hpp
precompiles_openssl.cpp
)
endif()

option(EVMONE_PRECOMPILES_SILKPRE "Enable precompiles support via silkpre library" OFF)
if(EVMONE_PRECOMPILES_SILKPRE)
include(FetchContent)
Expand Down
Loading