Skip to content

crypto: Use fixed-window exponentiation in modexp - #1618

Open
AskAlexSharov wants to merge 1 commit into
ipsilon:masterfrom
AskAlexSharov:alex/modexp-windowing
Open

crypto: Use fixed-window exponentiation in modexp#1618
AskAlexSharov wants to merge 1 commit into
ipsilon:masterfrom
AskAlexSharov:alex/modexp-windowing

Conversation

@AskAlexSharov

@AskAlexSharov AskAlexSharov commented Aug 1, 2026

Copy link
Copy Markdown

Summary

modexp_odd uses binary square-and-multiply — one Montgomery multiply per set exponent bit. For large exponents that is roughly twice the multiplies a windowed method needs.

This precomputes a small table of base powers (b^1 .. b^(2^w - 1) in Montgomery form) and consumes w exponent bits per multiply. The window width scales with the exponent size so the table cost stays amortized even for a sparse exponent:

  • w = 1 (plain binary, no table) for exponents ≤ 16 bits;
  • w = 2 up to 48 bits, w = 3 up to 144 bits, w = 4 above.

With w = 1 the loop is byte-for-byte the previous binary square-and-multiply.

Benchmarks

evmone-precompiles-bench --benchmark_filter='modexp<expmod_execute_evmone>', AMD EPYC 4344P, gcc 15.2.0, Release:

case before after speedup
mod_len:32 / exp_bits:256 19,604 ns 12,904 ns 1.52x
mod_len:32 / exp_bits:8192 650,488 ns 419,503 ns 1.55x
mod_len:504 / exp_bits:255 2,868,036 ns 1,869,504 ns 1.53x
mod_len:512 / exp_bits:8192 96,520,499 ns 60,619,147 ns 1.59x
mod_len:32 / exp_bits:33 2,582 ns 2,089 ns 1.24x

Small exponents (≤ 16 bits) are unchanged; none regress.

Cost

The power table adds MODEXP_TABLE_MAX * n words to the stack scratch buffer (STACK_CAPACITY and the modexp_odd scratch requirement are updated). At the EIP-7823 limit (n = 128 words) that is ~15 KB of additional stack in the single modexp frame.

The window widths and thresholds are simple, conservative choices; happy to tune them or switch to a sliding window (odd-power table, ~half the entries) if preferred.

Correctness

Adds expmod.windowing_vs_gmp: a differential test comparing evmone against GMP across many exponent bit-lengths (crossing the window-width thresholds) and bit patterns (all window values), for odd and even moduli. Existing expmod vectors and large_inputs continue to pass.

modexp_odd used binary square-and-multiply: one Montgomery multiply per set
exponent bit. For large exponents that is roughly twice the multiplies a
windowed method needs.

Precompute a small table of base powers (b^1 .. b^(2^w - 1) in Montgomery form)
and consume w exponent bits per multiply. The window width scales with the
exponent size (w = 1..4) so the table cost stays amortized even for a sparse
exponent, and small exponents keep the plain binary path (w = 1). With w = 1 the
loop is identical to the previous binary square-and-multiply.

Measured ~1.5-1.6x on large-exponent modexp (256-bit modulus, 256-bit exponent:
19.6us -> 12.9us; 4096-bit modulus, 8192-bit exponent: 96.5ms -> 60.6ms on an
AMD EPYC 4344P); smaller exponents also improve and none regress. The power
table adds MODEXP_TABLE_MAX*n words to the stack scratch buffer.

Add expmod.windowing_vs_gmp: a differential test against GMP over many exponent
bit-lengths and patterns, for odd and even moduli.
@AskAlexSharov
AskAlexSharov force-pushed the alex/modexp-windowing branch from ef07630 to 006ba7d Compare August 1, 2026 13:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant