diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1f342fca05..3752d4df51 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -642,6 +642,37 @@ jobs: key: codecov-cache-test-no_std-mod_builtin-${{ github.sha }} fail-on-cache-miss: true + - name: Fetch results for tests with stdlib (w/cairo-0-secp-hints; part. 1) + uses: actions/cache/restore@v3 + with: + path: lcov-test#1-cairo-0-secp-hints.info + key: codecov-cache-test#1-cairo-0-secp-hints-${{ github.sha }} + fail-on-cache-miss: true + - name: Fetch results for tests with stdlib (w/cairo-0-secp-hints; part. 2) + uses: actions/cache/restore@v3 + with: + path: lcov-test#2-cairo-0-secp-hints.info + key: codecov-cache-test#2-cairo-0-secp-hints-${{ github.sha }} + fail-on-cache-miss: true + - name: Fetch results for tests with stdlib (w/cairo-0-secp-hints; part. 3) + uses: actions/cache/restore@v3 + with: + path: lcov-test#3-cairo-0-secp-hints.info + key: codecov-cache-test#3-cairo-0-secp-hints-${{ github.sha }} + fail-on-cache-miss: true + - name: Fetch results for tests with stdlib (w/cairo-0-secp-hints; part. 4) + uses: actions/cache/restore@v3 + with: + path: lcov-test#4-cairo-0-secp-hints.info + key: codecov-cache-test#4-cairo-0-secp-hints-${{ github.sha }} + fail-on-cache-miss: true + - name: Fetch results for tests without stdlib (w/cairo-0-secp-hints) + uses: actions/cache/restore@v3 + with: + path: lcov-no_std-cairo-0-secp-hints.info + key: codecov-cache-test-no_std-cairo-0-secp-hints-${{ github.sha }} + fail-on-cache-miss: true + - name: Upload coverage to codecov.io uses: codecov/codecov-action@v3 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c3f5ab209..6add017a25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ #### Upcoming Changes +* chore: Pin types-rs version to the one set in lockfile [#2140](https://github.com/lambdaclass/cairo-vm/pull/2140) + +* BREAKING CHANGE: `get_prover_input_info()` now requires `&mut self` and takes ownershop on the trace instead of cloning it. [#2127](https://github.com/lambdaclass/cairo-vm/pull/2127) + +* Refactor: Replaced HashMap with BTreeMap to guarantee deterministic ordering of the data [#2023] (https://github.com/lambdaclass/cairo-vm/pull/2023) + +* fix: Updated the logic for collecting builtin segment data for prover input info, removing dependency on the existence of stop pointers. [#2022](https://github.com/lambdaclass/cairo-vm/pull/2022) + +* fix: Keep None values in memory segments for the prover input info [#2021](https://github.com/lambdaclass/cairo-vm/pull/2021) + * fix: Always use a normal segment in first SegmentArena segment [#1845](https://github.com/lambdaclass/cairo-vm/pull/1845) * chore: update cairo-lang dependencies to 2.12.0-dev.0 #[2040](https://github.com/lambdaclass/cairo-vm/pull/2040) @@ -28,6 +38,8 @@ * feat: Add `ProverInfo` and extract the relevant information for it from the runner [#2001](https://github.com/lambdaclass/cairo-vm/pull/2001) +* feat: Support hints for new blake felt serialization library code [#1994](https://github.com/lambdaclass/cairo-vm/pull/1994) + #### [2.0.1] - 2025-03-17 * feat: Limited padding of builtin segments to >=16 [#1981](https://github.com/lambdaclass/cairo-vm/pull/1981) diff --git a/cairo-vm-cli/src/main.rs b/cairo-vm-cli/src/main.rs index 6bb41c0496..35eab0bd58 100644 --- a/cairo-vm-cli/src/main.rs +++ b/cairo-vm-cli/src/main.rs @@ -179,12 +179,13 @@ fn run(args: impl Iterator) -> Result<(), Error> { entrypoint: &args.entrypoint, trace_enabled, relocate_mem: args.memory_file.is_some() || args.air_public_input.is_some(), + relocate_trace: trace_enabled, layout: args.layout, proof_mode: args.proof_mode, secure_run: args.secure_run, allow_missing_builtins: args.allow_missing_builtins, dynamic_layout_params: cairo_layout_params, - ..Default::default() + disable_trace_padding: false, }; let mut cairo_runner = match if args.run_from_cairo_pie { diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index fbe0760d94..51418afbc3 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -267,7 +267,12 @@ pub fn cairo_run_program( runner.run_for_steps(1, &mut hint_processor)?; } - runner.end_run(false, false, &mut hint_processor)?; + runner.end_run( + false, + false, + &mut hint_processor, + cairo_run_config.proof_mode, + )?; let result_inner_type_size = result_inner_type_size(return_type_id, &sierra_program_registry, &type_sizes); @@ -336,7 +341,7 @@ pub fn cairo_run_program( } } - runner.relocate(true)?; + runner.relocate(true, true)?; Ok((runner, return_values, serialized_output)) } diff --git a/cairo_programs/cairo-0-secp-hints-feature/negative_points.cairo b/cairo_programs/cairo-0-secp-hints-feature/negative_points.cairo new file mode 100644 index 0000000000..3fdaae6245 --- /dev/null +++ b/cairo_programs/cairo-0-secp-hints-feature/negative_points.cairo @@ -0,0 +1,167 @@ +%builtins range_check + +from starkware.cairo.common.secp256r1.ec import ( + EcPoint, +) +from starkware.cairo.common.secp256r1.bigint import nondet_bigint3 +from starkware.cairo.common.cairo_secp.bigint3 import BigInt3 + +func main{range_check_ptr: felt}() { + let point = EcPoint( + BigInt3(1, 2, 3), + BigInt3(-1, -2, -3), + ); + + let slope = compute_doubling_slope_prime(point); + assert slope = BigInt3( + 15487438216801236710343013, 27596288489803578791625491, 8178446608657045587339469 + ); + + let slope = compute_doubling_slope_secp256r1(point); + assert slope = BigInt3( + 56511396263956479754791421, 38561311687768998103117219, 2015104701319196654781984 + ); + + let x = BigInt3(-1,-2,-3); + let y = try_get_point_from_x_prime(x, 0); + assert y = BigInt3( + 39197606747300743094893670, 38008389934708701866119639, 2071781356858789560884686 + ); + + let x = BigInt3(-1,-2,-3); + let y = try_get_point_from_x_secp256r1(x, 0); + assert y = BigInt3( + 56004882917990234964232380, 17943756516348761157632108, 3811440313376405071875160 + ); + + let slope = BigInt3(-1,-2,-3); + let x = ec_double_x_prime(point, slope); + assert x = BigInt3( + 648518346341351470, 77370588372549613637288996, 18662792551970020321619971 + ); + + let slope = BigInt3(-1,-2,-3); + let x = ec_double_x_secp256r1(point, slope); + assert x = BigInt3( + 21299552074028835321108137, 50187220174510023990904347, 2291813387120727975022296 + ); + + return (); +} + +func compute_doubling_slope_prime{range_check_ptr}(point: EcPoint) -> BigInt3 { + %{ + from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_ALPHA, SECP256R1_P + from starkware.cairo.common.cairo_secp.secp_utils import pack + from starkware.python.math_utils import ec_double_slope + + # Compute the slope. + x = pack(ids.point.x, PRIME) + y = pack(ids.point.y, PRIME) + value = slope = ec_double_slope(point=(x, y), alpha=SECP256R1_ALPHA, p=SECP256R1_P) + %} + let (slope: BigInt3) = nondet_bigint3(); + return slope; +} + +func compute_doubling_slope_secp256r1{range_check_ptr}(point: EcPoint) -> BigInt3 { + %{ + from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_ALPHA, SECP256R1_P + from starkware.cairo.common.cairo_secp.secp_utils import pack + from starkware.python.math_utils import ec_double_slope + + # Compute the slope. + x = pack(ids.point.x, SECP256R1_P) + y = pack(ids.point.y, SECP256R1_P) + value = slope = ec_double_slope(point=(x, y), alpha=SECP256R1_ALPHA, p=SECP256R1_P) + %} + let (slope: BigInt3) = nondet_bigint3(); + return slope; +} + +func try_get_point_from_x_prime{range_check_ptr}(x: BigInt3, v: felt) -> BigInt3 { + %{ + from starkware.cairo.common.cairo_secp.secp_utils import SECP256R1, pack + from starkware.python.math_utils import y_squared_from_x + + y_square_int = y_squared_from_x( + x=pack(ids.x, PRIME), + alpha=SECP256R1.alpha, + beta=SECP256R1.beta, + field_prime=SECP256R1.prime, + ) + + # Note that (y_square_int ** ((SECP256R1.prime + 1) / 4)) ** 2 = + # = y_square_int ** ((SECP256R1.prime + 1) / 2) = + # = y_square_int ** ((SECP256R1.prime - 1) / 2 + 1) = + # = y_square_int * y_square_int ** ((SECP256R1.prime - 1) / 2) = y_square_int * {+/-}1. + y = pow(y_square_int, (SECP256R1.prime + 1) // 4, SECP256R1.prime) + + # We need to decide whether to take y or prime - y. + if ids.v % 2 == y % 2: + value = y + else: + value = (-y) % SECP256R1.prime + %} + let (y: BigInt3) = nondet_bigint3(); + return y; +} + +func try_get_point_from_x_secp256r1{range_check_ptr}(x: BigInt3, v: felt) -> BigInt3 { + %{ + from starkware.cairo.common.cairo_secp.secp_utils import SECP256R1, pack + from starkware.python.math_utils import y_squared_from_x + + y_square_int = y_squared_from_x( + x=pack(ids.x, SECP256R1.prime), + alpha=SECP256R1.alpha, + beta=SECP256R1.beta, + field_prime=SECP256R1.prime, + ) + + # Note that (y_square_int ** ((SECP256R1.prime + 1) / 4)) ** 2 = + # = y_square_int ** ((SECP256R1.prime + 1) / 2) = + # = y_square_int ** ((SECP256R1.prime - 1) / 2 + 1) = + # = y_square_int * y_square_int ** ((SECP256R1.prime - 1) / 2) = y_square_int * {+/-}1. + y = pow(y_square_int, (SECP256R1.prime + 1) // 4, SECP256R1.prime) + + # We need to decide whether to take y or prime - y. + if ids.v % 2 == y % 2: + value = y + else: + value = (-y) % SECP256R1.prime + %} + let (y: BigInt3) = nondet_bigint3(); + return y; +} + + +func ec_double_x_prime{range_check_ptr}(point: EcPoint, slope: BigInt3) -> BigInt3 { + %{ + from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P + from starkware.cairo.common.cairo_secp.secp_utils import pack + + slope = pack(ids.slope, PRIME) + x = pack(ids.point.x, PRIME) + y = pack(ids.point.y, PRIME) + + value = new_x = (pow(slope, 2, SECP256R1_P) - 2 * x) % SECP256R1_P + %} + let (new_x: BigInt3) = nondet_bigint3(); + return new_x; +} + +func ec_double_x_secp256r1{range_check_ptr}(point: EcPoint, slope: BigInt3) -> BigInt3 { + %{ + from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P + from starkware.cairo.common.cairo_secp.secp_utils import pack + + slope = pack(ids.slope, SECP256R1_P) + x = pack(ids.point.x, SECP256R1_P) + y = pack(ids.point.y, SECP256R1_P) + + value = new_x = (pow(slope, 2, SECP256R1_P) - 2 * x) % SECP256R1_P + %} + let (new_x: BigInt3) = nondet_bigint3(); + return new_x; +} diff --git a/cairo_programs/poseidon_builtin_hole.cairo b/cairo_programs/poseidon_builtin_hole.cairo new file mode 100644 index 0000000000..9a8d719c30 --- /dev/null +++ b/cairo_programs/poseidon_builtin_hole.cairo @@ -0,0 +1,12 @@ +%builtins poseidon +from starkware.cairo.common.cairo_builtins import PoseidonBuiltin +from starkware.cairo.common.poseidon_state import PoseidonBuiltinState + +func main{poseidon_ptr: PoseidonBuiltin*}() { + assert poseidon_ptr[0].input = PoseidonBuiltinState(1, 2, 3); + let result = poseidon_ptr[0].output; + let poseidon_ptr = poseidon_ptr + PoseidonBuiltin.SIZE; + assert result.s0 = 442682200349489646213731521593476982257703159825582578145778919623645026501; + assert result.s2 = 2512222140811166287287541003826449032093371832913959128171347018667852712082; + return (); +} diff --git a/requirements.txt b/requirements.txt index 6480d4d6e9..86b2eba9db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ bitarray==2.7.3 fastecdsa==2.3.2 sympy==1.11.1 typeguard==2.13.3 -cairo-lang==0.13.3 +cairo-lang==0.13.5 diff --git a/vm/Cargo.toml b/vm/Cargo.toml index be960a993c..071e3b585a 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -47,7 +47,7 @@ num-integer = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } hex = { workspace = true } -bincode = { workspace = true } +bincode = { workspace = true , features = ["alloc"]} starknet-crypto = { workspace = true } sha3 = { workspace = true } indoc = { workspace = true } @@ -59,7 +59,7 @@ keccak = { workspace = true } hashbrown = { workspace = true } anyhow = { workspace = true } thiserror = { workspace = true } -starknet-types-core = { version = "0.1.2", default-features = false, features = ["serde", "curve", "num-traits", "hash"] } +starknet-types-core = { version = "=0.1.7", default-features = false, features = ["serde", "curve", "num-traits", "hash"] } rust_decimal = { version = "1.35.0", default-features = false } # only for std diff --git a/vm/src/cairo_run.rs b/vm/src/cairo_run.rs index 63c40f19d1..88c706c4c0 100644 --- a/vm/src/cairo_run.rs +++ b/vm/src/cairo_run.rs @@ -27,7 +27,10 @@ pub struct CairoRunConfig<'a> { #[cfg_attr(feature = "test_utils", arbitrary(value = "main"))] pub entrypoint: &'a str, pub trace_enabled: bool, + /// Relocate memory if `true`, otherwise memory is not relocated. pub relocate_mem: bool, + // When `relocate_trace` is set to `false`, the trace will not be relocated even if `trace_enabled` is `true`. + pub relocate_trace: bool, pub layout: LayoutName, /// The `dynamic_layout_params` argument should only be used with dynamic layout. /// It is ignored otherwise. @@ -51,6 +54,8 @@ impl Default for CairoRunConfig<'_> { entrypoint: "main", trace_enabled: false, relocate_mem: false, + // Set to true to match expected behavior: trace is relocated only if trace_enabled is true. + relocate_trace: true, layout: LayoutName::plain, proof_mode: false, secure_run: None, @@ -103,9 +108,9 @@ pub fn cairo_run_program_with_initial_scope( cairo_run_config.disable_trace_padding, false, hint_processor, + cairo_run_config.proof_mode, )?; - cairo_runner.vm.verify_auto_deductions()?; cairo_runner.read_return_values(allow_missing_builtins)?; if cairo_run_config.proof_mode { cairo_runner.finalize_segments()?; @@ -113,7 +118,10 @@ pub fn cairo_run_program_with_initial_scope( if secure_run { verify_secure_runner(&cairo_runner, true, None)?; } - cairo_runner.relocate(cairo_run_config.relocate_mem)?; + cairo_runner.relocate( + cairo_run_config.relocate_mem, + cairo_run_config.relocate_trace, + )?; Ok(cairo_runner) } @@ -208,9 +216,9 @@ pub fn cairo_run_pie( cairo_run_config.disable_trace_padding, false, hint_processor, + cairo_run_config.proof_mode, )?; - cairo_runner.vm.verify_auto_deductions()?; cairo_runner.read_return_values(allow_missing_builtins)?; if secure_run { @@ -218,7 +226,10 @@ pub fn cairo_run_pie( // Check that the Cairo PIE produced by this run is compatible with the Cairo PIE received cairo_runner.get_cairo_pie()?.check_pie_compatibility(pie)?; } - cairo_runner.relocate(cairo_run_config.relocate_mem)?; + cairo_runner.relocate( + cairo_run_config.relocate_mem, + cairo_run_config.relocate_trace, + )?; Ok(cairo_runner) } @@ -258,9 +269,8 @@ pub fn cairo_run_fuzzed_program( res.map_err(|err| VmException::from_vm_error(&cairo_runner, err))?; - cairo_runner.end_run(false, false, hint_processor)?; + cairo_runner.end_run(false, false, hint_processor, cairo_run_config.proof_mode)?; - cairo_runner.vm.verify_auto_deductions()?; cairo_runner.read_return_values(allow_missing_builtins)?; if cairo_run_config.proof_mode { cairo_runner.finalize_segments()?; @@ -268,7 +278,10 @@ pub fn cairo_run_fuzzed_program( if secure_run { verify_secure_runner(&cairo_runner, true, None)?; } - cairo_runner.relocate(cairo_run_config.relocate_mem)?; + cairo_runner.relocate( + cairo_run_config.relocate_mem, + cairo_run_config.relocate_trace, + )?; Ok(cairo_runner) } @@ -368,7 +381,7 @@ mod tests { let end = cairo_runner.initialize(false).unwrap(); assert!(cairo_runner.run_until_pc(end, &mut hint_processor).is_ok()); - assert!(cairo_runner.relocate(true).is_ok()); + assert!(cairo_runner.relocate(true, true).is_ok()); // `main` returns without doing nothing, but `not_main` sets `[ap]` to `1` // Memory location was found empirically and simply hardcoded assert_eq!(cairo_runner.relocated_memory[2], Some(Felt252::from(123))); @@ -434,7 +447,7 @@ mod tests { let mut hint_processor = BuiltinHintProcessor::new_empty(); let mut cairo_runner = run_test_program(program_content, &mut hint_processor).unwrap(); - assert!(cairo_runner.relocate(false).is_ok()); + assert!(cairo_runner.relocate(false, true).is_ok()); let trace_entries = cairo_runner.relocated_trace.unwrap(); let mut buffer = [0; 24]; @@ -458,7 +471,7 @@ mod tests { let mut cairo_runner = run_test_program(program_content, &mut hint_processor).unwrap(); // relocate memory so we can dump it to file - assert!(cairo_runner.relocate(true).is_ok()); + assert!(cairo_runner.relocate(true, true).is_ok()); let mut buffer = [0; 120]; let mut buff_writer = SliceWriter::new(&mut buffer); @@ -482,7 +495,7 @@ mod tests { let mut cairo_runner = cairo_runner!(program); let end = cairo_runner.initialize(false).unwrap(); assert!(cairo_runner.run_until_pc(end, &mut hint_processor).is_ok()); - assert!(cairo_runner.relocate(false).is_ok()); + assert!(cairo_runner.relocate(false, false).is_ok()); assert!(cairo_runner.relocated_trace.is_none()); } diff --git a/vm/src/hint_processor/builtin_hint_processor/blake2s_utils.rs b/vm/src/hint_processor/builtin_hint_processor/blake2s_utils.rs index 3251cff0e8..8dfcedf030 100644 --- a/vm/src/hint_processor/builtin_hint_processor/blake2s_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/blake2s_utils.rs @@ -1,3 +1,4 @@ +use crate::hint_processor::hint_processor_utils::felt_to_usize; use crate::stdlib::{borrow::Cow, collections::HashMap, prelude::*}; use crate::types::errors::math_errors::MathError; @@ -17,9 +18,11 @@ use crate::{ vm::{errors::hint_errors::HintError, vm_core::VirtualMachine}, }; +use num_bigint::BigUint; +use num_integer::Integer; use num_traits::ToPrimitive; -use super::hint_utils::get_integer_from_var_name; +use super::hint_utils::{get_integer_from_var_name, insert_value_into_ap}; fn get_fixed_size_u32_array( h_range: &Vec>, @@ -242,6 +245,84 @@ pub fn blake2s_add_uint256_bigend( Ok(()) } +/* Implements Hint: +memory[ap] = (ids.end != ids.packed_values) and (memory[ids.packed_values] < 2**63) +*/ +pub fn is_less_than_63_bits_and_not_end( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + let end = get_ptr_from_var_name("end", vm, ids_data, ap_tracking)?; + let packed_values = get_ptr_from_var_name("packed_values", vm, ids_data, ap_tracking)?; + + if end == packed_values { + insert_value_into_ap(vm, 0)? + } else { + let val = vm.get_integer(packed_values)?; + insert_value_into_ap( + vm, + (val.to_biguint() < (BigUint::from(1_u32) << 63)) as usize, + )? + } + Ok(()) +} + +/* Implements Hint: +offset = 0 +for i in range(ids.packed_values_len): + val = (memory[ids.packed_values + i] % PRIME) + val_len = 2 if val < 2**63 else 8 + if val_len == 8: + val += 2**255 + for i in range(val_len - 1, -1, -1): + val, memory[ids.unpacked_u32s + offset + i] = divmod(val, 2**32) + assert val == 0 + offset += val_len +*/ +pub fn blake2s_unpack_felts( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + let packed_values_len = + get_integer_from_var_name("packed_values_len", vm, ids_data, ap_tracking)?; + let packed_values = get_ptr_from_var_name("packed_values", vm, ids_data, ap_tracking)?; + let unpacked_u32s = get_ptr_from_var_name("unpacked_u32s", vm, ids_data, ap_tracking)?; + + let vals = vm.get_integer_range(packed_values, felt_to_usize(&packed_values_len)?)?; + let pow2_32 = BigUint::from(1_u32) << 32; + let pow2_63 = BigUint::from(1_u32) << 63; + let pow2_255 = BigUint::from(1_u32) << 255; + + // Split value into either 2 or 8 32-bit limbs. + let out: Vec = vals + .into_iter() + .map(|val| val.to_biguint()) + .flat_map(|val| { + if val < pow2_63 { + let (high, low) = val.div_rem(&pow2_32); + vec![high, low] + } else { + let mut limbs = vec![BigUint::from(0_u32); 8]; + let mut val: BigUint = val + &pow2_255; + for limb in limbs.iter_mut().rev() { + let (q, r) = val.div_rem(&pow2_32); + *limb = r; + val = q; + } + limbs + } + }) + .map(Felt252::from) + .map(MaybeRelocatable::from) + .collect(); + + vm.load_data(unpacked_u32s, &out) + .map_err(HintError::Memory)?; + Ok(()) +} + /* Implements Hint: %{ from starkware.cairo.common.cairo_blake2s.blake2s_utils import IV, blake2s_compress @@ -604,6 +685,103 @@ mod tests { .is_none()); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn is_less_than_63_bits_and_not_end_ends() { + let hint_code = hint_code::IS_LESS_THAN_63_BITS_AND_NOT_END; + //Create vm + let mut vm = vm!(); + //Insert ids into memory + vm.segments = segments![((1, 0), (1, 2)), ((1, 1), (1, 2)), ((1, 2), 123)]; + vm.set_fp(3); + vm.set_ap(3); + let ids_data = ids_data!["end", "packed_values", "value"]; + //Execute the hint + assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(())); + //Check data ptr + check_memory![vm.segments.memory, ((1, 3), 0)]; + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn is_less_than_63_bits_and_not_end_small() { + let hint_code = hint_code::IS_LESS_THAN_63_BITS_AND_NOT_END; + //Create vm + let mut vm = vm!(); + //Insert ids into memory + vm.segments = segments![((1, 0), (1, 3)), ((1, 1), (1, 2)), ((1, 2), 123)]; + vm.set_fp(3); + vm.set_ap(3); + let ids_data = ids_data!["end", "packed_values", "value"]; + //Execute the hint + assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(())); + //Check data ptr + check_memory![vm.segments.memory, ((1, 3), 1)]; + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn is_less_than_63_bits_and_not_end_big() { + let hint_code = hint_code::IS_LESS_THAN_63_BITS_AND_NOT_END; + //Create vm + let mut vm = vm!(); + //Insert ids into memory + vm.segments = segments![ + ((1, 0), (1, 3)), + ((1, 1), (1, 2)), + ((1, 2), 0x10000000000000000) + ]; + vm.set_fp(3); + vm.set_ap(3); + let ids_data = ids_data!["end", "packed_values", "value"]; + //Execute the hint + assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(())); + //Check data ptr + check_memory![vm.segments.memory, ((1, 3), 0)]; + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn blake2s_unpack_felts() { + let hint_code = hint_code::BLAKE2S_UNPACK_FELTS; + //Create vm + let mut vm = vm!(); + //Insert ids into memory + vm.segments = segments![ + ((1, 0), 2), + ((1, 1), (1, 3)), + ((1, 2), (2, 0)), + ((1, 3), 0x123456781234), + ((1, 4), 0x1234abcd5678efab1234abcd) + ]; + vm.set_fp(5); + vm.set_ap(5); + let ids_data = ids_data![ + "packed_values_len", + "packed_values", + "unpacked_u32s", + "small_value", + "big_value" + ]; + vm.segments.add(); + //Execute the hint + assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(())); + //Check data ptr + check_memory![ + vm.segments.memory, + ((2, 0), 0x1234), + ((2, 1), 0x56781234), + ((2, 2), 0x80000000), + ((2, 3), 0), + ((2, 4), 0), + ((2, 5), 0), + ((2, 6), 0), + ((2, 7), 0x1234abcd), + ((2, 8), 0x5678efab), + ((2, 9), 0x1234abcd) + ]; + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn blake2s_add_uint256_bigend_valid_non_zero() { diff --git a/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs b/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs index f5ae7a96c4..4ba74eccb4 100644 --- a/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs +++ b/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs @@ -1,7 +1,6 @@ -#[cfg(feature = "cairo-0-secp-hints")] -use super::secp::cairo0_hints; +use super::blake2s_utils::example_blake2s_compress; use super::{ - blake2s_utils::finalize_blake2s_v3, + blake2s_utils::{blake2s_unpack_felts, finalize_blake2s_v3, is_less_than_63_bits_and_not_end}, ec_recover::{ ec_recover_divmod_n_packed, ec_recover_product_div_m, ec_recover_product_mod, ec_recover_sub_a_b, @@ -25,6 +24,10 @@ use super::{ }, }; use crate::Felt252; +use crate::{ + hint_processor::builtin_hint_processor::secp::secp_utils::{SECP256R1_ALPHA, SECP256R1_P}, + utils::CAIRO_PRIME, +}; use crate::{ hint_processor::{ builtin_hint_processor::secp::ec_utils::{ @@ -117,17 +120,14 @@ use crate::{ vm::{errors::hint_errors::HintError, vm_core::VirtualMachine}, }; +#[cfg(feature = "cairo-0-secp-hints")] +use crate::hint_processor::builtin_hint_processor::secp::cairo0_hints; #[cfg(feature = "test_utils")] -use crate::hint_processor::builtin_hint_processor::skip_next_instruction::skip_next_instruction; - -#[cfg(feature = "test_utils")] -use crate::hint_processor::builtin_hint_processor::print::{print_array, print_dict, print_felt}; -use crate::hint_processor::builtin_hint_processor::secp::secp_utils::{ - SECP256R1_ALPHA, SECP256R1_P, +use crate::hint_processor::builtin_hint_processor::{ + print::{print_array, print_dict, print_felt}, + skip_next_instruction::skip_next_instruction, }; -use super::blake2s_utils::example_blake2s_compress; - pub struct HintProcessorData { pub code: String, pub ap_tracking: ApTracking, @@ -360,6 +360,12 @@ impl HintProcessorLogic for BuiltinHintProcessor { hint_code::BLAKE2S_ADD_UINT256_BIGEND => { blake2s_add_uint256_bigend(vm, &hint_data.ids_data, &hint_data.ap_tracking) } + hint_code::IS_LESS_THAN_63_BITS_AND_NOT_END => { + is_less_than_63_bits_and_not_end(vm, &hint_data.ids_data, &hint_data.ap_tracking) + } + hint_code::BLAKE2S_UNPACK_FELTS => { + blake2s_unpack_felts(vm, &hint_data.ids_data, &hint_data.ap_tracking) + } hint_code::UNSAFE_KECCAK => { unsafe_keccak(vm, exec_scopes, &hint_data.ids_data, &hint_data.ap_tracking) } @@ -517,6 +523,7 @@ impl HintProcessorLogic for BuiltinHintProcessor { &hint_data.ids_data, &hint_data.ap_tracking, "point", + &CAIRO_PRIME, &SECP_P, &ALPHA, ), @@ -526,6 +533,7 @@ impl HintProcessorLogic for BuiltinHintProcessor { &hint_data.ids_data, &hint_data.ap_tracking, "point", + &CAIRO_PRIME, &SECP_P_V2, &ALPHA_V2, ), @@ -535,6 +543,7 @@ impl HintProcessorLogic for BuiltinHintProcessor { &hint_data.ids_data, &hint_data.ap_tracking, "pt", + &CAIRO_PRIME, &SECP_P, &ALPHA, ), @@ -544,6 +553,17 @@ impl HintProcessorLogic for BuiltinHintProcessor { &hint_data.ids_data, &hint_data.ap_tracking, "point", + SECP256R1_P.magnitude(), + &SECP256R1_P, + &SECP256R1_ALPHA, + ), + hint_code::EC_DOUBLE_SLOPE_V5 => compute_doubling_slope( + vm, + exec_scopes, + &hint_data.ids_data, + &hint_data.ap_tracking, + "point", + &CAIRO_PRIME, &SECP256R1_P, &SECP256R1_ALPHA, ), @@ -899,6 +919,16 @@ impl HintProcessorLogic for BuiltinHintProcessor { &hint_data.ids_data, &hint_data.ap_tracking, constants, + SECP256R1_P.magnitude(), + ), + #[cfg(feature = "cairo-0-secp-hints")] + cairo0_hints::SECP_DOUBLE_ASSIGN_NEW_X_V2 => cairo0_hints::secp_double_assign_new_x( + vm, + exec_scopes, + &hint_data.ids_data, + &hint_data.ap_tracking, + constants, + &CAIRO_PRIME, ), #[cfg(feature = "cairo-0-secp-hints")] cairo0_hints::FAST_SECP_ADD_ASSIGN_NEW_Y => cairo0_hints::fast_secp_add_assign_new_y( @@ -948,6 +978,17 @@ impl HintProcessorLogic for BuiltinHintProcessor { &hint_data.ids_data, &hint_data.ap_tracking, constants, + SECP256R1_P.magnitude(), + ), + + #[cfg(feature = "cairo-0-secp-hints")] + cairo0_hints::SECP_R1_GET_POINT_FROM_X_V2 => cairo0_hints::r1_get_point_from_x( + vm, + exec_scopes, + &hint_data.ids_data, + &hint_data.ap_tracking, + constants, + &CAIRO_PRIME, ), #[cfg(feature = "cairo-0-secp-hints")] diff --git a/vm/src/hint_processor/builtin_hint_processor/hint_code.rs b/vm/src/hint_processor/builtin_hint_processor/hint_code.rs index c8c5aaebc2..5d0919e0c8 100644 --- a/vm/src/hint_processor/builtin_hint_processor/hint_code.rs +++ b/vm/src/hint_processor/builtin_hint_processor/hint_code.rs @@ -426,6 +426,17 @@ segments.write_arg(ids.data + 4, [(ids.high >> (B * i)) & MASK for i in range(4) MASK = 2 ** 32 - 1 segments.write_arg(ids.data, [(ids.high >> (B * (3 - i))) & MASK for i in range(4)]) segments.write_arg(ids.data + 4, [(ids.low >> (B * (3 - i))) & MASK for i in range(4)])"#}), +(IS_LESS_THAN_63_BITS_AND_NOT_END, indoc! {r#"memory[ap] = to_felt_or_relocatable((ids.end != ids.packed_values) and (memory[ids.packed_values] < 2**63))"#}), +(BLAKE2S_UNPACK_FELTS, indoc! {r#"offset = 0 +for i in range(ids.packed_values_len): + val = (memory[ids.packed_values + i] % PRIME) + val_len = 2 if val < 2**63 else 8 + if val_len == 8: + val += 2**255 + for i in range(val_len - 1, -1, -1): + val, memory[ids.unpacked_u32s + offset + i] = divmod(val, 2**32) + assert val == 0 + offset += val_len"#}), (EXAMPLE_BLAKE2S_COMPRESS, indoc! {r#"from starkware.cairo.common.cairo_blake2s.blake2s_utils import IV, blake2s_compress _blake2s_input_chunk_size_felts = int(ids.BLAKE2S_INPUT_CHUNK_SIZE_FELTS) @@ -609,6 +620,14 @@ from starkware.python.math_utils import ec_double_slope x = pack(ids.point.x, SECP256R1_P) y = pack(ids.point.y, SECP256R1_P) value = slope = ec_double_slope(point=(x, y), alpha=SECP256R1_ALPHA, p=SECP256R1_P)"#}), +(EC_DOUBLE_SLOPE_V5, indoc! {r#"from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_ALPHA, SECP256R1_P +from starkware.cairo.common.cairo_secp.secp_utils import pack +from starkware.python.math_utils import ec_double_slope + +# Compute the slope. +x = pack(ids.point.x, PRIME) +y = pack(ids.point.y, PRIME) +value = slope = ec_double_slope(point=(x, y), alpha=SECP256R1_ALPHA, p=SECP256R1_P)"#}), (EC_DOUBLE_SLOPE_EXTERNAL_CONSTS, indoc! {r#"from starkware.cairo.common.cairo_secp.secp_utils import pack from starkware.python.math_utils import ec_double_slope diff --git a/vm/src/hint_processor/builtin_hint_processor/secp/bigint_utils.rs b/vm/src/hint_processor/builtin_hint_processor/secp/bigint_utils.rs index 5b32f4e691..d355ac67c0 100644 --- a/vm/src/hint_processor/builtin_hint_processor/secp/bigint_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/secp/bigint_utils.rs @@ -95,6 +95,17 @@ impl BigIntN<'_, NUM_LIMBS> { .sum() } + pub(crate) fn pack86_for_prime(self, prime: &BigUint) -> BigInt { + self.limbs + .into_iter() + .take(3) + .enumerate() + .map(|(idx, value)| { + crate::math_utils::signed_felt_for_prime(*value, prime).shl(idx * 86) + }) + .sum() + } + pub(crate) fn split(num: &BigUint) -> Self { let limbs = split(num, 128); Self::from_values(limbs) diff --git a/vm/src/hint_processor/builtin_hint_processor/secp/cairo0_hints.rs b/vm/src/hint_processor/builtin_hint_processor/secp/cairo0_hints.rs index f3ffeac78f..78ffd3b42c 100644 --- a/vm/src/hint_processor/builtin_hint_processor/secp/cairo0_hints.rs +++ b/vm/src/hint_processor/builtin_hint_processor/secp/cairo0_hints.rs @@ -67,6 +67,27 @@ y_square_int = y_squared_from_x( # = y_square_int * y_square_int ** ((SECP256R1.prime - 1) / 2) = y_square_int * {+/-}1. y = pow(y_square_int, (SECP256R1.prime + 1) // 4, SECP256R1.prime) +# We need to decide whether to take y or prime - y. +if ids.v % 2 == y % 2: + value = y +else: + value = (-y) % SECP256R1.prime"#}), +(SECP_R1_GET_POINT_FROM_X_V2, indoc! {r#"from starkware.cairo.common.cairo_secp.secp_utils import SECP256R1, pack +from starkware.python.math_utils import y_squared_from_x + +y_square_int = y_squared_from_x( + x=pack(ids.x, PRIME), + alpha=SECP256R1.alpha, + beta=SECP256R1.beta, + field_prime=SECP256R1.prime, +) + +# Note that (y_square_int ** ((SECP256R1.prime + 1) / 4)) ** 2 = +# = y_square_int ** ((SECP256R1.prime + 1) / 2) = +# = y_square_int ** ((SECP256R1.prime - 1) / 2 + 1) = +# = y_square_int * y_square_int ** ((SECP256R1.prime - 1) / 2) = y_square_int * {+/-}1. +y = pow(y_square_int, (SECP256R1.prime + 1) // 4, SECP256R1.prime) + # We need to decide whether to take y or prime - y. if ids.v % 2 == y % 2: value = y @@ -80,6 +101,14 @@ slope = pack(ids.slope, SECP256R1_P) x = pack(ids.point.x, SECP256R1_P) y = pack(ids.point.y, SECP256R1_P) +value = new_x = (pow(slope, 2, SECP256R1_P) - 2 * x) % SECP256R1_P"#}), +(SECP_DOUBLE_ASSIGN_NEW_X_V2, indoc! {r#"from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P +from starkware.cairo.common.cairo_secp.secp_utils import pack + +slope = pack(ids.slope, PRIME) +x = pack(ids.point.x, PRIME) +y = pack(ids.point.y, PRIME) + value = new_x = (pow(slope, 2, SECP256R1_P) - 2 * x) % SECP256R1_P"#}), (GENERATE_NIBBLES, indoc! {r#"num = (ids.scalar.high << 128) + ids.scalar.low nibbles = [(num >> i) & 0xf for i in range(0, 256, 4)] @@ -172,6 +201,7 @@ pub fn r1_get_point_from_x( ids_data: &HashMap, ap_tracking: &ApTracking, _constants: &HashMap, + pack_prime: &BigUint, ) -> Result<(), HintError> { exec_scopes.insert_value::("SECP256R1_P", SECP256R1_P.clone()); @@ -204,9 +234,8 @@ pub fn r1_get_point_from_x( // if (y & 1) != request.y_parity: // y = (-y) % prime - let x = Uint384::from_var_name("x", vm, ids_data, ap_tracking)? - .pack86() - .mod_floor(&SECP256R1_P); + let x = Uint384::from_var_name("x", vm, ids_data, ap_tracking)?; + let x = x.pack86_for_prime(pack_prime); let y_square_int = y_squared_from_x(&x, &SECP256R1_ALPHA, &SECP256R1_B, &SECP256R1_P); exec_scopes.insert_value::("y_square_int", y_square_int.clone()); @@ -255,6 +284,7 @@ pub fn secp_double_assign_new_x( ids_data: &HashMap, ap_tracking: &ApTracking, _constants: &HashMap, + pack_prime: &BigUint, ) -> Result<(), HintError> { exec_scopes.insert_value::("SECP256R1_P", SECP256R1_P.clone()); //ids.slope @@ -262,9 +292,9 @@ pub fn secp_double_assign_new_x( //ids.point let point = EcPoint::from_var_name("point", vm, ids_data, ap_tracking)?; - let slope = slope.pack86().mod_floor(&SECP256R1_P); - let x = point.x.pack86().mod_floor(&SECP256R1_P); - let y = point.y.pack86().mod_floor(&SECP256R1_P); + let slope = slope.pack86_for_prime(pack_prime); + let x = point.x.pack86_for_prime(pack_prime); + let y = point.y.pack86_for_prime(pack_prime); let value = (slope.modpow(&(2usize.into()), &SECP256R1_P) - (&x << 1u32)).mod_floor(&SECP256R1_P); @@ -531,6 +561,7 @@ mod tests { &ids_data, &ap_tracking, &constants, + SECP256R1_P.magnitude(), ) .expect("calculate_value() failed"); diff --git a/vm/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs b/vm/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs index 4eb99ac6d4..548d6bd8f3 100644 --- a/vm/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs +++ b/vm/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs @@ -119,12 +119,14 @@ Implements hint: value = slope = ec_double_slope(point=(x, y), alpha=0, p=SECP_P) %} */ +#[allow(clippy::too_many_arguments)] pub fn compute_doubling_slope( vm: &mut VirtualMachine, exec_scopes: &mut ExecutionScopes, ids_data: &HashMap, ap_tracking: &ApTracking, point_alias: &str, + pack_prime: &BigUint, secp_p: &BigInt, alpha: &BigInt, ) -> Result<(), HintError> { @@ -132,7 +134,14 @@ pub fn compute_doubling_slope( //ids.point let point = EcPoint::from_var_name(point_alias, vm, ids_data, ap_tracking)?; - let value = ec_double_slope(&(point.x.pack86(), point.y.pack86()), alpha, secp_p)?; + let value = ec_double_slope( + &( + point.x.pack86_for_prime(pack_prime), + point.y.pack86_for_prime(pack_prime), + ), + alpha, + secp_p, + )?; exec_scopes.insert_value("value", value.clone()); exec_scopes.insert_value("slope", value); Ok(()) diff --git a/vm/src/math_utils/mod.rs b/vm/src/math_utils/mod.rs index 75ad9888e9..fd1f2759e8 100644 --- a/vm/src/math_utils/mod.rs +++ b/vm/src/math_utils/mod.rs @@ -70,6 +70,16 @@ pub fn signed_felt(felt: Felt252) -> BigInt { } } +pub fn signed_felt_for_prime(value: Felt252, prime: &BigUint) -> BigInt { + let value = value.to_biguint(); + let half_prime = prime / 2u32; + if value > half_prime { + BigInt::from_biguint(num_bigint::Sign::Minus, prime - &value) + } else { + BigInt::from_biguint(num_bigint::Sign::Plus, value) + } +} + /// QM31 utility function, used specifically for Stwo. /// QM31 operations are to be relocated into https://github.com/lambdaclass/lambdaworks. /// Reads four u64 coordinates from a single Felt252. diff --git a/vm/src/tests/cairo_pie_test.rs b/vm/src/tests/cairo_pie_test.rs index e6de72ea28..bccda98d83 100644 --- a/vm/src/tests/cairo_pie_test.rs +++ b/vm/src/tests/cairo_pie_test.rs @@ -9,7 +9,7 @@ use wasm_bindgen_test::*; use crate::{ cairo_run::{cairo_run, CairoRunConfig}, hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, - stdlib::collections::HashMap, + stdlib::collections::{BTreeMap, HashMap}, types::relocatable::Relocatable, vm::runners::{ cairo_pie::{ @@ -45,7 +45,7 @@ fn pedersen_test() { // ret_pc_segment assert_eq!(pie_metadata.ret_pc_segment, SegmentInfo::from((6, 0))); // builtin_segments - let expected_builtin_segments = HashMap::from([ + let expected_builtin_segments = BTreeMap::from([ (BuiltinName::output, SegmentInfo::from((2, 1))), (BuiltinName::pedersen, SegmentInfo::from((3, 3))), (BuiltinName::range_check, SegmentInfo::from((4, 0))), @@ -71,7 +71,7 @@ fn pedersen_test() { let expected_execution_resources = ExecutionResources { n_steps: 14, n_memory_holes: 0, - builtin_instance_counter: HashMap::from([ + builtin_instance_counter: BTreeMap::from([ (BuiltinName::range_check, 0), (BuiltinName::output, 1), (BuiltinName::pedersen, 1), @@ -79,7 +79,7 @@ fn pedersen_test() { }; assert_eq!(cairo_pie.execution_resources, expected_execution_resources); // additional_data - let expected_additional_data = HashMap::from([ + let expected_additional_data = BTreeMap::from([ ( BuiltinName::output, BuiltinAdditionalData::Output(OutputBuiltinAdditionalData { @@ -128,7 +128,7 @@ fn common_signature() { assert_eq!(pie_metadata.ret_pc_segment, SegmentInfo::from((4, 0))); // builtin_segments let expected_builtin_segments = - HashMap::from([(BuiltinName::ecdsa, SegmentInfo::from((2, 2)))]); + BTreeMap::from([(BuiltinName::ecdsa, SegmentInfo::from((2, 2)))]); assert_eq!(pie_metadata.builtin_segments, expected_builtin_segments); // program_segment assert_eq!(pie_metadata.program_segment, SegmentInfo::from((0, 21))); @@ -150,11 +150,11 @@ fn common_signature() { let expected_execution_resources = ExecutionResources { n_steps: 11, n_memory_holes: 0, - builtin_instance_counter: HashMap::from([(BuiltinName::ecdsa, 1)]), + builtin_instance_counter: BTreeMap::from([(BuiltinName::ecdsa, 1)]), }; assert_eq!(cairo_pie.execution_resources, expected_execution_resources); // additional_data - let expected_additional_data = HashMap::from([( + let expected_additional_data = BTreeMap::from([( BuiltinName::ecdsa, BuiltinAdditionalData::Signature(HashMap::from([( Relocatable::from((2, 0)), @@ -223,7 +223,7 @@ fn relocate_segments() { let expected_execution_resources = ExecutionResources { n_steps: 22, n_memory_holes: 0, - builtin_instance_counter: HashMap::default(), + builtin_instance_counter: BTreeMap::default(), }; assert_eq!(cairo_pie.execution_resources, expected_execution_resources); // additional_data diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index 4e08fd25cb..b33a08535f 100644 --- a/vm/src/tests/cairo_run_test.rs +++ b/vm/src/tests/cairo_run_test.rs @@ -1241,6 +1241,7 @@ fn run_program_with_custom_mod_builtin_params( cairo_run_config.disable_trace_padding, false, &mut hint_processor, + cairo_run_config.proof_mode, ) .unwrap(); @@ -1344,6 +1345,15 @@ fn cairo_run_secp_cairo0_ec_mul_by_uint256() { run_program_simple(program_data.as_slice()); } +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +#[cfg(feature = "cairo-0-secp-hints")] +fn cairo_run_secp_cairo0_negative_points() { + let program_data = + include_bytes!("../../../cairo_programs/cairo-0-secp-hints-feature/negative_points.json"); + run_program_simple(program_data.as_slice()); +} + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] #[cfg(feature = "cairo-0-data-availability-hints")] diff --git a/vm/src/tests/compare_factorial_outputs_all_layouts.sh b/vm/src/tests/compare_factorial_outputs_all_layouts.sh index e634fbb9d4..c6c784069a 100755 --- a/vm/src/tests/compare_factorial_outputs_all_layouts.sh +++ b/vm/src/tests/compare_factorial_outputs_all_layouts.sh @@ -21,15 +21,6 @@ for layout in "plain" "small" "dex" "recursive" "starknet" "starknet_with_keccak else passed_tests=$((passed_tests + 1)) fi - # Compare memory - echo "Running memory comparison for layout $layout" - if ! ./vm/src/tests/memory_comparator.py factorial_rs.memory factorial_py.memory; then - echo "Memory differs for layout $layout" - exit_code=1 - failed_tests=$((failed_tests + 1)) - else - passed_tests=$((passed_tests + 1)) - fi # Compare air public input echo "Running air public input comparison for layout $layout" if ! ./vm/src/tests/air_public_input_comparator.py factorial_rs.air_public_input factorial_py.air_public_input; then diff --git a/vm/src/tests/compare_outputs_dynamic_layouts.sh b/vm/src/tests/compare_outputs_dynamic_layouts.sh index 41c7bf0f17..8346d4ba39 100755 --- a/vm/src/tests/compare_outputs_dynamic_layouts.sh +++ b/vm/src/tests/compare_outputs_dynamic_layouts.sh @@ -207,15 +207,6 @@ for case in "${CASES[@]}"; do passed_tests=$((passed_tests + 1)) fi - # Compare memory - echo "Running memory comparison for case: $case" - if ! ./vm/src/tests/memory_comparator.py program_rs.memory program_py.memory; then - echo "Memory differs for case: $case" - exit_code=1 - failed_tests=$((failed_tests + 1)) - else - passed_tests=$((passed_tests + 1)) - fi # Compare air public input echo "Running air public input comparison for case: $case" diff --git a/vm/src/tests/compare_vm_state.sh b/vm/src/tests/compare_vm_state.sh index 34c7486663..bac9aa8cb6 100755 --- a/vm/src/tests/compare_vm_state.sh +++ b/vm/src/tests/compare_vm_state.sh @@ -65,7 +65,7 @@ for file in $(ls $tests_path | grep .cairo$ | sed -E 's/\.cairo$//'); do fi fi - if $memory; then + if $memory && -z $proof_tests_path; then if ! ./memory_comparator.py $path_file.memory $path_file.rs.memory; then echo "Memory differs for $file" exit_code=1 diff --git a/vm/src/types/builtin_name.rs b/vm/src/types/builtin_name.rs index 5490f439a1..4f11488c5b 100644 --- a/vm/src/types/builtin_name.rs +++ b/vm/src/types/builtin_name.rs @@ -32,7 +32,7 @@ const MUL_MOD_BUILTIN_NAME_WITH_SUFFIX: &str = "mul_mod_builtin"; /// Enum representing the name of a cairo builtin #[cfg_attr(feature = "test_utils", derive(Arbitrary))] -#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Eq, Hash)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Eq, Hash, Ord, PartialOrd)] #[allow(non_camel_case_types)] pub enum BuiltinName { output, @@ -179,11 +179,11 @@ impl core::fmt::Display for BuiltinName { // Implementation of custom serialization & deserialization for maps using builtin names with suffixes as keys pub(crate) mod serde_generic_map_impl { use super::BuiltinName; - use crate::stdlib::{collections::HashMap, string::String}; + use crate::stdlib::{collections::BTreeMap, string::String}; use serde::{de::Error, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; pub fn serialize( - values: &HashMap, + values: &BTreeMap, serializer: S, ) -> Result where @@ -199,13 +199,13 @@ pub(crate) mod serde_generic_map_impl { pub fn deserialize<'de, D: Deserializer<'de>, V: Deserialize<'de>>( d: D, - ) -> Result, D::Error> { + ) -> Result, D::Error> { // First deserialize keys into String - let map = HashMap::::deserialize(d)?; + let map = BTreeMap::::deserialize(d)?; // Then match keys to BuiltinName and handle invalid names map.into_iter() .map(|(k, v)| BuiltinName::from_str_with_suffix(&k).map(|k| (k, v))) - .collect::>>() + .collect::>>() .ok_or(D::Error::custom("Invalid builtin name")) } } diff --git a/vm/src/types/instance_definitions/builtins_instance_def.rs b/vm/src/types/instance_definitions/builtins_instance_def.rs index d98b3b3a3f..ab04a3272d 100644 --- a/vm/src/types/instance_definitions/builtins_instance_def.rs +++ b/vm/src/types/instance_definitions/builtins_instance_def.rs @@ -282,6 +282,38 @@ impl BuiltinsInstanceDef { mul_mod, } } + + pub(crate) fn perpetual() -> BuiltinsInstanceDef { + BuiltinsInstanceDef { + output: true, + pedersen: Some(PedersenInstanceDef::new(Some(32))), + range_check: Some(RangeCheckInstanceDef::new(Some(16))), + ecdsa: Some(EcdsaInstanceDef::new(Some(2048))), + bitwise: None, + ec_op: None, + keccak: None, + poseidon: None, + range_check96: None, + add_mod: None, + mul_mod: None, + } + } + + pub(crate) fn dex_with_bitwise() -> BuiltinsInstanceDef { + BuiltinsInstanceDef { + output: true, + pedersen: Some(PedersenInstanceDef::default()), + range_check: Some(RangeCheckInstanceDef::default()), + ecdsa: Some(EcdsaInstanceDef::default()), + bitwise: Some(BitwiseInstanceDef::new(Some(64))), + ec_op: None, + keccak: None, + poseidon: None, + range_check96: None, + add_mod: None, + mul_mod: None, + } + } } #[cfg(test)] diff --git a/vm/src/types/layout.rs b/vm/src/types/layout.rs index 1c7fe138a8..30d920ef36 100644 --- a/vm/src/types/layout.rs +++ b/vm/src/types/layout.rs @@ -169,6 +169,30 @@ impl CairoLayout { builtins: BuiltinsInstanceDef::dynamic(params), } } + + pub(crate) fn perpetual_instance() -> CairoLayout { + CairoLayout { + name: LayoutName::perpetual, + rc_units: 4, + cpu_component_step: DEFAULT_CPU_COMPONENT_STEP, + memory_units_per_step: DEFAULT_MEMORY_UNITS_PER_STEP, + builtins: BuiltinsInstanceDef::perpetual(), + public_memory_fraction: 4, + diluted_pool_instance_def: None, + } + } + + pub(crate) fn dex_with_bitwise_instance() -> CairoLayout { + CairoLayout { + name: LayoutName::dex_with_bitwise, + rc_units: 4, + cpu_component_step: DEFAULT_CPU_COMPONENT_STEP, + memory_units_per_step: DEFAULT_MEMORY_UNITS_PER_STEP, + builtins: BuiltinsInstanceDef::dex_with_bitwise(), + public_memory_fraction: 4, + diluted_pool_instance_def: Some(DilutedPoolInstanceDef::new(2, 4, 16)), + } + } } #[cfg(feature = "test_utils")] @@ -496,6 +520,31 @@ mod tests { ); } + #[test] + fn get_perpetual_instance() { + let layout = CairoLayout::perpetual_instance(); + let builtins = BuiltinsInstanceDef::perpetual(); + assert_eq!(layout.name, LayoutName::perpetual); + assert_eq!(layout.rc_units, 4); + assert_eq!(layout.builtins, builtins); + assert_eq!(layout.public_memory_fraction, 4); + assert_eq!(layout.diluted_pool_instance_def, None); + } + + #[test] + fn get_dex_with_bitwise_instance() { + let layout = CairoLayout::dex_with_bitwise_instance(); + let builtins = BuiltinsInstanceDef::dex_with_bitwise(); + assert_eq!(layout.name, LayoutName::dex_with_bitwise); + assert_eq!(layout.rc_units, 4); + assert_eq!(layout.builtins, builtins); + assert_eq!(layout.public_memory_fraction, 4); + assert_eq!( + layout.diluted_pool_instance_def, + Some(DilutedPoolInstanceDef::new(2, 4, 16)) + ); + } + #[test] fn get_dynamic_instance() { // dummy cairo layout params diff --git a/vm/src/types/layout_name.rs b/vm/src/types/layout_name.rs index 0d3e13c021..24addc4507 100644 --- a/vm/src/types/layout_name.rs +++ b/vm/src/types/layout_name.rs @@ -22,6 +22,8 @@ pub enum LayoutName { all_cairo, dynamic, all_cairo_stwo, + perpetual, + dex_with_bitwise, } impl LayoutName { @@ -39,6 +41,8 @@ impl LayoutName { LayoutName::all_cairo => "all_cairo", LayoutName::dynamic => "dynamic", LayoutName::all_cairo_stwo => "all_cairo_stwo", + LayoutName::perpetual => "perpetual", + LayoutName::dex_with_bitwise => "dex_with_bitwise", } } } @@ -65,6 +69,8 @@ impl ValueEnum for LayoutName { Self::all_cairo, Self::dynamic, Self::all_cairo_stwo, + Self::perpetual, + Self::dex_with_bitwise, ] } diff --git a/vm/src/vm/runners/builtin_runner/mod.rs b/vm/src/vm/runners/builtin_runner/mod.rs index a8cb0a6d06..77e6de07da 100644 --- a/vm/src/vm/runners/builtin_runner/mod.rs +++ b/vm/src/vm/runners/builtin_runner/mod.rs @@ -401,7 +401,7 @@ impl BuiltinRunner { } } - fn cells_per_instance(&self) -> u32 { + pub fn cells_per_instance(&self) -> u32 { match self { BuiltinRunner::Bitwise(_) => CELLS_PER_BITWISE, BuiltinRunner::EcOp(_) => CELLS_PER_EC_OP, diff --git a/vm/src/vm/runners/builtin_runner/modulo.rs b/vm/src/vm/runners/builtin_runner/modulo.rs index 85a8fe8070..6d0d711945 100644 --- a/vm/src/vm/runners/builtin_runner/modulo.rs +++ b/vm/src/vm/runners/builtin_runner/modulo.rs @@ -821,15 +821,25 @@ mod tests { let mut hint_processor = BuiltinHintProcessor::new_empty(); let program = Program::from_bytes(program_data, Some("main")).unwrap(); - let mut runner = - CairoRunner::new(&program, LayoutName::all_cairo, None, true, false, false).unwrap(); + let proof_mode = true; + let mut runner = CairoRunner::new( + &program, + LayoutName::all_cairo, + None, + proof_mode, + false, + false, + ) + .unwrap(); let end = runner.initialize(false).unwrap(); // Modify add_mod & mul_mod params runner.run_until_pc(end, &mut hint_processor).unwrap(); runner.run_for_steps(1, &mut hint_processor).unwrap(); - runner.end_run(false, false, &mut hint_processor).unwrap(); + runner + .end_run(false, false, &mut hint_processor, proof_mode) + .unwrap(); runner.read_return_values(false).unwrap(); runner.finalize_segments().unwrap(); diff --git a/vm/src/vm/runners/builtin_runner/output.rs b/vm/src/vm/runners/builtin_runner/output.rs index ae54908565..df44c07a8d 100644 --- a/vm/src/vm/runners/builtin_runner/output.rs +++ b/vm/src/vm/runners/builtin_runner/output.rs @@ -12,6 +12,7 @@ use crate::vm::vm_memory::memory_segments::MemorySegmentManager; #[derive(Debug, Clone, PartialEq)] pub struct OutputBuiltinState { pub base: usize, + pub base_offset: usize, pub pages: Pages, pub attributes: Attributes, } @@ -19,6 +20,7 @@ pub struct OutputBuiltinState { #[derive(Debug, Clone)] pub struct OutputBuiltinRunner { base: usize, + pub base_offset: usize, pub(crate) pages: Pages, pub(crate) attributes: Attributes, pub(crate) stop_ptr: Option, @@ -29,6 +31,7 @@ impl OutputBuiltinRunner { pub fn new(included: bool) -> OutputBuiltinRunner { OutputBuiltinRunner { base: 0, + base_offset: 0, pages: HashMap::default(), attributes: HashMap::default(), stop_ptr: None, @@ -36,8 +39,9 @@ impl OutputBuiltinRunner { } } - pub fn new_state(&mut self, base: usize, included: bool) { + pub fn new_state(&mut self, base: usize, base_offset: usize, included: bool) { self.base = base; + self.base_offset = base_offset; self.pages = HashMap::default(); self.attributes = HashMap::default(); self.stop_ptr = None; @@ -143,6 +147,7 @@ impl OutputBuiltinRunner { pub fn set_state(&mut self, new_state: OutputBuiltinState) { self.base = new_state.base; + self.base_offset = new_state.base_offset; self.pages = new_state.pages; self.attributes = new_state.attributes; } @@ -150,6 +155,7 @@ impl OutputBuiltinRunner { pub fn get_state(&mut self) -> OutputBuiltinState { OutputBuiltinState { base: self.base, + base_offset: self.base_offset, pages: self.pages.clone(), attributes: self.attributes.clone(), } @@ -168,7 +174,7 @@ impl OutputBuiltinRunner { self.pages.insert( page_id, PublicMemoryPage { - start: page_start.offset, + start: page_start.offset - self.base_offset, size: page_size, }, ); @@ -493,6 +499,7 @@ mod tests { let new_state = OutputBuiltinState { base: 10, + base_offset: 0, pages: HashMap::from([(1, PublicMemoryPage { start: 0, size: 3 })]), attributes: HashMap::from([("gps_fact_topology".to_string(), vec![0, 2, 0])]), }; @@ -510,6 +517,7 @@ mod tests { fn new_state() { let mut builtin = OutputBuiltinRunner { base: 10, + base_offset: 0, pages: HashMap::from([(1, PublicMemoryPage { start: 0, size: 3 })]), attributes: HashMap::from([("gps_fact_topology".to_string(), vec![0, 2, 0])]), stop_ptr: Some(10), @@ -518,9 +526,10 @@ mod tests { let new_base = 11; let new_included = false; - builtin.new_state(new_base, new_included); + builtin.new_state(new_base, 2, new_included); assert_eq!(builtin.base, new_base); + assert_eq!(builtin.base_offset, 2); assert!(builtin.pages.is_empty()); assert!(builtin.attributes.is_empty()); assert_eq!(builtin.stop_ptr, None); @@ -614,6 +623,7 @@ mod tests { fn get_and_extend_additional_data() { let builtin_a = OutputBuiltinRunner { base: 0, + base_offset: 0, pages: HashMap::from([(1, PublicMemoryPage { start: 0, size: 3 })]), attributes: HashMap::from([("gps_fact_topology".to_string(), vec![0, 2, 0])]), stop_ptr: None, @@ -622,6 +632,7 @@ mod tests { let additional_data = builtin_a.get_additional_data(); let mut builtin_b = OutputBuiltinRunner { base: 0, + base_offset: 0, pages: Default::default(), attributes: Default::default(), stop_ptr: None, diff --git a/vm/src/vm/runners/cairo_pie.rs b/vm/src/vm/runners/cairo_pie.rs index b839be06f3..e83854fa1b 100644 --- a/vm/src/vm/runners/cairo_pie.rs +++ b/vm/src/vm/runners/cairo_pie.rs @@ -3,7 +3,10 @@ use crate::stdlib::prelude::{String, Vec}; use crate::types::builtin_name::BuiltinName; use crate::vm::errors::cairo_pie_errors::CairoPieValidationError; use crate::{ - stdlib::{collections::HashMap, prelude::*}, + stdlib::{ + collections::{BTreeMap, HashMap}, + prelude::*, + }, types::relocatable::{MaybeRelocatable, Relocatable}, Felt252, }; @@ -125,7 +128,7 @@ impl PartialEq for BuiltinAdditionalData { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct CairoPieAdditionalData( #[serde(with = "crate::types::builtin_name::serde_generic_map_impl")] - pub HashMap, + pub BTreeMap, ); #[derive(Serialize, Clone, Debug, PartialEq, Eq)] @@ -145,7 +148,7 @@ pub struct CairoPieMetadata { pub ret_fp_segment: SegmentInfo, pub ret_pc_segment: SegmentInfo, #[serde(serialize_with = "serde_impl::serialize_builtin_segments")] - pub builtin_segments: HashMap, + pub builtin_segments: BTreeMap, pub extra_segments: Vec, } @@ -321,8 +324,9 @@ impl CairoPie { let file = File::create(file_path)?; let mut zip_writer = ZipWriter::new(file); - let options = - zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Deflated); + let options = zip::write::FileOptions::default() + .compression_method(zip::CompressionMethod::Deflated) + .large_file(true); zip_writer.start_file("version.json", options)?; serde_json::to_writer(&mut zip_writer, &self.version)?; @@ -435,7 +439,7 @@ impl CairoPie { } pub(super) mod serde_impl { - use crate::stdlib::collections::HashMap; + use crate::stdlib::collections::{BTreeMap, HashMap}; use crate::types::builtin_name::BuiltinName; use num_traits::Num; @@ -840,7 +844,7 @@ pub(super) mod serde_impl { } pub fn serialize_builtin_segments( - values: &HashMap, + values: &BTreeMap, serializer: S, ) -> Result where diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 1c5fabd466..2a467400fc 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -1,3 +1,4 @@ +use crate::vm::trace::trace_entry::TraceEntry; use crate::{ air_private_input::AirPrivateInput, air_public_input::{PublicInput, PublicInputError}, @@ -11,7 +12,7 @@ use crate::{ types::{builtin_name::BuiltinName, layout::CairoLayoutParams, layout_name::LayoutName}, vm::{ runners::builtin_runner::SegmentArenaBuiltinRunner, - trace::trace_entry::{relocate_trace_register, RelocatedTraceEntry, TraceEntry}, + trace::trace_entry::{relocate_trace_register, RelocatedTraceEntry}, }, Felt252, }; @@ -190,6 +191,8 @@ impl CairoRunner { LayoutName::all_cairo => CairoLayout::all_cairo_instance(), LayoutName::all_cairo_stwo => CairoLayout::all_cairo_stwo_instance(), LayoutName::all_solidity => CairoLayout::all_solidity_instance(), + LayoutName::perpetual => CairoLayout::perpetual_instance(), + LayoutName::dex_with_bitwise => CairoLayout::dex_with_bitwise_instance(), LayoutName::dynamic => { let params = dynamic_layout_params.ok_or(RunnerError::MissingDynamicLayoutParams)?; @@ -888,13 +891,14 @@ impl CairoRunner { disable_trace_padding: bool, disable_finalize_all: bool, hint_processor: &mut dyn HintProcessor, + proof_mode: bool, ) -> Result<(), VirtualMachineError> { if self.run_ended { return Err(RunnerError::EndRunCalledTwice.into()); } self.vm.segments.memory.relocate_memory()?; - self.vm.end_run(&self.exec_scopes)?; + self.vm.end_run(&self.exec_scopes, proof_mode)?; if disable_finalize_all { return Ok(()); @@ -980,9 +984,9 @@ impl CairoRunner { Ok(()) } - pub fn relocate(&mut self, relocate_mem: bool) -> Result<(), TraceError> { + pub fn relocate(&mut self, relocate_mem: bool, relocate_trace: bool) -> Result<(), TraceError> { self.vm.segments.compute_effective_sizes(); - if !relocate_mem && self.vm.trace.is_none() { + if !relocate_mem && (self.vm.trace.is_none() || !relocate_trace) { return Ok(()); } // relocate_segments can fail if compute_effective_sizes is not called before. @@ -998,25 +1002,39 @@ impl CairoRunner { return Err(TraceError::MemoryError(memory_error)); } } - if self.vm.trace.is_some() { + if self.vm.trace.is_some() && relocate_trace { self.relocate_trace(&relocation_table)?; } self.vm.relocation_table = Some(relocation_table); Ok(()) } - // Returns a map from builtin base's segment index to stop_ptr offset - // Aka the builtin's segment number and its maximum offset + /// Returns a tuple of builtin base's segment index and stop_ptr offset + /// Aka the builtin's segment number and its maximum offset pub fn get_builtin_segments_info(&self) -> Result, RunnerError> { + let proof_mode = self.is_proof_mode(); let mut builtin_segment_info = Vec::new(); for builtin in &self.vm.builtin_runners { let (index, stop_ptr) = builtin.get_memory_segment_addresses(); - builtin_segment_info.push(( - index, - stop_ptr.ok_or_else(|| RunnerError::NoStopPointer(Box::new(builtin.name())))?, - )); + match (proof_mode, stop_ptr) { + // Segment present (same handling in both modes). + (_, Some(sp)) => builtin_segment_info.push((index, sp)), + + // If non proof-mode, only builtins in the program are present and they must + // point to a segment (so `stop_ptr` must be set). Throw an error if not. + (false, None) => { + return Err(RunnerError::NoStopPointer(Box::new( + builtin.name().to_owned(), + ))); + } + + // In proof‐mode there are builtin runners for all builtins in the layout, but only + // the ones that are in the program point to a segment (so `stop_ptr` is set). + // Only collect those and silently ignore the rest. + (true, None) => {} + } } Ok(builtin_segment_info) @@ -1065,7 +1083,7 @@ impl CairoRunner { Ok(ExecutionResources { n_steps, n_memory_holes, - builtin_instance_counter, + builtin_instance_counter: builtin_instance_counter.into_iter().collect(), }) } @@ -1152,7 +1170,7 @@ impl CairoRunner { self.run_until_pc(end, hint_processor) .map_err(|err| VmException::from_vm_error(self, err))?; - self.end_run(true, false, hint_processor)?; + self.end_run(true, false, hint_processor, self.is_proof_mode())?; if verify_secure { verify_secure_runner(self, false, program_segment_size)?; @@ -1412,7 +1430,7 @@ impl CairoRunner { execution_segment: (execution_base.segment_index, execution_size).into(), ret_fp_segment: (return_fp.segment_index, 0).into(), ret_pc_segment: (return_pc.segment_index, 0).into(), - builtin_segments, + builtin_segments: builtin_segments.into_iter().collect(), extra_segments, }; @@ -1486,79 +1504,38 @@ impl CairoRunner { .collect() } - /// Collects relevant information for the prover from the runner, including the - /// relocatable form of the trace, memory, public memory, and built-ins. - pub fn get_prover_input_info(&self) -> Result { - let relocatable_trace = self - .vm + /// Returns a reference to the relocatable trace. + pub fn get_relocatable_trace(&self) -> Result<&[TraceEntry], RunnerError> { + self.vm .trace - .as_ref() - .ok_or(RunnerError::Trace(TraceError::TraceNotEnabled))? - .clone(); + .as_deref() + .ok_or(RunnerError::Trace(TraceError::TraceNotEnabled)) + } - let relocatable_memory = self - .vm + /// Returns a vector of segments, where each segment is a vector of Option values, representing the relocatble memory values. + pub fn get_relocatable_memory(&self) -> Vec>> { + self.vm .segments .memory .data .iter() .map(|segment| segment.iter().map(|cell| cell.get_value()).collect()) - .collect(); - - let public_memory_offsets = self - .vm - .segments - .public_memory_offsets - .iter() - .map(|(segment, offset_page)| { - let offsets: Vec = offset_page.iter().map(|(offset, _)| *offset).collect(); - (*segment, offsets) - }) - .collect(); + .collect() + } - let builtins_segments: BTreeMap = self - .vm + /// Returns a map from the builtin segment index into its name. + pub fn get_builtin_segments(&self) -> BTreeMap { + self.vm .builtin_runners .iter() - .filter(|builtin| { - // Those segments are not treated as builtins by the prover. - !matches!( - builtin, - BuiltinRunner::SegmentArena(_) | BuiltinRunner::Output(_) - ) - }) .map(|builtin| { let (index, _) = builtin.get_memory_segment_addresses(); (index, builtin.name()) }) - .collect(); - - Ok(ProverInputInfo { - relocatable_trace, - relocatable_memory, - public_memory_offsets, - builtins_segments, - }) + .collect() } } -//* ---------------------- -//* ProverInputInfo -//* ---------------------- -/// This struct contains all relevant data for the prover. -/// All addresses are relocatable. -#[derive(Deserialize, Serialize)] -pub struct ProverInputInfo { - /// A vector of trace entries, i.e. pc, ap, fp, where pc is relocatable. - pub relocatable_trace: Vec, - /// A vector of segments, where each segment is a vector of maybe relocatable values or holes (`None`). - pub relocatable_memory: Vec>>, - /// A map from segment index to a vector of offsets within the segment, representing the public memory addresses. - pub public_memory_offsets: BTreeMap>, - /// A map from the builtin segment index into its name. - pub builtins_segments: BTreeMap, -} - #[derive(Clone, Debug, Eq, PartialEq)] pub struct SegmentInfo { pub index: isize, @@ -1574,7 +1551,7 @@ pub struct ExecutionResources { pub n_steps: usize, pub n_memory_holes: usize, #[serde(with = "crate::types::builtin_name::serde_generic_map_impl")] - pub builtin_instance_counter: HashMap, + pub builtin_instance_counter: BTreeMap, } /// Returns a copy of the execution resources where all the builtins with a usage counter @@ -1663,6 +1640,8 @@ mod tests { use crate::air_private_input::{PrivateInput, PrivateInputSignature, SignatureInput}; use crate::cairo_run::{cairo_run, CairoRunConfig}; use crate::stdlib::collections::{HashMap, HashSet}; + use crate::types::instance_definitions::bitwise_instance_def::CELLS_PER_BITWISE; + use crate::types::instance_definitions::keccak_instance_def::CELLS_PER_KECCAK; use crate::vm::vm_memory::memory::MemoryCell; use crate::felt_hex; @@ -3849,7 +3828,7 @@ mod tests { cairo_runner.run_ended = true; assert_matches!( - cairo_runner.end_run(true, false, &mut hint_processor), + cairo_runner.end_run(true, false, &mut hint_processor, false), Err(VirtualMachineError::RunnerError( RunnerError::EndRunCalledTwice )) @@ -3865,14 +3844,14 @@ mod tests { let mut cairo_runner = cairo_runner!(program); assert_matches!( - cairo_runner.end_run(true, false, &mut hint_processor), + cairo_runner.end_run(true, false, &mut hint_processor, false), Ok(()) ); cairo_runner.run_ended = false; cairo_runner.relocated_memory.clear(); assert_matches!( - cairo_runner.end_run(true, true, &mut hint_processor), + cairo_runner.end_run(true, true, &mut hint_processor, false), Ok(()) ); assert!(!cairo_runner.run_ended); @@ -3886,16 +3865,16 @@ mod tests { Some("main"), ) .unwrap(); - + let proof_mode = true; let mut hint_processor = BuiltinHintProcessor::new_empty(); - let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, true, true); + let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, proof_mode, true); let end = cairo_runner.initialize(false).unwrap(); cairo_runner .run_until_pc(end, &mut hint_processor) .expect("Call to `CairoRunner::run_until_pc()` failed."); assert_matches!( - cairo_runner.end_run(false, false, &mut hint_processor), + cairo_runner.end_run(false, false, &mut hint_processor, proof_mode), Ok(()) ); } @@ -3925,6 +3904,49 @@ mod tests { ); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn get_builtin_segments_info_non_proof_mode() { + let program_data = + include_bytes!("../../../../cairo_programs/proof_programs/assert_nn.json"); + let cairo_run_config = CairoRunConfig { + entrypoint: "main", + trace_enabled: false, + relocate_mem: false, + layout: LayoutName::small, + proof_mode: false, + secure_run: Some(true), + ..Default::default() + }; + let mut hint_executor = BuiltinHintProcessor::new_empty(); + let runner = cairo_run(program_data, &cairo_run_config, &mut hint_executor).unwrap(); + // Only the range_check builtin is used in this program, and in non-proof mode, it will + // be the first and only builtin segment initialized (and used). + assert_eq!(runner.get_builtin_segments_info(), Ok(vec![(2, 6)])); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn get_builtin_segments_info_proof_mode() { + let program_data = + include_bytes!("../../../../cairo_programs/proof_programs/assert_nn.json"); + let cairo_run_config = CairoRunConfig { + entrypoint: "main", + trace_enabled: false, + relocate_mem: false, + layout: LayoutName::small, + proof_mode: true, + secure_run: Some(true), + ..Default::default() + }; + let mut hint_executor = BuiltinHintProcessor::new_empty(); + let runner = cairo_run(program_data, &cairo_run_config, &mut hint_executor).unwrap(); + // Only the range_check builtin is used in this program, and in proof mode, it will + // be the first and only builtin segment used, but initialized after the other builtins + // in the layout. + assert_eq!(runner.get_builtin_segments_info(), Ok(vec![(4, 6)])); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_execution_resources_trace_not_enabled() { @@ -3939,7 +3961,7 @@ mod tests { Ok(ExecutionResources { n_steps: 10, n_memory_holes: 0, - builtin_instance_counter: HashMap::new(), + builtin_instance_counter: BTreeMap::new(), }), ); } @@ -3994,7 +4016,7 @@ mod tests { Ok(ExecutionResources { n_steps: 10, n_memory_holes: 0, - builtin_instance_counter: HashMap::new(), + builtin_instance_counter: BTreeMap::new(), }), ); } @@ -4019,7 +4041,7 @@ mod tests { Ok(ExecutionResources { n_steps: 10, n_memory_holes: 0, - builtin_instance_counter: HashMap::from([(BuiltinName::output, 4)]), + builtin_instance_counter: BTreeMap::from([(BuiltinName::output, 4)]), }), ); } @@ -4963,7 +4985,7 @@ mod tests { } fn setup_execution_resources() -> (ExecutionResources, ExecutionResources) { - let mut builtin_instance_counter: HashMap = HashMap::new(); + let mut builtin_instance_counter: BTreeMap = BTreeMap::new(); builtin_instance_counter.insert(BuiltinName::output, 8); let execution_resources_1 = ExecutionResources { @@ -5155,7 +5177,7 @@ mod tests { let execution_resources_1 = ExecutionResources { n_steps: 800, n_memory_holes: 0, - builtin_instance_counter: HashMap::from([ + builtin_instance_counter: BTreeMap::from([ (BuiltinName::pedersen, 7), (BuiltinName::range_check, 16), ]), @@ -5166,7 +5188,7 @@ mod tests { ExecutionResources { n_steps: 1600, n_memory_holes: 0, - builtin_instance_counter: HashMap::from([ + builtin_instance_counter: BTreeMap::from([ (BuiltinName::pedersen, 14), (BuiltinName::range_check, 32) ]) @@ -5176,7 +5198,7 @@ mod tests { let execution_resources_2 = ExecutionResources { n_steps: 545, n_memory_holes: 0, - builtin_instance_counter: HashMap::from([(BuiltinName::range_check, 17)]), + builtin_instance_counter: BTreeMap::from([(BuiltinName::range_check, 17)]), }; assert_eq!( @@ -5184,14 +5206,14 @@ mod tests { ExecutionResources { n_steps: 4360, n_memory_holes: 0, - builtin_instance_counter: HashMap::from([(BuiltinName::range_check, 136)]) + builtin_instance_counter: BTreeMap::from([(BuiltinName::range_check, 136)]) } ); let execution_resources_3 = ExecutionResources { n_steps: 42, n_memory_holes: 0, - builtin_instance_counter: HashMap::new(), + builtin_instance_counter: BTreeMap::new(), }; assert_eq!( @@ -5199,7 +5221,7 @@ mod tests { ExecutionResources { n_steps: 756, n_memory_holes: 0, - builtin_instance_counter: HashMap::new() + builtin_instance_counter: BTreeMap::new() } ); } @@ -5515,7 +5537,7 @@ mod tests { } #[test] - fn get_prover_input_info() { + fn test_get_relocatable_trace() { let program_content = include_bytes!("../../../../cairo_programs/proof_programs/common_signature.json"); let runner = crate::cairo_run::cairo_run( @@ -5528,7 +5550,7 @@ mod tests { &mut BuiltinHintProcessor::new_empty(), ) .unwrap(); - let prover_info = runner.get_prover_input_info().unwrap(); + let relocatable_trace = runner.get_relocatable_trace().unwrap(); let expected_trace = vec![ TraceEntry { pc: (0, 15).into(), @@ -5586,46 +5608,116 @@ mod tests { fp: 3, }, ]; + assert_eq!(relocatable_trace, expected_trace); + } + + #[test] + fn test_get_relocatable_memory() { + let program_content = + include_bytes!("../../../../cairo_programs/proof_programs/common_signature.json"); + let runner = crate::cairo_run::cairo_run( + program_content, + &CairoRunConfig { + layout: LayoutName::all_cairo, + ..Default::default() + }, + &mut BuiltinHintProcessor::new_empty(), + ) + .unwrap(); + let relocatable_memory = runner.get_relocatable_memory(); + let expected_in_memory_0_3 = MaybeRelocatable::Int(13.into()); let expected_in_memory_1_0 = MaybeRelocatable::RelocatableValue(Relocatable { segment_index: 2, offset: 0, }); - assert_eq!(prover_info.relocatable_trace, expected_trace); - assert_eq!( - prover_info.relocatable_memory[0][3], - Some(expected_in_memory_0_3) - ); - assert_eq!( - prover_info.relocatable_memory[1][0], - Some(expected_in_memory_1_0) - ); - assert!(prover_info.public_memory_offsets.is_empty()); - assert_eq!( - prover_info.builtins_segments, - BTreeMap::from([(2, BuiltinName::ecdsa)]) - ); + + assert_eq!(relocatable_memory[0][3], Some(expected_in_memory_0_3)); + assert_eq!(relocatable_memory[1][0], Some(expected_in_memory_1_0)); } #[test] - fn test_output_not_builtin_segment() { + fn test_get_builtin_segments() { let program_content = - include_bytes!("../../../../cairo_programs/proof_programs/split_felt.json"); + include_bytes!("../../../../cairo_programs/proof_programs/bitwise_builtin_test.json"); let runner = crate::cairo_run::cairo_run( program_content, &CairoRunConfig { - trace_enabled: true, layout: LayoutName::all_cairo, ..Default::default() }, &mut BuiltinHintProcessor::new_empty(), ) .unwrap(); - let prover_info = runner.get_prover_input_info().unwrap(); + let builtin_segments = runner.get_builtin_segments(); + + assert_eq!(builtin_segments[&2], BuiltinName::bitwise); + } + + #[test] + fn end_run_fill_builtins() { + let program = Program::from_bytes( + include_bytes!("../../../../cairo_programs/proof_programs/keccak_uint256.json"), + Some("main"), + ) + .unwrap(); + + let mut hint_processor = BuiltinHintProcessor::new_empty(); + let proof_mode = true; + let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, proof_mode, true); + + let end = cairo_runner.initialize(false).unwrap(); + cairo_runner + .run_until_pc(end, &mut hint_processor) + .expect("Call to `CairoRunner::run_until_pc()` failed."); + + // Before end run + assert!(cairo_runner.vm.segments.memory.data[6].len() as u32 % CELLS_PER_BITWISE != 0); + assert!(cairo_runner.vm.segments.memory.data[8].len() as u32 % CELLS_PER_KECCAK != 0); + + assert_matches!( + cairo_runner.end_run(false, false, &mut hint_processor, proof_mode), + Ok(()) + ); + + // After end run + assert!(cairo_runner.vm.segments.memory.data[6].len() as u32 % CELLS_PER_BITWISE == 0); + assert!(cairo_runner.vm.segments.memory.data[8].len() as u32 % CELLS_PER_KECCAK == 0); + assert!(cairo_runner.vm.segments.memory.data[6].last().is_some()); + assert!(cairo_runner.vm.segments.memory.data[8].last().is_some()); + + let builtin_segments = cairo_runner.get_builtin_segments(); + assert!(builtin_segments.get(&6) == Some(&BuiltinName::bitwise)); + assert!(builtin_segments.get(&8) == Some(&BuiltinName::keccak)); + } + + #[test] + fn end_run_fill_middle_holes() { + let program = Program::from_bytes( + include_bytes!("../../../../cairo_programs/proof_programs/poseidon_builtin_hole.json"), + Some("main"), + ) + .unwrap(); + + let mut hint_processor = BuiltinHintProcessor::new_empty(); + let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, true, true); + + let end = cairo_runner.initialize(false).unwrap(); + cairo_runner + .run_until_pc(end, &mut hint_processor) + .expect("Call to `CairoRunner::run_until_pc()` failed."); + + // Before end run + assert!(cairo_runner.vm.segments.memory.data[9][4].is_none()); + assert_matches!( + cairo_runner.end_run(false, false, &mut hint_processor, true), + Ok(()) + ); + + // After end run + assert!(!cairo_runner.vm.segments.memory.data[9][4].is_none()); - assert!(!prover_info - .builtins_segments - .values() - .any(|v| *v == BuiltinName::output)); + let builtin_segments = cairo_runner.get_builtin_segments(); + assert!(builtin_segments.get(&9) == Some(&BuiltinName::poseidon)); } } diff --git a/vm/src/vm/security.rs b/vm/src/vm/security.rs index 0dfdda8f23..08abc03198 100644 --- a/vm/src/vm/security.rs +++ b/vm/src/vm/security.rs @@ -150,7 +150,9 @@ mod test { runner.initialize(false).unwrap(); // runner.vm.segments.compute_effective_sizes(); let mut hint_processor = BuiltinHintProcessor::new_empty(); - runner.end_run(false, false, &mut hint_processor).unwrap(); + runner + .end_run(false, false, &mut hint_processor, false) + .unwrap(); // At the end of the run, the ret_fp should be the base of the new ret_fp segment we added // to the stack at the start of the run. runner.vm.run_context.fp = 0; @@ -215,7 +217,9 @@ mod test { runner.initialize(false).unwrap(); let mut hint_processor = BuiltinHintProcessor::new_empty(); - runner.end_run(false, false, &mut hint_processor).unwrap(); + runner + .end_run(false, false, &mut hint_processor, false) + .unwrap(); runner.vm.builtin_runners[0].set_stop_ptr(1); // Adding ((1, 1), (3, 0)) to the memory segment to simulate the ret_fp_segment. runner.vm.segments.memory = memory![((2, 0), 1), ((1, 1), (3, 0))]; diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index be89d2821d..502c0bb8cd 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -3,6 +3,7 @@ use crate::stdlib::{any::Any, borrow::Cow, collections::HashMap, prelude::*}; use crate::types::builtin_name::BuiltinName; #[cfg(feature = "extensive_hints")] use crate::types::program::HintRange; +use crate::vm::vm_memory::memory::MemoryCell; use crate::{ hint_processor::{ builtin_hint_processor::blake2s_hash::blake2s_compress, @@ -748,6 +749,66 @@ impl VirtualMachine { )) } + /// Updates the memory with missing built-in deductions and verifies the existing ones. + pub fn complete_builtin_auto_deductions(&mut self) -> Result<(), VirtualMachineError> { + for builtin in self.builtin_runners.iter() { + let builtin_index: usize = builtin.base(); + + // Output and SegmentArena do not need to be auto-deduced in the memory. + if matches!( + builtin, + BuiltinRunner::Output(_) | BuiltinRunner::SegmentArena(_) + ) { + continue; + } + + // Extend the segment size to a multiple of the number of cells per instance. + let current_builtin_segment = &mut self.segments.memory.data[builtin_index]; + let len = current_builtin_segment.len(); + let cells_per_instance = builtin.cells_per_instance() as usize; + current_builtin_segment.resize( + len.div_ceil(cells_per_instance) * cells_per_instance, + MemoryCell::NONE, + ); + + let mut missing_values: Vec<(usize, MemoryCell)> = vec![]; + + for (offset, cell) in self.segments.memory.data[builtin_index].iter().enumerate() { + if let Some(deduced_memory_cell) = builtin + .deduce_memory_cell( + Relocatable::from((builtin_index as isize, offset)), + &self.segments.memory, + ) + .map_err(VirtualMachineError::RunnerError)? + { + match cell.get_value() { + Some(memory_value) => { + // Checks that the value in the memory is correct. + if memory_value != deduced_memory_cell { + return Err(VirtualMachineError::InconsistentAutoDeduction( + Box::new(( + builtin.name(), + deduced_memory_cell, + Some(memory_value), + )), + )); + } + } + None => { + // Collect value to be stored in memory. + missing_values.push((offset, MemoryCell::new(deduced_memory_cell))); + } + } + } + } + + for (offset, value) in missing_values { + self.segments.memory.data[builtin_index][offset] = value; + } + } + Ok(()) + } + ///Makes sure that all assigned memory cells are consistent with their auto deduction rules. pub fn verify_auto_deductions(&self) -> Result<(), VirtualMachineError> { for builtin in self.builtin_runners.iter() { @@ -798,8 +859,16 @@ impl VirtualMachine { Ok(()) } - pub fn end_run(&mut self, exec_scopes: &ExecutionScopes) -> Result<(), VirtualMachineError> { - self.verify_auto_deductions()?; + pub fn end_run( + &mut self, + exec_scopes: &ExecutionScopes, + proof_mode: bool, + ) -> Result<(), VirtualMachineError> { + if proof_mode { + self.complete_builtin_auto_deductions()?; + } else { + self.verify_auto_deductions()?; + } self.run_finished = true; match exec_scopes.data.len() { 1 => Ok(()), @@ -4560,7 +4629,7 @@ mod tests { scopes.enter_scope(HashMap::new()); assert_matches!( - vm.end_run(scopes), + vm.end_run(scopes, false), Err(VirtualMachineError::MainScopeError( ExecScopeError::NoScopeError ))