Skip to content

Commit fabed90

Browse files
authored
crypto: Use actual mod_size instead of declared byte-padded size (#1475)
The modexp function was using mod_size derived from the byte length of the input (declared_mod_size), which includes leading zero padding. This caused the CRT combine's mul(result, mod_odd, y) to receive an oversized result buffer, violating the mul() assert that r.size() <= x.size() + y.size(). Return the actual trimmed mod_size from load_mod and use it for the CRT mul/add operations. The result buffer is still allocated at declared_mod_size (needed for store()), but CRT operations use the trimmed mod_size. Add regression test: 3^3 mod 12 encoded as 32 bytes (4 words padded, but value only needs 1 word).
1 parent 072e9cf commit fabed90

2 files changed

Lines changed: 17 additions & 9 deletions

File tree

lib/evmone_precompiles/modexp.cpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ constexpr void mul(
3636
assert(!x.empty());
3737
assert(!y.empty());
3838
assert(r.size() >= std::max(x.size(), y.size()));
39-
assert(r.size() <= x.size() + y.size()); // No support for zeroing r tail.
39+
assert(r.size() <= x.size() + y.size()); // No tail zeroing: r may truncate but not exceed.
4040

4141
// Ensure y is the shorter one to simplify the implementation and to have shorter outer loop.
4242
if (x.size() < y.size())
@@ -148,11 +148,12 @@ std::span<const uint64_t> shr(
148148
return trim(r.first(n));
149149
}
150150

151-
/// Result of loading the modulus: the odd part and trailing zero count.
151+
/// Result of loading the modulus: the odd part, trailing zero count, and total word size.
152152
struct ModLoad
153153
{
154154
std::span<const uint64_t> mod_odd; ///< Trimmed odd part (shifted in-place).
155155
unsigned mod_tz; ///< Total trailing zero bits (0 = odd modulus).
156+
size_t mod_size; ///< Total significant word count of the modulus.
156157
};
157158

158159
/// Loads modulus from big-endian bytes and extracts the odd part.
@@ -171,10 +172,10 @@ ModLoad load_mod(std::span<uint64_t> storage, std::span<const uint8_t> data) noe
171172
const auto mod_tz = static_cast<unsigned>(tz_words * 64 + bit_shift);
172173

173174
if (mod_tz == 0)
174-
return {top, 0};
175+
return {top, 0, top.size()};
175176

176177
// Right-shift in-place to extract the odd part.
177-
return {shr(storage, top, mod_tz), mod_tz};
178+
return {shr(storage, top, mod_tz), mod_tz, top.size()};
178179
}
179180

180181

@@ -519,8 +520,8 @@ void modexp(std::span<const uint8_t> base_bytes, std::span<const uint8_t> exp_by
519520
{
520521
const Exponent exp{exp_bytes};
521522

522-
const auto base_size = (base_bytes.size() + 7) / 8;
523-
const auto mod_size = (mod_bytes.size() + 7) / 8;
523+
const auto declared_base_size = (base_bytes.size() + 7) / 8;
524+
const auto declared_mod_size = (mod_bytes.size() + 7) / 8;
524525

525526
// Bump allocator for all working memory (values + scratch).
526527
// Stack buffer covers inputs up to the EIP-7823 limit (1024 bytes).
@@ -532,10 +533,13 @@ void modexp(std::span<const uint8_t> base_bytes, std::span<const uint8_t> exp_by
532533
std::pmr::monotonic_buffer_resource pool{stack_buf, sizeof(stack_buf)};
533534
std::pmr::polymorphic_allocator<uint64_t> alloc{&pool};
534535

535-
// Allocate and load values.
536-
const auto base = load({alloc.allocate(base_size), base_size}, base_bytes);
537-
const auto [mod_odd, mod_tz] = load_mod({alloc.allocate(mod_size), mod_size}, mod_bytes);
536+
// Allocate and load values. The actual mod_size may be smaller than declared
537+
// if the modulus has leading zero bytes.
538+
const auto base = load({alloc.allocate(declared_base_size), declared_base_size}, base_bytes);
539+
const auto [mod_odd, mod_tz, mod_size] =
540+
load_mod({alloc.allocate(declared_mod_size), declared_mod_size}, mod_bytes);
538541
assert(!mod_odd.empty()); // Modulus of zero must be handled outside.
542+
// Result sized to actual mod value, not declared byte length. store() zero-fills leading bytes.
539543
const auto result = std::span{alloc.allocate(mod_size), mod_size};
540544
std::ranges::fill(result, uint64_t{0});
541545

test/unittests/precompiles_expmod_test.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,10 @@ TEST_P(expmod, inputs)
273273
// Small base with multi-word even modulus (even path: trimmed mod shorter than w).
274274
// 3^3 mod 12, where 12 is encoded as 16 bytes.
275275
{"03", "03", "0000000000000000000000000000000c", "00000000000000000000000000000003"},
276+
// Even modulus with leading zeros: 3^3 mod 12, where 12 is encoded as 32 bytes.
277+
// CRT product size (odd_size + pow2_size = 2) < declared_mod_size (4 words).
278+
{"03", "03", "000000000000000000000000000000000000000000000000000000000000000c",
279+
"0000000000000000000000000000000000000000000000000000000000000003"},
276280
// Small base with even modulus having large pow2 factor
277281
// (even/pow2 path: base shorter than num_pow2_words).
278282
// 3^5 mod (5 * 2^128) = 243.

0 commit comments

Comments
 (0)