Skip to content

Commit fceb387

Browse files
authored
crypto: Refactor mul_amm to separate output from inputs (#1471)
Change mul_amm signature so that the output r is separate from the const inputs x and y (r must not alias either). This removes the internal t buffer parameter — mul_amm now writes directly to r. The caller uses a double-buffer technique: r_cur holds the current value, r_tmp receives the next mul_amm result. Squaring always writes to r_tmp; if a multiply follows, it writes back to r_cur directly, otherwise the spans are swapped. This eliminates the per-call copy from the old implementation. Also trim the result span passed to modexp_odd to match mod size exactly, and remove the trailing-zero fill (the caller pre-zeros the full result buffer).
1 parent 634cd06 commit fceb387

1 file changed

Lines changed: 41 additions & 35 deletions

File tree

lib/evmone_precompiles/modexp.cpp

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)