diff --git a/crates/starknet-types-core/Cargo.toml b/crates/starknet-types-core/Cargo.toml index 888ee42..9a69339 100644 --- a/crates/starknet-types-core/Cargo.toml +++ b/crates/starknet-types-core/Cargo.toml @@ -28,7 +28,7 @@ lambdaworks-crypto = { version = "0.5.0", default-features = false, optional = t parity-scale-codec = { version = "3.2.2", default-features = false, optional = true } [features] -default = ["std", "serde", "curve", "num-traits"] +default = ["std", "serde", "curve", "num-traits", "papyrus-encoding"] std = [ "bitvec/std", "lambdaworks-math/std", @@ -45,6 +45,8 @@ arbitrary = ["std", "dep:arbitrary"] parity-scale-codec = ["dep:parity-scale-codec"] serde = ["alloc", "dep:serde"] num-traits = [] +papyrus-encoding = [] +unsafe-non-zero = [] [dev-dependencies] proptest = "1.1.0" diff --git a/crates/starknet-types-core/src/curve/affine_point.rs b/crates/starknet-types-core/src/curve/affine_point.rs index 3e674d0..b97dd4b 100644 --- a/crates/starknet-types-core/src/curve/affine_point.rs +++ b/crates/starknet-types-core/src/curve/affine_point.rs @@ -3,6 +3,7 @@ use crate::felt::Felt; use lambdaworks_math::cyclic_group::IsGroup; use lambdaworks_math::elliptic_curve::short_weierstrass::curves::stark_curve::StarkCurve; use lambdaworks_math::elliptic_curve::short_weierstrass::point::ShortWeierstrassProjectivePoint; +use lambdaworks_math::elliptic_curve::short_weierstrass::traits::IsShortWeierstrass; use lambdaworks_math::elliptic_curve::traits::FromAffine; /// Represents a point on the Stark elliptic curve. @@ -18,6 +19,11 @@ impl AffinePoint { )?)) } + pub fn from_x(x: Felt) -> Option { + let y_squared = x * x * x + Felt(StarkCurve::a()) * x + Felt(StarkCurve::b()); + let y = y_squared.sqrt()?; + Self::new(x, y).ok() + } /// The point at infinity. pub fn identity() -> AffinePoint { Self(ShortWeierstrassProjectivePoint::neutral_element()) diff --git a/crates/starknet-types-core/src/curve/mod.rs b/crates/starknet-types-core/src/curve/mod.rs index 7f315a8..e58b3e3 100644 --- a/crates/starknet-types-core/src/curve/mod.rs +++ b/crates/starknet-types-core/src/curve/mod.rs @@ -2,6 +2,73 @@ mod affine_point; mod curve_errors; mod projective_point; +// use core::any::Any; + +// use lambdaworks_math::elliptic_curve::short_weierstrass::curves::stark_curve::StarkCurve; +// use lambdaworks_math::elliptic_curve::traits::IsEllipticCurve; + +// use crate::felt::Felt; +// use crate::felt::NonZeroFelt; + pub use self::affine_point::*; pub use self::curve_errors::*; pub use self::projective_point::*; + +// pub enum SignatureVerificationError { +// InvalidPublicKey, +// InvalidMessage, +// InvalidR, +// InvalidS, +// } +// const EC_ORDER: NonZeroFelt = unsafe { +// NonZeroFelt::from_raw_const([ +// 369010039416812937, +// 9, +// 1143265896874747514, +// 8939893405601011193, +// ]) +// }; + +// #[inline(always)] +// fn mul_by_bits(x: &AffinePoint, y: &Felt) -> AffinePoint { +// let x = ProjectivePoint::from_affine(x.x(), x.y()).unwrap(); +// let y: Vec = y.to_bits_le().into_iter().collect(); +// let z = &x * &y; +// z.to_affine() +// } +// pub fn verify_signature( +// public_key: &Felt, +// msg: &Felt, +// r: &Felt, +// s: &Felt, +// ) -> Result { +// if msg >= &Felt::ELEMENT_UPPER_BOUND { +// return Err(SignatureVerificationError::InvalidMessage); +// } +// if r == &Felt::ZERO || r >= &Felt::ELEMENT_UPPER_BOUND { +// return Err(SignatureVerificationError::InvalidR); +// } +// if s == &Felt::ZERO || s >= &Felt::ELEMENT_UPPER_BOUND { +// return Err(SignatureVerificationError::InvalidS); +// } + +// let full_public_key = match AffinePoint::from_x(*public_key) { +// Some(value) => value, +// None => return Err(SignatureVerificationError::InvalidPublicKey), +// }; + +// let w = s +// .mod_inverse(&EC_ORDER) +// .ok_or(SignatureVerificationError::InvalidS)?; +// if w == Felt::ZERO || w >= Felt::ELEMENT_UPPER_BOUND { +// return Err(SignatureVerificationError::InvalidS); +// } + +// let zw = msg.mul_mod(&w, &EC_ORDER); +// let zw_g = StarkCurve::generator().mul_by_bits(&zw); + +// let rw = r.mul_mod_floor(&w, &EC_ORDER); +// let rw_q = full_public_key.mul_by_bits(&rw); + +// Ok((&zw_g + &rw_q).x == *r || (&zw_g - &rw_q).x == *r) +// } diff --git a/crates/starknet-types-core/src/felt/mod.rs b/crates/starknet-types-core/src/felt/mod.rs index 5b76705..69eb907 100644 --- a/crates/starknet-types-core/src/felt/mod.rs +++ b/crates/starknet-types-core/src/felt/mod.rs @@ -12,6 +12,8 @@ use num_traits::{One, Zero}; #[cfg(feature = "num-traits")] mod num_traits_impl; +#[cfg(feature = "papyrus-encoding")] +mod papyrus_encoding; lazy_static! { pub static ref CAIRO_PRIME_BIGINT: BigInt = BigInt::from_str_radix( @@ -50,6 +52,48 @@ pub struct Felt(pub(crate) FieldElement); #[derive(Debug, Clone, Copy)] pub struct NonZeroFelt(FieldElement); +impl NonZeroFelt { + // /// Create a [NonZeroFelt] as a constant. If the value is zero will panic. + // pub const unsafe fn from_felt_const(felt: Felt) -> Self { + // let value = felt.0.representative().limbs; + // let mut i = 0; + // let mut zeros_nb = 0; + // while i < value.len() { + // if value[i] == 0 { + // zeros_nb += 1; + // } + // i += 1; + // } + // assert!(zeros_nb < value.len(), "Felt is zero"); + // Self(felt.0) + // } + + /// Create a [NonZeroFelt] as a constant. + /// # Safety + /// If the value is zero will panic. + pub const unsafe fn from_raw_const(value: [u64; 4]) -> Self { + let mut i = 0; + let mut zeros_nb = 0; + while i < value.len() { + if value[i] == 0 { + zeros_nb += 1; + } + i += 1; + } + assert!(zeros_nb < value.len(), "Felt is zero"); + let value = Felt::from_raw_const(value); + Self(value.0) + } + + /// Create a [NonZeroFelt] without checking it. If the [Felt] is indeed [Felt::ZERO] + /// this can lead to undefined behaviour and big security issue. + /// You should always use the [TryFrom] implementation + #[cfg(feature = "unsafe-non-zero")] + pub fn from_felt_unchecked(value: Felt) -> Self { + Self(value.0) + } +} + #[derive(Debug)] pub struct FeltIsZeroError; @@ -78,6 +122,26 @@ impl Felt { pub const MAX: Self = Self(FieldElement::::const_from_raw( UnsignedInteger::from_limbs([544, 0, 0, 32]), )); + pub const ELEMENT_UPPER_BOUND: Felt = Felt::from_raw_const([ + 576459263475450960, + 18446744073709255680, + 160989183, + 18446743986131435553, + ]); + + pub const fn from_raw_const(val: [u64; 4]) -> Self { + Self(FieldElement::::const_from_raw( + UnsignedInteger::from_limbs(val), + )) + } + + pub const fn from_u64(val: u64) -> Self { + Self(FieldElement::::const_from_raw( + UnsignedInteger::from_u64(val), + )) + } + + // pub const fn from_hex_const(hex_string: &str) -> Felt {} /// Creates a new [Felt] from its big-endian representation in a [u8; 32] array. /// This is as performant as [from_bytes_le](Felt::from_bytes_le). @@ -183,6 +247,14 @@ impl Felt { res } + /// Creates a new [Felt] from the raw internal representation. + /// See [UnisgnedInteger] to understand how it works under the hood. + pub fn from_raw(val: [u64; 4]) -> Self { + Self(FieldElement::::from_raw( + UnsignedInteger::from_limbs(val), + )) + } + /// Converts to big-endian byte representation in a [u8] array. /// This is as performant as [to_bytes_le](Felt::to_bytes_le) pub fn to_bytes_be(&self) -> [u8; 32] { @@ -230,6 +302,18 @@ impl Felt { alloc::format!("{self:#x}") } + /// Helper to produce a hexadecimal formatted string of 66 chars. + /// Equivalent to calling `format!("{self:#066x}")`. + #[cfg(feature = "alloc")] + pub fn to_fixed_hex_string(&self) -> alloc::string::String { + let hex_str = alloc::format!("{self:#x}"); + if hex_str.len() < 66 { + alloc::format!("0x{:0>64}", hex_str.strip_prefix("0x").unwrap()) + } else { + hex_str + } + } + /// Converts to little-endian bit representation. /// This is as performant as [to_bits_be](Felt::to_bits_be) #[cfg(target_pointer_width = "64")] @@ -367,6 +451,12 @@ impl Felt { } } + pub fn to_raw_reversed(&self) -> [u64; 4] { + let mut res = self.0.to_raw().limbs; + res.reverse(); + res + } + /// Convert `self`'s representative into an array of `u64` digits, /// least significant digits first. pub fn to_le_digits(&self) -> [u64; 4] { @@ -398,6 +488,10 @@ impl Felt { pub fn to_bigint(&self) -> BigInt { self.to_biguint().into() } + + pub fn prime() -> BigUint { + (*CAIRO_PRIME_BIGINT).to_biguint().unwrap() + } } #[cfg(feature = "arbitrary")] @@ -527,12 +621,29 @@ impl From<&BigInt> for Felt { } } +impl From for Felt { + fn from(bigint: BigInt) -> Felt { + let (sign, bytes) = bigint.mod_floor(&CAIRO_PRIME_BIGINT).to_bytes_le(); + let felt = Felt::from_bytes_le_slice(&bytes); + if sign == Sign::Minus { + felt.neg() + } else { + felt + } + } +} impl From<&BigUint> for Felt { fn from(biguint: &BigUint) -> Felt { Felt::from_bytes_le_slice(&biguint.to_bytes_le()) } } +impl From for Felt { + fn from(biguint: BigUint) -> Felt { + Felt::from_bytes_le_slice(&biguint.to_bytes_le()) + } +} + macro_rules! impl_from { ($from:ty, $with:ty) => { impl From<$from> for Felt { @@ -883,7 +994,7 @@ mod serde_impl { impl<'de> de::Visitor<'de> for FeltVisitor { type Value = Felt; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("Failed to deserialize hexadecimal string") } diff --git a/crates/starknet-types-core/src/felt/papyrus_encoding.rs b/crates/starknet-types-core/src/felt/papyrus_encoding.rs new file mode 100644 index 0000000..3b14ed6 --- /dev/null +++ b/crates/starknet-types-core/src/felt/papyrus_encoding.rs @@ -0,0 +1,71 @@ +use std::io::Error; + +use super::Felt; +/// Storage efficient serialization for field elements. +impl Felt { + // Felt encoding constants. + const CHOOSER_FULL: u8 = 15; + const CHOOSER_HALF: u8 = 14; + pub fn serialize(&self, res: &mut impl std::io::Write) -> Result<(), Error> { + let self_as_bytes = self.to_bytes_be(); + // We use the fact that bytes[0] < 0x10 and encode the size of the felt in the 4 most + // significant bits of the serialization, which we call `chooser`. We assume that 128 bit + // felts are prevalent (because of how uint256 is encoded in felts). + + // The first i for which nibbles 2i+1, 2i+2 are nonzero. Note that the first nibble is + // always 0. + let mut first_index = 31; + // Find first non zero byte + if let Some((index, value)) = self_as_bytes + .iter() + .enumerate() + .find(|(_index, value)| value != &&0u8) + { + if value < &16 { + // Can encode the chooser and the value on a single byte. + first_index = index; + } else { + // The chooser is encoded with the first nibble of the value. + first_index = index - 1; + } + }; + let chooser = if first_index < 15 { + // For 34 up to 63 nibble felts: chooser == 15, serialize using 32 bytes. + first_index = 0; + Felt::CHOOSER_FULL + } else if first_index < 18 { + // For 28 up to 33 nibble felts: chooser == 14, serialize using 17 bytes. + first_index = 15; + Felt::CHOOSER_HALF + } else { + // For up to 27 nibble felts: serialize the lower 1 + (chooser * 2) nibbles of the felt + // using chooser + 1 bytes. + (31 - first_index) as u8 + }; + res.write_all(&[(chooser << 4) | self_as_bytes[first_index]])?; + res.write_all(&self_as_bytes[first_index + 1..])?; + Ok(()) + } + + /// Storage efficient deserialization for field elements. + pub fn deserialize(bytes: &mut impl std::io::Read) -> Option { + let mut res = [0u8; 32]; + + bytes.read_exact(&mut res[..1]).ok()?; + let first = res[0]; + let chooser: u8 = first >> 4; + let first = first & 0x0f; + + let first_index = if chooser == Felt::CHOOSER_FULL { + 0 + } else if chooser == Felt::CHOOSER_HALF { + 15 + } else { + (31 - chooser) as usize + }; + res[0] = 0; + res[first_index] = first; + bytes.read_exact(&mut res[first_index + 1..]).ok()?; + Some(Self::from_bytes_be(&res)) + } +}