@@ -325,42 +325,44 @@ class Exponent
325325// /
326326// / See "Efficient Software Implementations of Modular Exponentiation":
327327// / https://eprint.iacr.org/2011/239.pdf
328- void mul_amm (std::span<uint64_t > r, std::span<const uint64_t > y, std::span<const uint64_t > mod,
329- uint64_t mod_inv, std::span<uint64_t > t) noexcept
328+ // /
329+ // / Computes r = x * y * R^-1 mod m (Almost Montgomery Multiplication).
330+ // / r must not alias x or y.
331+ void mul_amm (std::span<uint64_t > r, std::span<const uint64_t > x, std::span<const uint64_t > y,
332+ std::span<const uint64_t > mod, uint64_t mod_inv) noexcept
330333{
331334 // Use Coarsely Integrated Operand Scanning (CIOS) method with the "almost" reduction.
332335 const auto n = r.size ();
333336 assert (n > 0 );
337+ assert (x.size () == n);
334338 assert (y.size () == n);
335339 assert (mod.size () == n);
336- assert (t.size () == n);
337340 assert (mod.back () != 0 );
341+ assert (r.data () != x.data () && r.data () != y.data ()); // r must not alias inputs.
338342
339- const auto t_lo = t .subspan (0 , n - 1 );
340- const auto t_hi = t .subspan (1 );
343+ const auto r_lo = r .subspan (0 , n - 1 );
344+ const auto r_hi = r .subspan (1 );
341345 const auto mod_hi = mod.subspan (1 );
342346
343- std::ranges::fill (t , uint64_t {0 });
344- bool t_carry = false ;
347+ std::ranges::fill (r , uint64_t {0 });
348+ bool r_carry = false ;
345349 for (size_t i = 0 ; i != n; ++i)
346350 {
347- const auto c1 = addmul (t, t, r , y[i]);
348- const auto [sum1, d1] = intx::addc (c1, uint64_t {t_carry });
351+ const auto c1 = addmul (r, r, x , y[i]);
352+ const auto [sum1, d1] = intx::addc (c1, uint64_t {r_carry });
349353
350- const auto m = t [0 ] * mod_inv;
351- const auto c2 = (umul (mod[0 ], m) + t [0 ])[1 ];
354+ const auto m = r [0 ] * mod_inv;
355+ const auto c2 = (umul (mod[0 ], m) + r [0 ])[1 ];
352356
353- const auto c3 = addmul (t_lo, t_hi , mod_hi, m, c2);
357+ const auto c3 = addmul (r_lo, r_hi , mod_hi, m, c2);
354358 const auto [sum2, d2] = intx::addc (sum1, c3);
355- t [n - 1 ] = sum2;
356- assert (!(d1 && d2)); // At most one carry should be set.
357- t_carry = d1 || d2;
359+ r [n - 1 ] = sum2;
360+ assert (!(d1 && d2));
361+ r_carry = d1 || d2;
358362 }
359363
360- if (t_carry) // Reduce if t >= R.
361- sub (t, mod);
362-
363- std::ranges::copy (t, r.begin ());
364+ if (r_carry)
365+ sub (r, mod);
364366}
365367
366368// / Computes result[] = base[]^exp % mod[] for odd mod[] (mod[0] % 2 != 0).
@@ -370,7 +372,7 @@ void modexp_odd(std::span<uint64_t> result, std::span<const uint64_t> base, Expo
370372{
371373 assert (!mod.empty () && mod.back () != 0 ); // mod must be trimmed.
372374 assert (!base.empty () && base.back () != 0 ); // base must be trimmed.
373- assert (! result.empty ());
375+ assert (result.size () == mod. size ());
374376 assert (exp.bit_width () != 0 );
375377
376378 const auto n = mod.size ();
@@ -384,37 +386,40 @@ void modexp_odd(std::span<uint64_t> result, std::span<const uint64_t> base, Expo
384386 // The numerator u = base << (n*64): base in the upper words, lower n words are zero.
385387 const auto u = scratch.subspan (0 , n + base.size ());
386388 const auto base_mont = scratch.subspan (n + base.size (), n);
387- const auto t = scratch.subspan (2 * n + base.size (), n);
388389 const auto rem_scratch = scratch.subspan (2 * n + base.size (), 2 * n + 2 * base.size () + 2 );
389390
390391 std::ranges::fill (u.first (n), uint64_t {0 }); // Lower n words of u must be zero.
391392 std::ranges::copy (base, u.subspan (n).begin ());
392393 rem (base_mont, u, mod, rem_scratch);
393394
394- // Reuse the lower n words of u as the result buffer r.
395- const auto r = u.subspan (0 , n);
395+ // Double-buffer: r1 always holds the current value, r2 is scratch for mul_amm output.
396+ auto r_cur = result;
397+ auto r_tmp = u.first (n); // Reuse u scratch space.
398+ std::ranges::copy (base_mont, r_cur.begin ());
396399
397- std::ranges::copy (base_mont, r.begin ());
398400 for (auto i = exp.bit_width () - 1 ; i != 0 ; --i)
399401 {
400- mul_amm (r, r, mod, mod_inv, t);
402+ mul_amm (r_tmp, r_cur, r_cur, mod, mod_inv); // Square: r2 = r1 * r1.
401403 if (exp[i - 1 ])
402- mul_amm (r, base_mont, mod, mod_inv, t);
404+ mul_amm (r_cur, r_tmp, base_mont, mod, mod_inv); // Multiply: r1 = r2 * base_mont.
405+ else
406+ std::swap (r_cur, r_tmp); // No multiply: adopt r2 as r1.
403407 }
404408
405- // Convert the result from Montgomery form by multiplying with 1.
406- // Reuse base_mont as ONE (it is no longer needed after the loop).
409+ // Convert from Montgomery form: multiply by 1.
407410 std::ranges::fill (base_mont, uint64_t {0 });
408411 base_mont[0 ] = 1 ;
409- mul_amm (r, base_mont, mod, mod_inv, t);
412+ mul_amm (r_tmp, r_cur, base_mont, mod, mod_inv);
413+ std::swap (r_cur, r_tmp);
410414
411415 // Reduce if necessary: AMM can produce mod <= r < 2*mod.
412- if (!less (r , mod))
413- sub (r , mod);
414- assert (less (r , mod));
416+ if (!less (r_cur , mod))
417+ sub (r_cur , mod);
418+ assert (less (r_cur , mod));
415419
416- const auto [_, out] = std::ranges::copy (r, result.begin ());
417- std::ranges::fill (std::span{out, result.end ()}, uint64_t {0 });
420+ // If the result ended up in the scratch buffer, copy to result.
421+ if (r_cur.data () != result.data ())
422+ std::ranges::copy (r_cur, result.begin ());
418423}
419424
420425// / Trims the multi-word number x[] to k bits.
@@ -567,7 +572,8 @@ void modexp(std::span<const uint8_t> base_bytes, std::span<const uint8_t> exp_by
567572 const auto op_scratch = std::span{alloc.allocate (op_scratch_size), op_scratch_size};
568573
569574 // Place the odd result directly in the result buffer if the CRT is not needed.
570- const auto result_odd = need_crt ? std::span{alloc.allocate (odd_size), odd_size} : result;
575+ const auto result_odd =
576+ need_crt ? std::span{alloc.allocate (odd_size), odd_size} : result.first (odd_size);
571577 // Always place the power-of-two result in the result buffer.
572578 const auto result_pow2 = result.first (pow2_size);
573579
0 commit comments