Skip to main content

fixed_bigint/
fixeduint.rs

1// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use num_traits::{ToPrimitive, Zero};
16
17use core::convert::TryFrom;
18use core::fmt::Write;
19
20pub use crate::const_numtraits::{
21    ConstAbsDiff, ConstBitPrimInt, ConstBorrowingSub, ConstBounded, ConstCarryingAdd,
22    ConstCarryingMul, ConstCheckedPow, ConstDivCeil, ConstIlog, ConstIsqrt, ConstMultiple,
23    ConstOne, ConstPowerOfTwo, ConstPrimInt, ConstWideningMul, ConstZero,
24};
25use crate::machineword::{ConstMachineWord, MachineWord};
26
27#[allow(unused_imports)]
28use num_traits::{FromPrimitive, Num};
29
30mod abs_diff_impl;
31mod add_sub_impl;
32mod bit_ops_impl;
33mod checked_pow_impl;
34mod div_ceil_impl;
35mod euclid;
36mod extended_precision_impl;
37mod ilog_impl;
38mod isqrt_impl;
39mod iter_impl;
40mod midpoint_impl;
41mod mul_acc_ops_impl;
42mod mul_div_impl;
43mod multiple_impl;
44mod num_integer_impl;
45mod num_traits_casts;
46mod num_traits_identity;
47mod power_of_two_impl;
48mod prim_int_impl;
49mod roots_impl;
50mod string_conversion;
51// ConstToBytes trait (nightly only, uses generic_const_exprs)
52#[cfg(feature = "nightly")]
53mod const_to_from_bytes;
54// num_traits::ToBytes/FromBytes (stable impl, no generic_const_exprs viral bounds)
55#[cfg(any(feature = "nightly", feature = "use-unsafe"))]
56mod to_from_bytes;
57
58use crate::personality::{Ct, Nct, Personality, PersonalityMarker, PersonalityTag};
59#[cfg(feature = "zeroize")]
60use zeroize::DefaultIsZeroes;
61
62/// Fixed-size unsigned integer, represented by array of N words of builtin unsigned type T.
63///
64/// The optional `P: Personality` parameter selects which implementations of
65/// operation primitives are used at each call site. Defaults to [`Nct`]
66/// (non-constant-time). Use `FixedUInt<T, N, Ct>` for
67/// values that must be handled in constant time. See [`crate::personality`].
68///
69/// [`Nct`]: crate::personality::Nct
70/// [`Ct`]: crate::personality::Ct
71#[derive(Copy)]
72pub struct FixedUInt<T, const N: usize, P: Personality = Nct>
73where
74    T: MachineWord,
75{
76    /// Little-endian word array
77    pub(super) array: [T; N],
78    /// Personality marker (zero-size).
79    pub(super) _p: PersonalityMarker<P>,
80}
81
82// Debug is implemented manually so the Ct variant can redact its value.
83// Nct keeps the conventional "FixedUInt { array, _p }" format; Ct prints
84// `FixedUInt<…>` (placeholder) to keep limb contents out of panic
85// messages, dbg! output, and logs.
86impl<T: MachineWord + core::fmt::Debug, const N: usize> core::fmt::Debug for FixedUInt<T, N, Nct> {
87    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
88        f.debug_struct("FixedUInt")
89            .field("array", &self.array)
90            .finish()
91    }
92}
93
94impl<T: MachineWord, const N: usize> core::fmt::Debug for FixedUInt<T, N, Ct> {
95    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
96        f.write_str("FixedUInt<…>")
97    }
98}
99
100#[cfg(feature = "zeroize")]
101impl<T: MachineWord, const N: usize, P: Personality> DefaultIsZeroes for FixedUInt<T, N, P> {}
102
103impl<T, const N: usize, P: Personality> From<[T; N]> for FixedUInt<T, N, P>
104where
105    T: MachineWord,
106{
107    fn from(array: [T; N]) -> Self {
108        Self {
109            array,
110            _p: core::marker::PhantomData,
111        }
112    }
113}
114
115// Internal constructor for sites that need to build a FixedUInt from a raw
116// limb array.
117impl<T: MachineWord, const N: usize, P: Personality> FixedUInt<T, N, P> {
118    pub(crate) const fn from_array(array: [T; N]) -> Self {
119        Self {
120            array,
121            _p: core::marker::PhantomData,
122        }
123    }
124}
125
126// ---------------------------------------------------------------------------
127// Personality conversions.
128// ---------------------------------------------------------------------------
129
130/// Lossless conversion from `Nct` to `Ct`. Tightens the invariant
131/// (declares that the value will be handled under the CT threat model going
132/// forward). Bit representation is identical; this is a free reinterpretation.
133impl<T: MachineWord, const N: usize> From<FixedUInt<T, N, Nct>> for FixedUInt<T, N, Ct> {
134    fn from(v: FixedUInt<T, N, Nct>) -> Self {
135        FixedUInt::from_array(v.array)
136    }
137}
138
139impl<T: MachineWord, const N: usize> FixedUInt<T, N, Ct> {
140    /// Drop the CT guarantee and convert to the `Nct` variant.
141    ///
142    /// **This is an explicit downgrade.** The caller is asserting that the
143    /// value is no longer secret — typically because the CT-handling phase
144    /// has ended (e.g. a finalized signature, a published key, a post-
145    /// reduction modular value about to be serialized).
146    pub const fn forget_ct(self) -> FixedUInt<T, N, Nct> {
147        FixedUInt::from_array(self.array)
148    }
149}
150
151impl<T: MachineWord + subtle::ConditionallySelectable, const N: usize> FixedUInt<T, N, Ct> {
152    /// CT-friendly counterpart to `num_traits::CheckedAdd::checked_add`.
153    /// Returns `CtOption::new(res, Choice::from(!overflow))` — the result is
154    /// always computed (always-iterate via overflowing_add), and the
155    /// validity Choice carries the overflow flag without exposing it as
156    /// a control-flow signal.
157    pub fn ct_checked_add(&self, other: &Self) -> subtle::CtOption<Self> {
158        let (res, overflow) =
159            <Self as crate::const_numtraits::ConstOverflowingAdd>::overflowing_add(self, other);
160        let valid = subtle::Choice::from((!overflow) as u8);
161        subtle::CtOption::new(res, valid)
162    }
163
164    /// CT-friendly counterpart to `num_traits::CheckedSub::checked_sub`.
165    pub fn ct_checked_sub(&self, other: &Self) -> subtle::CtOption<Self> {
166        let (res, overflow) =
167            <Self as crate::const_numtraits::ConstOverflowingSub>::overflowing_sub(self, other);
168        let valid = subtle::Choice::from((!overflow) as u8);
169        subtle::CtOption::new(res, valid)
170    }
171
172    /// CT-friendly counterpart to `num_traits::CheckedMul::checked_mul`.
173    pub fn ct_checked_mul(&self, other: &Self) -> subtle::CtOption<Self> {
174        let (res, overflow) =
175            <Self as crate::const_numtraits::ConstOverflowingMul>::overflowing_mul(self, other);
176        let valid = subtle::Choice::from((!overflow) as u8);
177        subtle::CtOption::new(res, valid)
178    }
179
180    /// CT-friendly counterpart to `ConstCheckedShl::checked_shl`.
181    pub fn ct_checked_shl(&self, bits: u32) -> subtle::CtOption<Self> {
182        let (res, overflow) =
183            <Self as crate::const_numtraits::ConstOverflowingShl>::overflowing_shl(self, bits);
184        let valid = subtle::Choice::from((!overflow) as u8);
185        subtle::CtOption::new(res, valid)
186    }
187
188    /// CT-friendly counterpart to `ConstCheckedShr::checked_shr`.
189    pub fn ct_checked_shr(&self, bits: u32) -> subtle::CtOption<Self> {
190        let (res, overflow) =
191            <Self as crate::const_numtraits::ConstOverflowingShr>::overflowing_shr(self, bits);
192        let valid = subtle::Choice::from((!overflow) as u8);
193        subtle::CtOption::new(res, valid)
194    }
195
196    /// CT-friendly counterpart to `ConstPowerOfTwo::checked_next_power_of_two`.
197    pub fn ct_checked_next_power_of_two(self) -> subtle::CtOption<Self>
198    where
199        T: subtle::ConstantTimeEq,
200    {
201        let one = <Self as num_traits::One>::one();
202        let m_one = <Self as crate::const_numtraits::ConstWrappingSub>::wrapping_sub(&self, &one);
203        let leading = <Self as crate::const_numtraits::ConstBitPrimInt>::leading_zeros(m_one);
204        let bits = Self::BIT_SIZE as u32 - leading;
205        let shifted = one << (bits as usize);
206        let is_zero_choice =
207            <Self as subtle::ConstantTimeEq>::ct_eq(&self, &<Self as num_traits::Zero>::zero());
208        // result = is_zero ? 1 : shifted
209        let result = <Self as subtle::ConditionallySelectable>::conditional_select(
210            &shifted,
211            &one,
212            is_zero_choice,
213        );
214        // overflow iff bits >= BIT_SIZE; when input == 0 we treat as valid
215        // (the answer is 1).
216        let overflow = (bits >= Self::BIT_SIZE as u32) as u8;
217        let valid_otherwise = subtle::Choice::from(1u8 ^ overflow);
218        let valid = <subtle::Choice as subtle::ConditionallySelectable>::conditional_select(
219            &valid_otherwise,
220            &subtle::Choice::from(1u8),
221            is_zero_choice,
222        );
223        subtle::CtOption::new(result, valid)
224    }
225
226    pub fn ct_checked_pow(self, exp: u32) -> subtle::CtOption<Self>
227    where
228        T: subtle::ConstantTimeEq + subtle::ConstantTimeGreater,
229        for<'a> &'a Self: core::ops::Mul<&'a Self, Output = Self>,
230    {
231        use num_traits::ops::overflowing::OverflowingMul;
232        let mut result = <Self as num_traits::One>::one();
233        let mut base = self;
234        let mut e = exp;
235        let mut any_overflow: u8 = 0;
236        for _ in 0..u32::BITS {
237            // `black_box` opacifies the per-iteration bit so LLVM can't
238            // recognize the XOR-select as a cmov-on-secret-flag — see
239            // `const_ct_select` for the load-bearing explanation.
240            let bit = core::hint::black_box((e & 1) as u8);
241            let (candidate, mul_ov) = OverflowingMul::overflowing_mul(&result, &base);
242            // Multiply overflow matters iff bit_k is set.
243            any_overflow |= (mul_ov as u8) & bit;
244            // Per-limb CT-select of result vs candidate.
245            let bit_t = <T as core::convert::From<u8>>::from(bit);
246            let mask = core::hint::black_box(
247                bit_t * <T as crate::const_numtraits::ConstBounded>::max_value(),
248            );
249            for i in 0..N {
250                let diff = result.array[i] ^ candidate.array[i];
251                result.array[i] ^= mask & diff;
252            }
253            e >>= 1;
254            let (new_base, base_ov) = OverflowingMul::overflowing_mul(&base, &base);
255            // Square overflow matters iff there are remaining set bits in e.
256            let any_remaining: u8 = core::hint::black_box((e != 0) as u8);
257            any_overflow |= (base_ov as u8) & any_remaining;
258            base = new_base;
259        }
260        let valid = subtle::Choice::from(1u8 ^ any_overflow);
261        subtle::CtOption::new(result, valid)
262    }
263}
264
265// ---------------------------------------------------------------------------
266// subtle integration — Ct variant only.
267// ---------------------------------------------------------------------------
268
269impl<T: MachineWord + subtle::ConstantTimeEq, const N: usize> subtle::ConstantTimeEq
270    for FixedUInt<T, N, Ct>
271{
272    fn ct_eq(&self, other: &Self) -> subtle::Choice {
273        <[T] as subtle::ConstantTimeEq>::ct_eq(self.array.as_slice(), other.array.as_slice())
274    }
275}
276
277impl<T: MachineWord + subtle::ConditionallySelectable, const N: usize>
278    subtle::ConditionallySelectable for FixedUInt<T, N, Ct>
279{
280    fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
281        let mut array = a.array;
282        let mut i = 0;
283        while i < N {
284            array[i] = T::conditional_select(&a.array[i], &b.array[i], choice);
285            i += 1;
286        }
287        FixedUInt::from_array(array)
288    }
289}
290
291// `Ord::cmp` / `PartialOrd::partial_cmp` dispatch on `P::TAG`: the `Ct` arm
292// runs `const_cmp_ct`, a full-width scan with no short-circuit. The returned
293// `Ordering` is still a CT leak if a caller branches on it, so secret-data
294// callers should prefer `ConstantTimeGreater`/`ConstantTimeLess` below, which
295// produce a `Choice` that pairs with `ConditionallySelectable` for branch-free
296// Montgomery conditional-subtract.
297impl<T: MachineWord + subtle::ConstantTimeEq + subtle::ConstantTimeGreater, const N: usize>
298    subtle::ConstantTimeGreater for FixedUInt<T, N, Ct>
299{
300    fn ct_gt(&self, other: &Self) -> subtle::Choice {
301        let mut gt = subtle::Choice::from(0u8);
302        let mut undecided = subtle::Choice::from(1u8);
303        let mut i = N;
304        while i > 0 {
305            i -= 1;
306            let gt_here = self.array[i].ct_gt(&other.array[i]);
307            let eq_here = self.array[i].ct_eq(&other.array[i]);
308            gt |= undecided & gt_here;
309            undecided &= eq_here;
310        }
311        gt
312    }
313}
314
315impl<T: MachineWord + subtle::ConstantTimeEq + subtle::ConstantTimeGreater, const N: usize>
316    subtle::ConstantTimeLess for FixedUInt<T, N, Ct>
317{
318}
319
320const LONGEST_WORD_IN_BITS: usize = 128;
321
322impl<T: MachineWord, const N: usize, P: Personality> FixedUInt<T, N, P> {
323    const WORD_SIZE: usize = core::mem::size_of::<T>();
324    const WORD_BITS: usize = Self::WORD_SIZE * 8;
325    const BYTE_SIZE: usize = Self::WORD_SIZE * N;
326    const BIT_SIZE: usize = Self::BYTE_SIZE * 8;
327
328    /// Creates and zero-initializes a FixedUInt.
329    pub fn new() -> FixedUInt<T, N, P> {
330        FixedUInt::from_array([T::zero(); N])
331    }
332
333    /// Returns the underlying array.
334    pub fn words(&self) -> &[T; N] {
335        &self.array
336    }
337
338    /// Returns number of used bits.
339    pub fn bit_length(&self) -> u32 {
340        Self::BIT_SIZE as u32 - ConstBitPrimInt::leading_zeros(*self)
341    }
342}
343
344impl<T: MachineWord, const N: usize> FixedUInt<T, N, Nct> {
345    /// Performs a division, returning both the quotient and remainder in a tuple.
346    pub fn div_rem(&self, divisor: &Self) -> (Self, Self) {
347        let (quotient, remainder) = const_div_rem(&self.array, &divisor.array);
348        (Self::from_array(quotient), Self::from_array(remainder))
349    }
350
351    /// Converts to decimal string, given a buffer. CAVEAT: This method removes any leading zeroes
352    pub fn to_radix_str<'a>(
353        &self,
354        result: &'a mut [u8],
355        radix: u8,
356    ) -> Result<&'a str, core::fmt::Error> {
357        type Error = core::fmt::Error;
358
359        if !(2..=16).contains(&radix) {
360            return Err(Error {}); // Radix out of supported range
361        }
362        for byte in result.iter_mut() {
363            *byte = b'0';
364        }
365        if Zero::is_zero(self) {
366            if !result.is_empty() {
367                result[0] = b'0';
368                return core::str::from_utf8(&result[0..1]).map_err(|_| Error {});
369            } else {
370                return Err(Error {});
371            }
372        }
373
374        let mut number = *self;
375        let mut idx = result.len();
376
377        let radix_t = Self::from(radix);
378
379        while !Zero::is_zero(&number) {
380            if idx == 0 {
381                return Err(Error {}); // not enough space in result...
382            }
383
384            idx -= 1;
385            let (quotient, remainder) = number.div_rem(&radix_t);
386
387            let digit = remainder.to_u8().unwrap();
388            result[idx] = match digit {
389                0..=9 => b'0' + digit,          // digits
390                10..=16 => b'a' + (digit - 10), // alphabetic digits for bases > 10
391                _ => return Err(Error {}),
392            };
393
394            number = quotient;
395        }
396
397        let start = result[idx..].iter().position(|&c| c != b'0').unwrap_or(0);
398        let radix_str = core::str::from_utf8(&result[idx + start..]).map_err(|_| Error {})?;
399        Ok(radix_str)
400    }
401}
402
403// Const-compatible from_bytes helper functions
404c0nst::c0nst! {
405    /// Const-compatible from_le_bytes implementation for slices.
406    /// Derives word_size internally from size_of::<T>().
407    pub(crate) c0nst fn impl_from_le_bytes_slice<T: [c0nst] ConstMachineWord, const N: usize>(
408        bytes: &[u8],
409    ) -> [T; N] {
410        let word_size = core::mem::size_of::<T>();
411        let mut ret: [T; N] = [T::zero(); N];
412        let capacity = N * word_size;
413        let total_bytes = if bytes.len() < capacity { bytes.len() } else { capacity };
414
415        let mut byte_index = 0;
416        while byte_index < total_bytes {
417            let word_index = byte_index / word_size;
418            let byte_in_word = byte_index % word_size;
419
420            let byte_value: T = T::from(bytes[byte_index]);
421            let shifted_value = byte_value.shl(byte_in_word * 8);
422            ret[word_index] = ret[word_index].bitor(shifted_value);
423            byte_index += 1;
424        }
425        ret
426    }
427
428    /// Const-compatible from_be_bytes implementation for slices.
429    /// Derives word_size internally from size_of::<T>().
430    pub(crate) c0nst fn impl_from_be_bytes_slice<T: [c0nst] ConstMachineWord, const N: usize>(
431        bytes: &[u8],
432    ) -> [T; N] {
433        let word_size = core::mem::size_of::<T>();
434        let mut ret: [T; N] = [T::zero(); N];
435        let capacity_bytes = N * word_size;
436        let total_bytes = if bytes.len() < capacity_bytes { bytes.len() } else { capacity_bytes };
437
438        // For consistent truncation semantics with from_le_bytes, always take the
439        // least significant bytes (rightmost bytes in big-endian representation)
440        let start_offset = if bytes.len() > capacity_bytes {
441            bytes.len() - capacity_bytes
442        } else {
443            0
444        };
445
446        let mut byte_index = 0;
447        while byte_index < total_bytes {
448            // Take bytes from the end of the input (least significant in BE)
449            let be_byte_index = start_offset + total_bytes - 1 - byte_index;
450            let word_index = byte_index / word_size;
451            let byte_in_word = byte_index % word_size;
452
453            let byte_value: T = T::from(bytes[be_byte_index]);
454            let shifted_value = byte_value.shl(byte_in_word * 8);
455            ret[word_index] = ret[word_index].bitor(shifted_value);
456            byte_index += 1;
457        }
458        ret
459    }
460}
461
462// Inherent from_bytes methods (not const - use ConstFromBytes trait for const access)
463impl<T: MachineWord, const N: usize, P: Personality> FixedUInt<T, N, P> {
464    /// Create a little-endian integer value from its representation as a byte array in little endian.
465    pub fn from_le_bytes(bytes: &[u8]) -> Self {
466        Self::from_array(impl_from_le_bytes_slice::<T, N>(bytes))
467    }
468
469    /// Create a big-endian integer value from its representation as a byte array in big endian.
470    pub fn from_be_bytes(bytes: &[u8]) -> Self {
471        Self::from_array(impl_from_be_bytes_slice::<T, N>(bytes))
472    }
473}
474
475impl<T: MachineWord, const N: usize, P: Personality> FixedUInt<T, N, P> {
476    /// Converts the FixedUInt into a little-endian byte array.
477    pub fn to_le_bytes<'a>(&self, output_buffer: &'a mut [u8]) -> Result<&'a [u8], bool> {
478        let total_bytes = N * Self::WORD_SIZE;
479        if output_buffer.len() < total_bytes {
480            return Err(false); // Buffer too small
481        }
482        for (i, word) in self.array.iter().enumerate() {
483            let start = i * Self::WORD_SIZE;
484            let end = start + Self::WORD_SIZE;
485            let word_bytes = word.to_le_bytes();
486            output_buffer[start..end].copy_from_slice(word_bytes.as_ref());
487        }
488        Ok(&output_buffer[..total_bytes])
489    }
490
491    /// Converts the FixedUInt into a big-endian byte array.
492    pub fn to_be_bytes<'a>(&self, output_buffer: &'a mut [u8]) -> Result<&'a [u8], bool> {
493        let total_bytes = N * Self::WORD_SIZE;
494        if output_buffer.len() < total_bytes {
495            return Err(false); // Buffer too small
496        }
497        for (i, word) in self.array.iter().rev().enumerate() {
498            let start = i * Self::WORD_SIZE;
499            let end = start + Self::WORD_SIZE;
500            let word_bytes = word.to_be_bytes();
501            output_buffer[start..end].copy_from_slice(word_bytes.as_ref());
502        }
503        Ok(&output_buffer[..total_bytes])
504    }
505
506    /// Converts to hex string, given a buffer. CAVEAT: This method removes any leading zeroes
507    pub fn to_hex_str<'a>(&self, result: &'a mut [u8]) -> Result<&'a str, core::fmt::Error> {
508        type Error = core::fmt::Error;
509
510        let word_size = Self::WORD_SIZE;
511        // need length minus leading zeros
512        let need_bits = self.bit_length() as usize;
513        // number of needed characters (bits/4 = bytes * 2)
514        let need_chars = if need_bits > 0 { need_bits / 4 } else { 0 };
515
516        if result.len() < need_chars {
517            // not enough space in result...
518            return Err(Error {});
519        }
520        let offset = result.len() - need_chars;
521        for i in result.iter_mut() {
522            *i = b'0';
523        }
524
525        for iter_words in 0..self.array.len() {
526            let word = self.array[iter_words];
527            let mut encoded = [0u8; LONGEST_WORD_IN_BITS / 4];
528            let encode_slice = &mut encoded[0..word_size * 2];
529            let mut wordbytes = word.to_le_bytes();
530            wordbytes.as_mut().reverse();
531            let wordslice = wordbytes.as_ref();
532            to_slice_hex(wordslice, encode_slice).map_err(|_| Error {})?;
533            for iter_chars in 0..encode_slice.len() {
534                let copy_char_to = (iter_words * word_size * 2) + iter_chars;
535                if copy_char_to <= need_chars {
536                    let reverse_index = offset + (need_chars - copy_char_to);
537                    if reverse_index <= result.len() && reverse_index > 0 {
538                        let current_char = encode_slice[(encode_slice.len() - 1) - iter_chars];
539                        result[reverse_index - 1] = current_char;
540                    }
541                }
542            }
543        }
544
545        let convert = core::str::from_utf8(result).map_err(|_| Error {})?;
546        let pos = convert.find(|c: char| c != '0');
547        match pos {
548            Some(x) => Ok(&convert[x..convert.len()]),
549            None => {
550                if convert.starts_with('0') {
551                    Ok("0")
552                } else {
553                    Ok(convert)
554                }
555            }
556        }
557    }
558
559    /// Construct a new value with a different size.
560    ///
561    /// - If `N2 < N`, the most-significant (upper) words are truncated.
562    /// - If `N2 > N`, the additional most-significant words are filled with zeros.
563    #[must_use]
564    pub fn resize<const N2: usize>(&self) -> FixedUInt<T, N2, P> {
565        let mut array = [T::zero(); N2];
566        let min_size = N.min(N2);
567        array[..min_size].copy_from_slice(&self.array[..min_size]);
568        FixedUInt::<T, N2, P>::from_array(array)
569    }
570
571    fn hex_fmt(
572        &self,
573        formatter: &mut core::fmt::Formatter<'_>,
574        uppercase: bool,
575    ) -> Result<(), core::fmt::Error>
576    where
577        u8: core::convert::TryFrom<T>,
578    {
579        type Err = core::fmt::Error;
580
581        fn to_casedigit(byte: u8, uppercase: bool) -> Result<char, core::fmt::Error> {
582            let digit = core::char::from_digit(byte as u32, 16).ok_or(Err {})?;
583            if uppercase {
584                digit.to_uppercase().next().ok_or(Err {})
585            } else {
586                digit.to_lowercase().next().ok_or(Err {})
587            }
588        }
589
590        let mut leading_zero: bool = true;
591
592        let mut maybe_write = |nibble: char| -> Result<(), core::fmt::Error> {
593            leading_zero &= nibble == '0';
594            if !leading_zero {
595                formatter.write_char(nibble)?;
596            }
597            Ok(())
598        };
599
600        for index in (0..N).rev() {
601            let val = self.array[index];
602            let mask: T = 0xff.into();
603            for j in (0..Self::WORD_SIZE as u32).rev() {
604                let masked = val & mask.shl((j * 8) as usize);
605
606                let byte = u8::try_from(masked.shr((j * 8) as usize)).map_err(|_| Err {})?;
607
608                maybe_write(to_casedigit((byte & 0xf0) >> 4, uppercase)?)?;
609                maybe_write(to_casedigit(byte & 0x0f, uppercase)?)?;
610            }
611        }
612        Ok(())
613    }
614}
615
616c0nst::c0nst! {
617    /// Single canonical limb-wise add-with-carry over a fixed-width array.
618    /// CT under `Ct`-personality callers: iteration count is `N`, the inner
619    /// `ConstCarryingAdd::carrying_add` lowers to a hardware ADC, and no
620    /// step branches on the data.
621    pub(crate) c0nst fn add_with_carry<T: [c0nst] ConstMachineWord, const N: usize>(
622        a: &[T; N],
623        b: &[T; N],
624        carry_in: bool,
625    ) -> ([T; N], bool) {
626        let mut result = [T::zero(); N];
627        let mut carry = carry_in;
628        let mut i = 0usize;
629        while i < N {
630            let (sum, c) = ConstCarryingAdd::carrying_add(a[i], b[i], carry);
631            result[i] = sum;
632            carry = c;
633            i += 1;
634        }
635        (result, carry)
636    }
637
638    /// Mirror of `add_with_carry` for subtraction.
639    pub(crate) c0nst fn sub_with_borrow<T: [c0nst] ConstMachineWord, const N: usize>(
640        a: &[T; N],
641        b: &[T; N],
642        borrow_in: bool,
643    ) -> ([T; N], bool) {
644        let mut result = [T::zero(); N];
645        let mut borrow = borrow_in;
646        let mut i = 0usize;
647        while i < N {
648            let (diff, br) = ConstBorrowingSub::borrowing_sub(a[i], b[i], borrow);
649            result[i] = diff;
650            borrow = br;
651            i += 1;
652        }
653        (result, borrow)
654    }
655
656    /// In-place limb-wise add, no carry-in. Same per-limb primitive
657    /// (`ConstCarryingAdd::carrying_add`) as `add_with_carry`, just writing
658    /// directly to `target` to avoid a stack-allocated temp array that
659    /// LLVM might not always elide on embedded builds.
660    pub(crate) c0nst fn add_impl<T: [c0nst] ConstMachineWord, const N: usize>(
661        target: &mut [T; N],
662        other: &[T; N]
663    ) -> bool {
664        let mut carry = false;
665        let mut i = 0usize;
666        while i < N {
667            let (sum, c) = ConstCarryingAdd::carrying_add(target[i], other[i], carry);
668            target[i] = sum;
669            carry = c;
670            i += 1;
671        }
672        carry
673    }
674
675    /// In-place limb-wise sub, no borrow-in. Mirror of `add_impl`.
676    pub(crate) c0nst fn sub_impl<T: [c0nst] ConstMachineWord, const N: usize>(
677        target: &mut [T; N],
678        other: &[T; N]
679    ) -> bool {
680        let mut borrow = false;
681        let mut i = 0usize;
682        while i < N {
683            let (diff, br) = ConstBorrowingSub::borrowing_sub(target[i], other[i], borrow);
684            target[i] = diff;
685            borrow = br;
686            i += 1;
687        }
688        borrow
689    }
690}
691
692c0nst::c0nst! {
693    /// Const-compatible left shift implementation
694    pub(crate) c0nst fn const_shl_impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(
695        target: &mut FixedUInt<T, N, P>,
696        bits: usize,
697    ) {
698        if N == 0 {
699            return;
700        }
701        let word_bits = FixedUInt::<T, N>::WORD_BITS;
702        let nwords = bits / word_bits;
703        let nbits = bits - nwords * word_bits;
704
705        // If shift >= total bits, result is zero
706        if nwords >= N {
707            let mut i = 0;
708            while i < N {
709                target.array[i] = T::zero();
710                i += 1;
711            }
712            return;
713        }
714
715        // Move words (backwards)
716        let mut i = N;
717        while i > nwords {
718            i -= 1;
719            target.array[i] = target.array[i - nwords];
720        }
721        // Zero out the lower words
722        let mut i = 0;
723        while i < nwords {
724            target.array[i] = T::zero();
725            i += 1;
726        }
727
728        if nbits != 0 {
729            // Shift remaining bits (backwards)
730            let mut i = N;
731            while i > 1 {
732                i -= 1;
733                let right = target.array[i] << nbits;
734                let left = target.array[i - 1] >> (word_bits - nbits);
735                target.array[i] = right | left;
736            }
737            target.array[0] <<= nbits;
738        }
739    }
740
741    /// Const-compatible right shift implementation
742    pub(crate) c0nst fn const_shr_impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(
743        target: &mut FixedUInt<T, N, P>,
744        bits: usize,
745    ) {
746        if N == 0 {
747            return;
748        }
749        let word_bits = FixedUInt::<T, N>::WORD_BITS;
750        let nwords = bits / word_bits;
751        let nbits = bits - nwords * word_bits;
752
753        // If shift >= total bits, result is zero
754        if nwords >= N {
755            let mut i = 0;
756            while i < N {
757                target.array[i] = T::zero();
758                i += 1;
759            }
760            return;
761        }
762
763        let last_index = N - 1;
764        let last_word = N - nwords;
765
766        // Move words (forwards)
767        let mut i = 0;
768        while i < last_word {
769            target.array[i] = target.array[i + nwords];
770            i += 1;
771        }
772
773        // Zero out the upper words
774        let mut i = last_word;
775        while i < N {
776            target.array[i] = T::zero();
777            i += 1;
778        }
779
780        if nbits != 0 {
781            // Shift remaining bits (forwards)
782            let mut i = 0;
783            while i < last_index {
784                let left = target.array[i] >> nbits;
785                let right = target.array[i + 1] << (word_bits - nbits);
786                target.array[i] = left | right;
787                i += 1;
788            }
789            target.array[last_index] >>= nbits;
790        }
791    }
792
793    /// CT variant of `const_shl_impl`: barrel shifter. Iterates every
794    /// bit position of `bits` from 0 to `usize::BITS - 1`. At each
795    /// layer k, computes `target << 2^k` (via `const_shl_impl` with a
796    /// publicly-known power-of-two amount — non-CT internally but the
797    /// amount is *not* secret) and CT-selects per-limb between the
798    /// shifted and unshifted forms based on bit k of `bits`. Runtime
799    /// is O(N * usize::BITS), independent of the secret shift amount.
800    /// Used by the `Ct`-personality arm of `Shl<usize>` / `Shl<u32>`.
801    pub(crate) c0nst fn const_shl_ct<
802        T: [c0nst] ConstMachineWord + MachineWord,
803        const N: usize,
804        P: Personality,
805    >(
806        target: &mut FixedUInt<T, N, P>,
807        bits: usize,
808    ) {
809        if N == 0 {
810            return;
811        }
812        // `layers == usize::BITS`, so `k < layers` guarantees `1usize << k`
813        // stays in range. Do not raise this bound without revisiting the shift.
814        let layers = core::mem::size_of::<usize>() * 8;
815        let mut k = 0;
816        while k < layers {
817            let amount = 1usize << k;
818            // Build the "shifted by 2^k" candidate without mutating target.
819            let mut shifted = *target;
820            const_shl_impl(&mut shifted, amount);
821            // Spread bit k of `bits` to a full-T mask: 0 if cleared, T::MAX if set.
822            // `black_box` is load-bearing — see `const_ct_select` for the
823            // address-select rewrite it defeats.
824            let bit_k = core::hint::black_box(((bits >> k) & 1) as u8);
825            let bit_k_t = <T as core::convert::From<u8>>::from(bit_k);
826            let mask = <T as core::ops::Mul>::mul(bit_k_t, <T as ConstBounded>::max_value());
827            // CT-select per limb: target[i] ^= mask & (target[i] ^ shifted[i])
828            let mut i = 0;
829            while i < N {
830                let diff =
831                    <T as core::ops::BitXor>::bitxor(target.array[i], shifted.array[i]);
832                let masked = <T as core::ops::BitAnd>::bitand(mask, diff);
833                target.array[i] = <T as core::ops::BitXor>::bitxor(target.array[i], masked);
834                i += 1;
835            }
836            k += 1;
837        }
838    }
839
840    /// CT variant of `const_shr_impl`: barrel shifter, mirror of
841    /// `const_shl_ct`. See that helper for the design rationale.
842    pub(crate) c0nst fn const_shr_ct<
843        T: [c0nst] ConstMachineWord + MachineWord,
844        const N: usize,
845        P: Personality,
846    >(
847        target: &mut FixedUInt<T, N, P>,
848        bits: usize,
849    ) {
850        if N == 0 {
851            return;
852        }
853        // See `const_shl_ct`: `layers == usize::BITS` keeps `1usize << k`
854        // in range.
855        let layers = core::mem::size_of::<usize>() * 8;
856        let mut k = 0;
857        while k < layers {
858            let amount = 1usize << k;
859            let mut shifted = *target;
860            const_shr_impl(&mut shifted, amount);
861            // See `const_shl_ct` / `const_ct_select` for why `black_box` is here.
862            let bit_k = core::hint::black_box(((bits >> k) & 1) as u8);
863            let bit_k_t = <T as core::convert::From<u8>>::from(bit_k);
864            let mask = <T as core::ops::Mul>::mul(bit_k_t, <T as ConstBounded>::max_value());
865            let mut i = 0;
866            while i < N {
867                let diff =
868                    <T as core::ops::BitXor>::bitxor(target.array[i], shifted.array[i]);
869                let masked = <T as core::ops::BitAnd>::bitand(mask, diff);
870                target.array[i] = <T as core::ops::BitXor>::bitxor(target.array[i], masked);
871                i += 1;
872            }
873            k += 1;
874        }
875    }
876
877    /// Standalone const-compatible array multiplication (no FixedUInt dependency).
878    /// Returns (result_array, overflowed).
879    ///
880    /// The carry split (`accumulator > t_max ? ... : 0`) dispatches on
881    /// personality. Nct keeps the original predictable branch (the fast
882    /// path skips the shift+mask when the sum already fits in one word);
883    /// Ct does the shift+mask unconditionally so the body has no
884    /// value-dependent branch. Overflow accumulation is branchless (`|` on
885    /// bools) under both personalities since the per-step cost is tiny.
886    pub(crate) c0nst fn const_mul<T: [c0nst] ConstMachineWord, const N: usize, const CHECK_OVERFLOW: bool, P: Personality>(
887        op1: &[T; N],
888        op2: &[T; N],
889        word_bits: usize,
890    ) -> ([T; N], bool) {
891        let mut result: [T; N] = [<T as ConstZero>::zero(); N];
892        let mut overflowed = false;
893        let t_max = <T as ConstMachineWord>::to_double(<T as ConstBounded>::max_value());
894        let dw_zero = <<T as ConstMachineWord>::ConstDoubleWord as ConstZero>::zero();
895
896        let mut i = 0;
897        while i < N {
898            let mut carry = dw_zero;
899            let mut j = 0;
900            while j < N {
901                let round = i + j;
902                let op1_dw = <T as ConstMachineWord>::to_double(op1[i]);
903                let op2_dw = <T as ConstMachineWord>::to_double(op2[j]);
904                let mul_res = op1_dw * op2_dw;
905                let mut accumulator = if round < N {
906                    <T as ConstMachineWord>::to_double(result[round])
907                } else {
908                    dw_zero
909                };
910                accumulator += mul_res + carry;
911
912                match P::TAG {
913                    PersonalityTag::Nct => {
914                        if accumulator > t_max {
915                            carry = accumulator >> word_bits;
916                            accumulator &= t_max;
917                        } else {
918                            carry = dw_zero;
919                        }
920                    }
921                    PersonalityTag::Ct => {
922                        carry = accumulator >> word_bits;
923                        accumulator &= t_max;
924                    }
925                }
926                if round < N {
927                    result[round] = <T as ConstMachineWord>::from_double(accumulator);
928                } else if CHECK_OVERFLOW {
929                    overflowed |= accumulator != dw_zero;
930                }
931                j += 1;
932            }
933            if CHECK_OVERFLOW {
934                overflowed |= carry != dw_zero;
935            }
936            i += 1;
937        }
938        (result, overflowed)
939    }
940
941    /// Get the bit width of a word type.
942    pub(crate) c0nst fn const_word_bits<T>() -> usize {
943        core::mem::size_of::<T>() * 8
944    }
945
946    /// Compare two words, returning Some(ordering) if not equal, None if equal.
947    pub(crate) c0nst fn const_cmp_words<T: [c0nst] ConstMachineWord>(a: T, b: T) -> Option<core::cmp::Ordering> {
948        if a > b {
949            Some(core::cmp::Ordering::Greater)
950        } else if a < b {
951            Some(core::cmp::Ordering::Less)
952        } else {
953            None
954        }
955    }
956
957    /// Count leading zeros in a const-compatible way
958    pub(crate) c0nst fn const_leading_zeros<T: [c0nst] ConstMachineWord, const N: usize>(
959        array: &[T; N],
960    ) -> u32 {
961        let mut ret = 0u32;
962        let mut index = N;
963        while index > 0 {
964            index -= 1;
965            let v = array[index];
966            ret += <T as ConstBitPrimInt>::leading_zeros(v);
967            if !<T as ConstZero>::is_zero(&v) {
968                break;
969            }
970        }
971        ret
972    }
973
974    /// CT variant of `const_leading_zeros`: scans every limb without
975    /// short-circuiting. A bitmask tracks whether we're still in the
976    /// leading-zero region; once a non-zero limb is seen, subsequent
977    /// limbs contribute 0 to the total. Used by the `Ct`-personality
978    /// arm of `ConstBitPrimInt::leading_zeros`. Branchless apart from a
979    /// `bool -> u32` cast that rustc compiles to a setne.
980    pub(crate) c0nst fn const_leading_zeros_ct<T: [c0nst] ConstMachineWord, const N: usize>(
981        array: &[T; N],
982    ) -> u32 {
983        let mut total: u32 = 0;
984        // 0 while still in leading-zero region; u32::MAX once a non-zero limb is seen.
985        let mut decided: u32 = 0;
986        let mut index = N;
987        while index > 0 {
988            index -= 1;
989            let v = array[index];
990            let v_lz = <T as ConstBitPrimInt>::leading_zeros(v);
991            // Add this limb's lz contribution iff we haven't decided yet.
992            // `black_box` defeats the LLVM XOR/AND-select → cmov rewrite —
993            // see `const_ct_select` for the load-bearing explanation.
994            let undecided = core::hint::black_box(!decided);
995            total += undecided & v_lz;
996            // Lock the decision the moment we see a non-zero limb.
997            let v_nz_bit = (!<T as ConstZero>::is_zero(&v)) as u32;
998            let v_nz_mask = core::hint::black_box(v_nz_bit.wrapping_neg());
999            decided |= v_nz_mask;
1000        }
1001        total
1002    }
1003
1004    /// Count trailing zeros in a const-compatible way
1005    pub(crate) c0nst fn const_trailing_zeros<T: [c0nst] ConstMachineWord, const N: usize>(
1006        array: &[T; N],
1007    ) -> u32 {
1008        let mut ret = 0u32;
1009        let mut index = 0;
1010        while index < N {
1011            let v = array[index];
1012            ret += <T as ConstBitPrimInt>::trailing_zeros(v);
1013            if !<T as ConstZero>::is_zero(&v) {
1014                break;
1015            }
1016            index += 1;
1017        }
1018        ret
1019    }
1020
1021    /// CT variant of `const_trailing_zeros`: scans LSB-to-MSB without
1022    /// short-circuiting. Mirror of `const_leading_zeros_ct` — see that
1023    /// helper for the rationale. Used by the `Ct`-personality arm of
1024    /// `ConstBitPrimInt::trailing_zeros`.
1025    pub(crate) c0nst fn const_trailing_zeros_ct<T: [c0nst] ConstMachineWord, const N: usize>(
1026        array: &[T; N],
1027    ) -> u32 {
1028        let mut total: u32 = 0;
1029        // 0 while still in trailing-zero region; u32::MAX once a non-zero limb is seen.
1030        let mut decided: u32 = 0;
1031        let mut index = 0;
1032        while index < N {
1033            let v = array[index];
1034            let v_tz = <T as ConstBitPrimInt>::trailing_zeros(v);
1035            // See `const_leading_zeros_ct` / `const_ct_select` for why
1036            // `black_box` is here.
1037            let undecided = core::hint::black_box(!decided);
1038            total += undecided & v_tz;
1039            let v_nz_bit = (!<T as ConstZero>::is_zero(&v)) as u32;
1040            let v_nz_mask = core::hint::black_box(v_nz_bit.wrapping_neg());
1041            decided |= v_nz_mask;
1042            index += 1;
1043        }
1044        total
1045    }
1046
1047    /// Get bit length of array (total bits - leading zeros)
1048    pub(crate) c0nst fn const_bit_length<T: [c0nst] ConstMachineWord, const N: usize>(
1049        array: &[T; N],
1050    ) -> usize {
1051        let word_bits = const_word_bits::<T>();
1052        let bit_size = N * word_bits;
1053        bit_size - const_leading_zeros::<T, N>(array) as usize
1054    }
1055
1056    /// Check if array is zero
1057    pub(crate) c0nst fn const_is_zero<T: [c0nst] ConstMachineWord, const N: usize>(
1058        array: &[T; N],
1059    ) -> bool {
1060        let mut index = 0;
1061        while index < N {
1062            if !<T as ConstZero>::is_zero(&array[index]) {
1063                return false;
1064            }
1065            index += 1;
1066        }
1067        true
1068    }
1069
1070    /// CT variant of `const_is_zero`: OR-folds all N limbs into one accumulator
1071    /// before checking, so timing is uniform regardless of where (or whether)
1072    /// a non-zero limb appears. Used by the `Ct`-personality arm of
1073    /// `ConstZero::is_zero`.
1074    pub(crate) c0nst fn const_is_zero_ct<T: [c0nst] ConstMachineWord, const N: usize>(
1075        array: &[T; N],
1076    ) -> bool {
1077        let mut acc = <T as ConstZero>::zero();
1078        let mut index = 0;
1079        while index < N {
1080            acc = <T as core::ops::BitOr>::bitor(acc, array[index]);
1081            index += 1;
1082        }
1083        <T as ConstZero>::is_zero(&acc)
1084    }
1085
1086    /// Check if array is one. Short-circuits as soon as a non-matching limb
1087    /// is found, so timing leaks where the array first deviates from the
1088    /// canonical "one" representation. Used by the `Nct`-personality arm of
1089    /// `ConstOne::is_one`.
1090    pub(crate) c0nst fn const_is_one<T: [c0nst] ConstMachineWord, const N: usize>(
1091        array: &[T; N],
1092    ) -> bool {
1093        if N == 0 || !array[0].is_one() {
1094            return false;
1095        }
1096        let mut i = 1;
1097        while i < N {
1098            if !<T as ConstZero>::is_zero(&array[i]) {
1099                return false;
1100            }
1101            i += 1;
1102        }
1103        true
1104    }
1105
1106    /// CT variant of `const_is_one`: folds `(array[0] ^ 1) | array[1] | ...`
1107    /// into one accumulator before checking, so timing does not depend on
1108    /// *where* the array first differs from the canonical "one"
1109    /// representation. Used by the `Ct`-personality arm of `ConstOne::is_one`.
1110    pub(crate) c0nst fn const_is_one_ct<T: [c0nst] ConstMachineWord, const N: usize>(
1111        array: &[T; N],
1112    ) -> bool {
1113        if N == 0 {
1114            return false;
1115        }
1116        let mut acc = <T as core::ops::BitXor>::bitxor(array[0], <T as ConstOne>::one());
1117        let mut index = 1;
1118        while index < N {
1119            acc = <T as core::ops::BitOr>::bitor(acc, array[index]);
1120            index += 1;
1121        }
1122        <T as ConstZero>::is_zero(&acc)
1123    }
1124
1125    /// Set a specific bit in the array.
1126    ///
1127    /// The array uses little-endian representation where index 0 contains
1128    /// the least significant word, and bit 0 is the least significant bit
1129    /// of the entire integer.
1130    pub(crate) c0nst fn const_set_bit<T: [c0nst] ConstMachineWord, const N: usize>(
1131        array: &mut [T; N],
1132        pos: usize,
1133    ) {
1134        let word_bits = const_word_bits::<T>();
1135        let word_idx = pos / word_bits;
1136        if word_idx >= N {
1137            return;
1138        }
1139        let bit_idx = pos % word_bits;
1140        array[word_idx] |= <T as ConstOne>::one() << bit_idx;
1141    }
1142
1143    /// Compare two arrays in a const-compatible way.
1144    ///
1145    /// Arrays use little-endian representation where index 0 contains
1146    /// the least significant word.
1147    pub(crate) c0nst fn const_cmp<T: [c0nst] ConstMachineWord, const N: usize>(
1148        a: &[T; N],
1149        b: &[T; N],
1150    ) -> core::cmp::Ordering {
1151        let mut index = N;
1152        while index > 0 {
1153            index -= 1;
1154            if let Some(ord) = const_cmp_words(a[index], b[index]) {
1155                return ord;
1156            }
1157        }
1158        core::cmp::Ordering::Equal
1159    }
1160
1161    /// CT variant of `const_cmp`: scans every limb from high to low without
1162    /// short-circuiting; once the first differing limb is seen, subsequent
1163    /// limbs cannot overturn the locked decision. Used by the `Ct`-personality
1164    /// arm of `Ord::cmp` (and therefore `PartialOrd::partial_cmp`).
1165    pub(crate) c0nst fn const_cmp_ct<T: [c0nst] ConstMachineWord, const N: usize>(
1166        a: &[T; N],
1167        b: &[T; N],
1168    ) -> core::cmp::Ordering {
1169        // result encoding: 2 = Greater, 1 = Less, 0 = Equal.
1170        let mut result: u8 = 0;
1171        // 0 while still undecided; u8::MAX once a differing limb has been seen.
1172        let mut decided: u8 = 0;
1173        let mut index = N;
1174        while index > 0 {
1175            index -= 1;
1176            let gt = (a[index] > b[index]) as u8;
1177            let lt = (a[index] < b[index]) as u8;
1178            // here ∈ {0, 1, 2}: 2 for Greater, 1 for Less, 0 for Equal.
1179            let here = (gt << 1) | lt;
1180            // See `const_ct_select` for why `black_box` is here.
1181            let undecided_mask = core::hint::black_box(!decided);
1182            result |= undecided_mask & here;
1183            // Lock the decision the moment a non-zero `here` is observed.
1184            let here_nz_mask = core::hint::black_box(((here != 0) as u8).wrapping_neg());
1185            decided |= here_nz_mask;
1186        }
1187        match result {
1188            2 => core::cmp::Ordering::Greater,
1189            1 => core::cmp::Ordering::Less,
1190            _ => core::cmp::Ordering::Equal,
1191        }
1192    }
1193
1194    /// Get the value of array's word at position `word_idx` when logically shifted left.
1195    ///
1196    /// This helper computes what value would be at `word_idx` if the array
1197    /// were shifted left by `word_shift` words plus `bit_shift` bits.
1198    pub(crate) c0nst fn const_get_shifted_word<T: [c0nst] ConstMachineWord, const N: usize>(
1199        array: &[T; N],
1200        word_idx: usize,
1201        word_shift: usize,
1202        bit_shift: usize,
1203    ) -> T {
1204        let word_bits = const_word_bits::<T>();
1205
1206        // Guard against invalid bit_shift that would cause UB
1207        if bit_shift >= word_bits {
1208            return <T as ConstZero>::zero();
1209        }
1210
1211        if word_idx < word_shift {
1212            return <T as ConstZero>::zero();
1213        }
1214
1215        let source_idx = word_idx - word_shift;
1216
1217        if bit_shift == 0 {
1218            if source_idx < N {
1219                array[source_idx]
1220            } else {
1221                <T as ConstZero>::zero()
1222            }
1223        } else {
1224            let mut result = <T as ConstZero>::zero();
1225
1226            // Get bits from the primary source word
1227            if source_idx < N {
1228                result |= array[source_idx] << bit_shift;
1229            }
1230
1231            // Get high bits from the next lower word (if it exists)
1232            if source_idx > 0 && source_idx - 1 < N {
1233                let high_bits = array[source_idx - 1] >> (word_bits - bit_shift);
1234                result |= high_bits;
1235            }
1236
1237            result
1238        }
1239    }
1240
1241    /// Compare array vs (other << shift_bits) in a const-compatible way.
1242    ///
1243    /// This is useful for division algorithms where we need to compare
1244    /// the dividend against a shifted divisor without allocating.
1245    pub(crate) c0nst fn const_cmp_shifted<T: [c0nst] ConstMachineWord, const N: usize>(
1246        array: &[T; N],
1247        other: &[T; N],
1248        shift_bits: usize,
1249    ) -> core::cmp::Ordering {
1250        let word_bits = const_word_bits::<T>();
1251
1252        if shift_bits == 0 {
1253            return const_cmp::<T, N>(array, other);
1254        }
1255
1256        let word_shift = shift_bits / word_bits;
1257        if word_shift >= N {
1258            // other << shift_bits would overflow to 0
1259            if const_is_zero::<T, N>(array) {
1260                return core::cmp::Ordering::Equal;
1261            } else {
1262                return core::cmp::Ordering::Greater;
1263            }
1264        }
1265
1266        let bit_shift = shift_bits % word_bits;
1267
1268        // Compare from most significant words down
1269        let mut index = N;
1270        while index > 0 {
1271            index -= 1;
1272            let self_word = array[index];
1273            let other_shifted_word = const_get_shifted_word::<T, N>(
1274                other, index, word_shift, bit_shift
1275            );
1276
1277            if let Some(ord) = const_cmp_words(self_word, other_shifted_word) {
1278                return ord;
1279            }
1280        }
1281
1282        core::cmp::Ordering::Equal
1283    }
1284
1285    /// Subtract (other << shift_bits) from array in-place.
1286    ///
1287    /// This is used in division algorithms to subtract shifted divisor
1288    /// from the remainder without allocating.
1289    pub(crate) c0nst fn const_sub_shifted<T: [c0nst] ConstMachineWord, const N: usize>(
1290        array: &mut [T; N],
1291        other: &[T; N],
1292        shift_bits: usize,
1293    ) {
1294        let word_bits = const_word_bits::<T>();
1295
1296        if shift_bits == 0 {
1297            sub_impl::<T, N>(array, other);
1298            return;
1299        }
1300
1301        let word_shift = shift_bits / word_bits;
1302        if word_shift >= N {
1303            return;
1304        }
1305
1306        let bit_shift = shift_bits % word_bits;
1307        let mut borrow = T::zero();
1308        let mut index = 0;
1309        while index < N {
1310            let other_word = const_get_shifted_word::<T, N>(other, index, word_shift, bit_shift);
1311            let (res, borrow1) = array[index].overflowing_sub(&other_word);
1312            let (res, borrow2) = res.overflowing_sub(&borrow);
1313            borrow = if borrow1 || borrow2 { T::one() } else { T::zero() };
1314            array[index] = res;
1315            index += 1;
1316        }
1317    }
1318
1319    /// In-place division: dividend becomes quotient, returns remainder.
1320    ///
1321    /// Low-level const-compatible division on arrays.
1322    pub(crate) c0nst fn const_div<T: [c0nst] ConstMachineWord, const N: usize>(
1323        dividend: &mut [T; N],
1324        divisor: &[T; N],
1325    ) -> [T; N] {
1326        use core::cmp::Ordering;
1327
1328        match const_cmp::<T, N>(dividend, divisor) {
1329            // dividend < divisor: quotient = 0, remainder = dividend
1330            Ordering::Less => {
1331                let remainder = *dividend;
1332                let mut i = 0;
1333                while i < N {
1334                    dividend[i] = <T as ConstZero>::zero();
1335                    i += 1;
1336                }
1337                return remainder;
1338            }
1339            // dividend == divisor: quotient = 1, remainder = 0
1340            Ordering::Equal => {
1341                let mut i = 0;
1342                while i < N {
1343                    dividend[i] = <T as ConstZero>::zero();
1344                    i += 1;
1345                }
1346                if N > 0 {
1347                    dividend[0] = <T as ConstOne>::one();
1348                }
1349                return [<T as ConstZero>::zero(); N];
1350            }
1351            Ordering::Greater => {}
1352        }
1353
1354        let mut quotient = [<T as ConstZero>::zero(); N];
1355
1356        // Calculate initial bit position
1357        let dividend_bits = const_bit_length::<T, N>(dividend);
1358        let divisor_bits = const_bit_length::<T, N>(divisor);
1359
1360        let mut bit_pos = if dividend_bits >= divisor_bits {
1361            dividend_bits - divisor_bits
1362        } else {
1363            0
1364        };
1365
1366        // Adjust bit position to find the first position where divisor can be subtracted
1367        while bit_pos > 0 {
1368            let cmp = const_cmp_shifted::<T, N>(dividend, divisor, bit_pos);
1369            if !matches!(cmp, Ordering::Less) {
1370                break;
1371            }
1372            bit_pos -= 1;
1373        }
1374
1375        // Main division loop
1376        loop {
1377            let cmp = const_cmp_shifted::<T, N>(dividend, divisor, bit_pos);
1378            if !matches!(cmp, Ordering::Less) {
1379                const_sub_shifted::<T, N>(dividend, divisor, bit_pos);
1380                const_set_bit::<T, N>(&mut quotient, bit_pos);
1381            }
1382
1383            if bit_pos == 0 {
1384                break;
1385            }
1386            bit_pos -= 1;
1387        }
1388
1389        let remainder = *dividend;
1390        *dividend = quotient;
1391        remainder
1392    }
1393
1394    /// Const-compatible div_rem: returns (quotient, remainder).
1395    ///
1396    /// Panics on divide by zero.
1397    pub(crate) c0nst fn const_div_rem<T: [c0nst] ConstMachineWord, const N: usize>(
1398        dividend: &[T; N],
1399        divisor: &[T; N],
1400    ) -> ([T; N], [T; N]) {
1401        if const_is_zero(divisor) {
1402            maybe_panic(PanicReason::DivByZero)
1403        }
1404        let mut quotient = *dividend;
1405        let remainder = const_div(&mut quotient, divisor);
1406        (quotient, remainder)
1407    }
1408}
1409
1410c0nst::c0nst! {
1411    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> Default for FixedUInt<T, N, P> {
1412        fn default() -> Self {
1413            FixedUInt::from_array([<T as ConstZero>::zero(); N])
1414        }
1415    }
1416
1417    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> Clone for FixedUInt<T, N, P> {
1418        fn clone(&self) -> Self {
1419            *self
1420        }
1421    }
1422}
1423
1424// num_traits::Unsigned requires Num as a supertrait; Num is Nct-only,
1425// so Unsigned is Nct-only too.
1426impl<T: MachineWord, const N: usize> num_traits::Unsigned for FixedUInt<T, N, Nct> {}
1427
1428// #region Equality and Ordering
1429
1430c0nst::c0nst! {
1431    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::cmp::PartialEq for FixedUInt<T, N, P> {
1432        fn eq(&self, other: &Self) -> bool {
1433            match P::TAG {
1434                PersonalityTag::Nct => self.array == other.array,
1435                PersonalityTag::Ct => {
1436                    let mut diff = <T as crate::const_numtraits::ConstZero>::zero();
1437                    let mut i = 0;
1438                    while i < N {
1439                        let x = <T as core::ops::BitXor>::bitxor(self.array[i], other.array[i]);
1440                        diff = <T as core::ops::BitOr>::bitor(diff, x);
1441                        i += 1;
1442                    }
1443                    <T as crate::const_numtraits::ConstZero>::is_zero(&diff)
1444                }
1445            }
1446        }
1447    }
1448
1449    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::cmp::Eq for FixedUInt<T, N, P> {}
1450
1451    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::cmp::Ord for FixedUInt<T, N, P> {
1452        fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1453            match P::TAG {
1454                PersonalityTag::Nct => const_cmp(&self.array, &other.array),
1455                PersonalityTag::Ct => const_cmp_ct(&self.array, &other.array),
1456            }
1457        }
1458    }
1459
1460    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::cmp::PartialOrd for FixedUInt<T, N, P> {
1461        fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
1462            Some(self.cmp(other))
1463        }
1464    }
1465}
1466
1467// #endregion Equality and Ordering
1468
1469// #region core::convert::From<primitive>
1470
1471c0nst::c0nst! {
1472    /// Const-compatible conversion from little-endian bytes to array of words.
1473    /// Delegates to impl_from_le_bytes_slice to avoid code duplication.
1474    c0nst fn const_from_le_bytes<T: [c0nst] ConstMachineWord, const N: usize, const B: usize>(
1475        bytes: [u8; B],
1476    ) -> [T; N] {
1477        impl_from_le_bytes_slice::<T, N>(&bytes)
1478    }
1479
1480    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::convert::From<u8> for FixedUInt<T, N, P> {
1481        fn from(x: u8) -> Self {
1482            Self::from_array(const_from_le_bytes(x.to_le_bytes()))
1483        }
1484    }
1485
1486    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::convert::From<u16> for FixedUInt<T, N, P> {
1487        fn from(x: u16) -> Self {
1488            Self::from_array(const_from_le_bytes(x.to_le_bytes()))
1489        }
1490    }
1491
1492    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::convert::From<u32> for FixedUInt<T, N, P> {
1493        fn from(x: u32) -> Self {
1494            Self::from_array(const_from_le_bytes(x.to_le_bytes()))
1495        }
1496    }
1497
1498    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::convert::From<u64> for FixedUInt<T, N, P> {
1499        fn from(x: u64) -> Self {
1500            Self::from_array(const_from_le_bytes(x.to_le_bytes()))
1501        }
1502    }
1503}
1504
1505// #endregion core::convert::From<primitive>
1506
1507// #region helpers
1508
1509// This is slightly less than ideal, but PIE isn't directly constructible
1510// due to unstable members.
1511fn make_parse_int_err() -> core::num::ParseIntError {
1512    <u8>::from_str_radix("-", 2).err().unwrap()
1513}
1514fn make_overflow_err() -> core::num::ParseIntError {
1515    <u8>::from_str_radix("101", 16).err().unwrap()
1516}
1517fn make_empty_error() -> core::num::ParseIntError {
1518    <u8>::from_str_radix("", 8).err().unwrap()
1519}
1520
1521fn to_slice_hex<T: AsRef<[u8]>>(
1522    input: T,
1523    output: &mut [u8],
1524) -> Result<(), core::num::ParseIntError> {
1525    fn from_digit(byte: u8) -> Option<char> {
1526        core::char::from_digit(byte as u32, 16)
1527    }
1528    let r = input.as_ref();
1529    if r.len() * 2 != output.len() {
1530        return Err(make_parse_int_err());
1531    }
1532    for i in 0..r.len() {
1533        let byte = r[i];
1534        output[i * 2] = from_digit((byte & 0xf0) >> 4).ok_or_else(make_parse_int_err)? as u8;
1535        output[i * 2 + 1] = from_digit(byte & 0x0f).ok_or_else(make_parse_int_err)? as u8;
1536    }
1537
1538    Ok(())
1539}
1540
1541pub(super) enum PanicReason {
1542    Add,
1543    Sub,
1544    Mul,
1545    DivByZero,
1546}
1547
1548c0nst::c0nst! {
1549    pub(super) c0nst fn maybe_panic(r: PanicReason) {
1550        match r {
1551            PanicReason::Add => panic!("attempt to add with overflow"),
1552            PanicReason::Sub => panic!("attempt to subtract with overflow"),
1553            PanicReason::Mul => panic!("attempt to multiply with overflow"),
1554            PanicReason::DivByZero => panic!("attempt to divide by zero"),
1555        }
1556    }
1557
1558    /// Branchless per-limb select: returns `if_zero` when `choice == 0`,
1559    /// `if_one` when `choice == 1`.
1560    ///
1561    /// The `black_box` on `choice` is load-bearing. Without it, LLVM
1562    /// recognizes the algebraic identity `a ^ (mask & (a ^ b))` ==
1563    /// `if mask == 0 { a } else { b }` and rewrites the loop into a
1564    /// `csel` of the source ADDRESS followed by a load — a secret-
1565    /// dependent memory access that the asm-grep gate can't see but
1566    /// that the ctgrind taint pass catches. Opacifying the choice
1567    /// before it flows into `mask` keeps LLVM from proving the
1568    /// equivalence in the first place. This mirrors what `subtle`'s
1569    /// `Choice::from(u8)` does internally.
1570    pub(crate) c0nst fn const_ct_select<
1571        T: [c0nst] ConstMachineWord + MachineWord,
1572        const N: usize,
1573        P: Personality,
1574    >(
1575        if_zero: FixedUInt<T, N, P>,
1576        if_one: FixedUInt<T, N, P>,
1577        choice: u8,
1578    ) -> FixedUInt<T, N, P> {
1579        let choice = core::hint::black_box(choice);
1580        let bit_t = <T as core::convert::From<u8>>::from(choice);
1581        let mask = <T as core::ops::Mul>::mul(bit_t, <T as ConstBounded>::max_value());
1582        let mut result = if_zero;
1583        let mut i = 0;
1584        while i < N {
1585            let diff = <T as core::ops::BitXor>::bitxor(if_zero.array[i], if_one.array[i]);
1586            let masked = <T as core::ops::BitAnd>::bitand(mask, diff);
1587            result.array[i] = <T as core::ops::BitXor>::bitxor(if_zero.array[i], masked);
1588            i += 1;
1589        }
1590        result
1591    }
1592
1593    pub(super) c0nst fn maybe_panic_if<P: Personality>(
1594        overflow: bool,
1595        reason: PanicReason,
1596    ) {
1597        match P::TAG {
1598            PersonalityTag::Nct => {
1599                if overflow {
1600                    maybe_panic(reason);
1601                }
1602            }
1603            PersonalityTag::Ct => {
1604                let _ = overflow;
1605                let _ = reason;
1606            }
1607        }
1608    }
1609}
1610
1611// #endregion helpers
1612
1613#[cfg(test)]
1614mod tests {
1615    use super::FixedUInt as Bn;
1616    use super::*;
1617    use num_traits::One;
1618
1619    type Bn8 = Bn<u8, 8>;
1620    type Bn16 = Bn<u16, 4>;
1621    type Bn32 = Bn<u32, 2>;
1622
1623    c0nst::c0nst! {
1624        pub c0nst fn test_add<T: [c0nst] ConstMachineWord, const N: usize>(
1625            a: &mut [T; N],
1626            b: &[T; N]
1627        ) -> bool {
1628            add_impl(a, b)
1629        }
1630
1631        pub c0nst fn test_sub<T: [c0nst] ConstMachineWord, const N: usize>(
1632            a: &mut [T; N],
1633            b: &[T; N]
1634        ) -> bool {
1635            sub_impl(a, b)
1636        }
1637
1638        pub c0nst fn test_mul<T: [c0nst] ConstMachineWord, const N: usize>(
1639            a: &[T; N],
1640            b: &[T; N],
1641            word_bits: usize,
1642        ) -> ([T; N], bool) {
1643            const_mul::<T, N, true, crate::personality::Nct>(a, b, word_bits)
1644        }
1645
1646        pub c0nst fn arr_leading_zeros<T: [c0nst] ConstMachineWord, const N: usize>(
1647            a: &[T; N],
1648        ) -> u32 {
1649            const_leading_zeros::<T, N>(a)
1650        }
1651
1652        pub c0nst fn arr_trailing_zeros<T: [c0nst] ConstMachineWord, const N: usize>(
1653            a: &[T; N],
1654        ) -> u32 {
1655            const_trailing_zeros::<T, N>(a)
1656        }
1657
1658        pub c0nst fn arr_bit_length<T: [c0nst] ConstMachineWord, const N: usize>(
1659            a: &[T; N],
1660        ) -> usize {
1661            const_bit_length::<T, N>(a)
1662        }
1663
1664        pub c0nst fn arr_is_zero<T: [c0nst] ConstMachineWord, const N: usize>(
1665            a: &[T; N],
1666        ) -> bool {
1667            const_is_zero::<T, N>(a)
1668        }
1669
1670        pub c0nst fn arr_set_bit<T: [c0nst] ConstMachineWord, const N: usize>(
1671            a: &mut [T; N],
1672            pos: usize,
1673        ) {
1674            const_set_bit::<T, N>(a, pos)
1675        }
1676
1677        pub c0nst fn arr_cmp<T: [c0nst] ConstMachineWord, const N: usize>(
1678            a: &[T; N],
1679            b: &[T; N],
1680        ) -> core::cmp::Ordering {
1681            const_cmp::<T, N>(a, b)
1682        }
1683
1684        pub c0nst fn arr_cmp_shifted<T: [c0nst] ConstMachineWord, const N: usize>(
1685            a: &[T; N],
1686            b: &[T; N],
1687            shift_bits: usize,
1688        ) -> core::cmp::Ordering {
1689            const_cmp_shifted::<T, N>(a, b, shift_bits)
1690        }
1691
1692        pub c0nst fn arr_get_shifted_word<T: [c0nst] ConstMachineWord, const N: usize>(
1693            a: &[T; N],
1694            word_idx: usize,
1695            word_shift: usize,
1696            bit_shift: usize,
1697        ) -> T {
1698            const_get_shifted_word::<T, N>(a, word_idx, word_shift, bit_shift)
1699        }
1700    }
1701
1702    #[test]
1703    fn test_const_add_impl() {
1704        // Simple add, no overflow
1705        let mut a: [u8; 4] = [1, 0, 0, 0];
1706        let b: [u8; 4] = [2, 0, 0, 0];
1707        let overflow = test_add(&mut a, &b);
1708        assert_eq!(a, [3, 0, 0, 0]);
1709        assert!(!overflow);
1710
1711        // Add with carry propagation
1712        let mut a: [u8; 4] = [255, 0, 0, 0];
1713        let b: [u8; 4] = [1, 0, 0, 0];
1714        let overflow = test_add(&mut a, &b);
1715        assert_eq!(a, [0, 1, 0, 0]);
1716        assert!(!overflow);
1717
1718        // Add with overflow
1719        let mut a: [u8; 4] = [255, 255, 255, 255];
1720        let b: [u8; 4] = [1, 0, 0, 0];
1721        let overflow = test_add(&mut a, &b);
1722        assert_eq!(a, [0, 0, 0, 0]);
1723        assert!(overflow);
1724
1725        // Test with u32 words
1726        let mut a: [u32; 2] = [0xFFFFFFFF, 0];
1727        let b: [u32; 2] = [1, 0];
1728        let overflow = test_add(&mut a, &b);
1729        assert_eq!(a, [0, 1]);
1730        assert!(!overflow);
1731
1732        #[cfg(feature = "nightly")]
1733        {
1734            const ADD_RESULT: ([u8; 4], bool) = {
1735                let mut a = [1u8, 0, 0, 0];
1736                let b = [2u8, 0, 0, 0];
1737                let overflow = test_add(&mut a, &b);
1738                (a, overflow)
1739            };
1740            assert_eq!(ADD_RESULT, ([3, 0, 0, 0], false));
1741        }
1742    }
1743
1744    #[test]
1745    fn test_const_sub_impl() {
1746        // Simple sub, no overflow
1747        let mut a: [u8; 4] = [3, 0, 0, 0];
1748        let b: [u8; 4] = [1, 0, 0, 0];
1749        let overflow = test_sub(&mut a, &b);
1750        assert_eq!(a, [2, 0, 0, 0]);
1751        assert!(!overflow);
1752
1753        // Sub with borrow propagation
1754        let mut a: [u8; 4] = [0, 1, 0, 0];
1755        let b: [u8; 4] = [1, 0, 0, 0];
1756        let overflow = test_sub(&mut a, &b);
1757        assert_eq!(a, [255, 0, 0, 0]);
1758        assert!(!overflow);
1759
1760        // Sub with underflow
1761        let mut a: [u8; 4] = [0, 0, 0, 0];
1762        let b: [u8; 4] = [1, 0, 0, 0];
1763        let overflow = test_sub(&mut a, &b);
1764        assert_eq!(a, [255, 255, 255, 255]);
1765        assert!(overflow);
1766
1767        // Test with u32 words
1768        let mut a: [u32; 2] = [0, 1];
1769        let b: [u32; 2] = [1, 0];
1770        let overflow = test_sub(&mut a, &b);
1771        assert_eq!(a, [0xFFFFFFFF, 0]);
1772        assert!(!overflow);
1773
1774        #[cfg(feature = "nightly")]
1775        {
1776            const SUB_RESULT: ([u8; 4], bool) = {
1777                let mut a = [3u8, 0, 0, 0];
1778                let b = [1u8, 0, 0, 0];
1779                let overflow = test_sub(&mut a, &b);
1780                (a, overflow)
1781            };
1782            assert_eq!(SUB_RESULT, ([2, 0, 0, 0], false));
1783        }
1784    }
1785
1786    #[test]
1787    fn test_const_mul_impl() {
1788        // Simple mul: 3 * 4 = 12
1789        let a: [u8; 2] = [3, 0];
1790        let b: [u8; 2] = [4, 0];
1791        let (result, overflow) = test_mul(&a, &b, 8);
1792        assert_eq!(result, [12, 0]);
1793        assert!(!overflow);
1794
1795        // Mul with carry: 200 * 2 = 400 = 0x190 = [0x90, 0x01]
1796        let a: [u8; 2] = [200, 0];
1797        let b: [u8; 2] = [2, 0];
1798        let (result, overflow) = test_mul(&a, &b, 8);
1799        assert_eq!(result, [0x90, 0x01]);
1800        assert!(!overflow);
1801
1802        // Mul with overflow: 256 * 256 = 65536 which overflows 16 bits
1803        let a: [u8; 2] = [0, 1]; // 256
1804        let b: [u8; 2] = [0, 1]; // 256
1805        let (_result, overflow) = test_mul(&a, &b, 8);
1806        assert!(overflow);
1807
1808        // N=3 overflow at high position (round=4, i=2, j=2)
1809        // a = [0, 0, 1] = 65536, b = [0, 0, 1] = 65536
1810        // a * b = 65536^2 = 4294967296 which overflows 24 bits
1811        let a: [u8; 3] = [0, 0, 1];
1812        let b: [u8; 3] = [0, 0, 1];
1813        let (_result, overflow) = test_mul(&a, &b, 8);
1814        assert!(overflow, "N=3 high-position overflow not detected");
1815
1816        // N=3 overflow with larger high word values
1817        // a = [0, 0, 2] = 131072, b = [0, 0, 2] = 131072
1818        // a * b = 131072^2 = 17179869184 which overflows 24 bits
1819        let a: [u8; 3] = [0, 0, 2];
1820        let b: [u8; 3] = [0, 0, 2];
1821        let (_result, overflow) = test_mul(&a, &b, 8);
1822        assert!(
1823            overflow,
1824            "N=3 high-position overflow with larger values not detected"
1825        );
1826
1827        // N=3 non-overflow case: values that fit in 24 bits
1828        // a = [0, 1, 0] = 256, b = [0, 1, 0] = 256
1829        // a * b = 256 * 256 = 65536 = [0, 0, 1] which fits in 24 bits
1830        let a: [u8; 3] = [0, 1, 0];
1831        let b: [u8; 3] = [0, 1, 0];
1832        let (result, overflow) = test_mul(&a, &b, 8);
1833        assert_eq!(result, [0, 0, 1]);
1834        assert!(
1835            !overflow,
1836            "N=3 non-overflow incorrectly detected as overflow"
1837        );
1838
1839        // N=3 non-overflow with carry propagation
1840        // a = [255, 0, 0] = 255, b = [255, 0, 0] = 255
1841        // a * b = 255 * 255 = 65025 = 0xFE01 = [0x01, 0xFE, 0x00]
1842        let a: [u8; 3] = [255, 0, 0];
1843        let b: [u8; 3] = [255, 0, 0];
1844        let (result, overflow) = test_mul(&a, &b, 8);
1845        assert_eq!(result, [0x01, 0xFE, 0x00]);
1846        assert!(!overflow);
1847
1848        #[cfg(feature = "nightly")]
1849        {
1850            const MUL_RESULT: ([u8; 2], bool) = test_mul(&[3u8, 0], &[4u8, 0], 8);
1851            assert_eq!(MUL_RESULT, ([12, 0], false));
1852        }
1853    }
1854
1855    #[test]
1856    fn test_const_helpers() {
1857        // Test leading_zeros
1858        assert_eq!(arr_leading_zeros(&[0u8, 0, 0, 0]), 32); // all zeros
1859        assert_eq!(arr_leading_zeros(&[1u8, 0, 0, 0]), 31); // single bit
1860        assert_eq!(arr_leading_zeros(&[0u8, 0, 0, 1]), 7); // high byte has 1
1861        assert_eq!(arr_leading_zeros(&[0u8, 0, 0, 0x80]), 0); // MSB set
1862        assert_eq!(arr_leading_zeros(&[255u8, 255, 255, 255]), 0); // all ones
1863
1864        // Test trailing_zeros
1865        assert_eq!(arr_trailing_zeros(&[0u8, 0, 0, 0]), 32); // all zeros
1866        assert_eq!(arr_trailing_zeros(&[1u8, 0, 0, 0]), 0); // LSB set
1867        assert_eq!(arr_trailing_zeros(&[0u8, 1, 0, 0]), 8); // second byte
1868        assert_eq!(arr_trailing_zeros(&[0u8, 0, 0, 1]), 24); // fourth byte
1869        assert_eq!(arr_trailing_zeros(&[0x80u8, 0, 0, 0]), 7); // bit 7 of first byte
1870
1871        // Test bit_length
1872        assert_eq!(arr_bit_length(&[0u8, 0, 0, 0]), 0); // zero
1873        assert_eq!(arr_bit_length(&[1u8, 0, 0, 0]), 1); // 1
1874        assert_eq!(arr_bit_length(&[2u8, 0, 0, 0]), 2); // 2
1875        assert_eq!(arr_bit_length(&[3u8, 0, 0, 0]), 2); // 3
1876        assert_eq!(arr_bit_length(&[0u8, 1, 0, 0]), 9); // 256
1877        assert_eq!(arr_bit_length(&[0xF0u8, 0, 0, 0]), 8); // 240 (0xF0)
1878        assert_eq!(arr_bit_length(&[255u8, 255, 255, 255]), 32); // max
1879
1880        // Test is_zero
1881        assert!(arr_is_zero(&[0u8, 0, 0, 0]));
1882        assert!(!arr_is_zero(&[1u8, 0, 0, 0]));
1883        assert!(!arr_is_zero(&[0u8, 0, 0, 1]));
1884        assert!(!arr_is_zero(&[0u8, 1, 0, 0]));
1885
1886        // Test set_bit
1887        let mut arr: [u8; 4] = [0, 0, 0, 0];
1888        arr_set_bit(&mut arr, 0);
1889        assert_eq!(arr, [1, 0, 0, 0]);
1890
1891        let mut arr: [u8; 4] = [0, 0, 0, 0];
1892        arr_set_bit(&mut arr, 8);
1893        assert_eq!(arr, [0, 1, 0, 0]);
1894
1895        let mut arr: [u8; 4] = [0, 0, 0, 0];
1896        arr_set_bit(&mut arr, 31);
1897        assert_eq!(arr, [0, 0, 0, 0x80]);
1898
1899        // Set multiple bits
1900        let mut arr: [u8; 4] = [0, 0, 0, 0];
1901        arr_set_bit(&mut arr, 0);
1902        arr_set_bit(&mut arr, 3);
1903        arr_set_bit(&mut arr, 8);
1904        assert_eq!(arr, [0b00001001, 1, 0, 0]);
1905
1906        // Out of bounds should be no-op
1907        let mut arr: [u8; 4] = [0, 0, 0, 0];
1908        arr_set_bit(&mut arr, 32);
1909        assert_eq!(arr, [0, 0, 0, 0]);
1910
1911        // Test with u32 words
1912        assert_eq!(arr_leading_zeros(&[0u32, 0]), 64);
1913        assert_eq!(arr_leading_zeros(&[1u32, 0]), 63);
1914        assert_eq!(arr_leading_zeros(&[0u32, 1]), 31);
1915        assert_eq!(arr_trailing_zeros(&[0u32, 0]), 64);
1916        assert_eq!(arr_trailing_zeros(&[0u32, 1]), 32);
1917        assert_eq!(arr_bit_length(&[0u32, 0]), 0);
1918        assert_eq!(arr_bit_length(&[1u32, 0]), 1);
1919        assert_eq!(arr_bit_length(&[0u32, 1]), 33);
1920
1921        #[cfg(feature = "nightly")]
1922        {
1923            const LEADING: u32 = arr_leading_zeros(&[0u8, 0, 1, 0]);
1924            assert_eq!(LEADING, 15);
1925
1926            const TRAILING: u32 = arr_trailing_zeros(&[0u8, 0, 1, 0]);
1927            assert_eq!(TRAILING, 16);
1928
1929            const BIT_LEN: usize = arr_bit_length(&[0u8, 0, 1, 0]);
1930            assert_eq!(BIT_LEN, 17);
1931
1932            const IS_ZERO: bool = arr_is_zero(&[0u8, 0, 0, 0]);
1933            assert!(IS_ZERO);
1934
1935            const NOT_ZERO: bool = arr_is_zero(&[0u8, 1, 0, 0]);
1936            assert!(!NOT_ZERO);
1937
1938            const SET_BIT_RESULT: [u8; 4] = {
1939                let mut arr = [0u8, 0, 0, 0];
1940                arr_set_bit(&mut arr, 10);
1941                arr
1942            };
1943            assert_eq!(SET_BIT_RESULT, [0, 0b00000100, 0, 0]);
1944        }
1945    }
1946
1947    #[test]
1948    fn test_const_cmp() {
1949        use core::cmp::Ordering;
1950
1951        // Equal arrays
1952        assert_eq!(arr_cmp(&[1u8, 2, 3, 4], &[1u8, 2, 3, 4]), Ordering::Equal);
1953        assert_eq!(arr_cmp(&[0u8, 0, 0, 0], &[0u8, 0, 0, 0]), Ordering::Equal);
1954
1955        // Greater - high word differs
1956        assert_eq!(arr_cmp(&[0u8, 0, 0, 2], &[0u8, 0, 0, 1]), Ordering::Greater);
1957
1958        // Less - high word differs
1959        assert_eq!(arr_cmp(&[0u8, 0, 0, 1], &[0u8, 0, 0, 2]), Ordering::Less);
1960
1961        // Greater - low word differs (high words equal)
1962        assert_eq!(arr_cmp(&[2u8, 0, 0, 0], &[1u8, 0, 0, 0]), Ordering::Greater);
1963
1964        // Less - low word differs
1965        assert_eq!(arr_cmp(&[1u8, 0, 0, 0], &[2u8, 0, 0, 0]), Ordering::Less);
1966
1967        // Test with u32 words
1968        assert_eq!(arr_cmp(&[0u32, 1], &[0u32, 1]), Ordering::Equal);
1969        assert_eq!(arr_cmp(&[0u32, 2], &[0u32, 1]), Ordering::Greater);
1970        assert_eq!(arr_cmp(&[0u32, 1], &[0u32, 2]), Ordering::Less);
1971
1972        #[cfg(feature = "nightly")]
1973        {
1974            const CMP_EQ: Ordering = arr_cmp(&[1u8, 2, 3, 4], &[1u8, 2, 3, 4]);
1975            const CMP_GT: Ordering = arr_cmp(&[0u8, 0, 0, 2], &[0u8, 0, 0, 1]);
1976            const CMP_LT: Ordering = arr_cmp(&[0u8, 0, 0, 1], &[0u8, 0, 0, 2]);
1977            assert_eq!(CMP_EQ, Ordering::Equal);
1978            assert_eq!(CMP_GT, Ordering::Greater);
1979            assert_eq!(CMP_LT, Ordering::Less);
1980        }
1981    }
1982
1983    #[test]
1984    fn test_const_cmp_shifted() {
1985        use core::cmp::Ordering;
1986
1987        // No shift - same as regular cmp
1988        assert_eq!(
1989            arr_cmp_shifted(&[1u8, 0, 0, 0], &[1u8, 0, 0, 0], 0),
1990            Ordering::Equal
1991        );
1992
1993        // Compare [0, 1, 0, 0] (256) vs [1, 0, 0, 0] << 8 (256) = Equal
1994        assert_eq!(
1995            arr_cmp_shifted(&[0u8, 1, 0, 0], &[1u8, 0, 0, 0], 8),
1996            Ordering::Equal
1997        );
1998
1999        // Compare [0, 2, 0, 0] (512) vs [1, 0, 0, 0] << 8 (256) = Greater
2000        assert_eq!(
2001            arr_cmp_shifted(&[0u8, 2, 0, 0], &[1u8, 0, 0, 0], 8),
2002            Ordering::Greater
2003        );
2004
2005        // Compare [0, 0, 0, 0] (0) vs [1, 0, 0, 0] << 8 (256) = Less
2006        assert_eq!(
2007            arr_cmp_shifted(&[0u8, 0, 0, 0], &[1u8, 0, 0, 0], 8),
2008            Ordering::Less
2009        );
2010
2011        // Shift overflow: shift >= bit_size, other becomes 0
2012        // Compare [1, 0, 0, 0] vs [1, 0, 0, 0] << 32 (0) = Greater
2013        assert_eq!(
2014            arr_cmp_shifted(&[1u8, 0, 0, 0], &[1u8, 0, 0, 0], 32),
2015            Ordering::Greater
2016        );
2017
2018        // Compare [0, 0, 0, 0] vs anything << 32 (0) = Equal
2019        assert_eq!(
2020            arr_cmp_shifted(&[0u8, 0, 0, 0], &[255u8, 255, 255, 255], 32),
2021            Ordering::Equal
2022        );
2023
2024        // Test get_shifted_word helper with bit_shift == 0
2025        // [1, 2, 3, 4] shifted left by 1 word (8 bits for u8)
2026        // word 0 should be 0, word 1 should be 1, word 2 should be 2, etc.
2027        assert_eq!(arr_get_shifted_word(&[1u8, 2, 3, 4], 0, 1, 0), 0);
2028        assert_eq!(arr_get_shifted_word(&[1u8, 2, 3, 4], 1, 1, 0), 1);
2029        assert_eq!(arr_get_shifted_word(&[1u8, 2, 3, 4], 2, 1, 0), 2);
2030
2031        // Test get_shifted_word with bit_shift != 0 (cross-word bit combination)
2032        // [0x0F, 0xF0, 0, 0] with word_shift=0, bit_shift=4
2033        // word 0: 0x0F << 4 = 0xF0 (no lower word to borrow from)
2034        assert_eq!(arr_get_shifted_word(&[0x0Fu8, 0xF0, 0, 0], 0, 0, 4), 0xF0);
2035        // word 1: (0xF0 << 4) | (0x0F >> 4) = 0x00 | 0x00 = 0x00
2036        assert_eq!(arr_get_shifted_word(&[0x0Fu8, 0xF0, 0, 0], 1, 0, 4), 0x00);
2037
2038        // [0xFF, 0x00, 0, 0] with bit_shift=4
2039        // word 0: 0xFF << 4 = 0xF0
2040        assert_eq!(arr_get_shifted_word(&[0xFFu8, 0x00, 0, 0], 0, 0, 4), 0xF0);
2041        // word 1: (0x00 << 4) | (0xFF >> 4) = 0x00 | 0x0F = 0x0F
2042        assert_eq!(arr_get_shifted_word(&[0xFFu8, 0x00, 0, 0], 1, 0, 4), 0x0F);
2043
2044        // Combined word_shift and bit_shift
2045        // [0xAB, 0xCD, 0, 0] with word_shift=1, bit_shift=4
2046        // word 0: below word_shift, returns 0
2047        assert_eq!(arr_get_shifted_word(&[0xABu8, 0xCD, 0, 0], 0, 1, 4), 0);
2048        // word 1: source_idx=0, 0xAB << 4 = 0xB0 (no lower word)
2049        assert_eq!(arr_get_shifted_word(&[0xABu8, 0xCD, 0, 0], 1, 1, 4), 0xB0);
2050        // word 2: source_idx=1, (0xCD << 4) | (0xAB >> 4) = 0xD0 | 0x0A = 0xDA
2051        assert_eq!(arr_get_shifted_word(&[0xABu8, 0xCD, 0, 0], 2, 1, 4), 0xDA);
2052
2053        #[cfg(feature = "nightly")]
2054        {
2055            const CMP_SHIFTED_EQ: Ordering = arr_cmp_shifted(&[0u8, 1, 0, 0], &[1u8, 0, 0, 0], 8);
2056            const CMP_SHIFTED_GT: Ordering = arr_cmp_shifted(&[0u8, 2, 0, 0], &[1u8, 0, 0, 0], 8);
2057            assert_eq!(CMP_SHIFTED_EQ, Ordering::Equal);
2058            assert_eq!(CMP_SHIFTED_GT, Ordering::Greater);
2059        }
2060    }
2061
2062    #[test]
2063    fn test_core_convert_u8() {
2064        let f = Bn::<u8, 1>::from(1u8);
2065        assert_eq!(f.array, [1]);
2066        let f = Bn::<u8, 2>::from(1u8);
2067        assert_eq!(f.array, [1, 0]);
2068
2069        let f = Bn::<u16, 1>::from(1u8);
2070        assert_eq!(f.array, [1]);
2071        let f = Bn::<u16, 2>::from(1u8);
2072        assert_eq!(f.array, [1, 0]);
2073
2074        #[cfg(feature = "nightly")]
2075        {
2076            const F1: Bn<u8, 2> = Bn::<u8, 2>::from(42u8);
2077            assert_eq!(F1.array, [42, 0]);
2078        }
2079    }
2080
2081    #[test]
2082    fn test_core_convert_u16() {
2083        let f = Bn::<u8, 1>::from(1u16);
2084        assert_eq!(f.array, [1]);
2085        let f = Bn::<u8, 2>::from(1u16);
2086        assert_eq!(f.array, [1, 0]);
2087
2088        let f = Bn::<u8, 1>::from(256u16);
2089        assert_eq!(f.array, [0]);
2090        let f = Bn::<u8, 2>::from(257u16);
2091        assert_eq!(f.array, [1, 1]);
2092        let f = Bn::<u8, 2>::from(65535u16);
2093        assert_eq!(f.array, [255, 255]);
2094
2095        let f = Bn::<u16, 1>::from(1u16);
2096        assert_eq!(f.array, [1]);
2097        let f = Bn::<u16, 2>::from(1u16);
2098        assert_eq!(f.array, [1, 0]);
2099
2100        let f = Bn::<u16, 1>::from(65535u16);
2101        assert_eq!(f.array, [65535]);
2102
2103        #[cfg(feature = "nightly")]
2104        {
2105            const F1: Bn<u8, 2> = Bn::<u8, 2>::from(0x0102u16);
2106            assert_eq!(F1.array, [0x02, 0x01]);
2107        }
2108    }
2109
2110    #[test]
2111    fn test_core_convert_u32() {
2112        let f = Bn::<u8, 1>::from(1u32);
2113        assert_eq!(f.array, [1]);
2114        let f = Bn::<u8, 1>::from(256u32);
2115        assert_eq!(f.array, [0]);
2116
2117        let f = Bn::<u8, 2>::from(1u32);
2118        assert_eq!(f.array, [1, 0]);
2119        let f = Bn::<u8, 2>::from(257u32);
2120        assert_eq!(f.array, [1, 1]);
2121        let f = Bn::<u8, 2>::from(65535u32);
2122        assert_eq!(f.array, [255, 255]);
2123
2124        let f = Bn::<u8, 4>::from(1u32);
2125        assert_eq!(f.array, [1, 0, 0, 0]);
2126        let f = Bn::<u8, 4>::from(257u32);
2127        assert_eq!(f.array, [1, 1, 0, 0]);
2128        let f = Bn::<u8, 4>::from(u32::max_value());
2129        assert_eq!(f.array, [255, 255, 255, 255]);
2130
2131        let f = Bn::<u8, 1>::from(1u32);
2132        assert_eq!(f.array, [1]);
2133        let f = Bn::<u8, 1>::from(256u32);
2134        assert_eq!(f.array, [0]);
2135
2136        let f = Bn::<u16, 2>::from(65537u32);
2137        assert_eq!(f.array, [1, 1]);
2138
2139        let f = Bn::<u32, 1>::from(1u32);
2140        assert_eq!(f.array, [1]);
2141        let f = Bn::<u32, 2>::from(1u32);
2142        assert_eq!(f.array, [1, 0]);
2143
2144        let f = Bn::<u32, 1>::from(65537u32);
2145        assert_eq!(f.array, [65537]);
2146
2147        let f = Bn::<u32, 1>::from(u32::max_value());
2148        assert_eq!(f.array, [4294967295]);
2149
2150        #[cfg(feature = "nightly")]
2151        {
2152            const F1: Bn<u8, 4> = Bn::<u8, 4>::from(0x01020304u32);
2153            assert_eq!(F1.array, [0x04, 0x03, 0x02, 0x01]);
2154        }
2155    }
2156
2157    #[test]
2158    fn test_core_convert_u64() {
2159        let f = Bn::<u8, 8>::from(0x0102030405060708u64);
2160        assert_eq!(f.array, [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
2161
2162        let f = Bn::<u16, 4>::from(0x0102030405060708u64);
2163        assert_eq!(f.array, [0x0708, 0x0506, 0x0304, 0x0102]);
2164
2165        let f = Bn::<u32, 2>::from(0x0102030405060708u64);
2166        assert_eq!(f.array, [0x05060708, 0x01020304]);
2167
2168        let f = Bn::<u64, 1>::from(0x0102030405060708u64);
2169        assert_eq!(f.array, [0x0102030405060708]);
2170
2171        #[cfg(feature = "nightly")]
2172        {
2173            const F1: Bn<u8, 8> = Bn::<u8, 8>::from(0x0102030405060708u64);
2174            assert_eq!(F1.array, [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
2175        }
2176    }
2177
2178    #[test]
2179    fn testsimple() {
2180        assert_eq!(Bn::<u8, 8>::new(), Bn::<u8, 8>::new());
2181
2182        assert_eq!(Bn::<u8, 8>::from_u8(3).unwrap().to_u32(), Some(3));
2183        assert_eq!(Bn::<u16, 4>::from_u8(3).unwrap().to_u32(), Some(3));
2184        assert_eq!(Bn::<u32, 2>::from_u8(3).unwrap().to_u32(), Some(3));
2185        assert_eq!(Bn::<u32, 2>::from_u64(3).unwrap().to_u32(), Some(3));
2186        assert_eq!(Bn::<u8, 8>::from_u64(255).unwrap().to_u32(), Some(255));
2187        assert_eq!(Bn::<u8, 8>::from_u64(256).unwrap().to_u32(), Some(256));
2188        assert_eq!(Bn::<u8, 8>::from_u64(65536).unwrap().to_u32(), Some(65536));
2189    }
2190    #[test]
2191    fn testfrom() {
2192        let mut n1 = Bn::<u8, 8>::new();
2193        n1.array[0] = 1;
2194        assert_eq!(Some(1), n1.to_u32());
2195        n1.array[1] = 1;
2196        assert_eq!(Some(257), n1.to_u32());
2197
2198        let mut n2 = Bn::<u16, 8>::new();
2199        n2.array[0] = 0xffff;
2200        assert_eq!(Some(65535), n2.to_u32());
2201        n2.array[0] = 0x0;
2202        n2.array[2] = 0x1;
2203        // Overflow
2204        assert_eq!(None, n2.to_u32());
2205        assert_eq!(Some(0x100000000), n2.to_u64());
2206    }
2207
2208    #[test]
2209    fn test_from_str_bitlengths() {
2210        let test_s64 = "81906f5e4d3c2c01";
2211        let test_u64: u64 = 0x81906f5e4d3c2c01;
2212        let bb = Bn8::from_str_radix(test_s64, 16).unwrap();
2213        let cc = Bn8::from_u64(test_u64).unwrap();
2214        assert_eq!(cc.array, [0x01, 0x2c, 0x3c, 0x4d, 0x5e, 0x6f, 0x90, 0x81]);
2215        assert_eq!(bb.array, [0x01, 0x2c, 0x3c, 0x4d, 0x5e, 0x6f, 0x90, 0x81]);
2216        let dd = Bn16::from_u64(test_u64).unwrap();
2217        let ff = Bn16::from_str_radix(test_s64, 16).unwrap();
2218        assert_eq!(dd.array, [0x2c01, 0x4d3c, 0x6f5e, 0x8190]);
2219        assert_eq!(ff.array, [0x2c01, 0x4d3c, 0x6f5e, 0x8190]);
2220        let ee = Bn32::from_u64(test_u64).unwrap();
2221        let gg = Bn32::from_str_radix(test_s64, 16).unwrap();
2222        assert_eq!(ee.array, [0x4d3c2c01, 0x81906f5e]);
2223        assert_eq!(gg.array, [0x4d3c2c01, 0x81906f5e]);
2224    }
2225
2226    #[test]
2227    fn test_from_str_stringlengths() {
2228        let ab = Bn::<u8, 9>::from_str_radix("2281906f5e4d3c2c01", 16).unwrap();
2229        assert_eq!(
2230            ab.array,
2231            [0x01, 0x2c, 0x3c, 0x4d, 0x5e, 0x6f, 0x90, 0x81, 0x22]
2232        );
2233        assert_eq!(
2234            [0x2c01, 0x4d3c, 0x6f5e, 0],
2235            Bn::<u16, 4>::from_str_radix("6f5e4d3c2c01", 16)
2236                .unwrap()
2237                .array
2238        );
2239        assert_eq!(
2240            [0x2c01, 0x4d3c, 0x6f5e, 0x190],
2241            Bn::<u16, 4>::from_str_radix("1906f5e4d3c2c01", 16)
2242                .unwrap()
2243                .array
2244        );
2245        assert_eq!(
2246            Err(make_overflow_err()),
2247            Bn::<u16, 4>::from_str_radix("f81906f5e4d3c2c01", 16)
2248        );
2249        assert_eq!(
2250            Err(make_overflow_err()),
2251            Bn::<u16, 4>::from_str_radix("af81906f5e4d3c2c01", 16)
2252        );
2253        assert_eq!(
2254            Err(make_overflow_err()),
2255            Bn::<u16, 4>::from_str_radix("baaf81906f5e4d3c2c01", 16)
2256        );
2257        let ac = Bn::<u16, 5>::from_str_radix("baaf81906f5e4d3c2c01", 16).unwrap();
2258        assert_eq!(ac.array, [0x2c01, 0x4d3c, 0x6f5e, 0x8190, 0xbaaf]);
2259    }
2260
2261    #[test]
2262    fn test_resize() {
2263        type TestInt1 = FixedUInt<u32, 1>;
2264        type TestInt2 = FixedUInt<u32, 2>;
2265
2266        let a = TestInt1::from(u32::MAX);
2267        let b: TestInt2 = a.resize();
2268        assert_eq!(b, TestInt2::from([u32::MAX, 0]));
2269
2270        let a = TestInt2::from([u32::MAX, u32::MAX]);
2271        let b: TestInt1 = a.resize();
2272        assert_eq!(b, TestInt1::from(u32::MAX));
2273    }
2274
2275    #[test]
2276    fn test_bit_length() {
2277        assert_eq!(0, Bn8::from_u8(0).unwrap().bit_length());
2278        assert_eq!(1, Bn8::from_u8(1).unwrap().bit_length());
2279        assert_eq!(2, Bn8::from_u8(2).unwrap().bit_length());
2280        assert_eq!(2, Bn8::from_u8(3).unwrap().bit_length());
2281        assert_eq!(7, Bn8::from_u8(0x70).unwrap().bit_length());
2282        assert_eq!(8, Bn8::from_u8(0xF0).unwrap().bit_length());
2283        assert_eq!(9, Bn8::from_u16(0x1F0).unwrap().bit_length());
2284
2285        assert_eq!(20, Bn8::from_u64(990223).unwrap().bit_length());
2286        assert_eq!(32, Bn8::from_u64(0xefffffff).unwrap().bit_length());
2287        assert_eq!(32, Bn8::from_u64(0x8fffffff).unwrap().bit_length());
2288        assert_eq!(31, Bn8::from_u64(0x7fffffff).unwrap().bit_length());
2289        assert_eq!(34, Bn8::from_u64(0x3ffffffff).unwrap().bit_length());
2290
2291        assert_eq!(0, Bn32::from_u8(0).unwrap().bit_length());
2292        assert_eq!(1, Bn32::from_u8(1).unwrap().bit_length());
2293        assert_eq!(2, Bn32::from_u8(2).unwrap().bit_length());
2294        assert_eq!(2, Bn32::from_u8(3).unwrap().bit_length());
2295        assert_eq!(7, Bn32::from_u8(0x70).unwrap().bit_length());
2296        assert_eq!(8, Bn32::from_u8(0xF0).unwrap().bit_length());
2297        assert_eq!(9, Bn32::from_u16(0x1F0).unwrap().bit_length());
2298
2299        assert_eq!(20, Bn32::from_u64(990223).unwrap().bit_length());
2300        assert_eq!(32, Bn32::from_u64(0xefffffff).unwrap().bit_length());
2301        assert_eq!(32, Bn32::from_u64(0x8fffffff).unwrap().bit_length());
2302        assert_eq!(31, Bn32::from_u64(0x7fffffff).unwrap().bit_length());
2303        assert_eq!(34, Bn32::from_u64(0x3ffffffff).unwrap().bit_length());
2304    }
2305
2306    #[test]
2307    fn test_bit_length_1000() {
2308        // Test bit_length with value 1000
2309        let value = Bn32::from_u16(1000).unwrap();
2310
2311        // 1000 in binary is 1111101000, which has 10 bits
2312        // Let's verify the implementation is working correctly
2313        assert_eq!(value.to_u32().unwrap(), 1000);
2314        assert_eq!(value.bit_length(), 10);
2315
2316        // Test some edge cases around 1000
2317        assert_eq!(Bn32::from_u16(512).unwrap().bit_length(), 10); // 2^9 = 512
2318        assert_eq!(Bn32::from_u16(1023).unwrap().bit_length(), 10); // 2^10 - 1 = 1023
2319        assert_eq!(Bn32::from_u16(1024).unwrap().bit_length(), 11); // 2^10 = 1024
2320
2321        // Test with different word sizes to see if this makes a difference
2322        assert_eq!(Bn8::from_u16(1000).unwrap().bit_length(), 10);
2323        assert_eq!(Bn16::from_u16(1000).unwrap().bit_length(), 10);
2324
2325        // Test with different initialization methods
2326        let value_from_str = Bn32::from_str_radix("1000", 10).unwrap();
2327        assert_eq!(value_from_str.bit_length(), 10);
2328
2329        // This is the problematic case - let's debug it
2330        let value_from_bytes = Bn32::from_le_bytes(&1000u16.to_le_bytes());
2331        // Let's see what the actual value is
2332        assert_eq!(
2333            value_from_bytes.to_u32().unwrap_or(0),
2334            1000,
2335            "from_le_bytes didn't create the correct value"
2336        );
2337        assert_eq!(value_from_bytes.bit_length(), 10);
2338    }
2339    #[test]
2340    fn test_cmp() {
2341        let f0 = <Bn8 as Zero>::zero();
2342        let f1 = <Bn8 as Zero>::zero();
2343        let f2 = <Bn8 as One>::one();
2344        assert_eq!(f0, f1);
2345        assert!(f2 > f0);
2346        assert!(f0 < f2);
2347        let f3 = Bn32::from_u64(990223).unwrap();
2348        assert_eq!(f3, Bn32::from_u64(990223).unwrap());
2349        let f4 = Bn32::from_u64(990224).unwrap();
2350        assert!(f4 > Bn32::from_u64(990223).unwrap());
2351
2352        let f3 = Bn8::from_u64(990223).unwrap();
2353        assert_eq!(f3, Bn8::from_u64(990223).unwrap());
2354        let f4 = Bn8::from_u64(990224).unwrap();
2355        assert!(f4 > Bn8::from_u64(990223).unwrap());
2356
2357        #[cfg(feature = "nightly")]
2358        {
2359            use core::cmp::Ordering;
2360
2361            const A: FixedUInt<u8, 2> = FixedUInt::from_array([10, 0]);
2362            const B: FixedUInt<u8, 2> = FixedUInt::from_array([20, 0]);
2363            const C: FixedUInt<u8, 2> = FixedUInt::from_array([10, 0]);
2364
2365            const CMP_LT: Ordering = A.cmp(&B);
2366            const CMP_GT: Ordering = B.cmp(&A);
2367            const CMP_EQ: Ordering = A.cmp(&C);
2368            const EQ_TRUE: bool = A.eq(&C);
2369            const EQ_FALSE: bool = A.eq(&B);
2370
2371            assert_eq!(CMP_LT, Ordering::Less);
2372            assert_eq!(CMP_GT, Ordering::Greater);
2373            assert_eq!(CMP_EQ, Ordering::Equal);
2374            assert!(EQ_TRUE);
2375            assert!(!EQ_FALSE);
2376        }
2377    }
2378
2379    #[test]
2380    fn test_default() {
2381        let d: Bn8 = Default::default();
2382        assert!(Zero::is_zero(&d));
2383
2384        #[cfg(feature = "nightly")]
2385        {
2386            const D: FixedUInt<u8, 2> = <FixedUInt<u8, 2> as Default>::default();
2387            assert!(Zero::is_zero(&D));
2388        }
2389    }
2390
2391    #[test]
2392    fn test_clone() {
2393        let a: Bn8 = 42u8.into();
2394        let b = a.clone();
2395        assert_eq!(a, b);
2396
2397        #[cfg(feature = "nightly")]
2398        {
2399            const A: FixedUInt<u8, 2> = FixedUInt::from_array([42, 0]);
2400            const B: FixedUInt<u8, 2> = A.clone();
2401            assert_eq!(A.array, B.array);
2402        }
2403    }
2404
2405    #[test]
2406    fn test_le_be_bytes() {
2407        let le_bytes = [1, 2, 3, 4];
2408        let be_bytes = [4, 3, 2, 1];
2409        let u8_ver = FixedUInt::<u8, 4>::from_le_bytes(&le_bytes);
2410        let u16_ver = FixedUInt::<u16, 2>::from_le_bytes(&le_bytes);
2411        let u32_ver = FixedUInt::<u32, 1>::from_le_bytes(&le_bytes);
2412        let u8_ver_be = FixedUInt::<u8, 4>::from_be_bytes(&be_bytes);
2413        let u16_ver_be = FixedUInt::<u16, 2>::from_be_bytes(&be_bytes);
2414        let u32_ver_be = FixedUInt::<u32, 1>::from_be_bytes(&be_bytes);
2415
2416        assert_eq!(u8_ver.array, [1, 2, 3, 4]);
2417        assert_eq!(u16_ver.array, [0x0201, 0x0403]);
2418        assert_eq!(u32_ver.array, [0x04030201]);
2419        assert_eq!(u8_ver_be.array, [1, 2, 3, 4]);
2420        assert_eq!(u16_ver_be.array, [0x0201, 0x0403]);
2421        assert_eq!(u32_ver_be.array, [0x04030201]);
2422
2423        let mut output_buffer = [0u8; 16];
2424        assert_eq!(u8_ver.to_le_bytes(&mut output_buffer).unwrap(), &le_bytes);
2425        assert_eq!(u8_ver.to_be_bytes(&mut output_buffer).unwrap(), &be_bytes);
2426        assert_eq!(u16_ver.to_le_bytes(&mut output_buffer).unwrap(), &le_bytes);
2427        assert_eq!(u16_ver.to_be_bytes(&mut output_buffer).unwrap(), &be_bytes);
2428        assert_eq!(u32_ver.to_le_bytes(&mut output_buffer).unwrap(), &le_bytes);
2429        assert_eq!(u32_ver.to_be_bytes(&mut output_buffer).unwrap(), &be_bytes);
2430    }
2431
2432    // Test suite for division implementation
2433    #[test]
2434    fn test_div_small() {
2435        type TestInt = FixedUInt<u8, 2>;
2436
2437        // Test small values
2438        let test_cases = [
2439            (20u16, 3u16, 6u16),        // 20 / 3 = 6
2440            (100u16, 7u16, 14u16),      // 100 / 7 = 14
2441            (255u16, 5u16, 51u16),      // 255 / 5 = 51
2442            (65535u16, 256u16, 255u16), // max u16 / 256 = 255
2443        ];
2444
2445        for (dividend_val, divisor_val, expected) in test_cases {
2446            let dividend = TestInt::from(dividend_val);
2447            let divisor = TestInt::from(divisor_val);
2448            let expected_result = TestInt::from(expected);
2449
2450            assert_eq!(
2451                dividend / divisor,
2452                expected_result,
2453                "Division failed for {} / {} = {}",
2454                dividend_val,
2455                divisor_val,
2456                expected
2457            );
2458        }
2459    }
2460
2461    #[test]
2462    fn test_div_edge_cases() {
2463        type TestInt = FixedUInt<u16, 2>;
2464
2465        // Division by 1
2466        let dividend = TestInt::from(1000u16);
2467        let divisor = TestInt::from(1u16);
2468        assert_eq!(dividend / divisor, TestInt::from(1000u16));
2469
2470        // Equal values
2471        let dividend = TestInt::from(42u16);
2472        let divisor = TestInt::from(42u16);
2473        assert_eq!(dividend / divisor, TestInt::from(1u16));
2474
2475        // Dividend < divisor
2476        let dividend = TestInt::from(5u16);
2477        let divisor = TestInt::from(10u16);
2478        assert_eq!(dividend / divisor, TestInt::from(0u16));
2479
2480        // Powers of 2
2481        let dividend = TestInt::from(1024u16);
2482        let divisor = TestInt::from(4u16);
2483        assert_eq!(dividend / divisor, TestInt::from(256u16));
2484    }
2485
2486    #[test]
2487    fn test_helper_methods() {
2488        type TestInt = FixedUInt<u8, 2>;
2489
2490        // Test const_set_bit
2491        let mut val = <TestInt as Zero>::zero();
2492        const_set_bit(&mut val.array, 0);
2493        assert_eq!(val, TestInt::from(1u8));
2494
2495        const_set_bit(&mut val.array, 8);
2496        assert_eq!(val, TestInt::from(257u16)); // bit 0 + bit 8 = 1 + 256 = 257
2497
2498        // Test const_cmp_shifted
2499        let a = TestInt::from(8u8); // 1000 in binary
2500        let b = TestInt::from(1u8); // 0001 in binary
2501
2502        // b << 3 = 8, so a == (b << 3)
2503        assert_eq!(
2504            const_cmp_shifted(&a.array, &b.array, 3),
2505            core::cmp::Ordering::Equal
2506        );
2507
2508        // a > (b << 2) because b << 2 = 4
2509        assert_eq!(
2510            const_cmp_shifted(&a.array, &b.array, 2),
2511            core::cmp::Ordering::Greater
2512        );
2513
2514        // a < (b << 4) because b << 4 = 16
2515        assert_eq!(
2516            const_cmp_shifted(&a.array, &b.array, 4),
2517            core::cmp::Ordering::Less
2518        );
2519
2520        // Test const_sub_shifted
2521        let mut val = TestInt::from(10u8);
2522        let one = TestInt::from(1u8);
2523        const_sub_shifted(&mut val.array, &one.array, 2); // subtract 1 << 2 = 4
2524        assert_eq!(val, TestInt::from(6u8)); // 10 - 4 = 6
2525    }
2526
2527    #[test]
2528    fn test_shifted_operations_comprehensive() {
2529        type TestInt = FixedUInt<u32, 2>;
2530
2531        // Test cmp_shifted with various word boundary cases
2532        let a = TestInt::from(0x12345678u32);
2533        let b = TestInt::from(0x12345678u32);
2534
2535        // Equal comparison
2536        assert_eq!(
2537            const_cmp_shifted(&a.array, &b.array, 0),
2538            core::cmp::Ordering::Equal
2539        );
2540
2541        // Test shifts that cross word boundaries (assuming 32-bit words)
2542        let c = TestInt::from(0x123u32); // Small number
2543        let d = TestInt::from(0x48d159e2u32); // c << 16 + some bits
2544
2545        // c << 16 should be less than d
2546        assert_eq!(
2547            const_cmp_shifted(&d.array, &c.array, 16),
2548            core::cmp::Ordering::Greater
2549        );
2550
2551        // Test large shifts (beyond bit size, so shifted value becomes 0)
2552        let e = TestInt::from(1u32);
2553        let zero = TestInt::from(0u32);
2554        assert_eq!(
2555            const_cmp_shifted(&e.array, &zero.array, 100),
2556            core::cmp::Ordering::Greater
2557        );
2558        // When shift is beyond bit size, 1 << 100 becomes 0, so 0 == 0
2559        assert_eq!(
2560            const_cmp_shifted(&zero.array, &e.array, 100),
2561            core::cmp::Ordering::Equal
2562        );
2563
2564        // Test sub_shifted with word boundary crossing
2565        let mut val = TestInt::from(0x10000u32); // 65536
2566        let one = TestInt::from(1u32);
2567        const_sub_shifted(&mut val.array, &one.array, 15); // subtract 1 << 15 = 32768
2568        assert_eq!(val, TestInt::from(0x8000u32)); // 65536 - 32768 = 32768
2569
2570        // Test sub_shifted with multi-word operations
2571        let mut big_val = TestInt::from(0x100000000u64); // 2^32
2572        const_sub_shifted(&mut big_val.array, &one.array, 31); // subtract 1 << 31 = 2^31
2573        assert_eq!(big_val, TestInt::from(0x80000000u64)); // 2^32 - 2^31 = 2^31
2574    }
2575
2576    #[test]
2577    fn test_shifted_operations_edge_cases() {
2578        type TestInt = FixedUInt<u32, 2>;
2579
2580        // Test zero shifts
2581        let a = TestInt::from(42u32);
2582        let a2 = TestInt::from(42u32);
2583        assert_eq!(
2584            const_cmp_shifted(&a.array, &a2.array, 0),
2585            core::cmp::Ordering::Equal
2586        );
2587
2588        let mut b = TestInt::from(42u32);
2589        let ten = TestInt::from(10u32);
2590        const_sub_shifted(&mut b.array, &ten.array, 0);
2591        assert_eq!(b, TestInt::from(32u32));
2592
2593        // Test massive shifts (beyond bit size)
2594        let c = TestInt::from(123u32);
2595        let large = TestInt::from(456u32);
2596        assert_eq!(
2597            const_cmp_shifted(&c.array, &large.array, 200),
2598            core::cmp::Ordering::Greater
2599        );
2600
2601        let mut d = TestInt::from(123u32);
2602        const_sub_shifted(&mut d.array, &large.array, 200); // Should be no-op
2603        assert_eq!(d, TestInt::from(123u32));
2604
2605        // Test with zero values
2606        let zero = TestInt::from(0u32);
2607        let one = TestInt::from(1u32);
2608        assert_eq!(
2609            const_cmp_shifted(&zero.array, &zero.array, 10),
2610            core::cmp::Ordering::Equal
2611        );
2612        assert_eq!(
2613            const_cmp_shifted(&one.array, &zero.array, 10),
2614            core::cmp::Ordering::Greater
2615        );
2616    }
2617
2618    #[test]
2619    fn test_shifted_operations_equivalence() {
2620        type TestInt = FixedUInt<u32, 2>;
2621
2622        // Test that optimized operations give same results as naive shift+op
2623        let test_cases = [
2624            (0x12345u32, 0x678u32, 4),
2625            (0x1000u32, 0x10u32, 8),
2626            (0xABCDu32, 0x1u32, 16),
2627            (0x80000000u32, 0x1u32, 1),
2628        ];
2629
2630        for (a_val, b_val, shift) in test_cases {
2631            let a = TestInt::from(a_val);
2632            let b = TestInt::from(b_val);
2633
2634            // Test cmp_shifted equivalence
2635            let optimized_cmp = const_cmp_shifted(&a.array, &b.array, shift);
2636            let naive_cmp = a.cmp(&(b << shift));
2637            assert_eq!(
2638                optimized_cmp, naive_cmp,
2639                "cmp_shifted mismatch: {} vs ({} << {})",
2640                a_val, b_val, shift
2641            );
2642
2643            // Test sub_shifted equivalence (if subtraction won't underflow)
2644            if a >= (b << shift) {
2645                let mut optimized_result = a;
2646                const_sub_shifted(&mut optimized_result.array, &b.array, shift);
2647
2648                let naive_result = a - (b << shift);
2649                assert_eq!(
2650                    optimized_result, naive_result,
2651                    "sub_shifted mismatch: {} - ({} << {})",
2652                    a_val, b_val, shift
2653                );
2654            }
2655        }
2656    }
2657
2658    #[test]
2659    fn test_div_assign_in_place_optimization() {
2660        type TestInt = FixedUInt<u32, 2>;
2661
2662        // Test that div_assign uses the optimized in-place algorithm
2663        let test_cases = [
2664            (100u32, 10u32, 10u32, 0u32),     // 100 / 10 = 10 remainder 0
2665            (123u32, 7u32, 17u32, 4u32),      // 123 / 7 = 17 remainder 4
2666            (1000u32, 13u32, 76u32, 12u32),   // 1000 / 13 = 76 remainder 12
2667            (65535u32, 255u32, 257u32, 0u32), // 65535 / 255 = 257 remainder 0
2668        ];
2669
2670        for (dividend_val, divisor_val, expected_quotient, expected_remainder) in test_cases {
2671            // Test div_assign
2672            let mut dividend = TestInt::from(dividend_val);
2673            let divisor = TestInt::from(divisor_val);
2674
2675            dividend /= divisor;
2676            assert_eq!(
2677                dividend,
2678                TestInt::from(expected_quotient),
2679                "div_assign: {} / {} should be {}",
2680                dividend_val,
2681                divisor_val,
2682                expected_quotient
2683            );
2684
2685            // Test div_rem directly
2686            let dividend2 = TestInt::from(dividend_val);
2687            let (quotient, remainder) = dividend2.div_rem(&divisor);
2688            assert_eq!(
2689                quotient,
2690                TestInt::from(expected_quotient),
2691                "div_rem quotient: {} / {} should be {}",
2692                dividend_val,
2693                divisor_val,
2694                expected_quotient
2695            );
2696            assert_eq!(
2697                remainder,
2698                TestInt::from(expected_remainder),
2699                "div_rem remainder: {} % {} should be {}",
2700                dividend_val,
2701                divisor_val,
2702                expected_remainder
2703            );
2704
2705            // Verify: quotient * divisor + remainder == original dividend
2706            assert_eq!(
2707                quotient * divisor + remainder,
2708                TestInt::from(dividend_val),
2709                "Property check failed for {}",
2710                dividend_val
2711            );
2712        }
2713    }
2714
2715    #[test]
2716    fn test_div_assign_stack_efficiency() {
2717        type TestInt = FixedUInt<u32, 4>; // 16 bytes each
2718
2719        // Create test values
2720        let mut dividend = TestInt::from(0x123456789ABCDEFu64);
2721        let divisor = TestInt::from(0x12345u32);
2722        let original_dividend = dividend;
2723
2724        // Perform in-place division
2725        dividend /= divisor;
2726
2727        // Verify correctness
2728        let remainder = original_dividend % divisor;
2729        assert_eq!(dividend * divisor + remainder, original_dividend);
2730    }
2731
2732    #[test]
2733    fn test_rem_assign_optimization() {
2734        type TestInt = FixedUInt<u32, 2>;
2735
2736        let test_cases = [
2737            (100u32, 10u32, 0u32),    // 100 % 10 = 0
2738            (123u32, 7u32, 4u32),     // 123 % 7 = 4
2739            (1000u32, 13u32, 12u32),  // 1000 % 13 = 12
2740            (65535u32, 255u32, 0u32), // 65535 % 255 = 0
2741        ];
2742
2743        for (dividend_val, divisor_val, expected_remainder) in test_cases {
2744            let mut dividend = TestInt::from(dividend_val);
2745            let divisor = TestInt::from(divisor_val);
2746
2747            dividend %= divisor;
2748            assert_eq!(
2749                dividend,
2750                TestInt::from(expected_remainder),
2751                "rem_assign: {} % {} should be {}",
2752                dividend_val,
2753                divisor_val,
2754                expected_remainder
2755            );
2756        }
2757    }
2758
2759    #[test]
2760    fn test_div_with_remainder_property() {
2761        type TestInt = FixedUInt<u32, 2>;
2762
2763        // Test division with remainder property verification
2764        let test_cases = [
2765            (100u32, 10u32, 10u32),     // 100 / 10 = 10
2766            (123u32, 7u32, 17u32),      // 123 / 7 = 17
2767            (1000u32, 13u32, 76u32),    // 1000 / 13 = 76
2768            (65535u32, 255u32, 257u32), // 65535 / 255 = 257
2769        ];
2770
2771        for (dividend_val, divisor_val, expected_quotient) in test_cases {
2772            let dividend = TestInt::from(dividend_val);
2773            let divisor = TestInt::from(divisor_val);
2774
2775            // Test that div operator (which uses div_impl) works correctly
2776            let quotient = dividend / divisor;
2777            assert_eq!(
2778                quotient,
2779                TestInt::from(expected_quotient),
2780                "Division: {} / {} should be {}",
2781                dividend_val,
2782                divisor_val,
2783                expected_quotient
2784            );
2785
2786            // Verify the division property still holds
2787            let remainder = dividend % divisor;
2788            assert_eq!(
2789                quotient * divisor + remainder,
2790                dividend,
2791                "Division property check failed for {}",
2792                dividend_val
2793            );
2794        }
2795    }
2796
2797    #[test]
2798    fn test_code_simplification_benefits() {
2799        type TestInt = FixedUInt<u32, 2>;
2800
2801        // Verify division property holds
2802        let dividend = TestInt::from(12345u32);
2803        let divisor = TestInt::from(67u32);
2804        let quotient = dividend / divisor;
2805        let remainder = dividend % divisor;
2806
2807        // The division property should still hold
2808        assert_eq!(quotient * divisor + remainder, dividend);
2809    }
2810
2811    #[test]
2812    fn test_rem_assign_correctness_after_fix() {
2813        type TestInt = FixedUInt<u32, 2>;
2814
2815        // Test specific case: 17 % 5 = 2
2816        let mut a = TestInt::from(17u32);
2817        let b = TestInt::from(5u32);
2818
2819        // Historical note: an old bug caused quotient corruption during remainder calculation
2820        // Now const_div_rem properly computes both without corrupting intermediate state
2821        a %= b;
2822        assert_eq!(a, TestInt::from(2u32), "17 % 5 should be 2");
2823
2824        // Test that the original RemAssign bug would have failed this
2825        let mut test_val = TestInt::from(100u32);
2826        test_val %= TestInt::from(7u32);
2827        assert_eq!(
2828            test_val,
2829            TestInt::from(2u32),
2830            "100 % 7 should be 2 (not 14, the quotient)"
2831        );
2832    }
2833
2834    #[test]
2835    fn test_div_property_based() {
2836        type TestInt = FixedUInt<u16, 2>;
2837
2838        // Property: quotient * divisor + remainder == dividend
2839        let test_pairs = [
2840            (12345u16, 67u16),
2841            (1000u16, 13u16),
2842            (65535u16, 255u16),
2843            (5000u16, 7u16),
2844        ];
2845
2846        for (dividend_val, divisor_val) in test_pairs {
2847            let dividend = TestInt::from(dividend_val);
2848            let divisor = TestInt::from(divisor_val);
2849
2850            let quotient = dividend / divisor;
2851
2852            // Property verification: quotient * divisor + remainder == dividend
2853            let remainder = dividend - (quotient * divisor);
2854            let reconstructed = quotient * divisor + remainder;
2855
2856            assert_eq!(
2857                reconstructed,
2858                dividend,
2859                "Property failed for {} / {}: {} * {} + {} != {}",
2860                dividend_val,
2861                divisor_val,
2862                quotient.to_u32().unwrap_or(0),
2863                divisor_val,
2864                remainder.to_u32().unwrap_or(0),
2865                dividend_val
2866            );
2867
2868            // Remainder should be less than divisor
2869            assert!(
2870                remainder < divisor,
2871                "Remainder {} >= divisor {} for {} / {}",
2872                remainder.to_u32().unwrap_or(0),
2873                divisor_val,
2874                dividend_val,
2875                divisor_val
2876            );
2877        }
2878    }
2879}