From c7cf22e155aaf1acb6b3dc1053c0ee48a9dc7e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Delabrouille?= <34384633+tdelabro@users.noreply.github.com> Date: Tue, 26 Dec 2023 10:52:30 +0100 Subject: [PATCH] Impl num traits (#18) --- .gitignore | 2 + crates/starknet-types-core/Cargo.toml | 29 +++-- .../src/{ => felt}/felt_arbitrary.rs | 3 +- .../src/{felt.rs => felt/mod.rs} | 119 ++++++----------- .../src/felt/num_traits_impl.rs | 123 ++++++++++++++++++ crates/starknet-types-core/src/lib.rs | 2 - ensure_no_std/Cargo.toml | 1 + ensure_no_std/src/main.rs | 2 + 8 files changed, 189 insertions(+), 92 deletions(-) rename crates/starknet-types-core/src/{ => felt}/felt_arbitrary.rs (95%) rename crates/starknet-types-core/src/{felt.rs => felt/mod.rs} (96%) create mode 100644 crates/starknet-types-core/src/felt/num_traits_impl.rs diff --git a/.gitignore b/.gitignore index 196e176..2d01884 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ Cargo.lock # Added by cargo /target + +*.DS_Store diff --git a/crates/starknet-types-core/Cargo.toml b/crates/starknet-types-core/Cargo.toml index 612e168..01a451e 100644 --- a/crates/starknet-types-core/Cargo.toml +++ b/crates/starknet-types-core/Cargo.toml @@ -12,27 +12,38 @@ readme = "README.md" [dependencies] bitvec = { version = "1.0.1", default-features = false } -serde = { version = "1.0.163", optional = true, default-features = false } lambdaworks-math = {version = "0.3.0", default-features = false} -lambdaworks-crypto = {version = "0.3.0", default-features = false, optional = true} -parity-scale-codec = { version = "3.2.2", default-features = false, optional = true } -arbitrary = { version = "1.3.0", optional = true, default-features = false } num-traits = { version = "0.2.16", default-features = false } -num-bigint = {version = "0.4.4", default-features = false} -num-integer = {version = "0.1.45", default-features = false} +num-bigint = { version = "0.4.4", default-features = false } +num-integer = { version = "0.1.45", default-features = false } lazy_static = { version = "1.4.0", default-features = false, features = [ "spin_no_std", ] } +# Optional +arbitrary = { version = "1.3.0", optional = true } +serde = { version = "1.0.163", optional = true, default-features = false, features = ["alloc"] } +lambdaworks-crypto = { version = "0.3.0", default-features = false, optional = true } +parity-scale-codec = { version = "3.2.2", default-features = false, optional = true } + [features] -default = ["std", "serde", "curve"] +default = ["std", "serde", "curve", "num-traits"] +std = [ + "bitvec/std", + "lambdaworks-math/std", + "num-traits/std", + "num-bigint/std", + "num-integer/std", + "serde?/std", +] +alloc = [] curve = [] hash = ["dep:lambdaworks-crypto"] -std = ["alloc"] -alloc = ["serde?/alloc"] arbitrary = ["std", "dep:arbitrary"] parity-scale-codec = ["dep:parity-scale-codec"] +serde = ["alloc", "dep:serde"] +num-traits = [] [dev-dependencies] proptest = "1.1.0" diff --git a/crates/starknet-types-core/src/felt_arbitrary.rs b/crates/starknet-types-core/src/felt/felt_arbitrary.rs similarity index 95% rename from crates/starknet-types-core/src/felt_arbitrary.rs rename to crates/starknet-types-core/src/felt/felt_arbitrary.rs index 4f2b0f4..aa83fd0 100644 --- a/crates/starknet-types-core/src/felt_arbitrary.rs +++ b/crates/starknet-types-core/src/felt/felt_arbitrary.rs @@ -1,5 +1,4 @@ use lambdaworks_math::{field::element::FieldElement, unsigned_integer::element::UnsignedInteger}; -use num_traits::Zero; use proptest::prelude::*; use crate::felt::Felt; @@ -37,7 +36,7 @@ fn any_felt() -> impl Strategy { /// Returns a [`Strategy`] that generates any nonzero Felt /// This is used to generate input values for proptests pub fn nonzero_felt() -> impl Strategy { - any_felt().prop_filter("is zero", |x| !x.is_zero()) + any_felt().prop_filter("is zero", |&x| x != Felt::ZERO) } impl Arbitrary for Felt { diff --git a/crates/starknet-types-core/src/felt.rs b/crates/starknet-types-core/src/felt/mod.rs similarity index 96% rename from crates/starknet-types-core/src/felt.rs rename to crates/starknet-types-core/src/felt/mod.rs index 26b0140..5b76705 100644 --- a/crates/starknet-types-core/src/felt.rs +++ b/crates/starknet-types-core/src/felt/mod.rs @@ -1,3 +1,6 @@ +#[cfg(test)] +mod felt_arbitrary; + use core::ops::{Add, Mul, Neg}; use bitvec::array::BitArray; @@ -5,7 +8,10 @@ use lazy_static::lazy_static; use num_bigint::{BigInt, BigUint, Sign}; use num_integer::Integer; use num_traits::Num; -use num_traits::{FromPrimitive, One, ToPrimitive, Zero}; +use num_traits::{One, Zero}; + +#[cfg(feature = "num-traits")] +mod num_traits_impl; lazy_static! { pub static ref CAIRO_PRIME_BIGINT: BigInt = BigInt::from_str_radix( @@ -21,12 +27,9 @@ pub type BitArrayStore = [u64; 4]; #[cfg(not(target_pointer_width = "64"))] pub type BitArrayStore = [u32; 8]; +#[cfg(any(test, feature = "alloc"))] pub extern crate alloc; -use alloc::string::ToString; -#[cfg(not(target_pointer_width = "64"))] -use alloc::vec::Vec; - use lambdaworks_math::{ field::{ element::FieldElement, fields::fft_friendly::stark_252_prime_field::Stark252PrimeField, @@ -194,16 +197,26 @@ impl Felt { /// Converts to big-endian bit representation. /// This is as performant as [to_bits_le](Felt::to_bits_le) + #[cfg(target_pointer_width = "64")] + pub fn to_bits_be(&self) -> BitArray { + let mut limbs = self.0.representative().limbs; + limbs.reverse(); + + BitArray::new(limbs) + } + + /// Converts to big-endian bit representation. + /// This is as performant as [to_bits_le](Felt::to_bits_le) + #[cfg(all(feature = "alloc", not(target_pointer_width = "64")))] pub fn to_bits_be(&self) -> BitArray { let mut limbs = self.0.representative().limbs; limbs.reverse(); - #[cfg(not(target_pointer_width = "64"))] // Split limbs to adjust to BitArrayStore = [u32; 8] let limbs: [u32; 8] = limbs .into_iter() .flat_map(|n| [(n >> 32) as u32, n as u32]) - .collect::>() + .collect::>() .try_into() .unwrap(); @@ -219,15 +232,24 @@ impl Felt { /// Converts to little-endian bit representation. /// This is as performant as [to_bits_be](Felt::to_bits_be) + #[cfg(target_pointer_width = "64")] + pub fn to_bits_le(&self) -> BitArray { + let limbs = self.0.representative().limbs; + + BitArray::new(limbs) + } + + /// Converts to little-endian bit representation. + /// This is as performant as [to_bits_be](Felt::to_bits_be) + #[cfg(all(feature = "alloc", not(target_pointer_width = "64")))] pub fn to_bits_le(&self) -> BitArray { let limbs = self.0.representative().limbs; - #[cfg(not(target_pointer_width = "64"))] // Split limbs to adjust to BitArrayStore = [u32; 8] let limbs: [u32; 8] = limbs .into_iter() .flat_map(|n| [n as u32, (n >> 32) as u32]) - .collect::>() + .collect::>() .try_into() .unwrap(); @@ -446,7 +468,7 @@ impl TryFrom for NonZeroFelt { type Error = FeltIsZeroError; fn try_from(value: Felt) -> Result { - if value.is_zero() { + if value == Felt::ZERO { Err(FeltIsZeroError) } else { Ok(Self(value.0)) @@ -458,7 +480,7 @@ impl TryFrom<&Felt> for NonZeroFelt { type Error = FeltIsZeroError; fn try_from(value: &Felt) -> Result { - if value.is_zero() { + if *value == Felt::ZERO { Err(FeltIsZeroError) } else { Ok(Self(value.0)) @@ -532,57 +554,6 @@ impl_from!(i32, i128); impl_from!(i64, i128); impl_from!(isize, i128); -impl FromPrimitive for Felt { - fn from_i64(value: i64) -> Option { - Some(value.into()) - } - - fn from_u64(value: u64) -> Option { - Some(value.into()) - } - - fn from_i128(value: i128) -> Option { - Some(value.into()) - } - - fn from_u128(value: u128) -> Option { - Some(value.into()) - } -} - -// TODO: we need to decide whether we want conversions to signed primitives -// will support converting the high end of the field to negative. -impl ToPrimitive for Felt { - fn to_u64(&self) -> Option { - self.to_u128().and_then(|x| u64::try_from(x).ok()) - } - - fn to_i64(&self) -> Option { - self.to_u128().and_then(|x| i64::try_from(x).ok()) - } - - fn to_u128(&self) -> Option { - match self.0.representative().limbs { - [0, 0, hi, lo] => Some((lo as u128) | ((hi as u128) << 64)), - _ => None, - } - } - - fn to_i128(&self) -> Option { - self.to_u128().and_then(|x| i128::try_from(x).ok()) - } -} - -impl Zero for Felt { - fn is_zero(&self) -> bool { - *self == Felt::ZERO - } - - fn zero() -> Felt { - Felt::ZERO - } -} - impl Add<&Felt> for u64 { type Output = Option; @@ -874,10 +845,10 @@ mod arithmetic { } #[cfg(feature = "serde")] -mod serde { - use ::serde::{de, ser::SerializeSeq, Deserialize, Serialize}; +mod serde_impl { use alloc::{format, string::String}; use core::fmt; + use serde::{de, ser::SerializeSeq, Deserialize, Serialize}; use super::*; @@ -940,7 +911,7 @@ mod formatting { /// Represents [Felt] in decimal by default. impl fmt::Display for Felt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.is_zero() { + if *self == Felt::ZERO { return write!(f, "0"); } @@ -974,13 +945,13 @@ mod formatting { } /// Represents [Felt] in uppercase hexadecimal format. + #[cfg(feature = "alloc")] impl fmt::UpperHex for Felt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "0x{}", - self.0 - .to_string() + alloc::string::ToString::to_string(&self.0) .strip_prefix("0x") .unwrap() .to_uppercase() @@ -1025,8 +996,8 @@ mod errors { #[cfg(test)] mod test { use super::alloc::{format, string::String, vec::Vec}; + use super::felt_arbitrary::nonzero_felt; use super::*; - use crate::felt_arbitrary::nonzero_felt; use core::ops::Shl; use proptest::prelude::*; #[cfg(feature = "serde")] @@ -1255,7 +1226,7 @@ mod test { #[test] fn non_zero_is_not_zero(x in nonzero_felt()) { - prop_assert!(!x.is_zero()) + prop_assert!(x != Felt::ZERO) } #[test] @@ -1336,21 +1307,11 @@ mod test { assert_eq!(Felt::MAX.to_bytes_be(), max_bytes); } - #[test] - fn zero_is_zero() { - assert!(Felt::ZERO.is_zero()); - } - #[test] fn non_zero_felt_from_zero_should_fail() { assert!(NonZeroFelt::try_from(Felt::ZERO).is_err()); } - #[test] - fn default_is_zero() { - assert!(Felt::default().is_zero()); - } - #[test] fn mul_operations() { assert_eq!(Felt::ONE * Felt::THREE, Felt::THREE); diff --git a/crates/starknet-types-core/src/felt/num_traits_impl.rs b/crates/starknet-types-core/src/felt/num_traits_impl.rs new file mode 100644 index 0000000..886f13b --- /dev/null +++ b/crates/starknet-types-core/src/felt/num_traits_impl.rs @@ -0,0 +1,123 @@ +use super::Felt; +use num_traits::{FromPrimitive, Inv, One, Pow, ToPrimitive, Zero}; + +impl FromPrimitive for Felt { + fn from_i64(value: i64) -> Option { + Some(value.into()) + } + + fn from_u64(value: u64) -> Option { + Some(value.into()) + } + + fn from_i128(value: i128) -> Option { + Some(value.into()) + } + + fn from_u128(value: u128) -> Option { + Some(value.into()) + } +} + +// TODO: we need to decide whether we want conversions to signed primitives +// will support converting the high end of the field to negative. +impl ToPrimitive for Felt { + fn to_u64(&self) -> Option { + self.to_u128().and_then(|x| u64::try_from(x).ok()) + } + + fn to_i64(&self) -> Option { + self.to_u128().and_then(|x| i64::try_from(x).ok()) + } + + fn to_u128(&self) -> Option { + match self.0.representative().limbs { + [0, 0, hi, lo] => Some((lo as u128) | ((hi as u128) << 64)), + _ => None, + } + } + + fn to_i128(&self) -> Option { + self.to_u128().and_then(|x| i128::try_from(x).ok()) + } +} + +impl Zero for Felt { + fn is_zero(&self) -> bool { + *self == Felt::ZERO + } + + fn zero() -> Felt { + Felt::ZERO + } +} + +impl One for Felt { + fn one() -> Self { + Felt::ONE + } +} + +impl Inv for Felt { + type Output = Option; + + fn inv(self) -> Self::Output { + self.inverse() + } +} + +impl Pow for Felt { + type Output = Self; + + fn pow(self, rhs: u8) -> Self::Output { + Self(self.0.pow(rhs as u128)) + } +} +impl Pow for Felt { + type Output = Self; + + fn pow(self, rhs: u16) -> Self::Output { + Self(self.0.pow(rhs as u128)) + } +} +impl Pow for Felt { + type Output = Self; + + fn pow(self, rhs: u32) -> Self::Output { + Self(self.0.pow(rhs as u128)) + } +} +impl Pow for Felt { + type Output = Self; + + fn pow(self, rhs: u64) -> Self::Output { + Self(self.0.pow(rhs as u128)) + } +} +impl Pow for Felt { + type Output = Self; + + fn pow(self, rhs: u128) -> Self::Output { + Self(self.0.pow(rhs)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn zero_is_zero() { + assert!(Felt::ZERO.is_zero()); + } + + #[test] + fn one_is_one() { + assert!(Felt::ONE.is_one()); + } + + #[test] + fn default_is_zero() { + assert!(Felt::default().is_zero()); + } +} diff --git a/crates/starknet-types-core/src/lib.rs b/crates/starknet-types-core/src/lib.rs index 45bd805..bd641fc 100644 --- a/crates/starknet-types-core/src/lib.rs +++ b/crates/starknet-types-core/src/lib.rs @@ -5,5 +5,3 @@ pub mod curve; pub mod hash; pub mod felt; -#[cfg(test)] -mod felt_arbitrary; diff --git a/ensure_no_std/Cargo.toml b/ensure_no_std/Cargo.toml index 4d13191..84bc500 100644 --- a/ensure_no_std/Cargo.toml +++ b/ensure_no_std/Cargo.toml @@ -9,6 +9,7 @@ starknet-types-core = { path = "../crates/starknet-types-core", default-features "serde", "curve", "parity-scale-codec", + "num-traits", ] } starknet-types-rpc = { path = "../crates/starknet-types-rpc", default-features = false } wee_alloc = "0.4.5" diff --git a/ensure_no_std/src/main.rs b/ensure_no_std/src/main.rs index ae94696..3f94bd6 100644 --- a/ensure_no_std/src/main.rs +++ b/ensure_no_std/src/main.rs @@ -19,3 +19,5 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; #[allow(unused_imports)] use starknet_types_core; +#[allow(unused_imports)] +use starknet_types_rpc;