Skip to content

Commit e3c97f1

Browse files
committed
evmmax: Implement modexp
1 parent 44f2c85 commit e3c97f1

10 files changed

+357
-394
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

+34-7
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>
@@ -299,7 +299,7 @@ ExecutionResult ripemd160_execute(const uint8_t* input, size_t input_size, uint8
299299
return {EVMC_SUCCESS, 32};
300300
}
301301

302-
static ExecutionResult expmod_execute(
302+
ExecutionResult expmod_execute(
303303
const uint8_t* input, size_t input_size, uint8_t* output, size_t output_size) noexcept
304304
{
305305
static constexpr auto LEN_SIZE = sizeof(intx::uint256);
@@ -312,11 +312,38 @@ static ExecutionResult expmod_execute(
312312
return {EVMC_SUCCESS, output_size};
313313
}
314314

315-
#ifdef EVMONE_PRECOMPILES_SILKPRE
316-
return silkpre_expmod_execute(input, input_size, output, output_size);
317-
#else
318-
return expmod_stub(input, input_size, output, output_size);
319-
#endif
315+
static constexpr size_t input_header_required_size = 3 * LEN_SIZE;
316+
uint8_t input_header[input_header_required_size]{};
317+
std::copy_n(input, std::min(input_size, input_header_required_size), input_header);
318+
const auto base_len = intx::be::unsafe::load<intx::uint256>(input_header);
319+
const auto exp_len = intx::be::unsafe::load<intx::uint256>(&input_header[32]);
320+
const auto mod_len = intx::be::unsafe::load<intx::uint256>(&input_header[64]);
321+
322+
// This is assured by the analysis.
323+
assert(output_size == mod_len);
324+
325+
// Extend input to full size (input_header_required_size + base_len + exp_len + mod_len).
326+
// Filled with zeros
327+
const auto full_input_size =
328+
input_header_required_size + static_cast<size_t>(base_len + exp_len + mod_len);
329+
evmc::bytes full_input(full_input_size, 0);
330+
std::copy_n(input, std::min(input_size, full_input_size), full_input.data());
331+
332+
evmone::crypto::modexp(output, output_size,
333+
{
334+
&full_input[input_header_required_size],
335+
static_cast<size_t>(base_len),
336+
},
337+
{
338+
&full_input[input_header_required_size + static_cast<size_t>(base_len)],
339+
static_cast<size_t>(exp_len),
340+
},
341+
{
342+
&full_input[input_header_required_size + static_cast<size_t>(base_len + exp_len)],
343+
static_cast<size_t>(mod_len),
344+
});
345+
346+
return {EVMC_SUCCESS, output_size};
320347
}
321348

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

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)