Add small-value delayed reduction with Barrett algorithm#111
Add small-value delayed reduction with Barrett algorithm#111wu-s-john wants to merge 18 commits into
Conversation
Add support for accumulating field × small_int products (i32, i64, i128) with delayed modular reduction using generic Barrett reduction: - SmallValueField<V> trait for small integer ↔ field conversion - WideMul trait for widening multiplication - BarrettReductionConstants with compile-time computed μ = ⌊2^512/p⌋ - SignedWideLimbs<N> accumulator for signed product sums - DelayedReduction<i32/i64/i128> implementations for all fields
|
@wu-s-john, here are some bugs found by Copilot. BUG 1 (Critical) — barrett_reduce_7 drops carry, produces wrong result File: src/big_num/barrett.rs:125 let (s5, _) = c[5].carrying_add(0, cy); // carry DISCARDED The fold step computes c[0..6] + c[6] * R384_MOD but discards the carry out of position 5. I constructed a concrete input proving the result is wrong: For T256 with a 7-limb value near 2 × 2^384 − 1 (c[6]=1, c[0..6]≈2^384−1):
This is reachable with ~5 max-magnitude field × i128 products on T256/P256 (where R384_MOD ≈ 2^256). Any sumcheck accumulation with a handful of large same-sign products can trigger this. Fix: After the fold addition, if there's a carry, add R384_MOD to the 6-limb result (since the dropped carry represents 2^384 ≡ R384_MOD (mod p)): let (s5, extra_carry) = c[5].carrying_add(0, cy); Alternatively, fold into a 7-limb temporary and call a wider Barrett, or reduce before folding. BUG 2 (Medium) — accumulate_field_times_i128 panics on i128::MIN File: src/big_num/delayed_reduction.rs:98 (&mut acc.neg, (-*value) as u128) -i128::MIN overflows in signed i128 arithmetic. In debug builds, this panics. In release, it wraps and happens to give the correct magnitude by coincidence (bit reinterpretation). The i32 and i64 impls in macros.rs correctly use wrapping_neg(). This one does not. Fix: (&mut acc.neg, (*value).wrapping_neg() as u128) BUG 3 (Medium) — small_to_field for i32 panics on i32::MIN, wrong in release File: src/big_num/macros.rs:130 -Self::from((-val) as u64) For val = i32::MIN:
The i64 impl (i64_to_field) correctly uses val.wrapping_neg() as u64. Fix: -Self::from((val as i64).wrapping_neg() as u64) |
There was a problem hiding this comment.
Pull request overview
This PR adds big-number infrastructure to support delayed modular reduction for field × small-integer accumulations using a generic μ-Barrett reduction path, enabling upcoming small-value sumcheck optimizations.
Changes:
- Add
BarrettReductionConstants+ compile-time constant generation and provider wiring for BN254/Pasta/P256/T256. - Introduce generic
barrett_reduce_6/barrett_reduce_7reducers and signed wide accumulators to support delayed reduction of signed small values. - Add
SmallValueField/WideMulhelpers and corresponding test macros and provider test coverage.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/provider/pt256.rs | Wires Barrett constants, small-value conversions, delayed reduction, and tests for P256/T256 scalars. |
| src/provider/pasta.rs | Wires Barrett constants, small-value conversions, delayed reduction, and tests for Pasta scalars. |
| src/provider/bn254.rs | Wires Barrett constants, small-value conversions, delayed reduction, and tests for BN254 scalar. |
| src/errors.rs | Adds SmallValueOverflow error for failed small-int conversions. |
| src/big_num/wide_mul.rs | Adds widening-multiplication trait for small integer products. |
| src/big_num/small_value_field.rs | Adds SmallValueField trait + helpers and vec_to_small conversion routine. |
| src/big_num/mod.rs | Registers new modules and re-exports new traits/types. |
| src/big_num/macros.rs | Adds macros for Barrett constants, small-value field conversions, and delayed reduction impls. |
| src/big_num/limbs.rs | Adds SignedWideLimbs, sub_mag, and additional limb ops supporting new reducers/accumulators. |
| src/big_num/field_reduction_constants.rs | Introduces BarrettReductionConstants trait and test helpers for Barrett constants. |
| src/big_num/delayed_reduction.rs | Adds accumulation helpers for field×u64/u128 and test macros for small-value delayed reduction. |
| src/big_num/barrett.rs | Implements Barrett reduction for 6- and 7-limb inputs and associated tests/macros. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
PR: Add small-value delayed reduction with Barrett algorithm
Summary
This PR adds infrastructure for delayed modular reduction when accumulating field × small-integer products. This is a prerequisite for the upcoming small-value sumcheck optimization, which is a significantly larger change.
Motivation
In the sumcheck protocol, we frequently accumulate sums of products: Σ(field × value). Previously, we supported delayed reduction only for field × field products:
However, small-value sumcheck uses coefficients that fit in native integers (i32, i64, i128). These are not in Montgomery form:
Why Barrett Reduction?
Montgomery reduction computes
x × R⁻¹ mod p. When both operands are in Montgomery form,(a·R) × (b·R) = ab·R², and REDC givesab·R— exactly what we want.Barrett reduction computes
x mod pdirectly, without any R factor. When accumulating(field·R) × small_int, the product isfield·R·small_int. Barrett gives usfield·small_int mod pin Montgomery form — exactly right.The algorithm replaces expensive division by multiplication with a precomputed reciprocal:
Implementation Efficiency
The implementation is optimal with at most 1 conditional subtract for finalization:
2p < 2^256): The remainder fits in 4 limbs, enabling a fast path withmul_3x4_lo4and a singleif r ≥ p: r -= p2p ≈ 2^256): Uses 5-limb arithmetic with one correctionThe bound is tight because:
qsatisfiesq ≤ ⌊x/p⌋ ≤ q + 2r = x - q·psatisfies0 ≤ r < 3p0 ≤ r < 2p0 ≤ r < p✓In practice for our field sizes, a single correction suffices (verified by
debug_assert!).Verification of Barrett Constants
All precomputed constants are verified at test time against reference implementations using
num-bigint:BARRETT_MU⌊2^512 / p⌋using BigUint, compared against hardcoded limbsR384_MOD2^384 mod pusing BigUint, compared against hardcoded limbsUSE_4_LIMB_BARRETTdebug_assert!The test helpers in
field_reduction_constants.rsimplement this:Each field provider (BN254, Pasta, P256, T256) runs these tests via macros:
Additionally, the Barrett reduction itself is tested against field arithmetic:
reduce(field × small) == field * F::from(small)reduce(Σ field_i × small_i) == Σ (field_i * F::from(small_i))Future Work: Specialized Pasta Reduction (Already Implemented)
The Pasta curves (Pallas and Vesta) have primes in a special pseudo-Mersenne form that enables significantly faster reduction. From
src/provider/pasta.rs:Breaking down the structure in 64-bit limbs (little-endian):
Both primes have the form
p = 2²⁵⁴ + εwhere:limb[3] = 0x4000000000000000contributes exactly2^62 × 2^192 = 2^254limb[2] = 0(the 128-191 bit range is empty)ε = limb[0] + limb[1] × 2^64spans only ~125 bitsThis structure enables Solinas reduction, which exploits the congruence
2^254 ≡ -ε (mod p):This is faster than generic Barrett because:
Note: The specialized
pasta_reduce_6implementation is already complete and tested. It will be included in a follow-up PR after this one is approved and merged, to keep the review scope manageable.What's Included
BarrettReductionConstantsbarrett_reduce_6/7SignedWideLimbs<N>SmallValueField<V>DelayedReduction<i32/i64/i128>Overflow Capacity
WideLimbs<6>SignedWideLimbs<7>Sumcheck polynomials are bounded by practical sizes (≤2^40), so overflow is impossible.
Why Split This Out?
The small-value sumcheck PR that uses this infrastructure is substantially larger. Splitting the Barrett reduction foundation into its own PR makes review more manageable and establishes a clean abstraction boundary.