Skip to content

Commit 70d04b5

Browse files
Implement from_str_radix_vartime for Uint, BoxedUint (#603)
Signed-off-by: Andrew Whitehead <[email protected]>
1 parent 295d222 commit 70d04b5

4 files changed

Lines changed: 451 additions & 37 deletions

File tree

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ pub use crate::{
189189
pub use subtle;
190190

191191
#[cfg(feature = "alloc")]
192-
pub use crate::uint::boxed::{encoding::DecodeError, BoxedUint};
192+
pub use crate::uint::boxed::BoxedUint;
193193

194194
#[cfg(feature = "hybrid-array")]
195195
pub use {

src/traits.rs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub use num_traits::{
99
pub(crate) use sealed::PrecomputeInverterWithAdjuster;
1010

1111
use crate::{Limb, NonZero, Odd, Reciprocal};
12-
use core::fmt::Debug;
12+
use core::fmt::{self, Debug};
1313
use core::ops::{
1414
Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign,
1515
Mul, MulAssign, Neg, Not, Rem, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign,
@@ -22,9 +22,6 @@ use subtle::{
2222
#[cfg(feature = "rand_core")]
2323
use rand_core::CryptoRngCore;
2424

25-
#[cfg(feature = "rand_core")]
26-
use core::fmt;
27-
2825
/// Integers whose representation takes a bounded amount of space.
2926
pub trait Bounded {
3027
/// Size of this integer in bits.
@@ -541,6 +538,41 @@ pub trait Encoding: Sized {
541538
fn to_le_bytes(&self) -> Self::Repr;
542539
}
543540

541+
/// Possible errors in variable-time integer decoding methods.
542+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
543+
pub enum DecodeError {
544+
/// The input value was empty.
545+
Empty,
546+
547+
/// The input was not consistent with the format restrictions.
548+
InvalidDigit,
549+
550+
/// Input size is too small to fit in the given precision.
551+
InputSize,
552+
553+
/// The deserialized number is larger than the given precision.
554+
Precision,
555+
}
556+
557+
impl fmt::Display for DecodeError {
558+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
559+
match self {
560+
Self::Empty => write!(f, "empty value provided"),
561+
Self::InvalidDigit => {
562+
write!(f, "invalid digit character")
563+
}
564+
Self::InputSize => write!(f, "input size is too small to fit in the given precision"),
565+
Self::Precision => write!(
566+
f,
567+
"the deserialized number is larger than the given precision"
568+
),
569+
}
570+
}
571+
}
572+
573+
#[cfg(feature = "std")]
574+
impl std::error::Error for DecodeError {}
575+
544576
/// Support for optimized squaring
545577
pub trait Square {
546578
/// Computes the same as `self * self`, but may be more efficient.

src/uint/boxed/encoding.rs

Lines changed: 106 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,10 @@
11
//! Const-friendly decoding operations for [`BoxedUint`].
22
33
use super::BoxedUint;
4-
use crate::{uint::encoding, Limb, Word};
5-
use alloc::boxed::Box;
6-
use core::fmt;
4+
use crate::{uint::encoding, DecodeError, Limb, Word};
5+
use alloc::{boxed::Box, vec::Vec};
76
use subtle::{Choice, CtOption};
87

9-
/// Decoding errors for [`BoxedUint`].
10-
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11-
pub enum DecodeError {
12-
/// Input size is too small to fit in the given precision.
13-
InputSize,
14-
15-
/// The deserialized number is larger than the given precision.
16-
Precision,
17-
}
18-
19-
impl fmt::Display for DecodeError {
20-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21-
match self {
22-
Self::InputSize => write!(f, "input size is too small to fit in the given precision"),
23-
Self::Precision => write!(
24-
f,
25-
"the deserialized number is larger than the given precision"
26-
),
27-
}
28-
}
29-
}
30-
31-
#[cfg(feature = "std")]
32-
impl std::error::Error for DecodeError {}
33-
348
impl BoxedUint {
359
/// Create a new [`BoxedUint`] from the provided big endian bytes.
3610
///
@@ -142,7 +116,6 @@ impl BoxedUint {
142116
bytes.len() == Limb::BYTES * nlimbs * 2,
143117
"hex string is not the expected size"
144118
);
145-
146119
let mut res = vec![Limb::ZERO; nlimbs];
147120
let mut buf = [0u8; Limb::BYTES];
148121
let mut i = 0;
@@ -163,6 +136,76 @@ impl BoxedUint {
163136
}
164137
CtOption::new(Self { limbs: res.into() }, Choice::from((err == 0) as u8))
165138
}
139+
140+
/// Create a new [`BoxedUint`] from a big-endian string in a given base.
141+
///
142+
/// The string may begin with a `+` character, and may use underscore
143+
/// characters to separate digits.
144+
///
145+
/// If the input value contains non-digit characters or digits outside of the range `0..radix`
146+
/// this function will return [`DecodeError::InvalidDigit`].
147+
/// Panics if `radix` is not in the range from 2 to 36.
148+
pub fn from_str_radix_vartime(src: &str, radix: u32) -> Result<Self, DecodeError> {
149+
let mut dec = VecDecodeByLimb::default();
150+
encoding::decode_str_radix(src, radix, &mut dec)?;
151+
Ok(Self {
152+
limbs: dec.limbs.into(),
153+
})
154+
}
155+
156+
/// Create a new [`BoxedUint`] from a big-endian string in a given base,
157+
/// with a given precision.
158+
///
159+
/// The string may begin with a `+` character, and may use underscore
160+
/// characters to separate digits.
161+
///
162+
/// The `bits_precision` argument represents the precision of the resulting integer, which is
163+
/// fixed as this type is not arbitrary-precision.
164+
/// The new [`BoxedUint`] will be created with `bits_precision` rounded up to a multiple
165+
/// of [`Limb::BITS`].
166+
///
167+
/// If the input value contains non-digit characters or digits outside of the range `0..radix`
168+
/// this function will return [`DecodeError::InvalidDigit`].
169+
/// If the length of `bytes` is larger than `bits_precision` (rounded up to a multiple of 8)
170+
/// this function will return [`DecodeError::InputSize`].
171+
/// If the size of the decoded integer is larger than `bits_precision`,
172+
/// this function will return [`DecodeError::Precision`].
173+
/// Panics if `radix` is not in the range from 2 to 36.
174+
pub fn from_str_radix_with_precision_vartime(
175+
src: &str,
176+
radix: u32,
177+
bits_precision: u32,
178+
) -> Result<Self, DecodeError> {
179+
let mut ret = Self::zero_with_precision(bits_precision);
180+
encoding::decode_str_radix(
181+
src,
182+
radix,
183+
&mut encoding::SliceDecodeByLimb::new(&mut ret.limbs),
184+
)?;
185+
if bits_precision < ret.bits() {
186+
return Err(DecodeError::Precision);
187+
}
188+
Ok(ret)
189+
}
190+
}
191+
192+
/// Decoder target producing a Vec<Limb>
193+
#[derive(Default)]
194+
struct VecDecodeByLimb {
195+
limbs: Vec<Limb>,
196+
}
197+
198+
impl encoding::DecodeByLimb for VecDecodeByLimb {
199+
#[inline]
200+
fn limbs_mut(&mut self) -> &mut [Limb] {
201+
self.limbs.as_mut_slice()
202+
}
203+
204+
#[inline]
205+
fn push_limb(&mut self, limb: Limb) -> bool {
206+
self.limbs.push(limb);
207+
true
208+
}
166209
}
167210

168211
#[cfg(test)]
@@ -381,4 +424,38 @@ mod tests {
381424
let n = BoxedUint::from_be_slice(&bytes, 128).unwrap();
382425
assert_eq!(bytes.as_slice(), &*n.to_be_bytes());
383426
}
427+
428+
#[test]
429+
fn from_str_radix_invalid() {
430+
assert_eq!(
431+
BoxedUint::from_str_radix_vartime("?", 10,),
432+
Err(DecodeError::InvalidDigit)
433+
);
434+
assert_eq!(
435+
BoxedUint::from_str_radix_with_precision_vartime(
436+
"ffffffffffffffff_ffffffffffffffff_f",
437+
16,
438+
128
439+
),
440+
Err(DecodeError::InputSize)
441+
);
442+
assert_eq!(
443+
BoxedUint::from_str_radix_with_precision_vartime("1111111111111111", 2, 10),
444+
Err(DecodeError::Precision)
445+
);
446+
}
447+
448+
#[test]
449+
fn from_str_radix_10() {
450+
let dec = "+340_282_366_920_938_463_463_374_607_431_768_211_455";
451+
let res = BoxedUint::from_str_radix_vartime(dec, 10).expect("error decoding");
452+
assert_eq!(res, BoxedUint::max(128));
453+
}
454+
455+
#[test]
456+
fn from_str_radix_16() {
457+
let hex = "fedcba9876543210fedcba9876543210";
458+
let res = BoxedUint::from_str_radix_vartime(hex, 16).expect("error decoding");
459+
assert_eq!(hex, format!("{res:x}"));
460+
}
384461
}

0 commit comments

Comments
 (0)