@@ -374,7 +374,7 @@ void mul_amm(std::span<uint64_t> r, std::span<const uint64_t> y, std::span<const
374374}
375375
376376// / Computes result[] = base[]^exp % mod[] for odd mod[] (mod[0] % 2 != 0).
377- // / Scratch space required: 5n + 3*base.size() + 2 words, where n = mod.size().
377+ // / Scratch space required: 4n + 3*base.size() + 2 words, where n = mod.size().
378378void modexp_odd (std::span<uint64_t > result, std::span<const uint64_t > base, Exponent exp,
379379 std::span<const uint64_t > mod, std::span<uint64_t > scratch) noexcept
380380{
@@ -386,17 +386,16 @@ void modexp_odd(std::span<uint64_t> result, std::span<const uint64_t> base, Expo
386386 const auto n = mod.size ();
387387 const auto mod_inv = -evmmax::modinv (mod[0 ]);
388388
389- // Layout: u[n+base.size()] | base_mont[n] | t[n] | rem_scratch[ 2*(n+base.size())+2]
390- // After rem() returns, the rem_scratch is dead .
391- assert (scratch.size () >= 5 * n + 3 * base.size () + 2 );
389+ // Layout: u[n+base.size()] | base_mont[n] | t/rem_scratch[max(n, 2*(n+base.size())+2) ]
390+ // t and rem_scratch share the same region (exclusive lifetimes) .
391+ assert (scratch.size () >= 4 * n + 3 * base.size () + 2 );
392392
393393 // Compute base_mont = (base * R) % mod, where R = 2^(n*64).
394394 // The numerator u = base << (n*64): base in the upper words, lower n words are zero.
395395 const auto u = scratch.subspan (0 , n + base.size ());
396396 const auto base_mont = scratch.subspan (n + base.size (), n);
397- // TODO: t and rem_scratch have exclusive lifetimes.
398- const auto t = scratch.subspan (n + base.size () + n, n);
399- const auto rem_scratch = scratch.subspan (3 * n + base.size ());
397+ const auto t = scratch.subspan (2 * n + base.size (), n);
398+ const auto rem_scratch = scratch.subspan (2 * n + base.size (), 2 * n + 2 * base.size () + 2 );
400399
401400 std::ranges::fill (u.first (n), uint64_t {0 }); // Lower n words of u must be zero.
402401 std::ranges::copy (base, u.subspan (n).begin ());
@@ -429,14 +428,13 @@ void modexp_odd(std::span<uint64_t> result, std::span<const uint64_t> base, Expo
429428}
430429
431430// / Trims the multi-word number x[] to k bits.
432- // / TODO: Currently this assumes no leading zeros in x. Re-design this after modexp is dynamic.
433431void mask_pow2 (std::span<uint64_t > x, unsigned k) noexcept
434432{
435433 assert (k != 0 );
436- assert (x.size () > = (k + 63 ) / 64 );
437- assert (!x. empty ());
438- if ( const auto rem = k % 64 ; rem != 0 )
439- x.back () &= ( uint64_t {1 } << rem) - 1 ;
434+ assert (x.size () = = (k + 63 ) / 64 );
435+ // This implementation assumes the x.size() matches the k so we always mask the top word.
436+ // For k % 64 == 0, we don't mask anything.
437+ x.back () &= ~ uint64_t {0 } >> (-k % 64 ) ;
440438}
441439
442440// / Computes r[] = base[]^exp % 2^k.
@@ -527,10 +525,10 @@ void modexp(std::span<const uint8_t> base_bytes, std::span<const uint8_t> exp_by
527525
528526 // Bump allocator for all working memory (values + scratch).
529527 // Stack buffer covers inputs up to the EIP-7823 limit (1024 bytes).
530- // Capacity: values[b+2m] + op scratch[5m +3b+2] + CRT[m+2] = 4b+8m +4 words.
528+ // Capacity: values[b+2m] + op scratch[4m +3b+2] + CRT[m+2] = 4b+7m +4 words.
531529 // The worst case is an even modulus with 1 trailing zero bit (odd_size=m, pow2_size=1).
532530 static constexpr size_t MAX_SIZE = 1024 / sizeof (uint64_t ); // EIP-7823
533- static constexpr size_t STACK_CAPACITY = 4 * MAX_SIZE + 8 * MAX_SIZE + 4 ;
531+ static constexpr size_t STACK_CAPACITY = 4 * MAX_SIZE + 7 * MAX_SIZE + 4 ;
534532 alignas (uint64_t ) std::byte stack_buf[STACK_CAPACITY * sizeof (uint64_t )];
535533 std::pmr::monotonic_buffer_resource pool{stack_buf, sizeof (stack_buf)};
536534 std::pmr::polymorphic_allocator<uint64_t > alloc{&pool};
@@ -571,7 +569,7 @@ void modexp(std::span<const uint8_t> base_bytes, std::span<const uint8_t> exp_by
571569 const auto need_crt = !pow2_is_trivial && !odd_is_trivial;
572570
573571 // Allocate operation scratch (dead after each call, reused sequentially).
574- const size_t odd_scratch = !odd_is_trivial ? 5 * odd_size + 3 * base.size () + 2 : 0 ;
572+ const size_t odd_scratch = !odd_is_trivial ? 4 * odd_size + 3 * base.size () + 2 : 0 ;
575573 const size_t pow2_scratch = !pow2_is_trivial ? pow2_size : 0 ;
576574 const size_t inv_scratch = need_crt ? 2 * pow2_size : 0 ;
577575 const size_t op_scratch_size = std::max ({odd_scratch, pow2_scratch, inv_scratch});
0 commit comments