@@ -317,19 +317,61 @@ ExecutionResult expmod_execute(
317317 const uint8_t * input, size_t input_size, uint8_t * output, size_t output_size) noexcept
318318{
319319 static constexpr auto LEN_SIZE = sizeof (intx::uint256);
320+ static constexpr auto HEADER_SIZE = 3 * LEN_SIZE;
321+ static constexpr auto LEN32_OFF = LEN_SIZE - sizeof (uint32_t );
320322
321323 // The output size equal to the modulus size.
324+ const auto mod_len = output_size;
325+
322326 // Handle short incomplete input up front. The answer is 0 of the length of the modulus.
323- if (output_size == 0 || input_size <= 3 * LEN_SIZE) [[unlikely]]
327+ if (input_size <= HEADER_SIZE) [[unlikely]]
328+ {
329+ std::fill_n (output, output_size, 0 );
330+ return {EVMC_SUCCESS, output_size};
331+ }
332+
333+ const auto base_len = intx::be::unsafe::load<uint32_t >(&input[LEN32_OFF]);
334+ const auto exp_len = intx::be::unsafe::load<uint32_t >(&input[LEN_SIZE + LEN32_OFF]);
335+ assert (intx::be::unsafe::load<uint32_t >(&input[2 * LEN_SIZE + LEN32_OFF]) == mod_len);
336+
337+ const bytes_view payload{input + HEADER_SIZE, input_size - HEADER_SIZE};
338+ const size_t mod_off = base_len + exp_len; // Cannot overflow if gas cost computed before.
339+ const auto mod_explicit = payload.substr (std::min (mod_off, payload.size ()), mod_len);
340+
341+ // Handle the mod being zero early.
342+ // This serves two purposes:
343+ // - bigint libraries don't like zero modulus because division by 0 is not well-defined,
344+ // - having non-zero modulus guarantees that base and exp aren't out-of-bounds.
345+ if (mod_explicit.find_first_not_of (uint8_t {0 }) == bytes_view::npos) [[unlikely]]
324346 {
347+ // The modulus is zero, so the result is zero.
325348 std::fill_n (output, output_size, 0 );
326349 return {EVMC_SUCCESS, output_size};
327350 }
328351
352+ const auto mod_requires_padding = mod_explicit.size () != mod_len;
353+ if (mod_requires_padding) [[unlikely]]
354+ {
355+ // The modulus is the last argument and some of its bytes may be missing and be implicitly
356+ // zero. In this case, copy the explicit modulus bytes to the output buffer and pad the rest
357+ // with zeroes. The output buffer is guaranteed to have exactly the modulus size.
358+ const auto [_, output_p] = std::ranges::copy (mod_explicit, output);
359+ std::fill (output_p, output + output_size, 0 );
360+ }
361+
362+ const auto base = payload.substr (0 , base_len);
363+ const auto exp = payload.substr (base_len, exp_len);
364+ const auto mod = mod_requires_padding ? bytes_view{output, mod_len} : mod_explicit;
365+
329366#ifdef EVMONE_PRECOMPILES_SILKPRE
367+ (void )base;
368+ (void )exp;
369+ (void )mod;
370+ // For Silkpre use the raw input for compatibility.
330371 return silkpre_expmod_execute (input, input_size, output, output_size);
331372#else
332- return expmod_stub (input, input_size, output, output_size);
373+ expmod_stub (base, exp, mod, output);
374+ return {EVMC_SUCCESS, mod.size ()};
333375#endif
334376}
335377
0 commit comments