diff --git a/Cargo.lock b/Cargo.lock index 6976dd01ec..4f8a9152e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,6 +150,23 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits 0.2.18", + "zeroize", +] + [[package]] name = "ark-ff" version = "0.4.2" @@ -193,17 +210,53 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-secp256k1" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c02e954eaeb4ddb29613fee20840c2bbc85ca4396d53e33837e11905363c5f2" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-serialize" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ + "ark-serialize-derive", "ark-std", "digest", "num-bigint", ] +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-std" version = "0.4.0" @@ -907,6 +960,7 @@ dependencies = [ "anyhow", "arbitrary", "ark-ff", + "ark-secp256k1", "ark-std", "assert_matches", "bincode", @@ -1596,6 +1650,15 @@ dependencies = [ "ahash 0.7.8", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.9", +] + [[package]] name = "hashbrown" version = "0.14.3" diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 5f6bc49c16..dc43a9ad63 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -76,6 +76,7 @@ arbitrary = { workspace = true, features = ["derive"], optional = true } # Used to derive clap traits for CLIs clap = { version = "4.3.10", features = ["derive"], optional = true} +ark-secp256k1 = "0.4.0" [dev-dependencies] assert_matches = "1.5.0" diff --git a/vm/src/hint_processor/cairo_1_hint_processor/hint_processor.rs b/vm/src/hint_processor/cairo_1_hint_processor/hint_processor.rs index d05006912a..a30711608a 100644 --- a/vm/src/hint_processor/cairo_1_hint_processor/hint_processor.rs +++ b/vm/src/hint_processor/cairo_1_hint_processor/hint_processor.rs @@ -266,13 +266,101 @@ impl Cairo1HintProcessor { t_or_k0, t_or_k1, ), - + Hint::Starknet(cairo_lang_casm::hints::StarknetHint::SystemCall { system }) => { + Ok(self.execute_syscall(system, vm, exec_scopes)?) + } hint => Err(HintError::UnknownHint( format!("{:?}", hint).into_boxed_str(), )), } } + /// Executes a syscall. + fn execute_syscall( + &self, + system: &ResOperand, + vm: &mut VirtualMachine, + exec_scopes: &mut ExecutionScopes, + ) -> Result<(), HintError> { + let system_ptr = as_relocatable(vm, system)?; + let mut system_buffer = super::memory_buffer::MemBuffer::new(vm, system_ptr); + let selector = system_buffer.next_felt252()?.to_bytes_be(); + let mut gas_counter = system_buffer.next_usize()?; + let mut execute_handle_helper = + |handler: &mut dyn FnMut( + // The syscall buffer. + &mut super::memory_buffer::MemBuffer<'_>, + // The gas counter. + &mut usize, + ) + -> Result| { + match handler(&mut system_buffer, &mut gas_counter)? { + super::syscall::SyscallResult::Success(values) => { + system_buffer.write(gas_counter)?; + system_buffer.write(Felt252::from(0))?; + system_buffer.write_data(values.into_iter())?; + } + super::syscall::SyscallResult::Failure(revert_reason) => { + println!("syscall failed!"); + system_buffer.write(gas_counter)?; + system_buffer.write(Felt252::from(1))?; + system_buffer.write_arr(revert_reason.into_iter())?; + } + } + Ok(()) + }; + let selector = std::str::from_utf8(&selector) + .unwrap() + .trim_start_matches('\0'); + // *self + // .syscalls_used_resources + // .syscalls + // .entry(selector.into()) + // .or_default() += 1; + match selector { + "Secp256k1New" => execute_handle_helper(&mut |system_buffer, gas_counter| { + super::syscall::secp256k1_new( + gas_counter, + system_buffer.next_u256()?, + system_buffer.next_u256()?, + exec_scopes, + ) + }), + "Secp256k1Add" => execute_handle_helper(&mut |system_buffer, gas_counter| { + super::syscall::secp256k1_add( + gas_counter, + exec_scopes, + system_buffer.next_usize()?, + system_buffer.next_usize()?, + ) + }), + "Secp256k1Mul" => execute_handle_helper(&mut |system_buffer, gas_counter| { + super::syscall::secp256k1_mul( + gas_counter, + system_buffer.next_usize()?, + system_buffer.next_u256()?, + exec_scopes, + ) + }), + "Secp256k1GetPointFromX" => execute_handle_helper(&mut |system_buffer, gas_counter| { + super::syscall::secp256k1_get_point_from_x( + gas_counter, + system_buffer.next_u256()?, + system_buffer.next_bool()?, + exec_scopes, + ) + }), + "Secp256k1GetXy" => execute_handle_helper(&mut |system_buffer, gas_counter| { + super::syscall::secp256k1_get_xy( + gas_counter, + system_buffer.next_usize()?, + exec_scopes, + ) + }), + _ => panic!("Unknown selector for system call!"), + } + } + fn alloc_segment(&self, vm: &mut VirtualMachine, dst: &CellRef) -> Result<(), HintError> { let segment = vm.add_memory_segment(); vm.insert_value(cell_ref_to_relocatable(dst, vm)?, segment) diff --git a/vm/src/hint_processor/cairo_1_hint_processor/memory_buffer.rs b/vm/src/hint_processor/cairo_1_hint_processor/memory_buffer.rs new file mode 100644 index 0000000000..bd07b0ccb1 --- /dev/null +++ b/vm/src/hint_processor/cairo_1_hint_processor/memory_buffer.rs @@ -0,0 +1,141 @@ +use std::borrow::Cow; + +use crate::types::relocatable::MaybeRelocatable; +use crate::vm::errors::memory_errors::MemoryError; +use crate::Felt252; +use crate::{types::relocatable::Relocatable, vm::vm_core::VirtualMachine}; +use num_bigint::BigUint; +use num_traits::ToPrimitive; +use num_traits::Zero; +use std::ops::Shl; + +/// A helper struct to continuously write and read from a buffer in the VM memory. +pub struct MemBuffer<'a> { + /// The VM to write to. + /// This is a trait so that we would borrow the actual VM only once. + vm: &'a mut VirtualMachine, + /// The current location of the buffer. + pub ptr: Relocatable, +} +impl<'a> MemBuffer<'a> { + /// Creates a new buffer. + pub fn new(vm: &'a mut VirtualMachine, ptr: Relocatable) -> Self { + Self { vm, ptr } + } + + /// Creates a new segment and returns a buffer wrapping it. + pub fn new_segment(vm: &'a mut VirtualMachine) -> Self { + let ptr = vm.add_memory_segment(); + Self::new(vm, ptr) + } + + /// Returns the current position of the buffer and advances it by one. + fn next(&mut self) -> Relocatable { + let ptr = self.ptr; + self.ptr += 1; + ptr + } + + /// Returns the felt252 value in the current position of the buffer and advances it by one. + /// Fails if the value is not a felt252. + /// Borrows the buffer since a reference is returned. + pub fn next_felt252(&mut self) -> Result, MemoryError> { + let ptr = self.next(); + self.vm.get_integer(ptr) + } + + /// Returns the bool value in the current position of the buffer and advances it by one. + /// Fails with `MemoryError` if the value is not a felt252. + /// Panics if the value is not a bool. + pub fn next_bool(&mut self) -> Result { + let ptr = self.next(); + Ok(!(self.vm.get_integer(ptr)?.is_zero())) + } + + /// Returns the usize value in the current position of the buffer and advances it by one. + /// Fails with `MemoryError` if the value is not a felt252. + /// Panics if the value is not a usize. + pub fn next_usize(&mut self) -> Result { + Ok(self.next_felt252()?.to_usize().unwrap()) + } + + /// Returns the u128 value in the current position of the buffer and advances it by one. + /// Fails with `MemoryError` if the value is not a felt252. + /// Panics if the value is not a u128. + pub fn next_u128(&mut self) -> Result { + Ok(self.next_felt252()?.to_u128().unwrap()) + } + + /// Returns the u64 value in the current position of the buffer and advances it by one. + /// Fails with `MemoryError` if the value is not a felt252. + /// Panics if the value is not a u64. + #[allow(unused)] + pub fn next_u64(&mut self) -> Result { + Ok(self.next_felt252()?.to_u64().unwrap()) + } + + /// Returns the u256 value encoded starting from the current position of the buffer and advances + /// it by two. + /// Fails with `MemoryError` if any of the next two values are not felt252s. + /// Panics if any of the next two values are not u128. + pub fn next_u256(&mut self) -> Result { + Ok(self.next_u128()? + BigUint::from(self.next_u128()?).shl(128)) + } + + /// Returns the address value in the current position of the buffer and advances it by one. + /// Fails if the value is not an address. + #[allow(unused)] + pub fn next_addr(&mut self) -> Result { + let ptr = self.next(); + self.vm.get_relocatable(ptr) + } + + // /// Returns the array of integer values pointed to by the two next addresses in the buffer and + // /// advances it by two. Will fail if the two values are not addresses or if the addresses do + // /// not point to an array of integers. + // pub fn next_arr(&mut self) -> Result, HintError> { + // let start = self.next_addr()?; + // let end = self.next_addr()?; + // vm_get_range(self.vm, start, end) + // } + + /// Writes a value to the current position of the buffer and advances it by one. + pub fn write>(&mut self, value: T) -> Result<(), MemoryError> { + let ptr = self.next(); + self.vm.insert_value(ptr, value) + } + /// Writes an iterator of values starting from the current position of the buffer and advances + /// it to after the end of the written value. + pub fn write_data, Data: Iterator>( + &mut self, + data: Data, + ) -> Result<(), MemoryError> { + for value in data { + self.write(value)?; + } + Ok(()) + } + + /// Writes an array into a new segment and writes the start and end pointers to the current + /// position of the buffer. Advances the buffer by two. + pub fn write_arr, Data: Iterator>( + &mut self, + data: Data, + ) -> Result<(), MemoryError> { + let (start, end) = segment_with_data(self.vm, data)?; + self.write(start)?; + self.write(end) + } +} + +/// Creates a new segment in the VM memory and writes data to it, returning the start and end +/// pointers of the segment. +fn segment_with_data, Data: Iterator>( + vm: &mut VirtualMachine, + data: Data, +) -> Result<(Relocatable, Relocatable), MemoryError> { + let mut segment = MemBuffer::new_segment(vm); + let start = segment.ptr; + segment.write_data(data)?; + Ok((start, segment.ptr)) +} diff --git a/vm/src/hint_processor/cairo_1_hint_processor/mod.rs b/vm/src/hint_processor/cairo_1_hint_processor/mod.rs index 29d5f47bd3..4af0cc2d66 100644 --- a/vm/src/hint_processor/cairo_1_hint_processor/mod.rs +++ b/vm/src/hint_processor/cairo_1_hint_processor/mod.rs @@ -1,3 +1,5 @@ pub mod dict_manager; pub mod hint_processor; pub mod hint_processor_utils; +mod memory_buffer; +mod syscall; diff --git a/vm/src/hint_processor/cairo_1_hint_processor/syscall.rs b/vm/src/hint_processor/cairo_1_hint_processor/syscall.rs new file mode 100644 index 0000000000..7f15146999 --- /dev/null +++ b/vm/src/hint_processor/cairo_1_hint_processor/syscall.rs @@ -0,0 +1,164 @@ +use crate::types::{exec_scope::ExecutionScopes, relocatable::MaybeRelocatable}; +use crate::vm::errors::hint_errors::HintError; +use ark_ff::BigInteger; +use ark_ff::PrimeField; +use ark_secp256k1 as secp256k1; +use crate::Felt252; +use num_bigint::BigUint; +use num_integer::Integer; +use num_traits::Zero; +use starknet_types_core::felt::Felt; + +/// Resulting options from a syscall. +pub enum SyscallResult { + /// The syscall was successful. + Success(Vec), + /// The syscall failed, with the revert reason. + Failure(Vec), +} + +macro_rules! fail_syscall { + ($reason:expr) => { + let f = Felt252::from_bytes_be($reason); + return Ok(SyscallResult::Failure(vec![f])) + }; + ($existing:ident, $reason:expr) => { + $existing.push(Felt252::from_bytes_be($reason)); + return Ok(SyscallResult::Failure($existing)) + }; +} + +/// Executes the `secp256k1_new_syscall` syscall. +pub fn secp256k1_new( + #[allow(unused)] + gas_counter: &mut usize, + x: BigUint, + y: BigUint, + exec_scopes: &mut ExecutionScopes, +) -> Result { + let modulus = ::MODULUS.into(); + if x >= modulus || y >= modulus { + fail_syscall!(b"Coordinates out of range "); + } + let p = if x.is_zero() && y.is_zero() { + secp256k1::Affine::identity() + } else { + secp256k1::Affine::new_unchecked(x.into(), y.into()) + }; + Ok(SyscallResult::Success( + if !(p.is_on_curve() && p.is_in_correct_subgroup_assuming_on_curve()) { + vec![1.into(), 0.into()] + } else { + let ec = get_secp256k1_exec_scope(exec_scopes)?; + let id = ec.ec_points.len(); + ec.ec_points.push(p); + vec![0.into(), id.into()] + }, + )) +} + +/// Executes the `secp256k1_add_syscall` syscall. +pub fn secp256k1_add( + #[allow(unused)] + gas_counter: &mut usize, + exec_scopes: &mut ExecutionScopes, + p0_id: usize, + p1_id: usize, +) -> Result { + let ec = get_secp256k1_exec_scope(exec_scopes)?; + let p0 = &ec.ec_points[p0_id]; + let p1 = &ec.ec_points[p1_id]; + let sum = *p0 + *p1; + let id = ec.ec_points.len(); + ec.ec_points.push(sum.into()); + Ok(SyscallResult::Success(vec![id.into()])) +} + +/// Executes the `secp256k1_mul_syscall` syscall. +pub fn secp256k1_mul( + #[allow(unused)] + gas_counter: &mut usize, + p_id: usize, + scalar: BigUint, + exec_scopes: &mut ExecutionScopes, +) -> Result { + let ec = get_secp256k1_exec_scope(exec_scopes)?; + let p = &ec.ec_points[p_id]; + let product = *p * secp256k1::Fr::from(scalar); + let id = ec.ec_points.len(); + ec.ec_points.push(product.into()); + Ok(SyscallResult::Success(vec![id.into()])) +} + +/// Executes the `secp256k1_get_point_from_x_syscall` syscall. +pub fn secp256k1_get_point_from_x( + #[allow(unused)] + gas_counter: &mut usize, + x: BigUint, + y_parity: bool, + exec_scopes: &mut ExecutionScopes, +) -> Result { + if x >= ::MODULUS.into() { + fail_syscall!(b"Coordinates out of range "); + } + let x = x.into(); + let maybe_p = secp256k1::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() == y_parity { smaller } else { greater }, + ) + .map(|y| secp256k1::Affine::new_unchecked(x, y)) + .filter(|p| p.is_in_correct_subgroup_assuming_on_curve()); + let Some(p) = maybe_p else { + return Ok(SyscallResult::Success(vec![1.into(), 0.into()])); + }; + let ec = get_secp256k1_exec_scope(exec_scopes)?; + let id = ec.ec_points.len(); + ec.ec_points.push(p); + Ok(SyscallResult::Success(vec![0.into(), id.into()])) +} + +/// Executes the `secp256k1_get_xy_syscall` syscall. +pub fn secp256k1_get_xy( + #[allow(unused)] + gas_counter: &mut usize, + p_id: usize, + exec_scopes: &mut ExecutionScopes, +) -> Result { + let ec = get_secp256k1_exec_scope(exec_scopes)?; + let p = &ec.ec_points[p_id]; + let pow_2_128 = BigUint::from(u128::MAX) + 1u32; + let (x1, x0) = BigUint::from(p.x).div_rem(&pow_2_128); + let (y1, y0) = BigUint::from(p.y).div_rem(&pow_2_128); + Ok(SyscallResult::Success(vec![ + Felt::from(x0).into(), + Felt::from(x1).into(), + Felt::from(y0).into(), + Felt::from(y1).into(), + ])) +} + +/// Helper object to allocate and track Secp256k1 elliptic curve points. +#[derive(Default)] +struct Secp256k1ExecutionScope { + /// All elliptic curve points provided by the secp256k1 syscalls. + /// The id of a point is the index in the vector. + ec_points: Vec, +} + +/// Returns the `Secp256k1ExecScope` managing the different active points. +/// The first call to this function will create the scope, and subsequent calls will return it. +/// The first call would happen from some point creation syscall. +fn get_secp256k1_exec_scope( + exec_scopes: &mut ExecutionScopes, +) -> Result<&mut Secp256k1ExecutionScope, HintError> { + const NAME: &str = "secp256k1_exec_scope"; + if exec_scopes + .get_ref::(NAME) + .is_err() + { + exec_scopes.assign_or_update_variable(NAME, Box::::default()); + } + exec_scopes.get_mut_ref::(NAME) +}