Skip to main content

fixed_bigint/heapless/
arith.rs

1//! Add / Sub / Mul for `HeaplessBigInt`.
2//!
3//! Every op operates at the operands' width `max(a.len, b.len)` and
4//! returns bit-for-bit what the same-width `FixedUInt` returns: a value
5//! carried at `len = k` is a `k`-word integer, and the capacity beyond
6//! `len` does not exist as far as arithmetic is concerned. Wrapping,
7//! overflow, and carry/borrow all resolve at that width; `CAP` never
8//! enters an answer.
9//!
10//! Loop counts derive from the public `len`s and per-limb branchless
11//! primitives from `const_num_traits`, so iteration is CT-safe under the
12//! "len is public" invariant regardless of Personality — the Nct and Ct
13//! arms share one body.
14
15use super::cmp::ct_select;
16use super::{HeaplessBigInt, is_zero, zero};
17use crate::MachineWord;
18use const_num_traits::{
19    BorrowingSub, Bounded, CarryingAdd, CarryingMul, CheckedAdd, CheckedMul, CheckedSub, Ct, Nct,
20    OverflowingAdd, OverflowingMul, OverflowingSub, Personality, PersonalityTag, SaturatingAdd,
21    SaturatingMul, SaturatingSub, WrappingAdd, WrappingMul, WrappingSub,
22};
23use core::marker::PhantomData;
24
25// The checked `+`/`-`/`*` operators panic on overflow at the operands'
26// width. That panic branches on a data-dependent overflow bit, so it
27// runs for `Nct` only; the `Ct` arm wraps silently (like `wrapping_*`),
28// keeping control flow value-independent. Mirrors `FixedUInt`'s
29// `maybe_panic_if::<P>`.
30// All-ones value at a given width (`len` limbs saturated). This is the
31// saturation target for add/mul overflow: the max at the *operand* width
32// `max(a.len, b.len)`, matching `FixedUInt<T, width>::max_value()`. It is NOT
33// `Bounded::max_value()`, which on this carrier is the CAP-wide max.
34#[inline]
35pub(crate) fn max_at_len<T: MachineWord, const CAP: usize, P: Personality>(
36    len: u16,
37) -> HeaplessBigInt<T, CAP, P> {
38    let mut limbs = [zero::<T>(); CAP];
39    for l in &mut limbs[..len as usize] {
40        *l = <T as Bounded>::max_value();
41    }
42    HeaplessBigInt {
43        limbs,
44        len,
45        _p: PhantomData,
46    }
47}
48
49#[inline]
50fn panic_on_overflow_if_nct<P: Personality>(overflow: bool, msg: &'static str) {
51    match P::TAG {
52        PersonalityTag::Nct => assert!(!overflow, "{}", msg),
53        PersonalityTag::Ct => {}
54    }
55}
56
57// The value/mixed receiver forms of `+`/`-`/`*` are uniform pure delegation
58// to the hand-written `&Self op &Self` core (which owns the panic/wrap rule).
59// Trailing tokens after the method name become extra `T` bounds (`Mul` needs
60// `CarryingMul`).
61macro_rules! forward_arith_receivers {
62    ($imp:ident, $method:ident $($bound:tt)*) => {
63        impl<T: MachineWord $($bound)*, const CAP: usize, P: Personality> core::ops::$imp
64            for HeaplessBigInt<T, CAP, P>
65        {
66            type Output = Self;
67            fn $method(self, other: Self) -> Self {
68                (&self).$method(&other)
69            }
70        }
71        impl<T: MachineWord $($bound)*, const CAP: usize, P: Personality>
72            core::ops::$imp<&HeaplessBigInt<T, CAP, P>> for HeaplessBigInt<T, CAP, P>
73        {
74            type Output = Self;
75            fn $method(self, other: &Self) -> Self {
76                (&self).$method(other)
77            }
78        }
79        impl<T: MachineWord $($bound)*, const CAP: usize, P: Personality>
80            core::ops::$imp<HeaplessBigInt<T, CAP, P>> for &HeaplessBigInt<T, CAP, P>
81        {
82            type Output = HeaplessBigInt<T, CAP, P>;
83            fn $method(self, other: HeaplessBigInt<T, CAP, P>) -> HeaplessBigInt<T, CAP, P> {
84                self.$method(&other)
85            }
86        }
87    };
88}
89
90// ── Free-function slice kernels ──
91//
92// Take `&[T]` / `&mut [T]` so a future refactor can share them with
93// `fixed-bigint`'s existing fixed-width algorithms.
94
95/// `out[..n] = a[..n] + b[..n]`, returning the final carry-out.
96/// All three slices must have length ≥ `n`. `a` / `b` beyond their
97/// respective logical `len`s must be zero (zero-tail invariant).
98///
99/// Slicing to `..n` up front bounds-checks once; the zip loop then has no
100/// per-element indexing, so the body is panic-free.
101#[inline]
102pub(crate) fn add_slice<T: MachineWord>(a: &[T], b: &[T], out: &mut [T], n: usize) -> bool {
103    let mut carry = false;
104    for ((&ai, &bi), oi) in a[..n].iter().zip(&b[..n]).zip(&mut out[..n]) {
105        let (sum, c) = <T as CarryingAdd>::carrying_add(ai, bi, carry);
106        *oi = sum;
107        carry = c;
108    }
109    carry
110}
111
112/// `out[..n] = a[..n] - b[..n]`, returning the final borrow-out.
113/// Same length / zero-tail preconditions as [`add_slice`].
114#[inline]
115pub(crate) fn sub_slice<T: MachineWord>(a: &[T], b: &[T], out: &mut [T], n: usize) -> bool {
116    let mut borrow = false;
117    for ((&ai, &bi), oi) in a[..n].iter().zip(&b[..n]).zip(&mut out[..n]) {
118        let (diff, br) = <T as BorrowingSub>::borrowing_sub(ai, bi, borrow);
119        *oi = diff;
120        borrow = br;
121    }
122    borrow
123}
124
125/// Schoolbook `out[..out_n] += a[..a_n] * b[..b_n]`. Assumes `out` is
126/// zero-initialised on entry. A partial product past `out_n` is silently
127/// truncated; `wrapping_mul` passes `out_n = max(a_n, b_n)` to keep the
128/// low `width` words (the high half is dropped, as at a fixed width).
129///
130/// `T: CarryingMul<Unsigned = T, Output = T>` is stated explicitly because
131/// `MachineWord`'s supertrait chain does not include `CarryingMul` — the
132/// FixedUInt path routes multiplication through the `ConstDoubleWord`
133/// associated type rather than the `CarryingMul` primitive.
134#[inline]
135pub(crate) fn mul_slice<T: MachineWord + CarryingMul<Unsigned = T, Output = T>>(
136    a: &[T],
137    a_n: usize,
138    b: &[T],
139    b_n: usize,
140    out: &mut [T],
141    out_n: usize,
142) {
143    // Slice to the logical lengths up front: the `a[i]` / `b[j]` reads and
144    // the `out[pos]` writes are then provably in bounds, so LLVM drops the
145    // per-access bounds checks from the hot nested loop.
146    let a = &a[..a_n];
147    let b = &b[..b_n];
148    let out = &mut out[..out_n];
149    let mut i = 0;
150    while i < a_n {
151        let mut carry = zero::<T>();
152        let mut j = 0;
153        while j < b_n {
154            let pos = i + j;
155            if pos < out_n {
156                let (lo, hi) = <T as CarryingMul>::carrying_mul(a[i], b[j], carry);
157                let (sum, c1) = <T as CarryingAdd>::carrying_add(out[pos], lo, false);
158                out[pos] = sum;
159                let (new_carry, _) = <T as CarryingAdd>::carrying_add(hi, zero::<T>(), c1);
160                carry = new_carry;
161            }
162            j += 1;
163        }
164        let tail = i + b_n;
165        if tail < out_n {
166            let (sum, _) = <T as CarryingAdd>::carrying_add(out[tail], carry, false);
167            out[tail] = sum;
168        }
169        i += 1;
170    }
171}
172
173// ── Inherent methods on HeaplessBigInt ──
174
175impl<T: MachineWord, const CAP: usize, P: Personality> HeaplessBigInt<T, CAP, P> {
176    /// Wrapping addition at the operands' width `max(a.len, b.len)`; a
177    /// carry out of that width is discarded.
178    pub fn wrapping_add(&self, other: &Self) -> Self {
179        let out_len = core::cmp::max(self.len as usize, other.len as usize);
180        let mut out = Self::new_zero_with_len(out_len as u16);
181        let _carry = add_slice(&self.limbs, &other.limbs, &mut out.limbs, out_len);
182        debug_assert!(zero_tail_ok(&out.limbs, out_len));
183        out
184    }
185
186    /// Overflowing addition, at the operands' width `max(a.len, b.len)`.
187    /// Returns `(sum mod 2^(width·word_bits), carry_out)` — the carry is
188    /// the bit beyond the width, reported as a flag, exactly as
189    /// `FixedUInt<T, width>::overflowing_add` does. Symmetric to
190    /// [`overflowing_sub`](Self::overflowing_sub). Does not grow a limb.
191    pub fn overflowing_add(&self, other: &Self) -> (Self, bool) {
192        let out_len = core::cmp::max(self.len as usize, other.len as usize);
193        let mut out = Self::new_zero_with_len(out_len as u16);
194        let carry = add_slice(&self.limbs, &other.limbs, &mut out.limbs, out_len);
195        (out, carry)
196    }
197
198    /// Checked addition. `None` on overflow at the operands' width.
199    pub fn checked_add(&self, other: &Self) -> Option<Self> {
200        let (res, overflow) = self.overflowing_add(other);
201        if overflow { None } else { Some(res) }
202    }
203
204    /// Wrapping subtraction at the operands' width `max(a.len, b.len)`;
205    /// underflow wraps modulo `2^(max_len·WORD_BITS)`, so a value carried
206    /// at a narrower width wraps at that narrower width (like `u8` vs `u32`).
207    pub fn wrapping_sub(&self, other: &Self) -> Self {
208        let out_len = core::cmp::max(self.len as usize, other.len as usize);
209        let mut out = Self::new_zero_with_len(out_len as u16);
210        let _borrow = sub_slice(&self.limbs, &other.limbs, &mut out.limbs, out_len);
211        debug_assert!(zero_tail_ok(&out.limbs, out_len));
212        out
213    }
214
215    /// Overflowing subtraction. Returns `(wrapped_result, borrow_out)`.
216    /// Same width choice as [`wrapping_sub`](Self::wrapping_sub);
217    /// `borrow_out` is the underflow flag (`self < other`).
218    pub fn overflowing_sub(&self, other: &Self) -> (Self, bool) {
219        let out_len = core::cmp::max(self.len as usize, other.len as usize);
220        let mut out = Self::new_zero_with_len(out_len as u16);
221        let borrow = sub_slice(&self.limbs, &other.limbs, &mut out.limbs, out_len);
222        (out, borrow)
223    }
224
225    /// Checked subtraction. `None` on underflow.
226    pub fn checked_sub(&self, other: &Self) -> Option<Self> {
227        let (res, borrow) = self.overflowing_sub(other);
228        if borrow { None } else { Some(res) }
229    }
230}
231
232// Mul lives in its own impl block because `CarryingMul` is not part of
233// `MachineWord`'s supertrait chain — see `mul_slice`'s note.
234
235impl<T: MachineWord + CarryingMul<Unsigned = T, Output = T>, const CAP: usize, P: Personality>
236    HeaplessBigInt<T, CAP, P>
237{
238    /// Wrapping multiplication at the operands' width `max(a.len, b.len)`:
239    /// keeps the low `width` words (`a·b mod 2^(width·word_bits)`),
240    /// exactly like `FixedUInt<T, width>::wrapping_mul`. [`WideMul`] returns
241    /// both halves.
242    pub fn wrapping_mul(&self, other: &Self) -> Self {
243        let out_len = core::cmp::max(self.len as usize, other.len as usize);
244        let mut out = Self::new_zero_with_len(out_len as u16);
245        mul_slice(
246            &self.limbs,
247            self.len as usize,
248            &other.limbs,
249            other.len as usize,
250            &mut out.limbs,
251            out_len,
252        );
253        debug_assert!(zero_tail_ok(&out.limbs, out_len));
254        out
255    }
256
257    /// Overflowing multiplication, at the operands' width
258    /// `w = max(a.len, b.len)`. Returns `(a·b mod 2^(w·word_bits),
259    /// overflow)`, where `overflow` is set iff the product does not fit
260    /// in `w` words — bit-identical to `FixedUInt<T, w>::overflowing_mul`.
261    /// The split is at the value width (via the widening [`CarryingMul`]), so
262    /// the high half is the part beyond `w`; `CAP` is irrelevant.
263    pub fn overflowing_mul(&self, other: &Self) -> (Self, bool) {
264        let zero_v = <Self as const_num_traits::Zero>::zero();
265        let (lo, hi) = <Self as CarryingMul>::carrying_mul(*self, *other, zero_v);
266        (lo, !<Self as const_num_traits::Zero>::is_zero(&hi))
267    }
268
269    /// Checked multiplication. `None` when the product does not fit in the
270    /// operands' width `max(a.len, b.len)` — exactly when
271    /// `FixedUInt<T, width>::checked_mul` would return `None`.
272    pub fn checked_mul(&self, other: &Self) -> Option<Self> {
273        let (res, overflow) = self.overflowing_mul(other);
274        if overflow { None } else { Some(res) }
275    }
276}
277
278// ── const_num_traits CheckedAdd / CheckedMul (Nct only) ──
279//
280// The trait forms a downstream variable-time modular-inverse consumer binds
281// on. `checked_add` / `checked_mul` return `None` on overflow at the
282// operands' width, exactly as the same-width `FixedUInt` would. Trait
283// exposure is kept Nct-only to match the existing surface.
284// `HeaplessBigInt: Copy`, so bridging the by-value trait receiver to the
285// by-reference inherent method is free.
286
287impl<T, const CAP: usize> CheckedAdd for HeaplessBigInt<T, CAP, Nct>
288where
289    T: MachineWord,
290{
291    type Output = Self;
292    fn checked_add(self, v: Self) -> Option<Self> {
293        Self::checked_add(&self, &v)
294    }
295}
296
297impl<T, const CAP: usize> CheckedMul for HeaplessBigInt<T, CAP, Nct>
298where
299    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
300{
301    type Output = Self;
302    fn checked_mul(self, v: Self) -> Option<Self> {
303        Self::checked_mul(&self, &v)
304    }
305}
306
307impl<T, const CAP: usize> CheckedSub for HeaplessBigInt<T, CAP, Nct>
308where
309    T: MachineWord,
310{
311    type Output = Self;
312    fn checked_sub(self, v: Self) -> Option<Self> {
313        Self::checked_sub(&self, &v)
314    }
315}
316
317// Reference-receiver mirrors: `(&h).checked_add(&g)` binds the same generic
318// trait bound as the value form. The receiver and operand are already `&Self`,
319// so these forward to the inherent by-ref methods (`checked_add(&self, &Self)`)
320// rather than the by-value trait — no `[T; CAP]` copy of either operand.
321
322impl<T, const CAP: usize> CheckedAdd for &HeaplessBigInt<T, CAP, Nct>
323where
324    T: MachineWord,
325{
326    type Output = HeaplessBigInt<T, CAP, Nct>;
327    fn checked_add(self, v: Self) -> Option<Self::Output> {
328        HeaplessBigInt::<T, CAP, Nct>::checked_add(self, v)
329    }
330}
331
332impl<T, const CAP: usize> CheckedMul for &HeaplessBigInt<T, CAP, Nct>
333where
334    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
335{
336    type Output = HeaplessBigInt<T, CAP, Nct>;
337    fn checked_mul(self, v: Self) -> Option<Self::Output> {
338        HeaplessBigInt::<T, CAP, Nct>::checked_mul(self, v)
339    }
340}
341
342impl<T, const CAP: usize> CheckedSub for &HeaplessBigInt<T, CAP, Nct>
343where
344    T: MachineWord,
345{
346    type Output = HeaplessBigInt<T, CAP, Nct>;
347    fn checked_sub(self, v: Self) -> Option<Self::Output> {
348        HeaplessBigInt::<T, CAP, Nct>::checked_sub(self, v)
349    }
350}
351
352// ── num_traits Checked{Add,Sub,Mul} (Nct only) ──
353//
354// The `num_traits::PrimInt` supertraits, bridging its by-reference receiver to
355// the inherent by-reference method (same values as the const_num_traits forms
356// above; different crate and receiver shape).
357
358#[cfg(feature = "num-traits")]
359impl<T, const CAP: usize> num_traits::CheckedAdd for HeaplessBigInt<T, CAP, Nct>
360where
361    T: MachineWord,
362{
363    fn checked_add(&self, v: &Self) -> Option<Self> {
364        Self::checked_add(self, v)
365    }
366}
367
368#[cfg(feature = "num-traits")]
369impl<T, const CAP: usize> num_traits::CheckedSub for HeaplessBigInt<T, CAP, Nct>
370where
371    T: MachineWord,
372{
373    fn checked_sub(&self, v: &Self) -> Option<Self> {
374        Self::checked_sub(self, v)
375    }
376}
377
378#[cfg(feature = "num-traits")]
379impl<T, const CAP: usize> num_traits::CheckedMul for HeaplessBigInt<T, CAP, Nct>
380where
381    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
382{
383    fn checked_mul(&self, v: &Self) -> Option<Self> {
384        Self::checked_mul(self, v)
385    }
386}
387
388// ── const_num_traits Saturating{Add,Sub,Mul} (Nct only) ──
389//
390// Same gating as the Checked* trait forms above: the value-form trait
391// delegates to the by-reference inherent method (free, `HeaplessBigInt: Copy`).
392// The saturation target is the operand-width max/zero, not the CAP-wide
393// `Bounded` — see `max_at_len`.
394
395impl<T, const CAP: usize> SaturatingAdd for HeaplessBigInt<T, CAP, Nct>
396where
397    T: MachineWord,
398{
399    type Output = Self;
400    fn saturating_add(self, v: Self) -> Self {
401        Self::saturating_add(&self, &v)
402    }
403}
404
405impl<T, const CAP: usize> SaturatingSub for HeaplessBigInt<T, CAP, Nct>
406where
407    T: MachineWord,
408{
409    type Output = Self;
410    fn saturating_sub(self, v: Self) -> Self {
411        Self::saturating_sub(&self, &v)
412    }
413}
414
415impl<T, const CAP: usize> SaturatingMul for HeaplessBigInt<T, CAP, Nct>
416where
417    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
418{
419    type Output = Self;
420    fn saturating_mul(self, v: Self) -> Self {
421        Self::saturating_mul(&self, &v)
422    }
423}
424
425// ── Ct saturating: branchless select on the overflow/borrow flag ──
426//
427// The `Nct` forms above branch on the flag; the `Ct` forms pick the
428// saturated sentinel vs the wrapped result with `ct_select` (the whole-value
429// masked select), so timing doesn't reveal whether saturation happened. The
430// sentinel is the operand-width max/zero, same as the Nct path. Additive —
431// the `Nct` impls are untouched.
432
433impl<T, const CAP: usize> SaturatingAdd for HeaplessBigInt<T, CAP, Ct>
434where
435    T: MachineWord + subtle::ConditionallySelectable,
436{
437    type Output = Self;
438    fn saturating_add(self, v: Self) -> Self {
439        let (res, overflow) = OverflowingAdd::overflowing_add(self, v);
440        ct_select(&res, &max_at_len(res.len), overflow)
441    }
442}
443
444impl<T, const CAP: usize> SaturatingSub for HeaplessBigInt<T, CAP, Ct>
445where
446    T: MachineWord + subtle::ConditionallySelectable,
447{
448    type Output = Self;
449    fn saturating_sub(self, v: Self) -> Self {
450        let (res, borrow) = OverflowingSub::overflowing_sub(self, v);
451        ct_select(&res, &Self::new_zero_with_len(res.len), borrow)
452    }
453}
454
455impl<T, const CAP: usize> SaturatingMul for HeaplessBigInt<T, CAP, Ct>
456where
457    T: MachineWord + CarryingMul<Unsigned = T, Output = T> + subtle::ConditionallySelectable,
458{
459    type Output = Self;
460    fn saturating_mul(self, v: Self) -> Self {
461        let (res, overflow) = OverflowingMul::overflowing_mul(self, v);
462        ct_select(&res, &max_at_len(res.len), overflow)
463    }
464}
465
466// Reference-receiver mirrors for both personalities: deref and forward to the
467// matching value impl (`HeaplessBigInt: Copy`), so `(&h).saturating_add(&g)`
468// resolves the same way `h.saturating_add(g)` does.
469
470impl<T, const CAP: usize> SaturatingAdd for &HeaplessBigInt<T, CAP, Nct>
471where
472    T: MachineWord,
473{
474    type Output = HeaplessBigInt<T, CAP, Nct>;
475    fn saturating_add(self, v: Self) -> Self::Output {
476        <HeaplessBigInt<T, CAP, Nct> as SaturatingAdd>::saturating_add(*self, *v)
477    }
478}
479
480impl<T, const CAP: usize> SaturatingSub for &HeaplessBigInt<T, CAP, Nct>
481where
482    T: MachineWord,
483{
484    type Output = HeaplessBigInt<T, CAP, Nct>;
485    fn saturating_sub(self, v: Self) -> Self::Output {
486        <HeaplessBigInt<T, CAP, Nct> as SaturatingSub>::saturating_sub(*self, *v)
487    }
488}
489
490impl<T, const CAP: usize> SaturatingMul for &HeaplessBigInt<T, CAP, Nct>
491where
492    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
493{
494    type Output = HeaplessBigInt<T, CAP, Nct>;
495    fn saturating_mul(self, v: Self) -> Self::Output {
496        <HeaplessBigInt<T, CAP, Nct> as SaturatingMul>::saturating_mul(*self, *v)
497    }
498}
499
500impl<T, const CAP: usize> SaturatingAdd for &HeaplessBigInt<T, CAP, Ct>
501where
502    T: MachineWord + subtle::ConditionallySelectable,
503{
504    type Output = HeaplessBigInt<T, CAP, Ct>;
505    fn saturating_add(self, v: Self) -> Self::Output {
506        <HeaplessBigInt<T, CAP, Ct> as SaturatingAdd>::saturating_add(*self, *v)
507    }
508}
509
510impl<T, const CAP: usize> SaturatingSub for &HeaplessBigInt<T, CAP, Ct>
511where
512    T: MachineWord + subtle::ConditionallySelectable,
513{
514    type Output = HeaplessBigInt<T, CAP, Ct>;
515    fn saturating_sub(self, v: Self) -> Self::Output {
516        <HeaplessBigInt<T, CAP, Ct> as SaturatingSub>::saturating_sub(*self, *v)
517    }
518}
519
520impl<T, const CAP: usize> SaturatingMul for &HeaplessBigInt<T, CAP, Ct>
521where
522    T: MachineWord + CarryingMul<Unsigned = T, Output = T> + subtle::ConditionallySelectable,
523{
524    type Output = HeaplessBigInt<T, CAP, Ct>;
525    fn saturating_mul(self, v: Self) -> Self::Output {
526        <HeaplessBigInt<T, CAP, Ct> as SaturatingMul>::saturating_mul(*self, *v)
527    }
528}
529
530// ── Ct checked arithmetic: masked-return `CtOption` ──
531//
532// The overflow flag gates the `CtOption` mask instead of a branch, so a
533// secret operand's overflow isn't leaked through an `Option` discriminant.
534// No `ct_select` (or `ConditionallySelectable` bound) needed — the value is
535// always the wrapped result; only its observability is masked. P-generic,
536// like FixedUInt's.
537
538impl<T, const CAP: usize, P: Personality> const_num_traits::ops::ct::CtCheckedAdd
539    for HeaplessBigInt<T, CAP, P>
540where
541    T: MachineWord,
542{
543    fn ct_checked_add(&self, v: &Self) -> subtle::CtOption<Self> {
544        let (val, overflow) = self.overflowing_add(v);
545        subtle::CtOption::new(val, subtle::Choice::from(!overflow as u8))
546    }
547}
548
549impl<T, const CAP: usize, P: Personality> const_num_traits::ops::ct::CtCheckedSub
550    for HeaplessBigInt<T, CAP, P>
551where
552    T: MachineWord,
553{
554    fn ct_checked_sub(&self, v: &Self) -> subtle::CtOption<Self> {
555        let (val, borrow) = self.overflowing_sub(v);
556        subtle::CtOption::new(val, subtle::Choice::from(!borrow as u8))
557    }
558}
559
560impl<T, const CAP: usize, P: Personality> const_num_traits::ops::ct::CtCheckedMul
561    for HeaplessBigInt<T, CAP, P>
562where
563    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
564{
565    fn ct_checked_mul(&self, v: &Self) -> subtle::CtOption<Self> {
566        let (val, overflow) = self.overflowing_mul(v);
567        subtle::CtOption::new(val, subtle::Choice::from(!overflow as u8))
568    }
569}
570
571// `num_traits::Saturating` (add/sub only) — deprecated upstream but still
572// required by `num_traits::PrimInt`. Nct-only, matching the trait forms above.
573#[cfg(feature = "num-traits")]
574impl<T, const CAP: usize> num_traits::Saturating for HeaplessBigInt<T, CAP, Nct>
575where
576    T: MachineWord,
577{
578    fn saturating_add(self, v: Self) -> Self {
579        <Self as SaturatingAdd>::saturating_add(self, v)
580    }
581    fn saturating_sub(self, v: Self) -> Self {
582        <Self as SaturatingSub>::saturating_sub(self, v)
583    }
584}
585
586// Trim trailing-zero limbs — NCT-implicit content scan sets `len` to
587// `1 + index of highest non-zero limb` (or 0 for the mathematical zero).
588// Called only from Nct code paths; exposed as a public method on the
589// Nct-only impl block below.
590fn trim_content<T: MachineWord, const CAP: usize, P: Personality>(
591    mut v: HeaplessBigInt<T, CAP, P>,
592) -> HeaplessBigInt<T, CAP, P> {
593    // Scan the value's own words (0..len); the zero-tail invariant means
594    // limbs beyond len are already zero, so CAP need not appear.
595    let mut new_len: u16 = 0;
596    let mut i = 0;
597    while i < v.len as usize {
598        if !is_zero(&v.limbs[i]) {
599            new_len = (i + 1) as u16;
600        }
601        i += 1;
602    }
603    v.len = new_len;
604    v
605}
606
607// Nct-only public trim: normalises `len` to match the actual value.
608// Reasonable to call on any Nct-shape output whose `len` was inflated
609// by upstream shape arithmetic (chained mul, add-with-CAP-headroom).
610
611impl<T: MachineWord, const CAP: usize> HeaplessBigInt<T, CAP, Nct> {
612    /// Trim `len` down to the highest non-zero limb + 1 (0 for zero).
613    /// NCT-implicit — inspects limb content, so Nct-only.
614    #[inline]
615    pub fn trim(self) -> Self {
616        trim_content(self)
617    }
618
619    /// Saturating addition: on overflow, the all-ones value at the operands'
620    /// width `max(a.len, b.len)` (not the CAP-wide max), matching
621    /// `FixedUInt<T, width>::saturating_add`. This inherent form branches on
622    /// the overflow flag; the `Ct` carrier's branchless `SaturatingAdd` impl
623    /// (via `ct_select`) is defined separately.
624    pub fn saturating_add(&self, other: &Self) -> Self {
625        let (res, overflow) = self.overflowing_add(other);
626        if overflow { max_at_len(res.len) } else { res }
627    }
628
629    /// Saturating subtraction: clamps to zero at the operands' width on
630    /// underflow. Nct-only, same reason as `saturating_add`.
631    pub fn saturating_sub(&self, other: &Self) -> Self {
632        let (res, borrow) = self.overflowing_sub(other);
633        if borrow {
634            Self::new_zero_with_len(res.len)
635        } else {
636            res
637        }
638    }
639}
640
641impl<T: MachineWord + CarryingMul<Unsigned = T, Output = T>, const CAP: usize>
642    HeaplessBigInt<T, CAP, Nct>
643{
644    /// Saturating multiplication: on overflow, the all-ones value at the
645    /// operands' width `max(a.len, b.len)`. Nct-only, same reason as
646    /// `saturating_add`.
647    pub fn saturating_mul(&self, other: &Self) -> Self {
648        let (res, overflow) = self.overflowing_mul(other);
649        if overflow { max_at_len(res.len) } else { res }
650    }
651}
652
653// ── core::ops::{Add, Sub, Mul} — panic on overflow at the operand width ──
654//
655// Same contract as the same-width `FixedUInt`: forward to the
656// `overflowing_*` op and panic (Nct) or wrap (Ct) if it flags. Callers
657// wanting wrap or a flag use `wrapping_*` / `overflowing_*` / `checked_*`.
658
659impl<T: MachineWord, const CAP: usize, P: Personality> core::ops::Add<&HeaplessBigInt<T, CAP, P>>
660    for &HeaplessBigInt<T, CAP, P>
661{
662    type Output = HeaplessBigInt<T, CAP, P>;
663    fn add(self, other: &HeaplessBigInt<T, CAP, P>) -> Self::Output {
664        let (res, overflow) = self.overflowing_add(other);
665        panic_on_overflow_if_nct::<P>(overflow, "HeaplessBigInt::add overflow");
666        res
667    }
668}
669
670impl<T: MachineWord, const CAP: usize, P: Personality> core::ops::Sub<&HeaplessBigInt<T, CAP, P>>
671    for &HeaplessBigInt<T, CAP, P>
672{
673    type Output = HeaplessBigInt<T, CAP, P>;
674    fn sub(self, other: &HeaplessBigInt<T, CAP, P>) -> Self::Output {
675        let (res, borrow) = self.overflowing_sub(other);
676        panic_on_overflow_if_nct::<P>(borrow, "HeaplessBigInt::sub underflow");
677        res
678    }
679}
680
681impl<T: MachineWord + CarryingMul<Unsigned = T, Output = T>, const CAP: usize, P: Personality>
682    core::ops::Mul<&HeaplessBigInt<T, CAP, P>> for &HeaplessBigInt<T, CAP, P>
683{
684    type Output = HeaplessBigInt<T, CAP, P>;
685    fn mul(self, other: &HeaplessBigInt<T, CAP, P>) -> Self::Output {
686        let (res, overflow) = self.overflowing_mul(other);
687        panic_on_overflow_if_nct::<P>(overflow, "HeaplessBigInt::mul overflow");
688        res
689    }
690}
691
692// `HeaplessBigInt: Copy`, so forwarding these by-value operands to the
693// `&Self` core is a no-op at runtime.
694forward_arith_receivers!(Add, add);
695forward_arith_receivers!(Sub, sub);
696forward_arith_receivers!(Mul, mul + CarryingMul<Unsigned = T, Output = T>);
697
698// ── Compound-assign forms (delegate to the operator, same panic/wrap rule) ──
699
700impl<T: MachineWord, const CAP: usize, P: Personality> core::ops::AddAssign
701    for HeaplessBigInt<T, CAP, P>
702{
703    fn add_assign(&mut self, other: Self) {
704        self.add_assign(&other);
705    }
706}
707
708impl<T: MachineWord, const CAP: usize, P: Personality>
709    core::ops::AddAssign<&HeaplessBigInt<T, CAP, P>> for HeaplessBigInt<T, CAP, P>
710{
711    fn add_assign(&mut self, other: &Self) {
712        let out_len = core::cmp::max(self.len as usize, other.len as usize);
713        let mut out = Self::new_zero_with_len(out_len as u16);
714        let overflow = add_slice(&self.limbs, &other.limbs, &mut out.limbs, out_len);
715        panic_on_overflow_if_nct::<P>(overflow, "HeaplessBigInt::add overflow");
716        *self = out;
717    }
718}
719
720impl<T: MachineWord, const CAP: usize, P: Personality> core::ops::SubAssign
721    for HeaplessBigInt<T, CAP, P>
722{
723    fn sub_assign(&mut self, other: Self) {
724        self.sub_assign(&other);
725    }
726}
727
728impl<T: MachineWord, const CAP: usize, P: Personality>
729    core::ops::SubAssign<&HeaplessBigInt<T, CAP, P>> for HeaplessBigInt<T, CAP, P>
730{
731    fn sub_assign(&mut self, other: &Self) {
732        let out_len = core::cmp::max(self.len as usize, other.len as usize);
733        let mut out = Self::new_zero_with_len(out_len as u16);
734        let borrow = sub_slice(&self.limbs, &other.limbs, &mut out.limbs, out_len);
735        panic_on_overflow_if_nct::<P>(borrow, "HeaplessBigInt::sub underflow");
736        *self = out;
737    }
738}
739
740impl<T: MachineWord + CarryingMul<Unsigned = T, Output = T>, const CAP: usize, P: Personality>
741    core::ops::MulAssign for HeaplessBigInt<T, CAP, P>
742{
743    fn mul_assign(&mut self, other: Self) {
744        self.mul_assign(&other);
745    }
746}
747
748impl<T: MachineWord + CarryingMul<Unsigned = T, Output = T>, const CAP: usize, P: Personality>
749    core::ops::MulAssign<&HeaplessBigInt<T, CAP, P>> for HeaplessBigInt<T, CAP, P>
750{
751    fn mul_assign(&mut self, other: &Self) {
752        // Mirrors `overflowing_mul`: `carrying_mul` takes `Self` by value, so
753        // this one copy is inherent to the primitive (same as the operator).
754        let zero_v = <Self as const_num_traits::Zero>::zero();
755        let (lo, hi) = <Self as CarryingMul>::carrying_mul(*self, *other, zero_v);
756        let overflow = !<Self as const_num_traits::Zero>::is_zero(&hi);
757        panic_on_overflow_if_nct::<P>(overflow, "HeaplessBigInt::mul overflow");
758        *self = lo;
759    }
760}
761
762// ── const_num_traits Wrapping / Overflowing Add & Sub ──
763//
764// Delegate to the inherent methods; the traits take `self` by value,
765// the inherent methods take references. `HeaplessBigInt: Copy`, so
766// converting between the two is a no-op at runtime.
767
768impl<T: MachineWord, const CAP: usize, P: Personality> WrappingAdd for HeaplessBigInt<T, CAP, P> {
769    type Output = Self;
770    fn wrapping_add(self, v: Self) -> Self::Output {
771        Self::wrapping_add(&self, &v)
772    }
773}
774
775impl<T: MachineWord, const CAP: usize, P: Personality> WrappingSub for HeaplessBigInt<T, CAP, P> {
776    type Output = Self;
777    fn wrapping_sub(self, v: Self) -> Self::Output {
778        Self::wrapping_sub(&self, &v)
779    }
780}
781
782impl<T: MachineWord, const CAP: usize, P: Personality> OverflowingAdd
783    for HeaplessBigInt<T, CAP, P>
784{
785    type Output = Self;
786    fn overflowing_add(self, v: Self) -> (Self::Output, bool) {
787        Self::overflowing_add(&self, &v)
788    }
789}
790
791impl<T: MachineWord, const CAP: usize, P: Personality> OverflowingSub
792    for HeaplessBigInt<T, CAP, P>
793{
794    type Output = Self;
795    fn overflowing_sub(self, v: Self) -> (Self::Output, bool) {
796        Self::overflowing_sub(&self, &v)
797    }
798}
799
800// Reference-receiver variants mirror `FixedUInt`'s pattern (`add_sub_impl.rs`),
801// letting `&HeaplessBigInt` satisfy the same generic trait bound.
802
803impl<T: MachineWord, const CAP: usize, P: Personality> WrappingAdd for &HeaplessBigInt<T, CAP, P> {
804    type Output = HeaplessBigInt<T, CAP, P>;
805    fn wrapping_add(self, v: Self) -> Self::Output {
806        HeaplessBigInt::wrapping_add(self, v)
807    }
808}
809
810impl<T: MachineWord, const CAP: usize, P: Personality> WrappingSub for &HeaplessBigInt<T, CAP, P> {
811    type Output = HeaplessBigInt<T, CAP, P>;
812    fn wrapping_sub(self, v: Self) -> Self::Output {
813        HeaplessBigInt::wrapping_sub(self, v)
814    }
815}
816
817impl<T: MachineWord, const CAP: usize, P: Personality> OverflowingAdd
818    for &HeaplessBigInt<T, CAP, P>
819{
820    type Output = HeaplessBigInt<T, CAP, P>;
821    fn overflowing_add(self, v: Self) -> (Self::Output, bool) {
822        HeaplessBigInt::overflowing_add(self, v)
823    }
824}
825
826impl<T: MachineWord, const CAP: usize, P: Personality> OverflowingSub
827    for &HeaplessBigInt<T, CAP, P>
828{
829    type Output = HeaplessBigInt<T, CAP, P>;
830    fn overflowing_sub(self, v: Self) -> (Self::Output, bool) {
831        HeaplessBigInt::overflowing_sub(self, v)
832    }
833}
834
835// WrappingMul — explicit wrap-at-width multiply, for callers that want
836// the low half rather than `core::ops::Mul`'s panic-on-overflow.
837
838impl<T, const CAP: usize, P: Personality> WrappingMul for HeaplessBigInt<T, CAP, P>
839where
840    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
841{
842    type Output = Self;
843    fn wrapping_mul(self, v: Self) -> Self::Output {
844        Self::wrapping_mul(&self, &v)
845    }
846}
847
848impl<T, const CAP: usize, P: Personality> WrappingMul for &HeaplessBigInt<T, CAP, P>
849where
850    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
851{
852    type Output = HeaplessBigInt<T, CAP, P>;
853    fn wrapping_mul(self, v: Self) -> Self::Output {
854        HeaplessBigInt::wrapping_mul(self, v)
855    }
856}
857
858// OverflowingMul — value-width overflow flag, matching FixedUInt.
859
860impl<T, const CAP: usize, P: Personality> OverflowingMul for HeaplessBigInt<T, CAP, P>
861where
862    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
863{
864    type Output = Self;
865    fn overflowing_mul(self, v: Self) -> (Self::Output, bool) {
866        Self::overflowing_mul(&self, &v)
867    }
868}
869
870impl<T, const CAP: usize, P: Personality> OverflowingMul for &HeaplessBigInt<T, CAP, P>
871where
872    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
873{
874    type Output = HeaplessBigInt<T, CAP, P>;
875    fn overflowing_mul(self, v: Self) -> (Self::Output, bool) {
876        HeaplessBigInt::overflowing_mul(self, v)
877    }
878}
879
880// ── CarryingAdd at the bigint level ──
881//
882// `self + rhs + carry_in` with carry_out, over the operands' width
883// (`max(self.len, rhs.len)`), not `CAP` — the symmetric partner of
884// `borrowing_sub` below, same width rule as every other op. Same
885// value-width result as `overflowing_add`; the difference is the
886// `carry_in` input, which lets a multi-precision routine (wide-REDC)
887// chain carries across limbs the way `borrowing_sub` chains borrows.
888
889impl<T, const CAP: usize, P: Personality> CarryingAdd for HeaplessBigInt<T, CAP, P>
890where
891    T: MachineWord,
892{
893    type Output = Self;
894    fn carrying_add(self, rhs: Self, carry_in: bool) -> (Self::Output, bool) {
895        let out_len = core::cmp::max(self.len as usize, rhs.len as usize);
896        let mut out_limbs = [zero::<T>(); CAP];
897        let mut carry = carry_in;
898        let mut i = 0;
899        while i < out_len {
900            let (sum, c) = <T as CarryingAdd>::carrying_add(self.limbs[i], rhs.limbs[i], carry);
901            out_limbs[i] = sum;
902            carry = c;
903            i += 1;
904        }
905        (
906            HeaplessBigInt {
907                limbs: out_limbs,
908                len: out_len as u16,
909                _p: PhantomData,
910            },
911            carry,
912        )
913    }
914}
915
916// Reference-receiver mirror (deref and forward, `HeaplessBigInt: Copy`).
917
918impl<T, const CAP: usize, P: Personality> CarryingAdd for &HeaplessBigInt<T, CAP, P>
919where
920    T: MachineWord,
921{
922    type Output = HeaplessBigInt<T, CAP, P>;
923    fn carrying_add(self, rhs: Self, carry_in: bool) -> (Self::Output, bool) {
924        <HeaplessBigInt<T, CAP, P> as CarryingAdd>::carrying_add(*self, *rhs, carry_in)
925    }
926}
927
928// `self - rhs - borrow_in` with borrow_out, over the operands' width
929// (`max(self.len, rhs.len)`) — same width rule as `wrapping_sub`, so
930// underflow wraps at the value's width. Used by multi-precision reduction.
931
932impl<T, const CAP: usize, P: Personality> BorrowingSub for HeaplessBigInt<T, CAP, P>
933where
934    T: MachineWord,
935{
936    type Output = Self;
937    fn borrowing_sub(self, rhs: Self, borrow_in: bool) -> (Self::Output, bool) {
938        let out_len = core::cmp::max(self.len as usize, rhs.len as usize);
939        let mut out_limbs = [zero::<T>(); CAP];
940        let mut borrow = borrow_in;
941        let mut i = 0;
942        while i < out_len {
943            let (diff, br) =
944                <T as BorrowingSub>::borrowing_sub(self.limbs[i], rhs.limbs[i], borrow);
945            out_limbs[i] = diff;
946            borrow = br;
947            i += 1;
948        }
949        (
950            HeaplessBigInt {
951                limbs: out_limbs,
952                len: out_len as u16,
953                _p: PhantomData,
954            },
955            borrow,
956        )
957    }
958}
959
960// Reference-receiver mirror (deref and forward, `HeaplessBigInt: Copy`).
961
962impl<T, const CAP: usize, P: Personality> BorrowingSub for &HeaplessBigInt<T, CAP, P>
963where
964    T: MachineWord,
965{
966    type Output = HeaplessBigInt<T, CAP, P>;
967    fn borrowing_sub(self, rhs: Self, borrow_in: bool) -> (Self::Output, bool) {
968        <HeaplessBigInt<T, CAP, P> as BorrowingSub>::borrowing_sub(*self, *rhs, borrow_in)
969    }
970}
971
972// ── CarryingMul at the bigint level ──
973//
974// `(lo, hi) = self * rhs + carry (+ add)`, split at the operands' VALUE
975// width `W = max(len)` words: `lo` = low W words, `hi` = high W words,
976// reconstructing as `full = hi·2^(W·word_bits) + lo`. This matches
977// `bits_precision()` (= `len·word_bits`) and the primitive contract
978// (`200u8.wide_mul(200) = (64, 156)` splits at the type width) — and it
979// is what a wide Montgomery reduction reads back, since it reconstructs
980// against the operand's `bits_precision`, not the carrier's capacity.
981//
982// NOT `CAP`: for a sub-capacity field (`len < CAP` — e.g. a modulus
983// narrower than the carrier) a CAP split would strand the high half in
984// `lo` (`hi = 0`) and the REDC would be off by limbs. `CAP` is invisible
985// here just like every other value-width op; the only fixed-width use of
986// capacity is `ToBytes`'s owned holder. (For a full-width field
987// `len == CAP`, so the two coincide.)
988
989impl<T, const CAP: usize, P: Personality> CarryingMul for HeaplessBigInt<T, CAP, P>
990where
991    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
992{
993    type Unsigned = Self;
994    type Output = Self;
995
996    fn carrying_mul(self, rhs: Self, carry: Self) -> (Self::Unsigned, Self::Output) {
997        let zero_v = <Self as const_num_traits::Zero>::zero();
998        self.carrying_mul_add(rhs, carry, zero_v)
999    }
1000
1001    fn carrying_mul_add(self, rhs: Self, carry: Self, add: Self) -> (Self::Unsigned, Self::Output) {
1002        // Split point W = the operands' value width. `carry`/`add` are
1003        // added into the low half, so W must also cover them.
1004        let w = core::cmp::max(
1005            core::cmp::max(self.len as usize, rhs.len as usize),
1006            core::cmp::max(carry.len as usize, add.len as usize),
1007        );
1008        let mut lo_limbs = [zero::<T>(); CAP];
1009        let mut hi_limbs = [zero::<T>(); CAP];
1010
1011        // Schoolbook self * rhs into positions [0, 2W): pos < W → lo,
1012        // else hi[pos - W]. Iterate the operands' word counts (public
1013        // shape), not the array capacity.
1014        let a_n = self.len as usize;
1015        let b_n = rhs.len as usize;
1016        let mut i = 0;
1017        while i < a_n {
1018            let mut c = zero::<T>();
1019            let mut j = 0;
1020            while j < b_n {
1021                let pos = i + j;
1022                let (t_lo, t_hi) = <T as CarryingMul>::carrying_mul(self.limbs[i], rhs.limbs[j], c);
1023                let existing = if pos < w {
1024                    lo_limbs[pos]
1025                } else {
1026                    hi_limbs[pos - w]
1027                };
1028                let (sum, c1) = <T as CarryingAdd>::carrying_add(existing, t_lo, false);
1029                if pos < w {
1030                    lo_limbs[pos] = sum;
1031                } else {
1032                    hi_limbs[pos - w] = sum;
1033                }
1034                let (new_c, _) = <T as CarryingAdd>::carrying_add(t_hi, zero::<T>(), c1);
1035                c = new_c;
1036                j += 1;
1037            }
1038            // Row-final carry at column i + b_n.
1039            let tail = i + b_n;
1040            if tail < w {
1041                let (sum, _) = <T as CarryingAdd>::carrying_add(lo_limbs[tail], c, false);
1042                lo_limbs[tail] = sum;
1043            } else {
1044                let (sum, _) = <T as CarryingAdd>::carrying_add(hi_limbs[tail - w], c, false);
1045                hi_limbs[tail - w] = sum;
1046            }
1047            i += 1;
1048        }
1049
1050        // Fold carry, then add, into the low half [0, W); overflow into hi.
1051        for src in [&carry, &add] {
1052            let mut cin = false;
1053            let mut i = 0;
1054            while i < w {
1055                let (sum, c) = <T as CarryingAdd>::carrying_add(lo_limbs[i], src.limbs[i], cin);
1056                lo_limbs[i] = sum;
1057                cin = c;
1058                i += 1;
1059            }
1060            // Propagate the fold carry into the hi half. `Nct` may stop the
1061            // moment the carry dies; `Ct` must sweep the full width so timing
1062            // is independent of where — or whether — the carry chain
1063            // terminates (a secret-dependent bound here leaks the operands,
1064            // which the taint gate catches). A `false` carry-in makes each
1065            // step a constant-time no-op.
1066            let mut i = 0;
1067            match P::TAG {
1068                PersonalityTag::Nct => {
1069                    while cin && i < w {
1070                        let (sum, c) =
1071                            <T as CarryingAdd>::carrying_add(hi_limbs[i], zero::<T>(), true);
1072                        hi_limbs[i] = sum;
1073                        cin = c;
1074                        i += 1;
1075                    }
1076                }
1077                PersonalityTag::Ct => {
1078                    while i < w {
1079                        let (sum, c) =
1080                            <T as CarryingAdd>::carrying_add(hi_limbs[i], zero::<T>(), cin);
1081                        hi_limbs[i] = sum;
1082                        cin = c;
1083                        i += 1;
1084                    }
1085                }
1086            }
1087        }
1088
1089        let lo = HeaplessBigInt {
1090            limbs: lo_limbs,
1091            len: w as u16,
1092            _p: PhantomData,
1093        };
1094        let hi = HeaplessBigInt {
1095            limbs: hi_limbs,
1096            len: w as u16,
1097            _p: PhantomData,
1098        };
1099        (lo, hi)
1100    }
1101}
1102
1103// Reference-receiver mirror: both widening-mul methods deref and forward to
1104// the value impl (`HeaplessBigInt: Copy`). Mirrors `FixedUInt`'s `&Self`
1105// `CarryingMul` in `extended_precision_impl.rs`.
1106
1107impl<T, const CAP: usize, P: Personality> CarryingMul for &HeaplessBigInt<T, CAP, P>
1108where
1109    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
1110{
1111    type Unsigned = HeaplessBigInt<T, CAP, P>;
1112    type Output = HeaplessBigInt<T, CAP, P>;
1113
1114    fn carrying_mul(self, rhs: Self, carry: Self) -> (Self::Unsigned, Self::Output) {
1115        <HeaplessBigInt<T, CAP, P> as CarryingMul>::carrying_mul(*self, *rhs, *carry)
1116    }
1117
1118    fn carrying_mul_add(self, rhs: Self, carry: Self, add: Self) -> (Self::Unsigned, Self::Output) {
1119        <HeaplessBigInt<T, CAP, P> as CarryingMul>::carrying_mul_add(*self, *rhs, *carry, *add)
1120    }
1121}
1122
1123#[inline]
1124pub(crate) fn zero_tail_ok<T: MachineWord>(limbs: &[T], used: usize) -> bool {
1125    let mut i = used;
1126    while i < limbs.len() {
1127        if !is_zero(&limbs[i]) {
1128            return false;
1129        }
1130        i += 1;
1131    }
1132    true
1133}
1134
1135#[cfg(test)]
1136mod tests {
1137    use super::*;
1138    use const_num_traits::Zero;
1139
1140    type H = HeaplessBigInt<u32, 8, Nct>; // 256-bit CAP
1141
1142    #[test]
1143    fn saturation_is_operand_width_not_cap() {
1144        // Two len-1 (32-bit) values in a CAP-8 carrier: overflow saturates to
1145        // the 32-bit operand-width max, NOT the 256-bit CAP max — matching
1146        // FixedUInt<u32, 1>. This is what the fixed-width carrier harness can't
1147        // reach (its backings have width == CAP).
1148        let a = H::from_le_bytes(&0xFFFF_FFFFu32.to_le_bytes()); // len 1
1149        let one = H::from_le_bytes(&1u32.to_le_bytes());
1150        let s = SaturatingAdd::saturating_add(a, one);
1151        assert_eq!(s.len, 1);
1152        assert_eq!(s.limbs[0], 0xFFFF_FFFF);
1153
1154        // 2^16 * 2^16 = 2^32 overflows the 32-bit width; saturates the same way.
1155        let big = H::from_le_bytes(&0x1_0000u32.to_le_bytes());
1156        let m = SaturatingMul::saturating_mul(big, big);
1157        assert_eq!(m.len, 1);
1158        assert_eq!(m.limbs[0], 0xFFFF_FFFF);
1159    }
1160
1161    #[test]
1162    fn saturating_sub_clamps_to_zero_at_width() {
1163        let one = H::from_le_bytes(&1u32.to_le_bytes()); // len 1
1164        let two = H::from_le_bytes(&2u32.to_le_bytes());
1165        let s = SaturatingSub::saturating_sub(one, two);
1166        assert_eq!(s.len, 1);
1167        assert!(<H as Zero>::is_zero(&s));
1168    }
1169
1170    #[test]
1171    fn checked_div_rem_by_zero_is_none() {
1172        let a = H::from_le_bytes(&100u32.to_le_bytes());
1173        let z = <H as Zero>::zero();
1174        assert_eq!(a.checked_div(&z), None);
1175        assert_eq!(a.checked_rem(&z), None);
1176        let seven = H::from_le_bytes(&7u32.to_le_bytes());
1177        assert_eq!(a.checked_div(&seven).unwrap().limbs[0], 14);
1178        assert_eq!(a.checked_rem(&seven).unwrap().limbs[0], 2);
1179    }
1180
1181    // CtCheckedAdd/Sub/Mul: the value is always the wrapped result; is_some
1182    // masks overflow. Matches the plain checked_* value + overflow status.
1183    #[test]
1184    fn ct_checked_arithmetic() {
1185        use const_num_traits::ops::ct::{CtCheckedAdd, CtCheckedMul, CtCheckedSub};
1186        type Cc = HeaplessBigInt<u8, 4, Ct>;
1187
1188        // No overflow: is_some, value matches.
1189        let a = Cc::from(100u32);
1190        let b = Cc::from(50u32);
1191        let s = a.ct_checked_add(&b);
1192        assert!(bool::from(s.is_some()));
1193        assert_eq!(s.unwrap(), Cc::from(150u32));
1194        assert!(bool::from(a.ct_checked_sub(&b).is_some()));
1195        assert!(bool::from(
1196            Cc::from(7u32).ct_checked_mul(&Cc::from(9u32)).is_some()
1197        ));
1198
1199        // Overflow / underflow: is_none (masked).
1200        assert!(!bool::from(
1201            Cc::from(u32::MAX).ct_checked_add(&Cc::from(1u32)).is_some()
1202        ));
1203        assert!(!bool::from(
1204            Cc::from(0u32).ct_checked_sub(&Cc::from(1u32)).is_some()
1205        ));
1206        assert!(!bool::from(
1207            Cc::from(0x1_0000u32)
1208                .ct_checked_mul(&Cc::from(0x1_0000u32))
1209                .is_some()
1210        ));
1211    }
1212
1213    // The branchless Ct saturating impls must produce the same values as the
1214    // Nct ones (including the saturate/clamp cases), at the operand width.
1215    #[test]
1216    fn ct_saturating_matches_nct() {
1217        type Cn = HeaplessBigInt<u8, 4, Nct>;
1218        type Cc = HeaplessBigInt<u8, 4, Ct>;
1219        let cases = [(100u32, 50u32), (u32::MAX, 1), (u32::MAX, u32::MAX), (5, 9)];
1220        for (a, b) in cases {
1221            assert_eq!(
1222                SaturatingAdd::saturating_add(Cc::from(a), Cc::from(b)),
1223                Cc::from(a.saturating_add(b)),
1224                "ct saturating_add({a},{b})"
1225            );
1226            assert_eq!(
1227                SaturatingSub::saturating_sub(Cc::from(a), Cc::from(b)),
1228                Cc::from(a.saturating_sub(b))
1229            );
1230            assert_eq!(
1231                SaturatingMul::saturating_mul(Cc::from(a), Cc::from(b)),
1232                Cc::from(a.saturating_mul(b))
1233            );
1234            // Cross-check the Nct forms agree with std too.
1235            assert_eq!(
1236                SaturatingAdd::saturating_add(Cn::from(a), Cn::from(b)),
1237                Cn::from(a.saturating_add(b))
1238            );
1239        }
1240    }
1241
1242    // Reference-receiver trait forms resolve to the same value as the by-value
1243    // forms — one representative per family (Checked / Saturating / Carrying /
1244    // Borrowing), covering both the Nct and Ct saturating personalities.
1245    #[test]
1246    fn by_ref_matches_value() {
1247        let a = H::from(100u32);
1248        let b = H::from(7u32);
1249        assert_eq!(
1250            CheckedAdd::checked_add(&a, &b),
1251            CheckedAdd::checked_add(a, b)
1252        );
1253        assert_eq!(
1254            CheckedSub::checked_sub(&a, &b),
1255            CheckedSub::checked_sub(a, b)
1256        );
1257        assert_eq!(
1258            CheckedMul::checked_mul(&a, &b),
1259            CheckedMul::checked_mul(a, b)
1260        );
1261        assert_eq!(
1262            SaturatingAdd::saturating_add(&a, &b),
1263            SaturatingAdd::saturating_add(a, b)
1264        );
1265        assert_eq!(
1266            SaturatingSub::saturating_sub(&a, &b),
1267            SaturatingSub::saturating_sub(a, b)
1268        );
1269        assert_eq!(
1270            SaturatingMul::saturating_mul(&a, &b),
1271            SaturatingMul::saturating_mul(a, b)
1272        );
1273        assert_eq!(
1274            CarryingAdd::carrying_add(&a, &b, true),
1275            CarryingAdd::carrying_add(a, b, true)
1276        );
1277        assert_eq!(
1278            BorrowingSub::borrowing_sub(&a, &b, true),
1279            BorrowingSub::borrowing_sub(a, b, true)
1280        );
1281        let z = <H as Zero>::zero();
1282        assert_eq!(
1283            CarryingMul::carrying_mul(&a, &b, &z),
1284            CarryingMul::carrying_mul(a, b, z)
1285        );
1286        assert_eq!(
1287            CarryingMul::carrying_mul_add(&a, &b, &z, &b),
1288            CarryingMul::carrying_mul_add(a, b, z, b)
1289        );
1290
1291        // Ct saturating &Self mirror resolves to the value form too.
1292        type Cc = HeaplessBigInt<u8, 4, Ct>;
1293        let ca = Cc::from(u32::MAX);
1294        let cb = Cc::from(1u32);
1295        assert_eq!(
1296            SaturatingAdd::saturating_add(&ca, &cb),
1297            SaturatingAdd::saturating_add(ca, cb)
1298        );
1299    }
1300}