From 15f9dc0567e8c0987fef390b0e7bb86f0b19daaa Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:57:56 -0300 Subject: [PATCH] Add mod builtin (#1673) * Ignore pesky pie zip files * Add zero segment * Handle zero segment when counting memory holes * Add Changelog entry * Fix macro * First draft of runner & instance_def * Complete struct * Impl initialize_segments + read_n_words_value * Implement read_inputs * Implement read_memory_vars * Impl fill_inputs * Impl fill_offsets * Impl write_n_words_value * Implement fill_value * Implement fill_memory * Integrate mod builtin under feature flag * Add test file * Impl hint * Push hint impl file * Add quick impl of run_additional_security_checks * Some fixes * Fixes * Fix bugs * Temp testing code * Refactor: Use a struct to represent inputs * Refactor: use a struct to represent memory vars * Refactor: Use a constant N_WORDS * Refactor: use fixed N_WORDS size arrays to ensure safe indexing * Store prime as NonZeroFelt * Refactor: Used a fixed-size array for shift_powers * Clippy * Clippy * Small improvments * Refactor: Use only offsets_ptr instead of all inputs in fill_offsets * Refactor(Simplification): Reduce the return type of read_memory_vars to only a b & c as the other values are not used * Fix(Refactor: Use BigUint to represent values as we have to accomodate values up to U384 * Refactor: Optimize fill_offsets loop so clippy does not complain about it * Add a fill_memory wrapper on the vm to avoid cloning the builtin runners * Fixes * Check batch size * Refactors: Reduce duplication in mod_builtin_fill_memory & combine loops in fill_value * Safety: Use saturating sub * Refactor: Avoid creating empty structs in fill_memory * Add integration test * Add error handling + integration tests * Fix bugged shift_powers creation * Improve error formatting * Simplify error string generation * Fix test * Add test using large batch size + hint impl for circuits using large batch size * Add test with large batch size * Fix hint impl + rename module * Improve error handling of run_additional_security_checks * Remove unused method BuiltinRunner::get_memory_accesses * Impl mark_accessed_memory * Mark cells created by fill_memory as accessed during execution * Remove mark_accessed_memory * Finalize zero segment * WIP: Move final_stack to enum impl * Remove unused methods from builtin runners * Remove commented code * Get rid of empty impls in builtin runners * Move get_memory_segment_addresses impl to enum impl * Impl builtin methods * Add placeholder value * Fix cells_per_instance * Remove temp testing code * Run modulo builtin security checks when running builtin security checks * Use realistic value for mod builtin ratio * Draft(private inputs) * Impl air_private_input * Update private input structure * Improve struct compatibility for serialization * Relocate zero_segment_address + Use BtreeMap keep batches sorted * Fix field names * Clippy * Remove unused attribute from modulo module + limit feature gated code * Fix test files EOF * Relocate ptrs when creating mod private input * Remove feature * Update requirements.txt * Update program + add changelog entry * Impl new hints * Adjust recursive_large_output params * Update tests * Remove allow(unused) * Push bench file * Add non-std imports * Remove unused func * Remove dead code * Box error contents to reduce errir size * Avoid needless to_string when reporting errors * Copy common lib modules so that we can run mod builtin tests in CI * Add mod_builtin to special features so we can run the tests in CI * Fix file * Fix get_used_instances for segment arena * Fix test * Add temp fix * Clippy * fmt * Add no-std import * Add no-std import * Push changelog * Push deleted comment * fmt * Fix typo * Compile with proof mode on CI + add air_private_input tests * Run proof mode integration tests * Push symlinks for convenience as these will be moved soon * Fix test * Fix test value * Fix test value * Fix * Use rea data in layoyts + adjust tests accordingly * Update air priv input tests value (Accounting for increased ratios in layout) * Remove feature mod_builtin * Revert "Remove feature mod_builtin" This reverts commit 5cc921c6e9200dbdc4ad45fa937305f0d9108dd6. * Remove dbg print * Add imports --- .github/workflows/rust.yml | 2 +- CHANGELOG.md | 19 + Makefile | 15 +- cairo-vm-cli/Cargo.toml | 3 +- cairo1-run/src/cairo_run.rs | 6 +- .../mod_builtin_feature/common/modulo.cairo | 124 +++ .../mod_builtin_feature/mod_builtin.cairo | 53 ++ .../mod_builtin_failure.cairo | 53 ++ .../mod_builtin_large_batch_size.cairo | 53 ++ ...d_builtin_large_batch_size_benchmark.cairo | 65 ++ ...mod_builtin_large_batch_size_failure.cairo | 53 ++ .../proof/mod_builtin.cairo | 1 + .../proof/mod_builtin_failure.cairo | 1 + .../proof/mod_builtin_large_batch_size.cairo | 1 + ...d_builtin_large_batch_size_benchmark.cairo | 1 + ...mod_builtin_large_batch_size_failure.cairo | 1 + vm/Cargo.toml | 1 + vm/src/air_private_input.rs | 62 +- .../builtin_hint_processor_definition.rs | 12 + .../builtin_hint_processor/hint_code.rs | 4 + .../builtin_hint_processor/mod.rs | 1 + .../builtin_hint_processor/mod_circuit.rs | 93 ++ vm/src/math_utils/mod.rs | 14 + vm/src/serde/deserialize_program.rs | 5 + vm/src/tests/cairo_run_test.rs | 129 ++- vm/src/tests/mod.rs | 10 +- .../builtins_instance_def.rs | 36 + vm/src/types/instance_definitions/mod.rs | 2 + .../instance_definitions/mod_instance_def.rs | 20 + vm/src/types/relocatable.rs | 26 +- vm/src/vm/errors/runner_errors.rs | 18 + vm/src/vm/errors/vm_errors.rs | 4 + vm/src/vm/runners/builtin_runner/bitwise.rs | 120 +-- vm/src/vm/runners/builtin_runner/ec_op.rs | 113 +-- vm/src/vm/runners/builtin_runner/hash.rs | 106 +-- vm/src/vm/runners/builtin_runner/keccak.rs | 126 +-- vm/src/vm/runners/builtin_runner/mod.rs | 226 ++--- vm/src/vm/runners/builtin_runner/modulo.rs | 899 ++++++++++++++++++ vm/src/vm/runners/builtin_runner/output.rs | 69 +- vm/src/vm/runners/builtin_runner/poseidon.rs | 53 +- .../vm/runners/builtin_runner/range_check.rs | 115 +-- .../runners/builtin_runner/segment_arena.rs | 125 +-- vm/src/vm/runners/builtin_runner/signature.rs | 152 +-- vm/src/vm/runners/cairo_runner.rs | 30 +- vm/src/vm/vm_core.rs | 55 +- vm/src/vm/vm_memory/memory.rs | 24 + vm/src/vm/vm_memory/memory_segments.rs | 4 - 47 files changed, 2061 insertions(+), 1044 deletions(-) create mode 100644 cairo_programs/mod_builtin_feature/common/modulo.cairo create mode 100644 cairo_programs/mod_builtin_feature/mod_builtin.cairo create mode 100644 cairo_programs/mod_builtin_feature/mod_builtin_failure.cairo create mode 100644 cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size.cairo create mode 100644 cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_benchmark.cairo create mode 100644 cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_failure.cairo create mode 120000 cairo_programs/mod_builtin_feature/proof/mod_builtin.cairo create mode 120000 cairo_programs/mod_builtin_feature/proof/mod_builtin_failure.cairo create mode 120000 cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size.cairo create mode 120000 cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_benchmark.cairo create mode 120000 cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_failure.cairo create mode 100644 vm/src/hint_processor/builtin_hint_processor/mod_circuit.rs create mode 100644 vm/src/types/instance_definitions/mod_instance_def.rs create mode 100644 vm/src/vm/runners/builtin_runner/modulo.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 07fbdd0542..83452c25ff 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -323,7 +323,7 @@ jobs: strategy: fail-fast: false matrix: - special_features: ["", "extensive_hints"] + special_features: ["", "extensive_hints", "mod_builtin"] target: [ test#1, test#2, test#3, test#4, test-no_std#1, test-no_std#2, test-no_std#3, test-no_std#4, test-wasm ] name: Run tests runs-on: ubuntu-22.04 diff --git a/CHANGELOG.md b/CHANGELOG.md index df54baf11c..0471580ad1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ #### Upcoming Changes +* feat(BREAKING): Add mod builtin [#1673](https://github.com/lambdaclass/cairo-vm/pull/1673) + + Main Changes: + * Add the new `ModBuiltinRunner`, implementing the builtins `add_mod` & `mul_mod` + * Adds `add_mod` & `mul_mod` to the `all_cairo` & `dynamic` layouts under the `mod_builtin` feature flag. This will be added to the main code in a future update. + * Add method `VirtualMachine::fill_memory` in order to perform the new builtin's main logic from within hints + * Add hints to run arithmetic circuits using `add_mod` and/or `mul_mod` builtins + + Other Changes: + * BREAKING: BuiltinRunner method signature change from + `air_private_input(&self, memory: &Memory) -> Vec` to `pub fn air_private_input(&self, segments: &MemorySegmentManager) -> Vec` + * Add `MayleRelocatable::sub_usize` + * Implement `Add for Relocatable` + * Add `Memory::get_usize` + * BREAKING: Clean up unused/duplicated code from builtins module: + * Remove unused method `get_memory_segment_addresses` from all builtin runners & the enum + * Remove empty implementations of `deduce_memory_cell` & `add_validation_rules` from all builtin runners + * Remove duplicated implementation of `final_stack` from all builtin runners except output and move it to the enum implementation + * bugfix(BREAKING): Handle off2 immediate case in `get_integer_from_reference`[#1701](https://github.com/lambdaclass/cairo-vm/pull/1701) * `get_integer_from_reference` & `get_integer_from_var_name` output changed from `Result, HintError>` to `Result` diff --git a/Makefile b/Makefile index 145b221782..86b6d7d7df 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,10 @@ PROOF_BENCH_DIR=cairo_programs/benchmarks PROOF_BENCH_FILES:=$(wildcard $(PROOF_BENCH_DIR)/*.cairo) PROOF_COMPILED_BENCHES:=$(patsubst $(PROOF_BENCH_DIR)/%.cairo, $(PROOF_BENCH_DIR)/%.json, $(PROOF_BENCH_FILES)) +MOD_BUILTIN_TEST_PROOF_DIR=cairo_programs/mod_builtin_feature/proof +MOD_BUILTIN_TEST_PROOF_FILES:=$(wildcard $(MOD_BUILTIN_TEST_PROOF_DIR)/*.cairo) +COMPILED_MOD_BUILTIN_PROOF_TESTS:=$(patsubst $(MOD_BUILTIN_TEST_PROOF_DIR)/%.cairo, $(MOD_BUILTIN_TEST_PROOF_DIR)/%.json, $(MOD_BUILTIN_TEST_PROOF_FILES)) + $(TEST_PROOF_DIR)/%.json: $(TEST_PROOF_DIR)/%.cairo cairo-compile --cairo_path="$(TEST_PROOF_DIR):$(PROOF_BENCH_DIR)" $< --output $@ --proof_mode @@ -61,6 +65,9 @@ $(TEST_PROOF_DIR)/%.trace $(TEST_PROOF_DIR)/%.memory $(TEST_PROOF_DIR)/%.air_pub $(PROOF_BENCH_DIR)/%.json: $(PROOF_BENCH_DIR)/%.cairo cairo-compile --cairo_path="$(TEST_PROOF_DIR):$(PROOF_BENCH_DIR)" $< --output $@ --proof_mode +$(MOD_BUILTIN_TEST_PROOF_DIR)/%.json: $(MOD_BUILTIN_TEST_PROOF_DIR)/%.cairo + cairo-compile --cairo_path="$(MOD_BUILTIN_TEST_PROOF_DIR):$(MOD_BUILTIN_TEST_PROOF_DIR)" $< --output $@ --proof_mode + # ====================== # Run without proof mode # ====================== @@ -87,6 +94,10 @@ PRINT_TEST_DIR=cairo_programs/print_feature PRINT_TEST_FILES:=$(wildcard $(PRINT_TEST_DIR)/*.cairo) COMPILED_PRINT_TESTS:=$(patsubst $(PRINT_TEST_DIR)/%.cairo, $(PRINT_TEST_DIR)/%.json, $(PRINT_TEST_FILES)) +MOD_BUILTIN_TEST_DIR=cairo_programs/mod_builtin_feature +MOD_BUILTIN_TEST_FILES:=$(wildcard $(MOD_BUILTIN_TEST_DIR)/*.cairo) +COMPILED_MOD_BUILTIN_TESTS:=$(patsubst $(MOD_BUILTIN_TEST_DIR)/%.cairo, $(MOD_BUILTIN_TEST_DIR)/%.json, $(MOD_BUILTIN_TEST_FILES)) + NORETROCOMPAT_DIR:=cairo_programs/noretrocompat NORETROCOMPAT_FILES:=$(wildcard $(NORETROCOMPAT_DIR)/*.cairo) COMPILED_NORETROCOMPAT_TESTS:=$(patsubst $(NORETROCOMPAT_DIR)/%.cairo, $(NORETROCOMPAT_DIR)/%.json, $(NORETROCOMPAT_FILES)) @@ -228,8 +239,8 @@ run: check: cargo check -cairo_test_programs: $(COMPILED_TESTS) $(COMPILED_BAD_TESTS) $(COMPILED_NORETROCOMPAT_TESTS) $(COMPILED_PRINT_TESTS) -cairo_proof_programs: $(COMPILED_PROOF_TESTS) +cairo_test_programs: $(COMPILED_TESTS) $(COMPILED_BAD_TESTS) $(COMPILED_NORETROCOMPAT_TESTS) $(COMPILED_PRINT_TESTS) $(COMPILED_MOD_BUILTIN_TESTS) +cairo_proof_programs: $(COMPILED_PROOF_TESTS) $(COMPILED_MOD_BUILTIN_PROOF_TESTS) cairo_bench_programs: $(COMPILED_BENCHES) cairo_1_test_contracts: $(CAIRO_1_COMPILED_CASM_CONTRACTS) cairo_2_test_contracts: $(CAIRO_2_COMPILED_CASM_CONTRACTS) diff --git a/cairo-vm-cli/Cargo.toml b/cairo-vm-cli/Cargo.toml index 5acd241bf1..0b63d2745e 100644 --- a/cairo-vm-cli/Cargo.toml +++ b/cairo-vm-cli/Cargo.toml @@ -22,7 +22,6 @@ rstest = "0.17.0" [features] default = ["with_mimalloc"] - with_mimalloc = ["dep:mimalloc"] with_tracer = ["cairo-vm/tracer", "cairo-vm-tracer"] - +mod_builtin = ["cairo-vm/mod_builtin"] diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index f0d2c2df43..47d1f5bbdb 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -496,7 +496,11 @@ fn create_entry_code( BuiltinName::ec_op => EcOpType::ID, BuiltinName::poseidon => PoseidonType::ID, BuiltinName::segment_arena => SegmentArenaType::ID, - BuiltinName::keccak | BuiltinName::ecdsa | BuiltinName::output => return fp_loc, + BuiltinName::keccak + | BuiltinName::ecdsa + | BuiltinName::output + | BuiltinName::add_mod + | BuiltinName::mul_mod => return fp_loc, }; signature .ret_types diff --git a/cairo_programs/mod_builtin_feature/common/modulo.cairo b/cairo_programs/mod_builtin_feature/common/modulo.cairo new file mode 100644 index 0000000000..b52a85370e --- /dev/null +++ b/cairo_programs/mod_builtin_feature/common/modulo.cairo @@ -0,0 +1,124 @@ +// This file is a copy of common/modulo.cairo + added structs from common/cairo_builtins.cairo so that we can run modulo programs in CI +from starkware.cairo.common.math import safe_div, unsigned_div_rem +from starkware.cairo.common.registers import get_label_location + +// Represents a 384-bit unsigned integer d0 + 2**96 * d1 + 2**192 * d2 + 2**288 * d3 +// where each di is in [0, 2**96). +struct UInt384 { + d0: felt, + d1: felt, + d2: felt, + d3: felt, +} + +// Specifies the Add and Mul Mod builtins memory structure. +struct ModBuiltin { + // The modulus. + p: UInt384, + // A pointer to input values, the intermediate results and the output. + values_ptr: UInt384*, + // A pointer to offsets inside the values array, defining the circuit. + // The offsets array should contain 3 * n elements. + offsets_ptr: felt*, + // The number of operations to perform. + n: felt, +} + +const BATCH_SIZE = 1; + +// Returns the smallest felt 0 <= q < rc_bound such that x <= q * y. +func div_ceil{range_check_ptr}(x: felt, y: felt) -> felt { + let (q, r) = unsigned_div_rem(x, y); + if (r != 0) { + return q + 1; + } else { + return q; + } +} + +// Fills the first instance of the add_mod and mul_mod builtins and calls the fill_memory hint to +// fill the rest of the instances and the missing values in the values table. +// +// This function uses a hardcoded value of batch_size=8, and asserts the instance definitions use +// the same value. +func run_mod_p_circuit_with_large_batch_size{ + range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin* +}( + p: UInt384, + values_ptr: UInt384*, + add_mod_offsets_ptr: felt*, + add_mod_n: felt, + mul_mod_offsets_ptr: felt*, + mul_mod_n: felt, +) { + const BATCH_SIZE = 8; + let add_mod_n_instances = div_ceil(add_mod_n, BATCH_SIZE); + assert add_mod_ptr[0] = ModBuiltin( + p=p, + values_ptr=values_ptr, + offsets_ptr=add_mod_offsets_ptr, + n=add_mod_n_instances * BATCH_SIZE, + ); + + let mul_mod_n_instances = div_ceil(mul_mod_n, BATCH_SIZE); + assert mul_mod_ptr[0] = ModBuiltin( + p=p, + values_ptr=values_ptr, + offsets_ptr=mul_mod_offsets_ptr, + n=mul_mod_n_instances * BATCH_SIZE, + ); + + %{ + from starkware.cairo.lang.builtins.modulo.mod_builtin_runner import ModBuiltinRunner + assert builtin_runners["add_mod_builtin"].instance_def.batch_size == ids.BATCH_SIZE + assert builtin_runners["mul_mod_builtin"].instance_def.batch_size == ids.BATCH_SIZE + + ModBuiltinRunner.fill_memory( + memory=memory, + add_mod=(ids.add_mod_ptr.address_, builtin_runners["add_mod_builtin"], ids.add_mod_n), + mul_mod=(ids.mul_mod_ptr.address_, builtin_runners["mul_mod_builtin"], ids.mul_mod_n), + ) + %} + + let add_mod_ptr = &add_mod_ptr[add_mod_n_instances]; + let mul_mod_ptr = &mul_mod_ptr[mul_mod_n_instances]; + return (); +} + +// Fills the first instance of the add_mod and mul_mod builtins and calls the fill_memory hint to +// fill the rest of the instances and the missing values in the values table. +// +// This function uses a hardcoded value of batch_size=1, and asserts the instance definitions use +// the same value. +func run_mod_p_circuit{add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}( + p: UInt384, + values_ptr: UInt384*, + add_mod_offsets_ptr: felt*, + add_mod_n: felt, + mul_mod_offsets_ptr: felt*, + mul_mod_n: felt, +) { + assert add_mod_ptr[0] = ModBuiltin( + p=p, values_ptr=values_ptr, offsets_ptr=add_mod_offsets_ptr, n=add_mod_n + ); + + assert mul_mod_ptr[0] = ModBuiltin( + p=p, values_ptr=values_ptr, offsets_ptr=mul_mod_offsets_ptr, n=mul_mod_n + ); + + %{ + from starkware.cairo.lang.builtins.modulo.mod_builtin_runner import ModBuiltinRunner + assert builtin_runners["add_mod_builtin"].instance_def.batch_size == 1 + assert builtin_runners["mul_mod_builtin"].instance_def.batch_size == 1 + + ModBuiltinRunner.fill_memory( + memory=memory, + add_mod=(ids.add_mod_ptr.address_, builtin_runners["add_mod_builtin"], ids.add_mod_n), + mul_mod=(ids.mul_mod_ptr.address_, builtin_runners["mul_mod_builtin"], ids.mul_mod_n), + ) + %} + + let add_mod_ptr = &add_mod_ptr[add_mod_n]; + let mul_mod_ptr = &mul_mod_ptr[mul_mod_n]; + return (); +} diff --git a/cairo_programs/mod_builtin_feature/mod_builtin.cairo b/cairo_programs/mod_builtin_feature/mod_builtin.cairo new file mode 100644 index 0000000000..f7ca04517e --- /dev/null +++ b/cairo_programs/mod_builtin_feature/mod_builtin.cairo @@ -0,0 +1,53 @@ +%builtins range_check add_mod mul_mod +// TODO: Import directly from common library once released +from cairo_programs.mod_builtin_feature.common.modulo import ModBuiltin, UInt384, run_mod_p_circuit +// from starkware.common.cairo_builtins import ModBuiltin, UInt384 +// from starkware.cairo.common.modulo import run_mod_p_circuit +from starkware.cairo.common.registers import get_label_location +from starkware.cairo.common.alloc import alloc + +func main{range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}() { + alloc_locals; + + let p = UInt384(d0=1, d1=1, d2=0, d3=0); + let x1 = UInt384(d0=1, d1=0, d2=0, d3=0); + let x2 = UInt384(d0=2, d1=1, d2=0, d3=0); + let x3 = UInt384(d0=2, d1=0, d2=0, d3=0); + let res = UInt384(d0=1, d1=0, d2=0, d3=0); + + let (local values_arr: UInt384*) = alloc(); + assert values_arr[0] = x1; + assert values_arr[1] = x2; + assert values_arr[2] = x3; + assert values_arr[7] = res; + + let (local add_mod_offsets_arr: felt*) = alloc(); + assert add_mod_offsets_arr[0] = 0; // x1 + assert add_mod_offsets_arr[1] = 12; // x2 - x1 + assert add_mod_offsets_arr[2] = 4; // x2 + assert add_mod_offsets_arr[3] = 16; // (x2 - x1) * x3 + assert add_mod_offsets_arr[4] = 20; // x1 * x3 + assert add_mod_offsets_arr[5] = 24; // (x2 - x1) * x3 + x1 * x3 + + let (local mul_mod_offsets_arr: felt*) = alloc(); + assert mul_mod_offsets_arr[0] = 12; // x2 - x1 + assert mul_mod_offsets_arr[1] = 8; // x3 + assert mul_mod_offsets_arr[2] = 16; // (x2 - x1) * x3 + assert mul_mod_offsets_arr[3] = 0; // x1 + assert mul_mod_offsets_arr[4] = 8; // x3 + assert mul_mod_offsets_arr[5] = 20; // x1 * x3 + assert mul_mod_offsets_arr[6] = 8; // x3 + assert mul_mod_offsets_arr[7] = 28; // ((x2 - x1) * x3 + x1 * x3) / x3 = x2 mod p + assert mul_mod_offsets_arr[8] = 24; // (x2 - x1) * x3 + x1 * x3 + + run_mod_p_circuit( + p=p, + values_ptr=values_arr, + add_mod_offsets_ptr=add_mod_offsets_arr, + add_mod_n=2, + mul_mod_offsets_ptr=mul_mod_offsets_arr, + mul_mod_n=3, + ); + + return (); +} diff --git a/cairo_programs/mod_builtin_feature/mod_builtin_failure.cairo b/cairo_programs/mod_builtin_feature/mod_builtin_failure.cairo new file mode 100644 index 0000000000..6e7a9a3175 --- /dev/null +++ b/cairo_programs/mod_builtin_feature/mod_builtin_failure.cairo @@ -0,0 +1,53 @@ +%builtins range_check add_mod mul_mod +// TODO: Import directly from common library once released +from cairo_programs.mod_builtin_feature.common.modulo import ModBuiltin, UInt384, run_mod_p_circuit +// from starkware.common.cairo_builtins import ModBuiltin, UInt384 +// from starkware.cairo.common.modulo import run_mod_p_circuit +from starkware.cairo.common.registers import get_label_location +from starkware.cairo.common.alloc import alloc + +func main{range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}() { + alloc_locals; + + let p = UInt384(d0=1, d1=1, d2=0, d3=0); + let x1 = UInt384(d0=1, d1=0, d2=0, d3=0); + let x2 = UInt384(d0=2, d1=1, d2=0, d3=0); + let x3 = UInt384(d0=2, d1=0, d2=0, d3=0); + let res = UInt384(d0=2, d1=0, d2=0, d3=0); + + let (local values_arr: UInt384*) = alloc(); + assert values_arr[0] = x1; + assert values_arr[1] = x2; + assert values_arr[2] = x3; + assert values_arr[7] = res; + + let (local add_mod_offsets_arr: felt*) = alloc(); + assert add_mod_offsets_arr[0] = 0; // x1 + assert add_mod_offsets_arr[1] = 12; // x2 - x1 + assert add_mod_offsets_arr[2] = 4; // x2 + assert add_mod_offsets_arr[3] = 16; // (x2 - x1) * x3 + assert add_mod_offsets_arr[4] = 20; // x1 * x3 + assert add_mod_offsets_arr[5] = 24; // (x2 - x1) * x3 + x1 * x3 + + let (local mul_mod_offsets_arr: felt*) = alloc(); + assert mul_mod_offsets_arr[0] = 12; // x2 - x1 + assert mul_mod_offsets_arr[1] = 8; // x3 + assert mul_mod_offsets_arr[2] = 16; // (x2 - x1) * x3 + assert mul_mod_offsets_arr[3] = 0; // x1 + assert mul_mod_offsets_arr[4] = 8; // x3 + assert mul_mod_offsets_arr[5] = 20; // x1 * x3 + assert mul_mod_offsets_arr[6] = 8; // x3 + assert mul_mod_offsets_arr[7] = 28; // ((x2 - x1) * x3 + x1 * x3) / x3 = x2 mod p + assert mul_mod_offsets_arr[8] = 24; // (x2 - x1) * x3 + x1 * x3 + + run_mod_p_circuit( + p=p, + values_ptr=values_arr, + add_mod_offsets_ptr=add_mod_offsets_arr, + add_mod_n=2, + mul_mod_offsets_ptr=mul_mod_offsets_arr, + mul_mod_n=3, + ); + + return (); +} diff --git a/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size.cairo b/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size.cairo new file mode 100644 index 0000000000..e25670459e --- /dev/null +++ b/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size.cairo @@ -0,0 +1,53 @@ +%builtins range_check add_mod mul_mod +// TODO: Import directly from common library once released +from cairo_programs.mod_builtin_feature.common.modulo import ModBuiltin, UInt384, run_mod_p_circuit_with_large_batch_size +// from starkware.common.cairo_builtins import ModBuiltin, UInt384 +// from starkware.cairo.common.modulo import run_mod_p_circuit_with_large_batch_size +from starkware.cairo.common.registers import get_label_location +from starkware.cairo.common.alloc import alloc + +func main{range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}() { + alloc_locals; + + let p = UInt384(d0=1, d1=1, d2=0, d3=0); + let x1 = UInt384(d0=1, d1=0, d2=0, d3=0); + let x2 = UInt384(d0=2, d1=1, d2=0, d3=0); + let x3 = UInt384(d0=2, d1=0, d2=0, d3=0); + let res = UInt384(d0=1, d1=0, d2=0, d3=0); + + let (local values_arr: UInt384*) = alloc(); + assert values_arr[0] = x1; + assert values_arr[1] = x2; + assert values_arr[2] = x3; + assert values_arr[7] = res; + + let (local add_mod_offsets_arr: felt*) = alloc(); + assert add_mod_offsets_arr[0] = 0; // x1 + assert add_mod_offsets_arr[1] = 12; // x2 - x1 + assert add_mod_offsets_arr[2] = 4; // x2 + assert add_mod_offsets_arr[3] = 16; // (x2 - x1) * x3 + assert add_mod_offsets_arr[4] = 20; // x1 * x3 + assert add_mod_offsets_arr[5] = 24; // (x2 - x1) * x3 + x1 * x3 + + let (local mul_mod_offsets_arr: felt*) = alloc(); + assert mul_mod_offsets_arr[0] = 12; // x2 - x1 + assert mul_mod_offsets_arr[1] = 8; // x3 + assert mul_mod_offsets_arr[2] = 16; // (x2 - x1) * x3 + assert mul_mod_offsets_arr[3] = 0; // x1 + assert mul_mod_offsets_arr[4] = 8; // x3 + assert mul_mod_offsets_arr[5] = 20; // x1 * x3 + assert mul_mod_offsets_arr[6] = 8; // x3 + assert mul_mod_offsets_arr[7] = 28; // ((x2 - x1) * x3 + x1 * x3) / x3 = x2 mod p + assert mul_mod_offsets_arr[8] = 24; // (x2 - x1) * x3 + x1 * x3 + + run_mod_p_circuit_with_large_batch_size( + p=p, + values_ptr=values_arr, + add_mod_offsets_ptr=add_mod_offsets_arr, + add_mod_n=2, + mul_mod_offsets_ptr=mul_mod_offsets_arr, + mul_mod_n=3, + ); + + return (); +} diff --git a/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_benchmark.cairo b/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_benchmark.cairo new file mode 100644 index 0000000000..21d643af34 --- /dev/null +++ b/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_benchmark.cairo @@ -0,0 +1,65 @@ +%builtins range_check add_mod mul_mod +// TODO: Import directly from common library once released +from cairo_programs.mod_builtin_feature.common.modulo import ModBuiltin, UInt384, run_mod_p_circuit_with_large_batch_size +// from starkware.common.cairo_builtins import ModBuiltin, UInt384 +// from starkware.cairo.common.modulo import run_mod_p_circuit_with_large_batch_size +from starkware.cairo.common.registers import get_label_location +from starkware.cairo.common.alloc import alloc + +func run_circuit{range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}() { + alloc_locals; + + let p = UInt384(d0=1, d1=1, d2=0, d3=0); + let x1 = UInt384(d0=1, d1=0, d2=0, d3=0); + let x2 = UInt384(d0=2, d1=1, d2=0, d3=0); + let x3 = UInt384(d0=2, d1=0, d2=0, d3=0); + let res = UInt384(d0=1, d1=0, d2=0, d3=0); + + let (local values_arr: UInt384*) = alloc(); + assert values_arr[0] = x1; + assert values_arr[1] = x2; + assert values_arr[2] = x3; + assert values_arr[7] = res; + + let (local add_mod_offsets_arr: felt*) = alloc(); + assert add_mod_offsets_arr[0] = 0; // x1 + assert add_mod_offsets_arr[1] = 12; // x2 - x1 + assert add_mod_offsets_arr[2] = 4; // x2 + assert add_mod_offsets_arr[3] = 16; // (x2 - x1) * x3 + assert add_mod_offsets_arr[4] = 20; // x1 * x3 + assert add_mod_offsets_arr[5] = 24; // (x2 - x1) * x3 + x1 * x3 + + let (local mul_mod_offsets_arr: felt*) = alloc(); + assert mul_mod_offsets_arr[0] = 12; // x2 - x1 + assert mul_mod_offsets_arr[1] = 8; // x3 + assert mul_mod_offsets_arr[2] = 16; // (x2 - x1) * x3 + assert mul_mod_offsets_arr[3] = 0; // x1 + assert mul_mod_offsets_arr[4] = 8; // x3 + assert mul_mod_offsets_arr[5] = 20; // x1 * x3 + assert mul_mod_offsets_arr[6] = 8; // x3 + assert mul_mod_offsets_arr[7] = 28; // ((x2 - x1) * x3 + x1 * x3) / x3 = x2 mod p + assert mul_mod_offsets_arr[8] = 24; // (x2 - x1) * x3 + x1 * x3 + + run_mod_p_circuit_with_large_batch_size( + p=p, + values_ptr=values_arr, + add_mod_offsets_ptr=add_mod_offsets_arr, + add_mod_n=2, + mul_mod_offsets_ptr=mul_mod_offsets_arr, + mul_mod_n=3, + ); + + return (); +} + +func run_loop{range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}(n: felt) { + if (n == 0) { + return (); + } + run_circuit(); + return run_loop(n - 1); +} + +func main{range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}() { + return run_loop(100); +} diff --git a/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_failure.cairo b/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_failure.cairo new file mode 100644 index 0000000000..9605de779a --- /dev/null +++ b/cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_failure.cairo @@ -0,0 +1,53 @@ +%builtins range_check add_mod mul_mod +// TODO: Import directly from common library once released +from cairo_programs.mod_builtin_feature.common.modulo import ModBuiltin, UInt384, run_mod_p_circuit_with_large_batch_size +// from starkware.common.cairo_builtins import ModBuiltin, UInt384 +// from starkware.cairo.common.modulo import run_mod_p_circuit_with_large_batch_size +from starkware.cairo.common.registers import get_label_location +from starkware.cairo.common.alloc import alloc + +func main{range_check_ptr, add_mod_ptr: ModBuiltin*, mul_mod_ptr: ModBuiltin*}() { + alloc_locals; + + let p = UInt384(d0=1, d1=1, d2=0, d3=0); + let x1 = UInt384(d0=1, d1=0, d2=0, d3=0); + let x2 = UInt384(d0=2, d1=1, d2=0, d3=0); + let x3 = UInt384(d0=2, d1=0, d2=0, d3=0); + let res = UInt384(d0=2, d1=0, d2=0, d3=0); + + let (local values_arr: UInt384*) = alloc(); + assert values_arr[0] = x1; + assert values_arr[1] = x2; + assert values_arr[2] = x3; + assert values_arr[7] = res; + + let (local add_mod_offsets_arr: felt*) = alloc(); + assert add_mod_offsets_arr[0] = 0; // x1 + assert add_mod_offsets_arr[1] = 12; // x2 - x1 + assert add_mod_offsets_arr[2] = 4; // x2 + assert add_mod_offsets_arr[3] = 16; // (x2 - x1) * x3 + assert add_mod_offsets_arr[4] = 20; // x1 * x3 + assert add_mod_offsets_arr[5] = 24; // (x2 - x1) * x3 + x1 * x3 + + let (local mul_mod_offsets_arr: felt*) = alloc(); + assert mul_mod_offsets_arr[0] = 12; // x2 - x1 + assert mul_mod_offsets_arr[1] = 8; // x3 + assert mul_mod_offsets_arr[2] = 16; // (x2 - x1) * x3 + assert mul_mod_offsets_arr[3] = 0; // x1 + assert mul_mod_offsets_arr[4] = 8; // x3 + assert mul_mod_offsets_arr[5] = 20; // x1 * x3 + assert mul_mod_offsets_arr[6] = 8; // x3 + assert mul_mod_offsets_arr[7] = 28; // ((x2 - x1) * x3 + x1 * x3) / x3 = x2 mod p + assert mul_mod_offsets_arr[8] = 24; // (x2 - x1) * x3 + x1 * x3 + + run_mod_p_circuit_with_large_batch_size( + p=p, + values_ptr=values_arr, + add_mod_offsets_ptr=add_mod_offsets_arr, + add_mod_n=2, + mul_mod_offsets_ptr=mul_mod_offsets_arr, + mul_mod_n=3, + ); + + return (); +} diff --git a/cairo_programs/mod_builtin_feature/proof/mod_builtin.cairo b/cairo_programs/mod_builtin_feature/proof/mod_builtin.cairo new file mode 120000 index 0000000000..754dd814da --- /dev/null +++ b/cairo_programs/mod_builtin_feature/proof/mod_builtin.cairo @@ -0,0 +1 @@ +../mod_builtin.cairo \ No newline at end of file diff --git a/cairo_programs/mod_builtin_feature/proof/mod_builtin_failure.cairo b/cairo_programs/mod_builtin_feature/proof/mod_builtin_failure.cairo new file mode 120000 index 0000000000..11df1c30ef --- /dev/null +++ b/cairo_programs/mod_builtin_feature/proof/mod_builtin_failure.cairo @@ -0,0 +1 @@ +../mod_builtin_failure.cairo \ No newline at end of file diff --git a/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size.cairo b/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size.cairo new file mode 120000 index 0000000000..edcda419b0 --- /dev/null +++ b/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size.cairo @@ -0,0 +1 @@ +../mod_builtin_large_batch_size.cairo \ No newline at end of file diff --git a/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_benchmark.cairo b/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_benchmark.cairo new file mode 120000 index 0000000000..07b4071b7e --- /dev/null +++ b/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_benchmark.cairo @@ -0,0 +1 @@ +../mod_builtin_large_batch_size_benchmark.cairo \ No newline at end of file diff --git a/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_failure.cairo b/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_failure.cairo new file mode 120000 index 0000000000..1882352a93 --- /dev/null +++ b/cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size_failure.cairo @@ -0,0 +1 @@ +../mod_builtin_large_batch_size_failure.cairo \ No newline at end of file diff --git a/vm/Cargo.toml b/vm/Cargo.toml index f31f6df1fb..d1b25dace9 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -27,6 +27,7 @@ cairo-1-hints = [ "dep:ark-std", ] tracer = [] +mod_builtin = [] # Note that these features are not retro-compatible with the cairo Python VM. test_utils = [ diff --git a/vm/src/air_private_input.rs b/vm/src/air_private_input.rs index 52e3ac4cae..d1c5f1a8ca 100644 --- a/vm/src/air_private_input.rs +++ b/vm/src/air_private_input.rs @@ -1,11 +1,12 @@ use crate::{ stdlib::{ - collections::HashMap, + collections::{BTreeMap, HashMap}, prelude::{String, Vec}, }, vm::runners::builtin_runner::{ - BITWISE_BUILTIN_NAME, EC_OP_BUILTIN_NAME, HASH_BUILTIN_NAME, KECCAK_BUILTIN_NAME, - POSEIDON_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME, SIGNATURE_BUILTIN_NAME, + ADD_MOD_BUILTIN_NAME, BITWISE_BUILTIN_NAME, EC_OP_BUILTIN_NAME, HASH_BUILTIN_NAME, + KECCAK_BUILTIN_NAME, MUL_MOD_BUILTIN_NAME, POSEIDON_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME, + SIGNATURE_BUILTIN_NAME, }, }; use serde::{Deserialize, Serialize}; @@ -31,6 +32,10 @@ pub struct AirPrivateInputSerializable { keccak: Option>, #[serde(skip_serializing_if = "Option::is_none")] poseidon: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + add_mod: Option, + #[serde(skip_serializing_if = "Option::is_none")] + mul_mod: Option, } // Contains only builtin public inputs, useful for library users @@ -46,6 +51,7 @@ pub enum PrivateInput { PoseidonState(PrivateInputPoseidonState), KeccakState(PrivateInputKeccakState), Signature(PrivateInputSignature), + Mod(ModInput), } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] @@ -106,6 +112,44 @@ pub struct SignatureInput { pub w: Felt252, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct ModInput { + pub instances: Vec, + pub zero_value_address: usize, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct ModInputInstance { + pub index: usize, + pub p0: Felt252, + pub p1: Felt252, + pub p2: Felt252, + pub p3: Felt252, + pub values_ptr: usize, + pub offsets_ptr: usize, + pub n: usize, + pub batch: BTreeMap, +} + +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] +pub struct ModInputMemoryVars { + pub a_offset: usize, + pub a0: Felt252, + pub a1: Felt252, + pub a2: Felt252, + pub a3: Felt252, + pub b_offset: usize, + pub b0: Felt252, + pub b1: Felt252, + pub b2: Felt252, + pub b3: Felt252, + pub c_offset: usize, + pub c0: Felt252, + pub c1: Felt252, + pub c2: Felt252, + pub c3: Felt252, +} + impl AirPrivateInput { pub fn to_serializable( &self, @@ -122,6 +166,16 @@ impl AirPrivateInput { ec_op: self.0.get(EC_OP_BUILTIN_NAME).cloned(), keccak: self.0.get(KECCAK_BUILTIN_NAME).cloned(), poseidon: self.0.get(POSEIDON_BUILTIN_NAME).cloned(), + add_mod: self + .0 + .get(ADD_MOD_BUILTIN_NAME) + .and_then(|pi| pi.first()) + .cloned(), + mul_mod: self + .0 + .get(MUL_MOD_BUILTIN_NAME) + .and_then(|pi| pi.first()) + .cloned(), } } } @@ -224,6 +278,8 @@ mod tests { input_s2: Felt252::from(3), }, )]), + add_mod: None, + mul_mod: None, }; let private_input = AirPrivateInput::from(serializable_private_input.clone()); 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 93c60f70b5..e785949884 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 @@ -5,6 +5,7 @@ use super::{ ec_recover_sub_a_b, }, field_arithmetic::{u256_get_square_root, u384_get_square_root, uint384_div}, + mod_circuit::{run_p_mod_circuit, run_p_mod_circuit_with_large_batch_size}, secp::{ ec_utils::{ compute_doubling_slope_external_consts, compute_slope_and_assing_secp_p, @@ -822,6 +823,17 @@ impl HintProcessorLogic for BuiltinHintProcessor { } hint_code::EC_RECOVER_PRODUCT_DIV_M => ec_recover_product_div_m(exec_scopes), hint_code::SPLIT_XX => split_xx(vm, &hint_data.ids_data, &hint_data.ap_tracking), + hint_code::RUN_P_CIRCUIT => { + run_p_mod_circuit(vm, &hint_data.ids_data, &hint_data.ap_tracking) + } + hint_code::RUN_P_CIRCUIT_WITH_LARGE_BATCH_SIZE => { + run_p_mod_circuit_with_large_batch_size( + vm, + &hint_data.ids_data, + &hint_data.ap_tracking, + constants, + ) + } #[cfg(feature = "skip_next_instruction_hint")] hint_code::SKIP_NEXT_INSTRUCTION => skip_next_instruction(vm), #[cfg(feature = "print")] 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 b6b0f1ca24..563fe0d7e0 100644 --- a/vm/src/hint_processor/builtin_hint_processor/hint_code.rs +++ b/vm/src/hint_processor/builtin_hint_processor/hint_code.rs @@ -1422,6 +1422,10 @@ print( {k: v if isinstance(v, int) else [memory[v + i] for i in range(ids.pointer_size)] for k, v in data.items()} )"#; +pub const RUN_P_CIRCUIT: &str = "from starkware.cairo.lang.builtins.modulo.mod_builtin_runner import ModBuiltinRunner\nassert builtin_runners[\"add_mod_builtin\"].instance_def.batch_size == 1\nassert builtin_runners[\"mul_mod_builtin\"].instance_def.batch_size == 1\n\nModBuiltinRunner.fill_memory(\n memory=memory,\n add_mod=(ids.add_mod_ptr.address_, builtin_runners[\"add_mod_builtin\"], ids.add_mod_n),\n mul_mod=(ids.mul_mod_ptr.address_, builtin_runners[\"mul_mod_builtin\"], ids.mul_mod_n),\n)"; + +pub const RUN_P_CIRCUIT_WITH_LARGE_BATCH_SIZE: &str = "from starkware.cairo.lang.builtins.modulo.mod_builtin_runner import ModBuiltinRunner\nassert builtin_runners[\"add_mod_builtin\"].instance_def.batch_size == ids.BATCH_SIZE\nassert builtin_runners[\"mul_mod_builtin\"].instance_def.batch_size == ids.BATCH_SIZE\n\nModBuiltinRunner.fill_memory(\n memory=memory,\n add_mod=(ids.add_mod_ptr.address_, builtin_runners[\"add_mod_builtin\"], ids.add_mod_n),\n mul_mod=(ids.mul_mod_ptr.address_, builtin_runners[\"mul_mod_builtin\"], ids.mul_mod_n),\n)"; + pub const NONDET_ELEMENTS_OVER_TEN: &str = "memory[ap] = to_felt_or_relocatable(ids.elements_end - ids.elements >= 10)"; pub const NONDET_ELEMENTS_OVER_TWO: &str = diff --git a/vm/src/hint_processor/builtin_hint_processor/mod.rs b/vm/src/hint_processor/builtin_hint_processor/mod.rs index 8236f8a866..a890ad6ebe 100644 --- a/vm/src/hint_processor/builtin_hint_processor/mod.rs +++ b/vm/src/hint_processor/builtin_hint_processor/mod.rs @@ -16,6 +16,7 @@ pub mod keccak_utils; pub mod math_utils; pub mod memcpy_hint_utils; pub mod memset_utils; +mod mod_circuit; pub mod poseidon_utils; pub mod pow_utils; #[cfg(feature = "print")] diff --git a/vm/src/hint_processor/builtin_hint_processor/mod_circuit.rs b/vm/src/hint_processor/builtin_hint_processor/mod_circuit.rs new file mode 100644 index 0000000000..22fbc57d19 --- /dev/null +++ b/vm/src/hint_processor/builtin_hint_processor/mod_circuit.rs @@ -0,0 +1,93 @@ +use crate::stdlib::prelude::String; +use crate::{ + hint_processor::hint_processor_definition::HintReference, + serde::deserialize_program::ApTracking, + stdlib::collections::HashMap, + vm::{errors::hint_errors::HintError, vm_core::VirtualMachine}, + Felt252, +}; +#[cfg(not(feature = "mod_builtin"))] +use crate::{stdlib::prelude::Box, types::errors::math_errors::MathError}; +use num_traits::ToPrimitive; + +use super::hint_utils::{get_integer_from_var_name, get_ptr_from_var_name}; +/* Implements Hint: +%{ + from starkware.cairo.lang.builtins.modulo.mod_builtin_runner import ModBuiltinRunner + assert builtin_runners["add_mod_builtin"].instance_def.batch_size == 1 + assert builtin_runners["mul_mod_builtin"].instance_def.batch_size == 1 + + ModBuiltinRunner.fill_memory( + memory=memory, + add_mod=(ids.add_mod_ptr.address_, builtin_runners["add_mod_builtin"], ids.add_mod_n), + mul_mod=(ids.mul_mod_ptr.address_, builtin_runners["mul_mod_builtin"], ids.mul_mod_n), + ) +%} +*/ +pub fn run_p_mod_circuit( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + run_p_mod_circuit_inner(vm, ids_data, ap_tracking, 1) +} + +/* Implements Hint: + %{ + from starkware.cairo.lang.builtins.modulo.mod_builtin_runner import ModBuiltinRunner + assert builtin_runners["add_mod_builtin"].instance_def.batch_size == ids.BATCH_SIZE + assert builtin_runners["mul_mod_builtin"].instance_def.batch_size == ids.BATCH_SIZE + + ModBuiltinRunner.fill_memory( + memory=memory, + add_mod=(ids.add_mod_ptr.address_, builtin_runners["add_mod_builtin"], ids.add_mod_n), + mul_mod=(ids.mul_mod_ptr.address_, builtin_runners["mul_mod_builtin"], ids.mul_mod_n), + ) + %} +*/ +#[allow(unused_variables)] +pub fn run_p_mod_circuit_with_large_batch_size( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, + constants: &HashMap, +) -> Result<(), HintError> { + #[cfg(not(feature = "mod_builtin"))] + const LARGE_BATCH_SIZE_PATH: &str = + "starkware.cairo.common.modulo.run_mod_p_circuit_with_large_batch_size.BATCH_SIZE"; + #[cfg(not(feature = "mod_builtin"))] + let batch_size = constants + .get(LARGE_BATCH_SIZE_PATH) + .ok_or_else(|| HintError::MissingConstant(Box::new(LARGE_BATCH_SIZE_PATH)))?; + #[cfg(not(feature = "mod_builtin"))] + let batch_size = batch_size + .to_usize() + .ok_or_else(|| MathError::Felt252ToUsizeConversion(Box::new(*batch_size)))?; + #[cfg(feature = "mod_builtin")] + let batch_size = 8; // Hardcoded here as we are not importing from the common lib yet + run_p_mod_circuit_inner(vm, ids_data, ap_tracking, batch_size) +} + +pub fn run_p_mod_circuit_inner( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, + batch_size: usize, +) -> Result<(), HintError> { + let add_mod_ptr = get_ptr_from_var_name("add_mod_ptr", vm, ids_data, ap_tracking)?; + let mul_mod_ptr = get_ptr_from_var_name("mul_mod_ptr", vm, ids_data, ap_tracking)?; + let add_mod_n = get_integer_from_var_name("add_mod_n", vm, ids_data, ap_tracking)? + .as_ref() + .to_usize() + .unwrap(); + let mul_mod_n = get_integer_from_var_name("mul_mod_n", vm, ids_data, ap_tracking)? + .as_ref() + .to_usize() + .unwrap(); + vm.mod_builtin_fill_memory( + Some((add_mod_ptr, add_mod_n)), + Some((mul_mod_ptr, mul_mod_n)), + Some(batch_size), + ) + .map_err(HintError::Internal) +} diff --git a/vm/src/math_utils/mod.rs b/vm/src/math_utils/mod.rs index c464cb7228..bd5ff0935d 100644 --- a/vm/src/math_utils/mod.rs +++ b/vm/src/math_utils/mod.rs @@ -196,6 +196,20 @@ pub fn div_mod(n: &BigInt, m: &BigInt, p: &BigInt) -> Result Ok((n * a).mod_floor(p)) } +pub(crate) fn div_mod_unsigned( + n: &BigUint, + m: &BigUint, + p: &BigUint, +) -> Result { + // BigUint to BigInt conversion cannot fail & div_mod will always return a positive value if all values are positive so we can safely unwrap here + div_mod( + &n.to_bigint().unwrap(), + &m.to_bigint().unwrap(), + &p.to_bigint().unwrap(), + ) + .map(|i| i.to_biguint().unwrap()) +} + pub fn ec_add( point_a: (BigInt, BigInt), point_b: (BigInt, BigInt), diff --git a/vm/src/serde/deserialize_program.rs b/vm/src/serde/deserialize_program.rs index dda347101a..a849541d23 100644 --- a/vm/src/serde/deserialize_program.rs +++ b/vm/src/serde/deserialize_program.rs @@ -18,6 +18,7 @@ use crate::{ use crate::utils::PRIME_STR; use crate::vm::runners::builtin_runner::SEGMENT_ARENA_BUILTIN_NAME; +use crate::vm::runners::builtin_runner::{ADD_MOD_BUILTIN_NAME, MUL_MOD_BUILTIN_NAME}; use crate::Felt252; use crate::{ serde::deserialize_utils, @@ -55,6 +56,8 @@ pub enum BuiltinName { ec_op, poseidon, segment_arena, + add_mod, + mul_mod, } impl BuiltinName { @@ -69,6 +72,8 @@ impl BuiltinName { BuiltinName::ec_op => EC_OP_BUILTIN_NAME, BuiltinName::poseidon => POSEIDON_BUILTIN_NAME, BuiltinName::segment_arena => SEGMENT_ARENA_BUILTIN_NAME, + BuiltinName::add_mod => ADD_MOD_BUILTIN_NAME, + BuiltinName::mul_mod => MUL_MOD_BUILTIN_NAME, } } } diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index c06a3d4e3a..60bb867a6e 100644 --- a/vm/src/tests/cairo_run_test.rs +++ b/vm/src/tests/cairo_run_test.rs @@ -1,6 +1,15 @@ -use num_traits::Zero; - use crate::tests::*; +#[cfg(feature = "mod_builtin")] +use crate::{ + utils::test_utils::Program, + vm::{ + runners::{builtin_runner::BuiltinRunner, cairo_runner::CairoRunner}, + security::verify_secure_runner, + vm_core::VirtualMachine, + }, +}; + +use num_traits::Zero; #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] @@ -1061,3 +1070,119 @@ fn cairo_run_print_dict_array() { include_bytes!("../../../cairo_programs/print_feature/print_dict_array.json"); run_program_simple(program_data); } + +#[test] +#[cfg(feature = "mod_builtin")] +fn cairo_run_mod_builtin() { + let program_data = + include_bytes!("../../../cairo_programs/mod_builtin_feature/mod_builtin.json"); + run_program_with_custom_mod_builtin_params(program_data, false, 1, 3, None); +} + +#[test] +#[cfg(feature = "mod_builtin")] +fn cairo_run_mod_builtin_failure() { + let program_data = + include_bytes!("../../../cairo_programs/mod_builtin_feature/mod_builtin_failure.json"); + let error_msg = "mul_mod_builtin: Expected a * b == c (mod p). Got: instance=2, batch=0, p=9, a=2, b=2, c=2."; + run_program_with_custom_mod_builtin_params(program_data, false, 1, 3, Some(error_msg)); +} + +#[test] +#[cfg(feature = "mod_builtin")] +fn cairo_run_mod_builtin_large_batch_size() { + let program_data = include_bytes!( + "../../../cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size.json" + ); + run_program_with_custom_mod_builtin_params(program_data, false, 8, 3, None); +} + +#[test] +#[cfg(feature = "mod_builtin")] +fn cairo_run_mod_builtin_large_batch_size_failure() { + let program_data = include_bytes!( + "../../../cairo_programs/mod_builtin_feature/mod_builtin_large_batch_size_failure.json" + ); + let error_msg = "mul_mod_builtin: Expected a * b == c (mod p). Got: instance=0, batch=2, p=9, a=2, b=2, c=2."; + run_program_with_custom_mod_builtin_params(program_data, false, 8, 3, Some(error_msg)); +} + +#[test] +#[cfg(feature = "mod_builtin")] +fn cairo_run_mod_builtin_proof() { + let program_data = + include_bytes!("../../../cairo_programs/mod_builtin_feature/proof/mod_builtin.json"); + run_program_with_custom_mod_builtin_params(program_data, true, 1, 3, None); +} + +#[test] +#[cfg(feature = "mod_builtin")] +fn cairo_run_mod_builtin_large_batch_size_proof() { + let program_data = include_bytes!( + "../../../cairo_programs/mod_builtin_feature/proof/mod_builtin_large_batch_size.json" + ); + run_program_with_custom_mod_builtin_params(program_data, true, 8, 3, None); +} + +#[cfg(feature = "mod_builtin")] +fn run_program_with_custom_mod_builtin_params( + data: &[u8], + proof_mode: bool, + batch_size: usize, + word_bit_len: u32, + security_error: Option<&str>, +) { + let cairo_run_config = CairoRunConfig { + layout: "all_cairo", + proof_mode, + ..Default::default() + }; + let mut hint_processor = BuiltinHintProcessor::new_empty(); + let program = Program::from_bytes(data, Some(cairo_run_config.entrypoint)).unwrap(); + let mut cairo_runner = CairoRunner::new( + &program, + cairo_run_config.layout, + cairo_run_config.proof_mode, + ) + .unwrap(); + + let mut vm = VirtualMachine::new(cairo_run_config.trace_enabled); + let end = cairo_runner.initialize(&mut vm, false).unwrap(); + // Modify add_mod & mul_mod params + for runner in vm.get_builtin_runners_as_mut() { + if let BuiltinRunner::Mod(runner) = runner { + runner.override_layout_params(batch_size, word_bit_len) + } + } + + cairo_runner + .run_until_pc(end, &mut vm, &mut hint_processor) + .unwrap(); + + if cairo_run_config.proof_mode { + cairo_runner + .run_for_steps(1, &mut vm, &mut hint_processor) + .unwrap(); + } + cairo_runner + .end_run( + cairo_run_config.disable_trace_padding, + false, + &mut vm, + &mut hint_processor, + ) + .unwrap(); + + vm.verify_auto_deductions().unwrap(); + cairo_runner.read_return_values(&mut vm).unwrap(); + if cairo_run_config.proof_mode { + cairo_runner.finalize_segments(&mut vm).unwrap(); + } + let security_res = verify_secure_runner(&cairo_runner, true, None, &mut vm); + if let Some(error) = security_error { + assert!(security_res.is_err()); + assert!(security_res.err().unwrap().to_string().contains(error)); + return; + } + security_res.unwrap(); +} diff --git a/vm/src/tests/mod.rs b/vm/src/tests/mod.rs index bb80958789..d09918726f 100644 --- a/vm/src/tests/mod.rs +++ b/vm/src/tests/mod.rs @@ -48,24 +48,25 @@ mod skip_instruction_test; //For simple programs that should just succeed and have no special needs. //Checks memory holes == 0 fn run_program_simple(data: &[u8]) { - run_program(data, Some("all_cairo"), None, None) + run_program(data, false, Some("all_cairo"), None, None) } //For simple programs that should just succeed but using small layout. fn run_program_small(data: &[u8]) { - run_program(data, Some("small"), None, None) + run_program(data, false, Some("small"), None, None) } fn run_program_with_trace(data: &[u8], trace: &[(usize, usize, usize)]) { - run_program(data, Some("all_cairo"), Some(trace), None) + run_program(data, false, Some("all_cairo"), Some(trace), None) } fn run_program_with_error(data: &[u8], error: &str) { - run_program(data, Some("all_cairo"), None, Some(error)) + run_program(data, false, Some("all_cairo"), None, Some(error)) } fn run_program( data: &[u8], + proof_mode: bool, layout: Option<&str>, trace: Option<&[(usize, usize, usize)]>, error: Option<&str>, @@ -75,6 +76,7 @@ fn run_program( layout: layout.unwrap_or("all_cairo"), relocate_mem: true, trace_enabled: true, + proof_mode, ..Default::default() }; let res = cairo_run(data, &cairo_run_config, &mut hint_executor); diff --git a/vm/src/types/instance_definitions/builtins_instance_def.rs b/vm/src/types/instance_definitions/builtins_instance_def.rs index 10675423f7..1b21aa6da0 100644 --- a/vm/src/types/instance_definitions/builtins_instance_def.rs +++ b/vm/src/types/instance_definitions/builtins_instance_def.rs @@ -1,9 +1,11 @@ +use super::mod_instance_def::ModInstanceDef; use super::{ bitwise_instance_def::BitwiseInstanceDef, ec_op_instance_def::EcOpInstanceDef, ecdsa_instance_def::EcdsaInstanceDef, keccak_instance_def::KeccakInstanceDef, pedersen_instance_def::PedersenInstanceDef, poseidon_instance_def::PoseidonInstanceDef, range_check_instance_def::RangeCheckInstanceDef, }; + use serde::Serialize; #[derive(Serialize, Debug, PartialEq)] @@ -16,6 +18,8 @@ pub(crate) struct BuiltinsInstanceDef { pub(crate) ec_op: Option, pub(crate) keccak: Option, pub(crate) poseidon: Option, + pub(crate) add_mod: Option, + pub(crate) mul_mod: Option, } impl BuiltinsInstanceDef { @@ -29,6 +33,8 @@ impl BuiltinsInstanceDef { ec_op: None, keccak: None, poseidon: None, + add_mod: None, + mul_mod: None, } } @@ -42,6 +48,8 @@ impl BuiltinsInstanceDef { ec_op: None, keccak: None, poseidon: None, + add_mod: None, + mul_mod: None, } } @@ -55,6 +63,8 @@ impl BuiltinsInstanceDef { ec_op: None, keccak: None, poseidon: None, + add_mod: None, + mul_mod: None, } } @@ -68,6 +78,8 @@ impl BuiltinsInstanceDef { ec_op: None, keccak: None, poseidon: None, + add_mod: None, + mul_mod: None, } } @@ -81,6 +93,8 @@ impl BuiltinsInstanceDef { ec_op: Some(EcOpInstanceDef::new(Some(1024))), keccak: None, poseidon: Some(PoseidonInstanceDef::default()), + add_mod: None, + mul_mod: None, } } @@ -94,6 +108,8 @@ impl BuiltinsInstanceDef { ec_op: Some(EcOpInstanceDef::new(Some(1024))), keccak: Some(KeccakInstanceDef::new(Some(2048), vec![200; 8])), poseidon: Some(PoseidonInstanceDef::default()), + add_mod: None, + mul_mod: None, } } @@ -107,6 +123,8 @@ impl BuiltinsInstanceDef { ec_op: None, keccak: None, poseidon: Some(PoseidonInstanceDef::new(Some(8))), + add_mod: None, + mul_mod: None, } } @@ -120,6 +138,14 @@ impl BuiltinsInstanceDef { ec_op: Some(EcOpInstanceDef::new(Some(1024))), keccak: Some(KeccakInstanceDef::new(Some(2048), vec![200; 8])), poseidon: Some(PoseidonInstanceDef::new(Some(256))), + #[cfg(feature = "mod_builtin")] + add_mod: Some(ModInstanceDef::new(Some(128), 1, 96)), + #[cfg(feature = "mod_builtin")] + mul_mod: Some(ModInstanceDef::new(Some(256), 1, 96)), + #[cfg(not(feature = "mod_builtin"))] + add_mod: None, + #[cfg(not(feature = "mod_builtin"))] + mul_mod: None, } } @@ -133,6 +159,8 @@ impl BuiltinsInstanceDef { ec_op: Some(EcOpInstanceDef::default()), keccak: None, poseidon: None, + add_mod: None, + mul_mod: None, } } @@ -146,6 +174,14 @@ impl BuiltinsInstanceDef { ec_op: Some(EcOpInstanceDef::new(None)), keccak: None, poseidon: None, + #[cfg(feature = "mod_builtin")] + add_mod: Some(ModInstanceDef::new(None, 1, 96)), + #[cfg(feature = "mod_builtin")] + mul_mod: Some(ModInstanceDef::new(None, 1, 96)), + #[cfg(not(feature = "mod_builtin"))] + add_mod: None, + #[cfg(not(feature = "mod_builtin"))] + mul_mod: None, } } } diff --git a/vm/src/types/instance_definitions/mod.rs b/vm/src/types/instance_definitions/mod.rs index 53bcc15d1b..691be29847 100644 --- a/vm/src/types/instance_definitions/mod.rs +++ b/vm/src/types/instance_definitions/mod.rs @@ -5,6 +5,8 @@ pub mod diluted_pool_instance_def; pub mod ec_op_instance_def; pub mod ecdsa_instance_def; pub mod keccak_instance_def; +#[allow(unused)] +pub mod mod_instance_def; pub mod pedersen_instance_def; pub mod poseidon_instance_def; pub mod range_check_instance_def; diff --git a/vm/src/types/instance_definitions/mod_instance_def.rs b/vm/src/types/instance_definitions/mod_instance_def.rs new file mode 100644 index 0000000000..50bd8184c1 --- /dev/null +++ b/vm/src/types/instance_definitions/mod_instance_def.rs @@ -0,0 +1,20 @@ +use serde::Serialize; + +pub(crate) const N_WORDS: usize = 4; + +#[derive(Serialize, Debug, PartialEq, Clone)] +pub(crate) struct ModInstanceDef { + pub(crate) ratio: Option, + pub(crate) word_bit_len: u32, + pub(crate) batch_size: usize, +} + +impl ModInstanceDef { + pub(crate) fn new(ratio: Option, batch_size: usize, word_bit_len: u32) -> Self { + ModInstanceDef { + ratio, + word_bit_len, + batch_size, + } + } +} diff --git a/vm/src/types/relocatable.rs b/vm/src/types/relocatable.rs index 4bac0bba46..e569ce7707 100644 --- a/vm/src/types/relocatable.rs +++ b/vm/src/types/relocatable.rs @@ -127,6 +127,13 @@ impl AddAssign for Relocatable { } } +impl Add for Relocatable { + type Output = Result; + fn add(self, other: u32) -> Result { + self + other as usize + } +} + impl Add for Relocatable { type Output = Result; fn add(self, other: i32) -> Result { @@ -225,15 +232,8 @@ impl MaybeRelocatable { pub fn add_int(&self, other: &Felt252) -> Result { match *self { MaybeRelocatable::Int(ref value) => Ok(MaybeRelocatable::Int(value + other)), - MaybeRelocatable::RelocatableValue(ref rel) => { - let big_offset = other + rel.offset as u64; - let new_offset = big_offset.to_usize().ok_or_else(|| { - MathError::RelocatableAddFelt252OffsetExceeded(Box::new((*rel, *other))) - })?; - Ok(MaybeRelocatable::RelocatableValue(Relocatable { - segment_index: rel.segment_index, - offset: new_offset, - })) + MaybeRelocatable::RelocatableValue(rel) => { + Ok(MaybeRelocatable::RelocatableValue((rel + other)?)) } } } @@ -264,6 +264,14 @@ impl MaybeRelocatable { } } + /// Subs a usize from self + pub fn sub_usize(&self, other: usize) -> Result { + Ok(match *self { + MaybeRelocatable::Int(ref value) => MaybeRelocatable::Int(value - other as u64), + MaybeRelocatable::RelocatableValue(rel) => (rel - other)?.into(), + }) + } + /// Substracts two MaybeRelocatable values and returns the result as a MaybeRelocatable value. /// Only values of the same type may be substracted. /// Relocatable values can only be substracted if they belong to the same segment. diff --git a/vm/src/vm/errors/runner_errors.rs b/vm/src/vm/errors/runner_errors.rs index 01634ecb2c..ac59da83e9 100644 --- a/vm/src/vm/errors/runner_errors.rs +++ b/vm/src/vm/errors/runner_errors.rs @@ -104,6 +104,24 @@ pub enum RunnerError { InvalidPoint, #[error("Page ({0}) is not on the expected segment {1}")] PageNotOnSegment(Relocatable, usize), + #[error("Expected integer at address {} to be smaller than 2^{}. Got: {}.", (*.0).0, (*.0).1, (*.0).2)] + WordExceedsModBuiltinWordBitLen(Box<(Relocatable, u32, Felt252)>), + #[error("{}: Expected n >= 1. Got: {}.", (*.0).0, (*.0).1)] + ModBuiltinNLessThanOne(Box<(&'static str, usize)>), + #[error("{}: Missing value at address {}.", (*.0).0, (*.0).1)] + ModBuiltinMissingValue(Box<(&'static str, Relocatable)>), + #[error("{}: n must be <= {}", (*.0).0, (*.0).1)] + FillMemoryMaxExceeded(Box<(&'static str, usize)>), + #[error("{0}: write_n_words value must be 0 after loop")] + WriteNWordsValueNotZero(&'static str), + #[error("add_mod and mul_mod builtins must have the same n_words and word_bit_len.")] + ModBuiltinsMismatchedInstanceDef, + #[error("At least one of add_mod and mul_mod must be given.")] + FillMemoryNoBuiltinSet, + #[error("Could not fill the values table, add_mod_index={0}, mul_mod_index={1}")] + FillMemoryCoudNotFillTable(usize, usize), + #[error("{}: {}", (*.0).0, (*.0).1)] + ModBuiltinSecurityCheck(Box<(&'static str, String)>), } #[cfg(test)] diff --git a/vm/src/vm/errors/vm_errors.rs b/vm/src/vm/errors/vm_errors.rs index cbccde51b5..68b8ceed45 100644 --- a/vm/src/vm/errors/vm_errors.rs +++ b/vm/src/vm/errors/vm_errors.rs @@ -87,6 +87,8 @@ pub enum VirtualMachineError { NoRangeCheckBuiltin, #[error("Expected ecdsa builtin to be present")] NoSignatureBuiltin, + #[error("Expected {0} to be present")] + NoModBuiltin(&'static str), #[error("Div out of range: 0 < {} <= {}", (*.0).0, (*.0).1)] OutOfValidRange(Box<(Felt252, Felt252)>), #[error("Failed to compare {} and {}, cant compare a relocatable to an integer value", (*.0).0, (*.0).1)] @@ -131,6 +133,8 @@ pub enum VirtualMachineError { FailedToWriteOutput, #[error("Failed to find index {0} in the vm's relocation table")] RelocationNotFound(usize), + #[error("{} batch size is not {}", (*.0).0, (*.0).1)] + ModBuiltinBatchSize(Box<(&'static str, usize)>), } #[cfg(test)] diff --git a/vm/src/vm/runners/builtin_runner/bitwise.rs b/vm/src/vm/runners/builtin_runner/bitwise.rs index 1c4bdb889c..2a049690a2 100644 --- a/vm/src/vm/runners/builtin_runner/bitwise.rs +++ b/vm/src/vm/runners/builtin_runner/bitwise.rs @@ -15,8 +15,6 @@ use crate::{ }; use num_integer::div_ceil; -use super::BITWISE_BUILTIN_NAME; - #[derive(Debug, Clone)] pub struct BitwiseBuiltinRunner { ratio: Option, @@ -63,8 +61,6 @@ impl BitwiseBuiltinRunner { self.ratio } - pub fn add_validation_rule(&self, _memory: &mut Memory) {} - pub fn deduce_memory_cell( &self, address: Relocatable, @@ -121,10 +117,6 @@ impl BitwiseBuiltinRunner { )))) } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base) @@ -149,43 +141,6 @@ impl BitwiseBuiltinRunner { 4 * partition_lengh + num_trimmed } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(BITWISE_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(BITWISE_BUILTIN_NAME)))?; - if self.base as isize != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - BITWISE_BUILTIN_NAME, - stop_pointer, - self.base, - )))); - } - let stop_ptr = stop_pointer.offset; - let num_instances = self.get_used_instances(segments)?; - let used = num_instances * self.cells_per_instance as usize; - if stop_ptr != used { - return Err(RunnerError::InvalidStopPointer(Box::new(( - BITWISE_BUILTIN_NAME, - Relocatable::from((self.base as isize, used)), - Relocatable::from((self.base as isize, stop_ptr)), - )))); - } - self.stop_ptr = Some(stop_ptr); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(0); - Ok(pointer) - } - } - pub fn get_used_instances( &self, segments: &MemorySegmentManager, @@ -225,7 +180,7 @@ mod tests { use crate::relocatable; use crate::serde::deserialize_program::BuiltinName; use crate::vm::errors::memory_errors::MemoryError; - use crate::vm::runners::builtin_runner::BuiltinRunner; + use crate::vm::runners::builtin_runner::{BuiltinRunner, BITWISE_BUILTIN_NAME}; use crate::vm::vm_core::VirtualMachine; use crate::Felt252; use crate::{ @@ -258,7 +213,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true); + let mut builtin: BuiltinRunner = + BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true).into(); let mut vm = vm!(); @@ -282,7 +238,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true); + let mut builtin: BuiltinRunner = + BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true).into(); let mut vm = vm!(); @@ -310,7 +267,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_notincluded() { - let mut builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), false); + let mut builtin: BuiltinRunner = + BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), false).into(); let mut vm = vm!(); @@ -334,7 +292,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true); + let mut builtin: BuiltinRunner = + BitwiseBuiltinRunner::new(&BitwiseInstanceDef::new(Some(10)), true).into(); let mut vm = vm!(); @@ -492,63 +451,6 @@ mod tests { assert_eq!(result, Ok(None)); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None),); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = BuiltinRunner::Bitwise(BitwiseBuiltinRunner::new( - &BitwiseInstanceDef::default(), - true, - )); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { @@ -626,7 +528,7 @@ mod tests { let builtin: BuiltinRunner = BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into(); - let memory = memory![ + let segments = segments![ ((0, 0), 0), ((0, 1), 1), ((0, 2), 2), @@ -644,7 +546,7 @@ mod tests { ((0, 14), 14) ]; assert_eq!( - builtin.air_private_input(&memory), + builtin.air_private_input(&segments), (vec![ PrivateInput::Pair(PrivateInputPair { index: 0, diff --git a/vm/src/vm/runners/builtin_runner/ec_op.rs b/vm/src/vm/runners/builtin_runner/ec_op.rs index af1951f543..2c9be94793 100644 --- a/vm/src/vm/runners/builtin_runner/ec_op.rs +++ b/vm/src/vm/runners/builtin_runner/ec_op.rs @@ -13,8 +13,6 @@ use crate::Felt252; use num_integer::{div_ceil, Integer}; use starknet_types_core::curve::ProjectivePoint; -use super::EC_OP_BUILTIN_NAME; - #[derive(Debug, Clone)] pub struct EcOpBuiltinRunner { ratio: Option, @@ -104,8 +102,6 @@ impl EcOpBuiltinRunner { self.ratio } - pub fn add_validation_rule(&self, _memory: &mut Memory) {} - pub fn deduce_memory_cell( &self, address: Relocatable, @@ -193,10 +189,6 @@ impl EcOpBuiltinRunner { } } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base()) @@ -211,43 +203,6 @@ impl EcOpBuiltinRunner { Ok(div_ceil(used_cells, self.cells_per_instance as usize)) } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(EC_OP_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(EC_OP_BUILTIN_NAME)))?; - if self.base as isize != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - EC_OP_BUILTIN_NAME, - stop_pointer, - self.base, - )))); - } - let stop_ptr = stop_pointer.offset; - let num_instances = self.get_used_instances(segments)?; - let used = num_instances * self.cells_per_instance as usize; - if stop_ptr != used { - return Err(RunnerError::InvalidStopPointer(Box::new(( - EC_OP_BUILTIN_NAME, - Relocatable::from((self.base as isize, used)), - Relocatable::from((self.base as isize, stop_ptr)), - )))); - } - self.stop_ptr = Some(stop_ptr); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(0); - Ok(pointer) - } - } - pub fn format_ec_op_error( p: ProjectivePoint, m: num_bigint::BigUint, @@ -302,6 +257,7 @@ mod tests { use crate::utils::test_utils::*; use crate::vm::errors::cairo_run_errors::CairoRunError; use crate::vm::errors::vm_errors::VirtualMachineError; + use crate::vm::runners::builtin_runner::EC_OP_BUILTIN_NAME; use crate::vm::runners::cairo_runner::CairoRunner; use crate::{felt_hex, felt_str, relocatable}; @@ -329,7 +285,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true); + let mut builtin: BuiltinRunner = + EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true).into(); let mut vm = vm!(); @@ -353,7 +310,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true); + let mut builtin: BuiltinRunner = + EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true).into(); let mut vm = vm!(); @@ -381,7 +339,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_notincluded() { - let mut builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), false); + let mut builtin: BuiltinRunner = + EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), false).into(); let mut vm = vm!(); @@ -405,7 +364,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true); + let mut builtin: BuiltinRunner = + EcOpBuiltinRunner::new(&EcOpInstanceDef::new(Some(10)), true).into(); let mut vm = vm!(); @@ -838,57 +798,6 @@ mod tests { ); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None)); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = - BuiltinRunner::EcOp(EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true)); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = - BuiltinRunner::EcOp(EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = - BuiltinRunner::EcOp(EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { @@ -996,7 +905,7 @@ mod tests { let builtin: BuiltinRunner = EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true).into(); - let memory = memory![ + let segments = segments![ ((0, 0), 0), ((0, 1), 1), ((0, 2), 2), @@ -1004,7 +913,7 @@ mod tests { ((0, 4), 4) ]; assert_eq!( - builtin.air_private_input(&memory), + builtin.air_private_input(&segments), (vec![PrivateInput::EcOp(PrivateInputEcOp { index: 0, p_x: 0.into(), diff --git a/vm/src/vm/runners/builtin_runner/hash.rs b/vm/src/vm/runners/builtin_runner/hash.rs index 788c224644..c9b437403f 100644 --- a/vm/src/vm/runners/builtin_runner/hash.rs +++ b/vm/src/vm/runners/builtin_runner/hash.rs @@ -14,8 +14,6 @@ use crate::Felt252; use num_integer::{div_ceil, Integer}; use starknet_crypto::{pedersen_hash, FieldElement}; -use super::HASH_BUILTIN_NAME; - #[derive(Debug, Clone)] pub struct HashBuiltinRunner { pub base: usize, @@ -66,8 +64,6 @@ impl HashBuiltinRunner { self.ratio } - pub fn add_validation_rule(&self, _memory: &mut Memory) {} - pub fn deduce_memory_cell( &self, address: Relocatable, @@ -124,10 +120,6 @@ impl HashBuiltinRunner { Ok(None) } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base()) @@ -142,43 +134,6 @@ impl HashBuiltinRunner { Ok(div_ceil(used_cells, self.cells_per_instance as usize)) } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(HASH_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(HASH_BUILTIN_NAME)))?; - if self.base as isize != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - HASH_BUILTIN_NAME, - stop_pointer, - self.base, - )))); - } - let stop_ptr = stop_pointer.offset; - let num_instances = self.get_used_instances(segments)?; - let used = num_instances * self.cells_per_instance as usize; - if stop_ptr != used { - return Err(RunnerError::InvalidStopPointer(Box::new(( - HASH_BUILTIN_NAME, - Relocatable::from((self.base as isize, used)), - Relocatable::from((self.base as isize, stop_ptr)), - )))); - } - self.stop_ptr = Some(stop_ptr); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(0); - Ok(pointer) - } - } - pub fn get_additional_data(&self) -> BuiltinAdditionalData { let mut verified_addresses = Vec::new(); for (offset, is_verified) in self.verified_addresses.borrow().iter().enumerate() { @@ -221,6 +176,7 @@ mod tests { use crate::serde::deserialize_program::BuiltinName; use crate::types::program::Program; use crate::utils::test_utils::*; + use crate::vm::runners::builtin_runner::HASH_BUILTIN_NAME; use crate::vm::runners::cairo_runner::CairoRunner; use crate::{felt_hex, relocatable}; @@ -246,7 +202,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin = HashBuiltinRunner::new(Some(10), true); + let mut builtin: BuiltinRunner = HashBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -270,7 +226,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = HashBuiltinRunner::new(Some(10), true); + let mut builtin: BuiltinRunner = HashBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -298,7 +254,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_not_included() { - let mut builtin = HashBuiltinRunner::new(Some(10), false); + let mut builtin: BuiltinRunner = HashBuiltinRunner::new(Some(10), false).into(); let mut vm = vm!(); @@ -322,7 +278,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = HashBuiltinRunner::new(Some(10), true); + let mut builtin: BuiltinRunner = HashBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -480,54 +436,6 @@ mod tests { assert_eq!(result, Ok(None)); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = HashBuiltinRunner::new(Some(256), true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None),); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = BuiltinRunner::Hash(HashBuiltinRunner::new(Some(256), true)); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = BuiltinRunner::Hash(HashBuiltinRunner::new(Some(256), true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = BuiltinRunner::Hash(HashBuiltinRunner::new(Some(256), true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { @@ -577,7 +485,7 @@ mod tests { fn get_air_private_input() { let builtin: BuiltinRunner = HashBuiltinRunner::new(None, true).into(); - let memory = memory![ + let segments = segments![ ((0, 0), 0), ((0, 1), 1), ((0, 2), 2), @@ -590,7 +498,7 @@ mod tests { ((0, 9), 9) ]; assert_eq!( - builtin.air_private_input(&memory), + builtin.air_private_input(&segments), (vec![ PrivateInput::Pair(PrivateInputPair { index: 0, diff --git a/vm/src/vm/runners/builtin_runner/keccak.rs b/vm/src/vm/runners/builtin_runner/keccak.rs index a4f2346b27..cbd5ad0dc5 100644 --- a/vm/src/vm/runners/builtin_runner/keccak.rs +++ b/vm/src/vm/runners/builtin_runner/keccak.rs @@ -5,7 +5,6 @@ use crate::types::instance_definitions::keccak_instance_def::KeccakInstanceDef; use crate::types::relocatable::{MaybeRelocatable, Relocatable}; use crate::vm::errors::memory_errors::MemoryError; use crate::vm::errors::runner_errors::RunnerError; -use crate::vm::vm_core::VirtualMachine; use crate::vm::vm_memory::memory::Memory; use crate::vm::vm_memory::memory_segments::MemorySegmentManager; use crate::Felt252; @@ -64,8 +63,6 @@ impl KeccakBuiltinRunner { self.ratio } - pub fn add_validation_rule(&self, _memory: &mut Memory) {} - pub fn deduce_memory_cell( &self, address: Relocatable, @@ -129,10 +126,6 @@ impl KeccakBuiltinRunner { Ok(self.cache.borrow().get(&address).map(|x| x.into())) } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base()) @@ -147,57 +140,6 @@ impl KeccakBuiltinRunner { Ok(div_ceil(used_cells, self.cells_per_instance as usize)) } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(KECCAK_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(KECCAK_BUILTIN_NAME)))?; - if self.base as isize != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - KECCAK_BUILTIN_NAME, - stop_pointer, - self.base, - )))); - } - let stop_ptr = stop_pointer.offset; - let num_instances = self.get_used_instances(segments)?; - let used = num_instances * self.cells_per_instance as usize; - if stop_ptr != used { - return Err(RunnerError::InvalidStopPointer(Box::new(( - KECCAK_BUILTIN_NAME, - Relocatable::from((self.base as isize, used)), - Relocatable::from((self.base as isize, stop_ptr)), - )))); - } - self.stop_ptr = Some(stop_ptr); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(0); - Ok(pointer) - } - } - - pub fn get_memory_accesses( - &self, - vm: &VirtualMachine, - ) -> Result, MemoryError> { - let segment_size = vm - .segments - .get_segment_size(self.base) - .ok_or(MemoryError::MissingSegmentUsedSizes)?; - - Ok((0..segment_size) - .map(|i| (self.base as isize, i).into()) - .collect()) - } - pub fn get_used_diluted_check_units(&self, diluted_n_bits: u32) -> usize { // The diluted cells are: // state - 25 rounds times 1600 elements. @@ -301,8 +243,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin = - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true); + let mut builtin: BuiltinRunner = + KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true).into(); let mut vm = vm!(); @@ -326,8 +268,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true); + let mut builtin: BuiltinRunner = + KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true).into(); let mut vm = vm!(); @@ -354,8 +296,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_not_included() { - let mut builtin = - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), false); + let mut builtin: BuiltinRunner = + KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), false).into(); let mut vm = vm!(); @@ -379,8 +321,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = - KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true); + let mut builtin: BuiltinRunner = + KeccakBuiltinRunner::new(&KeccakInstanceDef::new(Some(10), vec![200; 8]), true).into(); let mut vm = vm!(); @@ -444,54 +386,6 @@ mod tests { assert_eq!(builtin.get_allocated_memory_units(&vm), Ok(256)); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None)); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { @@ -732,7 +626,7 @@ mod tests { let builtin: BuiltinRunner = KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true).into(); - let memory = memory![ + let segments = segments![ ((0, 0), 0), ((0, 1), 1), ((0, 2), 2), @@ -743,7 +637,7 @@ mod tests { ((0, 7), 7) ]; assert_eq!( - builtin.air_private_input(&memory), + builtin.air_private_input(&segments), (vec![PrivateInput::KeccakState(PrivateInputKeccakState { index: 0, input_s0: 0.into(), diff --git a/vm/src/vm/runners/builtin_runner/mod.rs b/vm/src/vm/runners/builtin_runner/mod.rs index 78eed172a2..9cc2778d11 100644 --- a/vm/src/vm/runners/builtin_runner/mod.rs +++ b/vm/src/vm/runners/builtin_runner/mod.rs @@ -13,21 +13,23 @@ mod bitwise; mod ec_op; mod hash; mod keccak; +mod modulo; mod output; mod poseidon; mod range_check; mod segment_arena; mod signature; -pub use self::keccak::KeccakBuiltinRunner; -pub use self::poseidon::PoseidonBuiltinRunner; -pub use self::segment_arena::SegmentArenaBuiltinRunner; pub use bitwise::BitwiseBuiltinRunner; pub use ec_op::EcOpBuiltinRunner; pub use hash::HashBuiltinRunner; +pub use keccak::KeccakBuiltinRunner; +pub use modulo::ModBuiltinRunner; use num_integer::div_floor; pub use output::OutputBuiltinRunner; +pub use poseidon::PoseidonBuiltinRunner; pub use range_check::RangeCheckBuiltinRunner; +pub use segment_arena::SegmentArenaBuiltinRunner; pub use signature::SignatureBuiltinRunner; use super::cairo_pie::BuiltinAdditionalData; @@ -41,6 +43,8 @@ pub const EC_OP_BUILTIN_NAME: &str = "ec_op_builtin"; pub const KECCAK_BUILTIN_NAME: &str = "keccak_builtin"; pub const POSEIDON_BUILTIN_NAME: &str = "poseidon_builtin"; pub const SEGMENT_ARENA_BUILTIN_NAME: &str = "segment_arena_builtin"; +pub const ADD_MOD_BUILTIN_NAME: &str = "add_mod_builtin"; +pub const MUL_MOD_BUILTIN_NAME: &str = "mul_mod_builtin"; /* NB: this enum is no accident: we may need (and cairo-vm-py *does* need) * structs containing this to be `Send`. The only two ways to achieve that @@ -61,6 +65,7 @@ pub enum BuiltinRunner { Signature(SignatureBuiltinRunner), Poseidon(PoseidonBuiltinRunner), SegmentArena(SegmentArenaBuiltinRunner), + Mod(ModBuiltinRunner), } impl BuiltinRunner { @@ -80,6 +85,7 @@ impl BuiltinRunner { BuiltinRunner::SegmentArena(ref mut segment_arena) => { segment_arena.initialize_segments(segments) } + BuiltinRunner::Mod(ref mut modulo) => modulo.initialize_segments(segments), } } @@ -94,6 +100,7 @@ impl BuiltinRunner { BuiltinRunner::Signature(ref signature) => signature.initial_stack(), BuiltinRunner::Poseidon(ref poseidon) => poseidon.initial_stack(), BuiltinRunner::SegmentArena(ref segment_arena) => segment_arena.initial_stack(), + BuiltinRunner::Mod(ref modulo) => modulo.initial_stack(), } } @@ -101,26 +108,40 @@ impl BuiltinRunner { pub fn final_stack( &mut self, segments: &MemorySegmentManager, - stack_pointer: Relocatable, + pointer: Relocatable, ) -> Result { - match self { - BuiltinRunner::Bitwise(ref mut bitwise) => bitwise.final_stack(segments, stack_pointer), - BuiltinRunner::EcOp(ref mut ec) => ec.final_stack(segments, stack_pointer), - BuiltinRunner::Hash(ref mut hash) => hash.final_stack(segments, stack_pointer), - BuiltinRunner::Output(ref mut output) => output.final_stack(segments, stack_pointer), - BuiltinRunner::RangeCheck(ref mut range_check) => { - range_check.final_stack(segments, stack_pointer) - } - BuiltinRunner::Keccak(ref mut keccak) => keccak.final_stack(segments, stack_pointer), - BuiltinRunner::Signature(ref mut signature) => { - signature.final_stack(segments, stack_pointer) - } - BuiltinRunner::Poseidon(ref mut poseidon) => { - poseidon.final_stack(segments, stack_pointer) + if let BuiltinRunner::Output(output) = self { + return output.final_stack(segments, pointer); + } + if self.included() { + let stop_pointer_addr = + (pointer - 1).map_err(|_| RunnerError::NoStopPointer(Box::new(self.name())))?; + let stop_pointer = segments + .memory + .get_relocatable(stop_pointer_addr) + .map_err(|_| RunnerError::NoStopPointer(Box::new(self.name())))?; + if self.base() as isize != stop_pointer.segment_index { + return Err(RunnerError::InvalidStopPointerIndex(Box::new(( + self.name(), + stop_pointer, + self.base(), + )))); } - BuiltinRunner::SegmentArena(ref mut segment_arena) => { - segment_arena.final_stack(segments, stack_pointer) + let stop_ptr = stop_pointer.offset; + let num_instances = self.get_used_instances(segments)?; + let used = num_instances * self.cells_per_instance() as usize; + if stop_ptr != used { + return Err(RunnerError::InvalidStopPointer(Box::new(( + self.name(), + Relocatable::from((self.base() as isize, used)), + Relocatable::from((self.base() as isize, stop_ptr)), + )))); } + self.set_stop_ptr(stop_ptr); + Ok(stop_pointer_addr) + } else { + self.set_stop_ptr(0); + Ok(pointer) } } @@ -160,6 +181,22 @@ impl BuiltinRunner { } } + /// Returns if the builtin is included in the program builtins + fn included(&self) -> bool { + match *self { + BuiltinRunner::Bitwise(ref bitwise) => bitwise.included, + BuiltinRunner::EcOp(ref ec) => ec.included, + BuiltinRunner::Hash(ref hash) => hash.included, + BuiltinRunner::Output(ref output) => output.included, + BuiltinRunner::RangeCheck(ref range_check) => range_check.included, + BuiltinRunner::Keccak(ref keccak) => keccak.included, + BuiltinRunner::Signature(ref signature) => signature.included, + BuiltinRunner::Poseidon(ref poseidon) => poseidon.included, + BuiltinRunner::SegmentArena(ref segment_arena) => segment_arena.included, + BuiltinRunner::Mod(ref modulo) => modulo.included, + } + } + ///Returns the builtin's base pub fn base(&self) -> usize { match *self { @@ -173,6 +210,7 @@ impl BuiltinRunner { BuiltinRunner::Poseidon(ref poseidon) => poseidon.base(), //Warning, returns only the segment index, base offset will be 3 BuiltinRunner::SegmentArena(ref segment_arena) => segment_arena.base(), + BuiltinRunner::Mod(ref modulo) => modulo.base(), } } @@ -186,22 +224,16 @@ impl BuiltinRunner { BuiltinRunner::Keccak(keccak) => keccak.ratio(), BuiltinRunner::Signature(ref signature) => signature.ratio(), BuiltinRunner::Poseidon(poseidon) => poseidon.ratio(), + BuiltinRunner::Mod(ref modulo) => modulo.ratio(), } } pub fn add_validation_rule(&self, memory: &mut Memory) { match *self { - BuiltinRunner::Bitwise(ref bitwise) => bitwise.add_validation_rule(memory), - BuiltinRunner::EcOp(ref ec) => ec.add_validation_rule(memory), - BuiltinRunner::Hash(ref hash) => hash.add_validation_rule(memory), - BuiltinRunner::Output(ref output) => output.add_validation_rule(memory), BuiltinRunner::RangeCheck(ref range_check) => range_check.add_validation_rule(memory), - BuiltinRunner::Keccak(ref keccak) => keccak.add_validation_rule(memory), BuiltinRunner::Signature(ref signature) => signature.add_validation_rule(memory), BuiltinRunner::Poseidon(ref poseidon) => poseidon.add_validation_rule(memory), - BuiltinRunner::SegmentArena(ref segment_arena) => { - segment_arena.add_validation_rule(memory) - } + _ => {} } } @@ -214,55 +246,14 @@ impl BuiltinRunner { BuiltinRunner::Bitwise(ref bitwise) => bitwise.deduce_memory_cell(address, memory), BuiltinRunner::EcOp(ref ec) => ec.deduce_memory_cell(address, memory), BuiltinRunner::Hash(ref hash) => hash.deduce_memory_cell(address, memory), - BuiltinRunner::Output(ref output) => output.deduce_memory_cell(address, memory), - BuiltinRunner::RangeCheck(ref range_check) => { - range_check.deduce_memory_cell(address, memory) - } BuiltinRunner::Keccak(ref keccak) => keccak.deduce_memory_cell(address, memory), - BuiltinRunner::Signature(ref signature) => { - signature.deduce_memory_cell(address, memory) - } BuiltinRunner::Poseidon(ref poseidon) => poseidon.deduce_memory_cell(address, memory), - BuiltinRunner::SegmentArena(ref segment_arena) => { - segment_arena.deduce_memory_cell(address, memory) - } + _ => Ok(None), } } - pub fn get_memory_accesses( - &self, - vm: &VirtualMachine, - ) -> Result, MemoryError> { - if let BuiltinRunner::SegmentArena(_) = self { - return Ok(vec![]); - } - let base = self.base(); - let segment_size = vm - .segments - .get_segment_size(base) - .ok_or(MemoryError::MissingSegmentUsedSizes)?; - - Ok((0..segment_size) - .map(|i| (base as isize, i).into()) - .collect()) - } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - match self { - BuiltinRunner::Bitwise(ref bitwise) => bitwise.get_memory_segment_addresses(), - BuiltinRunner::EcOp(ref ec) => ec.get_memory_segment_addresses(), - BuiltinRunner::Hash(ref hash) => hash.get_memory_segment_addresses(), - BuiltinRunner::Output(ref output) => output.get_memory_segment_addresses(), - BuiltinRunner::RangeCheck(ref range_check) => { - range_check.get_memory_segment_addresses() - } - BuiltinRunner::Keccak(ref keccak) => keccak.get_memory_segment_addresses(), - BuiltinRunner::Signature(ref signature) => signature.get_memory_segment_addresses(), - BuiltinRunner::Poseidon(ref poseidon) => poseidon.get_memory_segment_addresses(), - BuiltinRunner::SegmentArena(ref segment_arena) => { - segment_arena.get_memory_segment_addresses() - } - } + (self.base(), self.stop_ptr()) } pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { @@ -278,6 +269,7 @@ impl BuiltinRunner { BuiltinRunner::SegmentArena(ref segment_arena) => { segment_arena.get_used_cells(segments) } + BuiltinRunner::Mod(ref modulo) => modulo.get_used_cells(segments), } } @@ -297,6 +289,7 @@ impl BuiltinRunner { BuiltinRunner::SegmentArena(ref segment_arena) => { segment_arena.get_used_instances(segments) } + BuiltinRunner::Mod(modulo) => modulo.get_used_instances(segments), } } @@ -344,6 +337,7 @@ impl BuiltinRunner { BuiltinRunner::Signature(builtin) => builtin.cells_per_instance, BuiltinRunner::Poseidon(builtin) => builtin.cells_per_instance, BuiltinRunner::SegmentArena(builtin) => builtin.cells_per_instance, + BuiltinRunner::Mod(mod_builtin) => mod_builtin.cells_per_instance(), } } @@ -358,6 +352,7 @@ impl BuiltinRunner { BuiltinRunner::Signature(builtin) => builtin.n_input_cells, BuiltinRunner::Poseidon(builtin) => builtin.n_input_cells, BuiltinRunner::SegmentArena(builtin) => builtin.n_input_cells_per_instance, + BuiltinRunner::Mod(builtin) => builtin.n_input_cells(), } } @@ -371,6 +366,8 @@ impl BuiltinRunner { BuiltinRunner::Keccak(builtin) => builtin.instances_per_component, BuiltinRunner::Signature(builtin) => builtin.instances_per_component, BuiltinRunner::Poseidon(builtin) => builtin.instances_per_component, + // TODO: Placeholder till we see layout data + BuiltinRunner::Mod(_) => 1, } } @@ -385,6 +382,7 @@ impl BuiltinRunner { BuiltinRunner::Signature(_) => SIGNATURE_BUILTIN_NAME, BuiltinRunner::Poseidon(_) => POSEIDON_BUILTIN_NAME, BuiltinRunner::SegmentArena(_) => SEGMENT_ARENA_BUILTIN_NAME, + BuiltinRunner::Mod(b) => b.name(), } } @@ -392,6 +390,9 @@ impl BuiltinRunner { if let BuiltinRunner::Output(_) | BuiltinRunner::SegmentArena(_) = self { return Ok(()); } + if let BuiltinRunner::Mod(modulo) = self { + modulo.run_additional_security_checks(vm)?; + } let cells_per_instance = self.cells_per_instance() as usize; let n_input_cells = self.n_input_cells() as usize; let builtin_segment_index = self.base(); @@ -485,20 +486,20 @@ impl BuiltinRunner { } // Returns information about the builtin that should be added to the AIR private input. - pub fn air_private_input(&self, memory: &Memory) -> Vec { + pub fn air_private_input(&self, segments: &MemorySegmentManager) -> Vec { match self { - BuiltinRunner::RangeCheck(builtin) => builtin.air_private_input(memory), - BuiltinRunner::Bitwise(builtin) => builtin.air_private_input(memory), - BuiltinRunner::Hash(builtin) => builtin.air_private_input(memory), - BuiltinRunner::EcOp(builtin) => builtin.air_private_input(memory), - BuiltinRunner::Poseidon(builtin) => builtin.air_private_input(memory), - BuiltinRunner::Signature(builtin) => builtin.air_private_input(memory), - BuiltinRunner::Keccak(builtin) => builtin.air_private_input(memory), + BuiltinRunner::RangeCheck(builtin) => builtin.air_private_input(&segments.memory), + BuiltinRunner::Bitwise(builtin) => builtin.air_private_input(&segments.memory), + BuiltinRunner::Hash(builtin) => builtin.air_private_input(&segments.memory), + BuiltinRunner::EcOp(builtin) => builtin.air_private_input(&segments.memory), + BuiltinRunner::Poseidon(builtin) => builtin.air_private_input(&segments.memory), + BuiltinRunner::Signature(builtin) => builtin.air_private_input(&segments.memory), + BuiltinRunner::Keccak(builtin) => builtin.air_private_input(&segments.memory), + BuiltinRunner::Mod(builtin) => builtin.air_private_input(segments), _ => vec![], } } - #[cfg(test)] pub(crate) fn set_stop_ptr(&mut self, stop_ptr: usize) { match self { BuiltinRunner::Bitwise(ref mut bitwise) => bitwise.stop_ptr = Some(stop_ptr), @@ -512,6 +513,22 @@ impl BuiltinRunner { BuiltinRunner::SegmentArena(ref mut segment_arena) => { segment_arena.stop_ptr = Some(stop_ptr) } + BuiltinRunner::Mod(modulo) => modulo.stop_ptr = Some(stop_ptr), + } + } + + pub(crate) fn stop_ptr(&self) -> Option { + match self { + BuiltinRunner::Bitwise(ref bitwise) => bitwise.stop_ptr, + BuiltinRunner::EcOp(ref ec) => ec.stop_ptr, + BuiltinRunner::Hash(ref hash) => hash.stop_ptr, + BuiltinRunner::Output(ref output) => output.stop_ptr, + BuiltinRunner::RangeCheck(ref range_check) => range_check.stop_ptr, + BuiltinRunner::Keccak(ref keccak) => keccak.stop_ptr, + BuiltinRunner::Signature(ref signature) => signature.stop_ptr, + BuiltinRunner::Poseidon(ref poseidon) => poseidon.stop_ptr, + BuiltinRunner::SegmentArena(ref segment_arena) => segment_arena.stop_ptr, + BuiltinRunner::Mod(ref modulo) => modulo.stop_ptr, } } } @@ -570,6 +587,12 @@ impl From for BuiltinRunner { } } +impl From for BuiltinRunner { + fn from(runner: ModBuiltinRunner) -> Self { + BuiltinRunner::Mod(runner) + } +} + #[cfg(test)] mod tests { use super::*; @@ -593,49 +616,6 @@ mod tests { #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into(); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into(); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin: BuiltinRunner = - BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into(); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_n_input_cells_bitwise() { diff --git a/vm/src/vm/runners/builtin_runner/modulo.rs b/vm/src/vm/runners/builtin_runner/modulo.rs new file mode 100644 index 0000000000..8e74113096 --- /dev/null +++ b/vm/src/vm/runners/builtin_runner/modulo.rs @@ -0,0 +1,899 @@ +use crate::{ + air_private_input::{ModInput, ModInputInstance, ModInputMemoryVars, PrivateInput}, + math_utils::{div_mod_unsigned, safe_div_usize}, + stdlib::{ + borrow::Cow, + collections::BTreeMap, + prelude::{Box, Vec}, + }, + types::{ + errors::math_errors::MathError, + instance_definitions::mod_instance_def::{ModInstanceDef, N_WORDS}, + relocatable::{relocate_address, MaybeRelocatable, Relocatable}, + }, + vm::{ + errors::{ + memory_errors::MemoryError, runner_errors::RunnerError, vm_errors::VirtualMachineError, + }, + vm_core::VirtualMachine, + vm_memory::{memory::Memory, memory_segments::MemorySegmentManager}, + }, + Felt252, +}; +use core::{fmt::Display, ops::Shl}; +use num_bigint::BigUint; +use num_integer::div_ceil; +use num_integer::Integer; +use num_traits::One; +use num_traits::Zero; + +//The maximum n value that the function fill_memory accepts. +const FILL_MEMORY_MAX: usize = 100000; + +const INPUT_CELLS: usize = 7; + +const VALUES_PTR_OFFSET: u32 = 4; +const OFFSETS_PTR_OFFSET: u32 = 5; +const N_OFFSET: u32 = 6; + +#[derive(Debug, Clone)] +pub struct ModBuiltinRunner { + builtin_type: ModBuiltinType, + base: usize, + pub(crate) stop_ptr: Option, + instance_def: ModInstanceDef, + pub(crate) included: bool, + zero_segment_index: usize, + zero_segment_size: usize, + // Precomputed powers used for reading and writing values that are represented as n_words words of word_bit_len bits each. + shift: BigUint, + shift_powers: [BigUint; N_WORDS], +} + +#[derive(Debug, Clone)] +pub enum ModBuiltinType { + Mul, + Add, +} + +#[derive(Debug)] +pub enum Operation { + Mul, + Add, + Sub, + DivMod(BigUint), +} + +impl Display for Operation { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Operation::Mul => "*".fmt(f), + Operation::Add => "+".fmt(f), + Operation::Sub => "-".fmt(f), + Operation::DivMod(_) => "/".fmt(f), + } + } +} + +#[derive(Debug, Default)] +struct Inputs { + p: BigUint, + p_values: [Felt252; N_WORDS], + values_ptr: Relocatable, + offsets_ptr: Relocatable, + n: usize, +} + +impl ModBuiltinRunner { + pub(crate) fn new_add_mod(instance_def: &ModInstanceDef, included: bool) -> Self { + Self::new(instance_def.clone(), included, ModBuiltinType::Add) + } + + pub(crate) fn new_mul_mod(instance_def: &ModInstanceDef, included: bool) -> Self { + Self::new(instance_def.clone(), included, ModBuiltinType::Mul) + } + + fn new(instance_def: ModInstanceDef, included: bool, builtin_type: ModBuiltinType) -> Self { + let shift = BigUint::one().shl(instance_def.word_bit_len); + let shift_powers = core::array::from_fn(|i| shift.pow(i as u32)); + let zero_segment_size = core::cmp::max(N_WORDS, instance_def.batch_size * 3); + Self { + builtin_type, + base: 0, + stop_ptr: None, + instance_def, + included, + zero_segment_index: 0, + zero_segment_size, + shift, + shift_powers, + } + } + + pub fn name(&self) -> &'static str { + match self.builtin_type { + ModBuiltinType::Mul => super::MUL_MOD_BUILTIN_NAME, + ModBuiltinType::Add => super::ADD_MOD_BUILTIN_NAME, + } + } + + pub fn initialize_segments(&mut self, segments: &mut MemorySegmentManager) { + self.base = segments.add().segment_index as usize; // segments.add() always returns a positive index + self.zero_segment_index = segments.add_zero_segment(self.zero_segment_size) + } + + pub fn initial_stack(&self) -> Vec { + if self.included { + vec![MaybeRelocatable::from((self.base as isize, 0))] + } else { + vec![] + } + } + + pub fn base(&self) -> usize { + self.base + } + + pub fn ratio(&self) -> Option { + self.instance_def.ratio + } + + pub fn cells_per_instance(&self) -> u32 { + INPUT_CELLS as u32 + } + + pub fn n_input_cells(&self) -> u32 { + INPUT_CELLS as u32 + } + + pub fn batch_size(&self) -> usize { + self.instance_def.batch_size + } + + pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { + segments + .get_segment_used_size(self.base) + .ok_or(MemoryError::MissingSegmentUsedSizes) + } + + pub fn get_used_instances( + &self, + segments: &MemorySegmentManager, + ) -> Result { + let used_cells = self.get_used_cells(segments)?; + Ok(div_ceil(used_cells, self.cells_per_instance() as usize)) + } + + pub(crate) fn air_private_input(&self, segments: &MemorySegmentManager) -> Vec { + let segment_index = self.base as isize; + let segment_size = segments + .get_segment_used_size(self.base) + .unwrap_or_default(); + let relocation_table = segments.relocate_segments().unwrap_or_default(); + let mut instances = Vec::::new(); + for instance in 0..segment_size.checked_div(INPUT_CELLS).unwrap_or_default() { + let instance_addr_offset = instance * INPUT_CELLS; + let values_ptr = segments + .memory + .get_relocatable( + ( + segment_index, + instance_addr_offset + VALUES_PTR_OFFSET as usize, + ) + .into(), + ) + .unwrap_or_default(); + let offsets_ptr = segments + .memory + .get_relocatable( + ( + segment_index, + instance_addr_offset + OFFSETS_PTR_OFFSET as usize, + ) + .into(), + ) + .unwrap_or_default(); + let n = segments + .memory + .get_usize((segment_index, instance_addr_offset + N_OFFSET as usize).into()) + .unwrap_or_default(); + let p_values: [Felt252; N_WORDS] = core::array::from_fn(|i| { + segments + .memory + .get_integer((segment_index, instance_addr_offset + i).into()) + .unwrap_or_default() + .into_owned() + }); + let mut batch = BTreeMap::::new(); + let fetch_offset_and_words = |var_index: usize, + index_in_batch: usize| + -> (usize, [Felt252; N_WORDS]) { + let offset = segments + .memory + .get_usize((offsets_ptr + (3 * index_in_batch + var_index)).unwrap_or_default()) + .unwrap_or_default(); + let words: [Felt252; N_WORDS] = core::array::from_fn(|i| { + segments + .memory + .get_integer((values_ptr + (offset + i)).unwrap_or_default()) + .unwrap_or_default() + .into_owned() + }); + (offset, words) + }; + for index_in_batch in 0..self.batch_size() { + let (a_offset, a_values) = fetch_offset_and_words(0, index_in_batch); + let (b_offset, b_values) = fetch_offset_and_words(1, index_in_batch); + let (c_offset, c_values) = fetch_offset_and_words(2, index_in_batch); + batch.insert( + index_in_batch, + ModInputMemoryVars { + a_offset, + b_offset, + c_offset, + a0: a_values[0], + a1: a_values[1], + a2: a_values[2], + a3: a_values[3], + b0: b_values[0], + b1: b_values[1], + b2: b_values[2], + b3: b_values[3], + c0: c_values[0], + c1: c_values[1], + c2: c_values[2], + c3: c_values[3], + }, + ); + } + instances.push(ModInputInstance { + index: instance, + p0: p_values[0], + p1: p_values[1], + p2: p_values[2], + p3: p_values[3], + values_ptr: relocate_address(values_ptr, &relocation_table).unwrap_or_default(), + offsets_ptr: relocate_address(offsets_ptr, &relocation_table).unwrap_or_default(), + n, + batch, + }); + } + + vec![PrivateInput::Mod(ModInput { + instances, + zero_value_address: relocation_table + .get(self.zero_segment_index) + .cloned() + .unwrap_or_default(), + })] + } + + // Reads N_WORDS from memory, starting at address=addr. + // Returns the words and the value if all words are in memory. + // Verifies that all words are integers and are bounded by 2**self.instance_def.word_bit_len. + fn read_n_words_value( + &self, + memory: &Memory, + addr: Relocatable, + ) -> Result<([Felt252; N_WORDS], Option), RunnerError> { + let mut words = Default::default(); + let mut value = BigUint::zero(); + for i in 0..N_WORDS { + let addr_i = (addr + i)?; + match memory.get(&addr_i).map(Cow::into_owned) { + None => return Ok((words, None)), + Some(MaybeRelocatable::RelocatableValue(_)) => { + return Err(MemoryError::ExpectedInteger(Box::new(addr_i)).into()) + } + Some(MaybeRelocatable::Int(word)) => { + let biguint_word = word.to_biguint(); + if biguint_word >= self.shift { + return Err(RunnerError::WordExceedsModBuiltinWordBitLen(Box::new(( + addr_i, + self.instance_def.word_bit_len, + word, + )))); + } + words[i] = word; + value += biguint_word * &self.shift_powers[i]; + } + } + } + Ok((words, Some(value))) + } + + // Reads the inputs to the builtin (see Inputs) from the memory at address=addr. + // Returns a struct with the inputs. Asserts that it exists in memory. + // Returns also the value of p, not just its words. + fn read_inputs(&self, memory: &Memory, addr: Relocatable) -> Result { + let values_ptr = memory.get_relocatable((addr + VALUES_PTR_OFFSET)?)?; + let offsets_ptr = memory.get_relocatable((addr + OFFSETS_PTR_OFFSET)?)?; + let n = memory.get_usize((addr + N_OFFSET)?)?; + if n < 1 { + return Err(RunnerError::ModBuiltinNLessThanOne(Box::new(( + self.name(), + n, + )))); + } + let (p_values, p) = self.read_n_words_value(memory, addr)?; + let p = p.ok_or_else(|| { + RunnerError::ModBuiltinMissingValue(Box::new(( + self.name(), + (addr + N_WORDS).unwrap_or_default(), + ))) + })?; + Ok(Inputs { + p, + p_values, + values_ptr, + offsets_ptr, + n, + }) + } + + // Reads the memory variables to the builtin (see MEMORY_VARS) from the memory given + // the inputs (specifically, values_ptr and offsets_ptr). + // Computes and returns the values of a, b, and c. + fn read_memory_vars( + &self, + memory: &Memory, + values_ptr: Relocatable, + offsets_ptr: Relocatable, + index_in_batch: usize, + ) -> Result<(BigUint, BigUint, BigUint), RunnerError> { + let compute_value = |index: usize| -> Result { + let offset = memory.get_usize((offsets_ptr + (index + 3 * index_in_batch))?)?; + let value_addr = (values_ptr + offset)?; + let (_, value) = self.read_n_words_value(memory, value_addr)?; + let value = value.ok_or_else(|| { + RunnerError::ModBuiltinMissingValue(Box::new(( + self.name(), + (value_addr + N_WORDS).unwrap_or_default(), + ))) + })?; + Ok(value) + }; + + let a = compute_value(0)?; + let b = compute_value(1)?; + let c = compute_value(2)?; + Ok((a, b, c)) + } + + fn fill_inputs( + &self, + memory: &mut Memory, + builtin_ptr: Relocatable, + inputs: &Inputs, + ) -> Result<(), RunnerError> { + if inputs.n > FILL_MEMORY_MAX { + return Err(RunnerError::FillMemoryMaxExceeded(Box::new(( + self.name(), + FILL_MEMORY_MAX, + )))); + } + let n_instances = safe_div_usize(inputs.n, self.instance_def.batch_size)?; + for instance in 1..n_instances { + let instance_ptr = (builtin_ptr + instance * INPUT_CELLS)?; + for i in 0..N_WORDS { + memory.insert_as_accessed((instance_ptr + i)?, &inputs.p_values[i])?; + } + memory.insert_as_accessed((instance_ptr + VALUES_PTR_OFFSET)?, &inputs.values_ptr)?; + memory.insert_as_accessed( + (instance_ptr + OFFSETS_PTR_OFFSET)?, + (inputs.offsets_ptr + (3 * instance * self.instance_def.batch_size))?, + )?; + memory.insert_as_accessed( + (instance_ptr + N_OFFSET)?, + inputs + .n + .saturating_sub(instance * self.instance_def.batch_size), + )?; + } + Ok(()) + } + + // Copies the first offsets in the offsets table to its end, n_copies times. + fn fill_offsets( + &self, + memory: &mut Memory, + offsets_ptr: Relocatable, + index: usize, + n_copies: usize, + ) -> Result<(), RunnerError> { + if n_copies.is_zero() { + return Ok(()); + } + for i in 0..3_usize { + let addr = (offsets_ptr + i)?; + let offset = memory + .get(&((offsets_ptr + i)?)) + .ok_or_else(|| MemoryError::UnknownMemoryCell(Box::new(addr)))? + .into_owned(); + for copy_i in 0..n_copies { + memory.insert_as_accessed((offsets_ptr + (3 * (index + copy_i) + i))?, &offset)?; + } + } + Ok(()) + } + + // Given a value, writes its n_words to memory, starting at address=addr. + fn write_n_words_value( + &self, + memory: &mut Memory, + addr: Relocatable, + value: BigUint, + ) -> Result<(), RunnerError> { + let mut value = value; + for i in 0..N_WORDS { + let word = value.mod_floor(&self.shift); + memory.insert_as_accessed((addr + i)?, Felt252::from(word))?; + value = value.div_floor(&self.shift) + } + if !value.is_zero() { + return Err(RunnerError::WriteNWordsValueNotZero(self.name())); + } + Ok(()) + } + + // Fills a value in the values table, if exactly one value is missing. + // Returns true on success or if all values are already known. + fn fill_value( + &self, + memory: &mut Memory, + inputs: &Inputs, + index: usize, + op: &Operation, + inv_op: &Operation, + ) -> Result { + let mut addresses = Vec::new(); + let mut values = Vec::new(); + for i in 0..3 { + let addr = (inputs.values_ptr + + memory + .get_integer((inputs.offsets_ptr + (3 * index + i))?)? + .as_ref())?; + addresses.push(addr); + let (_, value) = self.read_n_words_value(memory, addr)?; + values.push(value) + } + let (a, b, c) = (&values[0], &values[1], &values[2]); + match (a, b, c) { + // Deduce c from a and b and write it to memory. + (Some(a), Some(b), None) => { + let value = apply_op(a, b, op)?.mod_floor(&inputs.p); + self.write_n_words_value(memory, addresses[2], value)?; + Ok(true) + } + // Deduce b from a and c and write it to memory. + (Some(a), None, Some(c)) => { + let value = apply_op(c, a, inv_op)?.mod_floor(&inputs.p); + self.write_n_words_value(memory, addresses[1], value)?; + Ok(true) + } + // Deduce a from b and c and write it to memory. + (None, Some(b), Some(c)) => { + let value = apply_op(c, b, inv_op)?.mod_floor(&inputs.p); + self.write_n_words_value(memory, addresses[0], value)?; + Ok(true) + } + // All values are already known. + (Some(_), Some(_), Some(_)) => Ok(true), + _ => Ok(false), + } + } + + /// NOTE: It is advisable to use VirtualMachine::mod_builtin_fill_memory instead of this method directly + /// when implementing hints to avoid cloning the runners + + /// Fills the memory with inputs to the builtin instances based on the inputs to the + /// first instance, pads the offsets table to fit the number of operations writen in the + /// input to the first instance, and caculates missing values in the values table. + + /// For each builtin, the given tuple is of the form (builtin_ptr, builtin_runner, n), + /// where n is the number of operations in the offsets table (i.e., the length of the + /// offsets table is 3*n). + + /// The number of operations written to the input of the first instance n' should be at + /// least n and a multiple of batch_size. Previous offsets are copied to the end of the + /// offsets table to make its length 3n'. + pub fn fill_memory( + memory: &mut Memory, + add_mod: Option<(Relocatable, &ModBuiltinRunner, usize)>, + mul_mod: Option<(Relocatable, &ModBuiltinRunner, usize)>, + ) -> Result<(), RunnerError> { + if add_mod.is_none() && mul_mod.is_none() { + return Err(RunnerError::FillMemoryNoBuiltinSet); + } + // Check that the instance definitions of the builtins are the same. + if let (Some((_, add_mod, _)), Some((_, mul_mod, _))) = (add_mod, mul_mod) { + if add_mod.instance_def.word_bit_len != mul_mod.instance_def.word_bit_len { + return Err(RunnerError::ModBuiltinsMismatchedInstanceDef); + } + } + // Fill the inputs to the builtins. + let (add_mod_inputs, add_mod_n) = + if let Some((add_mod_addr, add_mod, add_mod_index)) = add_mod { + let add_mod_inputs = add_mod.read_inputs(memory, add_mod_addr)?; + add_mod.fill_inputs(memory, add_mod_addr, &add_mod_inputs)?; + add_mod.fill_offsets( + memory, + add_mod_inputs.offsets_ptr, + add_mod_index, + add_mod_inputs.n.saturating_sub(add_mod_index), + )?; + (add_mod_inputs, add_mod_index) + } else { + Default::default() + }; + + let (mul_mod_inputs, mul_mod_n) = + if let Some((mul_mod_addr, mul_mod, mul_mod_index)) = mul_mod { + let mul_mod_inputs = mul_mod.read_inputs(memory, mul_mod_addr)?; + mul_mod.fill_inputs(memory, mul_mod_addr, &mul_mod_inputs)?; + mul_mod.fill_offsets( + memory, + mul_mod_inputs.offsets_ptr, + mul_mod_index, + mul_mod_inputs.n.saturating_sub(mul_mod_index), + )?; + (mul_mod_inputs, mul_mod_index) + } else { + Default::default() + }; + + // Get one of the builtin runners - the rest of this function doesn't depend on batch_size. + let mod_runner = if let Some((_, add_mod, _)) = add_mod { + add_mod + } else { + mul_mod.unwrap().1 + }; + // Fill the values table. + let mut add_mod_index = 0; + let mut mul_mod_index = 0; + // Create operation here to avoid cloning p in the loop + let div_operation = Operation::DivMod(mul_mod_inputs.p.clone()); + while add_mod_index < add_mod_n || mul_mod_index < mul_mod_n { + if add_mod_index < add_mod_n + && mod_runner.fill_value( + memory, + &add_mod_inputs, + add_mod_index, + &Operation::Add, + &Operation::Sub, + )? + { + add_mod_index += 1; + } else if mul_mod_index < mul_mod_n + && mod_runner.fill_value( + memory, + &mul_mod_inputs, + mul_mod_index, + &Operation::Mul, + &div_operation, + )? + { + mul_mod_index += 1; + } else { + return Err(RunnerError::FillMemoryCoudNotFillTable( + add_mod_index, + mul_mod_index, + )); + } + } + Ok(()) + } + + // Additional checks added to the standard builtin runner security checks + pub(crate) fn run_additional_security_checks( + &self, + vm: &VirtualMachine, + ) -> Result<(), VirtualMachineError> { + let segment_size = vm + .get_segment_used_size(self.base) + .ok_or(MemoryError::MissingSegmentUsedSizes)?; + let n_instances = div_ceil(segment_size, INPUT_CELLS); + let mut prev_inputs = Inputs::default(); + for instance in 0..n_instances { + let inputs = self.read_inputs( + &vm.segments.memory, + (self.base as isize, instance * INPUT_CELLS).into(), + )?; + if !instance.is_zero() && prev_inputs.n > self.instance_def.batch_size { + for i in 0..N_WORDS { + if inputs.p_values[i] != prev_inputs.p_values[i] { + return Err(RunnerError::ModBuiltinSecurityCheck(Box::new((self.name(), format!("inputs.p_values[i] != prev_inputs.p_values[i]. Got: i={}, inputs.p_values[i]={}, prev_inputs.p_values[i]={}", + i, inputs.p_values[i], prev_inputs.p_values[i])))).into()); + } + } + if inputs.values_ptr != prev_inputs.values_ptr { + return Err(RunnerError::ModBuiltinSecurityCheck(Box::new((self.name(), format!("inputs.values_ptr != prev_inputs.values_ptr. Got: inputs.values_ptr={}, prev_inputs.values_ptr={}", + inputs.values_ptr, prev_inputs.values_ptr)))).into()); + } + if inputs.offsets_ptr + != (prev_inputs.offsets_ptr + (3 * self.instance_def.batch_size))? + { + return Err(RunnerError::ModBuiltinSecurityCheck(Box::new((self.name(), format!("inputs.offsets_ptr != prev_inputs.offsets_ptr + 3 * batch_size. Got: inputs.offsets_ptr={}, prev_inputs.offsets_ptr={}, batch_size={}", + inputs.values_ptr, prev_inputs.values_ptr, self.instance_def.batch_size)))).into()); + } + if inputs.n != prev_inputs.n.saturating_sub(self.instance_def.batch_size) { + return Err(RunnerError::ModBuiltinSecurityCheck(Box::new((self.name(), format!("inputs.n != prev_inputs.n - batch_size. Got: inputs.n={}, prev_inputs.n={}, batch_size={}", + inputs.n, prev_inputs.n, self.instance_def.batch_size)))).into()); + } + } + for index_in_batch in 0..self.instance_def.batch_size { + let (a, b, c) = self.read_memory_vars( + &vm.segments.memory, + inputs.values_ptr, + inputs.offsets_ptr, + index_in_batch, + )?; + let op = match self.builtin_type { + ModBuiltinType::Add => Operation::Add, + ModBuiltinType::Mul => Operation::Mul, + }; + let a_op_b = apply_op(&a, &b, &op)?.mod_floor(&inputs.p); + if a_op_b != c.mod_floor(&inputs.p) { + // Build error string + let p = inputs.p; + let error_string = format!("Expected a {op} b == c (mod p). Got: instance={instance}, batch={index_in_batch}, p={p}, a={a}, b={b}, c={c}."); + return Err(RunnerError::ModBuiltinSecurityCheck(Box::new(( + self.name(), + error_string, + ))) + .into()); + } + } + prev_inputs = inputs; + } + if !n_instances.is_zero() && prev_inputs.n != self.instance_def.batch_size { + return Err(RunnerError::ModBuiltinSecurityCheck(Box::new(( + self.name(), + format!( + "prev_inputs.n != batch_size Got: prev_inputs.n={}, batch_size={}", + prev_inputs.n, self.instance_def.batch_size + ), + ))) + .into()); + } + Ok(()) + } + + #[cfg(test)] + #[cfg(feature = "mod_builtin")] + // Testing method used to test programs that use parameters which are not included in any layout + // For example, programs with large batch size + pub(crate) fn override_layout_params(&mut self, batch_size: usize, word_bit_len: u32) { + self.instance_def.batch_size = batch_size; + self.instance_def.word_bit_len = word_bit_len; + self.shift = BigUint::one().shl(word_bit_len); + self.shift_powers = core::array::from_fn(|i| self.shift.pow(i as u32)); + self.zero_segment_size = core::cmp::max(N_WORDS, batch_size * 3); + } +} + +fn apply_op(lhs: &BigUint, rhs: &BigUint, op: &Operation) -> Result { + Ok(match op { + Operation::Mul => lhs * rhs, + Operation::Add => lhs + rhs, + Operation::Sub => lhs - rhs, + Operation::DivMod(ref p) => div_mod_unsigned(lhs, rhs, p)?, + }) +} + +#[cfg(test)] +mod tests { + + #[test] + #[cfg(feature = "mod_builtin")] + fn test_air_private_input_small_batch_size() { + use super::*; + use crate::{ + air_private_input::{ModInput, ModInputInstance, ModInputMemoryVars, PrivateInput}, + hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, + utils::test_utils::Program, + vm::runners::{ + builtin_runner::{BuiltinRunner, ADD_MOD_BUILTIN_NAME, MUL_MOD_BUILTIN_NAME}, + cairo_runner::CairoRunner, + }, + Felt252, + }; + + let program_data = include_bytes!( + "../../../../../cairo_programs/mod_builtin_feature/proof/mod_builtin.json" + ); + + let mut hint_processor = BuiltinHintProcessor::new_empty(); + let program = Program::from_bytes(program_data, Some("main")).unwrap(); + let mut runner = CairoRunner::new(&program, "all_cairo", true).unwrap(); + + let mut vm = VirtualMachine::new(false); + let end = runner.initialize(&mut vm, false).unwrap(); + // Modify add_mod & mul_mod params + for runner in vm.get_builtin_runners_as_mut() { + if let BuiltinRunner::Mod(runner) = runner { + runner.override_layout_params(1, 3) + } + } + + runner + .run_until_pc(end, &mut vm, &mut hint_processor) + .unwrap(); + runner + .run_for_steps(1, &mut vm, &mut hint_processor) + .unwrap(); + runner + .end_run(false, false, &mut vm, &mut hint_processor) + .unwrap(); + runner.read_return_values(&mut vm).unwrap(); + runner.finalize_segments(&mut vm).unwrap(); + + let air_private_input = runner.get_air_private_input(&vm); + assert_eq!( + air_private_input.0.get(ADD_MOD_BUILTIN_NAME).unwrap()[0], + PrivateInput::Mod(ModInput { + instances: vec![ + ModInputInstance { + index: 0, + p0: Felt252::ONE, + p1: Felt252::ONE, + p2: Felt252::ZERO, + p3: Felt252::ZERO, + values_ptr: 18927, + offsets_ptr: 18959, + n: 2, + batch: BTreeMap::from([( + 0, + ModInputMemoryVars { + a_offset: 0, + a0: Felt252::ONE, + a1: Felt252::ZERO, + a2: Felt252::ZERO, + a3: Felt252::ZERO, + b_offset: 12, + b0: Felt252::ZERO, + b1: Felt252::ZERO, + b2: Felt252::ZERO, + b3: Felt252::ZERO, + c_offset: 4, + c0: Felt252::TWO, + c1: Felt252::ONE, + c2: Felt252::ZERO, + c3: Felt252::ZERO + } + ),]) + }, + ModInputInstance { + index: 1, + p0: Felt252::ONE, + p1: Felt252::ONE, + p2: Felt252::ZERO, + p3: Felt252::ZERO, + values_ptr: 18927, + offsets_ptr: 18962, + n: 1, + batch: BTreeMap::from([( + 0, + ModInputMemoryVars { + a_offset: 16, + a0: Felt252::ZERO, + a1: Felt252::ZERO, + a2: Felt252::ZERO, + a3: Felt252::ZERO, + b_offset: 20, + b0: Felt252::TWO, + b1: Felt252::ZERO, + b2: Felt252::ZERO, + b3: Felt252::ZERO, + c_offset: 24, + c0: Felt252::TWO, + c1: Felt252::ZERO, + c2: Felt252::ZERO, + c3: Felt252::ZERO + } + ),]) + } + ], + zero_value_address: 18027 + }) + ); + assert_eq!( + air_private_input.0.get(MUL_MOD_BUILTIN_NAME).unwrap()[0], + PrivateInput::Mod(ModInput { + instances: vec![ + ModInputInstance { + index: 0, + p0: Felt252::ONE, + p1: Felt252::ONE, + p2: Felt252::ZERO, + p3: Felt252::ZERO, + values_ptr: 18927, + offsets_ptr: 18965, + n: 3, + batch: BTreeMap::from([( + 0, + ModInputMemoryVars { + a_offset: 12, + a0: Felt252::ZERO, + a1: Felt252::ZERO, + a2: Felt252::ZERO, + a3: Felt252::ZERO, + b_offset: 8, + b0: Felt252::TWO, + b1: Felt252::ZERO, + b2: Felt252::ZERO, + b3: Felt252::ZERO, + c_offset: 16, + c0: Felt252::ZERO, + c1: Felt252::ZERO, + c2: Felt252::ZERO, + c3: Felt252::ZERO + } + ),]) + }, + ModInputInstance { + index: 1, + p0: Felt252::ONE, + p1: Felt252::ONE, + p2: Felt252::ZERO, + p3: Felt252::ZERO, + values_ptr: 18927, + offsets_ptr: 18968, + n: 2, + batch: BTreeMap::from([( + 0, + ModInputMemoryVars { + a_offset: 0, + a0: Felt252::ONE, + a1: Felt252::ZERO, + a2: Felt252::ZERO, + a3: Felt252::ZERO, + b_offset: 8, + b0: Felt252::TWO, + b1: Felt252::ZERO, + b2: Felt252::ZERO, + b3: Felt252::ZERO, + c_offset: 20, + c0: Felt252::TWO, + c1: Felt252::ZERO, + c2: Felt252::ZERO, + c3: Felt252::ZERO + } + ),]) + }, + ModInputInstance { + index: 2, + p0: Felt252::ONE, + p1: Felt252::ONE, + p2: Felt252::ZERO, + p3: Felt252::ZERO, + values_ptr: 18927, + offsets_ptr: 18971, + n: 1, + batch: BTreeMap::from([( + 0, + ModInputMemoryVars { + a_offset: 8, + a0: Felt252::TWO, + a1: Felt252::ZERO, + a2: Felt252::ZERO, + a3: Felt252::ZERO, + b_offset: 28, + b0: Felt252::ONE, + b1: Felt252::ZERO, + b2: Felt252::ZERO, + b3: Felt252::ZERO, + c_offset: 24, + c0: Felt252::TWO, + c1: Felt252::ZERO, + c2: Felt252::ZERO, + c3: Felt252::ZERO + } + ),]) + } + ], + zero_value_address: 18027 + }) + ) + } +} diff --git a/vm/src/vm/runners/builtin_runner/output.rs b/vm/src/vm/runners/builtin_runner/output.rs index 853ba038d2..393b0b7c1d 100644 --- a/vm/src/vm/runners/builtin_runner/output.rs +++ b/vm/src/vm/runners/builtin_runner/output.rs @@ -6,7 +6,6 @@ use crate::vm::runners::cairo_pie::{ Attributes, BuiltinAdditionalData, OutputBuiltinAdditionalData, Pages, PublicMemoryPage, }; use crate::vm::vm_core::VirtualMachine; -use crate::vm::vm_memory::memory::Memory; use crate::vm::vm_memory::memory_segments::MemorySegmentManager; use super::OUTPUT_BUILTIN_NAME; @@ -62,24 +61,10 @@ impl OutputBuiltinRunner { self.base } - pub fn add_validation_rule(&self, _memory: &mut Memory) {} - - pub fn deduce_memory_cell( - &self, - _address: Relocatable, - _memory: &Memory, - ) -> Result, RunnerError> { - Ok(None) - } - pub fn get_allocated_memory_units(&self, _vm: &VirtualMachine) -> Result { Ok(0) } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base) @@ -376,54 +361,6 @@ mod tests { assert_eq!(initial_stack.len(), 1); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = OutputBuiltinRunner::new(true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None),); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = BuiltinRunner::Output(OutputBuiltinRunner::new(true)); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = BuiltinRunner::Output(OutputBuiltinRunner::new(true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = BuiltinRunner::Output(OutputBuiltinRunner::new(true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { @@ -504,7 +441,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_add_validation_rule() { - let builtin = OutputBuiltinRunner::new(true); + let builtin: BuiltinRunner = OutputBuiltinRunner::new(true).into(); let mut vm = vm!(); vm.segments = segments![ @@ -535,8 +472,8 @@ mod tests { fn get_air_private_input() { let builtin: BuiltinRunner = OutputBuiltinRunner::new(true).into(); - let memory = memory![((0, 0), 0), ((0, 1), 1), ((0, 2), 2), ((0, 3), 3)]; - assert!(builtin.air_private_input(&memory).is_empty()); + let segments = segments![((0, 0), 0), ((0, 1), 1), ((0, 2), 2), ((0, 3), 3)]; + assert!(builtin.air_private_input(&segments).is_empty()); } #[test] diff --git a/vm/src/vm/runners/builtin_runner/poseidon.rs b/vm/src/vm/runners/builtin_runner/poseidon.rs index 3bc28ab228..d2a4ce9c18 100644 --- a/vm/src/vm/runners/builtin_runner/poseidon.rs +++ b/vm/src/vm/runners/builtin_runner/poseidon.rs @@ -110,10 +110,6 @@ impl PoseidonBuiltinRunner { Ok(self.cache.borrow().get(&address).map(|x| x.into())) } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base()) @@ -128,43 +124,6 @@ impl PoseidonBuiltinRunner { Ok(div_ceil(used_cells, self.cells_per_instance as usize)) } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(POSEIDON_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(POSEIDON_BUILTIN_NAME)))?; - if self.base as isize != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - POSEIDON_BUILTIN_NAME, - stop_pointer, - self.base, - )))); - } - let stop_ptr = stop_pointer.offset; - let num_instances = self.get_used_instances(segments)?; - let used = num_instances * self.cells_per_instance as usize; - if stop_ptr != used { - return Err(RunnerError::InvalidStopPointer(Box::new(( - POSEIDON_BUILTIN_NAME, - Relocatable::from((self.base as isize, used)), - Relocatable::from((self.base as isize, stop_ptr)), - )))); - } - self.stop_ptr = Some(stop_ptr); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(0); - Ok(pointer) - } - } - pub fn air_private_input(&self, memory: &Memory) -> Vec { let mut private_inputs = vec![]; if let Some(segment) = memory.data.get(self.base) { @@ -231,7 +190,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin = PoseidonBuiltinRunner::new(Some(10), true); + let mut builtin: BuiltinRunner = PoseidonBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -255,7 +214,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = PoseidonBuiltinRunner::new(Some(10), true); + let mut builtin: BuiltinRunner = PoseidonBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -283,7 +242,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_not_included() { - let mut builtin = PoseidonBuiltinRunner::new(Some(10), false); + let mut builtin: BuiltinRunner = PoseidonBuiltinRunner::new(Some(10), false).into(); let mut vm = vm!(); @@ -307,7 +266,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = PoseidonBuiltinRunner::new(Some(10), true); + let mut builtin: BuiltinRunner = PoseidonBuiltinRunner::new(Some(10), true).into(); let mut vm = vm!(); @@ -443,7 +402,7 @@ mod tests { fn get_air_private_input() { let builtin: BuiltinRunner = PoseidonBuiltinRunner::new(None, true).into(); - let memory = memory![ + let segments = segments![ ((0, 0), 0), ((0, 1), 1), ((0, 2), 2), @@ -458,7 +417,7 @@ mod tests { ((0, 11), 11) ]; assert_eq!( - builtin.air_private_input(&memory), + builtin.air_private_input(&segments), (vec![ PrivateInput::PoseidonState(PrivateInputPoseidonState { index: 0, diff --git a/vm/src/vm/runners/builtin_runner/range_check.rs b/vm/src/vm/runners/builtin_runner/range_check.rs index d70e74b9da..64d045f4e1 100644 --- a/vm/src/vm/runners/builtin_runner/range_check.rs +++ b/vm/src/vm/runners/builtin_runner/range_check.rs @@ -13,7 +13,7 @@ use crate::{ relocatable::{MaybeRelocatable, Relocatable}, }, vm::{ - errors::{memory_errors::MemoryError, runner_errors::RunnerError}, + errors::memory_errors::MemoryError, vm_memory::{ memory::{Memory, ValidationRule}, memory_segments::MemorySegmentManager, @@ -23,8 +23,6 @@ use crate::{ use num_traits::Zero; -use super::RANGE_CHECK_BUILTIN_NAME; - // NOTE: the current implementation is based on the bound 0x10000 const _INNER_RC_BOUND: u64 = 1u64 << INNER_RC_BOUND_SHIFT; const INNER_RC_BOUND_SHIFT: u64 = 16; @@ -107,18 +105,6 @@ impl RangeCheckBuiltinRunner { memory.add_validation_rule(self.base, rule); } - pub fn deduce_memory_cell( - &self, - _address: Relocatable, - _memory: &Memory, - ) -> Result, RunnerError> { - Ok(None) - } - - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base) @@ -159,43 +145,6 @@ impl RangeCheckBuiltinRunner { self.get_used_cells(segments) } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(RANGE_CHECK_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(RANGE_CHECK_BUILTIN_NAME)))?; - if self.base as isize != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - RANGE_CHECK_BUILTIN_NAME, - stop_pointer, - self.base, - )))); - } - let stop_ptr = stop_pointer.offset; - let num_instances = self.get_used_instances(segments)?; - let used = num_instances * self.cells_per_instance as usize; - if stop_ptr != used { - return Err(RunnerError::InvalidStopPointer(Box::new(( - RANGE_CHECK_BUILTIN_NAME, - Relocatable::from((self.base as isize, used)), - Relocatable::from((self.base as isize, stop_ptr)), - )))); - } - self.stop_ptr = Some(stop_ptr); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(0); - Ok(pointer) - } - } - pub fn air_private_input(&self, memory: &Memory) -> Vec { let mut private_inputs = vec![]; if let Some(segment) = memory.data.get(self.base) { @@ -214,6 +163,8 @@ mod tests { use super::*; use crate::relocatable; use crate::serde::deserialize_program::BuiltinName; + use crate::vm::errors::runner_errors::RunnerError; + use crate::vm::runners::builtin_runner::RANGE_CHECK_BUILTIN_NAME; use crate::vm::vm_memory::memory::Memory; use crate::{ hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, @@ -242,7 +193,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin = RangeCheckBuiltinRunner::new(Some(10), 12, true); + let mut builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(10), 12, true).into(); let mut vm = vm!(); @@ -266,7 +217,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = RangeCheckBuiltinRunner::new(Some(10), 12, true); + let mut builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(10), 12, true).into(); let mut vm = vm!(); @@ -294,7 +245,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_when_notincluded() { - let mut builtin = RangeCheckBuiltinRunner::new(Some(10), 12, false); + let mut builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(10), 12, false).into(); let mut vm = vm!(); @@ -318,7 +269,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = RangeCheckBuiltinRunner::new(Some(10), 12, true); + let mut builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(Some(10), 12, true).into(); let mut vm = vm!(); @@ -453,54 +404,6 @@ mod tests { assert_eq!(initial_stack.len(), 1); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = RangeCheckBuiltinRunner::new(Some(8), 8, true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None),); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(256), 8, true)); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(256), 8, true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = BuiltinRunner::RangeCheck(RangeCheckBuiltinRunner::new(Some(256), 8, true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_base() { @@ -608,9 +511,9 @@ mod tests { fn get_air_private_input() { let builtin: BuiltinRunner = RangeCheckBuiltinRunner::new(None, 4, true).into(); - let memory = memory![((0, 0), 0), ((0, 1), 1), ((0, 2), 2)]; + let segments = segments![((0, 0), 0), ((0, 1), 1), ((0, 2), 2)]; assert_eq!( - builtin.air_private_input(&memory), + builtin.air_private_input(&segments), (vec![ PrivateInput::Value(PrivateInputValue { index: 0, diff --git a/vm/src/vm/runners/builtin_runner/segment_arena.rs b/vm/src/vm/runners/builtin_runner/segment_arena.rs index 7f45f0e8f3..b505ac3813 100644 --- a/vm/src/vm/runners/builtin_runner/segment_arena.rs +++ b/vm/src/vm/runners/builtin_runner/segment_arena.rs @@ -1,7 +1,4 @@ -use crate::stdlib::boxed::Box; use crate::vm::errors::memory_errors::MemoryError; -use crate::vm::errors::runner_errors::RunnerError; -use crate::vm::vm_memory::memory::Memory; use crate::{ types::relocatable::{MaybeRelocatable, Relocatable}, vm::vm_memory::memory_segments::MemorySegmentManager, @@ -9,8 +6,7 @@ use crate::{ #[cfg(not(feature = "std"))] use alloc::vec::Vec; - -use super::SEGMENT_ARENA_BUILTIN_NAME; +use num_integer::div_ceil; const ARENA_BUILTIN_SIZE: u32 = 3; // The size of the builtin segment at the time of its creation. @@ -19,7 +15,7 @@ const INITIAL_SEGMENT_SIZE: usize = ARENA_BUILTIN_SIZE as usize; #[derive(Debug, Clone)] pub struct SegmentArenaBuiltinRunner { base: Relocatable, - included: bool, + pub(crate) included: bool, pub(crate) cells_per_instance: u32, pub(crate) n_input_cells_per_instance: u32, pub(crate) stop_ptr: Option, @@ -65,60 +61,14 @@ impl SegmentArenaBuiltinRunner { } } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(SEGMENT_ARENA_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(SEGMENT_ARENA_BUILTIN_NAME)))?; - if self.base.segment_index != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - SEGMENT_ARENA_BUILTIN_NAME, - stop_pointer, - self.base.segment_index as usize, - )))); - } - let used = self.get_used_cells(segments).map_err(RunnerError::Memory)?; - if stop_pointer != (self.base + used)? { - return Err(RunnerError::InvalidStopPointer(Box::new(( - SEGMENT_ARENA_BUILTIN_NAME, - (self.base + used)?, - stop_pointer, - )))); - } - self.stop_ptr = Some(stop_pointer.offset); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(self.base.offset); - Ok(pointer) - } - } - pub fn get_used_instances( &self, segments: &MemorySegmentManager, ) -> Result { - self.get_used_cells(segments) - } - - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base.segment_index as usize, self.stop_ptr) - } - - pub fn add_validation_rule(&self, _memory: &mut Memory) {} - - pub fn deduce_memory_cell( - &self, - _address: Relocatable, - _memory: &Memory, - ) -> Result, RunnerError> { - Ok(None) + Ok(div_ceil( + self.get_used_cells(segments)?, + self.cells_per_instance as usize, + )) } pub fn base(&self) -> usize { @@ -142,8 +92,12 @@ fn gen_arg(segments: &mut MemorySegmentManager, data: &[MaybeRelocatable; 3]) -> #[cfg(test)] mod tests { use super::*; + use crate::vm::errors::runner_errors::RunnerError; + use crate::vm::runners::builtin_runner::SEGMENT_ARENA_BUILTIN_NAME; use crate::vm::vm_core::VirtualMachine; use crate::{relocatable, utils::test_utils::*, vm::runners::builtin_runner::BuiltinRunner}; + #[cfg(not(feature = "std"))] + use alloc::boxed::Box; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; @@ -186,7 +140,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = SegmentArenaBuiltinRunner::new(true); + let mut builtin: BuiltinRunner = SegmentArenaBuiltinRunner::new(true).into(); let mut vm = vm!(); @@ -213,7 +167,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_valid() { - let mut builtin = SegmentArenaBuiltinRunner::new(false); + let mut builtin: BuiltinRunner = SegmentArenaBuiltinRunner::new(false).into(); let mut vm = vm!(); @@ -261,7 +215,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = SegmentArenaBuiltinRunner::new(true); + let mut builtin: BuiltinRunner = SegmentArenaBuiltinRunner::new(true).into(); let mut vm = vm!(); @@ -321,43 +275,6 @@ mod tests { assert_eq!(initial_stack.len(), 1); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = SegmentArenaBuiltinRunner::new(true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None),); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = BuiltinRunner::SegmentArena(SegmentArenaBuiltinRunner::new(true)); - let vm = vm!(); - - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![]),); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = BuiltinRunner::SegmentArena(SegmentArenaBuiltinRunner::new(true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = BuiltinRunner::SegmentArena(SegmentArenaBuiltinRunner::new(true)); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![]),); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { @@ -411,8 +328,8 @@ mod tests { let builtin = BuiltinRunner::SegmentArena(SegmentArenaBuiltinRunner::new(true)); let mut memory_segment_manager = MemorySegmentManager::new(); memory_segment_manager.segment_used_sizes = Some(vec![6]); - - assert_eq!(builtin.get_used_instances(&memory_segment_manager), Ok(3)); + // (SIZE(6) - INITIAL_SIZE(3)) / CELLS_PER_INSTANCE(3) + assert_eq!(builtin.get_used_instances(&memory_segment_manager), Ok(1)); } #[test] @@ -436,14 +353,6 @@ mod tests { ); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn test_add_validation_rule() { - let builtin = SegmentArenaBuiltinRunner::new(true); - let mut vm = vm!(); - builtin.add_validation_rule(&mut vm.segments.memory); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn initial_stackincluded_test() { @@ -501,7 +410,7 @@ mod tests { fn get_air_private_input() { let builtin: BuiltinRunner = SegmentArenaBuiltinRunner::new(true).into(); - let memory = memory![((0, 0), 0), ((0, 1), 1), ((0, 2), 2), ((0, 3), 3)]; - assert!(builtin.air_private_input(&memory).is_empty()); + let segments = segments![((0, 0), 0), ((0, 1), 1), ((0, 2), 2), ((0, 3), 3)]; + assert!(builtin.air_private_input(&segments).is_empty()); } } diff --git a/vm/src/vm/runners/builtin_runner/signature.rs b/vm/src/vm/runners/builtin_runner/signature.rs index d854afa05a..3d5fa6ef45 100644 --- a/vm/src/vm/runners/builtin_runner/signature.rs +++ b/vm/src/vm/runners/builtin_runner/signature.rs @@ -12,7 +12,7 @@ use crate::{ relocatable::{MaybeRelocatable, Relocatable}, }, vm::{ - errors::{memory_errors::MemoryError, runner_errors::RunnerError}, + errors::memory_errors::MemoryError, vm_memory::{ memory::{Memory, ValidationRule}, memory_segments::MemorySegmentManager, @@ -33,8 +33,6 @@ lazy_static! { .unwrap(); } -use super::SIGNATURE_BUILTIN_NAME; - #[derive(Debug, Clone)] pub struct SignatureBuiltinRunner { pub(crate) included: bool, @@ -156,22 +154,10 @@ impl SignatureBuiltinRunner { memory.add_validation_rule(self.base, rule); } - pub fn deduce_memory_cell( - &self, - _address: Relocatable, - _memory: &Memory, - ) -> Result, RunnerError> { - Ok(None) - } - pub fn ratio(&self) -> Option { self.ratio } - pub fn get_memory_segment_addresses(&self) -> (usize, Option) { - (self.base, self.stop_ptr) - } - pub fn get_used_cells(&self, segments: &MemorySegmentManager) -> Result { segments .get_segment_used_size(self.base) @@ -186,43 +172,6 @@ impl SignatureBuiltinRunner { Ok(div_ceil(used_cells, self.cells_per_instance as usize)) } - pub fn final_stack( - &mut self, - segments: &MemorySegmentManager, - pointer: Relocatable, - ) -> Result { - if self.included { - let stop_pointer_addr = (pointer - 1) - .map_err(|_| RunnerError::NoStopPointer(Box::new(SIGNATURE_BUILTIN_NAME)))?; - let stop_pointer = segments - .memory - .get_relocatable(stop_pointer_addr) - .map_err(|_| RunnerError::NoStopPointer(Box::new(SIGNATURE_BUILTIN_NAME)))?; - if self.base as isize != stop_pointer.segment_index { - return Err(RunnerError::InvalidStopPointerIndex(Box::new(( - SIGNATURE_BUILTIN_NAME, - stop_pointer, - self.base, - )))); - } - let stop_ptr = stop_pointer.offset; - let num_instances = self.get_used_instances(segments)?; - let used = num_instances * self.cells_per_instance as usize; - if stop_ptr != used { - return Err(RunnerError::InvalidStopPointer(Box::new(( - SIGNATURE_BUILTIN_NAME, - Relocatable::from((self.base as isize, used)), - Relocatable::from((self.base as isize, stop_ptr)), - )))); - } - self.stop_ptr = Some(stop_ptr); - Ok(stop_pointer_addr) - } else { - self.stop_ptr = Some(0); - Ok(pointer) - } - } - pub fn get_additional_data(&self) -> BuiltinAdditionalData { // Convert signatures to Felt tuple let signatures: HashMap = self @@ -281,8 +230,11 @@ mod tests { types::instance_definitions::ecdsa_instance_def::EcdsaInstanceDef, utils::test_utils::*, vm::{ - errors::memory_errors::{InsufficientAllocatedCellsError, MemoryError}, - runners::builtin_runner::BuiltinRunner, + errors::{ + memory_errors::{InsufficientAllocatedCellsError, MemoryError}, + runner_errors::RunnerError, + }, + runners::builtin_runner::{BuiltinRunner, SIGNATURE_BUILTIN_NAME}, vm_core::VirtualMachine, vm_memory::{memory::Memory, memory_segments::MemorySegmentManager}, }, @@ -326,7 +278,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack() { - let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let mut builtin: BuiltinRunner = + SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); let mut vm = vm!(); @@ -350,7 +303,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_stop_pointer() { - let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let mut builtin: BuiltinRunner = + SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); let mut vm = vm!(); @@ -378,7 +332,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_error_non_relocatable() { - let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let mut builtin: BuiltinRunner = + SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); let mut vm = vm!(); @@ -399,63 +354,6 @@ mod tests { ); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_segment_addresses() { - let builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None)); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_missing_segment_used_sizes() { - let builtin = BuiltinRunner::Signature(SignatureBuiltinRunner::new( - &EcdsaInstanceDef::default(), - true, - )); - let vm = vm!(); - - assert_eq!( - builtin.get_memory_accesses(&vm), - Err(MemoryError::MissingSegmentUsedSizes), - ); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses_empty() { - let builtin = BuiltinRunner::Signature(SignatureBuiltinRunner::new( - &EcdsaInstanceDef::default(), - true, - )); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![0]); - assert_eq!(builtin.get_memory_accesses(&vm), Ok(vec![])); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn get_memory_accesses() { - let builtin = BuiltinRunner::Signature(SignatureBuiltinRunner::new( - &EcdsaInstanceDef::default(), - true, - )); - let mut vm = vm!(); - - vm.segments.segment_used_sizes = Some(vec![4]); - assert_eq!( - builtin.get_memory_accesses(&vm), - Ok(vec![ - (builtin.base() as isize, 0).into(), - (builtin.base() as isize, 1).into(), - (builtin.base() as isize, 2).into(), - (builtin.base() as isize, 3).into(), - ]), - ); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_used_cells_missing_segment_used_sizes() { @@ -521,7 +419,8 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn deduce_memory_cell_test() { let memory = Memory::new(); - let builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let builtin: BuiltinRunner = + SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); let result = builtin.deduce_memory_cell(Relocatable::from((0, 5)), &memory); assert_eq!(result, Ok(None)); } @@ -540,23 +439,6 @@ mod tests { assert_eq!(builtin.base(), 0); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn test_get_memory_segment_addresses() { - let builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); - - assert_eq!(builtin.get_memory_segment_addresses(), (0, None)); - } - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn deduce_memory_cell() { - let memory = Memory::new(); - let builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); - let result = builtin.deduce_memory_cell(Relocatable::from((0, 5)), &memory); - assert_eq!(result, Ok(None)); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_allocated_memory_min_step_not_reached() { @@ -598,7 +480,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_invalid_stop_pointer() { - let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let mut builtin: BuiltinRunner = + SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); let mut vm = vm!(); vm.segments = segments![((0, 0), (1, 0))]; assert_eq!( @@ -614,7 +497,8 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn final_stack_no_used_instances() { - let mut builtin = SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true); + let mut builtin: BuiltinRunner = + SignatureBuiltinRunner::new(&EcdsaInstanceDef::default(), true).into(); let mut vm = vm!(); vm.segments = segments![((0, 0), (0, 0))]; assert_eq!( diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index af4048697a..f0482d4724 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -54,10 +54,12 @@ use num_integer::div_rem; use num_traits::{ToPrimitive, Zero}; use serde::{Deserialize, Serialize}; +use super::builtin_runner::ModBuiltinRunner; use super::{ builtin_runner::{KeccakBuiltinRunner, PoseidonBuiltinRunner}, cairo_pie::{self, CairoPie, CairoPieMetadata, CairoPieVersion}, }; +use crate::types::instance_definitions::mod_instance_def::ModInstanceDef; #[derive(Clone, Debug, Eq, PartialEq)] pub enum CairoArg { @@ -259,6 +261,8 @@ impl CairoRunner { BuiltinName::ec_op, BuiltinName::keccak, BuiltinName::poseidon, + BuiltinName::add_mod, + BuiltinName::mul_mod, ]; if !is_subsequence(&self.program.builtins, &builtin_ordered_list) { return Err(RunnerError::DisorderedBuiltins); @@ -329,6 +333,18 @@ impl CairoRunner { .push(PoseidonBuiltinRunner::new(instance_def.ratio, included).into()); } } + if let Some(instance_def) = self.layout.builtins.add_mod.as_ref() { + let included = program_builtins.remove(&BuiltinName::add_mod); + if included || self.is_proof_mode() { + builtin_runners.push(ModBuiltinRunner::new_add_mod(instance_def, included).into()); + } + } + if let Some(instance_def) = self.layout.builtins.mul_mod.as_ref() { + let included = program_builtins.remove(&BuiltinName::mul_mod); + if included || self.is_proof_mode() { + builtin_runners.push(ModBuiltinRunner::new_mul_mod(instance_def, included).into()); + } + } if !program_builtins.is_empty() && !allow_missing_builtins { return Err(RunnerError::NoBuiltinForInstance(Box::new(( program_builtins.iter().map(|n| n.name()).collect(), @@ -400,6 +416,14 @@ impl CairoRunner { .push(SegmentArenaBuiltinRunner::new(true).into()) } } + BuiltinName::add_mod => vm.builtin_runners.push( + ModBuiltinRunner::new_add_mod(&ModInstanceDef::new(Some(1), 1, 96), true) + .into(), + ), + BuiltinName::mul_mod => vm.builtin_runners.push( + ModBuiltinRunner::new_mul_mod(&ModInstanceDef::new(Some(1), 1, 96), true) + .into(), + ), } } @@ -1089,6 +1113,7 @@ impl CairoRunner { .finalize(Some(size), builtin_runner.base(), None) } } + vm.segments.finalize_zero_segment(); self.segments_finalized = true; Ok(()) } @@ -1413,10 +1438,7 @@ impl CairoRunner { pub fn get_air_private_input(&self, vm: &VirtualMachine) -> AirPrivateInput { let mut private_inputs = HashMap::new(); for builtin in vm.builtin_runners.iter() { - private_inputs.insert( - builtin.name(), - builtin.air_private_input(&vm.segments.memory), - ); + private_inputs.insert(builtin.name(), builtin.air_private_input(&vm.segments)); } AirPrivateInput(private_inputs) } diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index a1e74d87a4..afbf384ddf 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -19,7 +19,9 @@ use crate::{ exec_scope_errors::ExecScopeError, memory_errors::MemoryError, vm_errors::VirtualMachineError, }, - runners::builtin_runner::{BuiltinRunner, RangeCheckBuiltinRunner, SignatureBuiltinRunner}, + runners::builtin_runner::{ + BuiltinRunner, OutputBuiltinRunner, RangeCheckBuiltinRunner, SignatureBuiltinRunner, + }, trace::trace_entry::TraceEntry, vm_memory::memory_segments::MemorySegmentManager, }, @@ -32,7 +34,9 @@ use core::num::NonZeroUsize; use num_traits::{ToPrimitive, Zero}; use super::errors::runner_errors::RunnerError; -use super::runners::builtin_runner::{OutputBuiltinRunner, OUTPUT_BUILTIN_NAME}; +use super::runners::builtin_runner::{ + ModBuiltinRunner, ADD_MOD_BUILTIN_NAME, MUL_MOD_BUILTIN_NAME, OUTPUT_BUILTIN_NAME, +}; const MAX_TRACEBACK_ENTRIES: u32 = 20; @@ -1121,6 +1125,53 @@ impl VirtualMachine { } } } + + /// Fetches add_mod & mul_mod builtins according to the optional arguments and executes `fill_memory` + /// Returns an error if either of this optional parameters is true but the corresponding builtin is not present + /// Verifies that both builtin's (if present) batch sizes match the batch_size arg if set + // This method is needed as running `fill_memory` direclty from outside the vm struct would require cloning the builtin runners to avoid double borrowing + pub fn mod_builtin_fill_memory( + &mut self, + add_mod_ptr_n: Option<(Relocatable, usize)>, + mul_mod_ptr_n: Option<(Relocatable, usize)>, + batch_size: Option, + ) -> Result<(), VirtualMachineError> { + let fetch_builtin_params = |mod_params: Option<(Relocatable, usize)>, + mod_name: &'static str| + -> Result< + Option<(Relocatable, &ModBuiltinRunner, usize)>, + VirtualMachineError, + > { + if let Some((ptr, n)) = mod_params { + let mod_builtin = self + .builtin_runners + .iter() + .find_map(|b| match b { + BuiltinRunner::Mod(b) if b.name() == mod_name => Some(b), + _ => None, + }) + .ok_or_else(|| VirtualMachineError::NoModBuiltin(mod_name))?; + if let Some(batch_size) = batch_size { + if mod_builtin.batch_size() != batch_size { + return Err(VirtualMachineError::ModBuiltinBatchSize(Box::new(( + mod_builtin.name(), + batch_size, + )))); + } + } + Ok(Some((ptr, mod_builtin, n))) + } else { + Ok(None) + } + }; + + ModBuiltinRunner::fill_memory( + &mut self.segments.memory, + fetch_builtin_params(add_mod_ptr_n, ADD_MOD_BUILTIN_NAME)?, + fetch_builtin_params(mul_mod_ptr_n, MUL_MOD_BUILTIN_NAME)?, + ) + .map_err(VirtualMachineError::RunnerError) + } } pub struct VirtualMachineBuilder { diff --git a/vm/src/vm/vm_memory/memory.rs b/vm/src/vm/vm_memory/memory.rs index f50d5c11cd..178387f504 100644 --- a/vm/src/vm/vm_memory/memory.rs +++ b/vm/src/vm/vm_memory/memory.rs @@ -1,5 +1,6 @@ use crate::stdlib::{borrow::Cow, collections::HashMap, fmt, prelude::*}; +use crate::types::errors::math_errors::MathError; use crate::vm::runners::cairo_pie::CairoPieMemory; use crate::Felt252; use crate::{ @@ -284,6 +285,14 @@ impl Memory { } } + /// Gets the value from memory address as a usize. + /// Returns an Error if the value at the memory address is missing not a Felt252, or can't be converted to usize. + pub fn get_usize(&self, key: Relocatable) -> Result { + let felt = self.get_integer(key)?.into_owned(); + felt.to_usize() + .ok_or_else(|| MemoryError::Math(MathError::Felt252ToUsizeConversion(Box::new(felt)))) + } + /// Gets the value from memory address as a Relocatable value. /// Returns an Error if the value at the memory address is missing or not a Relocatable. pub fn get_relocatable(&self, key: Relocatable) -> Result { @@ -516,6 +525,21 @@ impl Memory { .count(), ) } + + // Inserts a value into memory & inmediately marks it as accessed if insertion was succesful + // Used by ModBuiltinRunner, as it accesses memory outside of it's segment when operating + pub(crate) fn insert_as_accessed( + &mut self, + key: Relocatable, + val: V, + ) -> Result<(), MemoryError> + where + MaybeRelocatable: From, + { + self.insert(key, val)?; + self.mark_as_accessed(key); + Ok(()) + } } impl From<&Memory> for CairoPieMemory { diff --git a/vm/src/vm/vm_memory/memory_segments.rs b/vm/src/vm/vm_memory/memory_segments.rs index aa8709419b..f9886da1a5 100644 --- a/vm/src/vm/vm_memory/memory_segments.rs +++ b/vm/src/vm/vm_memory/memory_segments.rs @@ -282,8 +282,6 @@ impl MemorySegmentManager { .insert(segment_index, public_memory.cloned().unwrap_or_default()); } - // TODO: remove allow - #[allow(unused)] // Creates the zero segment if it wasn't previously created // Fills the segment with the value 0 until size is reached // Returns the index of the zero segment @@ -302,8 +300,6 @@ impl MemorySegmentManager { self.zero_segment_index } - // TODO: remove allow - #[allow(unused)] // Finalizes the zero segment and clears it's tracking data from the manager pub(crate) fn finalize_zero_segment(&mut self) { if !self.zero_segment_index.is_zero() {