Skip to content

Commit fb40206

Browse files
committed
evmmax: Implement modexp
1 parent d62977d commit fb40206

10 files changed

+366
-403
lines changed

lib/evmone_precompiles/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ target_sources(
2020
pairing/bn254/pairing.cpp
2121
pairing/bn254/utils.hpp
2222
pairing/field_template.hpp
23+
modexp.hpp
24+
modexp.cpp
2325
ripemd160.hpp
2426
ripemd160.cpp
2527
secp256k1.hpp

lib/evmone_precompiles/modexp.cpp

+206
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#include "modexp.hpp"
2+
#include <evmmax/evmmax.hpp>
3+
4+
#include <bit>
5+
6+
using namespace intx;
7+
8+
namespace
9+
{
10+
template <typename UIntT>
11+
UIntT modexp_odd(const UIntT& base, const evmc::bytes_view& exp, const UIntT& mod)
12+
{
13+
const evmmax::ModArith<UIntT> arith(mod);
14+
15+
UIntT ret = arith.to_mont(UIntT{1});
16+
const auto base_mont = arith.to_mont(base);
17+
18+
for (auto e : exp)
19+
{
20+
unsigned char mask = 0x80;
21+
while (mask != 0)
22+
{
23+
ret = arith.mul(ret, ret);
24+
if ((mask & e) != 0)
25+
ret = arith.mul(ret, base_mont);
26+
27+
mask >>= 1;
28+
}
29+
}
30+
31+
return arith.from_mont(ret);
32+
}
33+
34+
template <typename UIntT>
35+
UIntT modexp_pow_of_two(const UIntT& base, const evmc::bytes_view& exp, const UIntT& mod)
36+
{
37+
const auto nlz = clz(mod);
38+
39+
const UIntT mod_mask = std::numeric_limits<UIntT>::max() >> (nlz + 1);
40+
UIntT ret = UIntT{1};
41+
for (auto e : exp)
42+
{
43+
unsigned char mask = 0x80;
44+
while (mask != 0)
45+
{
46+
ret = ret * ret;
47+
ret &= mod_mask;
48+
if ((mask & e) != 0)
49+
{
50+
ret = ret * base;
51+
ret &= mod_mask;
52+
}
53+
54+
mask >>= 1;
55+
}
56+
}
57+
58+
return ret;
59+
}
60+
61+
template <typename UIntT>
62+
size_t ctz(const UIntT& value)
63+
{
64+
size_t mod_tailing_zeros = 0;
65+
for (size_t i = 0; i < value.num_words; ++i)
66+
{
67+
if (value[i] == 0)
68+
{
69+
mod_tailing_zeros += value.word_num_bits;
70+
continue;
71+
}
72+
else
73+
{
74+
mod_tailing_zeros += static_cast<size_t>(std::countr_zero(value[i]));
75+
break;
76+
}
77+
}
78+
79+
return mod_tailing_zeros;
80+
}
81+
82+
template <typename UIntT>
83+
UIntT modinv_2k(const UIntT& x, size_t k)
84+
{
85+
UIntT b{1};
86+
UIntT res;
87+
for (size_t i = 0; i < k; ++i)
88+
{
89+
UIntT t = b & UIntT{1};
90+
b = (b - x * t) >> 1;
91+
res += t << i;
92+
}
93+
94+
return res;
95+
}
96+
97+
template <typename UIntT>
98+
UIntT modexp_impl(const UIntT& base, const evmc::bytes_view& exp, const UIntT& mod)
99+
{
100+
// is odd
101+
if ((mod & UIntT{1}) == UIntT{1})
102+
{
103+
return modexp_odd(base, exp, mod);
104+
}
105+
else if ((mod << (clz(mod) + 1)) == 0) // is power of 2
106+
{
107+
return modexp_pow_of_two(base, exp, mod);
108+
}
109+
else // is even
110+
{
111+
const auto mod_tailing_zeros = ctz(mod);
112+
113+
auto const N = mod >> mod_tailing_zeros;
114+
const UIntT K = UIntT{1} << mod_tailing_zeros;
115+
116+
const auto x1 = modexp_odd(base, exp, N);
117+
const auto x2 = modexp_pow_of_two(base, exp, K);
118+
119+
const auto N_inv = modinv_2k(N, mod_tailing_zeros);
120+
121+
return x1 + (((x2 - x1) * N_inv) % K) * N;
122+
}
123+
}
124+
125+
template <typename UIntT>
126+
UIntT load_from_bytes(const evmc::bytes_view& data)
127+
{
128+
constexpr auto num_bytes = UIntT::num_words * sizeof(typename UIntT::word_type);
129+
assert(data.size() <= num_bytes);
130+
if (data.size() == num_bytes)
131+
{
132+
return intx::be::unsafe::load<UIntT>(data.data());
133+
}
134+
else
135+
{
136+
evmc::bytes tmp;
137+
tmp.resize(num_bytes);
138+
std::memcpy(&tmp[num_bytes - data.size()], data.data(), data.size());
139+
return intx::be::unsafe::load<UIntT>(tmp.data());
140+
}
141+
}
142+
143+
} // namespace
144+
145+
namespace evmone::crypto
146+
{
147+
bool modexp(uint8_t* output, size_t output_size, const evmc::bytes_view& base,
148+
const evmc::bytes_view& exp, const evmc::bytes_view& mod)
149+
{
150+
constexpr auto MAX_INPUT_SIZE = 1024;
151+
if (base.size() > MAX_INPUT_SIZE || exp.size() > MAX_INPUT_SIZE || mod.size() > MAX_INPUT_SIZE)
152+
return false;
153+
154+
// mod is zero
155+
if (mod.find_first_not_of(uint8_t{0}) == std::string::npos)
156+
{
157+
memset(output, 0, output_size);
158+
return true;
159+
}
160+
161+
const auto size = std::max(mod.size(), base.size());
162+
163+
assert(output_size >= mod.size());
164+
165+
evmc::bytes res_bytes;
166+
if (size <= 32)
167+
{
168+
res_bytes.resize(32);
169+
intx::be::unsafe::store(res_bytes.data(),
170+
modexp_impl(load_from_bytes<uint256>(base), exp, load_from_bytes<uint256>(mod)));
171+
}
172+
else if (size <= 64)
173+
{
174+
res_bytes.resize(64);
175+
intx::be::unsafe::store(res_bytes.data(),
176+
modexp_impl(load_from_bytes<uint512>(base), exp, load_from_bytes<uint512>(mod)));
177+
}
178+
else if (size <= 128)
179+
{
180+
res_bytes.resize(128);
181+
intx::be::unsafe::store(
182+
res_bytes.data(), modexp_impl(load_from_bytes<intx::uint<1024>>(base), exp,
183+
load_from_bytes<intx::uint<1024>>(mod)));
184+
}
185+
else if (size <= 256)
186+
{
187+
res_bytes.resize(256);
188+
intx::be::unsafe::store(
189+
res_bytes.data(), modexp_impl(load_from_bytes<intx::uint<2048>>(base), exp,
190+
load_from_bytes<intx::uint<2048>>(mod)));
191+
}
192+
else
193+
{
194+
assert(output_size <= 1024);
195+
res_bytes.resize(1024);
196+
intx::be::unsafe::store(
197+
res_bytes.data(), modexp_impl(load_from_bytes<intx::uint<8192>>(base), exp,
198+
load_from_bytes<intx::uint<8192>>(mod)));
199+
}
200+
201+
memcpy(output, &res_bytes[res_bytes.size() - output_size], output_size);
202+
return true;
203+
}
204+
205+
206+
} // namespace evmone::crypto

lib/evmone_precompiles/modexp.hpp

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// evmone: Fast Ethereum Virtual Machine implementation
2+
// Copyright 2025 The evmone Authors.
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
#pragma once
6+
#include <evmc/evmc.hpp>
7+
#include <intx/intx.hpp>
8+
9+
namespace evmone::crypto
10+
{
11+
bool modexp(uint8_t* output, size_t output_size, const evmc::bytes_view& base,
12+
const evmc::bytes_view& exp, const evmc::bytes_view& mod);
13+
}

test/state/CMakeLists.txt

-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ target_sources(
2626
precompiles.hpp
2727
precompiles.cpp
2828
precompiles_internal.hpp
29-
precompiles_stubs.hpp
30-
precompiles_stubs.cpp
3129
requests.hpp
3230
requests.cpp
3331
rlp.hpp

test/state/precompiles.cpp

+51-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
#include "precompiles.hpp"
66
#include "precompiles_internal.hpp"
7-
#include "precompiles_stubs.hpp"
87
#include <evmone_precompiles/blake2b.hpp>
98
#include <evmone_precompiles/bls.hpp>
109
#include <evmone_precompiles/bn254.hpp>
1110
#include <evmone_precompiles/kzg.hpp>
11+
#include <evmone_precompiles/modexp.hpp>
1212
#include <evmone_precompiles/ripemd160.hpp>
1313
#include <evmone_precompiles/secp256k1.hpp>
1414
#include <evmone_precompiles/sha256.hpp>
@@ -405,6 +405,55 @@ ExecutionResult identity_execute(const uint8_t* input, size_t input_size, uint8_
405405
return {EVMC_SUCCESS, input_size};
406406
}
407407

408+
ExecutionResult expmod_execute(const uint8_t* input, size_t input_size, uint8_t* output,
409+
[[maybe_unused]] size_t output_size) noexcept
410+
{
411+
static constexpr size_t input_header_required_size = 3 * sizeof(intx::uint256);
412+
uint8_t input_header[input_header_required_size]{};
413+
std::copy_n(input, std::min(input_size, input_header_required_size), input_header);
414+
const auto base_len = intx::be::unsafe::load<intx::uint256>(input_header);
415+
const auto exp_len = intx::be::unsafe::load<intx::uint256>(&input_header[32]);
416+
const auto mod_len = intx::be::unsafe::load<intx::uint256>(&input_header[64]);
417+
418+
// This is assured by the analysis.
419+
assert(output_size == mod_len);
420+
421+
if (output_size == 0)
422+
{
423+
return {EVMC_SUCCESS, output_size};
424+
}
425+
426+
// In this case the base, exp and modulus are 0. It means that the results is zero too.
427+
if (input_size < input_header_required_size)
428+
{
429+
memset(output, 0, output_size);
430+
return {EVMC_SUCCESS, output_size};
431+
}
432+
433+
// Extend input to full size (input_header_required_size + base_len + exp_len + mod_len).
434+
// Filled with zeros
435+
const auto full_input_size =
436+
input_header_required_size + static_cast<size_t>(base_len + exp_len + mod_len);
437+
evmc::bytes full_input(full_input_size, 0);
438+
std::copy_n(input, std::min(input_size, full_input_size), full_input.data());
439+
440+
evmone::crypto::modexp(output, output_size,
441+
{
442+
&full_input[input_header_required_size],
443+
static_cast<size_t>(base_len),
444+
},
445+
{
446+
&full_input[input_header_required_size + static_cast<size_t>(base_len)],
447+
static_cast<size_t>(exp_len),
448+
},
449+
{
450+
&full_input[input_header_required_size + static_cast<size_t>(base_len + exp_len)],
451+
static_cast<size_t>(mod_len),
452+
});
453+
454+
return {EVMC_SUCCESS, output_size};
455+
}
456+
408457
ExecutionResult blake2bf_execute(const uint8_t* input, [[maybe_unused]] size_t input_size,
409458
uint8_t* output, [[maybe_unused]] size_t output_size) noexcept
410459
{
@@ -573,7 +622,7 @@ inline constexpr auto traits = []() noexcept {
573622
{sha256_analyze, sha256_execute},
574623
{ripemd160_analyze, ripemd160_execute},
575624
{identity_analyze, identity_execute},
576-
{expmod_analyze, expmod_stub},
625+
{expmod_analyze, expmod_execute},
577626
{ecadd_analyze, ecadd_execute},
578627
{ecmul_analyze, ecmul_execute},
579628
{ecpairing_analyze, ecpairing_execute},

test/state/precompiles_internal.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ ExecutionResult ripemd160_execute(
4545
const uint8_t* input, size_t input_size, uint8_t* output, size_t output_size) noexcept;
4646
ExecutionResult identity_execute(
4747
const uint8_t* input, size_t input_size, uint8_t* output, size_t output_size) noexcept;
48+
ExecutionResult expmod_execute(
49+
const uint8_t* input, size_t input_size, uint8_t* output, size_t max_output_size) noexcept;
4850
ExecutionResult ecadd_execute(
4951
const uint8_t* input, size_t input_size, uint8_t* output, size_t output_size) noexcept;
5052
ExecutionResult ecmul_execute(

0 commit comments

Comments
 (0)