Skip to main content

fixed_bigint/heapless/
mod.rs

1//! Unsigned integer whose width is chosen at runtime, not by the type.
2//!
3//! A [`HeaplessBigInt<T, CAP>`](HeaplessBigInt) carried at `len = k` **is** a
4//! `k`-word integer and behaves bit-for-bit like
5//! [`FixedUInt<T, k>`](crate::FixedUInt): arithmetic wraps, `<<` truncates, and
6//! overflow is reported at the value's width — it is *not* a growable bignum.
7//! The only difference from `FixedUInt` is that the width is `len`, a runtime
8//! (public) shape parameter, rather than the const `N`. You pick `len` at
9//! construction the way you pick `N`; `CAP` is only the storage ceiling
10//! (`len <= CAP`), invisible to arithmetic. Every op iterates `0..self.len`,
11//! never `0..CAP`. Same `Personality` typestate (`Nct` / `Ct`) and
12//! `T: MachineWord` bound as `FixedUInt`.
13//!
14//! Because the width is `len`, a value must be *constructed* at its intended
15//! width: `zero()` / `one()` / short decodes are minimal-width, so anywhere the
16//! operating width matters (accumulators, field elements, reduction targets)
17//! pin it from a witness with
18//! [`WithPrecision`](const_num_traits::WithPrecision) — e.g.
19//! `zero_with_precision_of(&modulus)` — rather than seeding from an identity
20//! and letting it silently run narrow.
21//!
22//! # Width contract
23//!
24//! Every operation resolves at the *value* width `len·word_bits` and returns
25//! bit-for-bit what the same-width `FixedUInt` would — no op grows past
26//! `max(operand len)` or narrows the magnitude, and `CAP` never enters a
27//! result. The result `len` of each op:
28//!
29//! | operation | result `len` |
30//! |---|---|
31//! | `wrapping`/`overflowing`/`checked` `add`·`sub`·`mul`, `+` `-` `*` | `max(a.len, b.len)` |
32//! | `Shl` (`<<`) | `self.len` — high bits past the width are discarded |
33//! | `Shr` (`>>`) | `self.len` minus the whole-word shift |
34//! | `WideMul` / [`CarryingMul`](const_num_traits::CarryingMul) | `lo` and `hi` each `max(a.len, b.len)`; reconstruct `hi·2^(W·word_bits) + lo` |
35//! | `Div` (`/`), `Rem` (`%`) | `max(dividend.len, divisor.len)` |
36//! | `BitAnd` (`&`) | `min(a.len, b.len)` |
37//! | `BitOr` | `max(a.len, b.len)` |
38//! | [`widened`](HeaplessBigInt::widened) / `WithPrecision` | the requested width (grow-only) |
39//!
40//! ## Construction & serialization widths
41//!
42//! Constructors and byte I/O carry *different* widths for the same value —
43//! there is no single "natural" one for a runtime carrier. When the width
44//! matters, pick the row you mean, or pin afterward with `WithPrecision`:
45//!
46//! | path | width |
47//! |---|---|
48//! | `From<u8/u16/u32>` | `ceil(size_of::<uN>() / word)` — the source int's width |
49//! | inherent [`from_le_bytes`](HeaplessBigInt::from_le_bytes) / `from_be_bytes(&[u8])` | `ceil(slice.len() / word)` — the slice width |
50//! | [`new_zero_with_len`](HeaplessBigInt::new_zero_with_len) / [`from_limbs`](HeaplessBigInt::from_limbs) | exactly the given `len` |
51//! | `FromBytes` **trait** (`BytesHolder<T, CAP>`) | **`CAP`** — an owned holder can't be runtime-sized |
52//! | inherent [`to_le_bytes`](HeaplessBigInt::to_le_bytes) / `to_be_bytes(&mut [u8])` | value width (`len·word` bytes) |
53//! | `ToBytes` **trait** (`BytesHolder<T, CAP>`) | **`CAP`** — same reason |
54//!
55//! The trait (`ToBytes`/`FromBytes`) paths are capacity-width because their
56//! owned `Bytes` associated type is fixed-size — the intended shape for a
57//! full-precision operand (a modulus with `len == CAP`) and for round-tripping
58//! against `FixedUInt<T, CAP>`. For value-width bytes use the inherent methods.
59
60use crate::MachineWord;
61use const_num_traits::{Ct, Nct, Personality};
62use core::marker::PhantomData;
63
64mod arith;
65mod bits;
66mod bitwise;
67mod bytes;
68#[cfg(feature = "cios")]
69mod cios;
70mod cmp;
71mod div_rem;
72mod from_prim;
73mod has_personality;
74mod identities;
75mod parity;
76mod shift;
77#[cfg(any(feature = "nightly", feature = "use-unsafe"))]
78mod to_bytes;
79#[cfg(feature = "zeroize")]
80mod zeroize_impl;
81
82/// A `len`-word unsigned integer whose width is chosen at runtime.
83///
84/// Behaves bit-for-bit like [`FixedUInt<T, len>`](crate::FixedUInt): every op
85/// resolves at the value's width (`len·word_bits`), never a growable bignum.
86/// `CAP` is the maximum limb count (compile-time storage ceiling); `len` is the
87/// logical used-limb count (runtime) and the operating width — the words in
88/// `[len, CAP)` do not exist for arithmetic. Invariants (enforced by the
89/// module):
90///
91/// - `CAP <= u16::MAX as usize` (compile-time-asserted).
92/// - `(len as usize) <= CAP`.
93/// - `limbs[len as usize..CAP]` is all zero at every observable state.
94/// - `len` is set only from public shape parameters, never from limb content.
95pub struct HeaplessBigInt<T, const CAP: usize, P: Personality = Nct>
96where
97    T: MachineWord,
98{
99    pub(crate) limbs: [T; CAP],
100    pub(crate) len: u16,
101    pub(crate) _p: PhantomData<P>,
102}
103
104/// Compile-time assertion that `CAP` fits in `u16` (the `len` field type).
105pub(crate) trait AssertCapFits {
106    const CHECK: ();
107}
108
109impl<T: MachineWord, const CAP: usize, P: Personality> AssertCapFits for HeaplessBigInt<T, CAP, P> {
110    const CHECK: () = assert!(
111        CAP <= u16::MAX as usize,
112        "HeaplessBigInt: CAP exceeds u16::MAX; len type cannot represent this capacity"
113    );
114}
115
116// ── Constructors (shape-setting from public parameters) ──
117
118impl<T: MachineWord, const CAP: usize, P: Personality> HeaplessBigInt<T, CAP, P> {
119    /// Zero with a caller-supplied logical length. The `len` is treated as
120    /// a public shape parameter from here on. Panics if `len > CAP`.
121    #[inline]
122    pub fn new_zero_with_len(len: u16) -> Self {
123        let () = <Self as AssertCapFits>::CHECK;
124        assert!(
125            (len as usize) <= CAP,
126            "HeaplessBigInt::new_zero_with_len: len {} > CAP {}",
127            len,
128            CAP,
129        );
130        Self {
131            limbs: [zero::<T>(); CAP],
132            len,
133            _p: PhantomData,
134        }
135    }
136
137    /// Full-capacity zero: `len = CAP`. Used where an algorithm needs a
138    /// pre-sized workspace (CIOS accumulator, product buffer).
139    #[inline]
140    pub fn zero_full_cap() -> Self {
141        Self::new_zero_with_len(CAP as u16)
142    }
143
144    /// Construct from a limb array + explicit `len`. Panics if `len > CAP`
145    /// or if any limb at index `>= len` is non-zero (invariant check).
146    ///
147    /// The tail check runs in every build, not just under `debug_assertions`:
148    /// downstream arithmetic, equality, and `widened` all assume the tail is
149    /// zero, so a release build that skipped it would silently promote a
150    /// hidden limb into the value.
151    #[inline]
152    pub fn from_limbs(limbs: [T; CAP], len: u16) -> Self {
153        let () = <Self as AssertCapFits>::CHECK;
154        assert!((len as usize) <= CAP);
155        let mut i = len as usize;
156        while i < CAP {
157            assert!(
158                is_zero(&limbs[i]),
159                "HeaplessBigInt::from_limbs: zero-tail invariant violated at index {}",
160                i
161            );
162            i += 1;
163        }
164        Self {
165            limbs,
166            len,
167            _p: PhantomData,
168        }
169    }
170}
171
172// ── Accessors ──
173
174impl<T: MachineWord, const CAP: usize, P: Personality> HeaplessBigInt<T, CAP, P> {
175    /// Logical length in used limbs. Public shape parameter.
176    #[inline]
177    pub const fn len(&self) -> u16 {
178        self.len
179    }
180
181    /// True iff `len == 0` (mathematical zero shape).
182    #[inline]
183    pub const fn is_empty(&self) -> bool {
184        self.len == 0
185    }
186
187    /// Maximum limb count.
188    #[inline]
189    pub const fn capacity(&self) -> usize {
190        CAP
191    }
192
193    /// Return a copy carried at `new_len` words, pinning the operating
194    /// width without changing the value.
195    ///
196    /// Arithmetic here is width-driven: a `HeaplessBigInt` at `len = k`
197    /// behaves exactly like `FixedUInt<T, k>`, and every op resolves at
198    /// `max(operand len)`. So a value assembled from small pieces (e.g. an
199    /// accumulator seeded from [`zero`](const_num_traits::Zero::zero) then
200    /// added to a one-word digit) is a *narrow* type and wraps at that
201    /// narrow width. To carry it at a chosen width — the way you pick `N`
202    /// for `FixedUInt` — pin it here once; subsequent ops keep that width
203    /// because `max` preserves it. Widening only relabels the width: the
204    /// limbs in `[len, new_len)` are already zero by the zero-tail
205    /// invariant.
206    ///
207    /// Panics if `new_len < len` (this only widens) or `new_len > CAP`.
208    #[inline]
209    #[must_use]
210    pub fn widened(&self, new_len: u16) -> Self {
211        assert!(
212            new_len >= self.len && new_len as usize <= CAP,
213            "widened: new_len {new_len} must be in [len {}, CAP {CAP}]",
214            self.len
215        );
216        let mut out = *self;
217        out.len = new_len;
218        out
219    }
220
221    /// Read-only view of the used limbs.
222    #[inline]
223    pub fn limbs(&self) -> &[T] {
224        &self.limbs[..self.len as usize]
225    }
226
227    /// Full-buffer view including the zero tail. Only meaningful under
228    /// the zero-tail invariant.
229    #[inline]
230    pub fn all_limbs(&self) -> &[T; CAP] {
231        &self.limbs
232    }
233}
234
235// ── Copy / Clone ──
236
237impl<T: MachineWord, const CAP: usize, P: Personality> Clone for HeaplessBigInt<T, CAP, P> {
238    #[inline]
239    fn clone(&self) -> Self {
240        *self
241    }
242}
243
244impl<T: MachineWord, const CAP: usize, P: Personality> Copy for HeaplessBigInt<T, CAP, P> {}
245
246// Debug is personality-split like `FixedUInt`: `Nct` prints the limbs,
247// `Ct` is opaque so secret values never reach a formatter.
248impl<T: MachineWord + core::fmt::Debug, const CAP: usize> core::fmt::Debug
249    for HeaplessBigInt<T, CAP, Nct>
250{
251    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
252        write!(
253            f,
254            "HeaplessBigInt<{}, {}, _>{{ limbs: {:?}, len: {} }}",
255            core::any::type_name::<T>(),
256            CAP,
257            &self.limbs[..self.len as usize],
258            self.len,
259        )
260    }
261}
262
263impl<T: MachineWord, const CAP: usize> core::fmt::Debug for HeaplessBigInt<T, CAP, Ct> {
264    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
265        f.write_str("HeaplessBigInt<…>")
266    }
267}
268
269// ── Internal helpers ──
270
271#[inline]
272pub(crate) fn zero<T: MachineWord>() -> T {
273    <T as const_num_traits::ConstZero>::ZERO
274}
275
276#[inline]
277pub(crate) fn is_zero<T: MachineWord>(v: &T) -> bool {
278    <T as const_num_traits::Zero>::is_zero(v)
279}