diff --git a/Cargo.toml b/Cargo.toml index 307ed93ff..3b7ec79e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,9 +41,9 @@ rand_chacha = "0.3" [features] default = ["rand"] alloc = ["serdect?/alloc"] +extra-sizes = [] rand = ["rand_core/std"] serde = ["dep:serdect"] -extra-sizes = [] [package.metadata.docs.rs] all-features = true diff --git a/src/boxed/uint.rs b/src/boxed/uint.rs index 85dabae26..65990a370 100644 --- a/src/boxed/uint.rs +++ b/src/boxed/uint.rs @@ -2,6 +2,7 @@ mod add; mod cmp; +pub(crate) mod encoding; mod mul; mod sub; @@ -30,11 +31,24 @@ pub struct BoxedUint { } impl BoxedUint { - /// Get the value `0`, represented as succinctly as possible. + /// Get the value `0` represented as succinctly as possible. pub fn zero() -> Self { Self::default() } + /// Get the value `0` with the given number of bits of precision. + /// + /// Panics if the precision is not a multiple of [`Limb::BITS`]. + pub fn zero_with_precision(bits_precision: usize) -> Self { + assert_eq!( + bits_precision % Limb::BITS, + 0, + "precision is not a multiple of limb size" + ); + + vec![Limb::ZERO; bits_precision / Limb::BITS].into() + } + /// Get the value `1`, represented as succinctly as possible. pub fn one() -> Self { Self { @@ -42,6 +56,17 @@ impl BoxedUint { } } + /// Get the value `1` with the given number of bits of precision. + /// + /// Panics if the precision is not at least [`Limb::BITS`] or if it is not + /// a multiple thereof. + pub fn one_with_precision(bits_precision: usize) -> Self { + assert!(bits_precision >= Limb::BITS, "precision too small"); + let mut ret = Self::zero_with_precision(bits_precision); + ret.limbs[0] = Limb::ONE; + ret + } + /// Is this [`BoxedUint`] equal to zero? pub fn is_zero(&self) -> Choice { self.limbs @@ -82,9 +107,9 @@ impl BoxedUint { /// Create a [`BoxedUint`] from an array of [`Word`]s (i.e. word-sized unsigned /// integers). #[inline] - pub fn from_words(words: &[Word]) -> Self { + pub fn from_words(words: impl IntoIterator) -> Self { Self { - limbs: words.iter().copied().map(Into::into).collect(), + limbs: words.into_iter().map(Into::into).collect(), } } @@ -92,12 +117,7 @@ impl BoxedUint { /// a [`BoxedUint`]. #[inline] pub fn to_words(&self) -> Box<[Word]> { - self.limbs - .iter() - .copied() - .map(Into::into) - .collect::>() - .into() + self.limbs.iter().copied().map(Into::into).collect() } /// Borrow the inner limbs as a slice of [`Word`]s. diff --git a/src/boxed/uint/encoding.rs b/src/boxed/uint/encoding.rs new file mode 100644 index 000000000..f3c2c32ae --- /dev/null +++ b/src/boxed/uint/encoding.rs @@ -0,0 +1,221 @@ +//! Const-friendly decoding operations for [`BoxedUint`]. + +use super::BoxedUint; +use crate::Limb; + +/// Decoding errors for [`BoxedUint`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum DecodeError { + /// Input is not a valid size. + InputSize, + + /// Precision is not a multiple of [`Limb::BYTES`]. + Precision, +} + +impl BoxedUint { + /// Create a new [`BoxedUint`] from the provided big endian bytes. + /// + /// The `bits_precision` argument represents the precision of the resulting integer, which is + /// fixed as this type is not arbitrary-precision. It MUST be a multiple of the limb size, i.e. + /// [`Limb::BITS`], or otherwise this function will return [`DecodeError::Precision`]. + /// + /// If the length of `bytes` (when interpreted as bits) is larger than `bits_precision`, this + /// function will return [`DecodeError::InputSize`]. + pub fn from_be_slice(bytes: &[u8], bits_precision: usize) -> Result { + if bits_precision % Limb::BITS != 0 { + return Err(DecodeError::Precision); + } + + if bytes.len() % Limb::BYTES != 0 || bytes.len() * 8 > bits_precision { + return Err(DecodeError::InputSize); + } + + let mut ret = Self::zero_with_precision(bits_precision); + + for (chunk, limb) in bytes.chunks(Limb::BYTES).rev().zip(ret.limbs.iter_mut()) { + *limb = Limb::from_be_slice(chunk); + } + + Ok(ret) + } + + /// Create a new [`BoxedUint`] from the provided little endian bytes. + /// + /// The `bits_precision` argument represents the precision of the resulting integer, which is + /// fixed as this type is not arbitrary-precision. It MUST be a multiple of the limb size, i.e. + /// [`Limb::BITS`], or otherwise this function will return [`DecodeError::Precision`]. + /// + /// If the length of `bytes` (when interpreted as bits) is larger than `bits_precision`, this + /// function will return [`DecodeError::InputSize`]. + pub fn from_le_slice(bytes: &[u8], bits_precision: usize) -> Result { + if bits_precision % Limb::BITS != 0 { + return Err(DecodeError::Precision); + } + + if bytes.len() % Limb::BYTES != 0 || bytes.len() * 8 > bits_precision { + return Err(DecodeError::InputSize); + } + + let mut ret = Self::zero_with_precision(bits_precision); + + for (chunk, limb) in bytes.chunks(Limb::BYTES).zip(ret.limbs.iter_mut()) { + *limb = Limb::from_le_slice(chunk); + } + + Ok(ret) + } +} + +#[cfg(test)] +mod tests { + use super::{BoxedUint, DecodeError}; + use crate::Limb; + use hex_literal::hex; + + #[test] + #[cfg(target_pointer_width = "32")] + fn from_be_slice_eq() { + let bytes = hex!("0011223344556677"); + let n = BoxedUint::from_be_slice(&bytes, 64).unwrap(); + assert_eq!(n.as_limbs(), &[Limb(0x44556677), Limb(0x00112233)]); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn from_be_slice_eq() { + let bytes = hex!("00112233445566778899aabbccddeeff"); + let n = BoxedUint::from_be_slice(&bytes, 128).unwrap(); + assert_eq!( + n.as_limbs(), + &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)] + ); + } + + #[test] + #[cfg(target_pointer_width = "32")] + fn from_be_slice_short() { + let bytes = hex!("0011223344556677"); + let n = BoxedUint::from_be_slice(&bytes, 128).unwrap(); + assert_eq!( + n.as_limbs(), + &[Limb(0x44556677), Limb(0x00112233), Limb::ZERO, Limb::ZERO] + ); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn from_be_slice_short() { + let bytes = hex!("00112233445566778899aabbccddeeff"); + let n = BoxedUint::from_be_slice(&bytes, 256).unwrap(); + assert_eq!( + n.as_limbs(), + &[ + Limb(0x8899aabbccddeeff), + Limb(0x0011223344556677), + Limb::ZERO, + Limb::ZERO + ] + ); + } + + #[test] + fn from_be_slice_too_long() { + let bytes = hex!("00112233445566778899aabbccddeeff"); + assert_eq!( + BoxedUint::from_be_slice(&bytes, 64), + Err(DecodeError::InputSize) + ); + } + + #[test] + fn from_be_slice_not_word_sized() { + let bytes = hex!("00112233445566778899aabbccddee"); + assert_eq!( + BoxedUint::from_be_slice(&bytes, 128), + Err(DecodeError::InputSize) + ); + } + + #[test] + fn from_be_slice_bad_precision() { + let bytes = hex!("00112233445566778899aabbccddeeff"); + assert_eq!( + BoxedUint::from_be_slice(&bytes, 127), + Err(DecodeError::Precision) + ); + } + + #[test] + #[cfg(target_pointer_width = "32")] + fn from_le_slice_eq() { + let bytes = hex!("7766554433221100"); + let n = BoxedUint::from_le_slice(&bytes, 64).unwrap(); + assert_eq!(n.as_limbs(), &[Limb(0x44556677), Limb(0x00112233)]); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn from_le_slice_eq() { + let bytes = hex!("ffeeddccbbaa99887766554433221100"); + let n = BoxedUint::from_le_slice(&bytes, 128).unwrap(); + assert_eq!( + n.as_limbs(), + &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)] + ); + } + + #[test] + #[cfg(target_pointer_width = "32")] + fn from_le_slice_short() { + let bytes = hex!("7766554433221100"); + let n = BoxedUint::from_le_slice(&bytes, 128).unwrap(); + assert_eq!( + n.as_limbs(), + &[Limb(0x44556677), Limb(0x00112233), Limb::ZERO, Limb::ZERO] + ); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn from_le_slice_short() { + let bytes = hex!("ffeeddccbbaa99887766554433221100"); + let n = BoxedUint::from_le_slice(&bytes, 256).unwrap(); + assert_eq!( + n.as_limbs(), + &[ + Limb(0x8899aabbccddeeff), + Limb(0x0011223344556677), + Limb::ZERO, + Limb::ZERO + ] + ); + } + + #[test] + fn from_le_slice_too_long() { + let bytes = hex!("ffeeddccbbaa99887766554433221100"); + assert_eq!( + BoxedUint::from_be_slice(&bytes, 64), + Err(DecodeError::InputSize) + ); + } + + #[test] + fn from_le_slice_not_word_sized() { + let bytes = hex!("ffeeddccbbaa998877665544332211"); + assert_eq!( + BoxedUint::from_be_slice(&bytes, 128), + Err(DecodeError::InputSize) + ); + } + + #[test] + fn from_le_slice_bad_precision() { + let bytes = hex!("ffeeddccbbaa99887766554433221100"); + assert_eq!( + BoxedUint::from_le_slice(&bytes, 127), + Err(DecodeError::Precision) + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 6decb177b..f6668c238 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -184,7 +184,7 @@ pub use crate::{ pub use subtle; #[cfg(feature = "alloc")] -pub use crate::boxed::uint::BoxedUint; +pub use crate::boxed::uint::{encoding::DecodeError, BoxedUint}; #[cfg(feature = "generic-array")] pub use { diff --git a/src/limb/encoding.rs b/src/limb/encoding.rs index ab28a6adb..f6906104c 100644 --- a/src/limb/encoding.rs +++ b/src/limb/encoding.rs @@ -30,6 +30,23 @@ impl Encoding for Limb { } } +#[cfg(feature = "alloc")] +impl Limb { + /// Decode limb from a big endian byte slice. + /// + /// Panics if the slice is not the same size as [`Limb::Repr`]. + pub(crate) fn from_be_slice(bytes: &[u8]) -> Self { + Self::from_be_bytes(bytes.try_into().expect("slice not limb-sized")) + } + + /// Decode limb from a little endian byte slice. + /// + /// Panics if the slice is not the same size as [`Limb::Repr`]. + pub(crate) fn from_le_slice(bytes: &[u8]) -> Self { + Self::from_le_bytes(bytes.try_into().expect("slice not limb-sized")) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/src/uint/encoding.rs b/src/uint/encoding.rs index 42f9de183..c15526580 100644 --- a/src/uint/encoding.rs +++ b/src/uint/encoding.rs @@ -1,4 +1,4 @@ -//! Const-friendly decoding operations for [`Uint`] +//! Const-friendly decoding/encoding operations for [`Uint`]. #[cfg(all(feature = "der", feature = "generic-array"))] mod der;