1010
1111using namespace intx ;
1212
13+ namespace evmone ::crypto
14+ {
1315namespace
1416{
1517// / Adds y to x: x[] += y[]. The result is truncated to the size of x. Returns the carry bit.
@@ -37,12 +39,25 @@ constexpr void sub(std::span<uint64_t> x, std::span<const uint64_t> y) noexcept
3739 std::tie (x[i], borrow) = subc (x[i], uint64_t {0 }, borrow);
3840}
3941
42+ // / Multiplies multi-word x by single word y: r[] = x[] * y. Returns the carry word.
43+ constexpr uint64_t mul (std::span<uint64_t > r, std::span<const uint64_t > x, uint64_t y) noexcept
44+ {
45+ assert (r.size () == x.size ());
46+
47+ uint64_t c = 0 ;
48+ #pragma GCC unroll 4
49+ for (size_t i = 0 ; i != x.size (); ++i)
50+ {
51+ const auto p = umul (x[i], y) + c;
52+ r[i] = p[0 ];
53+ c = p[1 ];
54+ }
55+ return c;
56+ }
57+
4058// / Multiplies each word of x by y and adds the matching word of p, propagating a carry to the next
4159// / word. Starts with initial carry c. Stores the result in r. Returns the final carry.
4260// / r[] = p[] + x[] * y (+ c).
43- // / TODO: Consider [[always_inline]].
44- // / TODO: Consider template by the span extent.
45- // / TODO: Consider using pointers for some spans.
4661constexpr uint64_t addmul (std::span<uint64_t > r, std::span<const uint64_t > p,
4762 std::span<const uint64_t > x, uint64_t y, uint64_t c = 0 ) noexcept
4863{
@@ -67,18 +82,24 @@ constexpr void mul(
6782 assert (!x.empty ());
6883 assert (!y.empty ());
6984 assert (r.size () >= std::max (x.size (), y.size ()));
85+ assert (r.size () <= x.size () + y.size ()); // No support for zeroing r tail.
7086
7187 // Ensure y is the shorter one to simplify the implementation and to have shorter outer loop.
7288 if (x.size () < y.size ())
7389 std::swap (x, y);
7490
75- // Iterations where we store high product words (above x/y size).
76- const auto extra = std::min (y.size (), r.size () - x.size ());
91+ // First iteration: use mul (not addmul) since r is uninitialized.
92+ const auto hi0 = mul (r.first (x.size ()), x, y[0 ]);
93+ if (r.size () > x.size ())
94+ r[x.size ()] = hi0;
7795
78- std::ranges::fill (r, 0 );
79- for (size_t j = 0 ; j < extra; ++j)
96+ // Growing phase: each iteration produces a new high word at r[j + x.size()].
97+ const auto hi_iters = std::min (y.size (), r.size () - x.size ());
98+ for (size_t j = 1 ; j < hi_iters; ++j)
8099 r[j + x.size ()] = addmul (r.subspan (j, x.size ()), r.subspan (j, x.size ()), x, y[j]);
81- for (size_t j = extra; j < y.size (); ++j)
100+
101+ // Truncating phase: product is wider than r, discard high words.
102+ for (size_t j = std::max (hi_iters, size_t {1 }); j < y.size (); ++j)
82103 addmul (r.subspan (j), r.subspan (j), x.first (r.size () - j), y[j]);
83104}
84105
@@ -344,9 +365,20 @@ void mul_amm(std::span<uint64_t> r, std::span<const uint64_t> x, std::span<const
344365 const auto r_hi = r.subspan (1 );
345366 const auto mod_hi = mod.subspan (1 );
346367
347- std::ranges::fill (r, uint64_t { 0 });
368+ // First iteration: r is uninitialized, so use mul instead of addmul.
348369 bool r_carry = false ;
349- for (size_t i = 0 ; i != n; ++i)
370+ {
371+ const auto c1 = mul (r, x, y[0 ]);
372+
373+ const auto m = r[0 ] * mod_inv;
374+ const auto c2 = (umul (mod[0 ], m) + r[0 ])[1 ];
375+
376+ const auto c3 = addmul (r_lo, r_hi, mod_hi, m, c2);
377+ std::tie (r[n - 1 ], r_carry) = intx::addc (c1, c3);
378+ }
379+
380+ // Remaining iterations.
381+ for (size_t i = 1 ; i != n; ++i)
350382 {
351383 const auto c1 = addmul (r, r, x, y[i]);
352384 const auto [sum1, d1] = intx::addc (c1, uint64_t {r_carry});
@@ -509,8 +541,6 @@ void modinv_pow2(
509541
510542} // namespace
511543
512- namespace evmone ::crypto
513- {
514544void modexp (std::span<const uint8_t > base_bytes, std::span<const uint8_t > exp_bytes,
515545 std::span<const uint8_t > mod_bytes, uint8_t * output) noexcept
516546{
0 commit comments