Skip to main content

p3_field/
helpers.rs

1use alloc::vec;
2use alloc::vec::Vec;
3use core::iter::Sum;
4use core::mem::MaybeUninit;
5use core::ops::{Add, Mul};
6
7use num_bigint::BigUint;
8use p3_maybe_rayon::prelude::*;
9
10use crate::field::Field;
11use crate::{PackedValue, PrimeCharacteristicRing, PrimeField, PrimeField32};
12
13/// Computes a multiplicative subgroup whose order is known in advance.
14pub fn cyclic_subgroup_known_order<F: Field>(
15    generator: F,
16    order: usize,
17) -> impl Iterator<Item = F> + Clone {
18    generator.powers().take(order)
19}
20
21/// Computes a coset of a multiplicative subgroup whose order is known in advance.
22pub fn cyclic_subgroup_coset_known_order<F: Field>(
23    generator: F,
24    shift: F,
25    order: usize,
26) -> impl Iterator<Item = F> + Clone {
27    generator.shifted_powers(shift).take(order)
28}
29
30/// Scales each element of the slice by `s` using packing.
31///
32/// # Performance
33/// For large slices, use [`par_scale_slice_in_place`].
34pub fn scale_slice_in_place_single_core<F: Field>(slice: &mut [F], s: F) {
35    let (packed, sfx) = F::Packing::pack_slice_with_suffix_mut(slice);
36    let packed_s: F::Packing = s.into();
37    packed.iter_mut().for_each(|x| *x *= packed_s);
38    sfx.iter_mut().for_each(|x| *x *= s);
39}
40
41/// Scales each element of the slice by `s` using packing and parallelization.
42///
43/// # Performance
44/// For small slices, use [`scale_slice_in_place_single_core`].
45/// Requires the `parallel` feature.
46#[inline]
47pub fn par_scale_slice_in_place<F: Field>(slice: &mut [F], s: F) {
48    let (packed, sfx) = F::Packing::pack_slice_with_suffix_mut(slice);
49    let packed_s: F::Packing = s.into();
50    packed.par_iter_mut().for_each(|x| *x *= packed_s);
51    sfx.iter_mut().for_each(|x| *x *= s);
52}
53
54/// Adds `other`, scaled by `s`, to the mutable `slice` using packing, or `slice += other * s`.
55///
56/// # Performance
57/// For large slices, use [`par_add_scaled_slice_in_place`].
58pub fn add_scaled_slice_in_place<F: Field>(slice: &mut [F], other: &[F], s: F) {
59    debug_assert_eq!(slice.len(), other.len(), "slices must have equal length");
60    let (slice_packed, slice_sfx) = F::Packing::pack_slice_with_suffix_mut(slice);
61    let (other_packed, other_sfx) = F::Packing::pack_slice_with_suffix(other);
62    let packed_s: F::Packing = s.into();
63    slice_packed
64        .iter_mut()
65        .zip(other_packed)
66        .for_each(|(x, y)| *x += *y * packed_s);
67    slice_sfx
68        .iter_mut()
69        .zip(other_sfx)
70        .for_each(|(x, y)| *x += *y * s);
71}
72
73/// Adds `other`, scaled by `s`, to the mutable `slice` using packing, or `slice += other * s`.
74///
75/// # Performance
76/// For small slices, use [`add_scaled_slice_in_place`].
77/// Requires the `parallel` feature.
78pub fn par_add_scaled_slice_in_place<F: Field>(slice: &mut [F], other: &[F], s: F) {
79    debug_assert_eq!(slice.len(), other.len(), "slices must have equal length");
80    let (slice_packed, slice_sfx) = F::Packing::pack_slice_with_suffix_mut(slice);
81    let (other_packed, other_sfx) = F::Packing::pack_slice_with_suffix(other);
82    let packed_s: F::Packing = s.into();
83    slice_packed
84        .par_iter_mut()
85        .zip(other_packed.par_iter())
86        .for_each(|(x, y)| *x += *y * packed_s);
87    slice_sfx
88        .iter_mut()
89        .zip(other_sfx)
90        .for_each(|(x, y)| *x += *y * s);
91}
92
93/// Extend a ring `R` element `x` to an array of length `D`
94/// by filling zeros.
95#[inline]
96#[must_use]
97pub const fn field_to_array<R: PrimeCharacteristicRing, const D: usize>(x: R) -> [R; D] {
98    let mut arr = [const { MaybeUninit::uninit() }; D];
99    arr[0] = MaybeUninit::new(x);
100    let mut i = 1;
101    while i < D {
102        arr[i] = MaybeUninit::new(R::ZERO);
103        i += 1;
104    }
105    // SAFETY: every element of `arr` has been initialized above, so reinterpreting
106    // `[MaybeUninit<R>; D]` as `[R; D]` is sound.
107    unsafe { core::mem::transmute_copy::<_, [R; D]>(&arr) }
108}
109
110/// Given an element x from a 32 bit field F_P compute x/2.
111#[inline]
112#[must_use]
113pub const fn halve_u32<const P: u32>(x: u32) -> u32 {
114    let shift = (P + 1) >> 1;
115    let half = x >> 1;
116    if x & 1 == 0 { half } else { half + shift }
117}
118
119/// Given an element x from a 64 bit field F_P compute x/2.
120#[inline]
121#[must_use]
122pub const fn halve_u64<const P: u64>(x: u64) -> u64 {
123    let shift = (P + 1) >> 1;
124    let half = x >> 1;
125    if x & 1 == 0 { half } else { half + shift }
126}
127
128/// Reduce a slice of 32-bit field elements into a single element of a larger field.
129///
130/// Uses base-$2^{32}$ decomposition:
131///
132/// ```math
133/// \begin{equation}
134///     \text{reduce\_32}(vals) = \sum_{i=0}^{n-1} a_i \cdot 2^{32i}
135/// \end{equation}
136/// ```
137///
138/// Equivalent to [`reduce_packed`] with `radix_bits = 32`.
139#[must_use]
140pub fn reduce_32<SF: PrimeField32, TF: PrimeField>(vals: &[SF]) -> TF {
141    reduce_packed(vals, 32)
142}
143
144/// Horner-evaluate `vals` as base-$2^{radix\_bits}$ digits into `TF`, shifting each digit by `+1`.
145///
146/// This reserves zero as an out-of-band "no digit" value, so sequences of different lengths remain
147/// distinct when packed into a fixed-width slot.
148#[must_use]
149pub fn reduce_packed_shifted<SF: PrimeField32, TF: PrimeField>(vals: &[SF], radix_bits: u32) -> TF {
150    debug_assert!((radix_bits < 64) && ((SF::ORDER_U32 as u64) < (1u64 << radix_bits)));
151    let base = TF::from_int(1u64 << radix_bits);
152    vals.iter()
153        .map(|val| TF::from_int(val.as_canonical_u32() as u64 + 1))
154        .horner(base)
155}
156
157/// Bit length of `F::ORDER_U32 - 1`, i.e. the smallest `b` with `F::ORDER_U32 - 1 < 2^b`.
158///
159/// Used for tight base-$2^b$ absorb packing so canonical [`PrimeField32`] digits are always
160/// valid base-$2^b$ digits (more limbs per [`PrimeField`] slot than radix $2^{32}$ when
161/// `ORDER_U32 < 2^{32}`).
162#[inline]
163#[must_use]
164pub const fn absorb_radix_bits<F: PrimeField32>() -> u32 {
165    u32::BITS - (F::ORDER_U32 - 1).leading_zeros()
166}
167
168/// Horner-evaluate `vals` as base-$2^{radix\_bits}$ digits into `TF`.
169///
170/// Requires every canonical `SF` digit to be strictly less than `2^{radix\_bits}` (true when
171/// `radix_bits ≥ absorb_radix_bits::<SF>()`).
172#[must_use]
173pub fn reduce_packed<SF: PrimeField32, TF: PrimeField>(vals: &[SF], radix_bits: u32) -> TF {
174    debug_assert!((absorb_radix_bits::<SF>() <= radix_bits) && (radix_bits < 64));
175    let base = TF::from_int(1u64 << radix_bits);
176    vals.iter()
177        .map(|val| TF::from_int(val.as_canonical_u32()))
178        .horner(base)
179}
180
181/// Largest `b` such that every integer in `[0, 2^b)` maps injectively into `F` via `PrimeField32::from_int`.
182///
183/// Equivalently `b = floor(log2(p-1))` for prime `p = F::ORDER_U32`.
184#[inline]
185#[must_use]
186pub const fn injective_pack_bits<F: PrimeField32>() -> u32 {
187    (F::ORDER_U32 - 1).ilog2()
188}
189
190/// Maximum number of [`PrimeField32`] elements packable into [`PrimeField`] injectively via
191/// [`reduce_packed`] with the given `radix_bits` (base-$2^{radix\_bits}$ digits bounded by
192/// `F::ORDER_U32 - 1`).
193///
194/// Returns the largest `k` such that
195/// `(F::ORDER_U32 - 1) · ∑_{i=0}^{k-1} (2^{radix\_bits})^i < PF::order()`.
196#[must_use]
197pub fn max_packed_injective_limbs<F: PrimeField32, PF: PrimeField>(radix_bits: u32) -> usize {
198    max_packed_injective_limbs_with_max_digit::<PF>(radix_bits, F::ORDER_U32 - 1)
199}
200
201fn max_packed_injective_limbs_with_max_digit<PF: PrimeField>(
202    radix_bits: u32,
203    max_digit: u32,
204) -> usize {
205    debug_assert!((0 < radix_bits) && (radix_bits < 64));
206    let max_digit = BigUint::from(max_digit);
207    let base = BigUint::from(1u32) << (radix_bits as usize);
208    let pf_order = PF::order();
209    let mut k = 0usize;
210    let mut max_val = BigUint::ZERO;
211    let mut power = BigUint::from(1u32);
212    loop {
213        let new_max = &max_val + &max_digit * &power;
214        if new_max >= pf_order {
215            break k;
216        }
217        max_val = new_max;
218        power *= &base;
219        k += 1;
220    }
221}
222
223/// Maximum number of shifted [`PrimeField32`] elements packable into [`PrimeField`] injectively
224/// via [`reduce_packed_shifted`] with the given `radix_bits`.
225///
226/// Returns the largest `k` such that
227/// `F::ORDER_U32 · ∑_{i=0}^{k-1} (2^{radix\_bits})^i < PF::order()`.
228#[must_use]
229pub fn max_shifted_packed_injective_limbs<F: PrimeField32, PF: PrimeField>(
230    radix_bits: u32,
231) -> usize {
232    max_packed_injective_limbs_with_max_digit::<PF>(radix_bits, F::ORDER_U32)
233}
234
235/// Maximum limbs per [`PrimeField`] rate slot when absorbing with radix
236/// $2^{\texttt{absorb\\_radix\\_bits::\<F\>()}}$ (see [`reduce_packed`]).
237#[must_use]
238pub fn max_absorb_injective_limbs<F: PrimeField32, PF: PrimeField>() -> usize {
239    max_packed_injective_limbs::<F, PF>(absorb_radix_bits::<F>())
240}
241
242/// Maximum shifted limbs per [`PrimeField`] rate slot when absorbing with radix
243/// $2^{\texttt{absorb\\_radix\\_bits::\<F\>()}}$ (see [`reduce_packed_shifted`]).
244#[must_use]
245pub fn max_shifted_absorb_injective_limbs<F: PrimeField32, PF: PrimeField>() -> usize {
246    max_shifted_packed_injective_limbs::<F, PF>(absorb_radix_bits::<F>())
247}
248
249/// Returns true iff every integer in `[0, SF::order())` fits in `num_limbs` little-endian
250/// base-`2^radix_bits` digits without truncation, i.e. `2^{num_limbs · radix_bits} ≥ SF::order()`.
251#[must_use]
252pub fn pf_packed_limbs_cover_order<SF: PrimeField>(num_limbs: usize, radix_bits: u32) -> bool {
253    let Some(total_bits) = num_limbs.checked_mul(radix_bits as usize) else {
254        return false;
255    };
256    (BigUint::from(1u32) << total_bits) >= SF::order()
257}
258
259/// Split `val` into `num_limbs` little-endian base-`2^radix_bits` limbs, each mapped into `TF`.
260///
261/// Each output limb is in `[0, 2^radix_bits)`. Pads with zero limbs if the value has fewer
262/// non-zero digits than `num_limbs`.
263///
264/// **Parameter requirements**
265///
266/// - `radix_bits ≤ injective_pack_bits::<TF>()` so each limb maps injectively into `TF` via
267///   `PrimeField32::from_int`. If `radix_bits` is too large, distinct limbs can collide after
268///   reduction modulo `TF::ORDER`.
269/// - For a **lossless** transcript binding of arbitrary `SF` values, also require
270///   `pf_packed_limbs_cover_order::<SF>(num_limbs, radix_bits)`. Deliberately truncated
271///   splits (e.g. challengers that use `floor` limb counts for squeeze) omit high bits by design
272///   and do not satisfy that coverage check.
273#[must_use]
274pub fn split_pf_to_packed_limbs<SF: PrimeField, TF: PrimeField32>(
275    val: SF,
276    num_limbs: usize,
277    radix_bits: u32,
278) -> Vec<TF> {
279    debug_assert!((0 < radix_bits) && (radix_bits < 64));
280    debug_assert!(
281        radix_bits <= injective_pack_bits::<TF>(),
282        "radix_bits must be ≤ injective_pack_bits::<TF>() for injective limb embedding"
283    );
284
285    // Use a primitive u32 mask!
286    let mask_u32: u32 = (1u32 << radix_bits) - 1;
287    let mut rem = val.as_canonical_biguint();
288    let mut out = vec![TF::ZERO; num_limbs];
289
290    for item in out.iter_mut() {
291        // Look at the lowest limb directly, no allocations
292        let limb = rem.iter_u32_digits().next().unwrap_or(0) & mask_u32;
293        *item = TF::from_int(limb);
294
295        // In-place bitshift modifies the BigUint without allocating
296        rem >>= radix_bits;
297    }
298
299    out
300}
301
302/// Number of `TF` limbs with statistical bias `< 1/|TF|` when decomposing a uniformly random
303/// `PF` element in base `|TF|` (see [`split_pf_to_field_order_limbs`]).
304///
305/// Returns the largest `k` such that `TF::ORDER^{k+1} < PF::ORDER`. Each retained limb `c_i`
306/// (`i < k`) has bias `≈ 1/⌊PF::ORDER / TF::ORDER^{i+2}⌋ < 1/TF::ORDER`.
307///
308/// Unlike the power-of-two radix variant ([`split_pf_to_packed_limbs`] with
309/// `radix_bits = injective_pack_bits::<TF>()`), which confines each challenge to
310/// `[0, 2^{radix_bits})` (≈ 50% of `TF`'s domain for BabyBear), this gives limbs that are
311/// near-uniform over the **entire** `TF` domain.
312///
313/// # BabyBear concrete values
314/// | PF | Good limbs |
315/// |---|---|
316/// | Goldilocks (64-bit) | 1 |
317/// | BN254 (254-bit) | 7 |
318#[must_use]
319pub fn squeeze_field_order_num_limbs<PF: PrimeField, TF: PrimeField32>() -> usize {
320    let p = BigUint::from(TF::ORDER_U32);
321    let n = PF::order();
322    let mut count = 0usize;
323    let mut power = BigUint::from(1u32);
324    while &power * &p < n {
325        power *= &p;
326        count += 1;
327    }
328    count.saturating_sub(1)
329}
330
331/// Split `val` into `num_limbs` little-endian base-`|TF|` limbs, each mapped into `TF`.
332///
333/// Decomposes `val` as `c0 + c1·p + c2·p² + …` (p = `TF::ORDER_U32`), returning
334/// `[c0, c1, …, c_{num_limbs-1}]` with `0 ≤ ci < p`. Pads with `TF::ZERO` if `val` has
335/// fewer significant digits than `num_limbs`.
336///
337/// Use [`squeeze_field_order_num_limbs`] to choose `num_limbs` such that each retained limb
338/// is near-uniform over all of `TF` when `val` is uniformly random.
339#[must_use]
340pub fn split_pf_to_field_order_limbs<SF: PrimeField, TF: PrimeField32>(
341    val: SF,
342    num_limbs: usize,
343) -> Vec<TF> {
344    let p_u32 = TF::ORDER_U32;
345    let mut rem = val.as_canonical_biguint();
346    let mut out = Vec::with_capacity(num_limbs);
347
348    for _ in 0..num_limbs {
349        // Fast, primitive 32-bit modulo (no heap allocation!)
350        let limb = (&rem % p_u32).to_u32_digits().first().copied().unwrap_or(0);
351        out.push(TF::from_int(limb));
352
353        // Fast, primitive in-place 32-bit division
354        rem /= p_u32;
355    }
356    out
357}
358
359/// Split a large field element into `n` base-$2^{64}$ chunks and map each into a 32-bit field.
360///
361/// Converts:
362/// ```math
363/// \begin{equation}
364///     x = \sum_{i=0}^{n-1} d_i \cdot 2^{64i}
365/// \end{equation}
366/// ```
367///
368/// Pads with zeros if needed.
369#[must_use]
370pub fn split_32<SF: PrimeField, TF: PrimeField32>(val: SF, n: usize) -> Vec<TF> {
371    let mut result: Vec<TF> = val
372        .as_canonical_biguint()
373        .to_u64_digits()
374        .iter()
375        .take(n)
376        .map(|d| TF::from_u64(*d))
377        .collect();
378
379    // Pad with zeros if needed
380    result.resize_with(n, || TF::ZERO);
381    result
382}
383
384/// Maximally generic dot product.
385#[must_use]
386pub fn dot_product<S, LI, RI>(li: LI, ri: RI) -> S
387where
388    LI: Iterator,
389    RI: Iterator,
390    LI::Item: Mul<RI::Item>,
391    S: Sum<<LI::Item as Mul<RI::Item>>::Output>,
392{
393    li.zip(ri).map(|(l, r)| l * r).sum()
394}
395
396/// Horner-style polynomial evaluation over a [`DoubleEndedIterator`].
397///
398/// The iterator yields coefficients in **ascending degree order**
399/// `[c_0, c_1, …, c_{n-1}]`. Both methods walk the iterator back-to-front
400/// via [`DoubleEndedIterator::rfold`], avoiding any allocation.
401///
402/// # Convention
403///
404/// Given an evaluation point `x` and accumulator `acc`,
405/// [`HornerIter::horner_acc`] computes
406///
407/// ```text
408/// acc · xⁿ + Σ_{i=0..n} c_i · xⁱ
409///   = c_0 + x · (c_1 + x · (… + x · (c_{n-1} + x · acc)))
410/// ```
411///
412/// [`HornerIter::horner`] is the same with `acc = Acc::default()`, i.e. the
413/// polynomial evaluation `Σ_i c_i · xⁱ`.
414///
415/// For inputs in *descending* degree order, call `.rev()` on the iterator
416/// first.
417pub trait HornerIter: DoubleEndedIterator + Sized {
418    /// Horner fold with an explicit accumulator. See the trait docs for the
419    /// evaluation convention.
420    #[inline]
421    fn horner_acc<Acc, X>(self, acc: Acc, x: X) -> Acc
422    where
423        Acc: Mul<X, Output = Acc> + Add<Self::Item, Output = Acc>,
424        X: Clone,
425    {
426        self.rfold(acc, |a, v| a * x.clone() + v)
427    }
428
429    /// Horner fold starting from `Acc::default()`. See the trait docs for the
430    /// evaluation convention.
431    #[inline]
432    fn horner<Acc, X>(self, x: X) -> Acc
433    where
434        Acc: Default + Mul<X, Output = Acc> + Add<Self::Item, Output = Acc>,
435        X: Clone,
436    {
437        self.horner_acc(Acc::default(), x)
438    }
439}
440
441impl<I: DoubleEndedIterator> HornerIter for I {}