diff --git a/CHANGELOG.md b/CHANGELOG.md index be9dd49bb4..58e0254b0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ #### Upcoming Changes +* feat: Reorganized builtins to be in the top of stack at the end of a run (Cairo1). + +* BREAKING: Remove `CairoRunner::add_additional_hash_builtin` & `VirtualMachine::disable_trace`[#1658](https://github.com/lambdaclass/cairo-vm/pull/1658) + +* feat: output builtin add_attribute method [#1691](https://github.com/lambdaclass/cairo-vm/pull/1691) + +* feat: add a method to retrieve the output builtin from the VM [#1690](https://github.com/lambdaclass/cairo-vm/pull/1690) + * feat: Add zero segment [#1668](https://github.com/lambdaclass/cairo-vm/pull/1668) * feat: Bump cairo_lang to 0.13.1 in testing env [#1687](https://github.com/lambdaclass/cairo-vm/pull/1687) @@ -33,6 +41,8 @@ * Adds the flag `append_return_values` to both the CLI and `Cairo1RunConfig` struct. * Enabling flag will add the output builtin and the necessary instructions to append the return values to the output builtin's memory segment. +* feat: Compute program hash chain [#1647](https://github.com/lambdaclass/cairo-vm/pull/1647) + * feat: Add cairo1-run output pretty-printing for felts, arrays/spans and dicts [#1630](https://github.com/lambdaclass/cairo-vm/pull/1630) * feat: output builtin features for bootloader support [#1580](https://github.com/lambdaclass/cairo-vm/pull/1580) diff --git a/Cargo.lock b/Cargo.lock index c3cd2a2000..d51294e05e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -973,6 +973,7 @@ dependencies = [ "clap", "itertools 0.11.0", "mimalloc", + "num-traits 0.2.18", "rstest", "thiserror", ] diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index fec7569fad..f0d2c2df43 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -1,4 +1,6 @@ -use cairo_lang_casm::{casm, casm_extend, hints::Hint, instructions::Instruction}; +use cairo_lang_casm::{ + casm, casm_extend, hints::Hint, inline::CasmContext, instructions::Instruction, +}; use cairo_lang_sierra::{ extensions::{ bitwise::BitwiseType, @@ -23,7 +25,7 @@ use cairo_lang_sierra_to_casm::{ metadata::{calc_metadata, Metadata, MetadataComputationConfig, MetadataError}, }; use cairo_lang_sierra_type_size::get_type_size_map; -use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; +use cairo_lang_utils::{casts::IntoOrPanic, unordered_hash_map::UnorderedHashMap}; use cairo_vm::{ hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor, math_utils::signed_felt, @@ -46,7 +48,7 @@ use cairo_vm::{ }; use itertools::{chain, Itertools}; use num_traits::{cast::ToPrimitive, Zero}; -use std::{collections::HashMap, iter::Peekable, slice::Iter}; +use std::{collections::HashMap, iter::Peekable}; use crate::{Error, FuncArg}; @@ -114,8 +116,7 @@ pub fn cairo_run_program( &type_sizes, main_func, initial_gas, - cairo_run_config.proof_mode || cairo_run_config.append_return_values, - cairo_run_config.args, + &cairo_run_config, )?; // Fetch return type data @@ -131,22 +132,12 @@ pub fn cairo_run_program( // This footer is used by lib funcs let libfunc_footer = create_code_footer(); - - // Header used to initiate the infinite loop after executing the program - // Also appends return values to output segment - let proof_mode_header = if cairo_run_config.proof_mode { - create_proof_mode_header(builtins.len() as i16, return_type_size) - } else if cairo_run_config.append_return_values { - create_append_return_values_header(builtins.len() as i16, return_type_size) - } else { - casm! {}.instructions - }; + let builtin_count: i16 = builtins.len().into_or_panic(); // This is the program we are actually running/proving // With (embedded proof mode), cairo1 header and the libfunc footer let instructions = chain!( - proof_mode_header.iter(), - entry_code.iter(), + entry_code.instructions.iter(), casm_program.instructions.iter(), libfunc_footer.iter(), ); @@ -165,12 +156,12 @@ pub fn cairo_run_program( let program = if cairo_run_config.proof_mode { Program::new_for_proof( - builtins, + builtins.clone(), data, 0, // Proof mode is on top - // jmp rel 0 is on PC == 2 - 2, + // `jmp rel 0` is the last line of the entry code. + entry_code.current_code_offset - 2, program_hints, ReferenceManager { references: Vec::new(), @@ -181,7 +172,7 @@ pub fn cairo_run_program( )? } else { Program::new( - builtins, + builtins.clone(), data, Some(0), program_hints, @@ -212,17 +203,17 @@ pub fn cairo_run_program( runner.run_for_steps(1, &mut vm, &mut hint_processor)?; } - if cairo_run_config.proof_mode || cairo_run_config.append_return_values { - // As we will be inserting the return values into the output segment after running the main program (right before the infinite loop) the computed size for the output builtin will be 0 - // We need to manually set the segment size for the output builtin's segment so memory hole counting doesn't fail due to having a higher accessed address count than the segment's size - vm.segments - .segment_sizes - .insert(2, return_type_size as usize); - } runner.end_run(false, false, &mut vm, &mut hint_processor)?; + let skip_output = cairo_run_config.proof_mode || cairo_run_config.append_return_values; // Fetch return values - let return_values = fetch_return_values(return_type_size, return_type_id, &vm)?; + let return_values = fetch_return_values( + return_type_size, + return_type_id, + &vm, + builtin_count, + skip_output, + )?; let serialized_output = if cairo_run_config.serialize_output { Some(serialize_output( @@ -238,16 +229,28 @@ pub fn cairo_run_program( // Set stop pointers for builtins so we can obtain the air public input if cairo_run_config.finalize_builtins { - finalize_builtins( - cairo_run_config.proof_mode || cairo_run_config.append_return_values, - &main_func.signature.ret_types, - &type_sizes, - &mut vm, - )?; - - if cairo_run_config.proof_mode || cairo_run_config.append_return_values { - // As the output builtin is not used by the program we need to compute it's stop ptr manually - vm.set_output_stop_ptr_offset(return_type_size as usize); + if skip_output { + // Set stop pointer for each builtin + vm.builtins_final_stack_from_stack_pointer_dict( + &builtins + .iter() + .enumerate() + .map(|(i, builtin)| { + ( + builtin.name(), + (vm.get_ap() - (builtins.len() - 1 - i)).unwrap(), + ) + }) + .collect(), + false, + )?; + } else { + finalize_builtins( + &main_func.signature.ret_types, + &type_sizes, + &mut vm, + builtin_count, + )?; } // Build execution public memory @@ -338,71 +341,6 @@ fn create_code_footer() -> Vec { .instructions } -// Create proof_mode specific instructions -// Including the "canonical" proof mode instructions (the ones added by the compiler in cairo 0) -// wich call the firt program instruction and then initiate an infinite loop. -// And also appending the return values to the output builtin's memory segment -fn create_proof_mode_header(builtin_count: i16, return_type_size: i16) -> Vec { - // As the output builtin is not used by cairo 1 (we forced it for this purpose), it's segment is always empty - // so we can start writing values directly from it's base, which is located relative to the fp before the other builtin's bases - let output_fp_offset: i16 = -(builtin_count + 2); // The 2 here represents the return_fp & end segments - - // The pc offset where the original program should start - // Without this header it should start at 0, but we add 2 for each call and jump instruction (as both of them use immediate values) - // and also 1 for each instruction added to copy each return value into the output segment - let program_start_offset: i16 = 4 + return_type_size; - - let mut ctx = casm! {}; - casm_extend! {ctx, - call rel program_start_offset; // Begin program execution by calling the first instruction in the original program - }; - // Append each return value to the output segment - for (i, j) in (1..return_type_size + 1).rev().enumerate() { - casm_extend! {ctx, - // [ap -j] is where each return value is located in memory - // [[fp + output_fp_offet] + 0] is the base of the output segment - [ap - j] = [[fp + output_fp_offset] + i as i16]; - }; - } - casm_extend! {ctx, - jmp rel 0; // Infinite loop - }; - ctx.instructions -} - -// Create specific instructions to append the return values to the output segment when not running in proof_mode -// Call the firt program instruction, appends the return values to the output builtin's memory segment and then returns -fn create_append_return_values_header( - builtin_count: i16, - return_type_size: i16, -) -> Vec { - // As the output builtin is not used by cairo 1 (we forced it for this purpose), it's segment is always empty - // so we can start writing values directly from it's base, which is located relative to the fp before the other builtin's bases - let output_fp_offset: i16 = -(builtin_count + 2); // The 2 here represents the return_fp & end segments - - // The pc offset where the original program should start - // Without this header it should start at 0, but we add 2 for the call and 1 for the return instruction - // and also 1 for each instruction added to copy each return value into the output segment - let program_start_offset: i16 = 3 + return_type_size; - - let mut ctx = casm! {}; - casm_extend! {ctx, - call rel program_start_offset; // Begin program execution by calling the first instruction in the original program - }; - // Append each return value to the output segment - for (i, j) in (1..return_type_size + 1).rev().enumerate() { - casm_extend! {ctx, - // [ap -j] is where each return value is located in memory - // [[fp + output_fp_offet] + 0] is the base of the output segment - [ap - j] = [[fp + output_fp_offset] + i as i16]; - }; - } - casm_extend! {ctx, - ret; - }; - ctx.instructions -} - /// Returns the instructions to add to the beginning of the code to successfully call the main /// function, as well as the builtins required to execute the program. fn create_entry_code( @@ -411,18 +349,19 @@ fn create_entry_code( type_sizes: &UnorderedHashMap, func: &Function, initial_gas: usize, - append_output: bool, - args: &[FuncArg], -) -> Result<(Vec, Vec), Error> { - let mut ctx = casm! {}; + config: &Cairo1RunConfig, +) -> Result<(CasmContext, Vec), Error> { + let copy_to_output_builtin = config.proof_mode || config.append_return_values; + let signature = &func.signature; // The builtins in the formatting expected by the runner. - let (builtins, builtin_offset) = get_function_builtins(func, append_output); - + let (builtins, builtin_offset) = + get_function_builtins(&signature.param_types, copy_to_output_builtin); + let mut ctx = casm! {}; // Load all vecs to memory. // Load all array args content to memory. let mut array_args_data = vec![]; let mut ap_offset: i16 = 0; - for arg in args { + for arg in config.args { let FuncArg::Array(values) = arg else { continue; }; @@ -442,10 +381,10 @@ fn create_entry_code( } let mut array_args_data_iter = array_args_data.iter(); let after_arrays_data_offset = ap_offset; - let mut arg_iter = args.iter().enumerate(); + let mut arg_iter = config.args.iter().enumerate(); let mut param_index = 0; let mut expected_arguments_size = 0; - if func.signature.param_types.iter().any(|ty| { + if signature.param_types.iter().any(|ty| { get_info(sierra_program_registry, ty) .map(|x| x.long_id.generic_id == SegmentArenaType::ID) .unwrap_or_default() @@ -464,19 +403,13 @@ fn create_entry_code( } ap_offset += 3; } - for ty in func.signature.param_types.iter() { + + for ty in &signature.param_types { let info = get_info(sierra_program_registry, ty) .ok_or_else(|| Error::NoInfoForType(ty.clone()))?; let generic_ty = &info.long_id.generic_id; if let Some(offset) = builtin_offset.get(generic_ty) { - let mut offset = *offset; - if append_output { - // Everything is off by 2 due to the proof mode header - offset += 2; - } - casm_extend! {ctx, - [ap + 0] = [fp - offset], ap++; - } + casm_extend!(ctx, [ap + 0] = [fp - *offset], ap++;); ap_offset += 1; } else if generic_ty == &SystemType::ID { casm_extend! {ctx, @@ -485,15 +418,11 @@ fn create_entry_code( } ap_offset += 1; } else if generic_ty == &GasBuiltinType::ID { - casm_extend! {ctx, - [ap + 0] = initial_gas, ap++; - } + casm_extend!(ctx, [ap + 0] = initial_gas, ap++;); ap_offset += 1; } else if generic_ty == &SegmentArenaType::ID { let offset = -ap_offset + after_arrays_data_offset; - casm_extend! {ctx, - [ap + 0] = [ap + offset] + 3, ap++; - } + casm_extend!(ctx, [ap + 0] = [ap + offset] + 3, ap++;); ap_offset += 1; } else { let ty_size = type_sizes[ty]; @@ -529,7 +458,8 @@ fn create_entry_code( param_index += 1; }; } - let actual_args_size = args + let actual_args_size = config + .args .iter() .map(|arg| match arg { FuncArg::Single(_) => 1, @@ -544,17 +474,102 @@ fn create_entry_code( } let before_final_call = ctx.current_code_offset; - let final_call_size = 3; + + let return_type_id = signature + .ret_types + .last() + .ok_or(Error::NoRetTypesInSignature)?; + let return_type_size = type_sizes + .get(return_type_id) + .cloned() + .ok_or_else(|| Error::NoTypeSizeForId(return_type_id.clone()))?; + let builtin_count: i16 = builtins.len().into_or_panic(); + let builtin_locations: Vec = builtins + .iter() + .enumerate() + .map(|(i, name)| { + let fp_loc = i.into_or_panic::() - builtin_count - 2; + let generic = match name { + BuiltinName::range_check => RangeCheckType::ID, + BuiltinName::pedersen => PedersenType::ID, + BuiltinName::bitwise => BitwiseType::ID, + BuiltinName::ec_op => EcOpType::ID, + BuiltinName::poseidon => PoseidonType::ID, + BuiltinName::segment_arena => SegmentArenaType::ID, + BuiltinName::keccak | BuiltinName::ecdsa | BuiltinName::output => return fp_loc, + }; + signature + .ret_types + .iter() + .position(|ty| { + sierra_program_registry + .get_type(ty) + .unwrap() + .info() + .long_id + .generic_id + == generic + }) + .map(|i| (signature.ret_types.len() - i).into_or_panic()) + .unwrap_or(fp_loc) + }) + .collect(); + if copy_to_output_builtin { + assert!( + builtins.iter().contains(&BuiltinName::output), + "Output builtin is required for proof mode or append_return_values" + ); + } + + let final_call_size = + // The call. + 2 + // The copying of the return values to the output segment. + + if copy_to_output_builtin { return_type_size.into_or_panic::() + 1 } else { 0 } + // Rewriting the builtins to top of the stack. + + builtins.len() + // The return or infinite loop. + + if config.proof_mode { 2 } else { 1 }; let offset = final_call_size + casm_program.debug_info.sierra_statement_info[func.entry_point.0].code_offset; - casm_extend! {ctx, - call rel offset; - ret; + casm_extend!(ctx, call rel offset;); + + if copy_to_output_builtin { + let Some(output_builtin_idx) = builtins.iter().position(|b| b == &BuiltinName::output) + else { + panic!("Output builtin is required for proof mode or append_return_values."); + }; + let output_fp_offset: i16 = builtin_locations[output_builtin_idx]; + for (i, j) in (1..return_type_size + 1).rev().enumerate() { + casm_extend! {ctx, + // [ap -j] is where each return value is located in memory + // [[fp + output_fp_offet] + 0] is the base of the output segment + [ap - j] = [[fp + output_fp_offset] + i as i16]; + }; + } + } + let mut ret_builtin_offset = return_type_size - 1; + for (builtin, location) in builtins.iter().zip(builtin_locations) { + if builtin == &BuiltinName::output && copy_to_output_builtin { + casm_extend!(ctx, [ap + 0] = [fp + location] + return_type_size, ap++;); + } else if location < 0 { + casm_extend!(ctx, [ap + 0] = [fp + location], ap++;); + } else { + casm_extend!(ctx, [ap + 0] = [ap - (ret_builtin_offset + location)], ap++;); + } + ret_builtin_offset += 1; + } + + if config.proof_mode { + casm_extend!(ctx, jmp rel 0;); + } else { + casm_extend!(ctx, ret;); } + assert_eq!(before_final_call + final_call_size, ctx.current_code_offset); - Ok((ctx.instructions, builtins)) + Ok((ctx, builtins)) } fn get_info<'a>( @@ -589,74 +604,35 @@ fn create_metadata( } } -/// Type representing the Output builtin. -#[derive(Default)] -pub struct OutputType {} -impl cairo_lang_sierra::extensions::NoGenericArgsGenericType for OutputType { - const ID: cairo_lang_sierra::ids::GenericTypeId = - cairo_lang_sierra::ids::GenericTypeId::new_inline("Output"); - const STORABLE: bool = true; - const DUPLICATABLE: bool = false; - const DROPPABLE: bool = false; - const ZERO_SIZED: bool = false; -} - fn get_function_builtins( - func: &Function, + params: &[cairo_lang_sierra::ids::ConcreteTypeId], append_output: bool, ) -> ( Vec, HashMap, ) { - let entry_params = &func.signature.param_types; let mut builtins = Vec::new(); let mut builtin_offset: HashMap = HashMap::new(); let mut current_offset = 3; - // Fetch builtins from the entry_params in the standard order - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Poseidon".into())) - { - builtins.push(BuiltinName::poseidon); - builtin_offset.insert(PoseidonType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("EcOp".into())) - { - builtins.push(BuiltinName::ec_op); - builtin_offset.insert(EcOpType::ID, current_offset); - current_offset += 1 - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Bitwise".into())) - { - builtins.push(BuiltinName::bitwise); - builtin_offset.insert(BitwiseType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("RangeCheck".into())) - { - builtins.push(BuiltinName::range_check); - builtin_offset.insert(RangeCheckType::ID, current_offset); - current_offset += 1; - } - if entry_params - .iter() - .any(|ti| ti.debug_name == Some("Pedersen".into())) - { - builtins.push(BuiltinName::pedersen); - builtin_offset.insert(PedersenType::ID, current_offset); - current_offset += 1; + for (debug_name, builtin_name, sierra_id) in [ + ("Poseidon", BuiltinName::poseidon, PoseidonType::ID), + ("EcOp", BuiltinName::ec_op, EcOpType::ID), + ("Bitwise", BuiltinName::bitwise, BitwiseType::ID), + ("RangeCheck", BuiltinName::range_check, RangeCheckType::ID), + ("Pedersen", BuiltinName::pedersen, PedersenType::ID), + ] { + if params + .iter() + .any(|id| id.debug_name.as_deref() == Some(debug_name)) + { + builtins.push(builtin_name); + builtin_offset.insert(sierra_id, current_offset); + current_offset += 1; + } } // Force an output builtin so that we can write the program output into it's segment if append_output { builtins.push(BuiltinName::output); - builtin_offset.insert(OutputType::ID, current_offset); } builtins.reverse(); (builtins, builtin_offset) @@ -666,8 +642,21 @@ fn fetch_return_values( return_type_size: i16, return_type_id: &ConcreteTypeId, vm: &VirtualMachine, + builtin_count: i16, + fetch_from_output: bool, ) -> Result, Error> { - let mut return_values = vm.get_return_values(return_type_size as usize)?; + let mut return_values = if fetch_from_output { + let output_builtin_end = vm + .get_relocatable((vm.get_ap() + (-builtin_count as i32)).unwrap()) + .unwrap(); + let output_builtin_base = (output_builtin_end + (-return_type_size as i32)).unwrap(); + vm.get_continuous_range(output_builtin_base, return_type_size.into_or_panic())? + } else { + vm.get_continuous_range( + (vm.get_ap() - (return_type_size + builtin_count) as usize).unwrap(), + return_type_size as usize, + )? + }; // Check if this result is a Panic result if return_type_id .debug_name @@ -708,10 +697,10 @@ fn fetch_return_values( // Calculates builtins' final_stack setting each stop_ptr // Calling this function is a must if either air_public_input or cairo_pie are needed fn finalize_builtins( - skip_output: bool, main_ret_types: &[ConcreteTypeId], type_sizes: &UnorderedHashMap, vm: &mut VirtualMachine, + builtin_count: i16, ) -> Result<(), Error> { // Set stop pointers for builtins so we can obtain the air public input // Cairo 1 programs have other return values aside from the used builtin's final pointers, so we need to hand-pick them @@ -721,8 +710,9 @@ fn finalize_builtins( let ret_types_and_sizes = main_ret_types.iter().zip(ret_types_sizes.clone()); let full_ret_types_size: i16 = ret_types_sizes.sum(); - let mut stack_pointer = (vm.get_ap() - (full_ret_types_size as usize).saturating_sub(1)) - .map_err(VirtualMachineError::Math)?; + let mut stack_pointer = (vm.get_ap() + - (full_ret_types_size as usize + builtin_count as usize).saturating_sub(1)) + .map_err(VirtualMachineError::Math)?; // Calculate the stack_ptr for each return builtin in the return values let mut builtin_name_to_stack_pointer = HashMap::new(); @@ -747,7 +737,7 @@ fn finalize_builtins( } // Set stop pointer for each builtin - vm.builtins_final_stack_from_stack_pointer_dict(&builtin_name_to_stack_pointer, skip_output)?; + vm.builtins_final_stack_from_stack_pointer_dict(&builtin_name_to_stack_pointer, false)?; Ok(()) } @@ -759,7 +749,7 @@ fn serialize_output( type_sizes: &UnorderedHashMap, ) -> String { let mut output_string = String::new(); - let mut return_values_iter: Peekable> = return_values.iter().peekable(); + let mut return_values_iter = return_values.iter().peekable(); serialize_output_inner( &mut return_values_iter, &mut output_string, @@ -771,8 +761,8 @@ fn serialize_output( output_string } -fn serialize_output_inner( - return_values_iter: &mut Peekable>, +fn serialize_output_inner<'a>( + return_values_iter: &mut Peekable>, output_string: &mut String, vm: &mut VirtualMachine, return_type_id: &ConcreteTypeId, @@ -1037,7 +1027,7 @@ fn serialize_output_inner( // Serialize the value // We create a peekable array here in order to use the serialize_output_inner as the value could be a span let value_vec = vec![value.clone()]; - let mut value_iter: Peekable> = value_vec.iter().peekable(); + let mut value_iter = value_vec.iter().peekable(); serialize_output_inner( &mut value_iter, output_string, @@ -1083,7 +1073,7 @@ fn serialize_output_inner( // Serialize the value // We create a peekable array here in order to use the serialize_output_inner as the value could be a span let value_vec = vec![value.clone()]; - let mut value_iter: Peekable> = value_vec.iter().peekable(); + let mut value_iter = value_vec.iter().peekable(); serialize_output_inner( &mut value_iter, output_string, diff --git a/vm/src/hint_processor/builtin_hint_processor/math_utils.rs b/vm/src/hint_processor/builtin_hint_processor/math_utils.rs index 80c326394b..964434b444 100644 --- a/vm/src/hint_processor/builtin_hint_processor/math_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/math_utils.rs @@ -453,7 +453,6 @@ pub fn sqrt( ))); //This is equal to mod_value > bigint!(2).pow(250) } - #[allow(deprecated)] insert_value_from_var_name( "root", Felt252::from(&isqrt(&mod_value.to_biguint())?), diff --git a/vm/src/hint_processor/builtin_hint_processor/secp/secp_utils.rs b/vm/src/hint_processor/builtin_hint_processor/secp/secp_utils.rs index 7f34ea4436..4457c975b3 100644 --- a/vm/src/hint_processor/builtin_hint_processor/secp/secp_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/secp/secp_utils.rs @@ -106,20 +106,17 @@ mod tests { constants.insert(BASE_86.to_string(), crate::math_utils::pow2_const(86)); let array_1 = bigint3_split(&BigUint::zero()); - #[allow(deprecated)] let array_2 = bigint3_split( &bigint!(999992) .to_biguint() .expect("Couldn't convert to BigUint"), ); - #[allow(deprecated)] let array_3 = bigint3_split( &bigint_str!("7737125245533626718119526477371252455336267181195264773712524553362") .to_biguint() .expect("Couldn't convert to BigUint"), ); //TODO, Check SecpSplitutOfRange limit - #[allow(deprecated)] let array_4 = bigint3_split( &bigint_str!( "773712524553362671811952647737125245533626718119526477371252455336267181195264" diff --git a/vm/src/hint_processor/builtin_hint_processor/secp/signature.rs b/vm/src/hint_processor/builtin_hint_processor/secp/signature.rs index aee4450cc9..57d72b5bba 100644 --- a/vm/src/hint_processor/builtin_hint_processor/secp/signature.rs +++ b/vm/src/hint_processor/builtin_hint_processor/secp/signature.rs @@ -109,7 +109,6 @@ pub fn get_point_from_x( constants: &HashMap, ) -> Result<(), HintError> { exec_scopes.insert_value("SECP_P", SECP_P.clone()); - #[allow(deprecated)] let beta = constants .get(BETA) .ok_or_else(|| HintError::MissingConstant(Box::new(BETA)))? @@ -122,7 +121,6 @@ pub fn get_point_from_x( // Divide by 4 let mut y = y_cube_int.modpow(&(&*SECP_P + 1_u32).shr(2_u32), &SECP_P); - #[allow(deprecated)] let v = get_integer_from_var_name("v", vm, ids_data, ap_tracking)?.to_bigint(); if v.is_even() != y.is_even() { y = &*SECP_P - y; diff --git a/vm/src/hint_processor/cairo_1_hint_processor/dict_manager.rs b/vm/src/hint_processor/cairo_1_hint_processor/dict_manager.rs index fe04c1f71a..354e332f03 100644 --- a/vm/src/hint_processor/cairo_1_hint_processor/dict_manager.rs +++ b/vm/src/hint_processor/cairo_1_hint_processor/dict_manager.rs @@ -13,7 +13,6 @@ pub struct DictTrackerExecScope { /// The data of the dictionary. data: HashMap, /// The index of the dictionary in the dict_infos segment. - #[allow(dead_code)] idx: usize, } diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 36509273b3..bbd334ae81 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -57,6 +57,7 @@ pub mod air_public_input; pub mod cairo_run; pub mod hint_processor; pub mod math_utils; +pub mod program_hash; pub mod serde; pub mod types; pub mod utils; diff --git a/vm/src/program_hash.rs b/vm/src/program_hash.rs new file mode 100644 index 0000000000..3ff1fa798c --- /dev/null +++ b/vm/src/program_hash.rs @@ -0,0 +1,204 @@ +use starknet_crypto::{pedersen_hash, FieldElement}; + +use crate::Felt252; + +use crate::serde::deserialize_program::BuiltinName; +use crate::stdlib::vec::Vec; +use crate::types::relocatable::MaybeRelocatable; +use crate::vm::runners::cairo_pie::StrippedProgram; + +type HashFunction = fn(&FieldElement, &FieldElement) -> FieldElement; + +#[derive(thiserror_no_std::Error, Debug)] +pub enum HashChainError { + #[error("Data array must contain at least one element.")] + EmptyData, +} + +#[derive(thiserror_no_std::Error, Debug)] +pub enum ProgramHashError { + #[error(transparent)] + HashChain(#[from] HashChainError), + + #[error( + "Invalid program builtin: builtin name too long to be converted to field element: {0}" + )] + InvalidProgramBuiltin(&'static str), + + #[error("Invalid program data: data contains relocatable(s)")] + InvalidProgramData, + + /// Conversion from Felt252 to FieldElement failed. This is unlikely to happen + /// unless the implementation of Felt252 changes and this code is not updated properly. + #[error("Conversion from Felt252 to FieldElement failed")] + Felt252ToFieldElementConversionFailed, +} + +/// Computes a hash chain over the data, in the following order: +/// h(data[0], h(data[1], h(..., h(data[n-2], data[n-1])))). +/// +/// Reimplements this Python function: +/// def compute_hash_chain(data, hash_func=pedersen_hash): +/// assert len(data) >= 1, f"len(data) for hash chain computation must be >= 1; got: {len(data)}." +/// return functools.reduce(lambda x, y: hash_func(y, x), data[::-1]) +fn compute_hash_chain<'a, I>( + data: I, + hash_func: HashFunction, +) -> Result +where + I: Iterator + DoubleEndedIterator, +{ + match data.copied().rev().reduce(|x, y| hash_func(&y, &x)) { + Some(result) => Ok(result), + None => Err(HashChainError::EmptyData), + } +} + +/// Creates an instance of `FieldElement` from a builtin name. +/// +/// Converts the builtin name to bytes then attempts to create a field element from +/// these bytes. This function will fail if the builtin name is over 31 characters. +fn builtin_to_field_element(builtin: &BuiltinName) -> Result { + // The Python implementation uses the builtin name without suffix + let builtin_name = builtin + .name() + .strip_suffix("_builtin") + .unwrap_or(builtin.name()); + + FieldElement::from_byte_slice_be(builtin_name.as_bytes()) + .map_err(|_| ProgramHashError::InvalidProgramBuiltin(builtin.name())) +} + +/// The `value: FieldElement` is `pub(crate)` and there is no accessor. +/// This function converts a `Felt252` to a `FieldElement` using a safe, albeit inefficient, +/// method. +fn felt_to_field_element(felt: &Felt252) -> Result { + let bytes = felt.to_bytes_be(); + FieldElement::from_bytes_be(&bytes) + .map_err(|_e| ProgramHashError::Felt252ToFieldElementConversionFailed) +} + +/// Converts a `MaybeRelocatable` into a `FieldElement` value. +/// +/// Returns `InvalidProgramData` if `maybe_relocatable` is not an integer +fn maybe_relocatable_to_field_element( + maybe_relocatable: &MaybeRelocatable, +) -> Result { + let felt = maybe_relocatable + .get_int_ref() + .ok_or(ProgramHashError::InvalidProgramData)?; + felt_to_field_element(felt) +} + +/// Computes the Pedersen hash of a program. +/// +/// Reimplements this Python function: +/// def compute_program_hash_chain(program: ProgramBase, bootloader_version=0): +/// builtin_list = [from_bytes(builtin.encode("ascii")) for builtin in program.builtins] +/// # The program header below is missing the data length, which is later added to the data_chain. +/// program_header = [bootloader_version, program.main, len(program.builtins)] + builtin_list +/// data_chain = program_header + program.data +/// +/// return compute_hash_chain([len(data_chain)] + data_chain) +pub fn compute_program_hash_chain( + program: &StrippedProgram, + bootloader_version: usize, +) -> Result { + let program_main = program.main; + let program_main = FieldElement::from(program_main); + + // Convert builtin names to field elements + let builtin_list: Result, _> = program + .builtins + .iter() + .map(builtin_to_field_element) + .collect(); + let builtin_list = builtin_list?; + + let program_header = vec![ + FieldElement::from(bootloader_version), + program_main, + FieldElement::from(program.builtins.len()), + ]; + + let program_data: Result, _> = program + .data + .iter() + .map(maybe_relocatable_to_field_element) + .collect(); + let program_data = program_data?; + + let data_chain_len = program_header.len() + builtin_list.len() + program_data.len(); + let data_chain_len_vec = vec![FieldElement::from(data_chain_len)]; + + // Prepare a chain of iterators to feed to the hash function + let data_chain = [ + &data_chain_len_vec, + &program_header, + &builtin_list, + &program_data, + ]; + + let hash = compute_hash_chain(data_chain.iter().flat_map(|&v| v.iter()), pedersen_hash)?; + Ok(hash) +} + +#[cfg(test)] +mod tests { + #[cfg(feature = "std")] + use {crate::types::program::Program, rstest::rstest, std::path::PathBuf}; + + use starknet_crypto::pedersen_hash; + + use super::*; + + #[test] + fn test_compute_hash_chain() { + let data: Vec = vec![ + FieldElement::from(1u64), + FieldElement::from(2u64), + FieldElement::from(3u64), + ]; + let expected_hash = pedersen_hash( + &FieldElement::from(1u64), + &pedersen_hash(&FieldElement::from(2u64), &FieldElement::from(3u64)), + ); + let computed_hash = compute_hash_chain(data.iter(), pedersen_hash) + .expect("Hash computation failed unexpectedly"); + + assert_eq!(computed_hash, expected_hash); + } + + #[cfg(feature = "std")] + #[rstest] + // Expected hashes generated with `cairo-hash-program` + #[case::fibonacci( + "../cairo_programs/fibonacci.json", + "0x43b17e9592f33142246af4c06cd2b574b460dd1f718d76b51341175a62b220f" + )] + #[case::field_arithmetic( + "../cairo_programs/field_arithmetic.json", + "0x1031772ca86e618b058101af9c9a3277bac90712b750bcea1cc69d6c7cad8a7" + )] + #[case::keccak_copy_inputs( + "../cairo_programs/keccak_copy_inputs.json", + "0x49484fdc8e7a85061f9f21b7e21fe276d8a88c8e96681101a2518809e686c6c" + )] + fn test_compute_program_hash_chain( + #[case] program_path: PathBuf, + #[case] expected_program_hash: String, + ) { + let program = + Program::from_file(program_path.as_path(), Some("main")) + .expect("Could not load program. Did you compile the sample programs? Run `make test` in the root directory."); + let stripped_program = program.get_stripped_program().unwrap(); + let bootloader_version = 0; + + let program_hash = compute_program_hash_chain(&stripped_program, bootloader_version) + .expect("Failed to compute program hash."); + + let program_hash_hex = format!("{:#x}", program_hash); + + assert_eq!(program_hash_hex, expected_program_hash); + } +} diff --git a/vm/src/types/layout.rs b/vm/src/types/layout.rs index ceea2496f9..f9a2f8376f 100644 --- a/vm/src/types/layout.rs +++ b/vm/src/types/layout.rs @@ -63,7 +63,6 @@ impl CairoLayout { } } - #[allow(dead_code)] pub(crate) fn recursive_instance() -> CairoLayout { CairoLayout { _name: String::from("recursive"), @@ -120,7 +119,6 @@ impl CairoLayout { } } - #[allow(dead_code)] pub(crate) fn all_cairo_instance() -> CairoLayout { CairoLayout { _name: String::from("all_cairo"), diff --git a/vm/src/vm/errors/vm_errors.rs b/vm/src/vm/errors/vm_errors.rs index db8f0278e4..cbccde51b5 100644 --- a/vm/src/vm/errors/vm_errors.rs +++ b/vm/src/vm/errors/vm_errors.rs @@ -81,6 +81,8 @@ pub enum VirtualMachineError { InconsistentAutoDeduction(Box<(&'static str, MaybeRelocatable, Option)>), #[error("Invalid hint encoding at pc: {0}")] InvalidHintEncoding(Box), + #[error("Expected output builtin to be present")] + NoOutputBuiltin, #[error("Expected range_check builtin to be present")] NoRangeCheckBuiltin, #[error("Expected ecdsa builtin to be present")] diff --git a/vm/src/vm/runners/builtin_runner/output.rs b/vm/src/vm/runners/builtin_runner/output.rs index bf9eb7c7d8..853ba038d2 100644 --- a/vm/src/vm/runners/builtin_runner/output.rs +++ b/vm/src/vm/runners/builtin_runner/output.rs @@ -129,6 +129,10 @@ impl OutputBuiltinRunner { } } + pub fn add_attribute(&mut self, name: String, value: Vec) { + self.attributes.insert(name, value); + } + pub fn get_additional_data(&self) -> BuiltinAdditionalData { BuiltinAdditionalData::Output(OutputBuiltinAdditionalData { pages: self.pages.clone(), @@ -611,6 +615,18 @@ mod tests { ) } + #[test] + pub fn add_attribute() { + let mut builtin = OutputBuiltinRunner::new(true); + assert!(builtin.attributes.is_empty()); + + let name = "gps_fact_topology".to_string(); + let values = vec![0, 12, 30]; + builtin.add_attribute(name.clone(), values.clone()); + + assert_eq!(builtin.attributes, HashMap::from([(name, values)])); + } + #[test] fn get_public_memory() { let mut builtin = OutputBuiltinRunner::new(true); diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 7becd79eea..af4048697a 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -1256,22 +1256,6 @@ impl CairoRunner { Ok(()) } - //NOTE: No longer needed in 0.11 - /// Add (or replace if already present) a custom hash builtin. Returns a Relocatable - /// with the new builtin base as the segment index. - pub fn add_additional_hash_builtin(&self, vm: &mut VirtualMachine) -> Relocatable { - // Create, initialize and insert the new custom hash runner. - let mut builtin: BuiltinRunner = HashBuiltinRunner::new(Some(32), true).into(); - builtin.initialize_segments(&mut vm.segments); - let segment_index = builtin.base() as isize; - vm.builtin_runners.push(builtin); - - Relocatable { - segment_index, - offset: 0, - } - } - // Iterates over the program builtins in reverse, calling BuiltinRunner::final_stack on each of them and returns the final pointer // This method is used by cairo-vm-py to replace starknet functionality pub fn get_builtins_final_stack( @@ -4799,32 +4783,6 @@ mod tests { assert_eq!(bitwise_builtin.stop_ptr, Some(5)); } - /// Test that add_additional_hash_builtin() creates an additional builtin. - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn add_additional_hash_builtin() { - let program = program!(); - let cairo_runner = cairo_runner!(program); - let mut vm = vm!(); - - let num_builtins = vm.builtin_runners.len(); - cairo_runner.add_additional_hash_builtin(&mut vm); - assert_eq!(vm.builtin_runners.len(), num_builtins + 1); - - let builtin = vm - .builtin_runners - .last() - .expect("missing last builtin runner"); - match builtin { - BuiltinRunner::Hash(builtin) => { - assert_eq!(builtin.base(), 0); - assert_eq!(builtin.ratio(), Some(32)); - assert!(builtin.included); - } - _ => unreachable!(), - } - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_from_entrypoint_custom_program_test() { diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 8bd47ea1df..a4098464e5 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -32,7 +32,7 @@ use core::num::NonZeroUsize; use num_traits::{ToPrimitive, Zero}; use super::errors::runner_errors::RunnerError; -use super::runners::builtin_runner::OUTPUT_BUILTIN_NAME; +use super::runners::builtin_runner::{OutputBuiltinRunner, OUTPUT_BUILTIN_NAME}; const MAX_TRACEBACK_ENTRIES: u32 = 20; @@ -942,8 +942,17 @@ impl VirtualMachine { Err(VirtualMachineError::NoSignatureBuiltin) } - pub fn disable_trace(&mut self) { - self.trace = None + + pub fn get_output_builtin_mut( + &mut self, + ) -> Result<&mut OutputBuiltinRunner, VirtualMachineError> { + for builtin in self.get_builtin_runners_as_mut() { + if let BuiltinRunner::Output(output_builtin) = builtin { + return Ok(output_builtin); + }; + } + + Err(VirtualMachineError::NoOutputBuiltin) } #[cfg(feature = "with_tracer")] @@ -1018,7 +1027,6 @@ impl VirtualMachine { let segment_used_sizes = self.segments.compute_effective_sizes(); let segment_index = builtin.base(); - #[allow(deprecated)] for i in 0..segment_used_sizes[segment_index] { let formatted_value = match self .segments @@ -3844,11 +3852,26 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn disable_trace() { - let mut vm = VirtualMachine::new(true); - assert!(vm.trace.is_some()); - vm.disable_trace(); - assert!(vm.trace.is_none()); + fn test_get_output_builtin_mut() { + let mut vm = vm!(); + + assert_matches!( + vm.get_output_builtin_mut(), + Err(VirtualMachineError::NoOutputBuiltin) + ); + + let output_builtin = OutputBuiltinRunner::new(true); + vm.builtin_runners.push(output_builtin.clone().into()); + + let vm_output_builtin = vm + .get_output_builtin_mut() + .expect("Output builtin should be returned"); + + assert_eq!(vm_output_builtin.base(), output_builtin.base()); + assert_eq!(vm_output_builtin.pages, output_builtin.pages); + assert_eq!(vm_output_builtin.attributes, output_builtin.attributes); + assert_eq!(vm_output_builtin.stop_ptr, output_builtin.stop_ptr); + assert_eq!(vm_output_builtin.included, output_builtin.included); } #[test]