Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
0xLucqs committed Feb 2, 2024
1 parent a1cf3ec commit 2332613
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 2 deletions.
4 changes: 3 additions & 1 deletion crates/starknet-types-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ lambdaworks-crypto = { version = "0.5.0", default-features = false, optional = t
parity-scale-codec = { version = "3.2.2", default-features = false, optional = true }

[features]
default = ["std", "serde", "curve", "num-traits"]
default = ["std", "serde", "curve", "num-traits", "papyrus-encoding"]
std = [
"bitvec/std",
"lambdaworks-math/std",
Expand All @@ -45,6 +45,8 @@ arbitrary = ["std", "dep:arbitrary"]
parity-scale-codec = ["dep:parity-scale-codec"]
serde = ["alloc", "dep:serde"]
num-traits = []
papyrus-encoding = []
unsafe-non-zero = []

[dev-dependencies]
proptest = "1.1.0"
Expand Down
6 changes: 6 additions & 0 deletions crates/starknet-types-core/src/curve/affine_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::felt::Felt;
use lambdaworks_math::cyclic_group::IsGroup;
use lambdaworks_math::elliptic_curve::short_weierstrass::curves::stark_curve::StarkCurve;
use lambdaworks_math::elliptic_curve::short_weierstrass::point::ShortWeierstrassProjectivePoint;
use lambdaworks_math::elliptic_curve::short_weierstrass::traits::IsShortWeierstrass;
use lambdaworks_math::elliptic_curve::traits::FromAffine;

/// Represents a point on the Stark elliptic curve.
Expand All @@ -18,6 +19,11 @@ impl AffinePoint {
)?))
}

pub fn from_x(x: Felt) -> Option<Self> {
let y_squared = x * x * x + Felt(StarkCurve::a()) * x + Felt(StarkCurve::b());
let y = y_squared.sqrt()?;
Self::new(x, y).ok()
}
/// The point at infinity.
pub fn identity() -> AffinePoint {
Self(ShortWeierstrassProjectivePoint::neutral_element())
Expand Down
67 changes: 67 additions & 0 deletions crates/starknet-types-core/src/curve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,73 @@ mod affine_point;
mod curve_errors;
mod projective_point;

// use core::any::Any;

// use lambdaworks_math::elliptic_curve::short_weierstrass::curves::stark_curve::StarkCurve;
// use lambdaworks_math::elliptic_curve::traits::IsEllipticCurve;

// use crate::felt::Felt;
// use crate::felt::NonZeroFelt;

pub use self::affine_point::*;
pub use self::curve_errors::*;
pub use self::projective_point::*;

// pub enum SignatureVerificationError {
// InvalidPublicKey,
// InvalidMessage,
// InvalidR,
// InvalidS,
// }
// const EC_ORDER: NonZeroFelt = unsafe {
// NonZeroFelt::from_raw_const([
// 369010039416812937,
// 9,
// 1143265896874747514,
// 8939893405601011193,
// ])
// };

// #[inline(always)]
// fn mul_by_bits(x: &AffinePoint, y: &Felt) -> AffinePoint {
// let x = ProjectivePoint::from_affine(x.x(), x.y()).unwrap();
// let y: Vec<bool> = y.to_bits_le().into_iter().collect();
// let z = &x * &y;
// z.to_affine()
// }
// pub fn verify_signature(
// public_key: &Felt,
// msg: &Felt,
// r: &Felt,
// s: &Felt,
// ) -> Result<bool, SignatureVerificationError> {
// if msg >= &Felt::ELEMENT_UPPER_BOUND {
// return Err(SignatureVerificationError::InvalidMessage);
// }
// if r == &Felt::ZERO || r >= &Felt::ELEMENT_UPPER_BOUND {
// return Err(SignatureVerificationError::InvalidR);
// }
// if s == &Felt::ZERO || s >= &Felt::ELEMENT_UPPER_BOUND {
// return Err(SignatureVerificationError::InvalidS);
// }

// let full_public_key = match AffinePoint::from_x(*public_key) {
// Some(value) => value,
// None => return Err(SignatureVerificationError::InvalidPublicKey),
// };

// let w = s
// .mod_inverse(&EC_ORDER)
// .ok_or(SignatureVerificationError::InvalidS)?;
// if w == Felt::ZERO || w >= Felt::ELEMENT_UPPER_BOUND {
// return Err(SignatureVerificationError::InvalidS);
// }

// let zw = msg.mul_mod(&w, &EC_ORDER);
// let zw_g = StarkCurve::generator().mul_by_bits(&zw);

// let rw = r.mul_mod_floor(&w, &EC_ORDER);
// let rw_q = full_public_key.mul_by_bits(&rw);

// Ok((&zw_g + &rw_q).x == *r || (&zw_g - &rw_q).x == *r)
// }
113 changes: 112 additions & 1 deletion crates/starknet-types-core/src/felt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use num_traits::{One, Zero};

#[cfg(feature = "num-traits")]
mod num_traits_impl;
#[cfg(feature = "papyrus-encoding")]
mod papyrus_encoding;

lazy_static! {
pub static ref CAIRO_PRIME_BIGINT: BigInt = BigInt::from_str_radix(
Expand Down Expand Up @@ -50,6 +52,48 @@ pub struct Felt(pub(crate) FieldElement<Stark252PrimeField>);
#[derive(Debug, Clone, Copy)]
pub struct NonZeroFelt(FieldElement<Stark252PrimeField>);

impl NonZeroFelt {
// /// Create a [NonZeroFelt] as a constant. If the value is zero will panic.
// pub const unsafe fn from_felt_const(felt: Felt) -> Self {
// let value = felt.0.representative().limbs;
// let mut i = 0;
// let mut zeros_nb = 0;
// while i < value.len() {
// if value[i] == 0 {
// zeros_nb += 1;
// }
// i += 1;
// }
// assert!(zeros_nb < value.len(), "Felt is zero");
// Self(felt.0)
// }

/// Create a [NonZeroFelt] as a constant.
/// # Safety
/// If the value is zero will panic.
pub const unsafe fn from_raw_const(value: [u64; 4]) -> Self {
let mut i = 0;
let mut zeros_nb = 0;
while i < value.len() {
if value[i] == 0 {
zeros_nb += 1;
}
i += 1;
}
assert!(zeros_nb < value.len(), "Felt is zero");
let value = Felt::from_raw_const(value);
Self(value.0)
}

/// Create a [NonZeroFelt] without checking it. If the [Felt] is indeed [Felt::ZERO]
/// this can lead to undefined behaviour and big security issue.
/// You should always use the [TryFrom] implementation
#[cfg(feature = "unsafe-non-zero")]
pub fn from_felt_unchecked(value: Felt) -> Self {
Self(value.0)
}
}

#[derive(Debug)]
pub struct FeltIsZeroError;

Expand Down Expand Up @@ -78,6 +122,26 @@ impl Felt {
pub const MAX: Self = Self(FieldElement::<Stark252PrimeField>::const_from_raw(
UnsignedInteger::from_limbs([544, 0, 0, 32]),
));
pub const ELEMENT_UPPER_BOUND: Felt = Felt::from_raw_const([
576459263475450960,
18446744073709255680,
160989183,
18446743986131435553,
]);

pub const fn from_raw_const(val: [u64; 4]) -> Self {
Self(FieldElement::<Stark252PrimeField>::const_from_raw(
UnsignedInteger::from_limbs(val),
))
}

pub const fn from_u64(val: u64) -> Self {
Self(FieldElement::<Stark252PrimeField>::const_from_raw(
UnsignedInteger::from_u64(val),
))
}

// pub const fn from_hex_const(hex_string: &str) -> Felt {}

/// Creates a new [Felt] from its big-endian representation in a [u8; 32] array.
/// This is as performant as [from_bytes_le](Felt::from_bytes_le).
Expand Down Expand Up @@ -183,6 +247,14 @@ impl Felt {
res
}

/// Creates a new [Felt] from the raw internal representation.
/// See [UnisgnedInteger] to understand how it works under the hood.
pub fn from_raw(val: [u64; 4]) -> Self {
Self(FieldElement::<Stark252PrimeField>::from_raw(
UnsignedInteger::from_limbs(val),
))
}

/// Converts to big-endian byte representation in a [u8] array.
/// This is as performant as [to_bytes_le](Felt::to_bytes_le)
pub fn to_bytes_be(&self) -> [u8; 32] {
Expand Down Expand Up @@ -230,6 +302,18 @@ impl Felt {
alloc::format!("{self:#x}")
}

/// Helper to produce a hexadecimal formatted string of 66 chars.
/// Equivalent to calling `format!("{self:#066x}")`.
#[cfg(feature = "alloc")]
pub fn to_fixed_hex_string(&self) -> alloc::string::String {
let hex_str = alloc::format!("{self:#x}");
if hex_str.len() < 66 {
alloc::format!("0x{:0>64}", hex_str.strip_prefix("0x").unwrap())
} else {
hex_str
}
}

/// Converts to little-endian bit representation.
/// This is as performant as [to_bits_be](Felt::to_bits_be)
#[cfg(target_pointer_width = "64")]
Expand Down Expand Up @@ -367,6 +451,12 @@ impl Felt {
}
}

pub fn to_raw_reversed(&self) -> [u64; 4] {
let mut res = self.0.to_raw().limbs;
res.reverse();
res
}

/// Convert `self`'s representative into an array of `u64` digits,
/// least significant digits first.
pub fn to_le_digits(&self) -> [u64; 4] {
Expand Down Expand Up @@ -398,6 +488,10 @@ impl Felt {
pub fn to_bigint(&self) -> BigInt {
self.to_biguint().into()
}

pub fn prime() -> BigUint {
(*CAIRO_PRIME_BIGINT).to_biguint().unwrap()
}
}

#[cfg(feature = "arbitrary")]
Expand Down Expand Up @@ -527,12 +621,29 @@ impl From<&BigInt> for Felt {
}
}

impl From<BigInt> for Felt {
fn from(bigint: BigInt) -> Felt {
let (sign, bytes) = bigint.mod_floor(&CAIRO_PRIME_BIGINT).to_bytes_le();
let felt = Felt::from_bytes_le_slice(&bytes);
if sign == Sign::Minus {
felt.neg()
} else {
felt
}
}
}
impl From<&BigUint> for Felt {
fn from(biguint: &BigUint) -> Felt {
Felt::from_bytes_le_slice(&biguint.to_bytes_le())
}
}

impl From<BigUint> for Felt {
fn from(biguint: BigUint) -> Felt {
Felt::from_bytes_le_slice(&biguint.to_bytes_le())
}
}

macro_rules! impl_from {
($from:ty, $with:ty) => {
impl From<$from> for Felt {
Expand Down Expand Up @@ -883,7 +994,7 @@ mod serde_impl {
impl<'de> de::Visitor<'de> for FeltVisitor {
type Value = Felt;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("Failed to deserialize hexadecimal string")
}

Expand Down
71 changes: 71 additions & 0 deletions crates/starknet-types-core/src/felt/papyrus_encoding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use std::io::Error;

use super::Felt;
/// Storage efficient serialization for field elements.
impl Felt {
// Felt encoding constants.
const CHOOSER_FULL: u8 = 15;
const CHOOSER_HALF: u8 = 14;
pub fn serialize(&self, res: &mut impl std::io::Write) -> Result<(), Error> {
let self_as_bytes = self.to_bytes_be();
// We use the fact that bytes[0] < 0x10 and encode the size of the felt in the 4 most
// significant bits of the serialization, which we call `chooser`. We assume that 128 bit
// felts are prevalent (because of how uint256 is encoded in felts).

// The first i for which nibbles 2i+1, 2i+2 are nonzero. Note that the first nibble is
// always 0.
let mut first_index = 31;
// Find first non zero byte
if let Some((index, value)) = self_as_bytes
.iter()
.enumerate()
.find(|(_index, value)| value != &&0u8)
{
if value < &16 {
// Can encode the chooser and the value on a single byte.
first_index = index;
} else {
// The chooser is encoded with the first nibble of the value.
first_index = index - 1;
}
};
let chooser = if first_index < 15 {
// For 34 up to 63 nibble felts: chooser == 15, serialize using 32 bytes.
first_index = 0;
Felt::CHOOSER_FULL
} else if first_index < 18 {
// For 28 up to 33 nibble felts: chooser == 14, serialize using 17 bytes.
first_index = 15;
Felt::CHOOSER_HALF
} else {
// For up to 27 nibble felts: serialize the lower 1 + (chooser * 2) nibbles of the felt
// using chooser + 1 bytes.
(31 - first_index) as u8
};
res.write_all(&[(chooser << 4) | self_as_bytes[first_index]])?;
res.write_all(&self_as_bytes[first_index + 1..])?;
Ok(())
}

/// Storage efficient deserialization for field elements.
pub fn deserialize(bytes: &mut impl std::io::Read) -> Option<Self> {
let mut res = [0u8; 32];

bytes.read_exact(&mut res[..1]).ok()?;
let first = res[0];
let chooser: u8 = first >> 4;
let first = first & 0x0f;

let first_index = if chooser == Felt::CHOOSER_FULL {
0
} else if chooser == Felt::CHOOSER_HALF {
15
} else {
(31 - chooser) as usize
};
res[0] = 0;
res[first_index] = first;
bytes.read_exact(&mut res[first_index + 1..]).ok()?;
Some(Self::from_bytes_be(&res))
}
}

0 comments on commit 2332613

Please sign in to comment.