diff --git a/crates/blockifier/src/execution.rs b/crates/blockifier/src/execution.rs index e3b5567d77..cc4a0b67a5 100644 --- a/crates/blockifier/src/execution.rs +++ b/crates/blockifier/src/execution.rs @@ -9,6 +9,8 @@ pub mod entry_point_execution; pub mod errors; pub mod execution_utils; pub mod hint_code; +pub mod secp; + #[cfg(feature = "cairo_native")] pub mod native; pub mod stack_trace; diff --git a/crates/blockifier/src/execution/native/syscall_handler.rs b/crates/blockifier/src/execution/native/syscall_handler.rs index ee03fce54c..e020d2d978 100644 --- a/crates/blockifier/src/execution/native/syscall_handler.rs +++ b/crates/blockifier/src/execution/native/syscall_handler.rs @@ -1,7 +1,11 @@ use std::collections::HashSet; +use std::convert::From; +use std::fmt; use std::hash::RandomState; use std::sync::Arc; +use ark_ec::short_weierstrass::{Affine, Projective, SWCurveConfig}; +use ark_ff::PrimeField; use cairo_native::starknet::{ BlockInfo, ExecutionInfo, @@ -14,6 +18,7 @@ use cairo_native::starknet::{ TxV2Info, U256, }; +use cairo_native::starknet_stub::u256_to_biguint; use cairo_vm::vm::runners::cairo_runner::ExecutionResources; use starknet_api::contract_class::EntryPointType; use starknet_api::core::{ @@ -45,6 +50,7 @@ use crate::execution::entry_point::{ use crate::execution::errors::EntryPointExecutionError; use crate::execution::execution_utils::execute_deployment; use crate::execution::native::utils::{calculate_resource_bounds, default_tx_v2_info}; +use crate::execution::secp; use crate::execution::syscalls::exceeds_event_size_limit; use crate::execution::syscalls::hint_processor::{ SyscallExecutionError, @@ -700,3 +706,85 @@ impl<'state> StarknetSyscallHandler for &mut NativeSyscallHandler<'state> { Ok(()) } } + +/// A wrapper around an elliptic curve point in affine coordinates (x,y) on a +/// short Weierstrass curve, specifically for Secp256k1/r1 curves. +/// +/// This type provides a unified interface for working with points on both +/// secp256k1 and secp256r1 curves through the generic `Curve` parameter. +#[derive(PartialEq, Clone, Copy)] +struct Secp256Point(Affine); + +// todo(xrvdg) remove dead_code annotation after adding syscalls +#[allow(dead_code)] +impl Secp256Point +where + Curve::BaseField: PrimeField, // constraint for get_point_by_id +{ + fn wrap_secp_result( + result: Result, SyscallExecutionError>, + ) -> Result>, SyscallExecutionError> + where + T: Into>, + { + match result { + Ok(None) => Ok(None), + Ok(Some(point)) => Ok(Some(Secp256Point(point.into()))), + Err(error) => Err(error), + } + } + + /// Given an (x, y) pair, this function: + /// - Returns the point at infinity for (0, 0). + /// - Returns `Err` if either `x` or `y` is outside the modulus. + /// - Returns `Ok(None)` if (x, y) are within the modulus but not on the curve. + /// - Ok(Some(Point)) if (x,y) are on the curve. + fn new(x: U256, y: U256) -> Result, SyscallExecutionError> { + let x = u256_to_biguint(x); + let y = u256_to_biguint(y); + + Self::wrap_secp_result(secp::new_affine(x, y)) + } + + fn add(p0: Self, p1: Self) -> Self { + let result: Projective = p0.0 + p1.0; + Secp256Point(result.into()) + } + + fn mul(p: Self, m: U256) -> Self { + let result = p.0 * Curve::ScalarField::from(u256_to_biguint(m)); + Secp256Point(result.into()) + } + + fn get_point_from_x(x: U256, y_parity: bool) -> Result, SyscallExecutionError> { + let x = u256_to_biguint(x); + + Self::wrap_secp_result(secp::get_point_from_x(x, y_parity)) + } +} + +impl fmt::Debug for Secp256Point { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Secp256Point").field(&self.0).finish() + } +} + +#[cfg(test)] +mod test { + use cairo_native::starknet::U256; + + use crate::execution::native::syscall_handler::Secp256Point; + + #[test] + fn infinity_test() { + let p1 = + Secp256Point::::get_point_from_x(U256 { lo: 1, hi: 0 }, false) + .unwrap() + .unwrap(); + + let p2 = Secp256Point::mul(p1, U256 { lo: 0, hi: 0 }); + assert!(p2.0.infinity); + + assert_eq!(p1, Secp256Point::add(p1, p2)); + } +} diff --git a/crates/blockifier/src/execution/secp.rs b/crates/blockifier/src/execution/secp.rs new file mode 100644 index 0000000000..7101b3a1ba --- /dev/null +++ b/crates/blockifier/src/execution/secp.rs @@ -0,0 +1,80 @@ +use ark_ec::short_weierstrass::{Affine, SWCurveConfig}; +use ark_ff::{PrimeField, Zero}; +use starknet_types_core::felt::Felt; + +use super::syscalls::hint_processor::{SyscallExecutionError, INVALID_ARGUMENT}; + +pub fn get_point_from_x( + x: num_bigint::BigUint, + y_parity: bool, +) -> Result>, SyscallExecutionError> +where + Curve::BaseField: PrimeField, // constraint for get_point_by_id +{ + modulus_bound_check::(&[&x])?; + + let x = x.into(); + let maybe_ec_point = Affine::::get_ys_from_x_unchecked(x) + .map(|(smaller, greater)| { + // Return the correct y coordinate based on the parity. + if ark_ff::BigInteger::is_odd(&smaller.into_bigint()) == y_parity { + smaller + } else { + greater + } + }) + .map(|y| Affine::::new_unchecked(x, y)) + .filter(|p| p.is_in_correct_subgroup_assuming_on_curve()); + + Ok(maybe_ec_point) +} + +pub fn new_affine( + x: num_bigint::BigUint, + y: num_bigint::BigUint, +) -> Result>, SyscallExecutionError> +where + Curve::BaseField: PrimeField, // constraint for get_point_by_id +{ + modulus_bound_check::(&[&x, &y])?; + + Ok(maybe_affine(x.into(), y.into())) +} + +fn modulus_bound_check( + bounds: &[&num_bigint::BigUint], +) -> Result<(), SyscallExecutionError> +where + Curve::BaseField: PrimeField, // constraint for get_point_by_id +{ + let modulus = Curve::BaseField::MODULUS.into(); + + if bounds.iter().any(|p| **p >= modulus) { + let error = match Felt::from_hex(INVALID_ARGUMENT) { + Ok(err) => SyscallExecutionError::SyscallError { error_data: vec![err] }, + Err(err) => SyscallExecutionError::from(err), + }; + + return Err(error); + } + + Ok(()) +} + +/// Variation on [`Affine::new`] that doesn't panic and maps (x,y) = (0,0) -> infinity +fn maybe_affine( + x: Curve::BaseField, + y: Curve::BaseField, +) -> Option> { + let ec_point = if x.is_zero() && y.is_zero() { + Affine::::identity() + } else { + Affine::::new_unchecked(x, y) + }; + + if ec_point.is_on_curve() && ec_point.is_in_correct_subgroup_assuming_on_curve() { + Some(ec_point) + } else { + None + } +} diff --git a/crates/blockifier/src/execution/syscalls/secp.rs b/crates/blockifier/src/execution/syscalls/secp.rs index d23bbd15dc..04d30dbe18 100644 --- a/crates/blockifier/src/execution/syscalls/secp.rs +++ b/crates/blockifier/src/execution/syscalls/secp.rs @@ -1,19 +1,15 @@ -use ark_ec::short_weierstrass; -use ark_ec::short_weierstrass::SWCurveConfig; -use ark_ff::{BigInteger, PrimeField}; +use ark_ec::short_weierstrass::{self, SWCurveConfig}; +use ark_ff::PrimeField; use cairo_vm::types::relocatable::Relocatable; use cairo_vm::vm::vm_core::VirtualMachine; use num_bigint::BigUint; -use num_traits::{ToPrimitive, Zero}; +use num_traits::ToPrimitive; use starknet_types_core::felt::Felt; use crate::abi::sierra_types::{SierraType, SierraU256}; use crate::execution::execution_utils::{felt_from_ptr, write_maybe_relocatable, write_u256}; -use crate::execution::syscalls::hint_processor::{ - felt_to_bool, - SyscallHintProcessor, - INVALID_ARGUMENT, -}; +use crate::execution::secp::new_affine; +use crate::execution::syscalls::hint_processor::{felt_to_bool, SyscallHintProcessor}; use crate::execution::syscalls::{ SyscallExecutionError, SyscallRequest, @@ -40,8 +36,8 @@ where } pub fn secp_mul(&mut self, request: SecpMulRequest) -> SyscallResult { - let ep_point = self.get_point_by_id(request.ec_point_id)?; - let result = *ep_point * Curve::ScalarField::from(request.multiplier); + let ec_point = self.get_point_by_id(request.ec_point_id)?; + let result = *ec_point * Curve::ScalarField::from(request.multiplier); let ec_point_id = self.allocate_point(result.into()); Ok(SecpOpRespone { ec_point_id }) } @@ -50,26 +46,9 @@ where &mut self, request: SecpGetPointFromXRequest, ) -> SyscallResult { - let modulos = Curve::BaseField::MODULUS.into(); - - if request.x >= modulos { - return Err(SyscallExecutionError::SyscallError { - error_data: vec![ - Felt::from_hex(INVALID_ARGUMENT).map_err(SyscallExecutionError::from)?, - ], - }); - } - - let x = request.x.into(); - let maybe_ec_point = short_weierstrass::Affine::::get_ys_from_x_unchecked(x) - .map(|(smaller, greater)| { - // Return the correct y coordinate based on the parity. - if smaller.into_bigint().is_odd() == request.y_parity { smaller } else { greater } - }) - .map(|y| short_weierstrass::Affine::::new_unchecked(x, y)) - .filter(|p| p.is_in_correct_subgroup_assuming_on_curve()); - - Ok(SecpGetPointFromXResponse { + let affine = crate::execution::secp::get_point_from_x(request.x, request.y_parity); + + affine.map(|maybe_ec_point| SecpGetPointFromXResponse { optional_ec_point_id: maybe_ec_point.map(|ec_point| self.allocate_point(ec_point)), }) } @@ -81,27 +60,10 @@ where } pub fn secp_new(&mut self, request: SecpNewRequest) -> SyscallResult { - let modulos = Curve::BaseField::MODULUS.into(); - let (x, y) = (request.x, request.y); - if x >= modulos || y >= modulos { - return Err(SyscallExecutionError::SyscallError { - error_data: vec![ - Felt::from_hex(INVALID_ARGUMENT).map_err(SyscallExecutionError::from)?, - ], - }); - } - let ec_point = if x.is_zero() && y.is_zero() { - short_weierstrass::Affine::::identity() - } else { - short_weierstrass::Affine::::new_unchecked(x.into(), y.into()) - }; - let optional_ec_point_id = - if ec_point.is_on_curve() && ec_point.is_in_correct_subgroup_assuming_on_curve() { - Some(self.allocate_point(ec_point)) - } else { - None - }; - Ok(SecpNewResponse { optional_ec_point_id }) + let affine = new_affine::(request.x, request.y)?; + Ok(SecpNewResponse { + optional_ec_point_id: affine.map(|ec_point| self.allocate_point(ec_point)), + }) } fn allocate_point(&mut self, ec_point: short_weierstrass::Affine) -> usize {