From ecb405deec53fbcb84d2b4054fafafc4cda967df Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Fri, 21 Nov 2025 16:19:37 -0800 Subject: [PATCH] Implement `Encoding` for all `Uint`s --- src/lib.rs | 2 + src/traits.rs | 2 +- src/uint.rs | 15 ++-- src/uint/div.rs | 10 ++- src/uint/encoding.rs | 175 +++++++++++++++++++++++++++++++++---------- src/uint/macros.rs | 36 --------- tests/monty_form.rs | 20 ++--- tests/uint.rs | 4 +- 8 files changed, 169 insertions(+), 95 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f81f682e9..234b5edb3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -167,6 +167,8 @@ #[macro_use] extern crate alloc; +pub use uint::encoding::{EncodedUint, TryFromSliceError}; + #[cfg(feature = "rand_core")] pub use rand_core; #[cfg(feature = "rlp")] diff --git a/src/traits.rs b/src/traits.rs index 1a49c2f15..efdb1e2b6 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -644,7 +644,7 @@ pub trait Encoding: Sized { + Copy + Clone + Sized - + for<'a> TryFrom<&'a [u8], Error = core::array::TryFromSliceError>; + + for<'a> TryFrom<&'a [u8], Error: core::error::Error>; /// Decode from big endian bytes. fn from_be_bytes(bytes: Self::Repr) -> Self; diff --git a/src/uint.rs b/src/uint.rs index c27ae6a81..a5276cd2e 100644 --- a/src/uint.rs +++ b/src/uint.rs @@ -16,10 +16,13 @@ pub use extra_sizes::*; pub(crate) use ref_type::UintRef; use crate::{ - Bounded, ConstChoice, ConstCtOption, ConstOne, ConstZero, Constants, Encoding, FixedInteger, - Int, Integer, Limb, NonZero, Odd, One, Unsigned, Word, Zero, modular::MontyForm, + Bounded, ConstChoice, ConstCtOption, ConstOne, ConstZero, Constants, FixedInteger, Int, + Integer, Limb, NonZero, Odd, One, Unsigned, Word, Zero, modular::MontyForm, }; +#[cfg(feature = "serde")] +use crate::Encoding; + #[macro_use] mod macros; @@ -413,10 +416,10 @@ where where D: Deserializer<'de>, { - let mut buffer = Self::ZERO.to_le_bytes(); + let mut buffer = Encoding::to_le_bytes(&Self::ZERO); serdect::array::deserialize_hex_or_bin(buffer.as_mut(), deserializer)?; - Ok(Self::from_le_bytes(buffer)) + Ok(Encoding::from_le_bytes(buffer)) } } @@ -429,7 +432,7 @@ where where S: Serializer, { - serdect::array::serialize_hex_lower_or_bin(&Encoding::to_le_bytes(self), serializer) + serdect::slice::serialize_hex_lower_or_bin(&Encoding::to_le_bytes(self), serializer) } } @@ -655,7 +658,7 @@ mod tests { let be_bytes = a.to_be_bytes(); let le_bytes = a.to_le_bytes(); for i in 0..16 { - assert_eq!(le_bytes[i], be_bytes[15 - i]); + assert_eq!(le_bytes.as_ref()[i], be_bytes.as_ref()[15 - i]); } let a_from_be = U128::from_be_bytes(be_bytes); diff --git a/src/uint/div.rs b/src/uint/div.rs index aa674520a..f918c63c5 100644 --- a/src/uint/div.rs +++ b/src/uint/div.rs @@ -873,10 +873,16 @@ mod tests { ); let rem = U256::rem_wide(lo_hi, &modulus); // Lower half is zero - assert_eq!(rem.to_be_bytes()[0..16], U128::ZERO.to_be_bytes()); + assert_eq!( + &rem.to_be_bytes().as_ref()[0..16], + U128::ZERO.to_be_bytes().as_ref() + ); // Upper half let expected = U128::from_be_hex("203F80FE03F80FE03F80FE03F80FE041"); - assert_eq!(rem.to_be_bytes()[16..], expected.to_be_bytes()); + assert_eq!( + &rem.to_be_bytes().as_ref()[16..], + expected.to_be_bytes().as_ref() + ); let remv = U256::rem_wide_vartime(lo_hi, &modulus); assert_eq!(rem, remv); diff --git a/src/uint/encoding.rs b/src/uint/encoding.rs index 478d257b7..0761ec586 100644 --- a/src/uint/encoding.rs +++ b/src/uint/encoding.rs @@ -6,18 +6,17 @@ mod der; #[cfg(feature = "rlp")] mod rlp; +use core::{fmt, ops::Deref}; + #[cfg(feature = "alloc")] use alloc::{string::String, vec::Vec}; use super::Uint; -use crate::{DecodeError, Limb, Word}; +use crate::{DecodeError, Encoding, Limb, Word}; #[cfg(feature = "alloc")] use crate::{ConstChoice, NonZero, Reciprocal, UintRef, WideWord}; -#[cfg(feature = "hybrid-array")] -use crate::Encoding; - #[cfg(feature = "alloc")] const RADIX_ENCODING_LIMBS_LARGE: usize = 16; #[cfg(feature = "alloc")] @@ -204,58 +203,158 @@ impl Uint { let mut buf = *self; radix_encode_limbs_mut_to_string(radix, buf.as_mut_uint_ref()) } + + /// Serialize as big endian bytes. + pub const fn to_be_bytes(&self) -> EncodedUint { + EncodedUint::new_be(self) + } + + /// Serialize as little endian bytes. + pub const fn to_le_bytes(&self) -> EncodedUint { + EncodedUint::new_le(self) + } +} + +/// [`Uint`] encoded as bytes. +// Until const generic expressions are stable, we cannot statically declare a `u8` array +// of the size `LIMBS * Limb::BYTES`. +// So instead we use the array of words, and treat it as an array of bytes. +// It's a little hacky, but it works, because the array is guaranteed to be contiguous. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct EncodedUint([Word; LIMBS]); + +#[allow(unsafe_code)] +const fn cast_slice(limbs: &[Word]) -> &[u8] { + let new_len = size_of_val(limbs); + unsafe { core::slice::from_raw_parts(limbs.as_ptr() as *mut u8, new_len) } } -/// Encode a [`Uint`] to a big endian byte array of the given size. -pub(crate) const fn uint_to_be_bytes( - uint: &Uint, -) -> [u8; BYTES] { - if BYTES != LIMBS * Limb::BYTES { - panic!("BYTES != LIMBS * Limb::BYTES"); +#[allow(unsafe_code)] +const fn cast_slice_mut(limbs: &mut [Word]) -> &mut [u8] { + let new_len = size_of_val(limbs); + unsafe { core::slice::from_raw_parts_mut(limbs.as_mut_ptr() as *mut u8, new_len) } +} + +impl EncodedUint { + const fn new_le(value: &Uint) -> Self { + let mut buffer = [0; LIMBS]; + let mut i = 0; + + while i < LIMBS { + let src_bytes = &value.limbs[i].0.to_le_bytes(); + + // We could cast the whole `buffer` to bytes at once, + // but IndexMut does not work in const context. + let dst_bytes: &mut [u8] = cast_slice_mut(core::slice::from_mut(&mut buffer[i])); + + // `copy_from_slice` can be used here when MSRV moves past 1.87 + let mut j = 0; + while j < Limb::BYTES { + dst_bytes[j] = src_bytes[j]; + j += 1; + } + + i += 1; + } + Self(buffer) } - let mut ret = [0u8; BYTES]; - let mut i = 0; + const fn new_be(value: &Uint) -> Self { + let mut buffer = [0; LIMBS]; + let mut i = 0; + while i < LIMBS { + let src_bytes = &value.limbs[i].0.to_be_bytes(); - while i < LIMBS { - let limb_bytes = uint.limbs[LIMBS - i - 1].0.to_be_bytes(); - let mut j = 0; + // We could cast the whole `buffer` to bytes at once, + // but IndexMut does not work in const context. + let dst_bytes: &mut [u8] = + cast_slice_mut(core::slice::from_mut(&mut buffer[LIMBS - 1 - i])); - while j < Limb::BYTES { - ret[i * Limb::BYTES + j] = limb_bytes[j]; - j += 1; + // `copy_from_slice` can be used here when MSRV moves past 1.87 + let mut j = 0; + while j < Limb::BYTES { + dst_bytes[j] = src_bytes[j]; + j += 1; + } + + i += 1; } + Self(buffer) + } +} - i += 1; +impl Default for EncodedUint { + fn default() -> Self { + Self([0; LIMBS]) } +} - ret +impl AsRef<[u8]> for EncodedUint { + fn as_ref(&self) -> &[u8] { + cast_slice(&self.0) + } } -/// Encode a [`Uint`] to a little endian byte array of the given size. -pub(crate) const fn uint_to_le_bytes( - uint: &Uint, -) -> [u8; BYTES] { - if BYTES != LIMBS * Limb::BYTES { - panic!("BYTES != LIMBS * Limb::BYTES"); +impl AsMut<[u8]> for EncodedUint { + fn as_mut(&mut self) -> &mut [u8] { + cast_slice_mut(&mut self.0) } +} - let mut ret = [0u8; BYTES]; - let mut i = 0; +impl Deref for EncodedUint { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} - while i < LIMBS { - let limb_bytes = uint.limbs[i].0.to_le_bytes(); - let mut j = 0; +/// Returned if an object cannot be instantiated from the given byte slice. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TryFromSliceError; - while j < Limb::BYTES { - ret[i * Limb::BYTES + j] = limb_bytes[j]; - j += 1; +impl fmt::Display for TryFromSliceError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "TryFromSliceError") + } +} + +impl core::error::Error for TryFromSliceError {} + +impl<'a, const LIMBS: usize> TryFrom<&'a [u8]> for EncodedUint { + type Error = TryFromSliceError; + + fn try_from(bytes: &'a [u8]) -> Result { + if bytes.len() != Uint::::BYTES { + return Err(TryFromSliceError); } + let mut result = Self::default(); + result.as_mut().copy_from_slice(bytes); + Ok(result) + } +} + +impl Encoding for Uint { + type Repr = EncodedUint; + + #[inline] + fn from_be_bytes(bytes: Self::Repr) -> Self { + Self::from_be_slice(bytes.as_ref()) + } - i += 1; + #[inline] + fn from_le_bytes(bytes: Self::Repr) -> Self { + Self::from_le_slice(bytes.as_ref()) } - ret + #[inline] + fn to_be_bytes(&self) -> Self::Repr { + self.to_be_bytes() + } + + #[inline] + fn to_le_bytes(&self) -> Self::Repr { + self.to_le_bytes() + } } /// Decode a single nibble of upper or lower hex @@ -1057,7 +1156,7 @@ mod tests { let n = UintEx::from_be_hex("0011223344556677"); let bytes = n.to_be_bytes(); - assert_eq!(bytes, hex!("0011223344556677")); + assert_eq!(bytes.as_ref(), hex!("0011223344556677")); #[cfg(feature = "der")] assert_eq!(super::der::count_der_be_bytes(&n.limbs), 7); @@ -1069,7 +1168,7 @@ mod tests { let n = UintEx::from_be_hex("00112233445566778899aabbccddeeff"); let bytes = n.to_be_bytes(); - assert_eq!(bytes, hex!("00112233445566778899aabbccddeeff")); + assert_eq!(bytes.as_ref(), hex!("00112233445566778899aabbccddeeff")); #[cfg(feature = "der")] assert_eq!(super::der::count_der_be_bytes(&n.limbs), 15); diff --git a/src/uint/macros.rs b/src/uint/macros.rs index 2682fc579..aaf40e0b6 100644 --- a/src/uint/macros.rs +++ b/src/uint/macros.rs @@ -7,42 +7,6 @@ macro_rules! impl_uint_aliases { #[doc = $doc] #[doc="unsigned big integer."] pub type $name = Uint<{ nlimbs!($bits) }>; - - impl $name { - /// Serialize as big endian bytes. - pub const fn to_be_bytes(&self) -> [u8; $bits / 8] { - encoding::uint_to_be_bytes::<{ nlimbs!($bits) }, { $bits / 8 }>(self) - } - - /// Serialize as little endian bytes. - pub const fn to_le_bytes(&self) -> [u8; $bits / 8] { - encoding::uint_to_le_bytes::<{ nlimbs!($bits) }, { $bits / 8 }>(self) - } - } - - impl Encoding for $name { - type Repr = [u8; $bits / 8]; - - #[inline] - fn from_be_bytes(bytes: Self::Repr) -> Self { - Self::from_be_slice(&bytes) - } - - #[inline] - fn from_le_bytes(bytes: Self::Repr) -> Self { - Self::from_le_slice(&bytes) - } - - #[inline] - fn to_be_bytes(&self) -> Self::Repr { - encoding::uint_to_be_bytes(self) - } - - #[inline] - fn to_le_bytes(&self) -> Self::Repr { - encoding::uint_to_le_bytes(self) - } - } )+ }; } diff --git a/tests/monty_form.rs b/tests/monty_form.rs index 1c27ec601..4f4398a7c 100644 --- a/tests/monty_form.rs +++ b/tests/monty_form.rs @@ -4,8 +4,8 @@ mod common; use common::to_biguint; use crypto_bigint::{ - Bounded, Constants, Encoding, Integer, Invert, Monty, NonZero, Odd, U128, U256, U512, U1024, - U2048, U4096, Unsigned, + Bounded, Constants, EncodedUint, Encoding, Integer, Invert, Monty, NonZero, Odd, U128, U256, + U512, U1024, U2048, U4096, Unsigned, modular::{MontyForm, MontyParams}, }; use num_bigint::BigUint; @@ -99,7 +99,7 @@ prop_compose! { monty_params_from_edge::(edge_bytes, &mut rng) }) ) -> Result<(U128, ::Monty , ::Monty, BigUint),TestCaseError> { - random_invertible_uint(bytes, monty_params, monty_params.modulus().get()) + random_invertible_uint(EncodedUint::try_from(bytes.as_ref()).unwrap(), monty_params, monty_params.modulus().get()) } } prop_compose! { @@ -109,7 +109,7 @@ prop_compose! { monty_params_from_edge::(edge_bytes, &mut rng) }) ) -> Result<(U256, ::Monty , ::Monty, BigUint),TestCaseError> { - random_invertible_uint(bytes, monty_params, monty_params.modulus().get()) + random_invertible_uint(EncodedUint::try_from(bytes.as_ref()).unwrap(), monty_params, monty_params.modulus().get()) } } prop_compose! { @@ -119,7 +119,7 @@ prop_compose! { monty_params_from_edge::(edge_bytes, &mut rng) }) ) -> Result<(U2048, ::Monty , ::Monty, BigUint),TestCaseError> { - random_invertible_uint(bytes, monty_params, monty_params.modulus().get()) + random_invertible_uint(EncodedUint::try_from(bytes.as_ref()).unwrap(), monty_params, monty_params.modulus().get()) } } prop_compose! { @@ -129,7 +129,7 @@ prop_compose! { monty_params_from_edge::(edge_bytes, &mut rng) }) ) -> Result<(U1024, ::Monty, ::Monty, BigUint),TestCaseError> { - random_invertible_uint(bytes, monty_params, monty_params.modulus().get()) + random_invertible_uint(EncodedUint::try_from(bytes.as_ref()).unwrap(), monty_params, monty_params.modulus().get()) } } proptest! { @@ -166,7 +166,7 @@ proptest! { ); // …and agrees with the num_modular crate assert_eq!( - BigUint::from_be_bytes(&normal_form_inv.to_be_bytes()), + BigUint::from_be_bytes(normal_form_inv.to_be_bytes().as_ref()), r_num_modular_inv, "num_modular ≠ crypto_bigint" ) @@ -205,7 +205,7 @@ proptest! { ); // …and agrees with the num_modular crate assert_eq!( - BigUint::from_be_bytes(&normal_form_inv.to_be_bytes()), + BigUint::from_be_bytes(normal_form_inv.to_be_bytes().as_ref()), r_num_modular_inv, "num_modular ≠ crypto_bigint" ) @@ -244,7 +244,7 @@ proptest! { ); // …and agrees with the num_modular crate assert_eq!( - BigUint::from_be_bytes(&normal_form_inv.to_be_bytes()), + BigUint::from_be_bytes(normal_form_inv.to_be_bytes().as_ref()), r_num_modular_inv, "num_modular ≠ crypto_bigint" ) @@ -283,7 +283,7 @@ proptest! { ); // …and agrees with the num_modular crate assert_eq!( - BigUint::from_be_bytes(&normal_form_inv.to_be_bytes()), + BigUint::from_be_bytes(normal_form_inv.to_be_bytes().as_ref()), r_num_modular_inv, "num_modular ≠ crypto_bigint" ) diff --git a/tests/uint.rs b/tests/uint.rs index 1a71131a1..78983e498 100644 --- a/tests/uint.rs +++ b/tests/uint.rs @@ -531,11 +531,11 @@ proptest! { #[test] fn encoding_reverse(a in uint()) { let mut bytes = a.to_be_bytes(); - bytes.reverse(); + bytes.as_mut().reverse(); prop_assert_eq!(a, U256::from_le_bytes(bytes)); let mut bytes = a.to_le_bytes(); - bytes.reverse(); + bytes.as_mut().reverse(); prop_assert_eq!(a, U256::from_be_bytes(bytes)); }