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::{HeaplessBigInt, is_zero, zero};
16use crate::MachineWord;
17use const_num_traits::{
18    BorrowingSub, CarryingAdd, CarryingMul, CheckedAdd, CheckedMul, Nct, OverflowingAdd,
19    OverflowingMul, OverflowingSub, Personality, PersonalityTag, WrappingAdd, WrappingMul,
20    WrappingSub,
21};
22use core::marker::PhantomData;
23
24// The checked `+`/`-`/`*` operators panic on overflow at the operands'
25// width. That panic branches on a data-dependent overflow bit, so it
26// runs for `Nct` only; the `Ct` arm wraps silently (like `wrapping_*`),
27// keeping control flow value-independent. Mirrors `FixedUInt`'s
28// `maybe_panic_if::<P>`.
29#[inline]
30fn panic_on_overflow_if_nct<P: Personality>(overflow: bool, msg: &'static str) {
31    match P::TAG {
32        PersonalityTag::Nct => assert!(!overflow, "{}", msg),
33        PersonalityTag::Ct => {}
34    }
35}
36
37// ── Free-function slice kernels ──
38//
39// Take `&[T]` / `&mut [T]` so a future refactor can share them with
40// `fixed-bigint`'s existing fixed-width algorithms.
41
42/// `out[..n] = a[..n] + b[..n]`, returning the final carry-out.
43/// All three slices must have length ≥ `n`. `a` / `b` beyond their
44/// respective logical `len`s must be zero (zero-tail invariant).
45///
46/// Slicing to `..n` up front bounds-checks once; the zip loop then has no
47/// per-element indexing, so the body is panic-free.
48#[inline]
49pub(crate) fn add_slice<T: MachineWord>(a: &[T], b: &[T], out: &mut [T], n: usize) -> bool {
50    let mut carry = false;
51    for ((&ai, &bi), oi) in a[..n].iter().zip(&b[..n]).zip(&mut out[..n]) {
52        let (sum, c) = <T as CarryingAdd>::carrying_add(ai, bi, carry);
53        *oi = sum;
54        carry = c;
55    }
56    carry
57}
58
59/// `out[..n] = a[..n] - b[..n]`, returning the final borrow-out.
60/// Same length / zero-tail preconditions as [`add_slice`].
61#[inline]
62pub(crate) fn sub_slice<T: MachineWord>(a: &[T], b: &[T], out: &mut [T], n: usize) -> bool {
63    let mut borrow = false;
64    for ((&ai, &bi), oi) in a[..n].iter().zip(&b[..n]).zip(&mut out[..n]) {
65        let (diff, br) = <T as BorrowingSub>::borrowing_sub(ai, bi, borrow);
66        *oi = diff;
67        borrow = br;
68    }
69    borrow
70}
71
72/// Schoolbook `out[..out_n] += a[..a_n] * b[..b_n]`. Assumes `out` is
73/// zero-initialised on entry. A partial product past `out_n` is silently
74/// truncated; `wrapping_mul` passes `out_n = max(a_n, b_n)` to keep the
75/// low `width` words (the high half is dropped, as at a fixed width).
76///
77/// `T: CarryingMul<Unsigned = T, Output = T>` is stated explicitly because
78/// `MachineWord`'s supertrait chain does not include `CarryingMul` — the
79/// FixedUInt path routes multiplication through the `ConstDoubleWord`
80/// associated type rather than the `CarryingMul` primitive.
81#[inline]
82pub(crate) fn mul_slice<T: MachineWord + CarryingMul<Unsigned = T, Output = T>>(
83    a: &[T],
84    a_n: usize,
85    b: &[T],
86    b_n: usize,
87    out: &mut [T],
88    out_n: usize,
89) {
90    // Slice to the logical lengths up front: the `a[i]` / `b[j]` reads and
91    // the `out[pos]` writes are then provably in bounds, so LLVM drops the
92    // per-access bounds checks from the hot nested loop.
93    let a = &a[..a_n];
94    let b = &b[..b_n];
95    let out = &mut out[..out_n];
96    let mut i = 0;
97    while i < a_n {
98        let mut carry = zero::<T>();
99        let mut j = 0;
100        while j < b_n {
101            let pos = i + j;
102            if pos < out_n {
103                let (lo, hi) = <T as CarryingMul>::carrying_mul(a[i], b[j], carry);
104                let (sum, c1) = <T as CarryingAdd>::carrying_add(out[pos], lo, false);
105                out[pos] = sum;
106                let (new_carry, _) = <T as CarryingAdd>::carrying_add(hi, zero::<T>(), c1);
107                carry = new_carry;
108            }
109            j += 1;
110        }
111        let tail = i + b_n;
112        if tail < out_n {
113            let (sum, _) = <T as CarryingAdd>::carrying_add(out[tail], carry, false);
114            out[tail] = sum;
115        }
116        i += 1;
117    }
118}
119
120// ── Inherent methods on HeaplessBigInt ──
121
122impl<T: MachineWord, const CAP: usize, P: Personality> HeaplessBigInt<T, CAP, P> {
123    /// Wrapping addition at the operands' width `max(a.len, b.len)`; a
124    /// carry out of that width is discarded.
125    pub fn wrapping_add(&self, other: &Self) -> Self {
126        let out_len = core::cmp::max(self.len as usize, other.len as usize);
127        let mut out = Self::new_zero_with_len(out_len as u16);
128        let _carry = add_slice(&self.limbs, &other.limbs, &mut out.limbs, out_len);
129        debug_assert!(zero_tail_ok(&out.limbs, out_len));
130        out
131    }
132
133    /// Overflowing addition, at the operands' width `max(a.len, b.len)`.
134    /// Returns `(sum mod 2^(width·word_bits), carry_out)` — the carry is
135    /// the bit beyond the width, reported as a flag, exactly as
136    /// `FixedUInt<T, width>::overflowing_add` does. Symmetric to
137    /// [`overflowing_sub`](Self::overflowing_sub). Does not grow a limb.
138    pub fn overflowing_add(&self, other: &Self) -> (Self, bool) {
139        let out_len = core::cmp::max(self.len as usize, other.len as usize);
140        let mut out = Self::new_zero_with_len(out_len as u16);
141        let carry = add_slice(&self.limbs, &other.limbs, &mut out.limbs, out_len);
142        (out, carry)
143    }
144
145    /// Checked addition. `None` on overflow at the operands' width.
146    pub fn checked_add(&self, other: &Self) -> Option<Self> {
147        let (res, overflow) = self.overflowing_add(other);
148        if overflow { None } else { Some(res) }
149    }
150
151    /// Wrapping subtraction at the operands' width `max(a.len, b.len)`;
152    /// underflow wraps modulo `2^(max_len·WORD_BITS)`, so a value carried
153    /// at a narrower width wraps at that narrower width (like `u8` vs `u32`).
154    pub fn wrapping_sub(&self, other: &Self) -> Self {
155        let out_len = core::cmp::max(self.len as usize, other.len as usize);
156        let mut out = Self::new_zero_with_len(out_len as u16);
157        let _borrow = sub_slice(&self.limbs, &other.limbs, &mut out.limbs, out_len);
158        debug_assert!(zero_tail_ok(&out.limbs, out_len));
159        out
160    }
161
162    /// Overflowing subtraction. Returns `(wrapped_result, borrow_out)`.
163    /// Same width choice as [`wrapping_sub`](Self::wrapping_sub);
164    /// `borrow_out` is the underflow flag (`self < other`).
165    pub fn overflowing_sub(&self, other: &Self) -> (Self, bool) {
166        let out_len = core::cmp::max(self.len as usize, other.len as usize);
167        let mut out = Self::new_zero_with_len(out_len as u16);
168        let borrow = sub_slice(&self.limbs, &other.limbs, &mut out.limbs, out_len);
169        (out, borrow)
170    }
171
172    /// Checked subtraction. `None` on underflow.
173    pub fn checked_sub(&self, other: &Self) -> Option<Self> {
174        let (res, borrow) = self.overflowing_sub(other);
175        if borrow { None } else { Some(res) }
176    }
177}
178
179// Mul lives in its own impl block because `CarryingMul` is not part of
180// `MachineWord`'s supertrait chain — see `mul_slice`'s note.
181
182impl<T: MachineWord + CarryingMul<Unsigned = T, Output = T>, const CAP: usize, P: Personality>
183    HeaplessBigInt<T, CAP, P>
184{
185    /// Wrapping multiplication at the operands' width `max(a.len, b.len)`:
186    /// keeps the low `width` words (`a·b mod 2^(width·word_bits)`),
187    /// exactly like `FixedUInt<T, width>::wrapping_mul`. [`WideMul`] returns
188    /// both halves.
189    pub fn wrapping_mul(&self, other: &Self) -> Self {
190        let out_len = core::cmp::max(self.len as usize, other.len as usize);
191        let mut out = Self::new_zero_with_len(out_len as u16);
192        mul_slice(
193            &self.limbs,
194            self.len as usize,
195            &other.limbs,
196            other.len as usize,
197            &mut out.limbs,
198            out_len,
199        );
200        debug_assert!(zero_tail_ok(&out.limbs, out_len));
201        out
202    }
203
204    /// Overflowing multiplication, at the operands' width
205    /// `w = max(a.len, b.len)`. Returns `(a·b mod 2^(w·word_bits),
206    /// overflow)`, where `overflow` is set iff the product does not fit
207    /// in `w` words — bit-identical to `FixedUInt<T, w>::overflowing_mul`.
208    /// The split is at the value width (via the widening [`CarryingMul`]), so
209    /// the high half is the part beyond `w`; `CAP` is irrelevant.
210    pub fn overflowing_mul(&self, other: &Self) -> (Self, bool) {
211        let zero_v = <Self as const_num_traits::Zero>::zero();
212        let (lo, hi) = <Self as CarryingMul>::carrying_mul(*self, *other, zero_v);
213        (lo, !<Self as const_num_traits::Zero>::is_zero(&hi))
214    }
215
216    /// Checked multiplication. `None` when the product does not fit in the
217    /// operands' width `max(a.len, b.len)` — exactly when
218    /// `FixedUInt<T, width>::checked_mul` would return `None`.
219    pub fn checked_mul(&self, other: &Self) -> Option<Self> {
220        let (res, overflow) = self.overflowing_mul(other);
221        if overflow { None } else { Some(res) }
222    }
223}
224
225// ── const_num_traits CheckedAdd / CheckedMul (Nct only) ──
226//
227// The trait forms a downstream variable-time modular-inverse consumer binds
228// on. `checked_add` / `checked_mul` return `None` on overflow at the
229// operands' width, exactly as the same-width `FixedUInt` would. Trait
230// exposure is kept Nct-only to match the existing surface.
231// `HeaplessBigInt: Copy`, so bridging the by-value trait receiver to the
232// by-reference inherent method is free.
233
234impl<T, const CAP: usize> CheckedAdd for HeaplessBigInt<T, CAP, Nct>
235where
236    T: MachineWord,
237{
238    type Output = Self;
239    fn checked_add(self, v: Self) -> Option<Self> {
240        Self::checked_add(&self, &v)
241    }
242}
243
244impl<T, const CAP: usize> CheckedMul for HeaplessBigInt<T, CAP, Nct>
245where
246    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
247{
248    type Output = Self;
249    fn checked_mul(self, v: Self) -> Option<Self> {
250        Self::checked_mul(&self, &v)
251    }
252}
253
254// Trim trailing-zero limbs — NCT-implicit content scan sets `len` to
255// `1 + index of highest non-zero limb` (or 0 for the mathematical zero).
256// Called only from Nct code paths; exposed as a public method on the
257// Nct-only impl block below.
258fn trim_content<T: MachineWord, const CAP: usize, P: Personality>(
259    mut v: HeaplessBigInt<T, CAP, P>,
260) -> HeaplessBigInt<T, CAP, P> {
261    // Scan the value's own words (0..len); the zero-tail invariant means
262    // limbs beyond len are already zero, so CAP need not appear.
263    let mut new_len: u16 = 0;
264    let mut i = 0;
265    while i < v.len as usize {
266        if !is_zero(&v.limbs[i]) {
267            new_len = (i + 1) as u16;
268        }
269        i += 1;
270    }
271    v.len = new_len;
272    v
273}
274
275// Nct-only public trim: normalises `len` to match the actual value.
276// Reasonable to call on any Nct-shape output whose `len` was inflated
277// by upstream shape arithmetic (chained mul, add-with-CAP-headroom).
278
279impl<T: MachineWord, const CAP: usize> HeaplessBigInt<T, CAP, Nct> {
280    /// Trim `len` down to the highest non-zero limb + 1 (0 for zero).
281    /// NCT-implicit — inspects limb content, so Nct-only.
282    #[inline]
283    pub fn trim(self) -> Self {
284        trim_content(self)
285    }
286}
287
288// ── core::ops::{Add, Sub, Mul} — panic on overflow at the operand width ──
289//
290// Same contract as the same-width `FixedUInt`: forward to the
291// `overflowing_*` op and panic (Nct) or wrap (Ct) if it flags. Callers
292// wanting wrap or a flag use `wrapping_*` / `overflowing_*` / `checked_*`.
293
294impl<T: MachineWord, const CAP: usize, P: Personality> core::ops::Add<&HeaplessBigInt<T, CAP, P>>
295    for &HeaplessBigInt<T, CAP, P>
296{
297    type Output = HeaplessBigInt<T, CAP, P>;
298    fn add(self, other: &HeaplessBigInt<T, CAP, P>) -> Self::Output {
299        let (res, overflow) = self.overflowing_add(other);
300        panic_on_overflow_if_nct::<P>(overflow, "HeaplessBigInt::add overflow");
301        res
302    }
303}
304
305impl<T: MachineWord, const CAP: usize, P: Personality> core::ops::Sub<&HeaplessBigInt<T, CAP, P>>
306    for &HeaplessBigInt<T, CAP, P>
307{
308    type Output = HeaplessBigInt<T, CAP, P>;
309    fn sub(self, other: &HeaplessBigInt<T, CAP, P>) -> Self::Output {
310        let (res, borrow) = self.overflowing_sub(other);
311        panic_on_overflow_if_nct::<P>(borrow, "HeaplessBigInt::sub underflow");
312        res
313    }
314}
315
316impl<T: MachineWord + CarryingMul<Unsigned = T, Output = T>, const CAP: usize, P: Personality>
317    core::ops::Mul<&HeaplessBigInt<T, CAP, P>> for &HeaplessBigInt<T, CAP, P>
318{
319    type Output = HeaplessBigInt<T, CAP, P>;
320    fn mul(self, other: &HeaplessBigInt<T, CAP, P>) -> Self::Output {
321        let (res, overflow) = self.overflowing_mul(other);
322        panic_on_overflow_if_nct::<P>(overflow, "HeaplessBigInt::mul overflow");
323        res
324    }
325}
326
327// Value + mixed-receiver variants for callers that want by-value operators:
328// owned-owned `+`/`-`/`*`, owned-ref `*`, ref-owned `-`. Each delegates to
329// the `&Self op &Self` variant. `HeaplessBigInt: Copy`, so forwarding
330// by-value operands to references is a no-op at runtime.
331
332impl<T: MachineWord, const CAP: usize, P: Personality> core::ops::Add
333    for HeaplessBigInt<T, CAP, P>
334{
335    type Output = Self;
336    fn add(self, other: Self) -> Self {
337        (&self).add(&other)
338    }
339}
340
341impl<T: MachineWord, const CAP: usize, P: Personality> core::ops::Add<&HeaplessBigInt<T, CAP, P>>
342    for HeaplessBigInt<T, CAP, P>
343{
344    type Output = Self;
345    fn add(self, other: &Self) -> Self {
346        (&self).add(other)
347    }
348}
349
350impl<T: MachineWord, const CAP: usize, P: Personality> core::ops::Add<HeaplessBigInt<T, CAP, P>>
351    for &HeaplessBigInt<T, CAP, P>
352{
353    type Output = HeaplessBigInt<T, CAP, P>;
354    fn add(self, other: HeaplessBigInt<T, CAP, P>) -> Self::Output {
355        self.add(&other)
356    }
357}
358
359impl<T: MachineWord, const CAP: usize, P: Personality> core::ops::Sub
360    for HeaplessBigInt<T, CAP, P>
361{
362    type Output = Self;
363    fn sub(self, other: Self) -> Self {
364        (&self).sub(&other)
365    }
366}
367
368impl<T: MachineWord, const CAP: usize, P: Personality> core::ops::Sub<&HeaplessBigInt<T, CAP, P>>
369    for HeaplessBigInt<T, CAP, P>
370{
371    type Output = Self;
372    fn sub(self, other: &Self) -> Self {
373        (&self).sub(other)
374    }
375}
376
377impl<T: MachineWord, const CAP: usize, P: Personality> core::ops::Sub<HeaplessBigInt<T, CAP, P>>
378    for &HeaplessBigInt<T, CAP, P>
379{
380    type Output = HeaplessBigInt<T, CAP, P>;
381    fn sub(self, other: HeaplessBigInt<T, CAP, P>) -> Self::Output {
382        self.sub(&other)
383    }
384}
385
386impl<T: MachineWord + CarryingMul<Unsigned = T, Output = T>, const CAP: usize, P: Personality>
387    core::ops::Mul for HeaplessBigInt<T, CAP, P>
388{
389    type Output = Self;
390    fn mul(self, other: Self) -> Self {
391        (&self).mul(&other)
392    }
393}
394
395impl<T: MachineWord + CarryingMul<Unsigned = T, Output = T>, const CAP: usize, P: Personality>
396    core::ops::Mul<&HeaplessBigInt<T, CAP, P>> for HeaplessBigInt<T, CAP, P>
397{
398    type Output = Self;
399    fn mul(self, other: &Self) -> Self {
400        (&self).mul(other)
401    }
402}
403
404impl<T: MachineWord + CarryingMul<Unsigned = T, Output = T>, const CAP: usize, P: Personality>
405    core::ops::Mul<HeaplessBigInt<T, CAP, P>> for &HeaplessBigInt<T, CAP, P>
406{
407    type Output = HeaplessBigInt<T, CAP, P>;
408    fn mul(self, other: HeaplessBigInt<T, CAP, P>) -> Self::Output {
409        self.mul(&other)
410    }
411}
412
413// ── const_num_traits Wrapping / Overflowing Add & Sub ──
414//
415// Delegate to the inherent methods; the traits take `self` by value,
416// the inherent methods take references. `HeaplessBigInt: Copy`, so
417// converting between the two is a no-op at runtime.
418
419impl<T: MachineWord, const CAP: usize, P: Personality> WrappingAdd for HeaplessBigInt<T, CAP, P> {
420    type Output = Self;
421    fn wrapping_add(self, v: Self) -> Self::Output {
422        Self::wrapping_add(&self, &v)
423    }
424}
425
426impl<T: MachineWord, const CAP: usize, P: Personality> WrappingSub for HeaplessBigInt<T, CAP, P> {
427    type Output = Self;
428    fn wrapping_sub(self, v: Self) -> Self::Output {
429        Self::wrapping_sub(&self, &v)
430    }
431}
432
433impl<T: MachineWord, const CAP: usize, P: Personality> OverflowingAdd
434    for HeaplessBigInt<T, CAP, P>
435{
436    type Output = Self;
437    fn overflowing_add(self, v: Self) -> (Self::Output, bool) {
438        Self::overflowing_add(&self, &v)
439    }
440}
441
442impl<T: MachineWord, const CAP: usize, P: Personality> OverflowingSub
443    for HeaplessBigInt<T, CAP, P>
444{
445    type Output = Self;
446    fn overflowing_sub(self, v: Self) -> (Self::Output, bool) {
447        Self::overflowing_sub(&self, &v)
448    }
449}
450
451// Reference-receiver variants mirror `FixedUInt`'s pattern (`add_sub_impl.rs`),
452// letting `&HeaplessBigInt` satisfy the same generic trait bound.
453
454impl<T: MachineWord, const CAP: usize, P: Personality> WrappingAdd for &HeaplessBigInt<T, CAP, P> {
455    type Output = HeaplessBigInt<T, CAP, P>;
456    fn wrapping_add(self, v: Self) -> Self::Output {
457        HeaplessBigInt::wrapping_add(self, v)
458    }
459}
460
461impl<T: MachineWord, const CAP: usize, P: Personality> WrappingSub for &HeaplessBigInt<T, CAP, P> {
462    type Output = HeaplessBigInt<T, CAP, P>;
463    fn wrapping_sub(self, v: Self) -> Self::Output {
464        HeaplessBigInt::wrapping_sub(self, v)
465    }
466}
467
468impl<T: MachineWord, const CAP: usize, P: Personality> OverflowingAdd
469    for &HeaplessBigInt<T, CAP, P>
470{
471    type Output = HeaplessBigInt<T, CAP, P>;
472    fn overflowing_add(self, v: Self) -> (Self::Output, bool) {
473        HeaplessBigInt::overflowing_add(self, v)
474    }
475}
476
477impl<T: MachineWord, const CAP: usize, P: Personality> OverflowingSub
478    for &HeaplessBigInt<T, CAP, P>
479{
480    type Output = HeaplessBigInt<T, CAP, P>;
481    fn overflowing_sub(self, v: Self) -> (Self::Output, bool) {
482        HeaplessBigInt::overflowing_sub(self, v)
483    }
484}
485
486// WrappingMul — explicit wrap-at-width multiply, for callers that want
487// the low half rather than `core::ops::Mul`'s panic-on-overflow.
488
489impl<T, const CAP: usize, P: Personality> WrappingMul for HeaplessBigInt<T, CAP, P>
490where
491    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
492{
493    type Output = Self;
494    fn wrapping_mul(self, v: Self) -> Self::Output {
495        Self::wrapping_mul(&self, &v)
496    }
497}
498
499impl<T, const CAP: usize, P: Personality> WrappingMul for &HeaplessBigInt<T, CAP, P>
500where
501    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
502{
503    type Output = HeaplessBigInt<T, CAP, P>;
504    fn wrapping_mul(self, v: Self) -> Self::Output {
505        HeaplessBigInt::wrapping_mul(self, v)
506    }
507}
508
509// OverflowingMul — value-width overflow flag, matching FixedUInt.
510
511impl<T, const CAP: usize, P: Personality> OverflowingMul for HeaplessBigInt<T, CAP, P>
512where
513    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
514{
515    type Output = Self;
516    fn overflowing_mul(self, v: Self) -> (Self::Output, bool) {
517        Self::overflowing_mul(&self, &v)
518    }
519}
520
521impl<T, const CAP: usize, P: Personality> OverflowingMul for &HeaplessBigInt<T, CAP, P>
522where
523    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
524{
525    type Output = HeaplessBigInt<T, CAP, P>;
526    fn overflowing_mul(self, v: Self) -> (Self::Output, bool) {
527        HeaplessBigInt::overflowing_mul(self, v)
528    }
529}
530
531// ── CarryingAdd at the bigint level ──
532//
533// `self + rhs + carry_in` with carry_out, over the operands' width
534// (`max(self.len, rhs.len)`), not `CAP` — the symmetric partner of
535// `borrowing_sub` below, same width rule as every other op. Same
536// value-width result as `overflowing_add`; the difference is the
537// `carry_in` input, which lets a multi-precision routine (wide-REDC)
538// chain carries across limbs the way `borrowing_sub` chains borrows.
539
540impl<T, const CAP: usize, P: Personality> CarryingAdd for HeaplessBigInt<T, CAP, P>
541where
542    T: MachineWord,
543{
544    type Output = Self;
545    fn carrying_add(self, rhs: Self, carry_in: bool) -> (Self::Output, bool) {
546        let out_len = core::cmp::max(self.len as usize, rhs.len as usize);
547        let mut out_limbs = [zero::<T>(); CAP];
548        let mut carry = carry_in;
549        let mut i = 0;
550        while i < out_len {
551            let (sum, c) = <T as CarryingAdd>::carrying_add(self.limbs[i], rhs.limbs[i], carry);
552            out_limbs[i] = sum;
553            carry = c;
554            i += 1;
555        }
556        (
557            HeaplessBigInt {
558                limbs: out_limbs,
559                len: out_len as u16,
560                _p: PhantomData,
561            },
562            carry,
563        )
564    }
565}
566
567// `self - rhs - borrow_in` with borrow_out, over the operands' width
568// (`max(self.len, rhs.len)`) — same width rule as `wrapping_sub`, so
569// underflow wraps at the value's width. Used by multi-precision reduction.
570
571impl<T, const CAP: usize, P: Personality> BorrowingSub for HeaplessBigInt<T, CAP, P>
572where
573    T: MachineWord,
574{
575    type Output = Self;
576    fn borrowing_sub(self, rhs: Self, borrow_in: bool) -> (Self::Output, bool) {
577        let out_len = core::cmp::max(self.len as usize, rhs.len as usize);
578        let mut out_limbs = [zero::<T>(); CAP];
579        let mut borrow = borrow_in;
580        let mut i = 0;
581        while i < out_len {
582            let (diff, br) =
583                <T as BorrowingSub>::borrowing_sub(self.limbs[i], rhs.limbs[i], borrow);
584            out_limbs[i] = diff;
585            borrow = br;
586            i += 1;
587        }
588        (
589            HeaplessBigInt {
590                limbs: out_limbs,
591                len: out_len as u16,
592                _p: PhantomData,
593            },
594            borrow,
595        )
596    }
597}
598
599// ── CarryingMul at the bigint level ──
600//
601// `(lo, hi) = self * rhs + carry (+ add)`, split at the operands' VALUE
602// width `W = max(len)` words: `lo` = low W words, `hi` = high W words,
603// reconstructing as `full = hi·2^(W·word_bits) + lo`. This matches
604// `bits_precision()` (= `len·word_bits`) and the primitive contract
605// (`200u8.wide_mul(200) = (64, 156)` splits at the type width) — and it
606// is what a wide Montgomery reduction reads back, since it reconstructs
607// against the operand's `bits_precision`, not the carrier's capacity.
608//
609// NOT `CAP`: for a sub-capacity field (`len < CAP` — e.g. a modulus
610// narrower than the carrier) a CAP split would strand the high half in
611// `lo` (`hi = 0`) and the REDC would be off by limbs. `CAP` is invisible
612// here just like every other value-width op; the only fixed-width use of
613// capacity is `ToBytes`'s owned holder. (For a full-width field
614// `len == CAP`, so the two coincide.)
615
616impl<T, const CAP: usize, P: Personality> CarryingMul for HeaplessBigInt<T, CAP, P>
617where
618    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
619{
620    type Unsigned = Self;
621    type Output = Self;
622
623    fn carrying_mul(self, rhs: Self, carry: Self) -> (Self::Unsigned, Self::Output) {
624        let zero_v = <Self as const_num_traits::Zero>::zero();
625        self.carrying_mul_add(rhs, carry, zero_v)
626    }
627
628    fn carrying_mul_add(self, rhs: Self, carry: Self, add: Self) -> (Self::Unsigned, Self::Output) {
629        // Split point W = the operands' value width. `carry`/`add` are
630        // added into the low half, so W must also cover them.
631        let w = core::cmp::max(
632            core::cmp::max(self.len as usize, rhs.len as usize),
633            core::cmp::max(carry.len as usize, add.len as usize),
634        );
635        let mut lo_limbs = [zero::<T>(); CAP];
636        let mut hi_limbs = [zero::<T>(); CAP];
637
638        // Schoolbook self * rhs into positions [0, 2W): pos < W → lo,
639        // else hi[pos - W]. Iterate the operands' word counts (public
640        // shape), not the array capacity.
641        let a_n = self.len as usize;
642        let b_n = rhs.len as usize;
643        let mut i = 0;
644        while i < a_n {
645            let mut c = zero::<T>();
646            let mut j = 0;
647            while j < b_n {
648                let pos = i + j;
649                let (t_lo, t_hi) = <T as CarryingMul>::carrying_mul(self.limbs[i], rhs.limbs[j], c);
650                let existing = if pos < w {
651                    lo_limbs[pos]
652                } else {
653                    hi_limbs[pos - w]
654                };
655                let (sum, c1) = <T as CarryingAdd>::carrying_add(existing, t_lo, false);
656                if pos < w {
657                    lo_limbs[pos] = sum;
658                } else {
659                    hi_limbs[pos - w] = sum;
660                }
661                let (new_c, _) = <T as CarryingAdd>::carrying_add(t_hi, zero::<T>(), c1);
662                c = new_c;
663                j += 1;
664            }
665            // Row-final carry at column i + b_n.
666            let tail = i + b_n;
667            if tail < w {
668                let (sum, _) = <T as CarryingAdd>::carrying_add(lo_limbs[tail], c, false);
669                lo_limbs[tail] = sum;
670            } else {
671                let (sum, _) = <T as CarryingAdd>::carrying_add(hi_limbs[tail - w], c, false);
672                hi_limbs[tail - w] = sum;
673            }
674            i += 1;
675        }
676
677        // Fold carry, then add, into the low half [0, W); overflow into hi.
678        for src in [&carry, &add] {
679            let mut cin = false;
680            let mut i = 0;
681            while i < w {
682                let (sum, c) = <T as CarryingAdd>::carrying_add(lo_limbs[i], src.limbs[i], cin);
683                lo_limbs[i] = sum;
684                cin = c;
685                i += 1;
686            }
687            let mut i = 0;
688            while cin && i < w {
689                let (sum, c) = <T as CarryingAdd>::carrying_add(hi_limbs[i], zero::<T>(), true);
690                hi_limbs[i] = sum;
691                cin = c;
692                i += 1;
693            }
694        }
695
696        let lo = HeaplessBigInt {
697            limbs: lo_limbs,
698            len: w as u16,
699            _p: PhantomData,
700        };
701        let hi = HeaplessBigInt {
702            limbs: hi_limbs,
703            len: w as u16,
704            _p: PhantomData,
705        };
706        (lo, hi)
707    }
708}
709
710#[inline]
711pub(crate) fn zero_tail_ok<T: MachineWord>(limbs: &[T], used: usize) -> bool {
712    let mut i = used;
713    while i < limbs.len() {
714        if !is_zero(&limbs[i]) {
715            return false;
716        }
717        i += 1;
718    }
719    true
720}