Skip to main content

modmath/
field.rs

1//! `Field<T, P>` — Montgomery-form modular arithmetic context, parameterized
2//! over the limb personality (`P: Personality`).
3//!
4//! This module owns the *generic* Montgomery surface (`mul`, `exp`, `add`,
5//! `sub`, `reduce`, `into_raw`, `zero`, `one`); curve- or RSA-specific
6//! specializations (`lazy_add`, Fermat `inv`, Solinas reduction, etc.) live
7//! in consumer crates that wrap these types.
8//!
9//! ## Personality parameter
10//!
11//! `Field<T, P>` selects the *algorithm* at the modmath level (variable-
12//! time CIOS with branching finalize vs. CT CIOS with conditional-select
13//! finalize); the personality of `T` itself (e.g. `FixedUInt<W, N, Nct>`
14//! vs `FixedUInt<W, N, Ct>`) selects the *limb-primitive* bodies. In
15//! practice the two personalities co-vary at the construction site —
16//! you build `Field<FixedUInt<W, N, Nct>, Nct>` for verify paths and
17//! `Field<FixedUInt<W, N, Ct>, Ct>` for signing paths. Type aliases
18//! [`FieldCt`] / [`FieldNct`] (and the matching [`ResidueCt`] /
19//! [`ResidueNct`]) are the canonical per-personality spellings — they
20//! read naturally at the call site and sidestep the type-inference
21//! ambiguity that bare `Field::new(modulus)` hits when two `impl`
22//! blocks (Nct/Ct) are both method-resolution candidates.
23//!
24//! Because the Nct and Ct algorithms have **different trait bounds on `T`**
25//! (`T: CiosMontMul` for Nct vs `T: CiosMontMulCt + ConditionallySelectable`
26//! for Ct), the per-personality method bodies live in separate `impl`
27//! blocks rather than dispatching via `match P::TAG`. Only the bounds
28//! shared by both algorithms (precompute, `new`, `zero`, `one`, the
29//! residue brand) live in the common `impl<T, P: Personality>` block.
30//!
31//! ## Branding
32//!
33//! Each `Field<T, P>` instance is implicitly tagged by its borrow lifetime
34//! `'f`, and the residues it produces carry that same brand plus the
35//! personality parameter:
36//!
37//! ```ignore
38//! let field = Field::new(modulus).unwrap();
39//! let r: Residue<'_, U256, Nct> = field.reduce(&seven);
40//! ```
41//!
42//! The borrow checker prevents a `Residue` from outliving its parent
43//! `Field`, and the `P` parameter prevents an Nct residue from being fed
44//! to a Ct method (and vice versa) at compile time. Covariance does not
45//! prevent mixing residues from two `Field` instances built in the same
46//! scope with the same `P` — a known limitation matched by ed25519's
47//! `field.rs`. A generative brand
48//! (`PhantomData<fn(&'f ()) -> &'f ()>` + closure) would close that gap
49//! when a future consumer needs it.
50
51use core::marker::PhantomData;
52
53use const_num_traits::{Ct, Nct, Odd, Personality};
54
55use crate::montgomery::basic_mont::{
56    wide_montgomery_mul, wide_montgomery_mul_acc, wide_montgomery_mul_acc_ct,
57    wide_montgomery_mul_ct, wide_redc, wide_redc_ct,
58};
59use crate::montgomery::{
60    CiosMontMul, CiosMontMulCt, compute_n_prime_newton, compute_r_mod_n, compute_r2_mod_n,
61    type_bit_width,
62};
63use crate::parity::Parity;
64use crate::wide_mul::WideMul;
65
66/// Bound on the value stored in a [`Residue`]. With the `zeroize`
67/// feature this requires `T: Zeroize`; otherwise it's vacuous.
68#[cfg(feature = "zeroize")]
69pub trait MontStorage: zeroize::Zeroize {}
70#[cfg(feature = "zeroize")]
71impl<T: zeroize::Zeroize> MontStorage for T {}
72
73#[cfg(not(feature = "zeroize"))]
74pub trait MontStorage {}
75#[cfg(not(feature = "zeroize"))]
76impl<T> MontStorage for T {}
77
78// ---------------------------------------------------------------------------
79// Field<T, P>
80// ---------------------------------------------------------------------------
81
82/// Montgomery context over modulus `T`, with algorithm choice driven by
83/// the personality marker `P` (defaults to [`Nct`] = variable-time fast
84/// path).
85///
86/// Use `Field<T, Nct>` for operations on **public** data only — signature
87/// verification, RSA public-key encryption, anything whose inputs are not
88/// secret. Use `Field<T, Ct>` (also reachable via the [`FieldCt`] type
89/// alias) for secret-handling paths.
90///
91/// `Clone` is a trivial 4×`T` memcpy — it does NOT re-run the
92/// `compute_r_mod_n` / `compute_r2_mod_n` precompute that `new()` does.
93/// Callers building a `Field` once per key and then reusing it (e.g.
94/// RSA-CRT) should clone the prebuilt instance rather than calling
95/// `new()` again with the same modulus.
96#[derive(Clone, Debug)]
97pub struct Field<T, P: Personality = Nct> {
98    modulus: T,
99    n_prime: T,
100    r_mod_n: T,
101    r2_mod_n: T,
102    _p: PhantomData<fn() -> P>,
103}
104
105/// Alias for the Nct variant of [`Field`]. Equivalent to `Field<T, Nct>`
106/// (matches the default personality). Provided for symmetry with
107/// [`FieldCt`] and to side-step the construction-site type-ambiguity
108/// pitfall — `FieldNct::new(modulus)` resolves unambiguously without
109/// the type-annotation/turbofish friction of `Field::new(modulus)`.
110pub type FieldNct<T> = Field<T, Nct>;
111
112/// Alias for the Ct variant of [`Field`]. Equivalent to `Field<T, Ct>`.
113/// Reads naturally at construction sites and sidesteps the type-inference
114/// ambiguity that bare `Field::new(modulus)` hits — `FieldCt::new(modulus)`
115/// resolves unambiguously because the alias fixes `P = Ct` at the type
116/// level. Symmetric with [`FieldNct`].
117pub type FieldCt<T> = Field<T, Ct>;
118
119/// A value in `Field<T, P>`, stored implicitly in Montgomery form.
120///
121/// The `'f` lifetime brand ties this residue to its parent `Field`; the
122/// `P` parameter ties it to the parent's algorithm personality. The
123/// borrow checker rejects code that uses a residue after its `Field` is
124/// dropped, or that mixes residues across personalities. See module docs
125/// for the covariance caveat.
126#[derive(Clone, Debug, PartialEq, Eq)]
127pub struct Residue<'f, T: MontStorage, P: Personality = Nct> {
128    mont: T,
129    _brand: PhantomData<&'f ()>,
130    _p: PhantomData<fn() -> P>,
131}
132
133#[cfg(feature = "zeroize")]
134impl<'f, T: MontStorage, P: Personality> zeroize::Zeroize for Residue<'f, T, P> {
135    fn zeroize(&mut self) {
136        self.mont.zeroize();
137    }
138}
139
140#[cfg(feature = "zeroize")]
141impl<'f, T: MontStorage, P: Personality> Drop for Residue<'f, T, P> {
142    fn drop(&mut self) {
143        self.mont.zeroize();
144    }
145}
146
147#[cfg(feature = "zeroize")]
148impl<'f, T: MontStorage, P: Personality> zeroize::ZeroizeOnDrop for Residue<'f, T, P> {}
149
150/// Alias for the Nct variant of [`Residue`]. Equivalent to
151/// `Residue<'f, T, Nct>`. Symmetric with [`ResidueCt`].
152pub type ResidueNct<'f, T> = Residue<'f, T, Nct>;
153
154/// Alias for the Ct variant of [`Residue`]. Equivalent to
155/// `Residue<'f, T, Ct>`. Symmetric with [`ResidueNct`].
156pub type ResidueCt<'f, T> = Residue<'f, T, Ct>;
157
158// ---------------------------------------------------------------------------
159// Shared impls (any P)
160// ---------------------------------------------------------------------------
161
162impl<T: MontStorage, P: Personality> Residue<'_, T, P> {
163    /// Returns a reference to the underlying Montgomery-form value.
164    ///
165    /// **Escape hatch.** Intended for downstream specialization layers
166    /// (e.g. `Curve25519Field`) that implement fast paths reading the raw
167    /// limbs. General consumers should not call this — use the methods on
168    /// [`Field`] instead.
169    pub fn mont_value(&self) -> &T {
170        &self.mont
171    }
172}
173
174impl<T, P: Personality> Field<T, P> {
175    /// Construct a `Field` directly from already-computed Montgomery
176    /// parameters. **`const fn` — usable in const initializers.**
177    ///
178    /// Intended for callers whose modulus is statically known at compile
179    /// time (curve constants, PQC parameters, RSA group constants for a
180    /// fixed key, etc.) and who want to expose the Field as a `const`
181    /// associated item or static, rather than paying the runtime
182    /// [`new`](Self::new) precompute on each instantiation.
183    ///
184    /// **The caller is responsible for the correctness of `n_prime`,
185    /// `r_mod_n`, and `r2_mod_n`** — see [`compute_n_prime_newton`],
186    /// [`compute_r_mod_n`], and [`compute_r2_mod_n`] for the algorithms.
187    /// Those helpers are not `const fn` today (their bodies use trait
188    /// method calls on `T`), so const-initializer callers must either
189    /// hand-compute the values, use a build script, or — for primitive
190    /// integers — compute them in a non-const context once at startup
191    /// and cache.
192    ///
193    /// No invariant checking is performed here. Passing inconsistent
194    /// parameters produces a `Field` whose arithmetic methods return
195    /// silently incorrect results.
196    ///
197    /// [`compute_n_prime_newton`]: crate::compute_n_prime_newton
198    /// [`compute_r_mod_n`]: crate::compute_r_mod_n
199    /// [`compute_r2_mod_n`]: crate::compute_r2_mod_n
200    pub const fn from_precomputed(modulus: T, n_prime: T, r_mod_n: T, r2_mod_n: T) -> Self {
201        Self {
202            modulus,
203            n_prime,
204            r_mod_n,
205            r2_mod_n,
206            _p: PhantomData,
207        }
208    }
209}
210
211impl<T, P: Personality> Field<T, P>
212where
213    T: Copy
214        + PartialEq
215        + PartialOrd
216        + const_num_traits::Zero
217        + const_num_traits::One
218        + const_num_traits::WrappingMul
219        + const_num_traits::WrappingAdd
220        + const_num_traits::WrappingSub
221        + const_num_traits::ops::overflowing::OverflowingAdd
222        + core::ops::Add<Output = T>
223        + core::ops::Sub<Output = T>
224        + core::ops::Mul<Output = T>
225        + Parity
226        + MontStorage,
227{
228    /// Construct a new `Field` from an already-proven-odd modulus.
229    ///
230    /// **Infallible.** The `Odd<T>` typestate hoists the "modulus is odd and
231    /// nonzero" precondition to the caller's trust boundary — typically a
232    /// single `Odd::new(p)?` (or `Odd::new(p).unwrap()` for a const modulus)
233    /// at config load. No runtime check inside this constructor, and the
234    /// `panic_fmt` symbol that an `unwrap()` on the old `Option` API would
235    /// have synthesized stays out of the linked binary on embedded targets
236    /// when the boundary check is const-evaluated.
237    ///
238    /// `Odd<T>` covers both the "non-zero" and "odd" halves (zero is even),
239    /// so this also discharges the modulus-nonzero check that [`new`] does.
240    ///
241    /// [`new`]: Self::new
242    pub fn new_odd(modulus: Odd<T>) -> Self {
243        let modulus = modulus.get();
244        let w = type_bit_width::<T>();
245        let n_prime = compute_n_prime_newton(modulus, w);
246        let r_mod_n = compute_r_mod_n(modulus, w);
247        let r2_mod_n = compute_r2_mod_n(r_mod_n, modulus, w);
248        Self {
249            modulus,
250            n_prime,
251            r_mod_n,
252            r2_mod_n,
253            _p: PhantomData,
254        }
255    }
256
257    /// Construct a new `Field` from an already-proven-odd modulus,
258    /// using the **constant-time** precompute path.
259    ///
260    /// Same precompute as [`new_odd`] (Newton's iteration for `N'`,
261    /// repeated modular doublings for `R mod N` and `R² mod N`), but
262    /// the doubling-and-reduction loop in
263    /// [`compute_r_mod_n_ct`](crate::montgomery::compute_r_mod_n_ct)
264    /// avoids value-dependent branches on the modulus.
265    ///
266    /// Use this when `modulus` is secret (e.g. RSA-CRT private primes
267    /// `p`, `q`). For public moduli (ed25519 / Curve25519 / krabipqc),
268    /// [`new_odd`] is faster and equivalent.
269    ///
270    /// Cost vs [`new_odd`]: one extra `wrapping_sub` and one
271    /// `conditional_select` per modular doubling step (`w` per
272    /// precompute call). Negligible against the subsequent field
273    /// operations the precompute amortizes.
274    ///
275    /// [`new_odd`]: Self::new_odd
276    pub fn new_odd_ct(modulus: Odd<T>) -> Self
277    where
278        T: subtle::ConditionallySelectable + subtle::ConstantTimeLess,
279    {
280        let modulus = modulus.get();
281        let w = type_bit_width::<T>();
282        let n_prime = compute_n_prime_newton(modulus, w);
283        let r_mod_n = crate::montgomery::compute_r_mod_n_ct(modulus, w);
284        let r2_mod_n = crate::montgomery::compute_r2_mod_n_ct(r_mod_n, modulus, w);
285        Self {
286            modulus,
287            n_prime,
288            r_mod_n,
289            r2_mod_n,
290            _p: PhantomData,
291        }
292    }
293
294    /// Construct a new `Field` over the given (odd, nonzero) `modulus`.
295    ///
296    /// Returns `None` if `modulus` is zero or even (Montgomery requires odd N).
297    /// Thin wrapper around [`new_odd`] that performs the parity proof at
298    /// runtime. Prefer [`new_odd`] in panic-sensitive paths so the modulus
299    /// proof becomes a one-shot boundary check rather than a returned
300    /// `Option<Self>` the caller must `.unwrap()`.
301    ///
302    /// [`new_odd`]: Self::new_odd
303    pub fn new(modulus: T) -> Option<Self> {
304        Odd::new(modulus).map(Self::new_odd)
305    }
306
307    /// Returns the modulus by reference.
308    ///
309    /// Returning `&T` rather than `T` avoids a memcpy of the full modulus
310    /// (~256 bytes for a 2048-bit carrier) at the call site. Consumers that
311    /// need a `T` by value can copy at the use point.
312    pub fn modulus(&self) -> &T {
313        &self.modulus
314    }
315
316    /// The additive identity (0 in Montgomery form is 0).
317    pub fn zero(&self) -> Residue<'_, T, P> {
318        Residue {
319            mont: T::zero(),
320            _brand: PhantomData,
321            _p: PhantomData,
322        }
323    }
324
325    /// The multiplicative identity (1 in Montgomery form is `R mod N`).
326    pub fn one(&self) -> Residue<'_, T, P> {
327        Residue {
328            mont: self.r_mod_n,
329            _brand: PhantomData,
330            _p: PhantomData,
331        }
332    }
333
334    /// Reconstruct a [`Residue`] from a raw value already in Montgomery form.
335    ///
336    /// **Escape hatch.** Intended for downstream specialization layers that
337    /// persist or compute Montgomery-form values outside this module and
338    /// need to re-attach the brand. The caller must guarantee `mont` is in
339    /// `[0, modulus)` and represents some value `x` such that
340    /// `mont == x * R mod modulus`.
341    pub fn residue_from_mont(&self, mont: T) -> Residue<'_, T, P> {
342        Residue {
343            mont,
344            _brand: PhantomData,
345            _p: PhantomData,
346        }
347    }
348}
349
350// ---------------------------------------------------------------------------
351// Nct-only impls — variable-time CIOS with branching finalize
352// ---------------------------------------------------------------------------
353
354impl<T> Field<T, Nct>
355where
356    T: Copy
357        + PartialEq
358        + PartialOrd
359        + const_num_traits::Zero
360        + const_num_traits::One
361        + const_num_traits::WrappingMul
362        + const_num_traits::WrappingAdd
363        + const_num_traits::WrappingSub
364        + const_num_traits::ops::overflowing::OverflowingAdd
365        + core::ops::Add<Output = T>
366        + core::ops::Sub<Output = T>
367        + core::ops::Mul<Output = T>
368        + Parity
369        + crate::NonCt
370        + MontStorage,
371{
372    /// Convert a raw value `< modulus` (or arbitrary value, which is then
373    /// reduced) to Montgomery form. Returns a brand-tagged [`Residue`].
374    pub fn reduce(&self, raw: &T) -> Residue<'_, T, Nct>
375    where
376        T: WideMul,
377    {
378        let mont = wide_montgomery_mul(*raw, self.r2_mod_n, self.modulus, self.n_prime);
379        Residue {
380            mont,
381            _brand: PhantomData,
382            _p: PhantomData,
383        }
384    }
385
386    /// Convert a [`Residue`] back to its raw `T` representative in `[0, modulus)`.
387    #[allow(clippy::wrong_self_convention)]
388    pub fn into_raw(&self, r: &Residue<'_, T, Nct>) -> T
389    where
390        T: WideMul,
391    {
392        wide_redc(r.mont, T::zero(), self.modulus, self.n_prime)
393    }
394
395    /// Modular addition: `(a + b) mod modulus`.
396    ///
397    /// **The conditional-subtract here is non-negotiable.** Future consumers
398    /// with a modulus narrower than `T::BITS` may be tempted to skip the
399    /// `wrapping_add` + cond-sub path (since `2 * modulus < 2^T::BITS` for
400    /// them, the wraparound never fires). That's a correct optimization, but
401    /// it belongs in a specialization layer (e.g. `Curve25519Field` in the
402    /// ed25519 crate), not in `modmath::Field`. Patches removing the
403    /// wrapping path will break RSA-CRT (full-width modulus, no slack) and
404    /// any other consumer at full type width; ed25519 has slack and uses its
405    /// own lazy variant in `Curve25519Field`.
406    pub fn add(&self, a: &Residue<'_, T, Nct>, b: &Residue<'_, T, Nct>) -> Residue<'_, T, Nct> {
407        let mont = crate::add::basic_mod_add_pr(a.mont, b.mont, self.modulus);
408        Residue {
409            mont,
410            _brand: PhantomData,
411            _p: PhantomData,
412        }
413    }
414
415    /// Modular subtraction: `(a - b) mod modulus`.
416    ///
417    /// Same load-bearing contract as [`add`](Self::add) — the borrow-detect
418    /// branch is required at full type width.
419    pub fn sub(&self, a: &Residue<'_, T, Nct>, b: &Residue<'_, T, Nct>) -> Residue<'_, T, Nct> {
420        let mont = crate::sub::basic_mod_sub_pr(a.mont, b.mont, self.modulus);
421        Residue {
422            mont,
423            _brand: PhantomData,
424            _p: PhantomData,
425        }
426    }
427
428    /// Modular multiplication via CIOS Montgomery multiplication.
429    ///
430    /// CIOS interleaves multiplication and reduction in one pass (~2N² + N
431    /// limb mults vs ~3N² for separate wide-mul + REDC), which dominates the
432    /// inner-loop cost on constrained cores. The functional output is
433    /// identical to `wide_montgomery_mul`.
434    ///
435    /// Marked `#[inline]` deliberately: this is the documented inner-loop
436    /// wrapper for Montgomery exponentiation, the body is a single trait
437    /// method call, and skipping it across crate boundaries costs ~250
438    /// cycles per call under `opt-level="z"` on Cortex-M. Not blanket cargo
439    /// culting — surgical on the actual hot path.
440    #[inline]
441    pub fn mul(&self, a: &Residue<'_, T, Nct>, b: &Residue<'_, T, Nct>) -> Residue<'_, T, Nct>
442    where
443        T: CiosMontMul,
444    {
445        let mont = CiosMontMul::cios_mont_mul(&a.mont, &b.mont, &self.modulus, &self.n_prime);
446        Residue {
447            mont,
448            _brand: PhantomData,
449            _p: PhantomData,
450        }
451    }
452
453    /// Modular exponentiation via square-and-multiply.
454    ///
455    /// `base` is taken as a [`Residue`] (already in Montgomery form); `exp`
456    /// is a raw `T`. The result is a [`Residue`] in Montgomery form.
457    ///
458    /// **Variable-time in `exp`.** The loop iterates `bit_length(exp)` times
459    /// and branches on each bit. Do not call with a secret `exp` — use the
460    /// Ct-variant `Field<T, Ct>::exp` instead.
461    pub fn exp(&self, base: &Residue<'_, T, Nct>, exp: &T) -> Residue<'_, T, Nct>
462    where
463        T: CiosMontMul + core::ops::ShrAssign<usize>,
464    {
465        let mut result = self.r_mod_n;
466        let mut base_var = base.mont;
467        let mut exp_val = *exp;
468        while exp_val > T::zero() {
469            if exp_val.is_odd() {
470                result =
471                    CiosMontMul::cios_mont_mul(&result, &base_var, &self.modulus, &self.n_prime);
472            }
473            exp_val >>= 1;
474            if exp_val > T::zero() {
475                base_var =
476                    CiosMontMul::cios_mont_mul(&base_var, &base_var, &self.modulus, &self.n_prime);
477            }
478        }
479        Residue {
480            mont: result,
481            _brand: PhantomData,
482            _p: PhantomData,
483        }
484    }
485
486    /// Wide multiply-accumulate: `(acc.0, acc.1) += a.mont * b.mont`.
487    ///
488    /// Brand-tagged wrapper around [`wide_montgomery_mul_acc`]. Pair
489    /// with [`Field::wide_redc`] to close the accumulator after a fused
490    /// inner-product loop. See the free-function for the `N ≤ R/q`
491    /// bound contract.
492    pub fn mul_acc(&self, acc: (T, T), a: &Residue<'_, T, Nct>, b: &Residue<'_, T, Nct>) -> (T, T)
493    where
494        T: WideMul,
495    {
496        wide_montgomery_mul_acc(acc.0, acc.1, a.mont, b.mont)
497    }
498
499    /// Close a wide accumulator into a brand-tagged [`Residue`].
500    pub fn wide_redc(&self, acc: (T, T)) -> Residue<'_, T, Nct>
501    where
502        T: WideMul,
503    {
504        let mont = wide_redc(acc.0, acc.1, self.modulus, self.n_prime);
505        Residue {
506            mont,
507            _brand: PhantomData,
508            _p: PhantomData,
509        }
510    }
511
512    /// Modular inverse via Fermat's little theorem: `a^(modulus − 2)`.
513    ///
514    /// **Requires `modulus` to be prime.** Variable-time over the bits
515    /// of `modulus − 2`. Returns `None` when `a` is the zero residue.
516    pub fn inv_fermat(&self, a: &Residue<'_, T, Nct>) -> Option<Residue<'_, T, Nct>>
517    where
518        T: CiosMontMul + core::ops::ShrAssign<usize>,
519    {
520        if a.mont == T::zero() {
521            return None;
522        }
523        let two = T::one().wrapping_add(T::one());
524        let exp_val = self.modulus.wrapping_sub(two);
525        Some(self.exp(a, &exp_val))
526    }
527
528    /// Modular inverse via extended Euclidean GCD on the raw Mont
529    /// value, then rebrand to Mont form via two Mont multiplies by
530    /// `R^2 mod N`.
531    ///
532    /// Works for any odd modulus (composite is fine). Variable-time —
533    /// do not call with secret inputs; use [`Self::inv_fermat`] for CT
534    /// paths. Returns `None` when `a` is not coprime to modulus.
535    pub fn inv_eea(&self, a: &Residue<'_, T, Nct>) -> Option<Residue<'_, T, Nct>>
536    where
537        T: WideMul + core::ops::Div<Output = T> + core::ops::Sub<Output = T>,
538    {
539        if a.mont == T::zero() {
540            return None;
541        }
542        let raw_inv = crate::inv::basic_mod_inv(a.mont, self.modulus)?;
543        // raw_inv = (a*R)^{-1} = a^{-1} * R^{-1} (residue form).
544        // Two Mont mults by R^2 lift it back to a^{-1} * R = Mont(a^{-1}).
545        let step1 = wide_montgomery_mul(raw_inv, self.r2_mod_n, self.modulus, self.n_prime);
546        let mont = wide_montgomery_mul(step1, self.r2_mod_n, self.modulus, self.n_prime);
547        Some(Residue {
548            mont,
549            _brand: PhantomData,
550            _p: PhantomData,
551        })
552    }
553}
554
555// ---------------------------------------------------------------------------
556// Ct-only impls — CT CIOS with conditional-select finalize
557// ---------------------------------------------------------------------------
558
559impl<'f, T> Residue<'f, T, Ct>
560where
561    T: subtle::ConditionallySelectable + MontStorage,
562{
563    /// Conditionally swap two residues in constant time.
564    ///
565    /// If `choice` is set, `a` and `b` exchange Montgomery-form values;
566    /// otherwise both are left unchanged. The operation is branchless.
567    ///
568    /// This is the primitive used by Montgomery ladders (x25519 scalar
569    /// multiplication, RSA blinded exponentiation). It is the **only**
570    /// residue swap that should appear in such a ladder; `std::mem::swap`
571    /// is not guaranteed to be branchless.
572    pub fn cswap(choice: subtle::Choice, a: &mut Self, b: &mut Self) {
573        T::conditional_swap(&mut a.mont, &mut b.mont, choice);
574    }
575}
576
577impl<'f, T> Residue<'f, T, Ct>
578where
579    T: subtle::ConstantTimeEq + MontStorage,
580{
581    /// Constant-time equality on the underlying Montgomery values.
582    ///
583    /// Use in place of derived `PartialEq` on Ct paths where the
584    /// equality outcome must not leak through timing (ML-KEM
585    /// decapsulation tag check, ed25519 signature verification).
586    pub fn ct_eq(&self, other: &Self) -> subtle::Choice {
587        self.mont.ct_eq(&other.mont)
588    }
589}
590
591impl<T> Field<T, Ct>
592where
593    T: Copy
594        + PartialEq
595        + PartialOrd
596        + const_num_traits::Zero
597        + const_num_traits::One
598        + const_num_traits::WrappingMul
599        + const_num_traits::WrappingAdd
600        + const_num_traits::WrappingSub
601        + const_num_traits::ops::overflowing::OverflowingAdd
602        + core::ops::Add<Output = T>
603        + core::ops::Sub<Output = T>
604        + core::ops::Mul<Output = T>
605        + Parity
606        + MontStorage,
607{
608    /// Construct a `Field<T, Ct>` from a **secret** modulus without a
609    /// value-dependent branch on the parity check.
610    ///
611    /// `Odd::new_ct` performs the parity check via [`CtParity`], producing a
612    /// masked [`subtle::CtOption`] rather than a control-flow branch. The
613    /// precompute (`compute_n_prime_newton`, `compute_r_mod_n`,
614    /// `compute_r2_mod_n`) runs unconditionally — its inputs are the secret
615    /// modulus's value, but the operations are constant-time word arithmetic
616    /// over the existing CT trait surface, and the `CtOption` wrapper
617    /// branchlessly masks the result if the modulus turned out to be even.
618    ///
619    /// Intended for the **RSA-CRT private-key path** where `p` and `q` are
620    /// secret primes. Public-modulus / verify-side callers should use
621    /// [`Field::new_odd`] instead — the secret-aware code path is strictly
622    /// more expensive on platforms with branch prediction.
623    ///
624    /// Collapses the boundary check at the consumer:
625    ///
626    /// ```ignore
627    /// // Old shape, panics on a secret-derived branch:
628    /// let field = Field::<_, Ct>::new(secret_p).expect("p is odd prime");
629    ///
630    /// // New shape, masked:
631    /// let field = Field::<_, Ct>::try_new_odd_ct(secret_p);
632    /// let result = field.map(|f| /* CT-sensitive ops */ );
633    /// ```
634    ///
635    /// [`CtParity`]: const_num_traits::CtParity
636    pub fn try_new_odd_ct(modulus: T) -> subtle::CtOption<Self>
637    where
638        T: const_num_traits::CtParity + subtle::ConditionallySelectable + subtle::ConstantTimeLess,
639    {
640        // Mask the parity check (no branch on the secret modulus). The
641        // precompute below uses the CT path ([`Self::new_odd_ct`]) so
642        // no value-dependent branches on the modulus value either —
643        // every step is `subtle::Choice`-masked. `CtOption::new(_,
644        // choice)` discards the result via the standard masked-`Some`
645        // pattern if the modulus turned out to be even.
646        let is_odd = modulus.ct_is_odd();
647        // SAFETY: when `is_odd` is unset the wrapped `Odd` proof carries a
648        // false predicate, but the resulting `Field` is unreachable through
649        // the `CtOption` mask. No body downstream consumes the proof except
650        // via the masked output.
651        let proof = unsafe { Odd::new_unchecked(modulus) };
652        let field = Self::new_odd_ct(proof);
653        subtle::CtOption::new(field, is_odd)
654    }
655
656    /// Convert a raw value to Montgomery form. Constant-time finalize.
657    pub fn reduce(&self, raw: &T) -> Residue<'_, T, Ct>
658    where
659        T: WideMul + subtle::ConditionallySelectable + subtle::ConstantTimeLess,
660    {
661        let mont = wide_montgomery_mul_ct(*raw, self.r2_mod_n, self.modulus, self.n_prime);
662        Residue {
663            mont,
664            _brand: PhantomData,
665            _p: PhantomData,
666        }
667    }
668
669    /// Convert a [`Residue`] back to raw form. Constant-time finalize.
670    #[allow(clippy::wrong_self_convention)]
671    pub fn into_raw(&self, r: &Residue<'_, T, Ct>) -> T
672    where
673        T: WideMul + subtle::ConditionallySelectable + subtle::ConstantTimeLess,
674    {
675        wide_redc_ct(r.mont, T::zero(), self.modulus, self.n_prime)
676    }
677
678    /// Modular addition — constant-time finalize.
679    ///
680    /// See `Field<T, Nct>::add` for the load-bearing comment about why the
681    /// wrapping cond-sub path is non-negotiable.
682    pub fn add(&self, a: &Residue<'_, T, Ct>, b: &Residue<'_, T, Ct>) -> Residue<'_, T, Ct>
683    where
684        T: subtle::ConditionallySelectable + subtle::ConstantTimeLess,
685    {
686        let sum = a.mont.wrapping_add(b.mont);
687        let sub = sum.wrapping_sub(self.modulus);
688        // Carry from wrapping: sum < a means wraparound occurred.
689        let carry = sum.ct_lt(&a.mont);
690        // Result >= modulus when !(sum < modulus).
691        let ge_m = !sum.ct_lt(&self.modulus);
692        let needs_sub = carry | ge_m;
693        let mont = T::conditional_select(&sum, &sub, needs_sub);
694        Residue {
695            mont,
696            _brand: PhantomData,
697            _p: PhantomData,
698        }
699    }
700
701    /// Modular subtraction — constant-time finalize.
702    ///
703    /// Same contract as `Field<T, Nct>::sub`.
704    pub fn sub(&self, a: &Residue<'_, T, Ct>, b: &Residue<'_, T, Ct>) -> Residue<'_, T, Ct>
705    where
706        T: subtle::ConditionallySelectable + subtle::ConstantTimeLess,
707    {
708        let diff = a.mont.wrapping_sub(b.mont);
709        let corrected = diff.wrapping_add(self.modulus);
710        // borrow == (a < b)
711        let borrow = a.mont.ct_lt(&b.mont);
712        let mont = T::conditional_select(&diff, &corrected, borrow);
713        Residue {
714            mont,
715            _brand: PhantomData,
716            _p: PhantomData,
717        }
718    }
719
720    /// Modular multiplication via CIOS — constant-time finalize.
721    ///
722    /// See `Field<T, Nct>::mul` for the rationale on CIOS vs. wide-REDC and
723    /// the `#[inline]` justification.
724    #[inline]
725    pub fn mul(&self, a: &Residue<'_, T, Ct>, b: &Residue<'_, T, Ct>) -> Residue<'_, T, Ct>
726    where
727        T: CiosMontMulCt,
728    {
729        let mont = CiosMontMulCt::cios_mont_mul_ct(&a.mont, &b.mont, &self.modulus, &self.n_prime);
730        Residue {
731            mont,
732            _brand: PhantomData,
733            _p: PhantomData,
734        }
735    }
736
737    /// Modular exponentiation — constant-time over `exp`.
738    ///
739    /// Implements a fixed-iteration Montgomery ladder over all
740    /// `bit_length(T)` bits of the exponent. Both square and multiply are
741    /// performed every iteration; the result is selected branchlessly. Loop
742    /// count does not depend on `exp`; per-iteration timing does not depend
743    /// on the bit pattern.
744    pub fn exp(&self, base: &Residue<'_, T, Ct>, exp: &T) -> Residue<'_, T, Ct>
745    where
746        T: CiosMontMulCt
747            + const_num_traits::CtIsZero
748            + subtle::ConditionallySelectable
749            + subtle::ConstantTimeEq
750            + core::ops::Shr<usize, Output = T>
751            + core::ops::BitAnd<Output = T>,
752    {
753        let w = type_bit_width::<T>();
754        let one = T::one();
755        let mut result = self.r_mod_n;
756
757        for i in (0..w).rev() {
758            // Always square.
759            result =
760                CiosMontMulCt::cios_mont_mul_ct(&result, &result, &self.modulus, &self.n_prime);
761            // Always compute the conditional product.
762            let multiplied =
763                CiosMontMulCt::cios_mont_mul_ct(&result, &base.mont, &self.modulus, &self.n_prime);
764            // Select based on bit i of exp.
765            let bit_t = (*exp >> i) & one;
766            let choice = !bit_t.ct_is_zero();
767            result = T::conditional_select(&result, &multiplied, choice);
768        }
769        Residue {
770            mont: result,
771            _brand: PhantomData,
772            _p: PhantomData,
773        }
774    }
775
776    /// Modular exponentiation — constant-time over the base, **variable-time
777    /// over the exponent**. Use when the exponent is public.
778    ///
779    /// This is the right primitive for several common cryptographic shapes:
780    ///
781    /// - **RSA encrypt / verify** — `m^e mod n` with the secret message `m`
782    ///   and the public exponent `e` (typically 65537). Saves `bit_length(T)
783    ///   - bit_length(e)` squarings vs. the fixed-iteration ladder, which is
784    ///   ~2031 squarings at 2048-bit modulus when `e = 65537`.
785    /// - **Curve25519 Fermat inverse** — `a^(p-2) mod p` where `p - 2` is the
786    ///   curve constant `2^255 - 21`. The exponent is public; the base is
787    ///   the secret intermediate `Z`. Skip-on-zero square-and-multiply
788    ///   matches the ~252-of-255 bits set without spending the per-bit
789    ///   `conditional_select` cost of the fixed-iteration ladder.
790    /// - **Curve25519 square root** — `a^((p+3)/8) mod p`, same shape.
791    ///
792    /// The squarings and multiplications themselves go through CT primitives
793    /// ([`cios_montgomery_mul_ct`](crate::montgomery::cios::cios_montgomery_mul_ct)),
794    /// so the
795    /// base and intermediate Montgomery values do not leak through timing.
796    /// What DOES leak is the bit pattern of `exp` — which is fine by
797    /// construction: the caller asserts the exponent is public.
798    ///
799    /// **Do not call with a secret exponent.** Use [`exp`](Self::exp)
800    /// instead, which is a fixed-iteration Montgomery ladder.
801    pub fn exp_public_exp(&self, base: &Residue<'_, T, Ct>, exp: &T) -> Residue<'_, T, Ct>
802    where
803        T: CiosMontMulCt + core::ops::Shr<usize, Output = T> + core::ops::BitAnd<Output = T>,
804    {
805        let w = type_bit_width::<T>();
806        let one = T::one();
807        let zero = T::zero();
808
809        // Find the position of the highest set bit (1-indexed: hi == top + 1).
810        // This loop and the rest of the function leak `bit_length(exp)`,
811        // which is the documented contract — `exp` is public.
812        let mut hi = w;
813        while hi > 0 {
814            if (*exp >> (hi - 1)) & one != zero {
815                break;
816            }
817            hi -= 1;
818        }
819
820        if hi == 0 {
821            // exp == 0: return 1 in Montgomery form.
822            return Residue {
823                mont: self.r_mod_n,
824                _brand: PhantomData,
825                _p: PhantomData,
826            };
827        }
828
829        // The top bit is set, so result starts at `base` (base^1 contribution
830        // for the 2^(hi-1) term). Then iterate over the remaining bits.
831        let mut result = base.mont;
832        for i in (0..hi - 1).rev() {
833            // Square.
834            result =
835                CiosMontMulCt::cios_mont_mul_ct(&result, &result, &self.modulus, &self.n_prime);
836            // Multiply only when the bit is set — branch on a public value.
837            if (*exp >> i) & one != zero {
838                result = CiosMontMulCt::cios_mont_mul_ct(
839                    &result,
840                    &base.mont,
841                    &self.modulus,
842                    &self.n_prime,
843                );
844            }
845        }
846
847        Residue {
848            mont: result,
849            _brand: PhantomData,
850            _p: PhantomData,
851        }
852    }
853
854    /// Wide multiply-accumulate (CT carry).
855    ///
856    /// Brand-tagged wrapper around [`wide_montgomery_mul_acc_ct`]. Pair
857    /// with [`Field::wide_redc`] (CT variant) to close the accumulator.
858    /// See the free-function for the `N ≤ R/q` bound contract.
859    pub fn mul_acc(&self, acc: (T, T), a: &Residue<'_, T, Ct>, b: &Residue<'_, T, Ct>) -> (T, T)
860    where
861        T: WideMul + subtle::ConditionallySelectable,
862    {
863        wide_montgomery_mul_acc_ct(acc.0, acc.1, a.mont, b.mont)
864    }
865
866    /// Close a wide accumulator (CT finalize) into a brand-tagged
867    /// [`Residue`].
868    pub fn wide_redc(&self, acc: (T, T)) -> Residue<'_, T, Ct>
869    where
870        T: WideMul + subtle::ConditionallySelectable + subtle::ConstantTimeLess,
871    {
872        let mont = wide_redc_ct(acc.0, acc.1, self.modulus, self.n_prime);
873        Residue {
874            mont,
875            _brand: PhantomData,
876            _p: PhantomData,
877        }
878    }
879
880    /// Modular inverse via Fermat: `a^(modulus − 2)` through the fixed-
881    /// iteration CT Montgomery ladder.
882    ///
883    /// **Requires `modulus` to be prime.** Constant-time over `a`'s
884    /// bits and zero-ness via the fixed `T::BITS`-iteration Montgomery
885    /// ladder in [`Self::exp`]. The loop count depends only on the
886    /// carrier type's bit width, not on `modulus - 2`'s significant
887    /// bit count or `a`'s value. Returns `CtOption::None`-masked for
888    /// the zero residue.
889    ///
890    /// Cost: one full ladder over every bit of `T` (e.g. 256
891    /// square-and-multiply iterations for a 256-bit carrier over a
892    /// Curve25519 scalar field), regardless of whether `modulus - 2`
893    /// occupies the full carrier width. For composite moduli (RSA
894    /// `n = p·q`) where Fermat doesn't apply, use
895    /// [`Self::inv_safegcd_ct`] instead.
896    pub fn inv_fermat(&self, a: &Residue<'_, T, Ct>) -> subtle::CtOption<Residue<'_, T, Ct>>
897    where
898        T: CiosMontMulCt
899            + const_num_traits::CtIsZero
900            + subtle::ConditionallySelectable
901            + subtle::ConstantTimeEq
902            + core::ops::Shr<usize, Output = T>
903            + core::ops::BitAnd<Output = T>,
904    {
905        let a_is_nonzero = !a.mont.ct_is_zero();
906        let two = T::one().wrapping_add(T::one());
907        let exp_val = self.modulus.wrapping_sub(two);
908        let result = self.exp(a, &exp_val);
909        subtle::CtOption::new(result, a_is_nonzero)
910    }
911
912    /// Constant-time modular inverse via Bernstein-Yang divsteps.
913    /// **Works for any modulus** — composite (RSA `n = p·q`) or prime —
914    /// unlike [`inv_fermat`] which assumes a prime modulus.
915    ///
916    /// Returns `CtOption::None` masked when `gcd(value, modulus) != 1`
917    /// (no inverse exists). Failure timing is independent of input
918    /// magnitudes.
919    ///
920    /// The modulus may occupy the full carrier width (MSB set in
921    /// `T`) — a 2048-bit RSA modulus works in an exact 2048-bit
922    /// carrier. The algorithm's signed intermediates are carried in
923    /// an internally widened representation, so no headroom bit is
924    /// required of `T`.
925    ///
926    /// Used by RSA private-key blinding, where the modulus is the
927    /// composite `n = p·q` and Fermat's little theorem doesn't apply.
928    /// See the `inv::safegcd` module source for the algorithm and
929    /// full precondition list.
930    ///
931    /// [`inv_fermat`]: Self::inv_fermat
932    pub fn inv_safegcd_ct(&self, a: &Residue<'_, T, Ct>) -> subtle::CtOption<Residue<'_, T, Ct>>
933    where
934        T: CiosMontMulCt
935            + WideMul
936            + subtle::ConditionallySelectable
937            + subtle::ConstantTimeLess
938            + const_num_traits::CtIsZero
939            + modmath_cios::CiosRowOps
940            + core::ops::Shr<usize, Output = T>
941            + core::ops::Shl<usize, Output = T>
942            + core::ops::BitOr<Output = T>,
943        <T as modmath_cios::CiosRowOps>::Word: const_num_traits::CtParity,
944    {
945        // The value in the Residue is in Montgomery form. To get the
946        // Montgomery form of the inverse:
947        //   a.mont           = value · R mod n
948        //   raw_inv          = safegcd(a.mont, n) = a.mont⁻¹ mod n
949        //                    = (value · R)⁻¹ mod n
950        //                    = value⁻¹ · R⁻¹ mod n
951        //   wanted: inv.mont = value⁻¹ · R mod n
952        //                    = raw_inv · R² mod n
953        // Computing raw_inv · R² mod n via Mont multiplications requires
954        // **two** multiplications by R², not one:
955        //   m1 = REDC(raw_inv · R²) = raw_inv · R mod n  (= value⁻¹ raw)
956        //   m2 = REDC(m1 · R²)      = m1 · R mod n       (= value⁻¹ · R = inv.mont)
957        // The first multiplication "converts raw_inv into something that
958        // multiplied by R again gives the desired Mont form". The
959        // second multiplication does that final · R step. Equivalent
960        // to one multiplication by R³, but we only have R² cached.
961        let inv_raw = crate::inv::safegcd::safegcd_inv_ct(&a.mont, &self.modulus);
962        // Extract the raw inverse, defaulting to zero when safegcd
963        // reports `None`. The two REDCs run unconditionally on the
964        // extracted value — under the CtOption mask any garbage they
965        // produce on the failure path is discarded.
966        let inv_exists = inv_raw.is_some();
967        let raw_inv = inv_raw.unwrap_or(T::zero());
968        let m1 = wide_montgomery_mul_ct(raw_inv, self.r2_mod_n, self.modulus, self.n_prime);
969        let mont = wide_montgomery_mul_ct(m1, self.r2_mod_n, self.modulus, self.n_prime);
970        let residue = Residue {
971            mont,
972            _brand: PhantomData,
973            _p: PhantomData,
974        };
975        subtle::CtOption::new(residue, inv_exists)
976    }
977}
978
979// ---------------------------------------------------------------------------
980// Tests
981// ---------------------------------------------------------------------------
982
983#[cfg(test)]
984mod tests {
985    use super::*;
986    use fixed_bigint::FixedUInt;
987    use subtle::Choice;
988    #[cfg(feature = "zeroize")]
989    use zeroize::Zeroize;
990
991    // Field<T, P> requires the right combination of T-bounds for the chosen
992    // P (CiosMontMul for Nct, CiosMontMulCt for Ct), which in practice means
993    // T must be a FixedUInt of the matching personality. Small tests use
994    // FixedUInt<u8, 2> aliases for tight ranges; larger tests use the U128
995    // family. Cross-personality tests bridge values via `.into()` (Nct → Ct)
996    // and `.forget_ct()` (explicit Ct → Nct).
997    type U16 = FixedUInt<u8, 2>;
998    type U16Ct = FixedUInt<u8, 2, Ct>;
999    type U128Ct = FixedUInt<u32, 4, Ct>;
1000
1001    fn u16(n: u16) -> U16 {
1002        U16::from(n)
1003    }
1004
1005    fn u16ct(n: u16) -> U16Ct {
1006        U16Ct::from(n)
1007    }
1008
1009    #[test]
1010    fn round_trip_small() {
1011        let f: Field<U16> = Field::new(u16(13)).unwrap();
1012        for raw in 0u16..13 {
1013            let r = f.reduce(&u16(raw));
1014            assert_eq!(f.into_raw(&r), u16(raw), "round trip failed for {raw}");
1015        }
1016    }
1017
1018    #[test]
1019    fn new_odd_matches_new() {
1020        // The infallible Odd-typestate constructor and the runtime-checked
1021        // `Option`-returning one must agree on the precompute (modulus,
1022        // n_prime, r_mod_n, r2_mod_n) for the same modulus value.
1023        let m = u16(13);
1024        let modulus_odd = Odd::new(m).expect("13 is odd");
1025        let from_odd: Field<U16> = Field::new_odd(modulus_odd);
1026        let from_opt: Field<U16> = Field::new(m).unwrap();
1027        assert_eq!(from_odd.modulus(), from_opt.modulus());
1028        // Round-trip through Field::mul under each to confirm the precompute
1029        // tables match observably.
1030        let a = from_odd.reduce(&u16(7));
1031        let b = from_odd.reduce(&u16(5));
1032        let via_odd = from_odd.into_raw(&from_odd.mul(&a, &b));
1033        let a2 = from_opt.reduce(&u16(7));
1034        let b2 = from_opt.reduce(&u16(5));
1035        let via_opt = from_opt.into_raw(&from_opt.mul(&a2, &b2));
1036        assert_eq!(via_odd, via_opt);
1037        assert_eq!(via_odd, u16(35 % 13));
1038    }
1039
1040    #[test]
1041    fn new_rejects_even_and_zero() {
1042        // Wrapper preserves the rejection semantics of the old API.
1043        assert!(Field::<U16>::new(u16(0)).is_none());
1044        assert!(Field::<U16>::new(u16(12)).is_none()); // even
1045        assert!(Field::<U16>::new(u16(13)).is_some()); // odd
1046    }
1047
1048    /// `try_new_odd_ct` produces a `CtOption<Field<T, Ct>>` whose
1049    /// `Some`-ness tracks `T::ct_is_odd`. The precompute runs
1050    /// unconditionally; the parity check is masked, not branched. Test
1051    /// on `u32` (which impls `CtParity` directly) since that's the
1052    /// straightforward case — the RSA-CRT consumer pattern will be on a
1053    /// bigint type, but the contract we're pinning here is the
1054    /// modmath-side adapter.
1055    #[test]
1056    fn try_new_odd_ct_masks_parity() {
1057        // Even modulus → `None`-masked.
1058        let even = Field::<u32, Ct>::try_new_odd_ct(12);
1059        assert_eq!(even.is_some().unwrap_u8(), 0);
1060
1061        // Zero is even → `None`-masked.
1062        let zero = Field::<u32, Ct>::try_new_odd_ct(0);
1063        assert_eq!(zero.is_some().unwrap_u8(), 0);
1064
1065        // Odd modulus → `Some` with a usable Field.
1066        let odd = Field::<u32, Ct>::try_new_odd_ct(13);
1067        assert_eq!(odd.is_some().unwrap_u8(), 1);
1068        let field: Field<u32, Ct> = odd.unwrap();
1069        // Same precompute as the infallible boundary constructor:
1070        let baseline = Field::<u32, Ct>::new_odd(Odd::new(13u32).unwrap());
1071        assert_eq!(field.modulus(), baseline.modulus());
1072    }
1073
1074    /// `Field::new_odd_ct` (the CT precompute path) must produce
1075    /// identical precompute values to `Field::new_odd` (the
1076    /// variable-time path) for every modulus. Pins the contract that
1077    /// `mod_double_ct` / `mod_exp2_ct` are CT-equivalent, not just
1078    /// "CT but different output."
1079    #[test]
1080    fn new_odd_ct_precompute_matches_new_odd() {
1081        for m in [3u32, 5, 7, 11, 13, 97, 65521, 0x7FFF_FFE7] {
1082            let modulus = Odd::new(m).unwrap();
1083            let f_nct = Field::<u32, Ct>::new_odd(modulus);
1084            let f_ct = Field::<u32, Ct>::new_odd_ct(modulus);
1085            assert_eq!(f_nct.modulus(), f_ct.modulus(), "modulus mismatch at m={m}");
1086            assert_eq!(f_nct.n_prime, f_ct.n_prime, "n_prime mismatch at m={m}");
1087            assert_eq!(f_nct.r_mod_n, f_ct.r_mod_n, "r_mod_n mismatch at m={m}");
1088            assert_eq!(f_nct.r2_mod_n, f_ct.r2_mod_n, "r2_mod_n mismatch at m={m}");
1089        }
1090    }
1091
1092    /// CT precompute on multi-limb FixedUInt produces identical
1093    /// output to the variable-time precompute. The actual RSA-CRT
1094    /// shape — the precompute is what would silently produce
1095    /// wrong results if `mod_double_ct` had a bug.
1096    #[test]
1097    fn new_odd_ct_precompute_matches_new_odd_fixed_bigint() {
1098        // 128-bit odd modulus (composite, RSA-CRT-shape)
1099        let m = U128Ct::from(0xFFFF_FFFF_FFFF_FFE7u64);
1100        let modulus = Odd::new(m).unwrap();
1101        let f_nct = Field::<U128Ct, Ct>::new_odd(modulus);
1102        let f_ct = Field::<U128Ct, Ct>::new_odd_ct(modulus);
1103        assert_eq!(f_nct.modulus(), f_ct.modulus());
1104        assert_eq!(f_nct.n_prime, f_ct.n_prime);
1105        assert_eq!(f_nct.r_mod_n, f_ct.r_mod_n);
1106        assert_eq!(f_nct.r2_mod_n, f_ct.r2_mod_n);
1107    }
1108
1109    #[test]
1110    fn add_sub_mul_small() {
1111        let f: Field<U16> = Field::new(u16(13)).unwrap();
1112        for a_raw in 0u16..13 {
1113            for b_raw in 0u16..13 {
1114                let a = f.reduce(&u16(a_raw));
1115                let b = f.reduce(&u16(b_raw));
1116                assert_eq!(f.into_raw(&f.add(&a, &b)), u16((a_raw + b_raw) % 13));
1117                assert_eq!(
1118                    f.into_raw(&f.sub(&a, &b)),
1119                    u16((a_raw + 13 - b_raw) % 13),
1120                    "sub failed for {a_raw}, {b_raw}"
1121                );
1122                assert_eq!(f.into_raw(&f.mul(&a, &b)), u16((a_raw * b_raw) % 13));
1123            }
1124        }
1125    }
1126
1127    #[test]
1128    fn zero_one_identity_small() {
1129        let f: Field<U16> = Field::new(u16(13)).unwrap();
1130        let z = f.zero();
1131        let o = f.one();
1132        assert_eq!(f.into_raw(&z), u16(0));
1133        assert_eq!(f.into_raw(&o), u16(1));
1134        // a + 0 = a, a * 1 = a
1135        for raw in 0u16..13 {
1136            let a = f.reduce(&u16(raw));
1137            assert_eq!(f.into_raw(&f.add(&a, &z)), u16(raw));
1138            assert_eq!(f.into_raw(&f.mul(&a, &o)), u16(raw));
1139        }
1140    }
1141
1142    #[test]
1143    fn exp_small() {
1144        let f: Field<U16> = Field::new(u16(13)).unwrap();
1145        // 7^5 mod 13 = 11
1146        let base = f.reduce(&u16(7));
1147        let result = f.exp(&base, &u16(5));
1148        assert_eq!(f.into_raw(&result), u16(11));
1149        // x^0 = 1
1150        let r0 = f.exp(&base, &u16(0));
1151        assert_eq!(f.into_raw(&r0), u16(1));
1152    }
1153
1154    #[test]
1155    fn ct_round_trip_small() {
1156        let f = FieldCt::new(u16ct(13)).unwrap();
1157        for raw in 0u16..13 {
1158            let r = f.reduce(&u16ct(raw));
1159            assert_eq!(f.into_raw(&r), u16ct(raw));
1160        }
1161    }
1162
1163    #[test]
1164    fn ct_matches_nct_small() {
1165        let f: Field<U16> = Field::new(u16(13)).unwrap();
1166        let fc = FieldCt::new(u16ct(13)).unwrap();
1167        for a_raw in 0u16..13 {
1168            for b_raw in 0u16..13 {
1169                let a = f.reduce(&u16(a_raw));
1170                let b = f.reduce(&u16(b_raw));
1171                let ac = fc.reduce(&u16ct(a_raw));
1172                let bc = fc.reduce(&u16ct(b_raw));
1173
1174                assert_eq!(
1175                    f.into_raw(&f.add(&a, &b)),
1176                    fc.into_raw(&fc.add(&ac, &bc)).forget_ct()
1177                );
1178                assert_eq!(
1179                    f.into_raw(&f.sub(&a, &b)),
1180                    fc.into_raw(&fc.sub(&ac, &bc)).forget_ct()
1181                );
1182                assert_eq!(
1183                    f.into_raw(&f.mul(&a, &b)),
1184                    fc.into_raw(&fc.mul(&ac, &bc)).forget_ct()
1185                );
1186            }
1187        }
1188        // exp cross-check
1189        let base = f.reduce(&u16(7));
1190        let base_ct = fc.reduce(&u16ct(7));
1191        for e in 0u16..20 {
1192            assert_eq!(
1193                f.into_raw(&f.exp(&base, &u16(e))),
1194                fc.into_raw(&fc.exp(&base_ct, &u16ct(e))).forget_ct()
1195            );
1196        }
1197    }
1198
1199    #[test]
1200    fn ct_cswap_small() {
1201        let f = FieldCt::new(u16ct(13)).unwrap();
1202        let mut a = f.reduce(&u16ct(3));
1203        let mut b = f.reduce(&u16ct(7));
1204        // choice = 0: no swap
1205        ResidueCt::cswap(Choice::from(0), &mut a, &mut b);
1206        assert_eq!(f.into_raw(&a), u16ct(3));
1207        assert_eq!(f.into_raw(&b), u16ct(7));
1208        // choice = 1: swap
1209        ResidueCt::cswap(Choice::from(1), &mut a, &mut b);
1210        assert_eq!(f.into_raw(&a), u16ct(7));
1211        assert_eq!(f.into_raw(&b), u16ct(3));
1212    }
1213
1214    /// Under personality, the safe NCT → CT bridge requires converting the
1215    /// underlying T's personality (free `.into()` from fixed-bigint), then
1216    /// constructing a fresh `Field<_, Ct>` on the Ct-typed modulus. Same-T
1217    /// `Field<T, Nct> -> Field<T, Ct>` conversion is degenerate (the per-P
1218    /// impl blocks have disjoint trait bounds, so the result is a methodless
1219    /// variant); this test documents the actual bridge pattern.
1220    #[test]
1221    fn nct_to_ct_upgrade_small() {
1222        let f: Field<U16> = Field::new(u16(13)).unwrap();
1223        let modulus_ct: U16Ct = (*f.modulus()).into();
1224        let fc = FieldCt::new(modulus_ct).unwrap();
1225        let a = fc.reduce(&u16ct(7));
1226        let b = fc.reduce(&u16ct(5));
1227        assert_eq!(fc.into_raw(&fc.mul(&a, &b)), u16ct(9)); // 35 mod 13 = 9
1228    }
1229
1230    #[test]
1231    fn exp_public_exp_matches_ct_exp_small() {
1232        // For every (base, exp) pair, exp_public_exp must produce the same
1233        // result as the fixed-iteration ladder exp.
1234        let f = FieldCt::new(u16ct(13)).unwrap();
1235        let base = f.reduce(&u16ct(7));
1236        for e in 0u16..32 {
1237            let via_ladder = f.exp(&base, &u16ct(e));
1238            let via_pub = f.exp_public_exp(&base, &u16ct(e));
1239            assert_eq!(
1240                f.into_raw(&via_ladder),
1241                f.into_raw(&via_pub),
1242                "exp_public_exp mismatch at e={e}"
1243            );
1244        }
1245    }
1246
1247    #[test]
1248    fn exp_public_exp_matches_ct_exp_u128() {
1249        // Same cross-check at FixedUInt<u32, 4> sizes against a few
1250        // characteristic exponents: 0, 1, small, a value with both low and
1251        // high set bits.
1252        let modulus = !U128Ct::from(0u64) - U128Ct::from(58u64);
1253        let f = FieldCt::new(modulus).unwrap();
1254        let base = f.reduce(&U128Ct::from(0xDEAD_BEEF_u64));
1255        let exps = [
1256            U128Ct::from(0u64),
1257            U128Ct::from(1u64),
1258            U128Ct::from(7u64),
1259            U128Ct::from(65537u64), // RSA-style public exponent
1260            U128Ct::from(0xCAFE_BABEu64),
1261        ];
1262        for e in &exps {
1263            let via_ladder = f.exp(&base, e);
1264            let via_pub = f.exp_public_exp(&base, e);
1265            assert_eq!(
1266                f.into_raw(&via_ladder),
1267                f.into_raw(&via_pub),
1268                "exp_public_exp mismatch at e={e:?}"
1269            );
1270        }
1271    }
1272
1273    #[test]
1274    fn brand_round_trip_fixed_bigint_u128() {
1275        // A larger odd modulus.
1276        let modulus = !U128Ct::from(0u64) - U128Ct::from(58u64);
1277        let f = FieldCt::new(modulus).unwrap();
1278        let raw = U128Ct::from(0xDEAD_BEEF_u64);
1279        let r = f.reduce(&raw);
1280        assert_eq!(f.into_raw(&r), raw);
1281    }
1282
1283    /// `inv_safegcd_ct` round-trip on a prime modulus. The CT
1284    /// composite-modulus inverse is the load-bearing primitive for RSA
1285    /// blinding; here we test on a prime (smaller test surface) and
1286    /// verify `inv * value ≡ 1 mod modulus`.
1287    #[test]
1288    fn inv_safegcd_ct_round_trip_prime_modulus() {
1289        let f = FieldCt::new(u16ct(13)).unwrap();
1290        for raw_val in 1u16..13 {
1291            let r = f.reduce(&u16ct(raw_val));
1292            let inv = f.inv_safegcd_ct(&r);
1293            assert_eq!(
1294                inv.is_some().unwrap_u8(),
1295                1,
1296                "expected inverse for {raw_val} mod 13"
1297            );
1298            let inv_residue = inv.unwrap();
1299            let product = f.mul(&r, &inv_residue);
1300            assert_eq!(
1301                f.into_raw(&product),
1302                u16ct(1),
1303                "{raw_val} * inv != 1 mod 13"
1304            );
1305        }
1306    }
1307
1308    /// `inv_safegcd_ct` on a composite modulus — the RSA blinding case.
1309    /// Confirms the algorithm works when the modulus is `p·q`, not
1310    /// prime, where Fermat inversion would fail.
1311    #[test]
1312    fn inv_safegcd_ct_composite_modulus() {
1313        // n = 3 * 5 = 15. Coprime values: 1, 2, 4, 7, 8, 11, 13, 14.
1314        let f = FieldCt::new(u16ct(15)).unwrap();
1315        for &raw_val in &[1u16, 2, 4, 7, 8, 11, 13, 14] {
1316            let r = f.reduce(&u16ct(raw_val));
1317            let inv = f.inv_safegcd_ct(&r);
1318            assert_eq!(
1319                inv.is_some().unwrap_u8(),
1320                1,
1321                "expected inverse for {raw_val} mod 15"
1322            );
1323            let product = f.mul(&r, &inv.unwrap());
1324            assert_eq!(
1325                f.into_raw(&product),
1326                u16ct(1),
1327                "{raw_val} * inv != 1 mod 15"
1328            );
1329        }
1330        // Non-coprime values: safegcd returns None.
1331        for &raw_val in &[3u16, 5, 6, 9, 10, 12] {
1332            let r = f.reduce(&u16ct(raw_val));
1333            let inv = f.inv_safegcd_ct(&r);
1334            assert_eq!(
1335                inv.is_some().unwrap_u8(),
1336                0,
1337                "expected None for non-coprime {raw_val} mod 15"
1338            );
1339        }
1340    }
1341
1342    /// `inv_safegcd_ct` with a modulus that occupies the full carrier
1343    /// width (MSB set) — the exact-width-carrier case (a 2048-bit RSA
1344    /// modulus in a 2048-bit `T`), scaled down to a 16-bit carrier.
1345    #[test]
1346    fn inv_safegcd_ct_full_width_modulus() {
1347        // U16Ct = FixedUInt<u8, 2, Ct> — 16-bit carrier holding the
1348        // odd 16-bit modulus 0xFFFD = 13 · 71² (MSB set, composite).
1349        let modulus = u16ct(0xFFFD);
1350        let f = FieldCt::new(modulus).unwrap();
1351        for raw_val in [1u16, 2, 7, 0xBEEF, 0xFFFC] {
1352            let r = f.reduce(&u16ct(raw_val));
1353            let inv = f.inv_safegcd_ct(&r);
1354            assert_eq!(
1355                inv.is_some().unwrap_u8(),
1356                1,
1357                "expected inverse for {raw_val:#x} mod 0xFFFD"
1358            );
1359            let product = f.mul(&r, &inv.unwrap());
1360            assert_eq!(
1361                f.into_raw(&product),
1362                u16ct(1),
1363                "{raw_val:#x} * inv != 1 mod 0xFFFD"
1364            );
1365        }
1366        // Non-coprime (shares factor 13) still masks to None.
1367        let r = f.reduce(&u16ct(13));
1368        assert_eq!(f.inv_safegcd_ct(&r).is_some().unwrap_u8(), 0);
1369    }
1370
1371    /// `inv_safegcd_ct` on a larger RSA-CRT-shaped composite modulus.
1372    /// n = p · q with small primes p, q. Confirms the algorithm runs
1373    /// correctly on a multi-limb FixedUInt and at sizes more
1374    /// representative of the RSA blinding workload than the toy
1375    /// `mod 15` case (still small enough that we can exhaustively
1376    /// check inv * value ≡ 1).
1377    #[test]
1378    fn inv_safegcd_ct_composite_modulus_u128() {
1379        // n = (2^32 + 7) · (2^24 + 7) — RSA-CRT-shape two-prime
1380        // composite, ~52 bits. safegcd handles composites; the result
1381        // works for any coprime value.
1382        let n_raw: u64 = 0x1_0000_0007 * 0x100_0007u64; // = 4503599644606465
1383        let modulus = U128Ct::from(n_raw);
1384        let f = FieldCt::new(modulus).unwrap();
1385
1386        // A handful of values coprime to n. (0xDEAD_BEEF deliberately
1387        // omitted — it shares factor 11 with this n.)
1388        let test_vals = [
1389            U128Ct::from(1u64),
1390            U128Ct::from(2u64),
1391            U128Ct::from(3u64),
1392            U128Ct::from(0xCAFE_BABEu64),
1393            U128Ct::from(0xFEED_FACEu64),
1394        ];
1395        for v in test_vals {
1396            let r = f.reduce(&v);
1397            let inv = f.inv_safegcd_ct(&r);
1398            assert_eq!(
1399                inv.is_some().unwrap_u8(),
1400                1,
1401                "expected inverse to exist for v={:?}",
1402                v
1403            );
1404            let product = f.mul(&r, &inv.unwrap());
1405            assert_eq!(f.into_raw(&product), U128Ct::from(1u64));
1406        }
1407    }
1408
1409    #[cfg(feature = "zeroize")]
1410    #[test]
1411    fn residue_zeroize_wipes_mont_small() {
1412        fn assert_zeroize_on_drop<T: zeroize::ZeroizeOnDrop>(_: &T) {}
1413        let f = FieldCt::new(u16ct(13)).unwrap();
1414        let mut r = f.reduce(&u16ct(7));
1415        assert_zeroize_on_drop(&r);
1416        assert_ne!(*r.mont_value(), u16ct(0));
1417        r.zeroize();
1418        assert_eq!(*r.mont_value(), u16ct(0));
1419    }
1420
1421    #[test]
1422    fn residue_from_mont_escape_hatch_small() {
1423        // Round-trip via the escape hatch: reduce -> mont_value -> residue_from_mont.
1424        let f: Field<U16> = Field::new(u16(13)).unwrap();
1425        for raw in 0u16..13 {
1426            let r = f.reduce(&u16(raw));
1427            let mont = *r.mont_value();
1428            let r2 = f.residue_from_mont(mont);
1429            assert_eq!(f.into_raw(&r2), u16(raw));
1430        }
1431    }
1432
1433    /// Documented limitation: covariance allows mixing residues across two
1434    /// distinct Field instances built in the same scope. Asserts current
1435    /// behavior (the compiler does NOT reject this) so a future generative
1436    /// brand can be observed as a hardening change.
1437    #[test]
1438    fn covariance_mixes_residues_documented_limitation() {
1439        let f1: Field<U16> = Field::new(u16(13)).unwrap();
1440        let f2: Field<U16> = Field::new(u16(13)).unwrap();
1441        let r1 = f1.reduce(&u16(5));
1442        // f2 accepting r1 compiles today. This is a documented limitation; a
1443        // generative brand would make this a type error.
1444        let _ = f2.into_raw(&r1);
1445    }
1446
1447    /// Personality demonstration: the same `Field` type signature
1448    /// parameterized differently (`<_, Nct>` vs `<_, Ct>`) computes the
1449    /// same modular arithmetic, with the personality choice driving which
1450    /// algorithm (variable-time branch vs CT conditional-select finalize)
1451    /// the compiler routes to via the per-P impl blocks.
1452    ///
1453    /// Also exercises the residue type discipline: a `Residue<_, _, Nct>`
1454    /// passed to a `Field<_, Ct>` method would be a compile error
1455    /// (different `P` parameter), and vice versa. Cross-personality
1456    /// comparison goes through `.forget_ct()` rather than a same-type
1457    /// `assert_eq!`.
1458    #[test]
1459    fn field_p_personality_cross_check_small() {
1460        // Same modulus value, two personalities.
1461        let m_nct = u16(13);
1462        let m_ct: U16Ct = m_nct.into();
1463
1464        let f_nct: Field<U16, Nct> = Field::new(m_nct).unwrap();
1465        let f_ct: Field<U16Ct, Ct> = Field::new(m_ct).unwrap();
1466
1467        // Pick a non-trivial product and exponentiation.
1468        let a_nct = f_nct.reduce(&u16(7));
1469        let b_nct = f_nct.reduce(&u16(5));
1470        let a_ct = f_ct.reduce(&u16ct(7));
1471        let b_ct = f_ct.reduce(&u16ct(5));
1472
1473        // Multiplication agrees across personalities.
1474        let mul_nct = f_nct.into_raw(&f_nct.mul(&a_nct, &b_nct));
1475        let mul_ct = f_ct.into_raw(&f_ct.mul(&a_ct, &b_ct));
1476        assert_eq!(mul_nct, mul_ct.forget_ct());
1477
1478        // Exponentiation agrees (with different algorithms underneath:
1479        // f_nct.exp is variable-time square-and-multiply, f_ct.exp is
1480        // fixed-iteration ladder).
1481        let exp_nct = f_nct.into_raw(&f_nct.exp(&a_nct, &u16(11)));
1482        let exp_ct = f_ct.into_raw(&f_ct.exp(&a_ct, &u16ct(11)));
1483        assert_eq!(exp_nct, exp_ct.forget_ct());
1484    }
1485
1486    /// The `FieldNct<T>` alias side-steps the construction-site type-
1487    /// ambiguity that bare `Field::new(modulus)` hits — no type annotation
1488    /// or turbofish required, because the alias fixes `P = Nct` at the
1489    /// type level (mirroring how `FieldCt::new` fixes `P = Ct`).
1490    ///
1491    /// Symmetric `ResidueNct` alias also exists for downstream consumers
1492    /// who want symmetric naming. Used here just for the type spelling.
1493    #[test]
1494    fn field_nct_alias_resolves_without_annotation() {
1495        let f = FieldNct::new(u16(13)).unwrap();
1496        let r: ResidueNct<'_, U16> = f.reduce(&u16(7));
1497        assert_eq!(f.into_raw(&r), u16(7));
1498        let two = f.reduce(&u16(2));
1499        assert_eq!(f.into_raw(&f.mul(&r, &two)), u16(14 % 13));
1500    }
1501
1502    /// `from_precomputed` is `const fn` and usable in a const initializer.
1503    /// This is the constructor static-modulus consumers (PQC, embedded RSA
1504    /// with a baked key, etc.) reach for when they want to expose a `Field`
1505    /// as a `const` associated item rather than paying the runtime
1506    /// `Field::new` precompute.
1507    ///
1508    /// Demonstrated here over `u32` (primitive) — `Field<u32, Nct>` is
1509    /// methodless because `u32` doesn't impl `CiosMontMul` (MulAccOps is
1510    /// FixedUInt-only), but `from_precomputed` itself works for any
1511    /// `T: Copy`. The intended consumer path is a downstream Mont-newtype
1512    /// wrapper that calls modmath's standalone `wide_montgomery_mul[_ct]`
1513    /// free functions, using `f.modulus()` to read the static modulus.
1514    #[test]
1515    fn from_precomputed_const_construction_u32() {
1516        // Hand-computed Montgomery params for modulus 13 at word width 32:
1517        //   n_prime  = -13^-1 mod 2^32 = 0x4EC4EC4F
1518        //   r_mod_n  = 2^32 mod 13     = 9
1519        //   r2_mod_n = (2^32)^2 mod 13 = 3
1520        const F: Field<u32, Nct> = Field::from_precomputed(13u32, 0x4EC4EC4F, 9, 3);
1521        assert_eq!(*F.modulus(), 13u32);
1522        // The struct fields are accessible to anyone in the same crate
1523        // through Field::modulus(); downstream consumers driving the Mont
1524        // newtype pattern will pull modulus + n_prime + r/r2 via a Modulus
1525        // trait extension on their own side and call modmath's standalone
1526        // primitives. This test just proves the const-context construction
1527        // path is real.
1528    }
1529
1530    /// `Field::mul_acc` + `Field::wide_redc` from a zero accumulator
1531    /// must equal `Field::mul` on the same operands.
1532    #[test]
1533    fn field_mul_acc_round_trip_small() {
1534        let f: Field<U16> = Field::new(u16(13)).unwrap();
1535        for a_raw in 0u16..13 {
1536            for b_raw in 0u16..13 {
1537                let a = f.reduce(&u16(a_raw));
1538                let b = f.reduce(&u16(b_raw));
1539                let direct = f.mul(&a, &b);
1540                let via_acc = f.wide_redc(f.mul_acc((u16(0), u16(0)), &a, &b));
1541                assert_eq!(f.into_raw(&direct), f.into_raw(&via_acc));
1542            }
1543        }
1544    }
1545
1546    /// Dot product through `Field::mul_acc` + single `Field::wide_redc`
1547    /// must equal the direct residue-domain sum of products.
1548    #[test]
1549    fn field_mul_acc_dot_product_small() {
1550        let f: Field<U16> = Field::new(u16(13)).unwrap();
1551        let pairs: &[(u16, u16)] = &[(2, 3), (5, 7), (11, 4), (1, 12)];
1552        let mut acc = (u16(0), u16(0));
1553        for &(a_raw, b_raw) in pairs {
1554            let a = f.reduce(&u16(a_raw));
1555            let b = f.reduce(&u16(b_raw));
1556            acc = f.mul_acc(acc, &a, &b);
1557        }
1558        let result = f.wide_redc(acc);
1559        let expected: u16 = pairs
1560            .iter()
1561            .fold(0u16, |s, &(a, b)| (s + (a * b) % 13) % 13);
1562        assert_eq!(f.into_raw(&result), u16(expected));
1563    }
1564
1565    /// `a * inv_fermat(a) == 1` for every nonzero residue at prime
1566    /// modulus 13; zero returns `None`.
1567    #[test]
1568    fn field_inv_fermat_small() {
1569        let f: Field<U16> = Field::new(u16(13)).unwrap();
1570        for raw in 1u16..13 {
1571            let a = f.reduce(&u16(raw));
1572            let inv = f.inv_fermat(&a).unwrap();
1573            assert_eq!(
1574                f.into_raw(&f.mul(&a, &inv)),
1575                u16(1),
1576                "fermat fails at {raw}"
1577            );
1578        }
1579        assert!(f.inv_fermat(&f.zero()).is_none());
1580    }
1581
1582    /// Same contract as inv_fermat but via EEA path; cross-checks the
1583    /// two methods agree at every nonzero residue.
1584    #[test]
1585    fn field_inv_eea_small() {
1586        let f: Field<U16> = Field::new(u16(13)).unwrap();
1587        for raw in 1u16..13 {
1588            let a = f.reduce(&u16(raw));
1589            let inv_e = f.inv_eea(&a).unwrap();
1590            let inv_f = f.inv_fermat(&a).unwrap();
1591            assert_eq!(f.into_raw(&f.mul(&a, &inv_e)), u16(1), "eea fails at {raw}");
1592            assert_eq!(
1593                f.into_raw(&inv_e),
1594                f.into_raw(&inv_f),
1595                "fermat/eea disagree at {raw}"
1596            );
1597        }
1598        assert!(f.inv_eea(&f.zero()).is_none());
1599    }
1600
1601    /// Ct variant of `Field::mul_acc` + `Field::wide_redc` must agree
1602    /// with `Field::mul`.
1603    #[test]
1604    fn field_mul_acc_ct_round_trip_small() {
1605        let f = FieldCt::new(u16ct(13)).unwrap();
1606        for a_raw in 0u16..13 {
1607            for b_raw in 0u16..13 {
1608                let a = f.reduce(&u16ct(a_raw));
1609                let b = f.reduce(&u16ct(b_raw));
1610                let direct = f.mul(&a, &b);
1611                let via_acc = f.wide_redc(f.mul_acc((u16ct(0), u16ct(0)), &a, &b));
1612                assert_eq!(f.into_raw(&direct), f.into_raw(&via_acc));
1613            }
1614        }
1615    }
1616
1617    /// Ct `inv_fermat` must satisfy `a * inv(a) == 1` at prime modulus.
1618    #[test]
1619    fn field_inv_fermat_ct_small() {
1620        let f = FieldCt::new(u16ct(13)).unwrap();
1621        for raw in 1u16..13 {
1622            let a = f.reduce(&u16ct(raw));
1623            let inv = f.inv_fermat(&a).into_option().unwrap();
1624            assert_eq!(
1625                f.into_raw(&f.mul(&a, &inv)),
1626                u16ct(1),
1627                "ct fermat fails at {raw}"
1628            );
1629        }
1630        assert!(f.inv_fermat(&f.zero()).into_option().is_none());
1631    }
1632
1633    /// `ResidueCt::ct_eq` matches `PartialEq` outcomes on representative
1634    /// inputs (true and false cases).
1635    #[test]
1636    fn residue_ct_eq_small() {
1637        let f = FieldCt::new(u16ct(13)).unwrap();
1638        let a = f.reduce(&u16ct(7));
1639        let b = f.reduce(&u16ct(7));
1640        let c = f.reduce(&u16ct(8));
1641        let eq_ab: bool = a.ct_eq(&b).into();
1642        let eq_ac: bool = a.ct_eq(&c).into();
1643        assert!(eq_ab);
1644        assert!(!eq_ac);
1645    }
1646}