From c569f44995a7e170b395799622631a9b19a7242f Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Mon, 11 Aug 2025 18:46:33 -0300 Subject: [PATCH 01/44] Add QM31Felt --- crates/starknet-types-core/Cargo.toml | 2 +- crates/starknet-types-core/src/felt/mod.rs | 1 + crates/starknet-types-core/src/felt/qm31.rs | 509 ++++++++++++++++++++ 3 files changed, 511 insertions(+), 1 deletion(-) create mode 100644 crates/starknet-types-core/src/felt/qm31.rs diff --git a/crates/starknet-types-core/Cargo.toml b/crates/starknet-types-core/Cargo.toml index f042b70..d19bbcd 100644 --- a/crates/starknet-types-core/Cargo.toml +++ b/crates/starknet-types-core/Cargo.toml @@ -60,7 +60,7 @@ regex = "1.11" serde_test = "1" criterion = "0.5" rand_chacha = "0.3" -rand = "0.8" +rand = { version = "0.8", features = ["small_rng"]} rstest = "0.24" lazy_static = { version = "1.5", default-features = false } diff --git a/crates/starknet-types-core/src/felt/mod.rs b/crates/starknet-types-core/src/felt/mod.rs index bdac536..2ffe80c 100644 --- a/crates/starknet-types-core/src/felt/mod.rs +++ b/crates/starknet-types-core/src/felt/mod.rs @@ -14,6 +14,7 @@ mod parity_scale_codec; #[cfg(feature = "prime-bigint")] mod prime_bigint; mod primitive_conversions; +mod qm31; #[cfg(feature = "serde")] mod serde; #[cfg(feature = "zeroize")] diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs new file mode 100644 index 0000000..ff3f278 --- /dev/null +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -0,0 +1,509 @@ +use core::fmt; + +use lambdaworks_math::field::{ + element::FieldElement, fields::fft_friendly::stark_252_prime_field::Stark252PrimeField, +}; + +use crate::felt::Felt; + +pub const STWO_PRIME: u64 = (1 << 31) - 1; +const STWO_PRIME_U128: u128 = STWO_PRIME as u128; +const MASK_36: u64 = (1 << 36) - 1; +const MASK_8: u64 = (1 << 8) - 1; + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct QM31Felt(pub(crate) FieldElement); + +impl QM31Felt { + /// QM31 utility function, used specifically for Stwo. + /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. + /// Reads four u64 coordinates from a single Felt. + /// STWO_PRIME fits in 36 bits, hence each coordinate can be represented by 36 bits and a QM31 + /// element can be stored in the first 144 bits of a Felt. + /// Returns an error if the input has over 144 bits or any coordinate is unreduced. + fn read_coordinates(&self) -> Result<[u64; 4], QM31Error> { + let felt: Felt = self.into(); + let limbs = felt.to_le_digits(); + if limbs[3] != 0 || limbs[2] >= 1 << 16 { + return Err(QM31Error::QM31UnreducedError(Box::new(felt))); + } + let coordinates = [ + (limbs[0] & MASK_36), + ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), + ((limbs[1] >> 8) & MASK_36), + ((limbs[1] >> 44) + (limbs[2] << 20)), + ]; + for x in coordinates.iter() { + if *x >= STWO_PRIME { + return Err(QM31Error::QM31UnreducedError(Box::new(felt))); + } + } + Ok(coordinates) + } + + /// Create a [QM31Felt] without checking it. If the coordinates cannot be + /// represented with 144 bits, this can lead to undefined behaviour and big + /// security issue. + /// You should always use the [TryFrom] implementation. + pub fn from_coordinates_unchecked(coordinates: [u64; 4]) -> QM31Felt { + let bytes_part1 = ((coordinates[0] % STWO_PRIME) as u128 + + (((coordinates[1] % STWO_PRIME) as u128) << 36)) + .to_le_bytes(); + let bytes_part2 = ((coordinates[2] % STWO_PRIME) as u128 + + (((coordinates[3] % STWO_PRIME) as u128) << 36)) + .to_le_bytes(); + let mut result_bytes = [0u8; 32]; + result_bytes[0..9].copy_from_slice(&bytes_part1[0..9]); + result_bytes[9..18].copy_from_slice(&bytes_part2[0..9]); + + let value = Felt::from_bytes_le(&result_bytes); + + Self(value.0) + } + + /// QM31 utility function, used specifically for Stwo. + /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. + /// Reduces four u64 coordinates and packs them into a single Felt. + /// STWO_PRIME fits in 36 bits, hence each coordinate can be represented by 36 bits and a QM31 + /// element can be stored in the first 144 bits of a Felt. + pub fn from_coordinates(coordinates: [u64; 4]) -> Result { + for x in coordinates.iter() { + if *x >= STWO_PRIME { + return Err(QM31Error::QM31InvalidCoordinates(Box::new(coordinates))); + } + } + + Ok(Self::from_coordinates_unchecked(coordinates)) + } + + /// QM31 utility function, used specifically for Stwo. + /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. + /// Computes the addition of two QM31 elements in reduced form. + /// Returns an error if either operand is not reduced. + pub fn add(&self, rhs: &QM31Felt) -> Result { + let coordinates1 = self.read_coordinates()?; + let coordinates2 = rhs.read_coordinates()?; + let result_unreduced_coordinates = [ + coordinates1[0] + coordinates2[0], + coordinates1[1] + coordinates2[1], + coordinates1[2] + coordinates2[2], + coordinates1[3] + coordinates2[3], + ]; + Self::from_coordinates(result_unreduced_coordinates) + } + + /// QM31 utility function, used specifically for Stwo. + /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. + /// Computes the negative of a QM31 element in reduced form. + /// Returns an error if the input is not reduced. + pub fn neg(&self) -> Result { + let coordinates = self.read_coordinates()?; + Self::from_coordinates([ + STWO_PRIME - coordinates[0], + STWO_PRIME - coordinates[1], + STWO_PRIME - coordinates[2], + STWO_PRIME - coordinates[3], + ]) + } + + /// QM31 utility function, used specifically for Stwo. + /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. + /// Computes the subtraction of two QM31 elements in reduced form. + /// Returns an error if either operand is not reduced. + pub fn sub(&self, rhs: &QM31Felt) -> Result { + let coordinates1 = self.read_coordinates()?; + let coordinates2 = rhs.read_coordinates()?; + let result_unreduced_coordinates = [ + STWO_PRIME + coordinates1[0] - coordinates2[0], + STWO_PRIME + coordinates1[1] - coordinates2[1], + STWO_PRIME + coordinates1[2] - coordinates2[2], + STWO_PRIME + coordinates1[3] - coordinates2[3], + ]; + Self::from_coordinates(result_unreduced_coordinates) + } + + /// QM31 utility function, used specifically for Stwo. + /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. + /// Computes the multiplication of two QM31 elements in reduced form. + /// Returns an error if either operand is not reduced. + pub fn mul(&self, rhs: &QM31Felt) -> Result { + let coordinates1_u64 = self.read_coordinates()?; + let coordinates2_u64 = rhs.read_coordinates()?; + let coordinates1 = coordinates1_u64.map(u128::from); + let coordinates2 = coordinates2_u64.map(u128::from); + + let result_coordinates = [ + ((5 * STWO_PRIME_U128 * STWO_PRIME_U128 + coordinates1[0] * coordinates2[0] + - coordinates1[1] * coordinates2[1] + + 2 * coordinates1[2] * coordinates2[2] + - 2 * coordinates1[3] * coordinates2[3] + - coordinates1[2] * coordinates2[3] + - coordinates1[3] * coordinates2[2]) + % STWO_PRIME_U128) as u64, + ((STWO_PRIME_U128 * STWO_PRIME_U128 + + coordinates1[0] * coordinates2[1] + + coordinates1[1] * coordinates2[0] + + 2 * (coordinates1[2] * coordinates2[3] + coordinates1[3] * coordinates2[2]) + + coordinates1[2] * coordinates2[2] + - coordinates1[3] * coordinates2[3]) + % STWO_PRIME_U128) as u64, + 2 * STWO_PRIME * STWO_PRIME + coordinates1_u64[0] * coordinates2_u64[2] + - coordinates1_u64[1] * coordinates2_u64[3] + + coordinates1_u64[2] * coordinates2_u64[0] + - coordinates1_u64[3] * coordinates2_u64[1], + coordinates1_u64[0] * coordinates2_u64[3] + + coordinates1_u64[1] * coordinates2_u64[2] + + coordinates1_u64[2] * coordinates2_u64[1] + + coordinates1_u64[3] * coordinates2_u64[0], + ]; + Self::from_coordinates(result_coordinates) + } + + /// M31 utility function, used specifically for Stwo. + /// M31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. + /// Computes the inverse in the M31 field using Fermat's little theorem, i.e., returns + /// `v^(STWO_PRIME-2) modulo STWO_PRIME`, which is the inverse of v unless v % STWO_PRIME == 0. + pub fn pow2147483645(v: u64) -> u64 { + let t0 = (Self::sqn(v, 2) * v) % STWO_PRIME; + let t1 = (Self::sqn(t0, 1) * t0) % STWO_PRIME; + let t2 = (Self::sqn(t1, 3) * t0) % STWO_PRIME; + let t3 = (Self::sqn(t2, 1) * t0) % STWO_PRIME; + let t4 = (Self::sqn(t3, 8) * t3) % STWO_PRIME; + let t5 = (Self::sqn(t4, 8) * t3) % STWO_PRIME; + (Self::sqn(t5, 7) * t2) % STWO_PRIME + } + + /// M31 utility function, used specifically for Stwo. + /// M31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. + /// Computes `v^(2^n) modulo STWO_PRIME`. + fn sqn(v: u64, n: usize) -> u64 { + let mut u = v; + for _ in 0..n { + u = (u * u) % STWO_PRIME; + } + u + } + + /// QM31 utility function, used specifically for Stwo. + /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. + /// Computes the inverse of a QM31 element in reduced form. + /// Returns an error if the denominator is zero or either operand is not reduced. + pub fn inverse(&self) -> Result { + let coordinates = self.read_coordinates()?; + + let b2_r = (coordinates[2] * coordinates[2] + STWO_PRIME * STWO_PRIME + - coordinates[3] * coordinates[3]) + % STWO_PRIME; + let b2_i = (2 * coordinates[2] * coordinates[3]) % STWO_PRIME; + + let denom_r = (coordinates[0] * coordinates[0] + STWO_PRIME * STWO_PRIME + - coordinates[1] * coordinates[1] + + 2 * STWO_PRIME + - 2 * b2_r + + b2_i) + % STWO_PRIME; + let denom_i = + (2 * coordinates[0] * coordinates[1] + 3 * STWO_PRIME - 2 * b2_i - b2_r) % STWO_PRIME; + + let denom_norm_squared = (denom_r * denom_r + denom_i * denom_i) % STWO_PRIME; + let denom_norm_inverse_squared = Self::pow2147483645(denom_norm_squared); + + let denom_inverse_r = (denom_r * denom_norm_inverse_squared) % STWO_PRIME; + let denom_inverse_i = ((STWO_PRIME - denom_i) * denom_norm_inverse_squared) % STWO_PRIME; + + Self::from_coordinates([ + coordinates[0] * denom_inverse_r + STWO_PRIME * STWO_PRIME + - coordinates[1] * denom_inverse_i, + coordinates[0] * denom_inverse_i + coordinates[1] * denom_inverse_r, + coordinates[3] * denom_inverse_i + STWO_PRIME * STWO_PRIME + - coordinates[2] * denom_inverse_r, + 2 * STWO_PRIME * STWO_PRIME + - coordinates[2] * denom_inverse_i + - coordinates[3] * denom_inverse_r, + ]) + } + + /// QM31 utility function, used specifically for Stwo. + /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. + /// Computes the division of two QM31 elements in reduced form. + /// Returns an error if the input is zero. + pub fn div(&self, rhs: &QM31Felt) -> Result { + let rhs_inv = rhs.inverse()?; + self.mul(&rhs_inv) + } +} + +#[derive(Debug)] +pub enum QM31Error { + QM31UnreducedError(Box), + QM31InvalidCoordinates(Box<[u64; 4]>), +} + +#[cfg(feature = "std")] +impl std::error::Error for QM31Error {} + +impl fmt::Display for QM31Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + QM31Error::QM31UnreducedError(felt) => writeln!( + f, + "Number is not a packing of a QM31 in reduced form: {}", + felt + ), + QM31Error::QM31InvalidCoordinates(coords) => writeln!( + f, + "Number is not a packing of a QM31 in reduced form: {:#?}", + coords + ), + } + } +} + +impl From<&QM31Felt> for Felt { + fn from(value: &QM31Felt) -> Self { + Felt(value.0) + } +} + +impl From for Felt { + fn from(value: QM31Felt) -> Self { + Felt(value.0) + } +} + +impl TryFrom for QM31Felt { + type Error = QM31Error; + fn try_from(value: Felt) -> Result { + let limbs = value.to_le_digits(); + if limbs[3] != 0 || limbs[2] >= 1 << 16 { + return Err(QM31Error::QM31UnreducedError(Box::new(value))); + } + let coordinates = [ + (limbs[0] & MASK_36), + ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), + ((limbs[1] >> 8) & MASK_36), + ((limbs[1] >> 44) + (limbs[2] << 20)), + ]; + Self::from_coordinates(coordinates) + } +} + +impl TryFrom<&Felt> for QM31Felt { + type Error = QM31Error; + fn try_from(value: &Felt) -> Result { + let limbs = value.to_le_digits(); + if limbs[3] != 0 || limbs[2] >= 1 << 16 { + return Err(QM31Error::QM31UnreducedError(Box::new(*value))); + } + let coordinates = [ + (limbs[0] & MASK_36), + ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), + ((limbs[1] >> 8) & MASK_36), + ((limbs[1] >> 44) + (limbs[2] << 20)), + ]; + Self::from_coordinates(coordinates) + } +} + +#[cfg(test)] +mod test { + use rand::{rngs::SmallRng, Rng, SeedableRng}; + + use crate::felt::{ + qm31::{QM31Error, QM31Felt, STWO_PRIME}, + Felt, + }; + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn qm31_packed_reduced_read_coordinates_over_144_bits() { + let mut felt_bytes = [0u8; 32]; + felt_bytes[18] = 1; + let felt = Felt::from_bytes_le(&felt_bytes); + let qm31: QM31Felt = felt.try_into().unwrap(); + assert!(matches!( + qm31.read_coordinates(), + Err(QM31Error::QM31UnreducedError(bx)) if *bx == felt + )); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn qm31_packed_reduced_read_coordinates_unreduced() { + let mut felt_bytes = [0u8; 32]; + felt_bytes[0] = 0xff; + felt_bytes[1] = 0xff; + felt_bytes[2] = 0xff; + felt_bytes[3] = (1 << 7) - 1; + let felt = Felt::from_bytes_le(&felt_bytes); + let qm31: QM31Felt = felt.try_into().unwrap(); + assert!(matches!( + qm31.read_coordinates(), + Err(QM31Error::QM31UnreducedError(bx)) if *bx == felt + )); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn test_qm31_packed_reduced_add() { + let x_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; + let y_coordinates = [1234567890, 1414213562, 1732050807, 1618033988]; + let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); + let y = QM31Felt::from_coordinates(y_coordinates).unwrap(); + let res = x.add(&y).unwrap(); + let res_coordinates = res.read_coordinates().unwrap(); + assert_eq!( + res_coordinates, + [ + (1414213562 + 1234567890) % STWO_PRIME, + (1732050807 + 1414213562) % STWO_PRIME, + (1618033988 + 1732050807) % STWO_PRIME, + (1234567890 + 1618033988) % STWO_PRIME, + ] + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn test_qm31_packed_reduced_neg() { + let x_coordinates = [1749652895, 834624081, 1930174752, 2063872165]; + let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); + let res = x.neg().unwrap(); + let res_coordinates = res.read_coordinates().unwrap(); + assert_eq!( + res_coordinates, + [ + STWO_PRIME - x_coordinates[0], + STWO_PRIME - x_coordinates[1], + STWO_PRIME - x_coordinates[2], + STWO_PRIME - x_coordinates[3] + ] + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn test_qm31_packed_reduced_sub() { + let x_coordinates = [ + (1414213562 + 1234567890) % STWO_PRIME, + (1732050807 + 1414213562) % STWO_PRIME, + (1618033988 + 1732050807) % STWO_PRIME, + (1234567890 + 1618033988) % STWO_PRIME, + ]; + let y_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; + let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); + let y = QM31Felt::from_coordinates(y_coordinates).unwrap(); + let res = x.sub(&y).unwrap(); + let res_coordinates = res.read_coordinates().unwrap(); + assert_eq!( + res_coordinates, + [1234567890, 1414213562, 1732050807, 1618033988] + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn test_qm31_packed_reduced_mul() { + let x_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; + let y_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; + let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); + let y = QM31Felt::from_coordinates(y_coordinates).unwrap(); + let res = x.mul(&y).unwrap(); + let res_coordinates = res.read_coordinates().unwrap(); + assert_eq!( + res_coordinates, + [947980980, 1510986506, 623360030, 1260310989] + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn test_qm31_packed_reduced_inv() { + let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; + let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); + let res = x.inverse().unwrap(); + let expected = Felt::from(1).try_into().unwrap(); + assert_eq!(x.mul(&res).unwrap(), expected); + + let x_coordinates = [1, 2, 3, 4]; + let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); + let res = x.inverse().unwrap(); + let expected = Felt::from(1).try_into().unwrap(); + assert_eq!(x.mul(&res).unwrap(), expected); + + let x_coordinates = [1749652895, 834624081, 1930174752, 2063872165]; + let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); + let res = x.inverse().unwrap(); + let expected = Felt::from(1).try_into().unwrap(); + assert_eq!(x.mul(&res).unwrap(), expected); + } + + // TODO: Refactor using proptest and separating particular cases + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn test_qm31_packed_reduced_inv_extensive() { + let mut rng = SmallRng::seed_from_u64(11480028852697973135); + #[derive(Clone, Copy)] + enum Configuration { + Zero, + One, + MinusOne, + Random, + } + let configurations = [ + Configuration::Zero, + Configuration::One, + Configuration::MinusOne, + Configuration::Random, + ]; + let mut cartesian_product = vec![]; + for &a in &configurations { + for &b in &configurations { + for &c in &configurations { + for &d in &configurations { + cartesian_product.push([a, b, c, d]); + } + } + } + } + + for test_case in cartesian_product { + let x_coordinates: [u64; 4] = test_case + .iter() + .map(|&x| match x { + Configuration::Zero => 0, + Configuration::One => 1, + Configuration::MinusOne => STWO_PRIME - 1, + Configuration::Random => rng.gen_range(0..STWO_PRIME), + }) + .collect::>() + .try_into() + .unwrap(); + if x_coordinates == [0, 0, 0, 0] { + continue; + } + let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); + let res = x.inverse().unwrap(); + let expected = Felt::from(1).try_into().unwrap(); + assert_eq!(x.mul(&res).unwrap(), expected); + } + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn test_qm31_packed_reduced_div() { + let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; + let y_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; + let xy_coordinates = [947980980, 1510986506, 623360030, 1260310989]; + let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); + let y = QM31Felt::from_coordinates(y_coordinates).unwrap(); + let xy = QM31Felt::from_coordinates(xy_coordinates).unwrap(); + + let res = xy.div(&y).unwrap(); + assert_eq!(res, x); + + let res = xy.div(&x).unwrap(); + assert_eq!(res, y); + } +} From 529979c592c4c15ce2782ffb8b0e77d19df58f98 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Tue, 12 Aug 2025 10:45:28 -0300 Subject: [PATCH 02/44] fix tests and improve doc comments --- crates/starknet-types-core/src/felt/qm31.rs | 225 ++++++++------------ 1 file changed, 93 insertions(+), 132 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index ff3f278..d8b0644 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -16,34 +16,28 @@ const MASK_8: u64 = (1 << 8) - 1; pub struct QM31Felt(pub(crate) FieldElement); impl QM31Felt { - /// QM31 utility function, used specifically for Stwo. - /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. - /// Reads four u64 coordinates from a single Felt. - /// STWO_PRIME fits in 36 bits, hence each coordinate can be represented by 36 bits and a QM31 - /// element can be stored in the first 144 bits of a Felt. - /// Returns an error if the input has over 144 bits or any coordinate is unreduced. - fn read_coordinates(&self) -> Result<[u64; 4], QM31Error> { - let felt: Felt = self.into(); - let limbs = felt.to_le_digits(); - if limbs[3] != 0 || limbs[2] >= 1 << 16 { - return Err(QM31Error::QM31UnreducedError(Box::new(felt))); - } + /// Reads four u64 coordinates from a single Felt. STWO_PRIME fits + /// in 36 bits, hence each coordinate can be represented by 36 bits and a QM31 element can be stored in the first + /// 144 bits of a Felt. Returns an error if the input has over 144 bits or any coordinate is unreduced. + /// + /// # Safety + /// This function reads from an already created QM31. If there were an error, it would've been caught during + /// its creation. + fn read_coordinates(&self) -> [u64; 4] { + let limbs = self.to_le_digits(); + let coordinates = [ (limbs[0] & MASK_36), ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), ((limbs[1] >> 8) & MASK_36), ((limbs[1] >> 44) + (limbs[2] << 20)), ]; - for x in coordinates.iter() { - if *x >= STWO_PRIME { - return Err(QM31Error::QM31UnreducedError(Box::new(felt))); - } - } - Ok(coordinates) + + coordinates } /// Create a [QM31Felt] without checking it. If the coordinates cannot be - /// represented with 144 bits, this can lead to undefined behaviour and big + /// represented within 144 bits, this can lead to undefined behaviour and big /// security issue. /// You should always use the [TryFrom] implementation. pub fn from_coordinates_unchecked(coordinates: [u64; 4]) -> QM31Felt { @@ -62,28 +56,33 @@ impl QM31Felt { Self(value.0) } - /// QM31 utility function, used specifically for Stwo. - /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. /// Reduces four u64 coordinates and packs them into a single Felt. - /// STWO_PRIME fits in 36 bits, hence each coordinate can be represented by 36 bits and a QM31 - /// element can be stored in the first 144 bits of a Felt. + /// STWO_PRIME fits in 36 bits, hence each coordinate can be represented + /// by 36 bits and a QM31 element can be stored in the first 144 bits of a Felt. pub fn from_coordinates(coordinates: [u64; 4]) -> Result { + let qm31 = Self::from_coordinates_unchecked(coordinates); + let limbs = qm31.to_le_digits(); + let coordinates = [ + (limbs[0] & MASK_36), + ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), + ((limbs[1] >> 8) & MASK_36), + ((limbs[1] >> 44) + (limbs[2] << 20)), + ]; + for x in coordinates.iter() { if *x >= STWO_PRIME { - return Err(QM31Error::QM31InvalidCoordinates(Box::new(coordinates))); + return Err(QM31Error::QM31UnreducedError(Box::new(qm31.into()))); } } - Ok(Self::from_coordinates_unchecked(coordinates)) + Ok(qm31) } - /// QM31 utility function, used specifically for Stwo. - /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. /// Computes the addition of two QM31 elements in reduced form. /// Returns an error if either operand is not reduced. pub fn add(&self, rhs: &QM31Felt) -> Result { - let coordinates1 = self.read_coordinates()?; - let coordinates2 = rhs.read_coordinates()?; + let coordinates1 = self.read_coordinates(); + let coordinates2 = rhs.read_coordinates(); let result_unreduced_coordinates = [ coordinates1[0] + coordinates2[0], coordinates1[1] + coordinates2[1], @@ -93,12 +92,10 @@ impl QM31Felt { Self::from_coordinates(result_unreduced_coordinates) } - /// QM31 utility function, used specifically for Stwo. - /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. /// Computes the negative of a QM31 element in reduced form. /// Returns an error if the input is not reduced. pub fn neg(&self) -> Result { - let coordinates = self.read_coordinates()?; + let coordinates = self.read_coordinates(); Self::from_coordinates([ STWO_PRIME - coordinates[0], STWO_PRIME - coordinates[1], @@ -107,13 +104,11 @@ impl QM31Felt { ]) } - /// QM31 utility function, used specifically for Stwo. - /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. /// Computes the subtraction of two QM31 elements in reduced form. /// Returns an error if either operand is not reduced. pub fn sub(&self, rhs: &QM31Felt) -> Result { - let coordinates1 = self.read_coordinates()?; - let coordinates2 = rhs.read_coordinates()?; + let coordinates1 = self.read_coordinates(); + let coordinates2 = rhs.read_coordinates(); let result_unreduced_coordinates = [ STWO_PRIME + coordinates1[0] - coordinates2[0], STWO_PRIME + coordinates1[1] - coordinates2[1], @@ -123,13 +118,11 @@ impl QM31Felt { Self::from_coordinates(result_unreduced_coordinates) } - /// QM31 utility function, used specifically for Stwo. - /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. /// Computes the multiplication of two QM31 elements in reduced form. /// Returns an error if either operand is not reduced. pub fn mul(&self, rhs: &QM31Felt) -> Result { - let coordinates1_u64 = self.read_coordinates()?; - let coordinates2_u64 = rhs.read_coordinates()?; + let coordinates1_u64 = self.read_coordinates(); + let coordinates2_u64 = rhs.read_coordinates(); let coordinates1 = coordinates1_u64.map(u128::from); let coordinates2 = coordinates2_u64.map(u128::from); @@ -160,11 +153,9 @@ impl QM31Felt { Self::from_coordinates(result_coordinates) } - /// M31 utility function, used specifically for Stwo. - /// M31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. /// Computes the inverse in the M31 field using Fermat's little theorem, i.e., returns /// `v^(STWO_PRIME-2) modulo STWO_PRIME`, which is the inverse of v unless v % STWO_PRIME == 0. - pub fn pow2147483645(v: u64) -> u64 { + fn pow2147483645(v: u64) -> u64 { let t0 = (Self::sqn(v, 2) * v) % STWO_PRIME; let t1 = (Self::sqn(t0, 1) * t0) % STWO_PRIME; let t2 = (Self::sqn(t1, 3) * t0) % STWO_PRIME; @@ -174,8 +165,6 @@ impl QM31Felt { (Self::sqn(t5, 7) * t2) % STWO_PRIME } - /// M31 utility function, used specifically for Stwo. - /// M31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. /// Computes `v^(2^n) modulo STWO_PRIME`. fn sqn(v: u64, n: usize) -> u64 { let mut u = v; @@ -185,12 +174,10 @@ impl QM31Felt { u } - /// QM31 utility function, used specifically for Stwo. - /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. /// Computes the inverse of a QM31 element in reduced form. /// Returns an error if the denominator is zero or either operand is not reduced. pub fn inverse(&self) -> Result { - let coordinates = self.read_coordinates()?; + let coordinates = self.read_coordinates(); let b2_r = (coordinates[2] * coordinates[2] + STWO_PRIME * STWO_PRIME - coordinates[3] * coordinates[3]) @@ -224,14 +211,26 @@ impl QM31Felt { ]) } - /// QM31 utility function, used specifically for Stwo. - /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. /// Computes the division of two QM31 elements in reduced form. /// Returns an error if the input is zero. pub fn div(&self, rhs: &QM31Felt) -> Result { let rhs_inv = rhs.inverse()?; self.mul(&rhs_inv) } + + /// Convert `self`'s representative into an array of `u64` digits, + /// least significant digits first. + pub fn to_le_digits(&self) -> [u64; 4] { + let mut limbs = self.0.representative().limbs; + limbs.reverse(); + limbs + } + + /// Convert `self`'s representative into an array of `u64` digits, + /// most significant digits first. + pub fn to_be_digits(&self) -> [u64; 4] { + self.0.representative().limbs + } } #[derive(Debug)] @@ -253,7 +252,7 @@ impl fmt::Display for QM31Error { ), QM31Error::QM31InvalidCoordinates(coords) => writeln!( f, - "Number is not a packing of a QM31 in reduced form: {:#?}", + "The given coordinates cannot be packed into a QM31: {:#?}", coords ), } @@ -274,41 +273,29 @@ impl From for Felt { impl TryFrom for QM31Felt { type Error = QM31Error; + fn try_from(value: Felt) -> Result { let limbs = value.to_le_digits(); - if limbs[3] != 0 || limbs[2] >= 1 << 16 { - return Err(QM31Error::QM31UnreducedError(Box::new(value))); - } - let coordinates = [ - (limbs[0] & MASK_36), - ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), - ((limbs[1] >> 8) & MASK_36), - ((limbs[1] >> 44) + (limbs[2] << 20)), - ]; - Self::from_coordinates(coordinates) + Self::from_coordinates(limbs) } } impl TryFrom<&Felt> for QM31Felt { type Error = QM31Error; + fn try_from(value: &Felt) -> Result { let limbs = value.to_le_digits(); - if limbs[3] != 0 || limbs[2] >= 1 << 16 { - return Err(QM31Error::QM31UnreducedError(Box::new(*value))); - } - let coordinates = [ - (limbs[0] & MASK_36), - ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), - ((limbs[1] >> 8) & MASK_36), - ((limbs[1] >> 44) + (limbs[2] << 20)), - ]; - Self::from_coordinates(coordinates) + Self::from_coordinates(limbs) } } #[cfg(test)] mod test { - use rand::{rngs::SmallRng, Rng, SeedableRng}; + use proptest::{ + array::uniform4, + prelude::{BoxedStrategy, Just, Strategy}, + prop_oneof, proptest, + }; use crate::felt::{ qm31::{QM31Error, QM31Felt, STWO_PRIME}, @@ -316,20 +303,18 @@ mod test { }; #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn qm31_packed_reduced_read_coordinates_over_144_bits() { + fn qm31_packed_reduced_coordinates_over_144_bits() { let mut felt_bytes = [0u8; 32]; felt_bytes[18] = 1; let felt = Felt::from_bytes_le(&felt_bytes); - let qm31: QM31Felt = felt.try_into().unwrap(); + let qm31: Result = felt.try_into(); assert!(matches!( - qm31.read_coordinates(), + qm31, Err(QM31Error::QM31UnreducedError(bx)) if *bx == felt )); } #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn qm31_packed_reduced_read_coordinates_unreduced() { let mut felt_bytes = [0u8; 32]; felt_bytes[0] = 0xff; @@ -337,22 +322,21 @@ mod test { felt_bytes[2] = 0xff; felt_bytes[3] = (1 << 7) - 1; let felt = Felt::from_bytes_le(&felt_bytes); - let qm31: QM31Felt = felt.try_into().unwrap(); + let qm31: Result = felt.try_into(); assert!(matches!( - qm31.read_coordinates(), + qm31, Err(QM31Error::QM31UnreducedError(bx)) if *bx == felt )); } #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_qm31_packed_reduced_add() { let x_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; let y_coordinates = [1234567890, 1414213562, 1732050807, 1618033988]; let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); let y = QM31Felt::from_coordinates(y_coordinates).unwrap(); let res = x.add(&y).unwrap(); - let res_coordinates = res.read_coordinates().unwrap(); + let res_coordinates = res.read_coordinates(); assert_eq!( res_coordinates, [ @@ -365,12 +349,11 @@ mod test { } #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_qm31_packed_reduced_neg() { let x_coordinates = [1749652895, 834624081, 1930174752, 2063872165]; let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); let res = x.neg().unwrap(); - let res_coordinates = res.read_coordinates().unwrap(); + let res_coordinates = res.read_coordinates(); assert_eq!( res_coordinates, [ @@ -383,7 +366,6 @@ mod test { } #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_qm31_packed_reduced_sub() { let x_coordinates = [ (1414213562 + 1234567890) % STWO_PRIME, @@ -395,7 +377,7 @@ mod test { let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); let y = QM31Felt::from_coordinates(y_coordinates).unwrap(); let res = x.sub(&y).unwrap(); - let res_coordinates = res.read_coordinates().unwrap(); + let res_coordinates = res.read_coordinates(); assert_eq!( res_coordinates, [1234567890, 1414213562, 1732050807, 1618033988] @@ -403,14 +385,13 @@ mod test { } #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_qm31_packed_reduced_mul() { let x_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; let y_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); let y = QM31Felt::from_coordinates(y_coordinates).unwrap(); let res = x.mul(&y).unwrap(); - let res_coordinates = res.read_coordinates().unwrap(); + let res_coordinates = res.read_coordinates(); assert_eq!( res_coordinates, [947980980, 1510986506, 623360030, 1260310989] @@ -418,7 +399,6 @@ mod test { } #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_qm31_packed_reduced_inv() { let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); @@ -439,59 +419,40 @@ mod test { assert_eq!(x.mul(&res).unwrap(), expected); } - // TODO: Refactor using proptest and separating particular cases - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn test_qm31_packed_reduced_inv_extensive() { - let mut rng = SmallRng::seed_from_u64(11480028852697973135); - #[derive(Clone, Copy)] - enum Configuration { - Zero, - One, - MinusOne, - Random, - } - let configurations = [ - Configuration::Zero, - Configuration::One, - Configuration::MinusOne, - Configuration::Random, - ]; - let mut cartesian_product = vec![]; - for &a in &configurations { - for &b in &configurations { - for &c in &configurations { - for &d in &configurations { - cartesian_product.push([a, b, c, d]); - } - } - } + /// Necessary strat to use proptest on the QM31 test + fn configuration_strat() -> BoxedStrategy { + prop_oneof![Just(0), Just(1), Just(STWO_PRIME - 1), 0..STWO_PRIME].boxed() + } + + proptest! { + #[test] + fn qm31_packed_reduced_inv_random(x_coordinates in uniform4(0u64..STWO_PRIME) + .prop_filter("All configs cant be 0", + |arr| !arr.iter().all(|x| *x == 0)) + ) { + let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); + let res = x.inverse().unwrap(); + // Expect 1_felt252 + let expected = QM31Felt::from_coordinates([1,0,0,0]).unwrap(); + assert_eq!(x.mul(&res).unwrap(), expected); } - for test_case in cartesian_product { - let x_coordinates: [u64; 4] = test_case - .iter() - .map(|&x| match x { - Configuration::Zero => 0, - Configuration::One => 1, - Configuration::MinusOne => STWO_PRIME - 1, - Configuration::Random => rng.gen_range(0..STWO_PRIME), - }) - .collect::>() - .try_into() - .unwrap(); - if x_coordinates == [0, 0, 0, 0] { - continue; - } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn qm31_packed_reduced_inv_extensive(x_coordinates in uniform4(configuration_strat()) + .prop_filter("All configs cant be 0", + |arr| !arr.iter().all(|x| *x == 0)) + .no_shrink() + ) { let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); let res = x.inverse().unwrap(); - let expected = Felt::from(1).try_into().unwrap(); + // Expect 1_felt252 + let expected = QM31Felt::from_coordinates([1,0,0,0]).unwrap(); assert_eq!(x.mul(&res).unwrap(), expected); } } #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_qm31_packed_reduced_div() { let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; let y_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; From b1551ba670a873a26efaa2fdee4ea7f87ae55661 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Tue, 12 Aug 2025 10:54:03 -0300 Subject: [PATCH 03/44] format --- crates/starknet-types-core/src/felt/qm31.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index d8b0644..389145b 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -57,7 +57,7 @@ impl QM31Felt { } /// Reduces four u64 coordinates and packs them into a single Felt. - /// STWO_PRIME fits in 36 bits, hence each coordinate can be represented + /// STWO_PRIME fits in 36 bits, hence each coordinate can be represented /// by 36 bits and a QM31 element can be stored in the first 144 bits of a Felt. pub fn from_coordinates(coordinates: [u64; 4]) -> Result { let qm31 = Self::from_coordinates_unchecked(coordinates); From 31bd70167ed9bba77294f9282242e609162469b0 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Tue, 12 Aug 2025 11:05:16 -0300 Subject: [PATCH 04/44] add coords checking when converting from a felt --- crates/starknet-types-core/src/felt/qm31.rs | 26 +++++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index 389145b..dd40f04 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -71,7 +71,7 @@ impl QM31Felt { for x in coordinates.iter() { if *x >= STWO_PRIME { - return Err(QM31Error::QM31UnreducedError(Box::new(qm31.into()))); + return Err(QM31Error::QM31UnreducedError(qm31.into())); } } @@ -235,8 +235,8 @@ impl QM31Felt { #[derive(Debug)] pub enum QM31Error { - QM31UnreducedError(Box), - QM31InvalidCoordinates(Box<[u64; 4]>), + QM31UnreducedError(Felt), + QM31InvalidCoordinates([u64; 4]), } #[cfg(feature = "std")] @@ -276,6 +276,14 @@ impl TryFrom for QM31Felt { fn try_from(value: Felt) -> Result { let limbs = value.to_le_digits(); + + // Check value fits in 144 bits. This check is only done here + // because we are trying to convert a Felt into a QM31Felt. This + // Felt should represent a packed QM31 which is at most 144 bits long. + if limbs[3] != 0 || limbs[2] >= 1 << 16 { + return Err(QM31Error::QM31InvalidCoordinates(limbs)); + } + Self::from_coordinates(limbs) } } @@ -285,6 +293,14 @@ impl TryFrom<&Felt> for QM31Felt { fn try_from(value: &Felt) -> Result { let limbs = value.to_le_digits(); + + // Check value fits in 144 bits. This check is only done here + // because we are trying to convert a Felt into a QM31Felt. This + // Felt should represent a packed QM31 which is at most 144 bits long. + if limbs[3] != 0 || limbs[2] >= 1 << 16 { + return Err(QM31Error::QM31InvalidCoordinates(limbs)); + } + Self::from_coordinates(limbs) } } @@ -310,7 +326,7 @@ mod test { let qm31: Result = felt.try_into(); assert!(matches!( qm31, - Err(QM31Error::QM31UnreducedError(bx)) if *bx == felt + Err(QM31Error::QM31UnreducedError(bx)) if bx == felt )); } @@ -325,7 +341,7 @@ mod test { let qm31: Result = felt.try_into(); assert!(matches!( qm31, - Err(QM31Error::QM31UnreducedError(bx)) if *bx == felt + Err(QM31Error::QM31UnreducedError(bx)) if bx == felt )); } From 725d4d0045494ec74960a32ba17c60677c632428 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Tue, 12 Aug 2025 12:52:34 -0300 Subject: [PATCH 05/44] fix clippy --- crates/starknet-types-core/Cargo.toml | 2 +- crates/starknet-types-core/src/felt/qm31.rs | 68 ++++++++++----------- 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/crates/starknet-types-core/Cargo.toml b/crates/starknet-types-core/Cargo.toml index d19bbcd..f042b70 100644 --- a/crates/starknet-types-core/Cargo.toml +++ b/crates/starknet-types-core/Cargo.toml @@ -60,7 +60,7 @@ regex = "1.11" serde_test = "1" criterion = "0.5" rand_chacha = "0.3" -rand = { version = "0.8", features = ["small_rng"]} +rand = "0.8" rstest = "0.24" lazy_static = { version = "1.5", default-features = false } diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index dd40f04..3fe60c2 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -1,16 +1,40 @@ use core::fmt; +use crate::felt::Felt; use lambdaworks_math::field::{ element::FieldElement, fields::fft_friendly::stark_252_prime_field::Stark252PrimeField, }; -use crate::felt::Felt; - pub const STWO_PRIME: u64 = (1 << 31) - 1; const STWO_PRIME_U128: u128 = STWO_PRIME as u128; const MASK_36: u64 = (1 << 36) - 1; const MASK_8: u64 = (1 << 8) - 1; +#[derive(Debug)] +pub enum QM31Error { + QM31UnreducedError(Felt), + QM31InvalidCoordinates([u64; 4]), +} + +#[cfg(feature = "std")] +impl std::error::Error for QM31Error {} + +impl fmt::Display for QM31Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + QM31Error::QM31UnreducedError(felt) => writeln!( + f, + "Number is not a packing of a QM31 in reduced form: {}", + felt + ), + QM31Error::QM31InvalidCoordinates(coords) => writeln!( + f, + "The given coordinates cannot be packed into a QM31: {:#?}", + coords + ), + } + } +} #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct QM31Felt(pub(crate) FieldElement); @@ -24,16 +48,14 @@ impl QM31Felt { /// This function reads from an already created QM31. If there were an error, it would've been caught during /// its creation. fn read_coordinates(&self) -> [u64; 4] { - let limbs = self.to_le_digits(); + let limbs = self.as_le_digits(); - let coordinates = [ + [ (limbs[0] & MASK_36), ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), ((limbs[1] >> 8) & MASK_36), ((limbs[1] >> 44) + (limbs[2] << 20)), - ]; - - coordinates + ] } /// Create a [QM31Felt] without checking it. If the coordinates cannot be @@ -61,7 +83,7 @@ impl QM31Felt { /// by 36 bits and a QM31 element can be stored in the first 144 bits of a Felt. pub fn from_coordinates(coordinates: [u64; 4]) -> Result { let qm31 = Self::from_coordinates_unchecked(coordinates); - let limbs = qm31.to_le_digits(); + let limbs = qm31.as_le_digits(); let coordinates = [ (limbs[0] & MASK_36), ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), @@ -220,7 +242,7 @@ impl QM31Felt { /// Convert `self`'s representative into an array of `u64` digits, /// least significant digits first. - pub fn to_le_digits(&self) -> [u64; 4] { + pub fn as_le_digits(&self) -> [u64; 4] { let mut limbs = self.0.representative().limbs; limbs.reverse(); limbs @@ -228,37 +250,11 @@ impl QM31Felt { /// Convert `self`'s representative into an array of `u64` digits, /// most significant digits first. - pub fn to_be_digits(&self) -> [u64; 4] { + pub fn as_be_digits(&self) -> [u64; 4] { self.0.representative().limbs } } -#[derive(Debug)] -pub enum QM31Error { - QM31UnreducedError(Felt), - QM31InvalidCoordinates([u64; 4]), -} - -#[cfg(feature = "std")] -impl std::error::Error for QM31Error {} - -impl fmt::Display for QM31Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - QM31Error::QM31UnreducedError(felt) => writeln!( - f, - "Number is not a packing of a QM31 in reduced form: {}", - felt - ), - QM31Error::QM31InvalidCoordinates(coords) => writeln!( - f, - "The given coordinates cannot be packed into a QM31: {:#?}", - coords - ), - } - } -} - impl From<&QM31Felt> for Felt { fn from(value: &QM31Felt) -> Self { Felt(value.0) From 4f615219320a5a99768255d4a7afd1320c233119 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Tue, 12 Aug 2025 13:02:00 -0300 Subject: [PATCH 06/44] add proper spacing --- crates/starknet-types-core/src/felt/qm31.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index 3fe60c2..fe1f8ce 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -35,6 +35,7 @@ impl fmt::Display for QM31Error { } } } + #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct QM31Felt(pub(crate) FieldElement); @@ -250,7 +251,7 @@ impl QM31Felt { /// Convert `self`'s representative into an array of `u64` digits, /// most significant digits first. - pub fn as_be_digits(&self) -> [u64; 4] { + pub fn to_be_digits(&self) -> [u64; 4] { self.0.representative().limbs } } From 97e1a99e870f38a47f4005cdfc4f7f1093a1e68b Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Tue, 12 Aug 2025 13:03:37 -0300 Subject: [PATCH 07/44] make read_coordinates pub --- crates/starknet-types-core/src/felt/qm31.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index fe1f8ce..0b2a5ef 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -48,7 +48,7 @@ impl QM31Felt { /// # Safety /// This function reads from an already created QM31. If there were an error, it would've been caught during /// its creation. - fn read_coordinates(&self) -> [u64; 4] { + pub fn read_coordinates(&self) -> [u64; 4] { let limbs = self.as_le_digits(); [ From a1b9664d38fb71c4efdfce35955e2831b96f4c91 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Tue, 12 Aug 2025 16:00:14 -0300 Subject: [PATCH 08/44] fix clippy --- crates/starknet-types-core/src/felt/qm31.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index 0b2a5ef..91d30da 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -251,7 +251,7 @@ impl QM31Felt { /// Convert `self`'s representative into an array of `u64` digits, /// most significant digits first. - pub fn to_be_digits(&self) -> [u64; 4] { + pub fn as_be_digits(&self) -> [u64; 4] { self.0.representative().limbs } } From 1435e7d55ac702294d129d480c3b58446c2281ae Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Wed, 13 Aug 2025 10:54:42 -0300 Subject: [PATCH 09/44] api restructure --- crates/starknet-types-core/src/felt/qm31.rs | 202 ++++++++------------ 1 file changed, 83 insertions(+), 119 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index 91d30da..888a16f 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -12,8 +12,7 @@ const MASK_8: u64 = (1 << 8) - 1; #[derive(Debug)] pub enum QM31Error { - QM31UnreducedError(Felt), - QM31InvalidCoordinates([u64; 4]), + InvalidQM31(Felt), } #[cfg(feature = "std")] @@ -22,16 +21,11 @@ impl std::error::Error for QM31Error {} impl fmt::Display for QM31Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - QM31Error::QM31UnreducedError(felt) => writeln!( + QM31Error::InvalidQM31(felt) => writeln!( f, - "Number is not a packing of a QM31 in reduced form: {}", + "Number used as QM31 since it's more than 144 bits long: {}", felt ), - QM31Error::QM31InvalidCoordinates(coords) => writeln!( - f, - "The given coordinates cannot be packed into a QM31: {:#?}", - coords - ), } } } @@ -41,14 +35,12 @@ impl fmt::Display for QM31Error { pub struct QM31Felt(pub(crate) FieldElement); impl QM31Felt { - /// Reads four u64 coordinates from a single Felt. STWO_PRIME fits - /// in 36 bits, hence each coordinate can be represented by 36 bits and a QM31 element can be stored in the first - /// 144 bits of a Felt. Returns an error if the input has over 144 bits or any coordinate is unreduced. - /// - /// # Safety - /// This function reads from an already created QM31. If there were an error, it would've been caught during - /// its creation. - pub fn read_coordinates(&self) -> [u64; 4] { + /// [Felt] constant that's equal to 0. + pub const ZERO: Self = Self(FieldElement::::from_hex_unchecked("0")); + + /// Reads four u64 coordinates from a single Felt. STWO_PRIME fits in 36 bits, hence each coordinate + /// can be represented by 36 bits and a QM31 element can be stored in the first 144 bits of a Felt. + pub fn as_raw(&self) -> [u64; 4] { let limbs = self.as_le_digits(); [ @@ -59,11 +51,10 @@ impl QM31Felt { ] } - /// Create a [QM31Felt] without checking it. If the coordinates cannot be - /// represented within 144 bits, this can lead to undefined behaviour and big - /// security issue. - /// You should always use the [TryFrom] implementation. - pub fn from_coordinates_unchecked(coordinates: [u64; 4]) -> QM31Felt { + /// Create a [QM31Felt] from the raw internal representation. Reduces four u64 coordinates and packs them + /// into a single Felt252. STWO_PRIME fits in 36 bits, hence each coordinate can be represented + /// by 36 bits and a QM31 element can be stored in the first 144 bits of a Felt252. + pub fn from_raw(coordinates: [u64; 4]) -> QM31Felt { let bytes_part1 = ((coordinates[0] % STWO_PRIME) as u128 + (((coordinates[1] % STWO_PRIME) as u128) << 36)) .to_le_bytes(); @@ -79,47 +70,25 @@ impl QM31Felt { Self(value.0) } - /// Reduces four u64 coordinates and packs them into a single Felt. - /// STWO_PRIME fits in 36 bits, hence each coordinate can be represented - /// by 36 bits and a QM31 element can be stored in the first 144 bits of a Felt. - pub fn from_coordinates(coordinates: [u64; 4]) -> Result { - let qm31 = Self::from_coordinates_unchecked(coordinates); - let limbs = qm31.as_le_digits(); - let coordinates = [ - (limbs[0] & MASK_36), - ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), - ((limbs[1] >> 8) & MASK_36), - ((limbs[1] >> 44) + (limbs[2] << 20)), - ]; - - for x in coordinates.iter() { - if *x >= STWO_PRIME { - return Err(QM31Error::QM31UnreducedError(qm31.into())); - } - } - - Ok(qm31) - } - /// Computes the addition of two QM31 elements in reduced form. /// Returns an error if either operand is not reduced. - pub fn add(&self, rhs: &QM31Felt) -> Result { - let coordinates1 = self.read_coordinates(); - let coordinates2 = rhs.read_coordinates(); + pub fn add(&self, rhs: &QM31Felt) -> QM31Felt { + let coordinates1 = self.as_raw(); + let coordinates2 = rhs.as_raw(); let result_unreduced_coordinates = [ coordinates1[0] + coordinates2[0], coordinates1[1] + coordinates2[1], coordinates1[2] + coordinates2[2], coordinates1[3] + coordinates2[3], ]; - Self::from_coordinates(result_unreduced_coordinates) + Self::from_raw(result_unreduced_coordinates) } /// Computes the negative of a QM31 element in reduced form. /// Returns an error if the input is not reduced. - pub fn neg(&self) -> Result { - let coordinates = self.read_coordinates(); - Self::from_coordinates([ + pub fn neg(&self) -> QM31Felt { + let coordinates = self.as_raw(); + Self::from_raw([ STWO_PRIME - coordinates[0], STWO_PRIME - coordinates[1], STWO_PRIME - coordinates[2], @@ -129,23 +98,23 @@ impl QM31Felt { /// Computes the subtraction of two QM31 elements in reduced form. /// Returns an error if either operand is not reduced. - pub fn sub(&self, rhs: &QM31Felt) -> Result { - let coordinates1 = self.read_coordinates(); - let coordinates2 = rhs.read_coordinates(); + pub fn sub(&self, rhs: &QM31Felt) -> QM31Felt { + let coordinates1 = self.as_raw(); + let coordinates2 = rhs.as_raw(); let result_unreduced_coordinates = [ STWO_PRIME + coordinates1[0] - coordinates2[0], STWO_PRIME + coordinates1[1] - coordinates2[1], STWO_PRIME + coordinates1[2] - coordinates2[2], STWO_PRIME + coordinates1[3] - coordinates2[3], ]; - Self::from_coordinates(result_unreduced_coordinates) + Self::from_raw(result_unreduced_coordinates) } /// Computes the multiplication of two QM31 elements in reduced form. /// Returns an error if either operand is not reduced. - pub fn mul(&self, rhs: &QM31Felt) -> Result { - let coordinates1_u64 = self.read_coordinates(); - let coordinates2_u64 = rhs.read_coordinates(); + pub fn mul(&self, rhs: &QM31Felt) -> QM31Felt { + let coordinates1_u64 = self.as_raw(); + let coordinates2_u64 = rhs.as_raw(); let coordinates1 = coordinates1_u64.map(u128::from); let coordinates2 = coordinates2_u64.map(u128::from); @@ -173,7 +142,7 @@ impl QM31Felt { + coordinates1_u64[2] * coordinates2_u64[1] + coordinates1_u64[3] * coordinates2_u64[0], ]; - Self::from_coordinates(result_coordinates) + Self::from_raw(result_coordinates) } /// Computes the inverse in the M31 field using Fermat's little theorem, i.e., returns @@ -199,8 +168,12 @@ impl QM31Felt { /// Computes the inverse of a QM31 element in reduced form. /// Returns an error if the denominator is zero or either operand is not reduced. - pub fn inverse(&self) -> Result { - let coordinates = self.read_coordinates(); + /// # Safety + /// If the value is zero will panic. + pub fn inverse(&self) -> QM31Felt { + assert_ne!(*self, Self::ZERO, "Zero is not an invertible number"); + + let coordinates = self.as_raw(); let b2_r = (coordinates[2] * coordinates[2] + STWO_PRIME * STWO_PRIME - coordinates[3] * coordinates[3]) @@ -222,7 +195,7 @@ impl QM31Felt { let denom_inverse_r = (denom_r * denom_norm_inverse_squared) % STWO_PRIME; let denom_inverse_i = ((STWO_PRIME - denom_i) * denom_norm_inverse_squared) % STWO_PRIME; - Self::from_coordinates([ + Self::from_raw([ coordinates[0] * denom_inverse_r + STWO_PRIME * STWO_PRIME - coordinates[1] * denom_inverse_i, coordinates[0] * denom_inverse_i + coordinates[1] * denom_inverse_r, @@ -236,9 +209,15 @@ impl QM31Felt { /// Computes the division of two QM31 elements in reduced form. /// Returns an error if the input is zero. + /// # Safety + /// Will panic if the rhs value is equal to zero. pub fn div(&self, rhs: &QM31Felt) -> Result { - let rhs_inv = rhs.inverse()?; - self.mul(&rhs_inv) + let rhs_inv = rhs.inverse(); + Ok(self.mul(&rhs_inv)) + } + + pub fn is_zero(&self) -> bool { + *self == Self::ZERO } /// Convert `self`'s representative into an array of `u64` digits, @@ -278,10 +257,10 @@ impl TryFrom for QM31Felt { // because we are trying to convert a Felt into a QM31Felt. This // Felt should represent a packed QM31 which is at most 144 bits long. if limbs[3] != 0 || limbs[2] >= 1 << 16 { - return Err(QM31Error::QM31InvalidCoordinates(limbs)); + return Err(QM31Error::InvalidQM31(value)); } - Self::from_coordinates(limbs) + Ok(Self(value.0)) } } @@ -295,10 +274,10 @@ impl TryFrom<&Felt> for QM31Felt { // because we are trying to convert a Felt into a QM31Felt. This // Felt should represent a packed QM31 which is at most 144 bits long. if limbs[3] != 0 || limbs[2] >= 1 << 16 { - return Err(QM31Error::QM31InvalidCoordinates(limbs)); + return Err(QM31Error::InvalidQM31(*value)); } - Self::from_coordinates(limbs) + Ok(Self(value.0)) } } @@ -323,22 +302,7 @@ mod test { let qm31: Result = felt.try_into(); assert!(matches!( qm31, - Err(QM31Error::QM31UnreducedError(bx)) if bx == felt - )); - } - - #[test] - fn qm31_packed_reduced_read_coordinates_unreduced() { - let mut felt_bytes = [0u8; 32]; - felt_bytes[0] = 0xff; - felt_bytes[1] = 0xff; - felt_bytes[2] = 0xff; - felt_bytes[3] = (1 << 7) - 1; - let felt = Felt::from_bytes_le(&felt_bytes); - let qm31: Result = felt.try_into(); - assert!(matches!( - qm31, - Err(QM31Error::QM31UnreducedError(bx)) if bx == felt + Err(QM31Error::InvalidQM31(bx)) if bx == felt )); } @@ -346,10 +310,10 @@ mod test { fn test_qm31_packed_reduced_add() { let x_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; let y_coordinates = [1234567890, 1414213562, 1732050807, 1618033988]; - let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); - let y = QM31Felt::from_coordinates(y_coordinates).unwrap(); - let res = x.add(&y).unwrap(); - let res_coordinates = res.read_coordinates(); + let x = QM31Felt::from_raw(x_coordinates); + let y = QM31Felt::from_raw(y_coordinates); + let res = x.add(&y); + let res_coordinates = res.as_raw(); assert_eq!( res_coordinates, [ @@ -364,9 +328,9 @@ mod test { #[test] fn test_qm31_packed_reduced_neg() { let x_coordinates = [1749652895, 834624081, 1930174752, 2063872165]; - let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); - let res = x.neg().unwrap(); - let res_coordinates = res.read_coordinates(); + let x = QM31Felt::from_raw(x_coordinates); + let res = x.neg(); + let res_coordinates = res.as_raw(); assert_eq!( res_coordinates, [ @@ -387,10 +351,10 @@ mod test { (1234567890 + 1618033988) % STWO_PRIME, ]; let y_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; - let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); - let y = QM31Felt::from_coordinates(y_coordinates).unwrap(); - let res = x.sub(&y).unwrap(); - let res_coordinates = res.read_coordinates(); + let x = QM31Felt::from_raw(x_coordinates); + let y = QM31Felt::from_raw(y_coordinates); + let res = x.sub(&y); + let res_coordinates = res.as_raw(); assert_eq!( res_coordinates, [1234567890, 1414213562, 1732050807, 1618033988] @@ -401,10 +365,10 @@ mod test { fn test_qm31_packed_reduced_mul() { let x_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; let y_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; - let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); - let y = QM31Felt::from_coordinates(y_coordinates).unwrap(); - let res = x.mul(&y).unwrap(); - let res_coordinates = res.read_coordinates(); + let x = QM31Felt::from_raw(x_coordinates); + let y = QM31Felt::from_raw(y_coordinates); + let res = x.mul(&y); + let res_coordinates = res.as_raw(); assert_eq!( res_coordinates, [947980980, 1510986506, 623360030, 1260310989] @@ -414,22 +378,22 @@ mod test { #[test] fn test_qm31_packed_reduced_inv() { let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; - let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); - let res = x.inverse().unwrap(); + let x = QM31Felt::from_raw(x_coordinates); + let res = x.inverse(); let expected = Felt::from(1).try_into().unwrap(); - assert_eq!(x.mul(&res).unwrap(), expected); + assert_eq!(x.mul(&res), expected); let x_coordinates = [1, 2, 3, 4]; - let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); - let res = x.inverse().unwrap(); + let x = QM31Felt::from_raw(x_coordinates); + let res = x.inverse(); let expected = Felt::from(1).try_into().unwrap(); - assert_eq!(x.mul(&res).unwrap(), expected); + assert_eq!(x.mul(&res), expected); let x_coordinates = [1749652895, 834624081, 1930174752, 2063872165]; - let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); - let res = x.inverse().unwrap(); + let x = QM31Felt::from_raw(x_coordinates); + let res = x.inverse(); let expected = Felt::from(1).try_into().unwrap(); - assert_eq!(x.mul(&res).unwrap(), expected); + assert_eq!(x.mul(&res), expected); } /// Necessary strat to use proptest on the QM31 test @@ -443,11 +407,11 @@ mod test { .prop_filter("All configs cant be 0", |arr| !arr.iter().all(|x| *x == 0)) ) { - let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); - let res = x.inverse().unwrap(); + let x = QM31Felt::from_raw(x_coordinates); + let res = x.inverse(); // Expect 1_felt252 - let expected = QM31Felt::from_coordinates([1,0,0,0]).unwrap(); - assert_eq!(x.mul(&res).unwrap(), expected); + let expected = QM31Felt::from_raw([1,0,0,0]); + assert_eq!(x.mul(&res), expected); } #[test] @@ -457,11 +421,11 @@ mod test { |arr| !arr.iter().all(|x| *x == 0)) .no_shrink() ) { - let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); - let res = x.inverse().unwrap(); + let x = QM31Felt::from_raw(x_coordinates); + let res = x.inverse(); // Expect 1_felt252 - let expected = QM31Felt::from_coordinates([1,0,0,0]).unwrap(); - assert_eq!(x.mul(&res).unwrap(), expected); + let expected = QM31Felt::from_raw([1,0,0,0]); + assert_eq!(x.mul(&res), expected); } } @@ -470,9 +434,9 @@ mod test { let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; let y_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; let xy_coordinates = [947980980, 1510986506, 623360030, 1260310989]; - let x = QM31Felt::from_coordinates(x_coordinates).unwrap(); - let y = QM31Felt::from_coordinates(y_coordinates).unwrap(); - let xy = QM31Felt::from_coordinates(xy_coordinates).unwrap(); + let x = QM31Felt::from_raw(x_coordinates); + let y = QM31Felt::from_raw(y_coordinates); + let xy = QM31Felt::from_raw(xy_coordinates); let res = xy.div(&y).unwrap(); assert_eq!(res, x); From 176c1de9864e27cdb02b9c642cba1fa96d7a1a3a Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Wed, 13 Aug 2025 11:03:32 -0300 Subject: [PATCH 10/44] fix doc comment --- crates/starknet-types-core/src/felt/qm31.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index 888a16f..069114c 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -35,7 +35,7 @@ impl fmt::Display for QM31Error { pub struct QM31Felt(pub(crate) FieldElement); impl QM31Felt { - /// [Felt] constant that's equal to 0. + /// [QM31Felt] constant that's equal to 0. pub const ZERO: Self = Self(FieldElement::::from_hex_unchecked("0")); /// Reads four u64 coordinates from a single Felt. STWO_PRIME fits in 36 bits, hence each coordinate From 21ec03e5ecce18dd4ca2eb97e678e23825b18af7 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Thu, 14 Aug 2025 10:18:44 -0300 Subject: [PATCH 11/44] change qm31 representation, remove outdated comments --- crates/starknet-types-core/src/felt/qm31.rs | 76 ++++++++------------- 1 file changed, 27 insertions(+), 49 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index 069114c..ebd710f 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -1,9 +1,6 @@ use core::fmt; use crate::felt::Felt; -use lambdaworks_math::field::{ - element::FieldElement, fields::fft_friendly::stark_252_prime_field::Stark252PrimeField, -}; pub const STWO_PRIME: u64 = (1 << 31) - 1; const STWO_PRIME_U128: u128 = STWO_PRIME as u128; @@ -12,7 +9,7 @@ const MASK_8: u64 = (1 << 8) - 1; #[derive(Debug)] pub enum QM31Error { - InvalidQM31(Felt), + FeltTooBig(Felt), } #[cfg(feature = "std")] @@ -21,7 +18,7 @@ impl std::error::Error for QM31Error {} impl fmt::Display for QM31Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - QM31Error::InvalidQM31(felt) => writeln!( + QM31Error::FeltTooBig(felt) => writeln!( f, "Number used as QM31 since it's more than 144 bits long: {}", felt @@ -32,23 +29,15 @@ impl fmt::Display for QM31Error { #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct QM31Felt(pub(crate) FieldElement); +pub struct QM31Felt([u64; 4]); impl QM31Felt { /// [QM31Felt] constant that's equal to 0. - pub const ZERO: Self = Self(FieldElement::::from_hex_unchecked("0")); + pub const ZERO: Self = Self([0, 0, 0, 0]); - /// Reads four u64 coordinates from a single Felt. STWO_PRIME fits in 36 bits, hence each coordinate - /// can be represented by 36 bits and a QM31 element can be stored in the first 144 bits of a Felt. + pub fn as_raw(&self) -> [u64; 4] { - let limbs = self.as_le_digits(); - - [ - (limbs[0] & MASK_36), - ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), - ((limbs[1] >> 8) & MASK_36), - ((limbs[1] >> 44) + (limbs[2] << 20)), - ] + self.0 } /// Create a [QM31Felt] from the raw internal representation. Reduces four u64 coordinates and packs them @@ -61,17 +50,25 @@ impl QM31Felt { let bytes_part2 = ((coordinates[2] % STWO_PRIME) as u128 + (((coordinates[3] % STWO_PRIME) as u128) << 36)) .to_le_bytes(); - let mut result_bytes = [0u8; 32]; + let mut result_bytes = [0; 32]; + result_bytes[0..9].copy_from_slice(&bytes_part1[0..9]); result_bytes[9..18].copy_from_slice(&bytes_part2[0..9]); - let value = Felt::from_bytes_le(&result_bytes); + let limbs = { + let felt = Felt::from_bytes_le_slice(&result_bytes); + felt.to_le_digits() + }; - Self(value.0) + Self([ + (limbs[0] & MASK_36), + ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), + ((limbs[1] >> 8) & MASK_36), + ((limbs[1] >> 44) + (limbs[2] << 20)), + ]) } /// Computes the addition of two QM31 elements in reduced form. - /// Returns an error if either operand is not reduced. pub fn add(&self, rhs: &QM31Felt) -> QM31Felt { let coordinates1 = self.as_raw(); let coordinates2 = rhs.as_raw(); @@ -85,7 +82,6 @@ impl QM31Felt { } /// Computes the negative of a QM31 element in reduced form. - /// Returns an error if the input is not reduced. pub fn neg(&self) -> QM31Felt { let coordinates = self.as_raw(); Self::from_raw([ @@ -97,7 +93,6 @@ impl QM31Felt { } /// Computes the subtraction of two QM31 elements in reduced form. - /// Returns an error if either operand is not reduced. pub fn sub(&self, rhs: &QM31Felt) -> QM31Felt { let coordinates1 = self.as_raw(); let coordinates2 = rhs.as_raw(); @@ -111,7 +106,6 @@ impl QM31Felt { } /// Computes the multiplication of two QM31 elements in reduced form. - /// Returns an error if either operand is not reduced. pub fn mul(&self, rhs: &QM31Felt) -> QM31Felt { let coordinates1_u64 = self.as_raw(); let coordinates2_u64 = rhs.as_raw(); @@ -147,7 +141,7 @@ impl QM31Felt { /// Computes the inverse in the M31 field using Fermat's little theorem, i.e., returns /// `v^(STWO_PRIME-2) modulo STWO_PRIME`, which is the inverse of v unless v % STWO_PRIME == 0. - fn pow2147483645(v: u64) -> u64 { + fn m31_inverse(v: u64) -> u64 { let t0 = (Self::sqn(v, 2) * v) % STWO_PRIME; let t1 = (Self::sqn(t0, 1) * t0) % STWO_PRIME; let t2 = (Self::sqn(t1, 3) * t0) % STWO_PRIME; @@ -167,7 +161,6 @@ impl QM31Felt { } /// Computes the inverse of a QM31 element in reduced form. - /// Returns an error if the denominator is zero or either operand is not reduced. /// # Safety /// If the value is zero will panic. pub fn inverse(&self) -> QM31Felt { @@ -190,7 +183,7 @@ impl QM31Felt { (2 * coordinates[0] * coordinates[1] + 3 * STWO_PRIME - 2 * b2_i - b2_r) % STWO_PRIME; let denom_norm_squared = (denom_r * denom_r + denom_i * denom_i) % STWO_PRIME; - let denom_norm_inverse_squared = Self::pow2147483645(denom_norm_squared); + let denom_norm_inverse_squared = Self::m31_inverse(denom_norm_squared); let denom_inverse_r = (denom_r * denom_norm_inverse_squared) % STWO_PRIME; let denom_inverse_i = ((STWO_PRIME - denom_i) * denom_norm_inverse_squared) % STWO_PRIME; @@ -208,7 +201,6 @@ impl QM31Felt { } /// Computes the division of two QM31 elements in reduced form. - /// Returns an error if the input is zero. /// # Safety /// Will panic if the rhs value is equal to zero. pub fn div(&self, rhs: &QM31Felt) -> Result { @@ -219,31 +211,17 @@ impl QM31Felt { pub fn is_zero(&self) -> bool { *self == Self::ZERO } - - /// Convert `self`'s representative into an array of `u64` digits, - /// least significant digits first. - pub fn as_le_digits(&self) -> [u64; 4] { - let mut limbs = self.0.representative().limbs; - limbs.reverse(); - limbs - } - - /// Convert `self`'s representative into an array of `u64` digits, - /// most significant digits first. - pub fn as_be_digits(&self) -> [u64; 4] { - self.0.representative().limbs - } } impl From<&QM31Felt> for Felt { fn from(value: &QM31Felt) -> Self { - Felt(value.0) + Felt::from_raw(value.0) } } impl From for Felt { fn from(value: QM31Felt) -> Self { - Felt(value.0) + Felt::from_raw(value.0) } } @@ -257,10 +235,10 @@ impl TryFrom for QM31Felt { // because we are trying to convert a Felt into a QM31Felt. This // Felt should represent a packed QM31 which is at most 144 bits long. if limbs[3] != 0 || limbs[2] >= 1 << 16 { - return Err(QM31Error::InvalidQM31(value)); + return Err(QM31Error::FeltTooBig(value)); } - Ok(Self(value.0)) + Ok(Self(limbs)) } } @@ -274,10 +252,10 @@ impl TryFrom<&Felt> for QM31Felt { // because we are trying to convert a Felt into a QM31Felt. This // Felt should represent a packed QM31 which is at most 144 bits long. if limbs[3] != 0 || limbs[2] >= 1 << 16 { - return Err(QM31Error::InvalidQM31(*value)); + return Err(QM31Error::FeltTooBig(*value)); } - Ok(Self(value.0)) + Ok(Self(limbs)) } } @@ -302,7 +280,7 @@ mod test { let qm31: Result = felt.try_into(); assert!(matches!( qm31, - Err(QM31Error::InvalidQM31(bx)) if bx == felt + Err(QM31Error::FeltTooBig(bx)) if bx == felt )); } From ddc19e79aaddfc97bbbbe04b3a637895fb79ad61 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Thu, 14 Aug 2025 10:40:03 -0300 Subject: [PATCH 12/44] return an error instead of panicking when trying to divide by zero --- crates/starknet-types-core/src/felt/qm31.rs | 34 +++++++++++---------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index ebd710f..920647b 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -10,6 +10,7 @@ const MASK_8: u64 = (1 << 8) - 1; #[derive(Debug)] pub enum QM31Error { FeltTooBig(Felt), + InvalidInversion, } #[cfg(feature = "std")] @@ -23,6 +24,7 @@ impl fmt::Display for QM31Error { "Number used as QM31 since it's more than 144 bits long: {}", felt ), + QM31Error::InvalidInversion => writeln!(f, "Attempt to invert a qm31 equal to zero"), } } } @@ -35,7 +37,6 @@ impl QM31Felt { /// [QM31Felt] constant that's equal to 0. pub const ZERO: Self = Self([0, 0, 0, 0]); - pub fn as_raw(&self) -> [u64; 4] { self.0 } @@ -55,10 +56,10 @@ impl QM31Felt { result_bytes[0..9].copy_from_slice(&bytes_part1[0..9]); result_bytes[9..18].copy_from_slice(&bytes_part2[0..9]); - let limbs = { + let limbs = { let felt = Felt::from_bytes_le_slice(&result_bytes); felt.to_le_digits() - }; + }; Self([ (limbs[0] & MASK_36), @@ -163,8 +164,10 @@ impl QM31Felt { /// Computes the inverse of a QM31 element in reduced form. /// # Safety /// If the value is zero will panic. - pub fn inverse(&self) -> QM31Felt { - assert_ne!(*self, Self::ZERO, "Zero is not an invertible number"); + pub fn inverse(&self) -> Result { + if *self == Self::ZERO { + return Err(QM31Error::InvalidInversion); + } let coordinates = self.as_raw(); @@ -188,7 +191,7 @@ impl QM31Felt { let denom_inverse_r = (denom_r * denom_norm_inverse_squared) % STWO_PRIME; let denom_inverse_i = ((STWO_PRIME - denom_i) * denom_norm_inverse_squared) % STWO_PRIME; - Self::from_raw([ + Ok(Self::from_raw([ coordinates[0] * denom_inverse_r + STWO_PRIME * STWO_PRIME - coordinates[1] * denom_inverse_i, coordinates[0] * denom_inverse_i + coordinates[1] * denom_inverse_r, @@ -197,14 +200,13 @@ impl QM31Felt { 2 * STWO_PRIME * STWO_PRIME - coordinates[2] * denom_inverse_i - coordinates[3] * denom_inverse_r, - ]) + ])) } - /// Computes the division of two QM31 elements in reduced form. - /// # Safety - /// Will panic if the rhs value is equal to zero. + /// Computes the division of two QM31 elements in reduced form. Returns an error + /// if the rhs value is equal to zero. pub fn div(&self, rhs: &QM31Felt) -> Result { - let rhs_inv = rhs.inverse(); + let rhs_inv = rhs.inverse()?; Ok(self.mul(&rhs_inv)) } @@ -357,19 +359,19 @@ mod test { fn test_qm31_packed_reduced_inv() { let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; let x = QM31Felt::from_raw(x_coordinates); - let res = x.inverse(); + let res = x.inverse().unwrap(); let expected = Felt::from(1).try_into().unwrap(); assert_eq!(x.mul(&res), expected); let x_coordinates = [1, 2, 3, 4]; let x = QM31Felt::from_raw(x_coordinates); - let res = x.inverse(); + let res = x.inverse().unwrap(); let expected = Felt::from(1).try_into().unwrap(); assert_eq!(x.mul(&res), expected); let x_coordinates = [1749652895, 834624081, 1930174752, 2063872165]; let x = QM31Felt::from_raw(x_coordinates); - let res = x.inverse(); + let res = x.inverse().unwrap(); let expected = Felt::from(1).try_into().unwrap(); assert_eq!(x.mul(&res), expected); } @@ -386,7 +388,7 @@ mod test { |arr| !arr.iter().all(|x| *x == 0)) ) { let x = QM31Felt::from_raw(x_coordinates); - let res = x.inverse(); + let res = x.inverse().unwrap(); // Expect 1_felt252 let expected = QM31Felt::from_raw([1,0,0,0]); assert_eq!(x.mul(&res), expected); @@ -400,7 +402,7 @@ mod test { .no_shrink() ) { let x = QM31Felt::from_raw(x_coordinates); - let res = x.inverse(); + let res = x.inverse().unwrap(); // Expect 1_felt252 let expected = QM31Felt::from_raw([1,0,0,0]); assert_eq!(x.mul(&res), expected); From a7691d78058ab01b6fd5e90c233490f7073298a2 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Thu, 14 Aug 2025 18:45:25 -0300 Subject: [PATCH 13/44] add test felt -> qm31 -> felt --- crates/starknet-types-core/src/felt/qm31.rs | 33 +++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index 920647b..f38213e 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -274,6 +274,39 @@ mod test { Felt, }; + #[test] + fn from_qm31_to_felt() { + let felt_expected = Felt::from(2i128.pow(126)); + let qm31: QM31Felt = felt_expected.try_into().unwrap(); + let felt = qm31.into(); + + assert_eq!(qm31, felt); + + let felt_expected = Felt::from(2i64.pow(62)); + let qm31: QM31Felt = felt_expected.try_into().unwrap(); + let felt = qm31.into(); + + assert_eq!(qm31, felt); + + let felt_expected = Felt::from(2i32.pow(30)); + let qm31: QM31Felt = felt_expected.try_into().unwrap(); + let felt = qm31.into(); + + assert_eq!(qm31, felt); + + let felt_expected = Felt::from(2i8.pow(6)); + let qm31: QM31Felt = felt_expected.try_into().unwrap(); + let felt = qm31.into(); + + assert_eq!(qm31, felt); + + let felt_expected = Felt::ZERO; + let qm31: QM31Felt = felt_expected.try_into().unwrap(); + let felt = qm31.into(); + + assert_eq!(qm31, felt); + } + #[test] fn qm31_packed_reduced_coordinates_over_144_bits() { let mut felt_bytes = [0u8; 32]; From bb721e5c437f0345a62706b0c742ff0303fba4e1 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Thu, 14 Aug 2025 18:59:19 -0300 Subject: [PATCH 14/44] fix doc comment --- crates/starknet-types-core/src/felt/qm31.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index f38213e..9790f86 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -162,8 +162,7 @@ impl QM31Felt { } /// Computes the inverse of a QM31 element in reduced form. - /// # Safety - /// If the value is zero will panic. + /// Returns an error if the operand is equal to zero. pub fn inverse(&self) -> Result { if *self == Self::ZERO { return Err(QM31Error::InvalidInversion); From 0dc0d277d1645d4d35935e855d596c56cc78b503 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Mon, 18 Aug 2025 09:57:42 -0300 Subject: [PATCH 15/44] reduce felt with converting into qm31 --- crates/starknet-types-core/src/felt/qm31.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index 9790f86..6c8a8f3 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -21,8 +21,7 @@ impl fmt::Display for QM31Error { match self { QM31Error::FeltTooBig(felt) => writeln!( f, - "Number used as QM31 since it's more than 144 bits long: {}", - felt + "Number used as QM31 since it's more than 144 bits long: {felt}" ), QM31Error::InvalidInversion => writeln!(f, "Attempt to invert a qm31 equal to zero"), } @@ -239,7 +238,7 @@ impl TryFrom for QM31Felt { return Err(QM31Error::FeltTooBig(value)); } - Ok(Self(limbs)) + Ok(Self::from_raw(limbs)) } } @@ -256,7 +255,7 @@ impl TryFrom<&Felt> for QM31Felt { return Err(QM31Error::FeltTooBig(*value)); } - Ok(Self(limbs)) + Ok(Self::from_raw(limbs)) } } @@ -274,8 +273,9 @@ mod test { }; #[test] - fn from_qm31_to_felt() { + fn from_positive_felt_to_qm31_to_felt() { let felt_expected = Felt::from(2i128.pow(126)); + let qm31: QM31Felt = felt_expected.try_into().unwrap(); let felt = qm31.into(); From 5473a0ff5e809cac9f01ae66e1385a44068e6865 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Mon, 18 Aug 2025 17:23:50 -0300 Subject: [PATCH 16/44] change From implementation + add test --- crates/starknet-types-core/src/felt/qm31.rs | 94 +++++++++++---------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index 6c8a8f3..17a2997 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -4,8 +4,6 @@ use crate::felt::Felt; pub const STWO_PRIME: u64 = (1 << 31) - 1; const STWO_PRIME_U128: u128 = STWO_PRIME as u128; -const MASK_36: u64 = (1 << 36) - 1; -const MASK_8: u64 = (1 << 8) - 1; #[derive(Debug)] pub enum QM31Error { @@ -44,28 +42,24 @@ impl QM31Felt { /// into a single Felt252. STWO_PRIME fits in 36 bits, hence each coordinate can be represented /// by 36 bits and a QM31 element can be stored in the first 144 bits of a Felt252. pub fn from_raw(coordinates: [u64; 4]) -> QM31Felt { - let bytes_part1 = ((coordinates[0] % STWO_PRIME) as u128 - + (((coordinates[1] % STWO_PRIME) as u128) << 36)) - .to_le_bytes(); - let bytes_part2 = ((coordinates[2] % STWO_PRIME) as u128 - + (((coordinates[3] % STWO_PRIME) as u128) << 36)) - .to_le_bytes(); - let mut result_bytes = [0; 32]; + Self([ + coordinates[0] % STWO_PRIME, + coordinates[1] % STWO_PRIME, + coordinates[2] % STWO_PRIME, + coordinates[3] % STWO_PRIME, + ]) + } + pub fn pack_into_felt(&self) -> Felt { + let coordinates = self.0; + + let bytes_part1 = (coordinates[0] as u128 + ((coordinates[1] as u128) << 36)).to_le_bytes(); + let bytes_part2 = (coordinates[2] as u128 + ((coordinates[3] as u128) << 36)).to_le_bytes(); + let mut result_bytes = [0u8; 32]; result_bytes[0..9].copy_from_slice(&bytes_part1[0..9]); result_bytes[9..18].copy_from_slice(&bytes_part2[0..9]); - let limbs = { - let felt = Felt::from_bytes_le_slice(&result_bytes); - felt.to_le_digits() - }; - - Self([ - (limbs[0] & MASK_36), - ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), - ((limbs[1] >> 8) & MASK_36), - ((limbs[1] >> 44) + (limbs[2] << 20)), - ]) + Felt::from_bytes_le(&result_bytes) } /// Computes the addition of two QM31 elements in reduced form. @@ -215,13 +209,13 @@ impl QM31Felt { impl From<&QM31Felt> for Felt { fn from(value: &QM31Felt) -> Self { - Felt::from_raw(value.0) + value.pack_into_felt() } } impl From for Felt { fn from(value: QM31Felt) -> Self { - Felt::from_raw(value.0) + value.pack_into_felt() } } @@ -261,6 +255,8 @@ impl TryFrom<&Felt> for QM31Felt { #[cfg(test)] mod test { + use core::{u128, u16, u8}; + use proptest::{ array::uniform4, prelude::{BoxedStrategy, Just, Strategy}, @@ -273,41 +269,51 @@ mod test { }; #[test] - fn from_positive_felt_to_qm31_to_felt() { - let felt_expected = Felt::from(2i128.pow(126)); + fn qm31_to_felt_packed() { + let u64_max_reduced = u64::MAX % STWO_PRIME; + + let value = u8::MAX; + let felt = Felt::from(value); + let qm31: QM31Felt = felt.try_into().unwrap(); + let qm31_to_felt = qm31.pack_into_felt(); - let qm31: QM31Felt = felt_expected.try_into().unwrap(); - let felt = qm31.into(); + assert_eq!(qm31_to_felt, Felt::from(value)); - assert_eq!(qm31, felt); + let value = u16::MAX; + let felt = Felt::from(value); + let qm31: QM31Felt = felt.try_into().unwrap(); + let qm31_to_felt = qm31.pack_into_felt(); - let felt_expected = Felt::from(2i64.pow(62)); - let qm31: QM31Felt = felt_expected.try_into().unwrap(); - let felt = qm31.into(); + assert_eq!(qm31_to_felt, Felt::from(value)); - assert_eq!(qm31, felt); + let value = u32::MAX; + let felt = Felt::from(value); + let qm31: QM31Felt = felt.try_into().unwrap(); + let qm31_to_felt = qm31.pack_into_felt(); - let felt_expected = Felt::from(2i32.pow(30)); - let qm31: QM31Felt = felt_expected.try_into().unwrap(); - let felt = qm31.into(); + assert_eq!(qm31_to_felt, Felt::from(value as u64 % STWO_PRIME)); - assert_eq!(qm31, felt); + let felt = Felt::from(u64::MAX); + let qm31: QM31Felt = felt.try_into().unwrap(); + let qm31_to_felt = qm31.pack_into_felt(); + dbg!(felt.to_le_digits()); - let felt_expected = Felt::from(2i8.pow(6)); - let qm31: QM31Felt = felt_expected.try_into().unwrap(); - let felt = qm31.into(); + assert_eq!(qm31_to_felt, Felt::from(u64_max_reduced)); - assert_eq!(qm31, felt); + let felt = Felt::from(u128::MAX); + let qm31: QM31Felt = felt.try_into().unwrap(); + let qm31_to_felt = qm31.pack_into_felt(); - let felt_expected = Felt::ZERO; - let qm31: QM31Felt = felt_expected.try_into().unwrap(); - let felt = qm31.into(); + let mut bytes = [0u8; 32]; + let bytes_part1 = + ((u64_max_reduced) as u128 + (((u64_max_reduced) as u128) << 36)).to_le_bytes(); + bytes[0..9].copy_from_slice(&bytes_part1[0..9]); - assert_eq!(qm31, felt); + assert_eq!(qm31_to_felt, Felt::from_bytes_le(&bytes)); } #[test] - fn qm31_packed_reduced_coordinates_over_144_bits() { + fn qm31_coordinates_over_144_bits() { let mut felt_bytes = [0u8; 32]; felt_bytes[18] = 1; let felt = Felt::from_bytes_le(&felt_bytes); From 7a646463a9c2b97eb421a2cd210f2f69746b1894 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Mon, 18 Aug 2025 17:53:16 -0300 Subject: [PATCH 17/44] update comments --- crates/starknet-types-core/src/felt/qm31.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index 17a2997..2b425b6 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -38,9 +38,7 @@ impl QM31Felt { self.0 } - /// Create a [QM31Felt] from the raw internal representation. Reduces four u64 coordinates and packs them - /// into a single Felt252. STWO_PRIME fits in 36 bits, hence each coordinate can be represented - /// by 36 bits and a QM31 element can be stored in the first 144 bits of a Felt252. + /// Create a [QM31Felt] from the raw internal representation. Reduces four u64 coordinates so that the fit in 144 bits. pub fn from_raw(coordinates: [u64; 4]) -> QM31Felt { Self([ coordinates[0] % STWO_PRIME, @@ -50,6 +48,7 @@ impl QM31Felt { ]) } + /// Packs the [QM31Felt] coordinates into a Felt. pub fn pack_into_felt(&self) -> Felt { let coordinates = self.0; @@ -62,7 +61,7 @@ impl QM31Felt { Felt::from_bytes_le(&result_bytes) } - /// Computes the addition of two QM31 elements in reduced form. + /// Computes the addition of two [QM31Felt] elements in reduced form. pub fn add(&self, rhs: &QM31Felt) -> QM31Felt { let coordinates1 = self.as_raw(); let coordinates2 = rhs.as_raw(); @@ -75,7 +74,7 @@ impl QM31Felt { Self::from_raw(result_unreduced_coordinates) } - /// Computes the negative of a QM31 element in reduced form. + /// Computes the negative of a [QM31Felt] element in reduced form. pub fn neg(&self) -> QM31Felt { let coordinates = self.as_raw(); Self::from_raw([ @@ -86,7 +85,7 @@ impl QM31Felt { ]) } - /// Computes the subtraction of two QM31 elements in reduced form. + /// Computes the subtraction of two [QM31Felt] elements in reduced form. pub fn sub(&self, rhs: &QM31Felt) -> QM31Felt { let coordinates1 = self.as_raw(); let coordinates2 = rhs.as_raw(); @@ -99,7 +98,7 @@ impl QM31Felt { Self::from_raw(result_unreduced_coordinates) } - /// Computes the multiplication of two QM31 elements in reduced form. + /// Computes the multiplication of two [QM31Felt] elements in reduced form. pub fn mul(&self, rhs: &QM31Felt) -> QM31Felt { let coordinates1_u64 = self.as_raw(); let coordinates2_u64 = rhs.as_raw(); @@ -154,7 +153,7 @@ impl QM31Felt { u } - /// Computes the inverse of a QM31 element in reduced form. + /// Computes the inverse of a [QM31Felt] element in reduced form. /// Returns an error if the operand is equal to zero. pub fn inverse(&self) -> Result { if *self == Self::ZERO { @@ -195,7 +194,7 @@ impl QM31Felt { ])) } - /// Computes the division of two QM31 elements in reduced form. Returns an error + /// Computes the division of two [QM31Felt] elements in reduced form. Returns an error /// if the rhs value is equal to zero. pub fn div(&self, rhs: &QM31Felt) -> Result { let rhs_inv = rhs.inverse()?; From f0751f4051819e484e50639dda8eee7294370635 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Mon, 18 Aug 2025 18:01:29 -0300 Subject: [PATCH 18/44] fix typo --- crates/starknet-types-core/src/felt/qm31.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index 2b425b6..424cfe1 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -38,7 +38,7 @@ impl QM31Felt { self.0 } - /// Create a [QM31Felt] from the raw internal representation. Reduces four u64 coordinates so that the fit in 144 bits. + /// Create a [QM31Felt] from the raw internal representation. Reduces four u64 coordinates so that they fit in 144 bits. pub fn from_raw(coordinates: [u64; 4]) -> QM31Felt { Self([ coordinates[0] % STWO_PRIME, From c63dfab4d2ddbdff85b68b5bb0f337d7eef39f73 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Mon, 18 Aug 2025 19:20:31 -0300 Subject: [PATCH 19/44] change TryFrom implementation to read the packed coordinates instead reducing them --- crates/starknet-types-core/src/felt/qm31.rs | 95 +++++++++++++-------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index 424cfe1..d6e6ae9 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -4,9 +4,12 @@ use crate::felt::Felt; pub const STWO_PRIME: u64 = (1 << 31) - 1; const STWO_PRIME_U128: u128 = STWO_PRIME as u128; +const MASK_36: u64 = (1 << 36) - 1; +const MASK_8: u64 = (1 << 8) - 1; #[derive(Debug)] pub enum QM31Error { + UnreducedFelt(Felt), FeltTooBig(Felt), InvalidInversion, } @@ -17,6 +20,10 @@ impl std::error::Error for QM31Error {} impl fmt::Display for QM31Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + QM31Error::UnreducedFelt(felt) => writeln!( + f, + "Number is not a packing of a QM31 in reduced form: {felt})" + ), QM31Error::FeltTooBig(felt) => writeln!( f, "Number used as QM31 since it's more than 144 bits long: {felt}" @@ -231,7 +238,21 @@ impl TryFrom for QM31Felt { return Err(QM31Error::FeltTooBig(value)); } - Ok(Self::from_raw(limbs)) + let coordinates = [ + (limbs[0] & MASK_36), + ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), + ((limbs[1] >> 8) & MASK_36), + ((limbs[1] >> 44) + (limbs[2] << 20)), + ]; + + // Check if the coordinates were reduced before. + for x in coordinates.iter() { + if *x >= STWO_PRIME { + return Err(QM31Error::UnreducedFelt(value)); + } + } + + Ok(Self(coordinates)) } } @@ -248,13 +269,27 @@ impl TryFrom<&Felt> for QM31Felt { return Err(QM31Error::FeltTooBig(*value)); } - Ok(Self::from_raw(limbs)) + // Check if the coordinates were reduced before. + let coordinates = [ + (limbs[0] & MASK_36), + ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), + ((limbs[1] >> 8) & MASK_36), + ((limbs[1] >> 44) + (limbs[2] << 20)), + ]; + + for x in coordinates.iter() { + if *x >= STWO_PRIME { + return Err(QM31Error::UnreducedFelt(*value)); + } + } + + Ok(Self(coordinates)) } } #[cfg(test)] mod test { - use core::{u128, u16, u8}; + use core::u64; use proptest::{ array::uniform4, @@ -268,47 +303,35 @@ mod test { }; #[test] - fn qm31_to_felt_packed() { - let u64_max_reduced = u64::MAX % STWO_PRIME; - - let value = u8::MAX; - let felt = Felt::from(value); - let qm31: QM31Felt = felt.try_into().unwrap(); - let qm31_to_felt = qm31.pack_into_felt(); - - assert_eq!(qm31_to_felt, Felt::from(value)); - - let value = u16::MAX; - let felt = Felt::from(value); - let qm31: QM31Felt = felt.try_into().unwrap(); - let qm31_to_felt = qm31.pack_into_felt(); + fn qm31_to_felt() { + let coordinates = QM31Felt::from_raw([1, 2, 3, 4]); + let packed_coordinates = Felt::from(coordinates); + let unpacked_coordinates = QM31Felt::try_from(packed_coordinates).unwrap(); + assert_eq!(coordinates, unpacked_coordinates); - assert_eq!(qm31_to_felt, Felt::from(value)); + let qm31 = QM31Felt::from_raw([u64::MAX, 0, 0, 0]); + let felt: Felt = qm31.try_into().unwrap(); + let felt_to_qm31 = QM31Felt::try_from(felt).unwrap(); - let value = u32::MAX; - let felt = Felt::from(value); - let qm31: QM31Felt = felt.try_into().unwrap(); - let qm31_to_felt = qm31.pack_into_felt(); + assert_eq!(felt_to_qm31, qm31); - assert_eq!(qm31_to_felt, Felt::from(value as u64 % STWO_PRIME)); + let qm31 = QM31Felt::from_raw([u64::MAX, u64::MAX, 0, 0]); + let felt: Felt = qm31.try_into().unwrap(); + let felt_to_qm31 = QM31Felt::try_from(felt).unwrap(); - let felt = Felt::from(u64::MAX); - let qm31: QM31Felt = felt.try_into().unwrap(); - let qm31_to_felt = qm31.pack_into_felt(); - dbg!(felt.to_le_digits()); + assert_eq!(felt_to_qm31, qm31); - assert_eq!(qm31_to_felt, Felt::from(u64_max_reduced)); + let qm31 = QM31Felt::from_raw([u64::MAX, u64::MAX, u64::MAX, 0]); + let felt: Felt = qm31.try_into().unwrap(); + let felt_to_qm31 = QM31Felt::try_from(felt).unwrap(); - let felt = Felt::from(u128::MAX); - let qm31: QM31Felt = felt.try_into().unwrap(); - let qm31_to_felt = qm31.pack_into_felt(); + assert_eq!(felt_to_qm31, qm31); - let mut bytes = [0u8; 32]; - let bytes_part1 = - ((u64_max_reduced) as u128 + (((u64_max_reduced) as u128) << 36)).to_le_bytes(); - bytes[0..9].copy_from_slice(&bytes_part1[0..9]); + let qm31 = QM31Felt::from_raw([u64::MAX, u64::MAX, u64::MAX, u64::MAX]); + let felt: Felt = qm31.try_into().unwrap(); + let felt_to_qm31 = QM31Felt::try_from(felt).unwrap(); - assert_eq!(qm31_to_felt, Felt::from_bytes_le(&bytes)); + assert_eq!(felt_to_qm31, qm31); } #[test] From 0a7e10548a3ec154e7ecc4c992fdfe4e633d4527 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Tue, 19 Aug 2025 09:13:30 -0300 Subject: [PATCH 20/44] rename to QM31, make pack_to_felt private, don't add a new line on error --- crates/starknet-types-core/src/felt/qm31.rs | 118 ++++++++++---------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index d6e6ae9..d5b6810 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -20,33 +20,33 @@ impl std::error::Error for QM31Error {} impl fmt::Display for QM31Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - QM31Error::UnreducedFelt(felt) => writeln!( + QM31Error::UnreducedFelt(felt) => write!( f, "Number is not a packing of a QM31 in reduced form: {felt})" ), - QM31Error::FeltTooBig(felt) => writeln!( + QM31Error::FeltTooBig(felt) => write!( f, "Number used as QM31 since it's more than 144 bits long: {felt}" ), - QM31Error::InvalidInversion => writeln!(f, "Attempt to invert a qm31 equal to zero"), + QM31Error::InvalidInversion => write!(f, "Attempt to invert a qm31 equal to zero"), } } } #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct QM31Felt([u64; 4]); +pub struct QM31([u64; 4]); -impl QM31Felt { - /// [QM31Felt] constant that's equal to 0. +impl QM31 { + /// [QM31] constant that's equal to 0. pub const ZERO: Self = Self([0, 0, 0, 0]); pub fn as_raw(&self) -> [u64; 4] { self.0 } - /// Create a [QM31Felt] from the raw internal representation. Reduces four u64 coordinates so that they fit in 144 bits. - pub fn from_raw(coordinates: [u64; 4]) -> QM31Felt { + /// Create a [QM31] from the raw internal representation. Reduces four u64 coordinates so that they fit in 144 bits. + pub fn from_raw(coordinates: [u64; 4]) -> QM31 { Self([ coordinates[0] % STWO_PRIME, coordinates[1] % STWO_PRIME, @@ -55,8 +55,8 @@ impl QM31Felt { ]) } - /// Packs the [QM31Felt] coordinates into a Felt. - pub fn pack_into_felt(&self) -> Felt { + /// Packs the [QM31] coordinates into a Felt. + fn pack_into_felt(&self) -> Felt { let coordinates = self.0; let bytes_part1 = (coordinates[0] as u128 + ((coordinates[1] as u128) << 36)).to_le_bytes(); @@ -68,8 +68,8 @@ impl QM31Felt { Felt::from_bytes_le(&result_bytes) } - /// Computes the addition of two [QM31Felt] elements in reduced form. - pub fn add(&self, rhs: &QM31Felt) -> QM31Felt { + /// Computes the addition of two [QM31] elements in reduced form. + pub fn add(&self, rhs: &QM31) -> QM31 { let coordinates1 = self.as_raw(); let coordinates2 = rhs.as_raw(); let result_unreduced_coordinates = [ @@ -81,8 +81,8 @@ impl QM31Felt { Self::from_raw(result_unreduced_coordinates) } - /// Computes the negative of a [QM31Felt] element in reduced form. - pub fn neg(&self) -> QM31Felt { + /// Computes the negative of a [QM31] element in reduced form. + pub fn neg(&self) -> QM31 { let coordinates = self.as_raw(); Self::from_raw([ STWO_PRIME - coordinates[0], @@ -92,8 +92,8 @@ impl QM31Felt { ]) } - /// Computes the subtraction of two [QM31Felt] elements in reduced form. - pub fn sub(&self, rhs: &QM31Felt) -> QM31Felt { + /// Computes the subtraction of two [QM31] elements in reduced form. + pub fn sub(&self, rhs: &QM31) -> QM31 { let coordinates1 = self.as_raw(); let coordinates2 = rhs.as_raw(); let result_unreduced_coordinates = [ @@ -105,8 +105,8 @@ impl QM31Felt { Self::from_raw(result_unreduced_coordinates) } - /// Computes the multiplication of two [QM31Felt] elements in reduced form. - pub fn mul(&self, rhs: &QM31Felt) -> QM31Felt { + /// Computes the multiplication of two [QM31] elements in reduced form. + pub fn mul(&self, rhs: &QM31) -> QM31 { let coordinates1_u64 = self.as_raw(); let coordinates2_u64 = rhs.as_raw(); let coordinates1 = coordinates1_u64.map(u128::from); @@ -160,9 +160,9 @@ impl QM31Felt { u } - /// Computes the inverse of a [QM31Felt] element in reduced form. + /// Computes the inverse of a [QM31] element in reduced form. /// Returns an error if the operand is equal to zero. - pub fn inverse(&self) -> Result { + pub fn inverse(&self) -> Result { if *self == Self::ZERO { return Err(QM31Error::InvalidInversion); } @@ -201,9 +201,9 @@ impl QM31Felt { ])) } - /// Computes the division of two [QM31Felt] elements in reduced form. Returns an error + /// Computes the division of two [QM31] elements in reduced form. Returns an error /// if the rhs value is equal to zero. - pub fn div(&self, rhs: &QM31Felt) -> Result { + pub fn div(&self, rhs: &QM31) -> Result { let rhs_inv = rhs.inverse()?; Ok(self.mul(&rhs_inv)) } @@ -213,26 +213,26 @@ impl QM31Felt { } } -impl From<&QM31Felt> for Felt { - fn from(value: &QM31Felt) -> Self { +impl From<&QM31> for Felt { + fn from(value: &QM31) -> Self { value.pack_into_felt() } } -impl From for Felt { - fn from(value: QM31Felt) -> Self { +impl From for Felt { + fn from(value: QM31) -> Self { value.pack_into_felt() } } -impl TryFrom for QM31Felt { +impl TryFrom for QM31 { type Error = QM31Error; fn try_from(value: Felt) -> Result { let limbs = value.to_le_digits(); // Check value fits in 144 bits. This check is only done here - // because we are trying to convert a Felt into a QM31Felt. This + // because we are trying to convert a Felt into a QM31. This // Felt should represent a packed QM31 which is at most 144 bits long. if limbs[3] != 0 || limbs[2] >= 1 << 16 { return Err(QM31Error::FeltTooBig(value)); @@ -256,14 +256,14 @@ impl TryFrom for QM31Felt { } } -impl TryFrom<&Felt> for QM31Felt { +impl TryFrom<&Felt> for QM31 { type Error = QM31Error; fn try_from(value: &Felt) -> Result { let limbs = value.to_le_digits(); // Check value fits in 144 bits. This check is only done here - // because we are trying to convert a Felt into a QM31Felt. This + // because we are trying to convert a Felt into a QM31. This // Felt should represent a packed QM31 which is at most 144 bits long. if limbs[3] != 0 || limbs[2] >= 1 << 16 { return Err(QM31Error::FeltTooBig(*value)); @@ -298,38 +298,38 @@ mod test { }; use crate::felt::{ - qm31::{QM31Error, QM31Felt, STWO_PRIME}, + qm31::{QM31Error, QM31, STWO_PRIME}, Felt, }; #[test] fn qm31_to_felt() { - let coordinates = QM31Felt::from_raw([1, 2, 3, 4]); + let coordinates = QM31::from_raw([1, 2, 3, 4]); let packed_coordinates = Felt::from(coordinates); - let unpacked_coordinates = QM31Felt::try_from(packed_coordinates).unwrap(); + let unpacked_coordinates = QM31::try_from(packed_coordinates).unwrap(); assert_eq!(coordinates, unpacked_coordinates); - let qm31 = QM31Felt::from_raw([u64::MAX, 0, 0, 0]); + let qm31 = QM31::from_raw([u64::MAX, 0, 0, 0]); let felt: Felt = qm31.try_into().unwrap(); - let felt_to_qm31 = QM31Felt::try_from(felt).unwrap(); + let felt_to_qm31 = QM31::try_from(felt).unwrap(); assert_eq!(felt_to_qm31, qm31); - let qm31 = QM31Felt::from_raw([u64::MAX, u64::MAX, 0, 0]); + let qm31 = QM31::from_raw([u64::MAX, u64::MAX, 0, 0]); let felt: Felt = qm31.try_into().unwrap(); - let felt_to_qm31 = QM31Felt::try_from(felt).unwrap(); + let felt_to_qm31 = QM31::try_from(felt).unwrap(); assert_eq!(felt_to_qm31, qm31); - let qm31 = QM31Felt::from_raw([u64::MAX, u64::MAX, u64::MAX, 0]); + let qm31 = QM31::from_raw([u64::MAX, u64::MAX, u64::MAX, 0]); let felt: Felt = qm31.try_into().unwrap(); - let felt_to_qm31 = QM31Felt::try_from(felt).unwrap(); + let felt_to_qm31 = QM31::try_from(felt).unwrap(); assert_eq!(felt_to_qm31, qm31); - let qm31 = QM31Felt::from_raw([u64::MAX, u64::MAX, u64::MAX, u64::MAX]); + let qm31 = QM31::from_raw([u64::MAX, u64::MAX, u64::MAX, u64::MAX]); let felt: Felt = qm31.try_into().unwrap(); - let felt_to_qm31 = QM31Felt::try_from(felt).unwrap(); + let felt_to_qm31 = QM31::try_from(felt).unwrap(); assert_eq!(felt_to_qm31, qm31); } @@ -339,7 +339,7 @@ mod test { let mut felt_bytes = [0u8; 32]; felt_bytes[18] = 1; let felt = Felt::from_bytes_le(&felt_bytes); - let qm31: Result = felt.try_into(); + let qm31: Result = felt.try_into(); assert!(matches!( qm31, Err(QM31Error::FeltTooBig(bx)) if bx == felt @@ -350,8 +350,8 @@ mod test { fn test_qm31_packed_reduced_add() { let x_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; let y_coordinates = [1234567890, 1414213562, 1732050807, 1618033988]; - let x = QM31Felt::from_raw(x_coordinates); - let y = QM31Felt::from_raw(y_coordinates); + let x = QM31::from_raw(x_coordinates); + let y = QM31::from_raw(y_coordinates); let res = x.add(&y); let res_coordinates = res.as_raw(); assert_eq!( @@ -368,7 +368,7 @@ mod test { #[test] fn test_qm31_packed_reduced_neg() { let x_coordinates = [1749652895, 834624081, 1930174752, 2063872165]; - let x = QM31Felt::from_raw(x_coordinates); + let x = QM31::from_raw(x_coordinates); let res = x.neg(); let res_coordinates = res.as_raw(); assert_eq!( @@ -391,8 +391,8 @@ mod test { (1234567890 + 1618033988) % STWO_PRIME, ]; let y_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; - let x = QM31Felt::from_raw(x_coordinates); - let y = QM31Felt::from_raw(y_coordinates); + let x = QM31::from_raw(x_coordinates); + let y = QM31::from_raw(y_coordinates); let res = x.sub(&y); let res_coordinates = res.as_raw(); assert_eq!( @@ -405,8 +405,8 @@ mod test { fn test_qm31_packed_reduced_mul() { let x_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; let y_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; - let x = QM31Felt::from_raw(x_coordinates); - let y = QM31Felt::from_raw(y_coordinates); + let x = QM31::from_raw(x_coordinates); + let y = QM31::from_raw(y_coordinates); let res = x.mul(&y); let res_coordinates = res.as_raw(); assert_eq!( @@ -418,19 +418,19 @@ mod test { #[test] fn test_qm31_packed_reduced_inv() { let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; - let x = QM31Felt::from_raw(x_coordinates); + let x = QM31::from_raw(x_coordinates); let res = x.inverse().unwrap(); let expected = Felt::from(1).try_into().unwrap(); assert_eq!(x.mul(&res), expected); let x_coordinates = [1, 2, 3, 4]; - let x = QM31Felt::from_raw(x_coordinates); + let x = QM31::from_raw(x_coordinates); let res = x.inverse().unwrap(); let expected = Felt::from(1).try_into().unwrap(); assert_eq!(x.mul(&res), expected); let x_coordinates = [1749652895, 834624081, 1930174752, 2063872165]; - let x = QM31Felt::from_raw(x_coordinates); + let x = QM31::from_raw(x_coordinates); let res = x.inverse().unwrap(); let expected = Felt::from(1).try_into().unwrap(); assert_eq!(x.mul(&res), expected); @@ -447,10 +447,10 @@ mod test { .prop_filter("All configs cant be 0", |arr| !arr.iter().all(|x| *x == 0)) ) { - let x = QM31Felt::from_raw(x_coordinates); + let x = QM31::from_raw(x_coordinates); let res = x.inverse().unwrap(); // Expect 1_felt252 - let expected = QM31Felt::from_raw([1,0,0,0]); + let expected = QM31::from_raw([1,0,0,0]); assert_eq!(x.mul(&res), expected); } @@ -461,10 +461,10 @@ mod test { |arr| !arr.iter().all(|x| *x == 0)) .no_shrink() ) { - let x = QM31Felt::from_raw(x_coordinates); + let x = QM31::from_raw(x_coordinates); let res = x.inverse().unwrap(); // Expect 1_felt252 - let expected = QM31Felt::from_raw([1,0,0,0]); + let expected = QM31::from_raw([1,0,0,0]); assert_eq!(x.mul(&res), expected); } } @@ -474,9 +474,9 @@ mod test { let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; let y_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; let xy_coordinates = [947980980, 1510986506, 623360030, 1260310989]; - let x = QM31Felt::from_raw(x_coordinates); - let y = QM31Felt::from_raw(y_coordinates); - let xy = QM31Felt::from_raw(xy_coordinates); + let x = QM31::from_raw(x_coordinates); + let y = QM31::from_raw(y_coordinates); + let xy = QM31::from_raw(xy_coordinates); let res = xy.div(&y).unwrap(); assert_eq!(res, x); From 5bca78caf2ef77032db715a55e0a2ca06f668922 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Tue, 19 Aug 2025 09:35:35 -0300 Subject: [PATCH 21/44] add doc to QM31 and better method names --- crates/starknet-types-core/src/felt/qm31.rs | 86 +++++++++++---------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index d5b6810..178b63e 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -33,6 +33,8 @@ impl fmt::Display for QM31Error { } } +/// Definition of a Quad M31 in its reduced form. The internal representation +/// is composed by the coordinates of the QM31, following a little endian ordering. #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct QM31([u64; 4]); @@ -41,12 +43,12 @@ impl QM31 { /// [QM31] constant that's equal to 0. pub const ZERO: Self = Self([0, 0, 0, 0]); - pub fn as_raw(&self) -> [u64; 4] { + pub fn inner(&self) -> [u64; 4] { self.0 } - /// Create a [QM31] from the raw internal representation. Reduces four u64 coordinates so that they fit in 144 bits. - pub fn from_raw(coordinates: [u64; 4]) -> QM31 { + /// Create a [QM31] in its reduced form from the given four u64 coordinates. + pub fn from_coordinates(coordinates: [u64; 4]) -> QM31 { Self([ coordinates[0] % STWO_PRIME, coordinates[1] % STWO_PRIME, @@ -70,21 +72,21 @@ impl QM31 { /// Computes the addition of two [QM31] elements in reduced form. pub fn add(&self, rhs: &QM31) -> QM31 { - let coordinates1 = self.as_raw(); - let coordinates2 = rhs.as_raw(); + let coordinates1 = self.inner(); + let coordinates2 = rhs.inner(); let result_unreduced_coordinates = [ coordinates1[0] + coordinates2[0], coordinates1[1] + coordinates2[1], coordinates1[2] + coordinates2[2], coordinates1[3] + coordinates2[3], ]; - Self::from_raw(result_unreduced_coordinates) + Self::from_coordinates(result_unreduced_coordinates) } /// Computes the negative of a [QM31] element in reduced form. pub fn neg(&self) -> QM31 { - let coordinates = self.as_raw(); - Self::from_raw([ + let coordinates = self.inner(); + Self::from_coordinates([ STWO_PRIME - coordinates[0], STWO_PRIME - coordinates[1], STWO_PRIME - coordinates[2], @@ -94,21 +96,21 @@ impl QM31 { /// Computes the subtraction of two [QM31] elements in reduced form. pub fn sub(&self, rhs: &QM31) -> QM31 { - let coordinates1 = self.as_raw(); - let coordinates2 = rhs.as_raw(); + let coordinates1 = self.inner(); + let coordinates2 = rhs.inner(); let result_unreduced_coordinates = [ STWO_PRIME + coordinates1[0] - coordinates2[0], STWO_PRIME + coordinates1[1] - coordinates2[1], STWO_PRIME + coordinates1[2] - coordinates2[2], STWO_PRIME + coordinates1[3] - coordinates2[3], ]; - Self::from_raw(result_unreduced_coordinates) + Self::from_coordinates(result_unreduced_coordinates) } /// Computes the multiplication of two [QM31] elements in reduced form. pub fn mul(&self, rhs: &QM31) -> QM31 { - let coordinates1_u64 = self.as_raw(); - let coordinates2_u64 = rhs.as_raw(); + let coordinates1_u64 = self.inner(); + let coordinates2_u64 = rhs.inner(); let coordinates1 = coordinates1_u64.map(u128::from); let coordinates2 = coordinates2_u64.map(u128::from); @@ -136,7 +138,7 @@ impl QM31 { + coordinates1_u64[2] * coordinates2_u64[1] + coordinates1_u64[3] * coordinates2_u64[0], ]; - Self::from_raw(result_coordinates) + Self::from_coordinates(result_coordinates) } /// Computes the inverse in the M31 field using Fermat's little theorem, i.e., returns @@ -167,7 +169,7 @@ impl QM31 { return Err(QM31Error::InvalidInversion); } - let coordinates = self.as_raw(); + let coordinates = self.inner(); let b2_r = (coordinates[2] * coordinates[2] + STWO_PRIME * STWO_PRIME - coordinates[3] * coordinates[3]) @@ -189,7 +191,7 @@ impl QM31 { let denom_inverse_r = (denom_r * denom_norm_inverse_squared) % STWO_PRIME; let denom_inverse_i = ((STWO_PRIME - denom_i) * denom_norm_inverse_squared) % STWO_PRIME; - Ok(Self::from_raw([ + Ok(Self::from_coordinates([ coordinates[0] * denom_inverse_r + STWO_PRIME * STWO_PRIME - coordinates[1] * denom_inverse_i, coordinates[0] * denom_inverse_i + coordinates[1] * denom_inverse_r, @@ -304,30 +306,30 @@ mod test { #[test] fn qm31_to_felt() { - let coordinates = QM31::from_raw([1, 2, 3, 4]); + let coordinates = QM31::from_coordinates([1, 2, 3, 4]); let packed_coordinates = Felt::from(coordinates); let unpacked_coordinates = QM31::try_from(packed_coordinates).unwrap(); assert_eq!(coordinates, unpacked_coordinates); - let qm31 = QM31::from_raw([u64::MAX, 0, 0, 0]); + let qm31 = QM31::from_coordinates([u64::MAX, 0, 0, 0]); let felt: Felt = qm31.try_into().unwrap(); let felt_to_qm31 = QM31::try_from(felt).unwrap(); assert_eq!(felt_to_qm31, qm31); - let qm31 = QM31::from_raw([u64::MAX, u64::MAX, 0, 0]); + let qm31 = QM31::from_coordinates([u64::MAX, u64::MAX, 0, 0]); let felt: Felt = qm31.try_into().unwrap(); let felt_to_qm31 = QM31::try_from(felt).unwrap(); assert_eq!(felt_to_qm31, qm31); - let qm31 = QM31::from_raw([u64::MAX, u64::MAX, u64::MAX, 0]); + let qm31 = QM31::from_coordinates([u64::MAX, u64::MAX, u64::MAX, 0]); let felt: Felt = qm31.try_into().unwrap(); let felt_to_qm31 = QM31::try_from(felt).unwrap(); assert_eq!(felt_to_qm31, qm31); - let qm31 = QM31::from_raw([u64::MAX, u64::MAX, u64::MAX, u64::MAX]); + let qm31 = QM31::from_coordinates([u64::MAX, u64::MAX, u64::MAX, u64::MAX]); let felt: Felt = qm31.try_into().unwrap(); let felt_to_qm31 = QM31::try_from(felt).unwrap(); @@ -350,10 +352,10 @@ mod test { fn test_qm31_packed_reduced_add() { let x_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; let y_coordinates = [1234567890, 1414213562, 1732050807, 1618033988]; - let x = QM31::from_raw(x_coordinates); - let y = QM31::from_raw(y_coordinates); + let x = QM31::from_coordinates(x_coordinates); + let y = QM31::from_coordinates(y_coordinates); let res = x.add(&y); - let res_coordinates = res.as_raw(); + let res_coordinates = res.inner(); assert_eq!( res_coordinates, [ @@ -368,9 +370,9 @@ mod test { #[test] fn test_qm31_packed_reduced_neg() { let x_coordinates = [1749652895, 834624081, 1930174752, 2063872165]; - let x = QM31::from_raw(x_coordinates); + let x = QM31::from_coordinates(x_coordinates); let res = x.neg(); - let res_coordinates = res.as_raw(); + let res_coordinates = res.inner(); assert_eq!( res_coordinates, [ @@ -391,10 +393,10 @@ mod test { (1234567890 + 1618033988) % STWO_PRIME, ]; let y_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; - let x = QM31::from_raw(x_coordinates); - let y = QM31::from_raw(y_coordinates); + let x = QM31::from_coordinates(x_coordinates); + let y = QM31::from_coordinates(y_coordinates); let res = x.sub(&y); - let res_coordinates = res.as_raw(); + let res_coordinates = res.inner(); assert_eq!( res_coordinates, [1234567890, 1414213562, 1732050807, 1618033988] @@ -405,10 +407,10 @@ mod test { fn test_qm31_packed_reduced_mul() { let x_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; let y_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; - let x = QM31::from_raw(x_coordinates); - let y = QM31::from_raw(y_coordinates); + let x = QM31::from_coordinates(x_coordinates); + let y = QM31::from_coordinates(y_coordinates); let res = x.mul(&y); - let res_coordinates = res.as_raw(); + let res_coordinates = res.inner(); assert_eq!( res_coordinates, [947980980, 1510986506, 623360030, 1260310989] @@ -418,19 +420,19 @@ mod test { #[test] fn test_qm31_packed_reduced_inv() { let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; - let x = QM31::from_raw(x_coordinates); + let x = QM31::from_coordinates(x_coordinates); let res = x.inverse().unwrap(); let expected = Felt::from(1).try_into().unwrap(); assert_eq!(x.mul(&res), expected); let x_coordinates = [1, 2, 3, 4]; - let x = QM31::from_raw(x_coordinates); + let x = QM31::from_coordinates(x_coordinates); let res = x.inverse().unwrap(); let expected = Felt::from(1).try_into().unwrap(); assert_eq!(x.mul(&res), expected); let x_coordinates = [1749652895, 834624081, 1930174752, 2063872165]; - let x = QM31::from_raw(x_coordinates); + let x = QM31::from_coordinates(x_coordinates); let res = x.inverse().unwrap(); let expected = Felt::from(1).try_into().unwrap(); assert_eq!(x.mul(&res), expected); @@ -447,10 +449,10 @@ mod test { .prop_filter("All configs cant be 0", |arr| !arr.iter().all(|x| *x == 0)) ) { - let x = QM31::from_raw(x_coordinates); + let x = QM31::from_coordinates(x_coordinates); let res = x.inverse().unwrap(); // Expect 1_felt252 - let expected = QM31::from_raw([1,0,0,0]); + let expected = QM31::from_coordinates([1,0,0,0]); assert_eq!(x.mul(&res), expected); } @@ -461,10 +463,10 @@ mod test { |arr| !arr.iter().all(|x| *x == 0)) .no_shrink() ) { - let x = QM31::from_raw(x_coordinates); + let x = QM31::from_coordinates(x_coordinates); let res = x.inverse().unwrap(); // Expect 1_felt252 - let expected = QM31::from_raw([1,0,0,0]); + let expected = QM31::from_coordinates([1,0,0,0]); assert_eq!(x.mul(&res), expected); } } @@ -474,9 +476,9 @@ mod test { let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; let y_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; let xy_coordinates = [947980980, 1510986506, 623360030, 1260310989]; - let x = QM31::from_raw(x_coordinates); - let y = QM31::from_raw(y_coordinates); - let xy = QM31::from_raw(xy_coordinates); + let x = QM31::from_coordinates(x_coordinates); + let y = QM31::from_coordinates(y_coordinates); + let xy = QM31::from_coordinates(xy_coordinates); let res = xy.div(&y).unwrap(); assert_eq!(res, x); From c29db22bb78e83eae43f3546c8d1eca8ebacbde6 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Tue, 19 Aug 2025 11:21:17 -0300 Subject: [PATCH 22/44] fmt --- crates/starknet-types-core/src/felt/qm31.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index 178b63e..fcff333 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -33,7 +33,7 @@ impl fmt::Display for QM31Error { } } -/// Definition of a Quad M31 in its reduced form. The internal representation +/// Definition of a Quad M31 in its reduced form. The internal representation /// is composed by the coordinates of the QM31, following a little endian ordering. #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -47,7 +47,7 @@ impl QM31 { self.0 } - /// Create a [QM31] in its reduced form from the given four u64 coordinates. + /// Creates a [QM31] in its reduced form from four u64 coordinates. pub fn from_coordinates(coordinates: [u64; 4]) -> QM31 { Self([ coordinates[0] % STWO_PRIME, From 9de3ac978c157a26d9fc93dbe8ff17f3bab1e994 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Tue, 19 Aug 2025 18:08:18 -0300 Subject: [PATCH 23/44] lowercase error massages + improve documentation --- crates/starknet-types-core/src/felt/qm31.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index fcff333..7845ad6 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -22,19 +22,20 @@ impl fmt::Display for QM31Error { match self { QM31Error::UnreducedFelt(felt) => write!( f, - "Number is not a packing of a QM31 in reduced form: {felt})" + "number is not a packing of a QM31 in reduced form: {felt})" ), QM31Error::FeltTooBig(felt) => write!( f, - "Number used as QM31 since it's more than 144 bits long: {felt}" + "number used as QM31 since it's more than 144 bits long: {felt}" ), - QM31Error::InvalidInversion => write!(f, "Attempt to invert a qm31 equal to zero"), + QM31Error::InvalidInversion => write!(f, "attempt to invert a qm31 equal to zero"), } } } -/// Definition of a Quad M31 in its reduced form. The internal representation -/// is composed by the coordinates of the QM31, following a little endian ordering. +/// Definition of a Quad M31 in its reduced form. +/// +/// The internal representation is composed of the coordinates of the QM31, following a little-endian ordering. #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct QM31([u64; 4]); @@ -141,8 +142,9 @@ impl QM31 { Self::from_coordinates(result_coordinates) } - /// Computes the inverse in the M31 field using Fermat's little theorem, i.e., returns - /// `v^(STWO_PRIME-2) modulo STWO_PRIME`, which is the inverse of v unless v % STWO_PRIME == 0. + /// Computes the inverse in the M31 field using Fermat's little theorem. + /// + /// Returns `v^(STWO_PRIME-2) modulo STWO_PRIME`, which is the inverse of v unless v % STWO_PRIME == 0. fn m31_inverse(v: u64) -> u64 { let t0 = (Self::sqn(v, 2) * v) % STWO_PRIME; let t1 = (Self::sqn(t0, 1) * t0) % STWO_PRIME; From 867423074ab980ab524e476b7d44d691e182572b Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Tue, 19 Aug 2025 18:41:33 -0300 Subject: [PATCH 24/44] derive Copy and Clone for QM31Error, add more docs --- crates/starknet-types-core/src/felt/qm31.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index 7845ad6..00c4825 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -7,7 +7,7 @@ const STWO_PRIME_U128: u128 = STWO_PRIME as u128; const MASK_36: u64 = (1 << 36) - 1; const MASK_8: u64 = (1 << 8) - 1; -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum QM31Error { UnreducedFelt(Felt), FeltTooBig(Felt), @@ -33,9 +33,10 @@ impl fmt::Display for QM31Error { } } -/// Definition of a Quad M31 in its reduced form. +/// Definition of a Quadruple Merseene 31 in its reduced form. /// -/// The internal representation is composed of the coordinates of the QM31, following a little-endian ordering. +/// The internal representation is composed of 4 coordinates, following a little-endian ordering. +/// Each coordinate can be represented by 36 bits. #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct QM31([u64; 4]); @@ -59,6 +60,9 @@ impl QM31 { } /// Packs the [QM31] coordinates into a Felt. + /// + /// A QM31 is composed of 4 coordinates each of which can be represented with 36 bits, meaing + /// it can be store in a Felt252. This method packs a given QM31 and stores in the first 144 bits of a Felt252. fn pack_into_felt(&self) -> Felt { let coordinates = self.0; @@ -143,7 +147,7 @@ impl QM31 { } /// Computes the inverse in the M31 field using Fermat's little theorem. - /// + /// /// Returns `v^(STWO_PRIME-2) modulo STWO_PRIME`, which is the inverse of v unless v % STWO_PRIME == 0. fn m31_inverse(v: u64) -> u64 { let t0 = (Self::sqn(v, 2) * v) % STWO_PRIME; @@ -165,6 +169,7 @@ impl QM31 { } /// Computes the inverse of a [QM31] element in reduced form. + /// /// Returns an error if the operand is equal to zero. pub fn inverse(&self) -> Result { if *self == Self::ZERO { @@ -205,8 +210,9 @@ impl QM31 { ])) } - /// Computes the division of two [QM31] elements in reduced form. Returns an error - /// if the rhs value is equal to zero. + /// Computes the division of two [QM31] elements in reduced form. + /// + /// Returns an error if the rhs value is equal to zero. pub fn div(&self, rhs: &QM31) -> Result { let rhs_inv = rhs.inverse()?; Ok(self.mul(&rhs_inv)) From daab73d97dec5d32516e0e94f41b353c7bfea1a6 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Tue, 19 Aug 2025 19:00:03 -0300 Subject: [PATCH 25/44] add unpack_from_felt to reduce duplication --- crates/starknet-types-core/src/felt/qm31.rs | 82 ++++++++------------- 1 file changed, 32 insertions(+), 50 deletions(-) diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/felt/qm31.rs index 00c4825..e01af04 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/felt/qm31.rs @@ -61,8 +61,8 @@ impl QM31 { /// Packs the [QM31] coordinates into a Felt. /// - /// A QM31 is composed of 4 coordinates each of which can be represented with 36 bits, meaing - /// it can be store in a Felt252. This method packs a given QM31 and stores in the first 144 bits of a Felt252. + /// A QM31 is composed of 4 coordinates each of which can be represented with 36 bits, meaning + /// it can be store in a Felt252. This method packs a given QM31 and stores it in the first 144 bits of a Felt252. fn pack_into_felt(&self) -> Felt { let coordinates = self.0; @@ -75,6 +75,34 @@ impl QM31 { Felt::from_bytes_le(&result_bytes) } + /// Unpacks the reduced coordinates stored in [Felt] and creates a [QM31]. + fn unpack_from_felt(felt: &Felt) -> Result { + let limbs = felt.to_le_digits(); + + // Check value fits in 144 bits. This check is only done here + // because we are trying to convert a Felt into a QM31. This + // Felt should represent a packed QM31 which is at most 144 bits long. + if limbs[3] != 0 || limbs[2] >= 1 << 16 { + return Err(QM31Error::FeltTooBig(*felt)); + } + + // Check if the coordinates were reduced before. + let coordinates = [ + (limbs[0] & MASK_36), + ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), + ((limbs[1] >> 8) & MASK_36), + ((limbs[1] >> 44) + (limbs[2] << 20)), + ]; + + for x in coordinates.iter() { + if *x >= STWO_PRIME { + return Err(QM31Error::UnreducedFelt(*felt)); + } + } + + Ok(QM31(coordinates)) + } + /// Computes the addition of two [QM31] elements in reduced form. pub fn add(&self, rhs: &QM31) -> QM31 { let coordinates1 = self.inner(); @@ -239,30 +267,7 @@ impl TryFrom for QM31 { type Error = QM31Error; fn try_from(value: Felt) -> Result { - let limbs = value.to_le_digits(); - - // Check value fits in 144 bits. This check is only done here - // because we are trying to convert a Felt into a QM31. This - // Felt should represent a packed QM31 which is at most 144 bits long. - if limbs[3] != 0 || limbs[2] >= 1 << 16 { - return Err(QM31Error::FeltTooBig(value)); - } - - let coordinates = [ - (limbs[0] & MASK_36), - ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), - ((limbs[1] >> 8) & MASK_36), - ((limbs[1] >> 44) + (limbs[2] << 20)), - ]; - - // Check if the coordinates were reduced before. - for x in coordinates.iter() { - if *x >= STWO_PRIME { - return Err(QM31Error::UnreducedFelt(value)); - } - } - - Ok(Self(coordinates)) + Self::unpack_from_felt(&value) } } @@ -270,30 +275,7 @@ impl TryFrom<&Felt> for QM31 { type Error = QM31Error; fn try_from(value: &Felt) -> Result { - let limbs = value.to_le_digits(); - - // Check value fits in 144 bits. This check is only done here - // because we are trying to convert a Felt into a QM31. This - // Felt should represent a packed QM31 which is at most 144 bits long. - if limbs[3] != 0 || limbs[2] >= 1 << 16 { - return Err(QM31Error::FeltTooBig(*value)); - } - - // Check if the coordinates were reduced before. - let coordinates = [ - (limbs[0] & MASK_36), - ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), - ((limbs[1] >> 8) & MASK_36), - ((limbs[1] >> 44) + (limbs[2] << 20)), - ]; - - for x in coordinates.iter() { - if *x >= STWO_PRIME { - return Err(QM31Error::UnreducedFelt(*value)); - } - } - - Ok(Self(coordinates)) + Self::unpack_from_felt(value) } } From 99ad0e077835117c11aa7e5a4afb60c182a9d9a8 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Wed, 20 Aug 2025 09:02:56 -0300 Subject: [PATCH 26/44] move qm31 implementation to its own module --- crates/starknet-types-core/src/felt/mod.rs | 1 - crates/starknet-types-core/src/lib.rs | 2 ++ crates/starknet-types-core/src/{felt/qm31.rs => qm31/mod.rs} | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) rename crates/starknet-types-core/src/{felt/qm31.rs => qm31/mod.rs} (99%) diff --git a/crates/starknet-types-core/src/felt/mod.rs b/crates/starknet-types-core/src/felt/mod.rs index 2ffe80c..bdac536 100644 --- a/crates/starknet-types-core/src/felt/mod.rs +++ b/crates/starknet-types-core/src/felt/mod.rs @@ -14,7 +14,6 @@ mod parity_scale_codec; #[cfg(feature = "prime-bigint")] mod prime_bigint; mod primitive_conversions; -mod qm31; #[cfg(feature = "serde")] mod serde; #[cfg(feature = "zeroize")] diff --git a/crates/starknet-types-core/src/lib.rs b/crates/starknet-types-core/src/lib.rs index 4245ab5..dc9d47e 100644 --- a/crates/starknet-types-core/src/lib.rs +++ b/crates/starknet-types-core/src/lib.rs @@ -7,6 +7,8 @@ pub mod hash; pub mod felt; +pub mod qm31; + #[cfg(any(feature = "std", feature = "alloc"))] pub mod short_string; pub mod u256; diff --git a/crates/starknet-types-core/src/felt/qm31.rs b/crates/starknet-types-core/src/qm31/mod.rs similarity index 99% rename from crates/starknet-types-core/src/felt/qm31.rs rename to crates/starknet-types-core/src/qm31/mod.rs index e01af04..277cc00 100644 --- a/crates/starknet-types-core/src/felt/qm31.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -289,9 +289,9 @@ mod test { prop_oneof, proptest, }; - use crate::felt::{ + use crate::{ + felt::Felt, qm31::{QM31Error, QM31, STWO_PRIME}, - Felt, }; #[test] From 3ec950124176309aff90d024f8071e04fc8a377a Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Wed, 20 Aug 2025 09:54:29 -0300 Subject: [PATCH 27/44] correct some docs --- crates/starknet-types-core/src/qm31/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index 277cc00..5a35acd 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -33,9 +33,9 @@ impl fmt::Display for QM31Error { } } -/// Definition of a Quadruple Merseene 31 in its reduced form. +/// Definition of a Quadruple Merseene 31. /// -/// The internal representation is composed of 4 coordinates, following a little-endian ordering. +/// The internal representation is composed of 4 coordinates, following a big-endian ordering. /// Each coordinate can be represented by 36 bits. #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] From 9c822f2b24585632c3ebc7d1a65557ea6afd8bb1 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Wed, 20 Aug 2025 10:45:55 -0300 Subject: [PATCH 28/44] remove From and TryFrom implementations --- crates/starknet-types-core/src/qm31/mod.rs | 60 ++++++---------------- 1 file changed, 16 insertions(+), 44 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index 5a35acd..e0d6e0b 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -63,7 +63,7 @@ impl QM31 { /// /// A QM31 is composed of 4 coordinates each of which can be represented with 36 bits, meaning /// it can be store in a Felt252. This method packs a given QM31 and stores it in the first 144 bits of a Felt252. - fn pack_into_felt(&self) -> Felt { + pub fn pack_into_felt(&self) -> Felt { let coordinates = self.0; let bytes_part1 = (coordinates[0] as u128 + ((coordinates[1] as u128) << 36)).to_le_bytes(); @@ -76,7 +76,7 @@ impl QM31 { } /// Unpacks the reduced coordinates stored in [Felt] and creates a [QM31]. - fn unpack_from_felt(felt: &Felt) -> Result { + pub fn unpack_from_felt(felt: &Felt) -> Result { let limbs = felt.to_le_digits(); // Check value fits in 144 bits. This check is only done here @@ -251,34 +251,6 @@ impl QM31 { } } -impl From<&QM31> for Felt { - fn from(value: &QM31) -> Self { - value.pack_into_felt() - } -} - -impl From for Felt { - fn from(value: QM31) -> Self { - value.pack_into_felt() - } -} - -impl TryFrom for QM31 { - type Error = QM31Error; - - fn try_from(value: Felt) -> Result { - Self::unpack_from_felt(&value) - } -} - -impl TryFrom<&Felt> for QM31 { - type Error = QM31Error; - - fn try_from(value: &Felt) -> Result { - Self::unpack_from_felt(value) - } -} - #[cfg(test)] mod test { use core::u64; @@ -297,31 +269,31 @@ mod test { #[test] fn qm31_to_felt() { let coordinates = QM31::from_coordinates([1, 2, 3, 4]); - let packed_coordinates = Felt::from(coordinates); - let unpacked_coordinates = QM31::try_from(packed_coordinates).unwrap(); + let packed_coordinates = coordinates.pack_into_felt(); + let unpacked_coordinates = QM31::unpack_from_felt(&packed_coordinates).unwrap(); assert_eq!(coordinates, unpacked_coordinates); let qm31 = QM31::from_coordinates([u64::MAX, 0, 0, 0]); - let felt: Felt = qm31.try_into().unwrap(); - let felt_to_qm31 = QM31::try_from(felt).unwrap(); + let felt: Felt = qm31.pack_into_felt(); + let felt_to_qm31 = QM31::unpack_from_felt(&felt).unwrap(); assert_eq!(felt_to_qm31, qm31); let qm31 = QM31::from_coordinates([u64::MAX, u64::MAX, 0, 0]); - let felt: Felt = qm31.try_into().unwrap(); - let felt_to_qm31 = QM31::try_from(felt).unwrap(); + let felt: Felt = qm31.pack_into_felt(); + let felt_to_qm31 = QM31::unpack_from_felt(&felt).unwrap(); assert_eq!(felt_to_qm31, qm31); let qm31 = QM31::from_coordinates([u64::MAX, u64::MAX, u64::MAX, 0]); - let felt: Felt = qm31.try_into().unwrap(); - let felt_to_qm31 = QM31::try_from(felt).unwrap(); + let felt: Felt = qm31.pack_into_felt(); + let felt_to_qm31 = QM31::unpack_from_felt(&felt).unwrap(); assert_eq!(felt_to_qm31, qm31); let qm31 = QM31::from_coordinates([u64::MAX, u64::MAX, u64::MAX, u64::MAX]); - let felt: Felt = qm31.try_into().unwrap(); - let felt_to_qm31 = QM31::try_from(felt).unwrap(); + let felt: Felt = qm31.pack_into_felt(); + let felt_to_qm31 = QM31::unpack_from_felt(&felt).unwrap(); assert_eq!(felt_to_qm31, qm31); } @@ -331,7 +303,7 @@ mod test { let mut felt_bytes = [0u8; 32]; felt_bytes[18] = 1; let felt = Felt::from_bytes_le(&felt_bytes); - let qm31: Result = felt.try_into(); + let qm31: Result = QM31::unpack_from_felt(&felt); assert!(matches!( qm31, Err(QM31Error::FeltTooBig(bx)) if bx == felt @@ -412,19 +384,19 @@ mod test { let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; let x = QM31::from_coordinates(x_coordinates); let res = x.inverse().unwrap(); - let expected = Felt::from(1).try_into().unwrap(); + let expected = QM31::unpack_from_felt(&Felt::from(1)).unwrap(); assert_eq!(x.mul(&res), expected); let x_coordinates = [1, 2, 3, 4]; let x = QM31::from_coordinates(x_coordinates); let res = x.inverse().unwrap(); - let expected = Felt::from(1).try_into().unwrap(); + let expected = QM31::unpack_from_felt(&Felt::from(1)).unwrap(); assert_eq!(x.mul(&res), expected); let x_coordinates = [1749652895, 834624081, 1930174752, 2063872165]; let x = QM31::from_coordinates(x_coordinates); let res = x.inverse().unwrap(); - let expected = Felt::from(1).try_into().unwrap(); + let expected = QM31::unpack_from_felt(&Felt::from(1)).unwrap(); assert_eq!(x.mul(&res), expected); } From a65f3668cae00e0dabb60a8a8725a530a2993acd Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Wed, 20 Aug 2025 10:55:28 -0300 Subject: [PATCH 29/44] add comment explaining what a coordinate refers to --- crates/starknet-types-core/src/qm31/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index e0d6e0b..eb771da 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -35,8 +35,8 @@ impl fmt::Display for QM31Error { /// Definition of a Quadruple Merseene 31. /// -/// The internal representation is composed of 4 coordinates, following a big-endian ordering. -/// Each coordinate can be represented by 36 bits. +/// The internal representation is composed of 4 limbs, following a big-endian ordering. +/// Each of this limbs can be represented by 36 bits. #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct QM31([u64; 4]); @@ -50,6 +50,8 @@ impl QM31 { } /// Creates a [QM31] in its reduced form from four u64 coordinates. + /// + /// A coordinate refers to a value in the M31 field. pub fn from_coordinates(coordinates: [u64; 4]) -> QM31 { Self([ coordinates[0] % STWO_PRIME, From d8bbdb5c7a2789ca1e060313ff1b9b1dc5396c2e Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Wed, 20 Aug 2025 14:55:59 -0300 Subject: [PATCH 30/44] add docs on the algorithms used and fix typo --- crates/starknet-types-core/src/qm31/mod.rs | 28 ++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index eb771da..c6187f7 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -33,7 +33,7 @@ impl fmt::Display for QM31Error { } } -/// Definition of a Quadruple Merseene 31. +/// Definition of a Quadruple Mersenne 31. /// /// The internal representation is composed of 4 limbs, following a big-endian ordering. /// Each of this limbs can be represented by 36 bits. @@ -50,7 +50,7 @@ impl QM31 { } /// Creates a [QM31] in its reduced form from four u64 coordinates. - /// + /// /// A coordinate refers to a value in the M31 field. pub fn from_coordinates(coordinates: [u64; 4]) -> QM31 { Self([ @@ -106,6 +106,11 @@ impl QM31 { } /// Computes the addition of two [QM31] elements in reduced form. + /// + /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. + /// + /// The algorithm was taken from the following papper: https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf + /// Section 1.1.2. pub fn add(&self, rhs: &QM31) -> QM31 { let coordinates1 = self.inner(); let coordinates2 = rhs.inner(); @@ -119,6 +124,11 @@ impl QM31 { } /// Computes the negative of a [QM31] element in reduced form. + /// + /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. + /// + /// The algorithm was taken from the following papper: https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf + /// Section 1.1.2. pub fn neg(&self) -> QM31 { let coordinates = self.inner(); Self::from_coordinates([ @@ -130,6 +140,11 @@ impl QM31 { } /// Computes the subtraction of two [QM31] elements in reduced form. + /// + /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. + /// + /// The algorithm was taken from the following papper: https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf + /// Section 1.1.2. pub fn sub(&self, rhs: &QM31) -> QM31 { let coordinates1 = self.inner(); let coordinates2 = rhs.inner(); @@ -143,6 +158,8 @@ impl QM31 { } /// Computes the multiplication of two [QM31] elements in reduced form. + /// + /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. pub fn mul(&self, rhs: &QM31) -> QM31 { let coordinates1_u64 = self.inner(); let coordinates2_u64 = rhs.inner(); @@ -190,6 +207,11 @@ impl QM31 { } /// Computes `v^(2^n) modulo STWO_PRIME`. + /// + /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. + /// + /// The algorithm was taken from the following papper: https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf + /// Section 1.1.3. fn sqn(v: u64, n: usize) -> u64 { let mut u = v; for _ in 0..n { @@ -200,6 +222,8 @@ impl QM31 { /// Computes the inverse of a [QM31] element in reduced form. /// + /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. + /// /// Returns an error if the operand is equal to zero. pub fn inverse(&self) -> Result { if *self == Self::ZERO { From dde739b945f3b9c3ffd391559be5ed46aced148d Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Wed, 20 Aug 2025 18:00:50 -0300 Subject: [PATCH 31/44] add docs on multiplication and inversion algorithms --- crates/starknet-types-core/src/qm31/mod.rs | 31 +++++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index c6187f7..4aa6225 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -109,7 +109,7 @@ impl QM31 { /// /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. /// - /// The algorithm was taken from the following papper: https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf + /// The algorithm was taken from the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.1.2. pub fn add(&self, rhs: &QM31) -> QM31 { let coordinates1 = self.inner(); @@ -127,7 +127,7 @@ impl QM31 { /// /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. /// - /// The algorithm was taken from the following papper: https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf + /// The algorithm was taken from the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.1.2. pub fn neg(&self) -> QM31 { let coordinates = self.inner(); @@ -143,7 +143,7 @@ impl QM31 { /// /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. /// - /// The algorithm was taken from the following papper: https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf + /// The algorithm was taken from the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.1.2. pub fn sub(&self, rhs: &QM31) -> QM31 { let coordinates1 = self.inner(); @@ -160,6 +160,16 @@ impl QM31 { /// Computes the multiplication of two [QM31] elements in reduced form. /// /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. + /// + /// The algorithm can be deduced from the implementation of the QM31 multiplication from the Stwo prover: + /// [Link](https://github.com/starkware-libs/stwo/blob/d9176e6e22319370a8501f799829b920c0db2eac/crates/stwo/src/core/fields/qm31.rs#L81). + /// Which multiplies 2 complex extension fields of M31: + /// [Link](https://github.com/starkware-libs/stwo/blob/d9176e6e22319370a8501f799829b920c0db2eac/crates/stwo/src/core/fields/cm31.rs#L47). + /// Whose real and imaginary components result from the M31 multiplication: + /// [Link](https://github.com/starkware-libs/stwo/blob/d9176e6e22319370a8501f799829b920c0db2eac/crates/stwo/src/core/fields/m31.rs#L105). + /// + /// The implementation of the QM31 multiplication is based on the following paper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) + /// Section 1.3, Ecuation 1.20. pub fn mul(&self, rhs: &QM31) -> QM31 { let coordinates1_u64 = self.inner(); let coordinates2_u64 = rhs.inner(); @@ -210,7 +220,7 @@ impl QM31 { /// /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. /// - /// The algorithm was taken from the following papper: https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf + /// The algorithm was taken from the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.1.3. fn sqn(v: u64, n: usize) -> u64 { let mut u = v; @@ -223,6 +233,19 @@ impl QM31 { /// Computes the inverse of a [QM31] element in reduced form. /// /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. + /// + /// The algorithm can be deduced from the implementation of the inverse of a QM31 from the Stwo prover: + /// [Link](https://github.com/starkware-libs/stwo/blob/d9176e6e22319370a8501f799829b920c0db2eac/crates/stwo/src/core/fields/qm31.rs#L120). + /// + /// Having the following inverse operation: `(a + bu)^-1 = (a - bu) / (a^2 - (2+i)b^2)`, the algorithm computes the denominator by computing + /// `(a^2 - (2+i)b^2)` first and then inverting it. The denominator's value inverse is computed as the inverse of a complex extension + /// field of M31: + /// [Link](https://github.com/starkware-libs/stwo/blob/d9176e6e22319370a8501f799829b920c0db2eac/crates/stwo/src/core/fields/cm31.rs#L68). + /// Finally, the inverse of the QM31 is computed: + /// [Link](https://github.com/starkware-libs/stwo/blob/d9176e6e22319370a8501f799829b920c0db2eac/crates/stwo/src/core/fields/qm31.rs#L127). + /// + /// The implementation of the QM31 inversion is based on the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) + /// Section 1.3, Ecuation 1.23. /// /// Returns an error if the operand is equal to zero. pub fn inverse(&self) -> Result { From 06ed0d57c7cf9c84b204fd01864026d1c81b3cac Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Wed, 20 Aug 2025 18:02:39 -0300 Subject: [PATCH 32/44] add docs on multiplication and inversion algorithms --- crates/starknet-types-core/src/qm31/mod.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index 4aa6225..508941c 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -160,14 +160,14 @@ impl QM31 { /// Computes the multiplication of two [QM31] elements in reduced form. /// /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. - /// + /// /// The algorithm can be deduced from the implementation of the QM31 multiplication from the Stwo prover: /// [Link](https://github.com/starkware-libs/stwo/blob/d9176e6e22319370a8501f799829b920c0db2eac/crates/stwo/src/core/fields/qm31.rs#L81). - /// Which multiplies 2 complex extension fields of M31: + /// Which multiplies 2 complex extension fields of M31: /// [Link](https://github.com/starkware-libs/stwo/blob/d9176e6e22319370a8501f799829b920c0db2eac/crates/stwo/src/core/fields/cm31.rs#L47). - /// Whose real and imaginary components result from the M31 multiplication: + /// Whose real and imaginary components result from the M31 multiplication: /// [Link](https://github.com/starkware-libs/stwo/blob/d9176e6e22319370a8501f799829b920c0db2eac/crates/stwo/src/core/fields/m31.rs#L105). - /// + /// /// The implementation of the QM31 multiplication is based on the following paper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.3, Ecuation 1.20. pub fn mul(&self, rhs: &QM31) -> QM31 { @@ -233,17 +233,17 @@ impl QM31 { /// Computes the inverse of a [QM31] element in reduced form. /// /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. - /// + /// /// The algorithm can be deduced from the implementation of the inverse of a QM31 from the Stwo prover: /// [Link](https://github.com/starkware-libs/stwo/blob/d9176e6e22319370a8501f799829b920c0db2eac/crates/stwo/src/core/fields/qm31.rs#L120). - /// - /// Having the following inverse operation: `(a + bu)^-1 = (a - bu) / (a^2 - (2+i)b^2)`, the algorithm computes the denominator by computing - /// `(a^2 - (2+i)b^2)` first and then inverting it. The denominator's value inverse is computed as the inverse of a complex extension + /// + /// Having the following inverse operation: `(a + bu)^-1 = (a - bu) / (a^2 - (2+i)b^2)`, the algorithm computes the denominator by computing + /// `(a^2 - (2+i)b^2)` first and then inverting it. The denominator's value inverse is computed as the inverse of a complex extension /// field of M31: /// [Link](https://github.com/starkware-libs/stwo/blob/d9176e6e22319370a8501f799829b920c0db2eac/crates/stwo/src/core/fields/cm31.rs#L68). /// Finally, the inverse of the QM31 is computed: /// [Link](https://github.com/starkware-libs/stwo/blob/d9176e6e22319370a8501f799829b920c0db2eac/crates/stwo/src/core/fields/qm31.rs#L127). - /// + /// /// The implementation of the QM31 inversion is based on the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.3, Ecuation 1.23. /// From 16893d8d7a52b91da53c2d6dfe00be0f1254b87d Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Thu, 21 Aug 2025 09:24:10 -0300 Subject: [PATCH 33/44] add more docs for pack_into_felt method --- crates/starknet-types-core/src/qm31/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index 508941c..f6268a7 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -61,10 +61,12 @@ impl QM31 { ]) } - /// Packs the [QM31] coordinates into a Felt. + /// Packs the [QM31] coordinates into a [Felt]. /// /// A QM31 is composed of 4 coordinates each of which can be represented with 36 bits, meaning - /// it can be store in a Felt252. This method packs a given QM31 and stores it in the first 144 bits of a Felt252. + /// it can be store in a Felt. This method packs a given QM31 and stores it in the first 144 bits of a Felt. + /// Having coordinates \[C0, C1, C2, C3\], the resulting Felt is computed with the following ecuation: + /// `felt = C0 + C1 << 36 + C2 << 72 + C3 << 108``. pub fn pack_into_felt(&self) -> Felt { let coordinates = self.0; From 020c0dcc7501c07f95b2076b2afbc5bc1b0d41d8 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Thu, 21 Aug 2025 12:56:12 -0300 Subject: [PATCH 34/44] change QM31 representation to u32 coordinates --- crates/starknet-types-core/src/qm31/mod.rs | 157 +++++++++++++-------- 1 file changed, 99 insertions(+), 58 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index f6268a7..22b091c 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -2,7 +2,8 @@ use core::fmt; use crate::felt::Felt; -pub const STWO_PRIME: u64 = (1 << 31) - 1; +pub const STWO_PRIME: u32 = (1 << 31) - 1; +const STWO_PRIME_U64: u64 = STWO_PRIME as u64; const STWO_PRIME_U128: u128 = STWO_PRIME as u128; const MASK_36: u64 = (1 << 36) - 1; const MASK_8: u64 = (1 << 8) - 1; @@ -26,7 +27,7 @@ impl fmt::Display for QM31Error { ), QM31Error::FeltTooBig(felt) => write!( f, - "number used as QM31 since it's more than 144 bits long: {felt}" + "number can't used as QM31 since it's more than 144 bits long: {felt}" ), QM31Error::InvalidInversion => write!(f, "attempt to invert a qm31 equal to zero"), } @@ -36,23 +37,40 @@ impl fmt::Display for QM31Error { /// Definition of a Quadruple Mersenne 31. /// /// The internal representation is composed of 4 limbs, following a big-endian ordering. -/// Each of this limbs can be represented by 36 bits. +/// Each of this limbs can be represented by 31 bits. #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct QM31([u64; 4]); +pub struct QM31([u32; 4]); impl QM31 { /// [QM31] constant that's equal to 0. pub const ZERO: Self = Self([0, 0, 0, 0]); - pub fn inner(&self) -> [u64; 4] { + pub fn inner(&self) -> [u32; 4] { self.0 } - /// Creates a [QM31] in its reduced form from four u64 coordinates. + /// Utility method to return the coordinates of a [QM31] as 64 values. + /// + /// This method is convinient for performing multications and inversions, + /// in which operating with the coordinates could result in overflows if + /// the 32 bit representation was used. + fn inner_u64(&self) -> [u64; 4] { + self.0.map(u64::from) + } + + /// Creates a [QM31] in its reduced form from four u32 coordinates. /// /// A coordinate refers to a value in the M31 field. - pub fn from_coordinates(coordinates: [u64; 4]) -> QM31 { + pub fn from_coordinates(coordinates: [u32; 4]) -> QM31 { + Self::reduce(coordinates) + } + + /// Applies the reduction operation in the Mersenne 31 field to each of the QM31 coordinates. + /// + /// The algorithm was taken from the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) + /// Section 1.1.2. + fn reduce(coordinates: [u32; 4]) -> QM31 { Self([ coordinates[0] % STWO_PRIME, coordinates[1] % STWO_PRIME, @@ -61,12 +79,29 @@ impl QM31 { ]) } + /// Applies the reduction operation in the Mersenne 31 field to each of the QM31 coordinates. + /// + /// This is an utility method to support coordinates which would overflow in 32 bit representation. + /// + /// The algorithm was taken from the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) + /// Section 1.1.2. + fn reduce_u64(coordinates: [u64; 4]) -> QM31 { + Self([ + (coordinates[0] % STWO_PRIME_U64) as u32, + (coordinates[1] % STWO_PRIME_U64) as u32, + (coordinates[2] % STWO_PRIME_U64) as u32, + (coordinates[3] % STWO_PRIME_U64) as u32, + ]) + } + /// Packs the [QM31] coordinates into a [Felt]. /// - /// A QM31 is composed of 4 coordinates each of which can be represented with 36 bits, meaning - /// it can be store in a Felt. This method packs a given QM31 and stores it in the first 144 bits of a Felt. + /// This method packs a given QM31 and stores it in the first 144 bits of a Felt. /// Having coordinates \[C0, C1, C2, C3\], the resulting Felt is computed with the following ecuation: - /// `felt = C0 + C1 << 36 + C2 << 72 + C3 << 108``. + /// `felt = C0 + C1 << 36 + C2 << 72 + C3 << 108` + /// + /// A QM31 is composed of 4 coordinates each represented with 31 bits which, for efficiency reasons, + /// are packed as 36 bit values. pub fn pack_into_felt(&self) -> Felt { let coordinates = self.0; @@ -85,12 +120,11 @@ impl QM31 { // Check value fits in 144 bits. This check is only done here // because we are trying to convert a Felt into a QM31. This - // Felt should represent a packed QM31 which is at most 144 bits long. + // Felt should represent a packed QM31 and should at most 144 bits long. if limbs[3] != 0 || limbs[2] >= 1 << 16 { return Err(QM31Error::FeltTooBig(*felt)); } - // Check if the coordinates were reduced before. let coordinates = [ (limbs[0] & MASK_36), ((limbs[0] >> 36) + ((limbs[1] & MASK_8) << 28)), @@ -98,18 +132,26 @@ impl QM31 { ((limbs[1] >> 44) + (limbs[2] << 20)), ]; + // Check if the coordinates were reduced before. for x in coordinates.iter() { - if *x >= STWO_PRIME { + if *x >= STWO_PRIME_U64 { return Err(QM31Error::UnreducedFelt(*felt)); } } - Ok(QM31(coordinates)) + // This conversion is safe because we've already checked that + // every coordinate < prime + Ok(QM31([ + coordinates[0] as u32, + coordinates[1] as u32, + coordinates[2] as u32, + coordinates[3] as u32, + ])) } /// Computes the addition of two [QM31] elements in reduced form. /// - /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. + /// In reduced form, a QM31 is composed of 4 limbs, each representes a value from the Mersenne 31 field. /// /// The algorithm was taken from the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.1.2. @@ -122,18 +164,18 @@ impl QM31 { coordinates1[2] + coordinates2[2], coordinates1[3] + coordinates2[3], ]; - Self::from_coordinates(result_unreduced_coordinates) + Self::reduce(result_unreduced_coordinates) } /// Computes the negative of a [QM31] element in reduced form. /// - /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. + /// In reduced form, a QM31 is composed of 4 limbs, each represents a value from the Mersenne 31 field. /// /// The algorithm was taken from the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.1.2. pub fn neg(&self) -> QM31 { let coordinates = self.inner(); - Self::from_coordinates([ + Self::reduce([ STWO_PRIME - coordinates[0], STWO_PRIME - coordinates[1], STWO_PRIME - coordinates[2], @@ -143,7 +185,7 @@ impl QM31 { /// Computes the subtraction of two [QM31] elements in reduced form. /// - /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. + /// In reduced form, a QM31 is composed of 4 limbs, each represents a value from the Mersenne 31 field. /// /// The algorithm was taken from the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.1.2. @@ -156,12 +198,12 @@ impl QM31 { STWO_PRIME + coordinates1[2] - coordinates2[2], STWO_PRIME + coordinates1[3] - coordinates2[3], ]; - Self::from_coordinates(result_unreduced_coordinates) + Self::reduce(result_unreduced_coordinates) } /// Computes the multiplication of two [QM31] elements in reduced form. /// - /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. + /// In reduced form, a QM31 is composed of 4 limbs, each represents a value from the Mersenne 31 field. /// /// The algorithm can be deduced from the implementation of the QM31 multiplication from the Stwo prover: /// [Link](https://github.com/starkware-libs/stwo/blob/d9176e6e22319370a8501f799829b920c0db2eac/crates/stwo/src/core/fields/qm31.rs#L81). @@ -173,8 +215,8 @@ impl QM31 { /// The implementation of the QM31 multiplication is based on the following paper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.3, Ecuation 1.20. pub fn mul(&self, rhs: &QM31) -> QM31 { - let coordinates1_u64 = self.inner(); - let coordinates2_u64 = rhs.inner(); + let coordinates1_u64 = self.inner_u64(); + let coordinates2_u64 = rhs.inner_u64(); let coordinates1 = coordinates1_u64.map(u128::from); let coordinates2 = coordinates2_u64.map(u128::from); @@ -193,7 +235,7 @@ impl QM31 { + coordinates1[2] * coordinates2[2] - coordinates1[3] * coordinates2[3]) % STWO_PRIME_U128) as u64, - 2 * STWO_PRIME * STWO_PRIME + coordinates1_u64[0] * coordinates2_u64[2] + 2 * STWO_PRIME_U64 * STWO_PRIME_U64 + coordinates1_u64[0] * coordinates2_u64[2] - coordinates1_u64[1] * coordinates2_u64[3] + coordinates1_u64[2] * coordinates2_u64[0] - coordinates1_u64[3] * coordinates2_u64[1], @@ -202,20 +244,20 @@ impl QM31 { + coordinates1_u64[2] * coordinates2_u64[1] + coordinates1_u64[3] * coordinates2_u64[0], ]; - Self::from_coordinates(result_coordinates) + Self::reduce_u64(result_coordinates) } /// Computes the inverse in the M31 field using Fermat's little theorem. /// /// Returns `v^(STWO_PRIME-2) modulo STWO_PRIME`, which is the inverse of v unless v % STWO_PRIME == 0. fn m31_inverse(v: u64) -> u64 { - let t0 = (Self::sqn(v, 2) * v) % STWO_PRIME; - let t1 = (Self::sqn(t0, 1) * t0) % STWO_PRIME; - let t2 = (Self::sqn(t1, 3) * t0) % STWO_PRIME; - let t3 = (Self::sqn(t2, 1) * t0) % STWO_PRIME; - let t4 = (Self::sqn(t3, 8) * t3) % STWO_PRIME; - let t5 = (Self::sqn(t4, 8) * t3) % STWO_PRIME; - (Self::sqn(t5, 7) * t2) % STWO_PRIME + let t0 = (Self::sqn(v, 2) * v) % STWO_PRIME_U64; + let t1 = (Self::sqn(t0, 1) * t0) % STWO_PRIME_U64; + let t2 = (Self::sqn(t1, 3) * t0) % STWO_PRIME_U64; + let t3 = (Self::sqn(t2, 1) * t0) % STWO_PRIME_U64; + let t4 = (Self::sqn(t3, 8) * t3) % STWO_PRIME_U64; + let t5 = (Self::sqn(t4, 8) * t3) % STWO_PRIME_U64; + (Self::sqn(t5, 7) * t2) % STWO_PRIME_U64 } /// Computes `v^(2^n) modulo STWO_PRIME`. @@ -227,14 +269,14 @@ impl QM31 { fn sqn(v: u64, n: usize) -> u64 { let mut u = v; for _ in 0..n { - u = (u * u) % STWO_PRIME; + u = (u * u) % STWO_PRIME_U64; } u } /// Computes the inverse of a [QM31] element in reduced form. /// - /// In reduced form, a QM31 is composed of 4 limbs, each represented a value from the Mersenne 31 field. + /// In reduced form, a QM31 is composed of 4 limbs, each representes a value from the Mersenne 31 field. /// /// The algorithm can be deduced from the implementation of the inverse of a QM31 from the Stwo prover: /// [Link](https://github.com/starkware-libs/stwo/blob/d9176e6e22319370a8501f799829b920c0db2eac/crates/stwo/src/core/fields/qm31.rs#L120). @@ -255,35 +297,36 @@ impl QM31 { return Err(QM31Error::InvalidInversion); } - let coordinates = self.inner(); + let coordinates = self.inner_u64(); - let b2_r = (coordinates[2] * coordinates[2] + STWO_PRIME * STWO_PRIME + let b2_r = (coordinates[2] * coordinates[2] + STWO_PRIME_U64 * STWO_PRIME_U64 - coordinates[3] * coordinates[3]) - % STWO_PRIME; - let b2_i = (2 * coordinates[2] * coordinates[3]) % STWO_PRIME; + % STWO_PRIME_U64; + let b2_i = (2 * coordinates[2] * coordinates[3]) % STWO_PRIME_U64; - let denom_r = (coordinates[0] * coordinates[0] + STWO_PRIME * STWO_PRIME + let denom_r = (coordinates[0] * coordinates[0] + STWO_PRIME_U64 * STWO_PRIME_U64 - coordinates[1] * coordinates[1] - + 2 * STWO_PRIME + + 2 * STWO_PRIME_U64 - 2 * b2_r + b2_i) - % STWO_PRIME; - let denom_i = - (2 * coordinates[0] * coordinates[1] + 3 * STWO_PRIME - 2 * b2_i - b2_r) % STWO_PRIME; + % STWO_PRIME_U64; + let denom_i = (2 * coordinates[0] * coordinates[1] + 3 * STWO_PRIME_U64 - 2 * b2_i - b2_r) + % STWO_PRIME_U64; - let denom_norm_squared = (denom_r * denom_r + denom_i * denom_i) % STWO_PRIME; + let denom_norm_squared = (denom_r * denom_r + denom_i * denom_i) % STWO_PRIME_U64; let denom_norm_inverse_squared = Self::m31_inverse(denom_norm_squared); - let denom_inverse_r = (denom_r * denom_norm_inverse_squared) % STWO_PRIME; - let denom_inverse_i = ((STWO_PRIME - denom_i) * denom_norm_inverse_squared) % STWO_PRIME; + let denom_inverse_r = (denom_r * denom_norm_inverse_squared) % STWO_PRIME_U64; + let denom_inverse_i = + ((STWO_PRIME_U64 - denom_i) * denom_norm_inverse_squared) % STWO_PRIME_U64; - Ok(Self::from_coordinates([ - coordinates[0] * denom_inverse_r + STWO_PRIME * STWO_PRIME + Ok(Self::reduce_u64([ + coordinates[0] * denom_inverse_r + STWO_PRIME_U64 * STWO_PRIME_U64 - coordinates[1] * denom_inverse_i, coordinates[0] * denom_inverse_i + coordinates[1] * denom_inverse_r, - coordinates[3] * denom_inverse_i + STWO_PRIME * STWO_PRIME + coordinates[3] * denom_inverse_i + STWO_PRIME_U64 * STWO_PRIME_U64 - coordinates[2] * denom_inverse_r, - 2 * STWO_PRIME * STWO_PRIME + 2 * STWO_PRIME_U64 * STWO_PRIME_U64 - coordinates[2] * denom_inverse_i - coordinates[3] * denom_inverse_r, ])) @@ -304,8 +347,6 @@ impl QM31 { #[cfg(test)] mod test { - use core::u64; - use proptest::{ array::uniform4, prelude::{BoxedStrategy, Just, Strategy}, @@ -324,25 +365,25 @@ mod test { let unpacked_coordinates = QM31::unpack_from_felt(&packed_coordinates).unwrap(); assert_eq!(coordinates, unpacked_coordinates); - let qm31 = QM31::from_coordinates([u64::MAX, 0, 0, 0]); + let qm31 = QM31::from_coordinates([u32::MAX, 0, 0, 0]); let felt: Felt = qm31.pack_into_felt(); let felt_to_qm31 = QM31::unpack_from_felt(&felt).unwrap(); assert_eq!(felt_to_qm31, qm31); - let qm31 = QM31::from_coordinates([u64::MAX, u64::MAX, 0, 0]); + let qm31 = QM31::from_coordinates([u32::MAX, u32::MAX, 0, 0]); let felt: Felt = qm31.pack_into_felt(); let felt_to_qm31 = QM31::unpack_from_felt(&felt).unwrap(); assert_eq!(felt_to_qm31, qm31); - let qm31 = QM31::from_coordinates([u64::MAX, u64::MAX, u64::MAX, 0]); + let qm31 = QM31::from_coordinates([u32::MAX, u32::MAX, u32::MAX, 0]); let felt: Felt = qm31.pack_into_felt(); let felt_to_qm31 = QM31::unpack_from_felt(&felt).unwrap(); assert_eq!(felt_to_qm31, qm31); - let qm31 = QM31::from_coordinates([u64::MAX, u64::MAX, u64::MAX, u64::MAX]); + let qm31 = QM31::from_coordinates([u32::MAX, u32::MAX, u32::MAX, u32::MAX]); let felt: Felt = qm31.pack_into_felt(); let felt_to_qm31 = QM31::unpack_from_felt(&felt).unwrap(); @@ -452,13 +493,13 @@ mod test { } /// Necessary strat to use proptest on the QM31 test - fn configuration_strat() -> BoxedStrategy { + fn configuration_strat() -> BoxedStrategy { prop_oneof![Just(0), Just(1), Just(STWO_PRIME - 1), 0..STWO_PRIME].boxed() } proptest! { #[test] - fn qm31_packed_reduced_inv_random(x_coordinates in uniform4(0u64..STWO_PRIME) + fn qm31_packed_reduced_inv_random(x_coordinates in uniform4(0u32..STWO_PRIME) .prop_filter("All configs cant be 0", |arr| !arr.iter().all(|x| *x == 0)) ) { From 10b7cd3f400c829f1138fd16afdd97e04ee7ad42 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Thu, 21 Aug 2025 12:58:02 -0300 Subject: [PATCH 35/44] add some more docs --- crates/starknet-types-core/src/qm31/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index 22b091c..f5e74b9 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -50,10 +50,10 @@ impl QM31 { self.0 } - /// Utility method to return the coordinates of a [QM31] as 64 values. - /// + /// Utility method to return the coordinates of a [QM31] as u64 values. + /// /// This method is convinient for performing multications and inversions, - /// in which operating with the coordinates could result in overflows if + /// in which operating with the coordinates could result in overflows if /// the 32 bit representation was used. fn inner_u64(&self) -> [u64; 4] { self.0.map(u64::from) @@ -100,7 +100,7 @@ impl QM31 { /// Having coordinates \[C0, C1, C2, C3\], the resulting Felt is computed with the following ecuation: /// `felt = C0 + C1 << 36 + C2 << 72 + C3 << 108` /// - /// A QM31 is composed of 4 coordinates each represented with 31 bits which, for efficiency reasons, + /// A QM31 is composed of 4 coordinates each represented with 31 bits which, for efficiency reasons, /// are packed as 36 bit values. pub fn pack_into_felt(&self) -> Felt { let coordinates = self.0; From f287335daa2d7c27f64bf07da8c76dfd10732fa1 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Thu, 21 Aug 2025 13:00:31 -0300 Subject: [PATCH 36/44] fix typo --- crates/starknet-types-core/src/qm31/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index f5e74b9..81a294f 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -120,7 +120,7 @@ impl QM31 { // Check value fits in 144 bits. This check is only done here // because we are trying to convert a Felt into a QM31. This - // Felt should represent a packed QM31 and should at most 144 bits long. + // Felt should represent a packed QM31 and should be at most 144 bits long. if limbs[3] != 0 || limbs[2] >= 1 << 16 { return Err(QM31Error::FeltTooBig(*felt)); } From fd1a9afcdd6ed766e86d6914398bf2dc51ff7a57 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Thu, 21 Aug 2025 13:01:50 -0300 Subject: [PATCH 37/44] format --- crates/starknet-types-core/src/qm31/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index 81a294f..952c095 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -51,9 +51,9 @@ impl QM31 { } /// Utility method to return the coordinates of a [QM31] as u64 values. - /// + /// /// This method is convinient for performing multications and inversions, - /// in which operating with the coordinates could result in overflows if + /// in which operating with the coordinates could result in overflows if /// the 32 bit representation was used. fn inner_u64(&self) -> [u64; 4] { self.0.map(u64::from) @@ -100,7 +100,7 @@ impl QM31 { /// Having coordinates \[C0, C1, C2, C3\], the resulting Felt is computed with the following ecuation: /// `felt = C0 + C1 << 36 + C2 << 72 + C3 << 108` /// - /// A QM31 is composed of 4 coordinates each represented with 31 bits which, for efficiency reasons, + /// A QM31 is composed of 4 coordinates each represented with 31 bits which, for efficiency reasons, /// are packed as 36 bit values. pub fn pack_into_felt(&self) -> Felt { let coordinates = self.0; From edd3fa54371a8854e33e30fdc53eb513a082929d Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Thu, 21 Aug 2025 14:50:27 -0300 Subject: [PATCH 38/44] change method inner to to_coordinates --- crates/starknet-types-core/src/qm31/mod.rs | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index 952c095..f09149a 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -46,7 +46,7 @@ impl QM31 { /// [QM31] constant that's equal to 0. pub const ZERO: Self = Self([0, 0, 0, 0]); - pub fn inner(&self) -> [u32; 4] { + pub fn to_coordinates(&self) -> [u32; 4] { self.0 } @@ -55,7 +55,7 @@ impl QM31 { /// This method is convinient for performing multications and inversions, /// in which operating with the coordinates could result in overflows if /// the 32 bit representation was used. - fn inner_u64(&self) -> [u64; 4] { + fn to_coordinates_u64(&self) -> [u64; 4] { self.0.map(u64::from) } @@ -156,8 +156,8 @@ impl QM31 { /// The algorithm was taken from the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.1.2. pub fn add(&self, rhs: &QM31) -> QM31 { - let coordinates1 = self.inner(); - let coordinates2 = rhs.inner(); + let coordinates1 = self.to_coordinates(); + let coordinates2 = rhs.to_coordinates(); let result_unreduced_coordinates = [ coordinates1[0] + coordinates2[0], coordinates1[1] + coordinates2[1], @@ -174,7 +174,7 @@ impl QM31 { /// The algorithm was taken from the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.1.2. pub fn neg(&self) -> QM31 { - let coordinates = self.inner(); + let coordinates = self.to_coordinates(); Self::reduce([ STWO_PRIME - coordinates[0], STWO_PRIME - coordinates[1], @@ -190,8 +190,8 @@ impl QM31 { /// The algorithm was taken from the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.1.2. pub fn sub(&self, rhs: &QM31) -> QM31 { - let coordinates1 = self.inner(); - let coordinates2 = rhs.inner(); + let coordinates1 = self.to_coordinates(); + let coordinates2 = rhs.to_coordinates(); let result_unreduced_coordinates = [ STWO_PRIME + coordinates1[0] - coordinates2[0], STWO_PRIME + coordinates1[1] - coordinates2[1], @@ -215,8 +215,8 @@ impl QM31 { /// The implementation of the QM31 multiplication is based on the following paper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.3, Ecuation 1.20. pub fn mul(&self, rhs: &QM31) -> QM31 { - let coordinates1_u64 = self.inner_u64(); - let coordinates2_u64 = rhs.inner_u64(); + let coordinates1_u64 = self.to_coordinates_u64(); + let coordinates2_u64 = rhs.to_coordinates_u64(); let coordinates1 = coordinates1_u64.map(u128::from); let coordinates2 = coordinates2_u64.map(u128::from); @@ -297,7 +297,7 @@ impl QM31 { return Err(QM31Error::InvalidInversion); } - let coordinates = self.inner_u64(); + let coordinates = self.to_coordinates_u64(); let b2_r = (coordinates[2] * coordinates[2] + STWO_PRIME_U64 * STWO_PRIME_U64 - coordinates[3] * coordinates[3]) @@ -409,7 +409,7 @@ mod test { let x = QM31::from_coordinates(x_coordinates); let y = QM31::from_coordinates(y_coordinates); let res = x.add(&y); - let res_coordinates = res.inner(); + let res_coordinates = res.to_coordinates(); assert_eq!( res_coordinates, [ @@ -426,7 +426,7 @@ mod test { let x_coordinates = [1749652895, 834624081, 1930174752, 2063872165]; let x = QM31::from_coordinates(x_coordinates); let res = x.neg(); - let res_coordinates = res.inner(); + let res_coordinates = res.to_coordinates(); assert_eq!( res_coordinates, [ @@ -450,7 +450,7 @@ mod test { let x = QM31::from_coordinates(x_coordinates); let y = QM31::from_coordinates(y_coordinates); let res = x.sub(&y); - let res_coordinates = res.inner(); + let res_coordinates = res.to_coordinates(); assert_eq!( res_coordinates, [1234567890, 1414213562, 1732050807, 1618033988] @@ -464,7 +464,7 @@ mod test { let x = QM31::from_coordinates(x_coordinates); let y = QM31::from_coordinates(y_coordinates); let res = x.mul(&y); - let res_coordinates = res.inner(); + let res_coordinates = res.to_coordinates(); assert_eq!( res_coordinates, [947980980, 1510986506, 623360030, 1260310989] From 8f0d2fc0059b734909652a7d519171a3ba6e66d7 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Thu, 21 Aug 2025 16:09:50 -0300 Subject: [PATCH 39/44] fix typo and clippy --- crates/starknet-types-core/src/qm31/mod.rs | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index f09149a..f313384 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -46,16 +46,16 @@ impl QM31 { /// [QM31] constant that's equal to 0. pub const ZERO: Self = Self([0, 0, 0, 0]); - pub fn to_coordinates(&self) -> [u32; 4] { + pub fn as_coordinates(&self) -> [u32; 4] { self.0 } /// Utility method to return the coordinates of a [QM31] as u64 values. /// - /// This method is convinient for performing multications and inversions, + /// This method is convenient for performing multications and inversions, /// in which operating with the coordinates could result in overflows if /// the 32 bit representation was used. - fn to_coordinates_u64(&self) -> [u64; 4] { + fn as_coordinates_u64(&self) -> [u64; 4] { self.0.map(u64::from) } @@ -156,8 +156,8 @@ impl QM31 { /// The algorithm was taken from the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.1.2. pub fn add(&self, rhs: &QM31) -> QM31 { - let coordinates1 = self.to_coordinates(); - let coordinates2 = rhs.to_coordinates(); + let coordinates1 = self.as_coordinates(); + let coordinates2 = rhs.as_coordinates(); let result_unreduced_coordinates = [ coordinates1[0] + coordinates2[0], coordinates1[1] + coordinates2[1], @@ -174,7 +174,7 @@ impl QM31 { /// The algorithm was taken from the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.1.2. pub fn neg(&self) -> QM31 { - let coordinates = self.to_coordinates(); + let coordinates = self.as_coordinates(); Self::reduce([ STWO_PRIME - coordinates[0], STWO_PRIME - coordinates[1], @@ -190,8 +190,8 @@ impl QM31 { /// The algorithm was taken from the following papper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.1.2. pub fn sub(&self, rhs: &QM31) -> QM31 { - let coordinates1 = self.to_coordinates(); - let coordinates2 = rhs.to_coordinates(); + let coordinates1 = self.as_coordinates(); + let coordinates2 = rhs.as_coordinates(); let result_unreduced_coordinates = [ STWO_PRIME + coordinates1[0] - coordinates2[0], STWO_PRIME + coordinates1[1] - coordinates2[1], @@ -215,8 +215,8 @@ impl QM31 { /// The implementation of the QM31 multiplication is based on the following paper: [Link](https://github.com/ingonyama-zk/papers/blob/main/Mersenne31_polynomial_arithmetic.pdf) /// Section 1.3, Ecuation 1.20. pub fn mul(&self, rhs: &QM31) -> QM31 { - let coordinates1_u64 = self.to_coordinates_u64(); - let coordinates2_u64 = rhs.to_coordinates_u64(); + let coordinates1_u64 = self.as_coordinates_u64(); + let coordinates2_u64 = rhs.as_coordinates_u64(); let coordinates1 = coordinates1_u64.map(u128::from); let coordinates2 = coordinates2_u64.map(u128::from); @@ -297,7 +297,7 @@ impl QM31 { return Err(QM31Error::InvalidInversion); } - let coordinates = self.to_coordinates_u64(); + let coordinates = self.as_coordinates_u64(); let b2_r = (coordinates[2] * coordinates[2] + STWO_PRIME_U64 * STWO_PRIME_U64 - coordinates[3] * coordinates[3]) @@ -409,7 +409,7 @@ mod test { let x = QM31::from_coordinates(x_coordinates); let y = QM31::from_coordinates(y_coordinates); let res = x.add(&y); - let res_coordinates = res.to_coordinates(); + let res_coordinates = res.as_coordinates(); assert_eq!( res_coordinates, [ @@ -426,7 +426,7 @@ mod test { let x_coordinates = [1749652895, 834624081, 1930174752, 2063872165]; let x = QM31::from_coordinates(x_coordinates); let res = x.neg(); - let res_coordinates = res.to_coordinates(); + let res_coordinates = res.as_coordinates(); assert_eq!( res_coordinates, [ @@ -450,7 +450,7 @@ mod test { let x = QM31::from_coordinates(x_coordinates); let y = QM31::from_coordinates(y_coordinates); let res = x.sub(&y); - let res_coordinates = res.to_coordinates(); + let res_coordinates = res.as_coordinates(); assert_eq!( res_coordinates, [1234567890, 1414213562, 1732050807, 1618033988] @@ -464,7 +464,7 @@ mod test { let x = QM31::from_coordinates(x_coordinates); let y = QM31::from_coordinates(y_coordinates); let res = x.mul(&y); - let res_coordinates = res.to_coordinates(); + let res_coordinates = res.as_coordinates(); assert_eq!( res_coordinates, [947980980, 1510986506, 623360030, 1260310989] From 5c9512e93e26e594ab505d5c6a5e2afeb3fcb2c7 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Fri, 22 Aug 2025 10:37:09 -0300 Subject: [PATCH 40/44] add general documentation --- crates/starknet-types-core/src/qm31/mod.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index f313384..875489d 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -1,3 +1,20 @@ +//! A Cairo-like QM31 type. +//! +//! The QM31 type represents a Degree-4 extension the Mersenne 31 Field. This extension can be +//! represented as two components of the Complex extension the Mersenne 31 Field as follows ((a, b), (c, d)), where +//! a, b, c and d represent a value from the Mersenne 31 Field, denotated as M31. +//! If anly a M31 was used by the verifier, then a 31 bit value wouldn't be enough to provide security +//! to the verification. A QM31 not only provides an efficient arithmetic field, since it is composed of four M31 values, but +//! also allows for a more secure level of verification as it offers a 124 bit value. By using this extension, +//! the verifier is able to generate challeges with a proper level of randomness, ensuring the security of the protocol. +//! +//! While the Cairo language's representation of QM31 consists of four `BoundedInt`s +//! simulating a 31 bit value, this implementation uses four 32 bit values. +//! +//! The conversion to a Felt is done by using the four elements of the struct, refered to as coordinates, as bytes and then +//! parsed as a little endian value. For a more efficient Felt representation, each coordinate is stored as a 36 bit. Hence, +//! a QM31 can be represented with the first 144 bits of a Felt. + use core::fmt; use crate::felt::Felt; @@ -36,8 +53,9 @@ impl fmt::Display for QM31Error { /// Definition of a Quadruple Mersenne 31. /// -/// The internal representation is composed of 4 limbs, following a big-endian ordering. -/// Each of this limbs can be represented by 31 bits. +/// The internal representation is composed of 4 coordinates, each representing a value +/// from the Mersenne 31 field. +/// Each of these coordinates can be represented by 31 bits. #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct QM31([u32; 4]); From b65e34b7b6298ef992f5f4ef8c81518dc7aa8267 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Fri, 22 Aug 2025 10:40:19 -0300 Subject: [PATCH 41/44] fix typo --- crates/starknet-types-core/src/qm31/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index 875489d..7ed758b 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -3,7 +3,7 @@ //! The QM31 type represents a Degree-4 extension the Mersenne 31 Field. This extension can be //! represented as two components of the Complex extension the Mersenne 31 Field as follows ((a, b), (c, d)), where //! a, b, c and d represent a value from the Mersenne 31 Field, denotated as M31. -//! If anly a M31 was used by the verifier, then a 31 bit value wouldn't be enough to provide security +//! If only a M31 was used by the verifier, then a 31 bit value wouldn't be enough to provide security //! to the verification. A QM31 not only provides an efficient arithmetic field, since it is composed of four M31 values, but //! also allows for a more secure level of verification as it offers a 124 bit value. By using this extension, //! the verifier is able to generate challeges with a proper level of randomness, ensuring the security of the protocol. From 7f3d6ef5c83c5b62bb42d59c60e139114e1a6978 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Fri, 22 Aug 2025 11:39:29 -0300 Subject: [PATCH 42/44] fix typos --- crates/starknet-types-core/src/qm31/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index 7ed758b..8ce6fe5 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -1,8 +1,8 @@ //! A Cairo-like QM31 type. //! -//! The QM31 type represents a Degree-4 extension the Mersenne 31 Field. This extension can be -//! represented as two components of the Complex extension the Mersenne 31 Field as follows ((a, b), (c, d)), where -//! a, b, c and d represent a value from the Mersenne 31 Field, denotated as M31. +//! The QM31 type represents a Degree-4 extension in the Mersenne 31 field. This extension can be +//! represented as two components of the Complex extension the Mersenne 31 field as follows ((a, b), (c, d)), where +//! a, b, c and d represent a value from the Mersenne 31 field, denotated as M31. //! If only a M31 was used by the verifier, then a 31 bit value wouldn't be enough to provide security //! to the verification. A QM31 not only provides an efficient arithmetic field, since it is composed of four M31 values, but //! also allows for a more secure level of verification as it offers a 124 bit value. By using this extension, From 6bad58f46ed8f08ac2f9ad8ac34de539155389bf Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Fri, 22 Aug 2025 17:45:31 -0300 Subject: [PATCH 43/44] improve documentation and format --- crates/starknet-types-core/src/qm31/mod.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index 8ce6fe5..bcc18b4 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -1,18 +1,20 @@ //! A Cairo-like QM31 type. //! //! The QM31 type represents a Degree-4 extension in the Mersenne 31 field. This extension can be -//! represented as two components of the Complex extension the Mersenne 31 field as follows ((a, b), (c, d)), where -//! a, b, c and d represent a value from the Mersenne 31 field, denotated as M31. -//! If only a M31 was used by the verifier, then a 31 bit value wouldn't be enough to provide security +//! represented as two components of the Complex extension in the Mersenne 31 field as follows ((a, b), (c, d)), where +//! a, b, c and d represent a value from the Mersenne 31 field, denotated as M31. These four values represent the coordinates +//! of a QM31. +//! If only an M31 was used by a verifier, then a 31 bit value wouldn't be enough to provide security //! to the verification. A QM31 not only provides an efficient arithmetic field, since it is composed of four M31 values, but //! also allows for a more secure level of verification as it offers a 124 bit value. By using this extension, -//! the verifier is able to generate challeges with a proper level of randomness, ensuring the security of the protocol. +//! a verifier is able to generate challenges with a proper level of randomness, ensuring the security of the protocol. //! //! While the Cairo language's representation of QM31 consists of four `BoundedInt`s -//! simulating a 31 bit value, this implementation uses four 32 bit values. +//! simulating a 31 bit value, this implementation uses four 32 bit values. The refered +//! representation can be seen here: [Link](https://github.com/starkware-libs/cairo/blob/main/corelib/src/qm31.cairo#L7). //! -//! The conversion to a Felt is done by using the four elements of the struct, refered to as coordinates, as bytes and then -//! parsed as a little endian value. For a more efficient Felt representation, each coordinate is stored as a 36 bit. Hence, +//! The conversion to a Felt is done by using the four cordinates of the struct as bytes and then +//! parsed as a little endian value. For a more efficient Felt representation, each coordinate is stored as a 36 bit. Hence, //! a QM31 can be represented with the first 144 bits of a Felt. use core::fmt; From 035a87cd4ea764e0f32a75dffd53b43a73508fab Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Fri, 22 Aug 2025 17:48:44 -0300 Subject: [PATCH 44/44] improve documentation --- crates/starknet-types-core/src/qm31/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/starknet-types-core/src/qm31/mod.rs b/crates/starknet-types-core/src/qm31/mod.rs index bcc18b4..6aff7a5 100644 --- a/crates/starknet-types-core/src/qm31/mod.rs +++ b/crates/starknet-types-core/src/qm31/mod.rs @@ -2,8 +2,8 @@ //! //! The QM31 type represents a Degree-4 extension in the Mersenne 31 field. This extension can be //! represented as two components of the Complex extension in the Mersenne 31 field as follows ((a, b), (c, d)), where -//! a, b, c and d represent a value from the Mersenne 31 field, denotated as M31. These four values represent the coordinates -//! of a QM31. +//! a, b, c and d represent a value from the Mersenne 31 field, denotated as M31. These four values can be interpreted +//! as the coordinates of a QM31. //! If only an M31 was used by a verifier, then a 31 bit value wouldn't be enough to provide security //! to the verification. A QM31 not only provides an efficient arithmetic field, since it is composed of four M31 values, but //! also allows for a more secure level of verification as it offers a 124 bit value. By using this extension,