From 22cb5186a8455463f7ad99ff9707bffac81dbf34 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 9 May 2023 18:02:43 +0300 Subject: [PATCH] feat: Move missing cairo-1 hints from starknet_in_rust repo (#1143) * Implement TryFrom for Program * Add cairo1hintprocessor dependency * Map hints to pc * Add Cairo1HintProcessor * Move cairo-1-hint-processor to cairo-rs crate * fixes * Fix test helper * Remove allow * fix test func * Add builtins to test * Extract builtins from contract_class * Add _builtin to builtin names in etrypoint data * Copy logic from cairo1 contract execution in starknet * Remove unused code * Use lower initial_gas value * Add program segment size argument * Check return values in run_cairo_1_entrypoint fn * Remove debug prints * Add basic fibonacci test * Add another fibonacci case * Always verify secure * Clippy * Compile casm contracts when running test target * Remove unwrap from cell_ref_to_relocatable * Remove paniking macro from extract_buffer * Misc improvements * Misc improvements * Misc improvements * Misc improvements * Remove unwraps & asserts from DictSquashExecScope::pop_current_key * Remove unwraps & asserts from DictManagerExecScope::new_default_dict * Remove expect from get_dict_tracker * Add constants for cairo 1 compiler binaries in makefile * Add cairo 1 compiler to deps target in makefile * Add cairo folder to clean target * Remove todo from execute method * Separate helper functions from Cairo1HintProcessor implementation * Add untracked file * Fix * Add changelog entry * Add a job to compile cairo 1 contracts in ci * Add job dependency * Fix yml syntax * Fix yml syntax * Temporarily extempt cairo-1-hint-processor from codecov * Fix yml syntax * Fix workflow * Remove cache code from new job * Fix yml syntax * Fix wrong path * Fix makefile * Build only compiler binaries * Add cairo-1-contracts-cache * Fetch contracts cache in jobs that need them * Use no-std version of HashMap * Import stdlib::prelude::* * Wrap print in not-wasm block * Remove std path * use slices instead of vec * Make DictSquashExecScope fields private * Import hint processor dependencies without default features * -Clippy * Add type * Compile cairo 1 contracts in build-programs job * Rename cache * Use target dependency instead of explicit $(MAKE) * Fix yml syntax * Check for cairo folder before cloning cairo repo * Ommit folder name * Swap paths * Add cairo-1-hints feature flag * Add compile-hint feature to tests run in workflow * Add cairo-1-hints to test_utils * Add cairo-1-hints to test_utils * Use both paths when fetching compiled test data * Remove cairo-1-hints feature from test_utils feature * Move dependencies to cairo-1-hints feature * Update CHANGELOG.md * Move Felt252DictEntryInit hint impl * Add hint Felt252DictEntryUpdate * Add hint GetCurrentAccessDelta * Add hint InitSquashData * Add hint AllocConstantSize * Add hint GetCurrentAccessIndex * Add hint Uint256SquareRoot * Fix cfg directive * Misc fixes * Add changelog entry * Add hint ShouldContinueSquashLoop * Add hint FieldSqrt * Fixes * Add cairo-1-hints to test workflow * Add Cairo 1 considerations for Gitignore and Makefile (#1144) * Add ignore for casm and sierra files * Add libfunc experimental for cairo 1 compilation * Add new enty to CHANGELOG * Update CHANGELOG.md --------- Co-authored-by: Roberto Catalan --- CHANGELOG.md | 12 + .../cairo_1_hint_processor/dict_manager.rs | 2 +- .../cairo_1_hint_processor/hint_processor.rs | 264 ++++++++++++++++++ 3 files changed, 277 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23ed807220..a6c64c072c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ #### Upcoming Changes +* Add more hints to `Cairo1HintProcessor` [#1143](https://github.com/lambdaclass/cairo-rs/pull/1098) + + * `Cairo1HintProcessor` can now run the following hints: + * Felt252DictEntryInit + * Felt252DictEntryUpdate + * GetCurrentAccessDelta + * InitSquashData + * AllocConstantSize + * GetCurrentAccessIndex + * ShouldContinueSquashLoop + * FieldSqrt + * Add a test for the `DivMod` hint [#1138](https://github.com/lambdaclass/cairo-rs/pull/1138). * Add some small considerations regarding Cairo 1 programs [#1144](https://github.com/lambdaclass/cairo-rs/pull/1144): diff --git a/src/hint_processor/cairo_1_hint_processor/dict_manager.rs b/src/hint_processor/cairo_1_hint_processor/dict_manager.rs index 123adc01f9..e0c705fb00 100644 --- a/src/hint_processor/cairo_1_hint_processor/dict_manager.rs +++ b/src/hint_processor/cairo_1_hint_processor/dict_manager.rs @@ -94,7 +94,7 @@ pub struct DictSquashExecScope { /// A map from key to the list of indices accessing it, each list in reverse order. pub(crate) access_indices: HashMap>, /// Descending list of keys. - keys: Vec, + pub(crate) keys: Vec, } impl DictSquashExecScope { diff --git a/src/hint_processor/cairo_1_hint_processor/hint_processor.rs b/src/hint_processor/cairo_1_hint_processor/hint_processor.rs index 3763cff6e1..7060443922 100644 --- a/src/hint_processor/cairo_1_hint_processor/hint_processor.rs +++ b/src/hint_processor/cairo_1_hint_processor/hint_processor.rs @@ -4,6 +4,7 @@ use crate::any_box; use crate::felt::{felt_str, Felt252}; use crate::hint_processor::cairo_1_hint_processor::dict_manager::DictSquashExecScope; use crate::hint_processor::hint_processor_definition::HintReference; +use crate::types::relocatable::Relocatable; use crate::stdlib::collections::HashMap; use crate::stdlib::prelude::*; @@ -26,6 +27,12 @@ use num_bigint::BigUint; use num_integer::Integer; use num_traits::{cast::ToPrimitive, Zero}; +/// Execution scope for constant memory allocation. +struct MemoryExecScope { + /// The first free address in the segment. + next_address: Relocatable, +} + #[derive(MontConfig)] #[modulus = "3618502788666131213697322783095070105623107215331596699973092056135872020481"] #[generator = "3"] @@ -174,6 +181,31 @@ impl Cairo1HintProcessor { Hint::Core(CoreHint::ShouldSkipSquashLoop { should_skip_loop }) => { self.should_skip_squash_loop(vm, exec_scopes, should_skip_loop) } + Hint::Core(CoreHint::Felt252DictEntryInit { dict_ptr, key }) => { + self.dict_entry_init(vm, exec_scopes, dict_ptr, key) + } + Hint::Core(CoreHint::Felt252DictEntryUpdate { dict_ptr, value }) => { + self.felt_252_dict_entry_update(vm, exec_scopes, dict_ptr, value) + } + Hint::Core(CoreHint::GetCurrentAccessDelta { index_delta_minus1 }) => { + self.get_current_access_delta(vm, exec_scopes, index_delta_minus1) + } + Hint::Core(CoreHint::InitSquashData { + dict_accesses, + n_accesses, + big_keys, + .. + }) => self.init_squash_data(vm, exec_scopes, dict_accesses, n_accesses, big_keys), + Hint::Core(CoreHint::AllocConstantSize { size, dst }) => { + self.alloc_constant_size(vm, exec_scopes, size, dst) + } + Hint::Core(CoreHint::GetCurrentAccessIndex { range_check_ptr }) => { + self.get_current_access_index(vm, exec_scopes, range_check_ptr) + } + Hint::Core(CoreHint::ShouldContinueSquashLoop { should_continue }) => { + self.should_continue_squash_loop(vm, exec_scopes, should_continue) + } + Hint::Core(CoreHint::FieldSqrt { val, sqrt }) => self.field_sqrt(vm, val, sqrt), hint => Err(HintError::UnknownHint(hint.to_string())), } } @@ -586,6 +618,27 @@ impl Cairo1HintProcessor { Ok(()) } + fn dict_entry_init( + &self, + vm: &mut VirtualMachine, + exec_scopes: &mut ExecutionScopes, + dict_ptr: &ResOperand, + key: &ResOperand, + ) -> Result<(), HintError> { + let (dict_base, dict_offset) = extract_buffer(dict_ptr)?; + let dict_address = get_ptr(vm, dict_base, &dict_offset)?; + let key = res_operand_get_val(vm, key)?; + let dict_manager_exec_scope = + exec_scopes.get_mut_ref::("dict_manager_exec_scope")?; + + let prev_value = dict_manager_exec_scope + .get_from_tracker(dict_address, &key) + .unwrap_or_else(|| DictManagerExecScope::DICT_DEFAULT_VALUE.into()); + + vm.insert_value((dict_address + 1)?, prev_value) + .map_err(HintError::from) + } + fn debug_print( &self, vm: &mut VirtualMachine, @@ -660,6 +713,217 @@ impl Cairo1HintProcessor { Ok(()) } + + fn felt_252_dict_entry_update( + &self, + vm: &mut VirtualMachine, + exec_scopes: &mut ExecutionScopes, + dict_ptr: &ResOperand, + value: &ResOperand, + ) -> Result<(), HintError> { + let (dict_base, dict_offset) = extract_buffer(dict_ptr)?; + let dict_address = get_ptr(vm, dict_base, &dict_offset)?; + let key = get_double_deref_val(vm, dict_base, &(dict_offset + Felt252::from(-3)))?; + let value = res_operand_get_val(vm, value)?; + let dict_manager_exec_scope = exec_scopes + .get_mut_ref::("dict_manager_exec_scope") + .map_err(|_| { + HintError::CustomHint( + "Trying to write to a dict while dict manager was not initialized.".to_string(), + ) + })?; + dict_manager_exec_scope.insert_to_tracker(dict_address, key, value); + + Ok(()) + } + + fn get_current_access_delta( + &self, + vm: &mut VirtualMachine, + exec_scopes: &mut ExecutionScopes, + index_delta_minus1: &CellRef, + ) -> Result<(), HintError> { + let dict_squash_exec_scope: &mut DictSquashExecScope = + exec_scopes.get_mut_ref("dict_squash_exec_scope")?; + let prev_access_index = dict_squash_exec_scope + .pop_current_access_index() + .ok_or(HintError::CustomHint("no accessed index".to_string()))?; + let index_delta_minus_1_val = dict_squash_exec_scope + .current_access_index() + .ok_or(HintError::CustomHint("no index accessed".to_string()))? + .clone() + - prev_access_index + - 1_u32; + + vm.insert_value( + cell_ref_to_relocatable(index_delta_minus1, vm)?, + index_delta_minus_1_val, + )?; + + Ok(()) + } + + fn init_squash_data( + &self, + vm: &mut VirtualMachine, + exec_scopes: &mut ExecutionScopes, + dict_accesses: &ResOperand, + n_accesses: &ResOperand, + big_keys: &CellRef, + ) -> Result<(), HintError> { + let dict_access_size = 3; + let rangecheck_bound = Felt252::from(u128::MAX) + 1u32; + + exec_scopes.assign_or_update_variable( + "dict_squash_exec_scope", + Box::::default(), + ); + let dict_squash_exec_scope = + exec_scopes.get_mut_ref::("dict_squash_exec_scope")?; + let (dict_accesses_base, dict_accesses_offset) = extract_buffer(dict_accesses)?; + let dict_accesses_address = get_ptr(vm, dict_accesses_base, &dict_accesses_offset)?; + let n_accesses = + res_operand_get_val(vm, n_accesses)? + .to_usize() + .ok_or(HintError::CustomHint( + "Number of accesses is too large or negative.".to_string(), + ))?; + + for i in 0..n_accesses { + let current_key = vm.get_integer((dict_accesses_address + i * dict_access_size)?)?; + dict_squash_exec_scope + .access_indices + .entry(current_key.into_owned()) + .and_modify(|indices| indices.push(Felt252::from(i))) + .or_insert_with(|| vec![Felt252::from(i)]); + } + // Reverse the accesses in order to pop them in order later. + for (_, accesses) in dict_squash_exec_scope.access_indices.iter_mut() { + accesses.reverse(); + } + + dict_squash_exec_scope.keys = dict_squash_exec_scope + .access_indices + .keys() + .cloned() + .collect(); + dict_squash_exec_scope.keys.sort_by(|a, b| b.cmp(a)); + // big_keys indicates if the keys are greater than rangecheck_bound. If they are not + // a simple range check is used instead of assert_le_felt252. + + let val = Felt252::from((dict_squash_exec_scope.keys[0] < rangecheck_bound) as u8); + + vm.insert_value(cell_ref_to_relocatable(big_keys, vm)?, val)?; + + vm.insert_value( + cell_ref_to_relocatable(big_keys, vm)?, + dict_squash_exec_scope + .current_key() + .ok_or(HintError::CustomHint("No current key".to_string()))?, + )?; + + Ok(()) + } + + fn alloc_constant_size( + &self, + vm: &mut VirtualMachine, + exec_scopes: &mut ExecutionScopes, + size: &ResOperand, + dst: &CellRef, + ) -> Result<(), HintError> { + let object_size = res_operand_get_val(vm, size)? + .to_usize() + .expect("Object size too large."); + let memory_exec_scope = + match exec_scopes.get_mut_ref::("memory_exec_scope") { + Ok(memory_exec_scope) => memory_exec_scope, + Err(_) => { + exec_scopes.assign_or_update_variable( + "memory_exec_scope", + Box::new(MemoryExecScope { + next_address: vm.add_memory_segment(), + }), + ); + exec_scopes.get_mut_ref::("memory_exec_scope")? + } + }; + + vm.insert_value( + cell_ref_to_relocatable(dst, vm)?, + memory_exec_scope.next_address, + )?; + + memory_exec_scope.next_address.offset += object_size; + Ok(()) + } + + fn get_current_access_index( + &self, + vm: &mut VirtualMachine, + exec_scopes: &mut ExecutionScopes, + range_check_ptr: &ResOperand, + ) -> Result<(), HintError> { + let dict_squash_exec_scope: &mut DictSquashExecScope = + exec_scopes.get_mut_ref("dict_squash_exec_scope")?; + let (range_check_base, range_check_offset) = extract_buffer(range_check_ptr)?; + let range_check_ptr = get_ptr(vm, range_check_base, &range_check_offset)?; + let current_access_index = + dict_squash_exec_scope + .current_access_index() + .ok_or(HintError::CustomHint( + "No current accessed index".to_string(), + ))?; + vm.insert_value(range_check_ptr, current_access_index)?; + + Ok(()) + } + + fn should_continue_squash_loop( + &self, + vm: &mut VirtualMachine, + exec_scopes: &mut ExecutionScopes, + dst: &CellRef, + ) -> Result<(), HintError> { + let dict_squash_exec_scope: &mut DictSquashExecScope = + exec_scopes.get_mut_ref("dict_squash_exec_scope")?; + let current_access_indices = dict_squash_exec_scope + .current_access_indices() + .ok_or(HintError::EmptyCurrentAccessIndices)?; + + let should_continue = Felt252::from((current_access_indices.len() > 1) as u8); + + vm.insert_value(cell_ref_to_relocatable(dst, vm)?, should_continue) + .map_err(HintError::from) + } + + fn field_sqrt( + &self, + vm: &mut VirtualMachine, + val: &ResOperand, + sqrt: &CellRef, + ) -> Result<(), HintError> { + let value = Fq::from(res_operand_get_val(vm, val)?.to_biguint()); + + let three_fq = Fq::from(Felt252::new(3).to_biguint()); + let res = if value.legendre().is_qr() { + value + } else { + value * three_fq + }; + + if let Some(root) = res.sqrt() { + let root0: BigUint = root.into_bigint().into(); + let root1: BigUint = (-root).into_bigint().into(); + let root = Felt252::from(std::cmp::min(root0, root1)); + vm.insert_value(cell_ref_to_relocatable(sqrt, vm)?, root) + .map_err(HintError::from) + } else { + Err(HintError::CustomHint( + "Field element is not a square".to_string(), + )) + } + } } impl HintProcessor for Cairo1HintProcessor {