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