Skip to main content

crypto_bigint/uint/
encoding.rs

1//! Const-friendly decoding/encoding operations for [`Uint`].
2
3#[cfg(feature = "der")]
4mod der;
5#[cfg(feature = "rlp")]
6mod rlp;
7
8use super::Uint;
9use crate::{ByteOrder, DecodeError, EncodedSize, Encoding, Limb, Word};
10use core::{fmt, ops::Deref};
11
12#[cfg(feature = "alloc")]
13use crate::{NonZero, Reciprocal, UintRef, WideWord, bitlen};
14#[cfg(feature = "alloc")]
15use alloc::{string::String, vec::Vec};
16
17#[cfg(feature = "alloc")]
18const RADIX_ENCODING_LIMBS_LARGE: usize = 16;
19#[cfg(feature = "alloc")]
20const RADIX_ENCODING_THRESHOLD_LARGE: usize = 24;
21
22const RADIX_ENCODING_MIN: u32 = 2;
23const RADIX_ENCODING_MAX: u32 = 36;
24
25impl<const LIMBS: usize> Uint<LIMBS> {
26    /// Create a new [`Uint`] from the provided big endian bytes.
27    ///
28    /// # Panics
29    /// - If the supplied byte slice is the incorrect size
30    #[must_use]
31    pub const fn from_be_slice(bytes: &[u8]) -> Self {
32        assert!(
33            bytes.len() == Limb::BYTES * LIMBS,
34            "bytes are not the expected size"
35        );
36
37        let mut res = [Limb::ZERO; LIMBS];
38        let mut buf = [0u8; Limb::BYTES];
39        let mut i = 0;
40
41        while i < LIMBS {
42            let mut j = 0;
43            while j < Limb::BYTES {
44                buf[j] = bytes[i * Limb::BYTES + j];
45                j += 1;
46            }
47            res[LIMBS - i - 1] = Limb(Word::from_be_bytes(buf));
48            i += 1;
49        }
50
51        Uint::new(res)
52    }
53
54    /// Create a new [`Uint`] from the provided little endian bytes.
55    ///
56    /// # Panics
57    /// - If the supplied byte slice is the incorrect size
58    #[must_use]
59    pub const fn from_le_slice(bytes: &[u8]) -> Self {
60        assert!(
61            bytes.len() == Limb::BYTES * LIMBS,
62            "bytes are not the expected size"
63        );
64
65        let mut res = [Limb::ZERO; LIMBS];
66        let mut buf = [0u8; Limb::BYTES];
67        let mut i = 0;
68
69        while i < LIMBS {
70            let mut j = 0;
71            while j < Limb::BYTES {
72                buf[j] = bytes[i * Limb::BYTES + j];
73                j += 1;
74            }
75            res[i] = Limb(Word::from_le_bytes(buf));
76            i += 1;
77        }
78
79        Uint::new(res)
80    }
81
82    /// Create a new [`Uint`] from the provided slice, using the supplied [`ByteOrder`] to
83    /// determine the endianness.
84    ///
85    /// # Panics
86    /// - If the supplied byte slice is the incorrect size
87    #[must_use]
88    pub const fn from_slice(bytes: &[u8], byte_order: ByteOrder) -> Self {
89        match byte_order {
90            ByteOrder::BigEndian => Self::from_be_slice(bytes),
91            ByteOrder::LittleEndian => Self::from_le_slice(bytes),
92        }
93    }
94
95    /// Create a new [`Uint`] from the provided big endian hex string.
96    ///
97    /// # Panics
98    /// - if the hex is malformed or not zero-padded accordingly for the size.
99    #[must_use]
100    pub const fn from_be_hex(hex: &str) -> Self {
101        let bytes = hex.as_bytes();
102
103        assert!(
104            bytes.len() == Limb::BYTES * LIMBS * 2,
105            "hex string is not the expected size"
106        );
107
108        let mut res = [Limb::ZERO; LIMBS];
109        let mut buf = [0u8; Limb::BYTES];
110        let mut i = 0;
111        let mut err = 0;
112
113        while i < LIMBS {
114            let mut j = 0;
115            while j < Limb::BYTES {
116                let offset = (i * Limb::BYTES + j) * 2;
117                let (result, byte_err) = decode_hex_byte([bytes[offset], bytes[offset + 1]]);
118                err |= byte_err;
119                buf[j] = result;
120                j += 1;
121            }
122            res[LIMBS - i - 1] = Limb(Word::from_be_bytes(buf));
123            i += 1;
124        }
125
126        assert!(err == 0, "invalid hex byte");
127
128        Uint::new(res)
129    }
130
131    /// Create a new [`Uint`] from the provided little endian hex string.
132    ///
133    /// # Panics
134    /// - if the hex is malformed or not zero-padded accordingly for the size.
135    #[must_use]
136    pub const fn from_le_hex(hex: &str) -> Self {
137        let bytes = hex.as_bytes();
138
139        assert!(
140            bytes.len() == Limb::BYTES * LIMBS * 2,
141            "bytes are not the expected size"
142        );
143
144        let mut res = [Limb::ZERO; LIMBS];
145        let mut buf = [0u8; Limb::BYTES];
146        let mut i = 0;
147        let mut err = 0;
148
149        while i < LIMBS {
150            let mut j = 0;
151            while j < Limb::BYTES {
152                let offset = (i * Limb::BYTES + j) * 2;
153                let (result, byte_err) = decode_hex_byte([bytes[offset], bytes[offset + 1]]);
154                err |= byte_err;
155                buf[j] = result;
156                j += 1;
157            }
158            res[i] = Limb(Word::from_le_bytes(buf));
159            i += 1;
160        }
161
162        assert!(err == 0, "invalid hex byte");
163
164        Uint::new(res)
165    }
166
167    /// Create a new [`Uint`] from the provided hex string, using the supplied [`ByteOrder`] to
168    /// determine the endianness.
169    ///
170    /// # Panics
171    /// - if the hex is malformed or not zero-padded accordingly for the size.
172    #[must_use]
173    pub const fn from_hex(hex: &str, byte_order: ByteOrder) -> Self {
174        match byte_order {
175            ByteOrder::BigEndian => Self::from_be_hex(hex),
176            ByteOrder::LittleEndian => Self::from_le_hex(hex),
177        }
178    }
179
180    /// Serialize this [`Uint`] as big-endian, writing it into the provided
181    /// byte slice.
182    #[cfg(feature = "hybrid-array")]
183    #[inline]
184    pub(crate) fn write_be_bytes(&self, out: &mut [u8]) {
185        debug_assert_eq!(out.len(), Limb::BYTES * LIMBS);
186
187        for (src, dst) in self
188            .limbs
189            .iter()
190            .rev()
191            .cloned()
192            .zip(out.chunks_exact_mut(Limb::BYTES))
193        {
194            dst.copy_from_slice(&src.to_be_bytes());
195        }
196    }
197
198    /// Serialize this [`Uint`] as little-endian, writing it into the provided
199    /// byte slice.
200    #[cfg(feature = "hybrid-array")]
201    #[inline]
202    pub(crate) fn write_le_bytes(&self, out: &mut [u8]) {
203        debug_assert_eq!(out.len(), Limb::BYTES * LIMBS);
204
205        for (src, dst) in self
206            .limbs
207            .iter()
208            .cloned()
209            .zip(out.chunks_exact_mut(Limb::BYTES))
210        {
211            dst.copy_from_slice(&src.to_le_bytes());
212        }
213    }
214
215    /// Create a new [`Uint`] from a string slice in a given base.
216    ///
217    /// The string may begin with a `+` character, and may use
218    /// underscore characters to separate digits.
219    ///
220    /// # Errors
221    /// - Returns [`DecodeError::InputSize`] if the size of the decoded integer is larger than this
222    ///   type can represent
223    /// - Returns [`DecodeError::InvalidDigit`] if the input value contains non-digit characters or
224    ///   digits outside of the range `0..radix`.
225    ///
226    /// # Panics
227    /// - if `radix` is not in the range from 2 to 36.
228    pub fn from_str_radix_vartime(src: &str, radix: u32) -> Result<Self, DecodeError> {
229        let mut slf = Self::ZERO;
230        radix_decode_str(src, radix, &mut SliceDecodeByLimb::new(&mut slf.limbs))?;
231        Ok(slf)
232    }
233
234    /// Format a [`Uint`] as a string in a given base.
235    ///
236    /// # Panics
237    /// - if `radix` is not in the range from 2 to 36.
238    #[cfg(feature = "alloc")]
239    #[must_use]
240    pub fn to_string_radix_vartime(&self, radix: u32) -> String {
241        let mut buf = *self;
242        radix_encode_limbs_mut_to_string(radix, buf.as_mut_uint_ref())
243    }
244
245    /// Serialize as big endian bytes.
246    #[must_use]
247    pub const fn to_be_bytes(&self) -> EncodedUint<LIMBS> {
248        EncodedUint::new_be(self)
249    }
250
251    /// Serialize as little endian bytes.
252    #[must_use]
253    pub const fn to_le_bytes(&self) -> EncodedUint<LIMBS> {
254        EncodedUint::new_le(self)
255    }
256}
257
258/// [`Uint`] encoded as bytes.
259// Until const generic expressions are stable, we cannot statically declare a `u8` array
260// of the size `LIMBS * Limb::BYTES`.
261// So instead we use the array of words, and treat it as an array of bytes.
262// It's a little hacky, but it works, because the array is guaranteed to be contiguous.
263#[derive(Copy, Clone, Debug, PartialEq, Eq)]
264pub struct EncodedUint<const LIMBS: usize>([Word; LIMBS]);
265
266#[allow(unsafe_code)]
267const fn cast_slice(limbs: &[Word]) -> &[u8] {
268    let new_len = size_of_val(limbs);
269
270    // SAFETY:
271    // - `size_of_val` returns the size of `limbs` in bytes
272    // - We are creating a new slice of the same size, but casting word to bytes, so we don't need
273    //   to worry about alignment because bytes are always aligned
274    unsafe { core::slice::from_raw_parts(limbs.as_ptr() as *mut u8, new_len) }
275}
276
277#[allow(unsafe_code)]
278const fn cast_slice_mut(limbs: &mut [Word]) -> &mut [u8] {
279    let new_len = size_of_val(limbs);
280
281    // SAFETY:
282    // - `size_of_val` returns the size of `limbs` in bytes
283    // - We are creating a new slice of the same size, but casting word to bytes, so we don't need
284    //   to worry about alignment because bytes are always aligned
285    unsafe { core::slice::from_raw_parts_mut(limbs.as_mut_ptr().cast::<u8>(), new_len) }
286}
287
288impl<const LIMBS: usize> EncodedUint<LIMBS> {
289    /// Extracts a byte slice containing the entire contents.
290    #[must_use]
291    pub const fn as_slice(&self) -> &[u8] {
292        cast_slice(&self.0)
293    }
294
295    /// Extracts a mutable byte slice containing the entire contents.
296    pub const fn as_mut_slice(&mut self) -> &mut [u8] {
297        cast_slice_mut(&mut self.0)
298    }
299
300    const fn new_le(value: &Uint<LIMBS>) -> Self {
301        let mut buffer = [0; LIMBS];
302        let mut i = 0;
303
304        while i < LIMBS {
305            let src_bytes = &value.limbs[i].0.to_le_bytes();
306
307            // We could cast the whole `buffer` to bytes at once,
308            // but IndexMut does not work in const context.
309            let dst_bytes: &mut [u8] = cast_slice_mut(core::slice::from_mut(&mut buffer[i]));
310
311            // `copy_from_slice` can be used here when MSRV moves past 1.87
312            let mut j = 0;
313            while j < Limb::BYTES {
314                dst_bytes[j] = src_bytes[j];
315                j += 1;
316            }
317
318            i += 1;
319        }
320        Self(buffer)
321    }
322
323    const fn new_be(value: &Uint<LIMBS>) -> Self {
324        let mut buffer = [0; LIMBS];
325        let mut i = 0;
326        while i < LIMBS {
327            let src_bytes = &value.limbs[i].0.to_be_bytes();
328
329            // We could cast the whole `buffer` to bytes at once,
330            // but IndexMut does not work in const context.
331            let dst_bytes: &mut [u8] =
332                cast_slice_mut(core::slice::from_mut(&mut buffer[LIMBS - 1 - i]));
333
334            // `copy_from_slice` can be used here when MSRV moves past 1.87
335            let mut j = 0;
336            while j < Limb::BYTES {
337                dst_bytes[j] = src_bytes[j];
338                j += 1;
339            }
340
341            i += 1;
342        }
343        Self(buffer)
344    }
345}
346
347impl<const LIMBS: usize> Default for EncodedUint<LIMBS> {
348    fn default() -> Self {
349        Self([0; LIMBS])
350    }
351}
352
353impl<const LIMBS: usize> AsRef<[u8]> for EncodedUint<LIMBS> {
354    fn as_ref(&self) -> &[u8] {
355        self.as_slice()
356    }
357}
358
359impl<const LIMBS: usize> AsMut<[u8]> for EncodedUint<LIMBS> {
360    fn as_mut(&mut self) -> &mut [u8] {
361        self.as_mut_slice()
362    }
363}
364
365impl<const LIMBS: usize> Deref for EncodedUint<LIMBS> {
366    type Target = [u8];
367    fn deref(&self) -> &Self::Target {
368        self.as_ref()
369    }
370}
371
372impl<const BYTES: usize, const LIMBS: usize> From<EncodedUint<LIMBS>> for [u8; BYTES]
373where
374    EncodedUint<LIMBS>: EncodedSize<Target = [u8; BYTES]>,
375{
376    #[inline]
377    fn from(input: EncodedUint<LIMBS>) -> Self {
378        Self::from(&input)
379    }
380}
381
382impl<const BYTES: usize, const LIMBS: usize> From<&EncodedUint<LIMBS>> for [u8; BYTES]
383where
384    EncodedUint<LIMBS>: EncodedSize<Target = [u8; BYTES]>,
385{
386    #[inline]
387    fn from(input: &EncodedUint<LIMBS>) -> Self {
388        let mut output = [0u8; BYTES];
389        output.as_mut_slice().copy_from_slice(input.as_ref());
390        output
391    }
392}
393
394impl<const BYTES: usize, const LIMBS: usize> From<[u8; BYTES]> for EncodedUint<LIMBS>
395where
396    [u8; BYTES]: EncodedSize<Target = EncodedUint<LIMBS>>,
397{
398    #[inline]
399    fn from(input: [u8; BYTES]) -> Self {
400        Self::from(&input)
401    }
402}
403
404impl<const BYTES: usize, const LIMBS: usize> From<&[u8; BYTES]> for EncodedUint<LIMBS>
405where
406    [u8; BYTES]: EncodedSize<Target = EncodedUint<LIMBS>>,
407{
408    #[inline]
409    fn from(input: &[u8; BYTES]) -> Self {
410        let mut output = Self::default();
411        output.as_mut().copy_from_slice(input.as_ref());
412        output
413    }
414}
415
416/// Returned if an object cannot be instantiated from the given byte slice.
417#[derive(Clone, Copy, Debug, PartialEq, Eq)]
418pub struct TryFromSliceError;
419
420impl fmt::Display for TryFromSliceError {
421    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
422        write!(f, "TryFromSliceError")
423    }
424}
425
426impl core::error::Error for TryFromSliceError {}
427
428impl<'a, const LIMBS: usize> TryFrom<&'a [u8]> for EncodedUint<LIMBS> {
429    type Error = TryFromSliceError;
430
431    fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
432        if bytes.len() != Uint::<LIMBS>::BYTES {
433            return Err(TryFromSliceError);
434        }
435        let mut result = Self::default();
436        result.as_mut().copy_from_slice(bytes);
437        Ok(result)
438    }
439}
440
441impl<const LIMBS: usize> Encoding for Uint<LIMBS> {
442    type Repr = EncodedUint<LIMBS>;
443
444    #[inline]
445    fn from_be_bytes(bytes: Self::Repr) -> Self {
446        Self::from_be_slice(bytes.as_ref())
447    }
448
449    #[inline]
450    fn from_le_bytes(bytes: Self::Repr) -> Self {
451        Self::from_le_slice(bytes.as_ref())
452    }
453
454    #[inline]
455    fn to_be_bytes(&self) -> Self::Repr {
456        self.to_be_bytes()
457    }
458
459    #[inline]
460    fn to_le_bytes(&self) -> Self::Repr {
461        self.to_le_bytes()
462    }
463}
464
465/// Decode a single nibble of upper or lower hex
466#[inline(always)]
467#[allow(clippy::cast_sign_loss)]
468const fn decode_nibble(src: u8) -> u16 {
469    let byte = src as i16;
470    let mut ret: i16 = -1;
471
472    // 0-9  0x30-0x39
473    // if (byte > 0x2f && byte < 0x3a) ret += byte - 0x30 + 1; // -47
474    ret += (((0x2fi16 - byte) & (byte - 0x3a)) >> 8) & (byte - 47);
475    // A-F  0x41-0x46
476    // if (byte > 0x40 && byte < 0x47) ret += byte - 0x41 + 10 + 1; // -54
477    ret += (((0x40i16 - byte) & (byte - 0x47)) >> 8) & (byte - 54);
478    // a-f  0x61-0x66
479    // if (byte > 0x60 && byte < 0x67) ret += byte - 0x61 + 10 + 1; // -86
480    ret += (((0x60i16 - byte) & (byte - 0x67)) >> 8) & (byte - 86);
481
482    ret as u16
483}
484
485/// Decode a single byte encoded as two hexadecimal characters.
486/// Second element of the tuple is non-zero if the `bytes` values are not in the valid range
487/// (0-9, a-z, A-Z).
488#[inline(always)]
489#[allow(clippy::cast_possible_truncation)]
490pub(crate) const fn decode_hex_byte(bytes: [u8; 2]) -> (u8, u16) {
491    let hi = decode_nibble(bytes[0]);
492    let lo = decode_nibble(bytes[1]);
493    let byte = (hi << 4) | lo;
494    let err = byte >> 8;
495    let result = byte as u8;
496    (result, err)
497}
498
499/// Allow decoding of integers into fixed and variable-length types
500pub(crate) trait DecodeByLimb {
501    /// Access the limbs as a mutable slice
502    fn limbs_mut(&mut self) -> &mut [Limb];
503
504    /// Append a new most-significant limb
505    fn push_limb(&mut self, limb: Limb) -> bool;
506}
507
508/// Wrap a `Limb` slice as a target for decoding
509pub(crate) struct SliceDecodeByLimb<'de> {
510    limbs: &'de mut [Limb],
511    len: usize,
512}
513
514impl<'de> SliceDecodeByLimb<'de> {
515    #[inline]
516    pub fn new(limbs: &'de mut [Limb]) -> Self {
517        Self { limbs, len: 0 }
518    }
519}
520
521impl DecodeByLimb for SliceDecodeByLimb<'_> {
522    #[inline]
523    fn push_limb(&mut self, limb: Limb) -> bool {
524        if self.len < self.limbs.len() {
525            self.limbs[self.len] = limb;
526            self.len += 1;
527            true
528        } else {
529            false
530        }
531    }
532
533    #[inline]
534    fn limbs_mut(&mut self) -> &mut [Limb] {
535        &mut self.limbs[..self.len]
536    }
537}
538
539/// Decode an ascii string in base `radix`, writing the result to the `DecodeByLimb` instance `out`.
540///
541/// The input must be a non-empty ascii string, may begin with a `+` character, and may use `_` as a
542/// separator between digits.
543///
544/// # Panics
545/// - if `radix` is not within `RADIX_ENCODING_MIN..=RADIX_ENCODING_MAX`.
546#[allow(clippy::cast_possible_truncation, clippy::panic_in_result_fn)]
547pub(crate) fn radix_decode_str<D: DecodeByLimb>(
548    src: &str,
549    radix: u32,
550    out: &mut D,
551) -> Result<(), DecodeError> {
552    assert!(
553        (RADIX_ENCODING_MIN..=RADIX_ENCODING_MAX).contains(&radix),
554        "unsupported radix"
555    );
556    if radix == 2 || radix == 4 || radix == 16 {
557        radix_decode_str_aligned_digits(src, radix as u8, out)
558    } else {
559        radix_decode_str_digits(src, radix as u8, out)
560    }
561}
562
563#[inline(always)]
564/// Perform basic validation and pre-processing on a digit string
565fn radix_preprocess_str(src: &str) -> Result<&[u8], DecodeError> {
566    // Treat the input as ascii bytes
567    let src_b = src.as_bytes();
568    let mut digits = src_b.strip_prefix(b"+").unwrap_or(src_b);
569
570    if digits.is_empty() {
571        // Blank string or plain "+" not allowed
572        Err(DecodeError::Empty)
573    } else if digits.starts_with(b"_") || digits.ends_with(b"_") {
574        // Leading or trailing underscore not allowed
575        Err(DecodeError::InvalidDigit)
576    } else {
577        // Strip leading zeroes to simplify parsing
578        while digits[0] == b'0' || digits[0] == b'_' {
579            digits = &digits[1..];
580            if digits.is_empty() {
581                break;
582            }
583        }
584        Ok(digits)
585    }
586}
587
588/// Decode a string of digits in base `radix`
589#[allow(clippy::cast_possible_truncation)]
590fn radix_decode_str_digits<D: DecodeByLimb>(
591    src: &str,
592    radix: u8,
593    out: &mut D,
594) -> Result<(), DecodeError> {
595    let digits = radix_preprocess_str(src)?;
596    let mut buf = [0u8; Limb::BITS as _];
597    let mut limb_digits = Word::MAX.ilog(radix.into()) as usize;
598    let mut limb_max = Limb(Word::pow(radix.into(), limb_digits as _));
599    let mut digits_pos = 0;
600    let mut buf_pos = 0;
601
602    while digits_pos < digits.len() {
603        // Parse digits from most significant, to fill buffer limb
604        loop {
605            let digit = match digits[digits_pos] {
606                b @ b'0'..=b'9' => b - b'0',
607                b @ b'a'..=b'z' => b + 10 - b'a',
608                b @ b'A'..=b'Z' => b + 10 - b'A',
609                b'_' => {
610                    digits_pos += 1;
611                    continue;
612                }
613                _ => radix,
614            };
615            if digit >= radix {
616                return Err(DecodeError::InvalidDigit);
617            }
618            buf[buf_pos] = digit;
619            buf_pos += 1;
620            digits_pos += 1;
621
622            if digits_pos == digits.len() || buf_pos == limb_digits {
623                break;
624            }
625        }
626
627        // On the final loop, there may be fewer digits to process
628        if buf_pos < limb_digits {
629            limb_digits = buf_pos;
630            limb_max = Limb(Word::pow(radix.into(), limb_digits as _));
631        }
632
633        // Combine the digit bytes into a limb
634        let mut carry = Limb::ZERO;
635        for c in buf[..limb_digits].iter().copied() {
636            carry = Limb(carry.0 * Word::from(radix) + Word::from(c));
637        }
638        // Multiply the existing limbs by `radix` ^ `limb_digits`,
639        // and add the new least-significant limb
640        for limb in out.limbs_mut().iter_mut() {
641            (*limb, carry) = limb.carrying_mul_add(limb_max, carry, Limb::ZERO);
642        }
643        // Append the new carried limb, if any
644        if carry.0 != 0 && !out.push_limb(carry) {
645            return Err(DecodeError::InputSize);
646        }
647
648        buf_pos = 0;
649        buf[..limb_digits].fill(0);
650    }
651
652    Ok(())
653}
654
655/// Decode digits for bases where an integer number of characters
656/// can represent a saturated Limb (specifically 2, 4, and 16).
657#[allow(clippy::integer_division_remainder_used, reason = "needs triage")]
658fn radix_decode_str_aligned_digits<D: DecodeByLimb>(
659    src: &str,
660    radix: u8,
661    out: &mut D,
662) -> Result<(), DecodeError> {
663    debug_assert!(radix == 2 || radix == 4 || radix == 16);
664
665    let digits = radix_preprocess_str(src)?;
666    let shift = radix.trailing_zeros();
667    let limb_digits = (Limb::BITS / shift) as usize;
668    let mut buf = [0u8; Limb::BITS as _];
669    let mut buf_pos = 0;
670    let mut digits_pos = digits.len();
671
672    while digits_pos > 0 {
673        // Parse digits from the least significant, to fill the buffer limb
674        loop {
675            digits_pos -= 1;
676
677            let digit = match digits[digits_pos] {
678                b @ b'0'..=b'9' => b - b'0',
679                b @ b'a'..=b'z' => b + 10 - b'a',
680                b @ b'A'..=b'Z' => b + 10 - b'A',
681                b'_' => {
682                    // cannot occur when c == 0
683                    continue;
684                }
685                _ => radix,
686            };
687            if digit >= radix {
688                return Err(DecodeError::InvalidDigit);
689            }
690            buf[buf_pos] = digit;
691            buf_pos += 1;
692
693            if digits_pos == 0 || buf_pos == limb_digits {
694                break;
695            }
696        }
697
698        if buf_pos > 0 {
699            // Combine the digit bytes into a limb
700            let mut w: Word = 0;
701            for c in buf[..buf_pos].iter().rev().copied() {
702                w = (w << shift) | Word::from(c);
703            }
704            // Append the new most-significant limb
705            if !out.push_limb(Limb(w)) {
706                return Err(DecodeError::InputSize);
707            }
708
709            buf_pos = 0;
710            buf[..limb_digits].fill(0);
711        }
712    }
713
714    Ok(())
715}
716
717/// Encode a slice of limbs to a string in base `radix`. The result will have no leading
718/// zeros unless the value itself is zero.
719///
720/// # Panics
721/// - if `radix` is not in the range from 2 to 36.
722#[cfg(feature = "alloc")]
723pub(crate) fn radix_encode_limbs_to_string(radix: u32, limbs: &[Limb]) -> String {
724    let mut array_buf = [Limb::ZERO; 128];
725    let mut vec_buf = Vec::new();
726    let limb_count = limbs.len();
727    let buf = if limb_count <= array_buf.len() {
728        array_buf[..limb_count].copy_from_slice(limbs);
729        &mut array_buf[..limb_count]
730    } else {
731        vec_buf.extend_from_slice(limbs);
732        &mut vec_buf[..limb_count]
733    };
734    radix_encode_limbs_mut_to_string(radix, UintRef::new_mut(buf))
735}
736
737/// Encode a slice of limbs to a string in base `radix`. The contents of the slice
738/// will be used as a working buffer. The result will have no leading zeros unless
739/// the value itself is zero.
740///
741/// # Panics
742/// - if `radix` is not in the range from 2 to 36.
743#[cfg(feature = "alloc")]
744pub(crate) fn radix_encode_limbs_mut_to_string(radix: u32, limbs: &mut UintRef) -> String {
745    assert!(
746        (RADIX_ENCODING_MIN..=RADIX_ENCODING_MAX).contains(&radix),
747        "unsupported radix"
748    );
749
750    let mut out;
751    if radix.is_power_of_two() {
752        let size = bitlen::from_limbs(limbs.nlimbs()).div_ceil(radix.trailing_zeros()) as usize;
753        out = vec![0u8; size];
754        radix_encode_limbs_by_shifting(radix, limbs, &mut out[..]);
755    } else {
756        let params = RadixDivisionParams::for_radix(radix);
757        let size = params.encoded_size(limbs.nlimbs());
758        out = vec![0u8; size];
759        params.encode_limbs(limbs, &mut out[..]);
760    }
761    let size = out.len();
762    let mut skip = 0;
763    while skip + 1 < size && out[skip] == b'0' {
764        skip += 1;
765    }
766    if skip > 0 {
767        out.copy_within(skip..size, 0);
768        out.truncate(size - skip);
769    }
770    String::from_utf8(out).expect("utf-8 decoding error")
771}
772
773/// For `radix` values which are a power of two, encode the mutable limb slice to
774/// the output buffer as ASCII characters in base `radix`. Leading zeros are added to
775/// fill `out`. The slice `limbs` is used as a working buffer. Output will be truncated
776/// if the provided buffer is too small.
777#[cfg(feature = "alloc")]
778#[allow(clippy::cast_possible_truncation, reason = "needs triage")]
779#[allow(clippy::integer_division_remainder_used, reason = "needs triage")]
780fn radix_encode_limbs_by_shifting(radix: u32, limbs: &UintRef, out: &mut [u8]) {
781    debug_assert!(radix.is_power_of_two());
782    debug_assert!(!out.is_empty());
783
784    let radix_bits = radix.trailing_zeros();
785    let mask = (radix - 1) as u8;
786    let mut out_idx = out.len();
787    let mut digits: WideWord = 0;
788    let mut digits_bits = 0;
789    let mut digit;
790
791    for limb in limbs.iter().chain([&Limb::ZERO]) {
792        digits_bits += Limb::BITS;
793        digits |= WideWord::from(limb.0) << (digits_bits % Limb::BITS);
794        for _ in 0..((digits_bits / radix_bits) as usize).min(out_idx) {
795            out_idx -= 1;
796            (digit, digits) = ((digits as u8) & mask, digits >> radix_bits);
797            out[out_idx] = if digit < 10 {
798                b'0' + digit
799            } else {
800                b'a' + (digit - 10)
801            };
802            digits_bits -= radix_bits;
803        }
804    }
805
806    out[0..out_idx].fill(b'0');
807}
808
809/// Parameter set used to perform radix encoding by division.
810#[cfg(feature = "alloc")]
811#[derive(Debug, Clone, Copy)]
812pub(crate) struct RadixDivisionParams {
813    radix: u32,
814    digits_per_limb: usize,
815    bits_per_limb: u32,
816    recip_limb: Reciprocal,
817    digits_large: usize,
818    div_large: [Limb; RADIX_ENCODING_LIMBS_LARGE],
819    recip_large: Reciprocal,
820    shift_large: u32,
821}
822
823#[cfg(feature = "alloc")]
824impl RadixDivisionParams {
825    // Generate all valid parameters ahead of time
826    #[allow(trivial_numeric_casts)]
827    const ALL: [Self; 31] = {
828        let mut res = [Self {
829            radix: 0,
830            digits_per_limb: 0,
831            bits_per_limb: 0,
832            recip_limb: Reciprocal::default(),
833            digits_large: 0,
834            div_large: [Limb::ZERO; RADIX_ENCODING_LIMBS_LARGE],
835            shift_large: 0,
836            recip_large: Reciprocal::default(),
837        }; 31];
838        let mut radix: u32 = 3;
839        let mut i: usize = 0;
840        while radix <= RADIX_ENCODING_MAX {
841            if radix.is_power_of_two() {
842                radix += 1;
843                continue;
844            }
845            let digits_limb = Word::MAX.ilog(radix as Word);
846            let div_limb = NonZero::new_unchecked(Limb((radix as Word).pow(digits_limb)));
847            let bits_limb = Limb::BITS - div_limb.as_ref().leading_zeros() - 1;
848            let (div_large, digits_large, shift_large) =
849                radix_large_divisor(radix, div_limb, digits_limb as usize);
850            let recip_large = Reciprocal::new(
851                div_large[RADIX_ENCODING_LIMBS_LARGE - 1]
852                    .to_nz()
853                    .expect_copied("zero divisor"),
854            );
855            res[i] = Self {
856                radix,
857                digits_per_limb: digits_limb as usize,
858                bits_per_limb: bits_limb,
859                recip_limb: Reciprocal::new(div_limb),
860                digits_large,
861                div_large,
862                shift_large,
863                recip_large,
864            };
865            radix += 1;
866            i += 1;
867        }
868        res
869    };
870
871    #[allow(trivial_numeric_casts)]
872    pub const fn for_radix(radix: u32) -> Self {
873        assert!(
874            !(radix < RADIX_ENCODING_MIN || radix > RADIX_ENCODING_MAX),
875            "invalid radix for division"
876        );
877        let ret = Self::ALL[(radix + radix.leading_zeros() - 33) as usize];
878        assert!(ret.radix == radix, "radix lookup failure");
879        ret
880    }
881
882    /// Get the minimum size of the required output buffer for encoding a set of limbs.
883    pub const fn encoded_size(&self, limb_count: usize) -> usize {
884        // a slightly pessimistic estimate
885        limb_count * (self.digits_per_limb + 1)
886    }
887
888    /// Encode the mutable limb slice to the output buffer as ASCII characters in base
889    /// `radix`. Leading zeros are added to fill `out`. The slice `limbs` is used as a
890    /// working buffer. Output will be truncated if the provided buffer is too small.
891    #[allow(clippy::cast_possible_truncation)]
892    pub fn encode_limbs(&self, mut limbs: &mut UintRef, out: &mut [u8]) {
893        let mut out_idx = out.len();
894        let mut remain = Uint::<RADIX_ENCODING_LIMBS_LARGE>::ZERO;
895
896        while out_idx > 0 {
897            let (digits, next_idx) = if limbs.nlimbs() >= RADIX_ENCODING_THRESHOLD_LARGE {
898                let div_large = UintRef::new(&self.div_large);
899                let remain_mut = remain.as_mut_uint_ref();
900
901                // Divide by the large divisor
902                let limbs_hi = limbs.shl_assign_limb_vartime(self.shift_large);
903                let rem_high = limbs.div_rem_large_shifted::<true>(
904                    limbs_hi,
905                    div_large,
906                    RADIX_ENCODING_LIMBS_LARGE as u32,
907                    self.recip_large,
908                );
909                let limbs_rem;
910                // At this point, the limbs at and above RADIX_ENCODING_LIMBS_LARGE represent
911                // the quotient, and the limbs below (plus rem_high) represent the remainder
912                (limbs_rem, limbs) = limbs.split_at_mut(RADIX_ENCODING_LIMBS_LARGE - 1);
913
914                // Copy out the remainder
915                remain_mut
916                    .leading_mut(RADIX_ENCODING_LIMBS_LARGE - 1)
917                    .copy_from(limbs_rem);
918                remain_mut.limbs[RADIX_ENCODING_LIMBS_LARGE - 1] = rem_high;
919                remain_mut.shr_assign_limb_vartime(self.shift_large);
920
921                (remain_mut, out_idx.saturating_sub(self.digits_large))
922            } else {
923                (core::mem::replace(&mut limbs, UintRef::new_mut(&mut [])), 0)
924            };
925
926            // Encode the next batch of digits
927            self.encode_limbs_small(digits, &mut out[next_idx..out_idx]);
928            out_idx = next_idx;
929        }
930    }
931
932    #[allow(trivial_numeric_casts, reason = "needs triage")]
933    #[allow(clippy::cast_possible_truncation, reason = "needs triage")]
934    #[allow(clippy::integer_division_remainder_used, reason = "needs triage")]
935    fn encode_limbs_small(&self, mut limbs: &mut UintRef, out: &mut [u8]) {
936        const DIGITS: &[u8; 36] = b"0123456789abcdefghijklmnopqrstuvwxyz";
937
938        let radix = Word::from(self.radix);
939        let mut out_idx = out.len();
940        let mut bits_acc = 0;
941        let (mut digit, mut digits_word);
942
943        while out_idx > 0 {
944            if limbs.is_empty() {
945                out[0..out_idx].fill(b'0');
946                break;
947            }
948
949            // The remainder represents a digit in base `radix ** digits_per_limb`
950            let limbs_hi = limbs.shl_assign_limb_vartime(self.recip_limb.shift());
951            digits_word = limbs
952                .div_rem_limb_with_reciprocal_shifted(limbs_hi, &self.recip_limb)
953                .0;
954
955            // Reduce the length of the input as we consume a limb's worth of bits (conservatively)
956            bits_acc += self.bits_per_limb;
957            if bits_acc >= Limb::BITS {
958                bits_acc -= Limb::BITS;
959                limbs = limbs.leading_mut(limbs.nlimbs().saturating_sub(1));
960            }
961
962            // Output the individual digits
963            for _ in 0..self.digits_per_limb.min(out_idx) {
964                out_idx -= 1;
965                (digits_word, digit) = (digits_word / radix, (digits_word % radix) as usize);
966                out[out_idx] = DIGITS[digit];
967            }
968        }
969    }
970}
971
972/// Compute the maximum radix divisor for a number of limbs.
973/// Returns a pair of the large divisor value and the number of digits,
974/// such that `divisor = radix ** digits`. The value `div_limb` is the
975/// largest power of `radix` that can fit within a limb.
976#[cfg(feature = "alloc")]
977#[allow(trivial_numeric_casts)]
978const fn radix_large_divisor<const LIMBS: usize>(
979    radix: u32,
980    div_limb: NonZero<Limb>,
981    digits_limb: usize,
982) -> ([Limb; LIMBS], usize, u32) {
983    let mut out = [Limb::ZERO; LIMBS];
984    let mut digits_large = digits_limb;
985    let mut top = 1;
986    out[0] = div_limb.get_copy();
987    // Calculate largest power of div_limb (itself a power of radix)
988    while top < LIMBS {
989        let mut carry = Limb::ZERO;
990        let mut j = 0;
991        while j < top {
992            (out[j], carry) = out[j].carrying_mul_add(div_limb.get_copy(), carry, Limb::ZERO);
993            j += 1;
994        }
995        if carry.0 != 0 {
996            out[top] = carry;
997            top += 1;
998        }
999        digits_large += digits_limb;
1000    }
1001    // Multiply by radix while we can do so without overflowing
1002    let mut out_test = out;
1003    loop {
1004        let mut carry = Limb::ZERO;
1005        let mut j = 0;
1006        while j < LIMBS {
1007            (out_test[j], carry) = out[j].carrying_mul_add(Limb(radix as Word), carry, Limb::ZERO);
1008            j += 1;
1009        }
1010        if carry.0 == 0 {
1011            out = out_test;
1012            digits_large += 1;
1013        } else {
1014            break;
1015        }
1016    }
1017
1018    let out_mut = UintRef::new_mut(&mut out);
1019    let shift = out_mut.leading_zeros();
1020    out_mut.shl_assign_limb_vartime(shift);
1021    (out, digits_large, shift)
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026    use crate::{DecodeError, EncodedUint, Limb, U64, U128};
1027    use hex_literal::hex;
1028
1029    #[cfg(feature = "alloc")]
1030    use {
1031        super::{radix_encode_limbs_to_string, radix_large_divisor},
1032        crate::{NonZero, Uint, Word},
1033        alloc::format,
1034    };
1035
1036    cpubits::cpubits! {
1037        32 => {
1038            use crate::U64 as UintEx;
1039
1040            #[test]
1041            fn from_be_slice() {
1042                let bytes = hex!("0011223344556677");
1043                let n = UintEx::from_be_slice(&bytes);
1044                assert_eq!(n.as_limbs(), &[Limb(0x44556677), Limb(0x00112233)]);
1045            }
1046
1047            #[test]
1048            fn from_le_slice() {
1049                let bytes = hex!("7766554433221100");
1050                let n = UintEx::from_le_slice(&bytes);
1051                assert_eq!(n.as_limbs(), &[Limb(0x44556677), Limb(0x00112233)]);
1052            }
1053
1054            #[test]
1055            fn from_be_hex() {
1056                let n = UintEx::from_be_hex("0011223344556677");
1057                assert_eq!(n.as_limbs(), &[Limb(0x44556677), Limb(0x00112233)]);
1058            }
1059
1060            #[test]
1061            fn from_le_hex() {
1062                let n = UintEx::from_le_hex("7766554433221100");
1063                assert_eq!(n.as_limbs(), &[Limb(0x44556677), Limb(0x00112233)]);
1064            }
1065        }
1066        64 => {
1067            use crate::U128 as UintEx;
1068
1069            #[test]
1070            fn from_be_slice() {
1071                let bytes = hex!("00112233445566778899aabbccddeeff");
1072                let n = UintEx::from_be_slice(&bytes);
1073                assert_eq!(
1074                    n.as_limbs(),
1075                    &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)]
1076                );
1077            }
1078
1079            #[test]
1080            fn from_le_slice() {
1081                let bytes = hex!("ffeeddccbbaa99887766554433221100");
1082                let n = UintEx::from_le_slice(&bytes);
1083                assert_eq!(
1084                    n.as_limbs(),
1085                    &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)]
1086                );
1087            }
1088
1089            #[test]
1090            fn from_be_hex() {
1091                let n = UintEx::from_be_hex("00112233445566778899aabbccddeeff");
1092                assert_eq!(
1093                    n.as_limbs(),
1094                    &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)]
1095                );
1096            }
1097
1098            #[test]
1099            fn from_le_hex() {
1100                let n = UintEx::from_le_hex("ffeeddccbbaa99887766554433221100");
1101                assert_eq!(
1102                    n.as_limbs(),
1103                    &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)]
1104                );
1105            }
1106        }
1107    }
1108
1109    #[cfg(feature = "alloc")]
1110    #[test]
1111    fn hex_upper() {
1112        let hex = "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD";
1113        let n = U128::from_be_hex(hex);
1114        assert_eq!(hex, format!("{n:X}"));
1115    }
1116
1117    #[cfg(feature = "alloc")]
1118    #[test]
1119    fn hex_lower() {
1120        let hex = "aaaaaaaabbbbbbbbccccccccdddddddd";
1121        let n = U128::from_be_hex(hex);
1122        assert_eq!(hex, format!("{n:x}"));
1123    }
1124
1125    #[cfg(feature = "alloc")]
1126    #[test]
1127    fn fmt_binary() {
1128        let hex = "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD";
1129        let n = U128::from_be_hex(hex);
1130        let expect = "\
1131            1010101010101010101010101010101010111011101110111011101110111011\
1132            1100110011001100110011001100110011011101110111011101110111011101";
1133        assert_eq!(expect, format!("{n:b}"));
1134    }
1135
1136    #[test]
1137    fn from_str_radix_disallowed() {
1138        let tests = [
1139            ("", 10, DecodeError::Empty),
1140            ("+", 10, DecodeError::Empty),
1141            ("_", 10, DecodeError::InvalidDigit),
1142            ("0_", 10, DecodeError::InvalidDigit),
1143            ("0_", 10, DecodeError::InvalidDigit),
1144            ("a", 10, DecodeError::InvalidDigit),
1145            (".", 10, DecodeError::InvalidDigit),
1146            (
1147                "99999999999999999999999999999999",
1148                10,
1149                DecodeError::InputSize,
1150            ),
1151        ];
1152        for (input, radix, expect) in tests {
1153            assert_eq!(U64::from_str_radix_vartime(input, radix), Err(expect));
1154        }
1155    }
1156
1157    #[test]
1158    fn from_str_radix_2() {
1159        let buf = &[b'1'; 128];
1160        let radix = U128::from_u64(2);
1161        let radix_max = U128::from_u64(1);
1162        let mut last: Option<U128> = None;
1163        for idx in (1..buf.len()).rev() {
1164            let res = U128::from_str_radix_vartime(
1165                core::str::from_utf8(&buf[..idx]).expect("utf-8 error"),
1166                2,
1167            )
1168            .expect("error decoding");
1169            assert!(!bool::from(res.is_zero()));
1170            if let Some(prev) = last {
1171                assert_eq!(res.saturating_mul(&radix).saturating_add(&radix_max), prev);
1172            }
1173            last = Some(res);
1174        }
1175        assert_eq!(last, Some(radix_max));
1176    }
1177
1178    #[test]
1179    fn from_str_radix_5() {
1180        let buf = &[b'4'; 55];
1181        let radix = U128::from_u64(5);
1182        let radix_max = U128::from_u64(4);
1183        let mut last: Option<U128> = None;
1184        for idx in (1..buf.len()).rev() {
1185            let res = U128::from_str_radix_vartime(
1186                core::str::from_utf8(&buf[..idx]).expect("utf-8 error"),
1187                5,
1188            )
1189            .expect("error decoding");
1190            assert!(!bool::from(res.is_zero()));
1191            if let Some(prev) = last {
1192                assert_eq!(res.saturating_mul(&radix).saturating_add(&radix_max), prev);
1193            }
1194            last = Some(res);
1195        }
1196        assert_eq!(last, Some(radix_max));
1197    }
1198
1199    #[test]
1200    fn from_str_radix_10() {
1201        let dec = "+340_282_366_920_938_463_463_374_607_431_768_211_455";
1202        let res = U128::from_str_radix_vartime(dec, 10).expect("error decoding");
1203        assert_eq!(res, U128::MAX);
1204    }
1205
1206    #[cfg(feature = "alloc")]
1207    #[test]
1208    fn from_str_radix_16() {
1209        let hex = "fedcba9876543210fedcba9876543210";
1210        let res = U128::from_str_radix_vartime(hex, 16).expect("error decoding");
1211        assert_eq!(hex, format!("{res:x}"));
1212    }
1213
1214    #[cfg(feature = "alloc")]
1215    #[test]
1216    fn encode_radix_8() {
1217        assert_eq!(
1218            &radix_encode_limbs_to_string(8, U128::MAX.as_limbs()),
1219            "3777777777777777777777777777777777777777777"
1220        );
1221        assert_eq!(&radix_encode_limbs_to_string(8, U128::ZERO.as_limbs()), "0");
1222        assert_eq!(&radix_encode_limbs_to_string(8, U128::ONE.as_limbs()), "1");
1223
1224        let hex = "1234567123456765432107654321";
1225        let res = U128::from_str_radix_vartime(hex, 8).expect("error decoding");
1226        let out = radix_encode_limbs_to_string(8, res.as_limbs());
1227        assert_eq!(&out, hex);
1228    }
1229
1230    #[cfg(feature = "alloc")]
1231    #[test]
1232    fn encode_radix_10() {
1233        assert_eq!(
1234            &radix_encode_limbs_to_string(10, U128::MAX.as_limbs()),
1235            "340282366920938463463374607431768211455"
1236        );
1237        assert_eq!(
1238            &radix_encode_limbs_to_string(10, U128::ZERO.as_limbs()),
1239            "0"
1240        );
1241        assert_eq!(&radix_encode_limbs_to_string(10, U128::ONE.as_limbs()), "1");
1242    }
1243
1244    #[cfg(feature = "alloc")]
1245    #[test]
1246    fn encode_radix_16() {
1247        let hex = "fedcba9876543210fedcba9876543210";
1248        let res = U128::from_str_radix_vartime(hex, 16).expect("error decoding");
1249        let out = radix_encode_limbs_to_string(16, res.as_limbs());
1250        assert_eq!(&out, hex);
1251    }
1252
1253    #[cfg(all(feature = "rand_core", feature = "alloc"))]
1254    #[test]
1255    fn encode_radix_round_trip() {
1256        use crate::{Random, U256};
1257        use rand_core::SeedableRng;
1258        let mut rng = chacha20::ChaCha8Rng::seed_from_u64(1);
1259
1260        let rounds = if cfg!(miri) { 10 } else { 100 };
1261        for _ in 0..rounds {
1262            let uint = U256::random_from_rng(&mut rng);
1263            for radix in 2..=36 {
1264                let enc = uint.to_string_radix_vartime(radix);
1265                let res = U256::from_str_radix_vartime(&enc, radix).expect("decoding error");
1266                assert_eq!(
1267                    res, uint,
1268                    "round trip failure: radix {radix} encoded {uint} as {enc}"
1269                );
1270            }
1271        }
1272    }
1273
1274    cpubits::cpubits! {
1275        32 => {
1276            #[test]
1277            fn encode_be_hex() {
1278                let n = UintEx::from_be_hex("0011223344556677");
1279
1280                let bytes = n.to_be_bytes();
1281                assert_eq!(bytes.as_ref(), hex!("0011223344556677"));
1282
1283                #[cfg(feature = "der")]
1284                assert_eq!(super::der::count_der_be_bytes(&n.limbs), 7);
1285            }
1286        }
1287        64 => {
1288            #[test]
1289            fn encode_be_hex() {
1290                let n = UintEx::from_be_hex("00112233445566778899aabbccddeeff");
1291
1292                let bytes = n.to_be_bytes();
1293                assert_eq!(bytes.as_ref(), hex!("00112233445566778899aabbccddeeff"));
1294
1295                #[cfg(feature = "der")]
1296                assert_eq!(super::der::count_der_be_bytes(&n.limbs), 15);
1297            }
1298        }
1299    }
1300
1301    #[test]
1302    fn infer_sizes() {
1303        let bytes = b"0011223344556677";
1304
1305        let n = EncodedUint::from(bytes);
1306        assert_eq!(n.as_slice(), bytes);
1307
1308        let n = EncodedUint::from(*bytes);
1309        assert_eq!(n.as_slice(), bytes);
1310
1311        let n: [u8; 16] = EncodedUint::from(bytes).into();
1312        assert_eq!(n.as_slice(), bytes);
1313
1314        let n: [u8; 16] = (&EncodedUint::from(bytes)).into();
1315        assert_eq!(n.as_slice(), bytes);
1316    }
1317
1318    #[allow(clippy::cast_lossless)]
1319    #[allow(trivial_numeric_casts)]
1320    #[cfg(feature = "alloc")]
1321    #[test]
1322    fn test_radix_large_divisor() {
1323        let radix = 5u32;
1324        let digits_limb = Word::MAX.ilog(radix as Word);
1325        let div_limb = NonZero::new_unchecked(Limb((radix as Word).pow(digits_limb)));
1326        let (div_large, _digits_large, _shift_large) =
1327            radix_large_divisor::<4>(radix, div_limb, digits_limb as usize);
1328        assert!(
1329            Uint::new(div_large)
1330                .checked_mul(&Uint::<1>::from_u32(radix))
1331                .is_none()
1332                .to_bool()
1333        );
1334    }
1335}