diff --git a/secp_test.cairo b/secp_test.cairo new file mode 100644 index 000000000..bdbd8316e --- /dev/null +++ b/secp_test.cairo @@ -0,0 +1,28 @@ +use starknet::{secp256k1, secp256r1}; + +#[starknet::interface] +trait ISecp256k1MulTest { + fn secp256k1_mul_test(self: @TContractState, n: u256) -> (u256, u256); +} + +#[starknet::contract] +mod Secp256k1MulTest { + use core::starknet::secp256_trait::Secp256PointTrait; +use core::result::ResultTrait; +use starknet::{secp256k1, secp256r1}; + use starknet::secp256k1::Secp256k1Impl; + + #[storage] + struct Storage {} + + #[abi(embed_v0)] + impl Secp256k1MulTest of super::ISecp256k1MulTest { + + fn secp256k1_mul_test(self: @ContractState, n: u256) -> (u256, u256) { + let point: secp256k1::Secp256k1Point = Secp256k1Impl::get_generator_point(); + let scalar: u256 = u256 { high: 0, low: 1 }; + let result = secp256k1::secp256k1_mul_syscall(point, scalar); + result.unwrap().get_coordinates().unwrap() + } + } +} diff --git a/src/syscalls/business_logic_syscall_handler.rs b/src/syscalls/business_logic_syscall_handler.rs index 1c5a46245..34be06c36 100644 --- a/src/syscalls/business_logic_syscall_handler.rs +++ b/src/syscalls/business_logic_syscall_handler.rs @@ -4,7 +4,8 @@ use super::{ syscall_request::{ CallContractRequest, DeployRequest, EmitEventRequest, FromPtr, GetBlockHashRequest, GetBlockTimestampRequest, KeccakRequest, LibraryCallRequest, ReplaceClassRequest, - SendMessageToL1Request, StorageReadRequest, StorageWriteRequest, SyscallRequest, + SecpAddRequest, SendMessageToL1Request, StorageReadRequest, StorageWriteRequest, + SyscallRequest, }, syscall_response::{ CallContractResponse, DeployResponse, FailureReason, GetBlockHashResponse, @@ -40,7 +41,7 @@ use crate::{ BlockInfo, ExecutionResourcesManager, }, transaction::{error::TransactionError, Address, ClassHash}, - utils::{calculate_sn_keccak, felt_to_hash, get_big_int, get_felt_range}, + utils::{felt_to_hash, get_big_int, get_felt_range}, }; use cairo_vm::Felt252; use cairo_vm::{ @@ -63,11 +64,19 @@ use { std::{cell::RefCell, rc::Rc}, }; -pub(crate) const STEP: u128 = 100; -pub(crate) const SYSCALL_BASE: u128 = 100 * STEP; -pub(crate) const KECCAK_ROUND_COST: u128 = 180000; - lazy_static! { + static ref SYSCALLS: Vec = Vec::from([ + "emit_event".to_string(), + "deploy".to_string(), + "get_tx_info".to_string(), + "send_message_to_l1".to_string(), + "library_call".to_string(), + "get_caller_address".to_string(), + "get_contract_address".to_string(), + "get_sequencer_address".to_string(), + "get_block_timestamp".to_string(), + ]); + /// Felt->syscall map that was extracted from new_syscalls.json (Cairo 1.0 syscalls) static ref SELECTOR_TO_SYSCALL: HashMap = { let mut map: HashMap = HashMap::with_capacity(9); @@ -88,10 +97,27 @@ lazy_static! { map.insert(75202468540281_u128.into(), "deploy"); map.insert(1280709301550335749748_u128.into(), "emit_event"); map.insert(25828017502874050592466629733_u128.into(), "storage_write"); - map.insert(Felt252::from_bytes_be(&calculate_sn_keccak("get_block_timestamp".as_bytes())), "get_block_timestamp"); - map.insert(Felt252::from_bytes_be(&calculate_sn_keccak("get_block_number".as_bytes())), "get_block_number"); + map.insert(Felt252::from_bytes_be_slice("get_block_timestamp".as_bytes()), "get_block_timestamp"); + map.insert(Felt252::from_bytes_be_slice("get_block_number".as_bytes()), "get_block_number"); map.insert(Felt252::from_bytes_be_slice("Keccak".as_bytes()), "keccak"); + // SECP256k1 syscalls + let secp_syscalls = [ + ("Secp256k1New", "secp256k1_new"), + ("Secp256k1Add", "secp256k1_add"), + ("Secp256k1Mul", "secp256k1_mul"), + ("Secp256k1GetPointFromX", "secp256k1_get_point_from_x"), + ("Secp256k1GetXy", "secp256k1_get_xy"), + ("Secp256r1New", "secp256r1_new"), + ("Secp256r1Add", "secp256r1_add"), + ("Secp256r1Mul", "secp256r1_mul"), + ("Secp256r1GetPointFromX", "secp256r1_get_point_from_x"), + ("Secp256r1GetXy", "secp256r1_get_xy") + ]; + + for (syscall, syscall_name) in secp_syscalls { + map.insert(Felt252::from_bytes_be_slice(syscall.as_bytes()), syscall_name); + } map }; @@ -122,6 +148,20 @@ lazy_static! { map.insert("keccak", 0); map.insert("get_block_hash", SYSCALL_BASE + 50 * STEP); + // Secp256k1 + map.insert("secp256k1_add", 406 * STEP + 29 * RANGE_CHECK); + map.insert("secp256k1_get_point_from_x", 391 * STEP + 30 * RANGE_CHECK + 20 * MEMORY_HOLE); + map.insert("secp256k1_get_xy", 239 * STEP + 11 * RANGE_CHECK + 40 * MEMORY_HOLE); + map.insert("secp256k1_mul", 76501 * STEP + 7045 * RANGE_CHECK + 2 * MEMORY_HOLE); + map.insert("secp256k1_new", 475 * STEP + 35 * RANGE_CHECK + 40 * MEMORY_HOLE); + + // Secp256r1 + map.insert("secp256r1_add", 589 * STEP + 57 * RANGE_CHECK); + map.insert("secp256r1_get_point_from_x", 510 * STEP + 44 * RANGE_CHECK + 20 * MEMORY_HOLE); + map.insert("secp256r1_get_xy", 241 * STEP + 11 * RANGE_CHECK + 40 * MEMORY_HOLE); + map.insert("secp256r1_mul", 125340 * STEP + 13961 * RANGE_CHECK + 2 * MEMORY_HOLE); + map.insert("secp256r1_new", 594 * STEP + 49 * RANGE_CHECK + 40 * MEMORY_HOLE); + map }; } @@ -218,30 +258,11 @@ impl<'a, S: StateReader, C: ContractClassCache> BusinessLogicSyscallHandler<'a, _contract_address: Address, state: &'a mut CachedState, ) -> Self { - let syscalls = Vec::from([ - // Emits an event with a given set of keys and data. - "emit_event".to_string(), - // Deploys a new instance of a previously declared class. - "deploy".to_string(), - // Gets information about the original transaction. - "get_tx_info".to_string(), - // Sends a message to L1. - "send_message_to_l1".to_string(), - // Calls the requested function in any previously declared class. - "library_call".to_string(), - // Returns the address of the calling contract, or 0 if the call was not initiated by another contract. - "get_caller_address".to_string(), - // Gets the address of the contract who raised the system call. - "get_contract_address".to_string(), - // Returns the address of the sequencer that generated the current block. - "get_sequencer_address".to_string(), - // Gets the timestamp of the block in which the transaction is executed. - "get_block_timestamp".to_string(), - ]); let events = Vec::new(); let tx_execution_context = Default::default(); let read_only_segments = Vec::new(); - let resources_manager = ExecutionResourcesManager::new(syscalls, Default::default()); + let resources_manager = + ExecutionResourcesManager::new(SYSCALLS.clone(), Default::default()); let contract_address = Address(1.into()); let caller_address = Address(0.into()); let l2_to_l1_messages = Vec::new(); @@ -1079,6 +1100,17 @@ impl<'a, S: StateReader, C: ContractClassCache> BusinessLogicSyscallHandler<'a, "send_message_to_l1" => SendMessageToL1Request::from_ptr(vm, syscall_ptr), "replace_class" => ReplaceClassRequest::from_ptr(vm, syscall_ptr), "keccak" => KeccakRequest::from_ptr(vm, syscall_ptr), + "secp256k1_add" => SecpAddRequest::from_ptr(vm, syscall_ptr), + "secp256r1_add" => SecpAddRequest::from_ptr(vm, syscall_ptr), + // "secp256k1_get_point_from_x" => Secp256, + // "secp256k1_get_xy".to_string(), + // "secp256k1_get_xy".to_string(), + // "secp256k1_mul".to_string(), + // "secp256k1_new".to_string(), + // "secp256r1_get_point_from_x".to_string(), + // "secp256r1_get_xy".to_string(), + // "secp256r1_mul".to_string(), + // "secp256r1_new".to_string(), _ => Err(SyscallHandlerError::UnknownSyscall( syscall_name.to_string(), )), diff --git a/src/syscalls/mod.rs b/src/syscalls/mod.rs index 48a332316..c8cbf02c5 100644 --- a/src/syscalls/mod.rs +++ b/src/syscalls/mod.rs @@ -7,6 +7,7 @@ pub mod hint_code; #[cfg(feature = "cairo-native")] pub mod native_syscall_handler; pub mod other_syscalls; +pub mod secp_syscall_handler; pub mod syscall_handler; pub mod syscall_handler_errors; pub mod syscall_info; diff --git a/src/syscalls/secp_syscall_handler.rs b/src/syscalls/secp_syscall_handler.rs new file mode 100644 index 000000000..8e5e6c97e --- /dev/null +++ b/src/syscalls/secp_syscall_handler.rs @@ -0,0 +1,33 @@ +use cairo_vm::Felt252; +use lazy_static::lazy_static; + +lazy_static! { + static ref SECP_SYSCALLS: Vec = vec![ + "secp256k1_add".to_string(), + "secp256r1_add".to_string(), + "secp256k1_get_xy".to_string(), + "secp256r1_get_xy".to_string(), + "secp256k1_mul".to_string(), + "secp256r1_mul".to_string(), + "secp256k1_new".to_string(), + "secp256r1_new".to_string(), + "secp256r1_get_point_from_x".to_string(), + "secp256k1_get_point_from_x".to_string() + ]; +} + +pub(crate) const SECP_STEP: u128 = 100; +pub(crate) const SYSCALL_BASE: u128 = 100 * STEP; +pub(crate) const KECCAK_ROUND_COST: u128 = 180000; +pub(crate) const RANGE_CHECK: u128 = 70; +pub(crate) const MEMORY_HOLE: u128 = 10; + +pub struct Secp256SyscallHandler { + points: Vec, +} + +impl Secp256SyscallHandler { + pub fn secp_add(&self) -> Felt252 { + unimplemented!() + } +} diff --git a/src/syscalls/syscall_request.rs b/src/syscalls/syscall_request.rs index fe07e6479..37f9e4ee0 100644 --- a/src/syscalls/syscall_request.rs +++ b/src/syscalls/syscall_request.rs @@ -48,12 +48,34 @@ pub(crate) enum SyscallRequest { ReplaceClass(ReplaceClassRequest), /// Computes the Keccak256 hash of the given data. Keccak(KeccakRequest), + /// secp256 operations + Secp256k1New(Secp256k1NewRequest), + Secp256Add(SecpAddRequest), + Secp256k1Mul(Secp256k1MulRequest), + Secp256k1GetPointFromX(Secp256k1GetPointFromXRequest), + Secp256k1GetXy(Secp256k1GetXyRequest), + /// + Secp256r1New(Secp256r1NewRequest), + Secp256r1Mul(Secp256r1MulRequest), + Secp256r1GetPointFromX(Secp256r1GetPointFromXRequest), + Secp256r1GetXy(Secp256r1GetXyRequest), } // ~~~~~~~~~~~~~~~~~~~~~~~~~ // SyscallRequest variants // ~~~~~~~~~~~~~~~~~~~~~~~~~ +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct SecpAddRequest { + pub lhs_id: Felt252, + pub rhs_id: Felt252, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct SecpOpResponse { + pub ec_point_id: usize, +} + /// Gets the timestamp of the block in which the transaction is executed. #[derive(Clone, Debug, PartialEq)] pub(crate) struct GetBlockTimestampRequest {} @@ -263,6 +285,21 @@ pub(crate) trait FromPtr { ) -> Result; } +impl FromPtr for SecpAddRequest { + fn from_ptr( + vm: &VirtualMachine, + syscall_ptr: Relocatable, + ) -> Result { + let lhs = vm.get_integer(syscall_ptr)?; + let rhs = vm.get_integer(syscall_ptr)?; + let operation = SecpAddRequest { + lhs_id: *lhs, + rhs_id: *rhs, + }; + Ok(SyscallRequest::Secp256Add(operation)) + } +} + impl FromPtr for ReplaceClassRequest { fn from_ptr( vm: &VirtualMachine, diff --git a/starknet_programs/cairo2/secp_test.cairo b/starknet_programs/cairo2/secp_test.cairo new file mode 100644 index 000000000..bdbd8316e --- /dev/null +++ b/starknet_programs/cairo2/secp_test.cairo @@ -0,0 +1,28 @@ +use starknet::{secp256k1, secp256r1}; + +#[starknet::interface] +trait ISecp256k1MulTest { + fn secp256k1_mul_test(self: @TContractState, n: u256) -> (u256, u256); +} + +#[starknet::contract] +mod Secp256k1MulTest { + use core::starknet::secp256_trait::Secp256PointTrait; +use core::result::ResultTrait; +use starknet::{secp256k1, secp256r1}; + use starknet::secp256k1::Secp256k1Impl; + + #[storage] + struct Storage {} + + #[abi(embed_v0)] + impl Secp256k1MulTest of super::ISecp256k1MulTest { + + fn secp256k1_mul_test(self: @ContractState, n: u256) -> (u256, u256) { + let point: secp256k1::Secp256k1Point = Secp256k1Impl::get_generator_point(); + let scalar: u256 = u256 { high: 0, low: 1 }; + let result = secp256k1::secp256k1_mul_syscall(point, scalar); + result.unwrap().get_coordinates().unwrap() + } + } +} diff --git a/tests/integration_tests/mod.rs b/tests/integration_tests/mod.rs index 1428f1723..4f3df663a 100644 --- a/tests/integration_tests/mod.rs +++ b/tests/integration_tests/mod.rs @@ -10,6 +10,7 @@ pub mod increase_balance; pub mod internal_calls; pub mod internals; pub mod multi_syscall_test; +pub mod secp; pub mod storage; pub mod syscalls; pub mod syscalls_errors; diff --git a/tests/integration_tests/secp.rs b/tests/integration_tests/secp.rs new file mode 100644 index 000000000..08e8caf17 --- /dev/null +++ b/tests/integration_tests/secp.rs @@ -0,0 +1,132 @@ +// #![deny(warnings)] + +use cairo_lang_starknet::casm_contract_class::CasmContractClass; +use cairo_vm::{ + vm::runners::{builtin_runner::RANGE_CHECK_BUILTIN_NAME, cairo_runner::ExecutionResources}, + Felt252, +}; + +use pretty_assertions_sorted::assert_eq; +use starknet_in_rust::{ + definitions::{block_context::BlockContext, constants::TRANSACTION_VERSION}, + execution::{ + execution_entry_point::ExecutionEntryPoint, CallInfo, CallType, TransactionExecutionContext, + }, + services::api::contract_classes::compiled_class::CompiledClass, + state::{ + cached_state::CachedState, + contract_class_cache::{ContractClassCache, PermanentContractClassCache}, + in_memory_state_reader::InMemoryStateReader, + ExecutionResourcesManager, + }, + transaction::{Address, ClassHash}, + EntryPointType, +}; +use std::{collections::HashMap, sync::Arc}; + +#[test] +fn secp_syscall_test() { + // Create program and entry point types for contract class + let program_data = include_bytes!("../../starknet_programs/cairo2/secp.casm"); + + let contract_class: CasmContractClass = serde_json::from_slice(program_data).unwrap(); + let entrypoints = contract_class.clone().entry_points_by_type; + let secp_entrypoint_selector = &entrypoints.external.get(0).unwrap().selector; + + // Create state reader with class hash data + let contract_class_cache = PermanentContractClassCache::default(); + + let address = Address(1111.into()); + let class_hash: ClassHash = ClassHash([1; 32]); + let nonce = Felt252::ZERO; + + contract_class_cache.set_contract_class( + class_hash, + CompiledClass::Casm { + casm: Arc::new(contract_class), + sierra: None, + }, + ); + let mut state_reader = InMemoryStateReader::default(); + state_reader + .address_to_class_hash_mut() + .insert(address.clone(), class_hash); + state_reader + .address_to_nonce_mut() + .insert(address.clone(), nonce); + + // Create state from the state_reader and contract cache. + let mut state = CachedState::new(Arc::new(state_reader), Arc::new(contract_class_cache)); + + // Create an execution entry point + let calldata: Vec = [0.into(), Felt252::from_hex("0x6d921cc3a0edd").unwrap()].to_vec(); + let caller_address = Address(0000.into()); + let entry_point_type = EntryPointType::External; + + let exec_entry_point = ExecutionEntryPoint::new( + address, + calldata.clone(), + Felt252::from(secp_entrypoint_selector), + caller_address, + entry_point_type, + Some(CallType::Delegate), + Some(class_hash), + 100000, + ); + + // Execute the entrypoint + let block_context = BlockContext::default(); + let mut tx_execution_context = TransactionExecutionContext::new( + Address(0.into()), + Felt252::ZERO, + Vec::new(), + Default::default(), + 10.into(), + block_context.invoke_tx_max_n_steps(), + *TRANSACTION_VERSION, + ); + let mut resources_manager = ExecutionResourcesManager::default(); + + // expected results + let expected_call_info = CallInfo { + caller_address: Address(0.into()), + call_type: Some(CallType::Delegate), + contract_address: Address(1111.into()), + entry_point_selector: Some(Felt252::from(secp_entrypoint_selector)), + entry_point_type: Some(EntryPointType::External), + calldata, + retdata: [144.into()].to_vec(), + execution_resources: Some(ExecutionResources { + n_steps: 301, + n_memory_holes: 0, + builtin_instance_counter: HashMap::from([(RANGE_CHECK_BUILTIN_NAME.to_string(), 15)]), + }), + class_hash: Some(class_hash), + gas_consumed: 23020, + ..Default::default() + }; + let callinfo = exec_entry_point + .execute( + &mut state, + &block_context, + &mut resources_manager, + &mut tx_execution_context, + false, + block_context.invoke_tx_max_n_steps(), + #[cfg(feature = "cairo-native")] + None, + ) + .unwrap() + .call_info + .unwrap(); + + let retdata: Vec = callinfo + .retdata + .clone() + .into_iter() + .map(|e| format!("{e}")) + .collect(); + dbg!(format!("{:?}", retdata)); + + assert_eq!(callinfo, expected_call_info); +}