Skip to main content

crypto_bigint/uint/boxed/
encoding.rs

1//! Const-friendly decoding operations for [`BoxedUint`].
2
3use super::BoxedUint;
4use crate::{CtEq, CtOption, DecodeError, Encoding, Limb, Word, bitlen, uint::encoding};
5use alloc::{boxed::Box, string::String, vec::Vec};
6
7#[cfg(feature = "serde")]
8mod serde;
9
10impl BoxedUint {
11    /// Create a new [`BoxedUint`] from the provided big endian bytes.
12    ///
13    /// The `bits_precision` argument represents the precision of the resulting integer, which is
14    /// fixed as this type is not arbitrary-precision.
15    ///
16    /// The new [`BoxedUint`] will be created with `bits_precision` rounded up to a multiple of
17    /// [`Limb::BITS`].
18    ///
19    /// # Errors
20    /// - Returns [`DecodeError::InputSize`] if the length of `bytes` is larger than
21    ///   `bits_precision` (rounded up to a multiple of 8).
22    /// - Returns [`DecodeError::Precision`] if the size of the decoded integer is larger than
23    ///   `bits_precision`.
24    pub fn from_be_slice(bytes: &[u8], bits_precision: u32) -> Result<Self, DecodeError> {
25        if bytes.len() > bitlen::to_bytes(bits_precision) {
26            return Err(DecodeError::InputSize);
27        }
28
29        Ok(Self::from_be_slice_truncated(bytes, bits_precision))
30    }
31
32    /// Create a new [`BoxedUint`] from the provided big endian bytes, zero padding if necessary,
33    /// and truncating to the least significant bytes in the event the given amount of data exceeds
34    /// `bits_precision`.
35    #[must_use]
36    #[allow(clippy::missing_panics_doc, reason = "should not panic in practice")]
37    pub fn from_be_slice_truncated(bytes: &[u8], bits_precision: u32) -> Self {
38        let mut ret = Self::zero_with_precision(bits_precision);
39        encoding::fill_limbs_from_be_slice_truncated(bytes, &mut ret.limbs, bits_precision)
40            .expect("should fit in requested precision");
41        ret
42    }
43
44    /// Create a new [`BoxedUint`] from the provided big endian bytes, automatically selecting its
45    /// precision based on the size of the input.
46    ///
47    /// This method is variable-time with respect to all subsequent operations since it chooses the
48    /// limb count based on the input size, and is therefore only suitable for public inputs.
49    ///
50    /// When working with secret values, use [`BoxedUint::from_be_slice`].
51    #[must_use]
52    pub fn from_be_slice_vartime(bytes: &[u8]) -> Self {
53        Self::from_be_slice_truncated(bytes, bitlen::from_bytes(bytes.len()))
54    }
55
56    /// Create a new [`BoxedUint`] from the provided little endian bytes.
57    ///
58    /// The `bits_precision` argument represents the precision of the resulting integer, which is
59    /// fixed as this type is not arbitrary-precision.
60    ///
61    /// The new [`BoxedUint`] will be created with `bits_precision`
62    /// rounded up to a multiple of [`Limb::BITS`].
63    ///
64    /// # Errors
65    /// - Returns [`DecodeError::InputSize`] if the length of `bytes` is larger than
66    ///   `bits_precision` (rounded up to a multiple of 8).
67    /// - Returns [`DecodeError::Precision`] if the size of the decoded integer is larger than
68    ///   `bits_precision`.
69    pub fn from_le_slice(bytes: &[u8], bits_precision: u32) -> Result<Self, DecodeError> {
70        if bytes.len() > bitlen::to_bytes(bits_precision) {
71            return Err(DecodeError::InputSize);
72        }
73
74        Ok(Self::from_le_slice_truncated(bytes, bits_precision))
75    }
76
77    /// Create a new [`BoxedUint`] from the provided little endian bytes, zero padding if necessary,
78    /// and truncating to the least significant bytes in the event the given amount of data exceeds
79    /// `bits_precision`.
80    #[must_use]
81    #[allow(clippy::missing_panics_doc, reason = "should not panic in practice")]
82    pub fn from_le_slice_truncated(bytes: &[u8], bits_precision: u32) -> Self {
83        let mut ret = Self::zero_with_precision(bits_precision);
84        encoding::fill_limbs_from_le_slice_truncated(bytes, &mut ret.limbs, bits_precision)
85            .expect("should fit in requested precision");
86        ret
87    }
88
89    /// Create a new [`BoxedUint`] from the provided little endian bytes, automatically selecting
90    /// its precision based on the size of the input.
91    ///
92    /// This method is variable-time with respect to all subsequent operations since it chooses the
93    /// limb count based on the input size, and is therefore only suitable for public inputs.
94    ///
95    /// When working with secret values, use [`BoxedUint::from_le_slice`].
96    #[must_use]
97    pub fn from_le_slice_vartime(bytes: &[u8]) -> Self {
98        Self::from_le_slice_truncated(bytes, bitlen::from_bytes(bytes.len()))
99    }
100
101    /// Serialize this [`BoxedUint`] as big-endian.
102    #[inline]
103    #[must_use]
104    pub fn to_be_bytes(&self) -> Box<[u8]> {
105        let mut out = vec![0u8; self.limbs.len() * Limb::BYTES];
106
107        for (src, dst) in self
108            .limbs
109            .iter()
110            .rev()
111            .cloned()
112            .zip(out.chunks_exact_mut(Limb::BYTES))
113        {
114            dst.copy_from_slice(&src.0.to_be_bytes());
115        }
116
117        out.into()
118    }
119
120    /// Serialize this [`BoxedUint`] as big-endian without leading zeroes.
121    #[inline]
122    #[must_use]
123    #[allow(clippy::integer_division_remainder_used, reason = "vartime")]
124    pub fn to_be_bytes_trimmed_vartime(&self) -> Box<[u8]> {
125        let zeroes = self.leading_zeros() as usize / 8;
126        (&self.to_be_bytes()[zeroes..]).into()
127    }
128
129    /// Serialize this [`BoxedUint`] as little-endian.
130    #[inline]
131    #[must_use]
132    pub fn to_le_bytes(&self) -> Box<[u8]> {
133        let mut out = vec![0u8; self.limbs.len() * Limb::BYTES];
134
135        for (src, dst) in self
136            .limbs
137            .iter()
138            .cloned()
139            .zip(out.chunks_exact_mut(Limb::BYTES))
140        {
141            dst.copy_from_slice(&src.0.to_le_bytes());
142        }
143
144        out.into()
145    }
146
147    /// Serialize this [`BoxedUint`] as little-endian without trailing zeroes.
148    #[inline]
149    #[must_use]
150    #[allow(clippy::integer_division_remainder_used, reason = "vartime")]
151    pub fn to_le_bytes_trimmed_vartime(&self) -> Box<[u8]> {
152        let zeroes = self.leading_zeros() as usize / 8;
153        let bytes = self.to_le_bytes();
154        (&bytes[..bytes.len() - zeroes]).into()
155    }
156
157    /// Create a new [`BoxedUint`] from the provided big endian hex string.
158    ///
159    /// # Panics
160    /// - if hex string is not the expected size
161    #[must_use]
162    pub fn from_be_hex(hex: &str, bits_precision: u32) -> CtOption<Self> {
163        let nlimbs = bitlen::to_limbs(bits_precision);
164        let bytes = hex.as_bytes();
165
166        assert_eq!(
167            bytes.len(),
168            Limb::BYTES * nlimbs * 2,
169            "hex string is not the expected size"
170        );
171
172        let mut res = vec![Limb::ZERO; nlimbs];
173        let mut buf = [0u8; Limb::BYTES];
174        let mut i = 0;
175        let mut err = 0;
176
177        while i < nlimbs {
178            let mut j = 0;
179            while j < Limb::BYTES {
180                let offset = (i * Limb::BYTES + j) * 2;
181                let (result, byte_err) =
182                    encoding::decode_hex_byte([bytes[offset], bytes[offset + 1]]);
183                err |= byte_err;
184                buf[j] = result;
185                j += 1;
186            }
187            res[nlimbs - i - 1] = Limb(Word::from_be_bytes(buf));
188            i += 1;
189        }
190
191        CtOption::new(Self { limbs: res.into() }, err.ct_eq(&0))
192    }
193
194    /// Create a new [`BoxedUint`] from a big-endian string in a given base.
195    ///
196    /// The string may begin with a `+` character, and may use underscore
197    /// characters to separate digits.
198    ///
199    /// # Errors
200    /// - Returns [`DecodeError::InvalidDigit`] if the input value contains non-digit characters or
201    ///   digits outside of the range `0..radix`.
202    ///
203    /// # Panics
204    /// - if `radix` is not in the range from 2 to 36.
205    pub fn from_str_radix_vartime(src: &str, radix: u32) -> Result<Self, DecodeError> {
206        let mut dec = VecDecodeByLimb::default();
207        encoding::radix_decode_str(src, radix, &mut dec)?;
208        Ok(Self {
209            limbs: dec.limbs.into(),
210        })
211    }
212
213    /// Create a new [`BoxedUint`] from a big-endian string in a given base,
214    /// with a given precision.
215    ///
216    /// The string may begin with a `+` character, and may use underscore
217    /// characters to separate digits.
218    ///
219    /// The `bits_precision` argument represents the precision of the resulting integer, which is
220    /// fixed as this type is not arbitrary-precision.
221    ///
222    /// The new [`BoxedUint`] will be created with `bits_precision` rounded up to a multiple
223    /// of [`Limb::BITS`].
224    ///
225    /// # Errors
226    /// - Returns [`DecodeError::InputSize`] if the length of `bytes` is larger than
227    ///   `bits_precision` (rounded up to a multiple of 8).
228    /// - Returns [`DecodeError::InvalidDigit`] if the input value contains non-digit characters or
229    ///   digits are outside the range `0..radix`.
230    /// - Returns [`DecodeError::Precision`] if the size of the decoded integer is larger than
231    ///   `bits_precision`.
232    ///
233    /// # Panics
234    /// - if `radix` is not in the range from 2 to 36.
235    pub fn from_str_radix_with_precision_vartime(
236        src: &str,
237        radix: u32,
238        bits_precision: u32,
239    ) -> Result<Self, DecodeError> {
240        let mut ret = Self::zero_with_precision(bits_precision);
241        encoding::radix_decode_str(
242            src,
243            radix,
244            &mut encoding::SliceDecodeByLimb::new(&mut ret.limbs),
245        )?;
246        if bits_precision < ret.bits() {
247            return Err(DecodeError::Precision);
248        }
249        Ok(ret)
250    }
251
252    /// Format a [`BoxedUint`] as a string in a given base.
253    ///
254    /// # Panics
255    /// - if `radix` is not in the range from 2 to 36.
256    #[must_use]
257    pub fn to_string_radix_vartime(&self, radix: u32) -> String {
258        encoding::radix_encode_limbs_to_string(radix, &self.limbs)
259    }
260}
261
262impl Encoding for BoxedUint {
263    type Repr = Box<[u8]>;
264
265    fn from_be_bytes(bytes: Self::Repr) -> Self {
266        Self::from_be_slice_vartime(&bytes)
267    }
268
269    fn from_le_bytes(bytes: Self::Repr) -> Self {
270        Self::from_le_slice_vartime(&bytes)
271    }
272
273    fn from_be_slice_truncated(bytes: &[u8], bits_precision: u32) -> Self {
274        Self::from_be_slice_truncated(bytes, bits_precision)
275    }
276
277    fn from_le_slice_truncated(bytes: &[u8], bits_precision: u32) -> Self {
278        Self::from_le_slice_truncated(bytes, bits_precision)
279    }
280
281    fn to_be_bytes(&self) -> Self::Repr {
282        Self::to_be_bytes(self)
283    }
284
285    fn to_le_bytes(&self) -> Self::Repr {
286        Self::to_le_bytes(self)
287    }
288}
289
290/// Decoder target producing a Vec<Limb>
291#[derive(Default)]
292struct VecDecodeByLimb {
293    limbs: Vec<Limb>,
294}
295
296impl encoding::DecodeByLimb for VecDecodeByLimb {
297    #[inline]
298    fn limbs_mut(&mut self) -> &mut [Limb] {
299        self.limbs.as_mut_slice()
300    }
301
302    #[inline]
303    fn push_limb(&mut self, limb: Limb) -> bool {
304        self.limbs.push(limb);
305        true
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::{BoxedUint, DecodeError};
312    use crate::Limb;
313    use hex_literal::hex;
314
315    cpubits::cpubits! {
316        32 => {
317            #[test]
318            fn from_be_slice_eq() {
319                let bytes = hex!("0011223344556677");
320                let n = BoxedUint::from_be_slice(&bytes, 64).unwrap();
321                assert_eq!(n.as_limbs(), &[Limb(0x44556677), Limb(0x00112233)]);
322            }
323
324            #[test]
325            fn from_be_slice_short() {
326                let bytes = hex!("0011223344556677");
327                let n = BoxedUint::from_be_slice(&bytes, 128).unwrap();
328                assert_eq!(
329                    n.as_limbs(),
330                    &[Limb(0x44556677), Limb(0x00112233), Limb::ZERO, Limb::ZERO]
331                );
332            }
333
334            #[test]
335            fn from_be_slice_not_word_sized() {
336                let bytes = hex!("112233445566778899aabbccddeeff");
337                let n = BoxedUint::from_be_slice(&bytes, 127).unwrap();
338                assert_eq!(
339                    n.as_limbs(),
340                    &[
341                        Limb(0xccddeeff),
342                        Limb(0x8899aabb),
343                        Limb(0x44556677),
344                        Limb(0x00112233)
345                    ]
346                );
347                assert_eq!(n.bits_precision(), 128);
348            }
349
350            #[test]
351            fn from_le_slice_eq() {
352                let bytes = hex!("7766554433221100");
353                let n = BoxedUint::from_le_slice(&bytes, 64).unwrap();
354                assert_eq!(n.as_limbs(), &[Limb(0x44556677), Limb(0x00112233)]);
355            }
356
357            #[test]
358            fn from_le_slice_short() {
359                let bytes = hex!("7766554433221100");
360                let n = BoxedUint::from_le_slice(&bytes, 128).unwrap();
361                assert_eq!(
362                    n.as_limbs(),
363                    &[Limb(0x44556677), Limb(0x00112233), Limb::ZERO, Limb::ZERO]
364                );
365            }
366
367            #[test]
368            fn from_le_slice_not_word_sized() {
369                let bytes = hex!("ffeeddccbbaa998877665544332211");
370                let n = BoxedUint::from_le_slice(&bytes, 127).unwrap();
371                assert_eq!(
372                    n.as_limbs(),
373                    &[
374                        Limb(0xccddeeff),
375                        Limb(0x8899aabb),
376                        Limb(0x44556677),
377                        Limb(0x00112233)
378                    ]
379                );
380                assert_eq!(n.bits_precision(), 128);
381            }
382        }
383        64 => {
384            #[test]
385            #[should_panic]
386            fn from_be_hex_short() {
387                let hex = "00112233445566778899aabbccddee";
388                let _ = BoxedUint::from_be_hex(hex, 128).unwrap();
389            }
390
391            #[test]
392            fn from_be_slice_eq() {
393                let bytes = hex!("00112233445566778899aabbccddeeff");
394                let n = BoxedUint::from_be_slice(&bytes, 128).unwrap();
395                assert_eq!(
396                    n.as_limbs(),
397                    &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)]
398                );
399            }
400
401            #[test]
402            fn from_be_slice_short() {
403                let bytes = hex!("00112233445566778899aabbccddeeff");
404                let n = BoxedUint::from_be_slice(&bytes, 256).unwrap();
405                assert_eq!(
406                    n.as_limbs(),
407                    &[
408                        Limb(0x8899aabbccddeeff),
409                        Limb(0x0011223344556677),
410                        Limb::ZERO,
411                        Limb::ZERO
412                    ]
413                );
414            }
415
416            #[test]
417            fn from_be_slice_not_word_sized() {
418                let bytes = hex!("112233445566778899aabbccddeeff");
419                let n = BoxedUint::from_be_slice(&bytes, 127).unwrap();
420                assert_eq!(
421                    n.as_limbs(),
422                    &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)]
423                );
424                assert_eq!(n.bits_precision(), 128);
425            }
426
427            #[test]
428            fn from_le_slice_eq() {
429                let bytes = hex!("ffeeddccbbaa99887766554433221100");
430                let n = BoxedUint::from_le_slice(&bytes, 128).unwrap();
431                assert_eq!(
432                    n.as_limbs(),
433                    &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)]
434                );
435            }
436
437            #[test]
438            fn from_le_slice_short() {
439                let bytes = hex!("ffeeddccbbaa99887766554433221100");
440                let n = BoxedUint::from_le_slice(&bytes, 256).unwrap();
441                assert_eq!(
442                    n.as_limbs(),
443                    &[
444                        Limb(0x8899aabbccddeeff),
445                        Limb(0x0011223344556677),
446                        Limb::ZERO,
447                        Limb::ZERO
448                    ]
449                );
450            }
451
452            #[test]
453            fn from_le_slice_not_word_sized() {
454                let bytes = hex!("ffeeddccbbaa998877665544332211");
455                let n = BoxedUint::from_le_slice(&bytes, 127).unwrap();
456                assert_eq!(
457                    n.as_limbs(),
458                    &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)]
459                );
460                assert_eq!(n.bits_precision(), 128);
461            }
462        }
463    }
464
465    #[test]
466    fn from_be_slice_too_long() {
467        let bytes = hex!("00112233445566778899aabbccddeeff");
468        assert_eq!(
469            BoxedUint::from_be_slice(&bytes, 64),
470            Err(DecodeError::InputSize)
471        );
472    }
473
474    #[test]
475    fn from_be_slice_non_multiple_precision() {
476        let bytes = hex!("0f112233445566778899aabbccddeeff");
477        let n = BoxedUint::from_be_slice(&bytes, 121).unwrap();
478        assert_eq!(n.bits(), 121);
479    }
480
481    #[test]
482    fn from_be_slice_rejects_value_exceeding_precision() {
483        assert_eq!(
484            BoxedUint::from_be_slice(&[0x01, 0x00], 8),
485            Err(DecodeError::InputSize)
486        );
487    }
488
489    #[test]
490    fn from_be_slice_truncated_exact_fit() {
491        let bytes = [0u8; 32];
492        let n = BoxedUint::from_be_slice_truncated(&bytes, 256);
493        assert_eq!(&*n.to_be_bytes(), &bytes);
494    }
495
496    #[test]
497    fn from_be_slice_truncated_truncates_to_lsb() {
498        let n = BoxedUint::from_be_slice_truncated(&[0xDE, 0xAD, 0xBE, 0xEF], 16);
499        let out = n.to_be_bytes();
500        assert_eq!(&out[(out.len() - 2)..], &[0xBE, 0xEF]);
501    }
502
503    #[test]
504    fn from_be_slice_truncated_zero_pads_short_input() {
505        let n = BoxedUint::from_be_slice_truncated(&[0x42], 32);
506        assert_eq!(n.to_be_bytes().last(), Some(&0x42));
507        assert!(n.to_be_bytes().iter().rev().skip(1).all(|&b| b == 0));
508    }
509
510    #[test]
511    fn from_be_slice_truncated_top_byte_masked() {
512        let n = BoxedUint::from_be_slice_truncated(&[0xFF, 0xFF], 12);
513        assert!(n.bits() <= 12);
514    }
515
516    #[test]
517    fn from_le_slice_truncated_exact_fit() {
518        let bytes = [0u8; 32];
519        let n = BoxedUint::from_le_slice_truncated(&bytes, 256);
520        assert_eq!(&*n.to_le_bytes(), &bytes);
521    }
522
523    #[test]
524    fn from_le_slice_truncated_truncates_to_lsb() {
525        let n = BoxedUint::from_le_slice_truncated(&[0xEF, 0xBE, 0xAD, 0xDE], 16);
526        assert_eq!(&n.to_le_bytes()[..2], &[0xEF, 0xBE]);
527    }
528
529    #[test]
530    fn from_le_slice_truncated_top_byte_masked() {
531        let n = BoxedUint::from_le_slice_truncated(&[0xFF, 0xFF], 12);
532        assert!(n.bits() <= 12,);
533    }
534
535    #[test]
536    fn from_be_slice_vartime() {
537        let bytes = hex!(
538            "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111F"
539        );
540        let uint = BoxedUint::from_be_slice_vartime(&bytes);
541        assert_eq!(&*uint.to_be_bytes_trimmed_vartime(), bytes.as_slice());
542    }
543
544    #[test]
545    fn from_le_slice_too_long() {
546        let bytes = hex!("ffeeddccbbaa99887766554433221100");
547        assert_eq!(
548            BoxedUint::from_be_slice(&bytes, 64),
549            Err(DecodeError::InputSize)
550        );
551    }
552
553    #[test]
554    fn from_le_slice_non_multiple_precision() {
555        let bytes = hex!("ffeeddccbbaa998877665544332211ff");
556        let n = BoxedUint::from_le_slice(&bytes, 121).unwrap();
557        assert_eq!(n.bits(), 121);
558    }
559
560    #[test]
561    fn from_le_slice_vartime() {
562        let bytes = hex!(
563            "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111F"
564        );
565        let uint = BoxedUint::from_le_slice_vartime(&bytes);
566        assert_eq!(&*uint.to_le_bytes_trimmed_vartime(), bytes.as_slice());
567    }
568
569    #[test]
570    fn to_be_bytes() {
571        let bytes = hex!("00112233445566778899aabbccddeeff");
572        let n = BoxedUint::from_be_slice(&bytes, 128).unwrap();
573        assert_eq!(bytes.as_slice(), &*n.to_be_bytes());
574    }
575
576    #[test]
577    fn to_be_bytes_trimmed_vartime() {
578        let bytes = hex!("ff112233445566778899aabbccddeeff");
579        let n = BoxedUint::from_be_slice(&bytes, 128).unwrap();
580        assert_eq!(&bytes, &*n.to_be_bytes_trimmed_vartime());
581
582        let bytes = hex!("00112233445566778899aabbccddeeff");
583        let n = BoxedUint::from_be_slice(&bytes, 128).unwrap();
584        assert_eq!(&bytes.as_slice()[1..], &*n.to_be_bytes_trimmed_vartime());
585
586        let bytes: &[u8] = b"";
587        let n = BoxedUint::from_be_slice(bytes, 128).unwrap();
588        assert_eq!(
589            hex!("00000000000000000000000000000000"),
590            n.to_be_bytes().as_ref()
591        );
592        assert_eq!(bytes, n.to_be_bytes_trimmed_vartime().as_ref());
593
594        let bytes = hex!("00012233445566778899aabbccddeeff");
595        let n = BoxedUint::from_be_slice(&bytes, 128).unwrap();
596        assert_eq!(&bytes.as_slice()[1..], &*n.to_be_bytes_trimmed_vartime());
597
598        let bytes = hex!("00000000000000000000000000000001");
599        let n = BoxedUint::from_be_slice(&bytes, 128).unwrap();
600        assert_eq!(bytes, n.to_be_bytes().as_ref());
601        assert_eq!(&bytes.as_slice()[15..], &*n.to_be_bytes_trimmed_vartime());
602    }
603
604    #[test]
605    fn to_le_bytes() {
606        let bytes = hex!("ffeeddccbbaa99887766554433221100");
607        let n = BoxedUint::from_le_slice(&bytes, 128).unwrap();
608        assert_eq!(bytes.as_slice(), &*n.to_le_bytes());
609    }
610
611    #[test]
612    fn to_le_bytes_trimmed_vartime() {
613        let bytes = hex!("ffeeddccbbaa998877665544332211ff");
614        let n = BoxedUint::from_le_slice(&bytes, 128).unwrap();
615        assert_eq!(bytes.as_slice(), &*n.to_le_bytes_trimmed_vartime());
616
617        let bytes = hex!("ffeeddccbbaa99887766554433221100");
618        let n = BoxedUint::from_le_slice(&bytes, 128).unwrap();
619        assert_eq!(&bytes.as_slice()[..15], &*n.to_le_bytes_trimmed_vartime());
620
621        let bytes = hex!("ff000000000000000000000000000000");
622        let n = BoxedUint::from_le_slice(&bytes, 128).unwrap();
623        assert_eq!(&bytes.as_slice()[..1], &*n.to_le_bytes_trimmed_vartime());
624
625        let bytes = hex!("01000000000000000000000000000000");
626        let n = BoxedUint::from_le_slice(&bytes, 128).unwrap();
627        assert_eq!(&bytes.as_slice()[..1], &*n.to_le_bytes_trimmed_vartime());
628
629        let bytes = hex!("00000000000000000000000000000000");
630        let n = BoxedUint::from_le_slice(&bytes, 128).unwrap();
631        assert_eq!(b"", &*n.to_le_bytes_trimmed_vartime());
632    }
633
634    #[test]
635    fn from_str_radix_invalid() {
636        assert_eq!(
637            BoxedUint::from_str_radix_vartime("?", 10,),
638            Err(DecodeError::InvalidDigit)
639        );
640        assert_eq!(
641            BoxedUint::from_str_radix_with_precision_vartime(
642                "ffffffffffffffff_ffffffffffffffff_f",
643                16,
644                128
645            ),
646            Err(DecodeError::InputSize)
647        );
648        assert_eq!(
649            BoxedUint::from_str_radix_with_precision_vartime("1111111111111111", 2, 10),
650            Err(DecodeError::Precision)
651        );
652    }
653
654    #[test]
655    fn from_str_radix_10() {
656        let dec = "+340_282_366_920_938_463_463_374_607_431_768_211_455";
657        let res = BoxedUint::from_str_radix_vartime(dec, 10).expect("error decoding");
658        assert_eq!(res, BoxedUint::max(128));
659    }
660
661    #[test]
662    fn from_str_radix_16() {
663        let hex = "fedcba9876543210fedcba9876543210";
664        let res = BoxedUint::from_str_radix_vartime(hex, 16).expect("error decoding");
665        assert_eq!(hex, format!("{res:x}"));
666    }
667
668    #[test]
669    #[cfg(feature = "rand_core")]
670    fn encode_radix_round_trip() {
671        use crate::RandomBits;
672        use rand_core::SeedableRng;
673        let mut rng = chacha20::ChaCha8Rng::seed_from_u64(1);
674
675        let rounds = if cfg!(miri) { 10 } else { 100 };
676        let bits = if cfg!(miri) { 256 } else { 4096 };
677        for _ in 0..rounds {
678            let uint = BoxedUint::random_bits(&mut rng, bits);
679            for radix in 2..=36 {
680                let enc = uint.to_string_radix_vartime(radix);
681                let res = BoxedUint::from_str_radix_vartime(&enc, radix).expect("decoding error");
682                assert_eq!(
683                    res, uint,
684                    "round trip failure: radix {radix} encoded {uint} as {enc}"
685                );
686            }
687        }
688    }
689}