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}