From 56e364582b84e7017860608544586e75680857f1 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sat, 2 Dec 2023 10:33:01 -0800 Subject: [PATCH 1/6] Round up precision to the nearest multiple of Limb::BITS on creation instead of panicking --- src/uint/boxed.rs | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/src/uint/boxed.rs b/src/uint/boxed.rs index 62b5f4916..219031bc3 100644 --- a/src/uint/boxed.rs +++ b/src/uint/boxed.rs @@ -50,6 +50,10 @@ pub struct BoxedUint { } impl BoxedUint { + fn limbs_for_precision(bits_precision: u32) -> usize { + ((bits_precision + Limb::BITS - 1) / Limb::BITS) as usize + } + /// Get the value `0` represented as succinctly as possible. pub fn zero() -> Self { Self { @@ -59,15 +63,9 @@ impl BoxedUint { /// Get the value `0` with the given number of bits of precision. /// - /// Panics if the precision is not a multiple of [`Limb::BITS`]. + /// `bits_precision` is rounded up to a multiple of [`Limb::BITS`]. pub fn zero_with_precision(bits_precision: u32) -> Self { - assert_eq!( - bits_precision % Limb::BITS, - 0, - "precision is not a multiple of limb size" - ); - - vec![Limb::ZERO; (bits_precision / Limb::BITS) as usize].into() + vec![Limb::ZERO; Self::limbs_for_precision(bits_precision)].into() } /// Get the value `1`, represented as succinctly as possible. @@ -79,10 +77,8 @@ 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. + /// `bits_precision` is rounded up to a multiple of [`Limb::BITS`]. pub fn one_with_precision(bits_precision: u32) -> Self { - assert!(bits_precision >= Limb::BITS, "precision too small"); let mut ret = Self::zero_with_precision(bits_precision); ret.limbs[0] = Limb::ONE; ret @@ -127,15 +123,9 @@ impl BoxedUint { /// Get the maximum value for a given number of bits of precision. /// - /// Panics if the precision is not a multiple of [`Limb::BITS`]. + /// `bits_precision` is rounded up to a multiple of [`Limb::BITS`]. pub fn max(bits_precision: u32) -> Self { - assert_eq!( - bits_precision % Limb::BITS, - 0, - "precision is not a multiple of limb size" - ); - - vec![Limb::MAX; (bits_precision / Limb::BITS) as usize].into() + vec![Limb::MAX; Self::limbs_for_precision(bits_precision)].into() } /// Create a [`BoxedUint`] from an array of [`Word`]s (i.e. word-sized unsigned @@ -235,10 +225,8 @@ impl BoxedUint { /// Widen this type's precision to the given number of bits. /// - /// Panics if `bits_precision` is not a multiple of `Limb::BITS` or smaller than the current - /// precision. + /// Panics if `bits_precision` is smaller than the current precision. pub fn widen(&self, bits_precision: u32) -> BoxedUint { - assert!(bits_precision % Limb::BITS == 0); assert!(bits_precision >= self.bits_precision()); let mut ret = BoxedUint::zero_with_precision(bits_precision); @@ -248,10 +236,8 @@ impl BoxedUint { /// Shortens this type's precision to the given number of bits. /// - /// Panics if `bits_precision` is not a multiple of `Limb::BITS` or smaller than the current - /// precision. + /// Panics if `bits_precision` is larger than the current precision. pub fn shorten(&self, bits_precision: u32) -> BoxedUint { - assert!(bits_precision % Limb::BITS == 0); assert!(bits_precision <= self.bits_precision()); let mut ret = BoxedUint::zero_with_precision(bits_precision); let nlimbs = ret.nlimbs(); From d51e8eabb841d21f43908d32e0e195060073ee1e Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sat, 2 Dec 2023 10:35:06 -0800 Subject: [PATCH 2/6] Support any `bits_precision` in BoxedUint::random And also fix the copy-paste artifacts in tests. --- src/uint/boxed/rand.rs | 38 +++++++++++++++++++++++++++----------- src/uint/rand.rs | 2 -- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/uint/boxed/rand.rs b/src/uint/boxed/rand.rs index 386cfec51..d6890b347 100644 --- a/src/uint/boxed/rand.rs +++ b/src/uint/boxed/rand.rs @@ -6,13 +6,19 @@ use rand_core::CryptoRngCore; impl BoxedUint { /// Generate a cryptographically secure random [`BoxedUint`]. - pub fn random(mut rng: &mut impl CryptoRngCore, bits_precision: u32) -> Self { + /// in range `[0, 2^bits_precision)`. + pub fn random(rng: &mut impl CryptoRngCore, bits_precision: u32) -> Self { let mut ret = BoxedUint::zero_with_precision(bits_precision); for limb in &mut *ret.limbs { - *limb = Limb::random(&mut rng) + *limb = Limb::random(rng) } + // Since `bits_precision` will be rounded up on creation of `ret`, + // we need to clear the high bits if the rounding occurred. + ret.limbs[ret.limbs.len() - 1] = + ret.limbs[ret.limbs.len() - 1] & (Limb::MAX >> (ret.bits_precision() - bits_precision)); + ret } } @@ -36,28 +42,38 @@ impl RandomMod for BoxedUint { #[cfg(test)] mod tests { - use crate::{NonZero, RandomMod, U256}; + use crate::{BoxedUint, NonZero, RandomMod}; use rand_core::SeedableRng; + #[test] + fn random() { + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1); + + let r = BoxedUint::random(&mut rng, 256); + assert!(r.bits_precision() == 256); + + let r = BoxedUint::random(&mut rng, 256 - 32 + 1); + assert!(r.bits_precision() == 256); + assert!(r < BoxedUint::one_with_precision(256) << (256 - 32 + 1)); + } + #[test] fn random_mod() { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1); // Ensure `random_mod` runs in a reasonable amount of time - let modulus = NonZero::new(U256::from(42u8)).unwrap(); - let res = U256::random_mod(&mut rng, &modulus); + let modulus = NonZero::new(BoxedUint::from(42u8)).unwrap(); + let res = BoxedUint::random_mod(&mut rng, &modulus); // Check that the value is in range - assert!(res >= U256::ZERO); - assert!(res < U256::from(42u8)); + assert!(res < BoxedUint::from(42u8)); // Ensure `random_mod` runs in a reasonable amount of time // when the modulus is larger than 1 limb - let modulus = NonZero::new(U256::from(0x10000000000000001u128)).unwrap(); - let res = U256::random_mod(&mut rng, &modulus); + let modulus = NonZero::new(BoxedUint::from(0x10000000000000001u128)).unwrap(); + let res = BoxedUint::random_mod(&mut rng, &modulus); // Check that the value is in range - assert!(res >= U256::ZERO); - assert!(res < U256::from(0x10000000000000001u128)); + assert!(res < BoxedUint::from(0x10000000000000001u128)); } } diff --git a/src/uint/rand.rs b/src/uint/rand.rs index 8f97c7b1b..3028a5e9e 100644 --- a/src/uint/rand.rs +++ b/src/uint/rand.rs @@ -87,7 +87,6 @@ mod tests { let res = U256::random_mod(&mut rng, &modulus); // Check that the value is in range - assert!(res >= U256::ZERO); assert!(res < U256::from(42u8)); // Ensure `random_mod` runs in a reasonable amount of time @@ -96,7 +95,6 @@ mod tests { let res = U256::random_mod(&mut rng, &modulus); // Check that the value is in range - assert!(res >= U256::ZERO); assert!(res < U256::from(0x10000000000000001u128)); } } From 214789cff671173783a1be05d8d3feaf2d53ff0b Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sat, 2 Dec 2023 10:34:45 -0800 Subject: [PATCH 3/6] Round up bits_precision when decoding from bytes --- src/limb/encoding.rs | 18 +++++- src/uint/boxed/encoding.rs | 110 ++++++++++++++++++++++++++----------- 2 files changed, 93 insertions(+), 35 deletions(-) diff --git a/src/limb/encoding.rs b/src/limb/encoding.rs index f6906104c..035693aee 100644 --- a/src/limb/encoding.rs +++ b/src/limb/encoding.rs @@ -34,16 +34,28 @@ impl Encoding for Limb { impl Limb { /// Decode limb from a big endian byte slice. /// - /// Panics if the slice is not the same size as [`Limb::Repr`]. + /// Panics if the slice is larger than [`Limb::Repr`]. pub(crate) fn from_be_slice(bytes: &[u8]) -> Self { - Self::from_be_bytes(bytes.try_into().expect("slice not limb-sized")) + let mut repr = Self::ZERO.to_be_bytes(); + let repr_len = repr.len(); + if bytes.len() > repr_len { + panic!("The given slice is larger than the limb size"); + } + repr[(repr_len - bytes.len())..].copy_from_slice(bytes); + Self::from_be_bytes(repr) } /// 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")) + let mut repr = Self::ZERO.to_le_bytes(); + let repr_len = repr.len(); + if bytes.len() > repr_len { + panic!("The given slice is larger than the limb size"); + } + repr[..bytes.len()].copy_from_slice(bytes); + Self::from_le_bytes(repr) } } diff --git a/src/uint/boxed/encoding.rs b/src/uint/boxed/encoding.rs index ab4b0c0ad..6101017cd 100644 --- a/src/uint/boxed/encoding.rs +++ b/src/uint/boxed/encoding.rs @@ -8,10 +8,10 @@ use core::fmt; /// Decoding errors for [`BoxedUint`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum DecodeError { - /// Input is not a valid size. + /// Input size is too small to fit in the given precision. InputSize, - /// Precision is not a multiple of [`Limb::BYTES`]. + /// The deserialized number is larger than the given precision. Precision, } @@ -31,51 +31,53 @@ 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`]. + /// fixed as this type is not arbitrary-precision. + /// The new [`BoxedUint`] will be created with `bits_precision` + /// rounded up to a multiple of [`Limb::BITS`]. /// - /// If the length of `bytes` (when interpreted as bits) is larger than `bits_precision`, this - /// function will return [`DecodeError::InputSize`]. + /// If the length of `bytes` is larger than `bits_precision` (rounded up to a multiple of 8) + /// this function will return [`DecodeError::InputSize`]. + /// If the size of the decoded integer is larger than `bits_precision`, + /// this function will return [`DecodeError::Precision`]. pub fn from_be_slice(bytes: &[u8], bits_precision: u32) -> Result { if bytes.is_empty() && bits_precision == 0 { return Ok(Self::zero()); } - if bits_precision % Limb::BITS != 0 { - return Err(DecodeError::Precision); - } - - if bytes.len() % Limb::BYTES != 0 || bytes.len() * 8 > bits_precision as usize { + if bytes.len() > (bits_precision as usize + 7) / 8 { 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()) { + for (chunk, limb) in bytes.rchunks(Limb::BYTES).zip(ret.limbs.iter_mut()) { *limb = Limb::from_be_slice(chunk); } + if bits_precision < ret.bits() { + return Err(DecodeError::Precision); + } + 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`]. + /// fixed as this type is not arbitrary-precision. + /// The new [`BoxedUint`] will be created with `bits_precision` + /// rounded up to a multiple of [`Limb::BITS`]. /// - /// If the length of `bytes` (when interpreted as bits) is larger than `bits_precision`, this - /// function will return [`DecodeError::InputSize`]. + /// If the length of `bytes` is larger than `bits_precision` (rounded up to a multiple of 8) + /// this function will return [`DecodeError::InputSize`]. + /// If the size of the decoded integer is larger than `bits_precision`, + /// this function will return [`DecodeError::Precision`]. pub fn from_le_slice(bytes: &[u8], bits_precision: u32) -> Result { if bytes.is_empty() && bits_precision == 0 { return Ok(Self::zero()); } - if bits_precision % Limb::BITS != 0 { - return Err(DecodeError::Precision); - } - - if bytes.len() % Limb::BYTES != 0 || bytes.len() * 8 > bits_precision as usize { + if bytes.len() > (bits_precision as usize + 7) / 8 { return Err(DecodeError::InputSize); } @@ -85,6 +87,10 @@ impl BoxedUint { *limb = Limb::from_le_slice(chunk); } + if bits_precision < ret.bits() { + return Err(DecodeError::Precision); + } + Ok(ret) } @@ -186,19 +192,39 @@ mod tests { } #[test] + #[cfg(target_pointer_width = "32")] fn from_be_slice_not_word_sized() { - let bytes = hex!("00112233445566778899aabbccddee"); + let bytes = hex!("112233445566778899aabbccddeeff"); + let n = BoxedUint::from_be_slice(&bytes, 127).unwrap(); assert_eq!( - BoxedUint::from_be_slice(&bytes, 128), - Err(DecodeError::InputSize) + n.as_limbs(), + &[ + Limb(0xccddeeff), + Limb(0x8899aabb), + Limb(0x44556677), + Limb(0x00112233) + ] ); + assert_eq!(n.bits_precision(), 128); } #[test] - fn from_be_slice_bad_precision() { - let bytes = hex!("00112233445566778899aabbccddeeff"); + #[cfg(target_pointer_width = "64")] + fn from_be_slice_not_word_sized() { + let bytes = hex!("112233445566778899aabbccddeeff"); + let n = BoxedUint::from_be_slice(&bytes, 127).unwrap(); assert_eq!( - BoxedUint::from_be_slice(&bytes, 127), + n.as_limbs(), + &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)] + ); + assert_eq!(n.bits_precision(), 128); + } + + #[test] + fn from_be_slice_non_multiple_precision() { + let bytes = hex!("0f112233445566778899aabbccddeeff"); + assert_eq!( + BoxedUint::from_be_slice(&bytes, 121), Err(DecodeError::Precision) ); } @@ -259,19 +285,39 @@ mod tests { } #[test] + #[cfg(target_pointer_width = "32")] fn from_le_slice_not_word_sized() { let bytes = hex!("ffeeddccbbaa998877665544332211"); + let n = BoxedUint::from_le_slice(&bytes, 127).unwrap(); assert_eq!( - BoxedUint::from_be_slice(&bytes, 128), - Err(DecodeError::InputSize) + n.as_limbs(), + &[ + Limb(0xccddeeff), + Limb(0x8899aabb), + Limb(0x44556677), + Limb(0x00112233) + ] ); + assert_eq!(n.bits_precision(), 128); } #[test] - fn from_le_slice_bad_precision() { - let bytes = hex!("ffeeddccbbaa99887766554433221100"); + #[cfg(target_pointer_width = "64")] + fn from_le_slice_not_word_sized() { + let bytes = hex!("ffeeddccbbaa998877665544332211"); + let n = BoxedUint::from_le_slice(&bytes, 127).unwrap(); + assert_eq!( + n.as_limbs(), + &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)] + ); + assert_eq!(n.bits_precision(), 128); + } + + #[test] + fn from_le_slice_non_multiple_precision() { + let bytes = hex!("ffeeddccbbaa998877665544332211f0"); assert_eq!( - BoxedUint::from_le_slice(&bytes, 127), + BoxedUint::from_le_slice(&bytes, 121), Err(DecodeError::Precision) ); } From 8e81c072135829419103cb38fb2a6a81a7d55647 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sat, 2 Dec 2023 10:31:56 -0800 Subject: [PATCH 4/6] Round up `nlimbs!()` to a multiple of Limb::BITS --- src/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/macros.rs b/src/macros.rs index a291f015f..c5a82beda 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -59,7 +59,7 @@ macro_rules! const_assert_ne { #[macro_export] macro_rules! nlimbs { ($bits:expr) => { - $bits / $crate::Limb::BITS as usize + (($bits + $crate::Limb::BITS - 1) / $crate::Limb::BITS) as usize }; } From 41462bd9ecfd57e440e1d7e34a323d81f9120db7 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sat, 2 Dec 2023 10:22:14 -0800 Subject: [PATCH 5/6] Make BoxedUint constructor arguments more descriptive --- src/uint/boxed.rs | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/uint/boxed.rs b/src/uint/boxed.rs index 219031bc3..71fb349d7 100644 --- a/src/uint/boxed.rs +++ b/src/uint/boxed.rs @@ -50,8 +50,8 @@ pub struct BoxedUint { } impl BoxedUint { - fn limbs_for_precision(bits_precision: u32) -> usize { - ((bits_precision + Limb::BITS - 1) / Limb::BITS) as usize + fn limbs_for_precision(at_least_bits_precision: u32) -> usize { + ((at_least_bits_precision + Limb::BITS - 1) / Limb::BITS) as usize } /// Get the value `0` represented as succinctly as possible. @@ -63,9 +63,9 @@ impl BoxedUint { /// Get the value `0` with the given number of bits of precision. /// - /// `bits_precision` is rounded up to a multiple of [`Limb::BITS`]. - pub fn zero_with_precision(bits_precision: u32) -> Self { - vec![Limb::ZERO; Self::limbs_for_precision(bits_precision)].into() + /// `at_least_bits_precision` is rounded up to a multiple of [`Limb::BITS`]. + pub fn zero_with_precision(at_least_bits_precision: u32) -> Self { + vec![Limb::ZERO; Self::limbs_for_precision(at_least_bits_precision)].into() } /// Get the value `1`, represented as succinctly as possible. @@ -77,9 +77,9 @@ impl BoxedUint { /// Get the value `1` with the given number of bits of precision. /// - /// `bits_precision` is rounded up to a multiple of [`Limb::BITS`]. - pub fn one_with_precision(bits_precision: u32) -> Self { - let mut ret = Self::zero_with_precision(bits_precision); + /// `at_least_bits_precision` is rounded up to a multiple of [`Limb::BITS`]. + pub fn one_with_precision(at_least_bits_precision: u32) -> Self { + let mut ret = Self::zero_with_precision(at_least_bits_precision); ret.limbs[0] = Limb::ONE; ret } @@ -121,11 +121,12 @@ impl BoxedUint { !self.is_odd() } - /// Get the maximum value for a given number of bits of precision. + /// Get the maximum value for a `BoxedUint` created with `at_least_bits_precision` + /// precision bits requested. /// - /// `bits_precision` is rounded up to a multiple of [`Limb::BITS`]. - pub fn max(bits_precision: u32) -> Self { - vec![Limb::MAX; Self::limbs_for_precision(bits_precision)].into() + /// That is, returns the value `2^self.bits_precision() - 1`. + pub fn max(at_least_bits_precision: u32) -> Self { + vec![Limb::MAX; Self::limbs_for_precision(at_least_bits_precision)].into() } /// Create a [`BoxedUint`] from an array of [`Word`]s (i.e. word-sized unsigned @@ -225,21 +226,21 @@ impl BoxedUint { /// Widen this type's precision to the given number of bits. /// - /// Panics if `bits_precision` is smaller than the current precision. - pub fn widen(&self, bits_precision: u32) -> BoxedUint { - assert!(bits_precision >= self.bits_precision()); + /// Panics if `at_least_bits_precision` is smaller than the current precision. + pub fn widen(&self, at_least_bits_precision: u32) -> BoxedUint { + assert!(at_least_bits_precision >= self.bits_precision()); - let mut ret = BoxedUint::zero_with_precision(bits_precision); + let mut ret = BoxedUint::zero_with_precision(at_least_bits_precision); ret.limbs[..self.nlimbs()].copy_from_slice(&self.limbs); ret } /// Shortens this type's precision to the given number of bits. /// - /// Panics if `bits_precision` is larger than the current precision. - pub fn shorten(&self, bits_precision: u32) -> BoxedUint { - assert!(bits_precision <= self.bits_precision()); - let mut ret = BoxedUint::zero_with_precision(bits_precision); + /// Panics if `at_least_bits_precision` is larger than the current precision. + pub fn shorten(&self, at_least_bits_precision: u32) -> BoxedUint { + assert!(at_least_bits_precision <= self.bits_precision()); + let mut ret = BoxedUint::zero_with_precision(at_least_bits_precision); let nlimbs = ret.nlimbs(); ret.limbs.copy_from_slice(&self.limbs[..nlimbs]); ret From ba220e9dc60b70bf801d483fa8fde9b3c561a0f8 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sat, 2 Dec 2023 10:53:58 -0800 Subject: [PATCH 6/6] RFCs --- src/limb/encoding.rs | 14 ++++++++------ src/uint/boxed/encoding.rs | 7 +++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/limb/encoding.rs b/src/limb/encoding.rs index 035693aee..d77ab1fe5 100644 --- a/src/limb/encoding.rs +++ b/src/limb/encoding.rs @@ -38,9 +38,10 @@ impl Limb { pub(crate) fn from_be_slice(bytes: &[u8]) -> Self { let mut repr = Self::ZERO.to_be_bytes(); let repr_len = repr.len(); - if bytes.len() > repr_len { - panic!("The given slice is larger than the limb size"); - } + assert!( + bytes.len() <= repr_len, + "The given slice is larger than the limb size" + ); repr[(repr_len - bytes.len())..].copy_from_slice(bytes); Self::from_be_bytes(repr) } @@ -51,9 +52,10 @@ impl Limb { pub(crate) fn from_le_slice(bytes: &[u8]) -> Self { let mut repr = Self::ZERO.to_le_bytes(); let repr_len = repr.len(); - if bytes.len() > repr_len { - panic!("The given slice is larger than the limb size"); - } + assert!( + bytes.len() <= repr_len, + "The given slice is larger than the limb size" + ); repr[..bytes.len()].copy_from_slice(bytes); Self::from_le_bytes(repr) } diff --git a/src/uint/boxed/encoding.rs b/src/uint/boxed/encoding.rs index 6101017cd..a5bd54788 100644 --- a/src/uint/boxed/encoding.rs +++ b/src/uint/boxed/encoding.rs @@ -18,8 +18,11 @@ pub enum DecodeError { impl fmt::Display for DecodeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::InputSize => write!(f, "input is not a valid size"), - Self::Precision => write!(f, "precision is not a multiple of the word size"), + Self::InputSize => write!(f, "input size is too small to fit in the given precision"), + Self::Precision => write!( + f, + "the deserialized number is larger than the given precision" + ), } } }