diff --git a/.gitignore b/.gitignore index 98e6148071..a6f4053583 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ **/*.trace **/*.memory **/*.air_public_input +**/*.air_private_input **/*.swp bench/results .python-version diff --git a/CHANGELOG.md b/CHANGELOG.md index d17d848d28..8306723148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: Implement air_private_input [#1552](https://github.com/lambdaclass/cairo-vm/pull/1552) + * feat: Add `proof_mode` flag to `cairo1-run` [#1537] (https://github.com/lambdaclass/cairo-vm/pull/1537) * The cairo1-run crate no longer compiles and executes in proof_mode by default * Add flag `proof_mode` to cairo1-run crate. Activating this flag will enable proof_mode compilation and execution diff --git a/Makefile b/Makefile index 47f0f36e23..e39d995130 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ endif deps deps-macos cargo-deps build run check test clippy coverage benchmark \ compare_benchmarks_deps compare_benchmarks docs clean \ compare_vm_output compare_trace_memory compare_trace compare_memory \ - compare_trace_memory_proof compare_all_proof compare_trace_proof compare_memory_proof compare_air_public_input \ + compare_trace_memory_proof compare_all_proof compare_trace_proof compare_memory_proof compare_air_public_input compare_air_private_input\ cairo_bench_programs cairo_proof_programs cairo_test_programs cairo_1_test_contracts cairo_2_test_contracts \ cairo_trace cairo-vm_trace cairo_proof_trace cairo-vm_proof_trace \ fuzzer-deps fuzzer-run-cairo-compiled fuzzer-run-hint-diff build-cairo-lang hint-accountant \ @@ -37,10 +37,12 @@ COMPILED_PROOF_TESTS:=$(patsubst $(TEST_PROOF_DIR)/%.cairo, $(TEST_PROOF_DIR)/%. CAIRO_MEM_PROOF:=$(patsubst $(TEST_PROOF_DIR)/%.json, $(TEST_PROOF_DIR)/%.memory, $(COMPILED_PROOF_TESTS)) CAIRO_TRACE_PROOF:=$(patsubst $(TEST_PROOF_DIR)/%.json, $(TEST_PROOF_DIR)/%.trace, $(COMPILED_PROOF_TESTS)) CAIRO_AIR_PUBLIC_INPUT:=$(patsubst $(TEST_PROOF_DIR)/%.json, $(TEST_PROOF_DIR)/%.air_public_input, $(COMPILED_PROOF_TESTS)) +CAIRO_AIR_PRIVATE_INPUT:=$(patsubst $(TEST_PROOF_DIR)/%.json, $(TEST_PROOF_DIR)/%.air_private_input, $(COMPILED_PROOF_TESTS)) CAIRO_RS_MEM_PROOF:=$(patsubst $(TEST_PROOF_DIR)/%.json, $(TEST_PROOF_DIR)/%.rs.memory, $(COMPILED_PROOF_TESTS)) CAIRO_RS_TRACE_PROOF:=$(patsubst $(TEST_PROOF_DIR)/%.json, $(TEST_PROOF_DIR)/%.rs.trace, $(COMPILED_PROOF_TESTS)) CAIRO_RS_AIR_PUBLIC_INPUT:=$(patsubst $(TEST_PROOF_DIR)/%.json, $(TEST_PROOF_DIR)/%.rs.air_public_input, $(COMPILED_PROOF_TESTS)) +CAIRO_RS_AIR_PRIVATE_INPUT:=$(patsubst $(TEST_PROOF_DIR)/%.json, $(TEST_PROOF_DIR)/%.rs.air_private_input, $(COMPILED_PROOF_TESTS)) PROOF_BENCH_DIR=cairo_programs/benchmarks PROOF_BENCH_FILES:=$(wildcard $(PROOF_BENCH_DIR)/*.cairo) @@ -49,11 +51,11 @@ PROOF_COMPILED_BENCHES:=$(patsubst $(PROOF_BENCH_DIR)/%.cairo, $(PROOF_BENCH_DIR $(TEST_PROOF_DIR)/%.json: $(TEST_PROOF_DIR)/%.cairo cairo-compile --cairo_path="$(TEST_PROOF_DIR):$(PROOF_BENCH_DIR)" $< --output $@ --proof_mode -$(TEST_PROOF_DIR)/%.rs.trace $(TEST_PROOF_DIR)/%.rs.memory $(TEST_PROOF_DIR)/%.rs.air_public_input: $(TEST_PROOF_DIR)/%.json $(RELBIN) - cargo llvm-cov run -p cairo-vm-cli --release --no-report -- --layout starknet_with_keccak --proof_mode $< --trace_file $@ --memory_file $(@D)/$(*F).rs.memory --air_public_input $(@D)/$(*F).rs.air_public_input +$(TEST_PROOF_DIR)/%.rs.trace $(TEST_PROOF_DIR)/%.rs.memory $(TEST_PROOF_DIR)/%.rs.air_public_input $(TEST_PROOF_DIR)/%.rs.air_private_input: $(TEST_PROOF_DIR)/%.json $(RELBIN) + cargo llvm-cov run -p cairo-vm-cli --release --no-report -- --layout starknet_with_keccak --proof_mode $< --trace_file $(@D)/$(*F).rs.trace --memory_file $(@D)/$(*F).rs.memory --air_public_input $(@D)/$(*F).rs.air_public_input --air_private_input $(@D)/$(*F).rs.air_private_input -$(TEST_PROOF_DIR)/%.trace $(TEST_PROOF_DIR)/%.memory $(TEST_PROOF_DIR)/%.air_public_input: $(TEST_PROOF_DIR)/%.json - cairo-run --layout starknet_with_keccak --proof_mode --program $< --trace_file $(@D)/$(*F).trace --air_public_input $(@D)/$(*F).air_public_input --memory_file $(@D)/$(*F).memory +$(TEST_PROOF_DIR)/%.trace $(TEST_PROOF_DIR)/%.memory $(TEST_PROOF_DIR)/%.air_public_input $(TEST_PROOF_DIR)/%.air_private_input: $(TEST_PROOF_DIR)/%.json + cairo-run --layout starknet_with_keccak --proof_mode --program $< --trace_file $(@D)/$(*F).trace --air_public_input $(@D)/$(*F).air_public_input --memory_file $(@D)/$(*F).memory --air_private_input $(@D)/$(*F).air_private_input $(PROOF_BENCH_DIR)/%.json: $(PROOF_BENCH_DIR)/%.cairo cairo-compile --cairo_path="$(TEST_PROOF_DIR):$(PROOF_BENCH_DIR)" $< --output $@ --proof_mode @@ -289,8 +291,8 @@ compare_memory: $(CAIRO_RS_MEM) $(CAIRO_MEM) compare_trace_memory_proof: $(COMPILED_PROOF_TESTS) $(CAIRO_RS_TRACE_PROOF) $(CAIRO_TRACE_PROOF) $(CAIRO_RS_MEM_PROOF) $(CAIRO_MEM_PROOF) cd vm/src/tests; ./compare_vm_state.sh trace memory proof_mode -compare_all_proof: $(COMPILED_PROOF_TESTS) $(CAIRO_RS_TRACE_PROOF) $(CAIRO_TRACE_PROOF) $(CAIRO_RS_MEM_PROOF) $(CAIRO_MEM_PROOF) $(CAIRO_RS_AIR_PUBLIC_INPUT) $(CAIRO_AIR_PUBLIC_INPUT) - cd vm/src/tests; ./compare_vm_state.sh trace memory proof_mode air_public_input +compare_all_proof: $(COMPILED_PROOF_TESTS) $(CAIRO_RS_TRACE_PROOF) $(CAIRO_TRACE_PROOF) $(CAIRO_RS_MEM_PROOF) $(CAIRO_MEM_PROOF) $(CAIRO_RS_AIR_PUBLIC_INPUT) $(CAIRO_AIR_PUBLIC_INPUT) $(CAIRO_RS_AIR_PRIVATE_INPUT) $(CAIRO_AIR_PRIVATE_INPUT) + cd vm/src/tests; ./compare_vm_state.sh trace memory proof_mode air_public_input air_private_input compare_trace_proof: $(CAIRO_RS_TRACE_PROOF) $(CAIRO_TRACE_PROOF) cd vm/src/tests; ./compare_vm_state.sh trace proof_mode @@ -301,6 +303,9 @@ compare_memory_proof: $(CAIRO_RS_MEM_PROOF) $(CAIRO_MEM_PROOF) compare_air_public_input: $(CAIRO_RS_AIR_PUBLIC_INPUT) $(CAIRO_AIR_PUBLIC_INPUT) cd vm/src/tests; ./compare_vm_state.sh memory proof_mode air_public_input +compare_air_private_input: $(CAIRO_RS_AIR_PRIVATE_INPUT) $(CAIRO_AIR_PRIVATE_INPUT) + cd vm/src/tests; ./compare_vm_state.sh memory proof_mode air_private_input + # Run with nightly enable the `doc_cfg` feature wich let us provide clear explaination about which parts of the code are behind a feature flag docs: RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --verbose --release --locked --no-deps --all-features --open @@ -318,6 +323,7 @@ clean: rm -f $(TEST_PROOF_DIR)/*.memory rm -f $(TEST_PROOF_DIR)/*.trace rm -f $(TEST_PROOF_DIR)/*.air_public_input + rm -f $(TEST_PROOF_DIR)/*.air_private_input rm -rf cairo-vm-env rm -rf cairo-vm-pypy-env rm -rf cairo diff --git a/README.md b/README.md index 52c2dd4043..1e45ad3f50 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,8 @@ The cairo-vm-cli supports the following optional arguments: - `--air_public_input `: Receives the name of a file and outputs the AIR public inputs into it. Can only be used if proof_mode is also enabled. +- `--air_private_input `: Receives the name of a file and outputs the AIR private inputs into it. Can only be used if proof_mode, trace_file & memory_file are also enabled. + For example, to obtain the air public inputs from a fibonacci program run, we can run : ```bash diff --git a/cairo-vm-cli/src/main.rs b/cairo-vm-cli/src/main.rs index 6bfd81db4c..a24ef7bb92 100644 --- a/cairo-vm-cli/src/main.rs +++ b/cairo-vm-cli/src/main.rs @@ -40,6 +40,8 @@ struct Args { secure_run: Option, #[clap(long = "air_public_input")] air_public_input: Option, + #[clap(long = "air_private_input")] + air_private_input: Option, } fn validate_layout(value: &str) -> Result { @@ -119,6 +121,30 @@ fn run(args: impl Iterator) -> Result<(), Error> { return Err(Error::Cli(error)); } + if args.air_private_input.is_some() && !args.proof_mode { + let error = Args::command().error( + clap::error::ErrorKind::ArgumentConflict, + "--air_private_input can only be used in proof_mode.", + ); + return Err(Error::Cli(error)); + } + + if args.air_private_input.is_some() && args.trace_file.is_none() { + let error = Args::command().error( + clap::error::ErrorKind::ArgumentConflict, + "--trace_file must be set when --air_private_input is set.", + ); + return Err(Error::Cli(error)); + } + + if args.air_private_input.is_some() && args.memory_file.is_none() { + let error = Args::command().error( + clap::error::ErrorKind::ArgumentConflict, + "--memory_file must be set when --air_private_input is set.", + ); + return Err(Error::Cli(error)); + } + let trace_enabled = args.trace_file.is_some() || args.air_public_input.is_some(); let mut hint_executor = BuiltinHintProcessor::new_empty(); let cairo_run_config = cairo_run::CairoRunConfig { @@ -148,7 +174,7 @@ fn run(args: impl Iterator) -> Result<(), Error> { print!("{output_buffer}"); } - if let Some(trace_path) = args.trace_file { + if let Some(ref trace_path) = args.trace_file { let relocated_trace = cairo_runner .relocated_trace .as_ref() @@ -162,7 +188,7 @@ fn run(args: impl Iterator) -> Result<(), Error> { trace_writer.flush()?; } - if let Some(memory_path) = args.memory_file { + if let Some(ref memory_path) = args.memory_file { let memory_file = std::fs::File::create(memory_path)?; let mut memory_writer = FileWriter::new(io::BufWriter::with_capacity(5 * 1024 * 1024, memory_file)); @@ -176,6 +202,31 @@ fn run(args: impl Iterator) -> Result<(), Error> { std::fs::write(file_path, json)?; } + if let (Some(file_path), Some(ref trace_file), Some(ref memory_file)) = + (args.air_private_input, args.trace_file, args.memory_file) + { + // Get absolute paths of trace_file & memory_file + let trace_path = trace_file + .as_path() + .canonicalize() + .unwrap_or(trace_file.clone()) + .to_string_lossy() + .to_string(); + let memory_path = memory_file + .as_path() + .canonicalize() + .unwrap_or(memory_file.clone()) + .to_string_lossy() + .to_string(); + + let json = cairo_runner + .get_air_private_input(&vm) + .to_serializable(trace_path, memory_path) + .serialize_json() + .map_err(PublicInputError::Serde)?; + std::fs::write(file_path, json)?; + } + Ok(()) } @@ -212,6 +263,27 @@ mod tests { assert_matches!(run(args), Err(Error::Cli(_))); } + #[rstest] + #[case(["cairo-vm-cli", "../cairo_programs/fibonacci.json", "--air_private_input", "/dev/null", "--proof_mode", "--memory_file", "/dev/null"].as_slice())] + fn test_run_air_private_input_no_trace(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Err(Error::Cli(_))); + } + + #[rstest] + #[case(["cairo-vm-cli", "../cairo_programs/fibonacci.json", "--air_private_input", "/dev/null", "--proof_mode", "--trace_file", "/dev/null"].as_slice())] + fn test_run_air_private_input_no_memory(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Err(Error::Cli(_))); + } + + #[rstest] + #[case(["cairo-vm-cli", "../cairo_programs/fibonacci.json", "--air_private_input", "/dev/null", "--trace_file", "/dev/null", "--memory_file", "/dev/null"].as_slice())] + fn test_run_air_private_input_no_proof(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Err(Error::Cli(_))); + } + #[rstest] fn test_run_ok( #[values(None, @@ -234,16 +306,17 @@ mod tests { #[values(false, true)] print_output: bool, #[values(false, true)] entrypoint: bool, #[values(false, true)] air_public_input: bool, + #[values(false, true)] air_private_input: bool, ) { let mut args = vec!["cairo-vm-cli".to_string()]; if let Some(layout) = layout { args.extend_from_slice(&["--layout".to_string(), layout.to_string()]); } if air_public_input { - args.extend_from_slice(&[ - "--air_public_input".to_string(), - "air_input.pub".to_string(), - ]); + args.extend_from_slice(&["--air_public_input".to_string(), "/dev/null".to_string()]); + } + if air_private_input { + args.extend_from_slice(&["--air_private_input".to_string(), "/dev/null".to_string()]); } if proof_mode { trace_file = true; @@ -266,7 +339,9 @@ mod tests { } args.push("../cairo_programs/proof_programs/fibonacci.json".to_string()); - if air_public_input && !proof_mode { + if air_public_input && !proof_mode + || (air_private_input && (!proof_mode || !trace_file || !memory_file)) + { assert_matches!(run(args.into_iter()), Err(_)); } else { assert_matches!(run(args.into_iter()), Ok(_)); diff --git a/fuzzer/Cargo.lock b/fuzzer/Cargo.lock index 5d9996922c..236551e8c5 100644 --- a/fuzzer/Cargo.lock +++ b/fuzzer/Cargo.lock @@ -160,7 +160,7 @@ checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "cairo-vm" -version = "0.9.1" +version = "1.0.0-rc0" dependencies = [ "anyhow", "arbitrary", diff --git a/vm/src/air_private_input.rs b/vm/src/air_private_input.rs new file mode 100644 index 0000000000..4ea30d0d44 --- /dev/null +++ b/vm/src/air_private_input.rs @@ -0,0 +1,142 @@ +use crate::{ + stdlib::{ + collections::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, + }, +}; +use serde::{Deserialize, Serialize}; + +use crate::Felt252; + +// Serializable format, matches the file output of the python implementation +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct AirPrivateInputSerializable { + trace_path: String, + memory_path: String, + pedersen: Vec, + range_check: Vec, + ecdsa: Vec, + bitwise: Vec, + ec_op: Vec, + keccak: Vec, + poseidon: Vec, +} + +// Contains only builtin public inputs, useful for library users +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AirPrivateInput(pub HashMap<&'static str, Vec>); + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(untagged)] +pub enum PrivateInput { + Value(PrivateInputValue), + Pair(PrivateInputPair), + EcOp(PrivateInputEcOp), + PoseidonState(PrivateInputPoseidonState), + KeccakState(PrivateInputKeccakState), + Signature(PrivateInputSignature), +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct PrivateInputValue { + pub index: usize, + pub value: Felt252, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct PrivateInputPair { + pub index: usize, + pub x: Felt252, + pub y: Felt252, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct PrivateInputEcOp { + pub index: usize, + pub p_x: Felt252, + pub p_y: Felt252, + pub m: Felt252, + pub q_x: Felt252, + pub q_y: Felt252, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct PrivateInputPoseidonState { + pub index: usize, + pub input_s0: Felt252, + pub input_s1: Felt252, + pub input_s2: Felt252, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct PrivateInputKeccakState { + pub index: usize, + pub input_s0: Felt252, + pub input_s1: Felt252, + pub input_s2: Felt252, + pub input_s3: Felt252, + pub input_s4: Felt252, + pub input_s5: Felt252, + pub input_s6: Felt252, + pub input_s7: Felt252, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct PrivateInputSignature { + pub index: usize, + pub pubkey: Felt252, + pub msg: Felt252, + pub signature_input: SignatureInput, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct SignatureInput { + pub r: Felt252, + pub w: Felt252, +} + +impl AirPrivateInput { + pub fn to_serializable( + &self, + trace_path: String, + memory_path: String, + ) -> AirPrivateInputSerializable { + AirPrivateInputSerializable { + trace_path, + memory_path, + pedersen: self.0.get(HASH_BUILTIN_NAME).cloned().unwrap_or_default(), + range_check: self + .0 + .get(RANGE_CHECK_BUILTIN_NAME) + .cloned() + .unwrap_or_default(), + ecdsa: self + .0 + .get(SIGNATURE_BUILTIN_NAME) + .cloned() + .unwrap_or_default(), + bitwise: self + .0 + .get(BITWISE_BUILTIN_NAME) + .cloned() + .unwrap_or_default(), + ec_op: self.0.get(EC_OP_BUILTIN_NAME).cloned().unwrap_or_default(), + keccak: self.0.get(KECCAK_BUILTIN_NAME).cloned().unwrap_or_default(), + poseidon: self + .0 + .get(POSEIDON_BUILTIN_NAME) + .cloned() + .unwrap_or_default(), + } + } +} + +impl AirPrivateInputSerializable { + pub fn serialize_json(&self) -> Result { + serde_json::to_string_pretty(&self) + } +} diff --git a/vm/src/lib.rs b/vm/src/lib.rs index a1138650a7..88415c121c 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -53,6 +53,7 @@ pub mod stdlib { pub use crate::without_std::*; } +pub mod air_private_input; pub mod air_public_input; pub mod cairo_run; pub mod hint_processor; diff --git a/vm/src/tests/air_private_input_comparator.py b/vm/src/tests/air_private_input_comparator.py new file mode 100755 index 0000000000..92ebf987f3 --- /dev/null +++ b/vm/src/tests/air_private_input_comparator.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +import sys +import json + + +filename1 = sys.argv[1] +filename2 = sys.argv[2] + +with open(filename1, 'r') as cairo_lang_input_file, open(filename2, 'r') as cairo_vm_input_file: + cairo_lang_input = json.load(cairo_lang_input_file) + cairo_vm_input = json.load(cairo_vm_input_file) + # The trace_path & memory_path fields contain the path of each file which will differ + # as we use a different extension to differentiate between python & rust vm outputs + cairo_lang_input["trace_path"] = "" + cairo_lang_input["memory_path"] = "" + + cairo_vm_input["trace_path"] = "" + cairo_vm_input["memory_path"] = "" + + if cairo_lang_input == cairo_vm_input: + + print(f"Comparison succesful for {filename1} vs {filename2}") + else: + print(f"Comparison unsuccesful for {filename1} vs {filename2}") + exit(1) diff --git a/vm/src/tests/compare_vm_state.sh b/vm/src/tests/compare_vm_state.sh index a4ad23b0ab..24388d20bf 100755 --- a/vm/src/tests/compare_vm_state.sh +++ b/vm/src/tests/compare_vm_state.sh @@ -9,6 +9,7 @@ exit_code=0 trace=false memory=false air_public_input=false +air_private_input=false passed_tests=0 failed_tests=0 @@ -26,6 +27,9 @@ for i in $@; do "air_public_input") air_public_input=true echo "Requested air_public_input comparison" ;; + "air_private_input") air_private_input=true + echo "Requested air_private_input comparison" + ;; *) ;; esac @@ -76,6 +80,16 @@ for file in $(ls $tests_path | grep .cairo$ | sed -E 's/\.cairo$//'); do passed_tests=$((passed_tests + 1)) fi fi + + if $air_private_input; then + if ! ./air_private_input_comparator.py $path_file.air_private_input $path_file.rs.air_private_input; then + echo "Air Private Input differs for $file" + exit_code=1 + failed_tests=$((failed_tests + 1)) + else + passed_tests=$((passed_tests + 1)) + fi + fi done if test $failed_tests != 0; then diff --git a/vm/src/types/relocatable.rs b/vm/src/types/relocatable.rs index 353be453ae..e903b1670c 100644 --- a/vm/src/types/relocatable.rs +++ b/vm/src/types/relocatable.rs @@ -320,6 +320,7 @@ impl MaybeRelocatable { } } + // TODO: Check if its more performant to use get_int instead /// Returns a reference to the inner value if it is a Felt252, returns None otherwise. pub fn get_int_ref(&self) -> Option<&Felt252> { match self { @@ -328,6 +329,14 @@ impl MaybeRelocatable { } } + /// Returns the inner value if it is a Felt252, returns None otherwise. + pub fn get_int(&self) -> Option { + match self { + MaybeRelocatable::Int(num) => Some(*num), + MaybeRelocatable::RelocatableValue(_) => None, + } + } + /// Returns the inner value if it is a Relocatable, returns None otherwise. pub fn get_relocatable(&self) -> Option { match self { diff --git a/vm/src/vm/runners/builtin_runner/bitwise.rs b/vm/src/vm/runners/builtin_runner/bitwise.rs index fea49fa89a..825aab3942 100644 --- a/vm/src/vm/runners/builtin_runner/bitwise.rs +++ b/vm/src/vm/runners/builtin_runner/bitwise.rs @@ -1,3 +1,4 @@ +use crate::air_private_input::{PrivateInput, PrivateInputPair}; use crate::stdlib::{boxed::Box, vec::Vec}; use crate::Felt252; use crate::{ @@ -191,6 +192,30 @@ impl BitwiseBuiltinRunner { let used_cells = self.get_used_cells(segments)?; Ok(div_ceil(used_cells, self.cells_per_instance as usize)) } + + pub fn air_private_input(&self, memory: &Memory) -> Vec { + let mut private_inputs = vec![]; + if let Some(segment) = memory.data.get(self.base) { + let segment_len = segment.len(); + for (index, off) in (0..segment_len) + .step_by(CELLS_PER_BITWISE as usize) + .enumerate() + { + // Add the input cells of each bitwise instance to the private inputs + if let (Ok(x), Ok(y)) = ( + memory.get_integer((self.base as isize, off).into()), + memory.get_integer((self.base as isize, off + 1).into()), + ) { + private_inputs.push(PrivateInput::Pair(PrivateInputPair { + index, + x: *x, + y: *y, + })) + } + } + } + private_inputs + } } #[cfg(test)] @@ -594,4 +619,49 @@ mod tests { )); assert_eq!(builtin.get_used_diluted_check_units(50, 25), 250); } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn get_air_private_input() { + let builtin: BuiltinRunner = + BitwiseBuiltinRunner::new(&BitwiseInstanceDef::default(), true).into(); + + let memory = memory![ + ((0, 0), 0), + ((0, 1), 1), + ((0, 2), 2), + ((0, 3), 3), + ((0, 4), 4), + ((0, 5), 5), + ((0, 6), 6), + ((0, 7), 7), + ((0, 8), 8), + ((0, 9), 9), + ((0, 10), 10), + ((0, 11), 11), + ((0, 12), 12), + ((0, 13), 13), + ((0, 14), 14) + ]; + assert_eq!( + builtin.air_private_input(&memory), + (vec![ + PrivateInput::Pair(PrivateInputPair { + index: 0, + x: 0.into(), + y: 1.into() + }), + PrivateInput::Pair(PrivateInputPair { + index: 1, + x: 5.into(), + y: 6.into() + }), + PrivateInput::Pair(PrivateInputPair { + index: 2, + x: 10.into(), + y: 11.into() + }), + ]), + ); + } } diff --git a/vm/src/vm/runners/builtin_runner/ec_op.rs b/vm/src/vm/runners/builtin_runner/ec_op.rs index 938cd390fb..c824f0153d 100644 --- a/vm/src/vm/runners/builtin_runner/ec_op.rs +++ b/vm/src/vm/runners/builtin_runner/ec_op.rs @@ -1,3 +1,4 @@ +use crate::air_private_input::{PrivateInput, PrivateInputEcOp}; use crate::stdlib::{borrow::Cow, prelude::*}; use crate::stdlib::{cell::RefCell, collections::HashMap}; use crate::types::instance_definitions::ec_op_instance_def::{ @@ -260,6 +261,36 @@ impl EcOpBuiltinRunner { m = {m:?}\n Q = {q:?}.") } + + pub fn air_private_input(&self, memory: &Memory) -> Vec { + let mut private_inputs = vec![]; + if let Some(segment) = memory.data.get(self.base) { + let segment_len = segment.len(); + for (index, off) in (0..segment_len) + .step_by(CELLS_PER_EC_OP as usize) + .enumerate() + { + // Add the input cells of each ec_op instance to the private inputs + if let (Ok(p_x), Ok(p_y), Ok(q_x), Ok(q_y), Ok(m)) = ( + memory.get_integer((self.base as isize, off).into()), + memory.get_integer((self.base as isize, off + 1).into()), + memory.get_integer((self.base as isize, off + 2).into()), + memory.get_integer((self.base as isize, off + 3).into()), + memory.get_integer((self.base as isize, off + 4).into()), + ) { + private_inputs.push(PrivateInput::EcOp(PrivateInputEcOp { + index, + p_x: *p_x, + p_y: *p_y, + m: *m, + q_x: *q_x, + q_y: *q_y, + })) + } + } + } + private_inputs + } } #[cfg(test)] @@ -959,4 +990,30 @@ mod tests { Ok(_) => panic!("Expected run to fail"), } } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn get_air_private_input() { + let builtin: BuiltinRunner = + EcOpBuiltinRunner::new(&EcOpInstanceDef::default(), true).into(); + + let memory = memory![ + ((0, 0), 0), + ((0, 1), 1), + ((0, 2), 2), + ((0, 3), 3), + ((0, 4), 4) + ]; + assert_eq!( + builtin.air_private_input(&memory), + (vec![PrivateInput::EcOp(PrivateInputEcOp { + index: 0, + p_x: 0.into(), + p_y: 1.into(), + m: 4.into(), + q_x: 2.into(), + q_y: 3.into(), + })]) + ); + } } diff --git a/vm/src/vm/runners/builtin_runner/hash.rs b/vm/src/vm/runners/builtin_runner/hash.rs index 555f71f473..f8e8fbe333 100644 --- a/vm/src/vm/runners/builtin_runner/hash.rs +++ b/vm/src/vm/runners/builtin_runner/hash.rs @@ -1,3 +1,4 @@ +use crate::air_private_input::{PrivateInput, PrivateInputPair}; use crate::stdlib::{cell::RefCell, prelude::*}; use crate::types::errors::math_errors::MathError; use crate::types::instance_definitions::pedersen_instance_def::{ @@ -187,6 +188,30 @@ impl HashBuiltinRunner { } BuiltinAdditionalData::Hash(verified_addresses) } + + pub fn air_private_input(&self, memory: &Memory) -> Vec { + let mut private_inputs = vec![]; + if let Some(segment) = memory.data.get(self.base) { + let segment_len = segment.len(); + for (index, off) in (0..segment_len) + .step_by(CELLS_PER_HASH as usize) + .enumerate() + { + // Add the input cells of each hash instance to the private inputs + if let (Ok(x), Ok(y)) = ( + memory.get_integer((self.base as isize, off).into()), + memory.get_integer((self.base as isize, off + 1).into()), + ) { + private_inputs.push(PrivateInput::Pair(PrivateInputPair { + index, + x: *x, + y: *y, + })) + } + } + } + private_inputs + } } #[cfg(test)] @@ -547,4 +572,43 @@ mod tests { BuiltinAdditionalData::Hash(verified_addresses) ) } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn get_air_private_input() { + let builtin: BuiltinRunner = HashBuiltinRunner::new(None, true).into(); + + let memory = memory![ + ((0, 0), 0), + ((0, 1), 1), + ((0, 2), 2), + ((0, 3), 3), + ((0, 4), 4), + ((0, 5), 5), + ((0, 6), 6), + ((0, 7), 7), + ((0, 8), 8), + ((0, 9), 9) + ]; + assert_eq!( + builtin.air_private_input(&memory), + (vec![ + PrivateInput::Pair(PrivateInputPair { + index: 0, + x: 0.into(), + y: 1.into() + }), + PrivateInput::Pair(PrivateInputPair { + index: 1, + x: 3.into(), + y: 4.into() + }), + PrivateInput::Pair(PrivateInputPair { + index: 2, + x: 6.into(), + y: 7.into() + }), + ]), + ); + } } diff --git a/vm/src/vm/runners/builtin_runner/keccak.rs b/vm/src/vm/runners/builtin_runner/keccak.rs index cf5c1868db..3eed4a61b6 100644 --- a/vm/src/vm/runners/builtin_runner/keccak.rs +++ b/vm/src/vm/runners/builtin_runner/keccak.rs @@ -1,3 +1,4 @@ +use crate::air_private_input::{PrivateInput, PrivateInputKeccakState}; use crate::math_utils::safe_div_usize; use crate::stdlib::{cell::RefCell, collections::HashMap, prelude::*}; use crate::types::instance_definitions::keccak_instance_def::KeccakInstanceDef; @@ -220,6 +221,51 @@ impl KeccakBuiltinRunner { keccak::f1600(&mut keccak_input); Ok(keccak_input.iter().flat_map(|x| x.to_le_bytes()).collect()) } + + pub fn air_private_input(&self, memory: &Memory) -> Vec { + let mut private_inputs = vec![]; + if let Some(segment) = memory.data.get(self.base) { + let segment_len = segment.len(); + for (index, off) in (0..segment_len) + .step_by(self.cells_per_instance as usize) + .enumerate() + { + // Add the input cells of each keccak instance to the private inputs + if let ( + Ok(input_s0), + Ok(input_s1), + Ok(input_s2), + Ok(input_s3), + Ok(input_s4), + Ok(input_s5), + Ok(input_s6), + Ok(input_s7), + ) = ( + memory.get_integer((self.base as isize, off).into()), + memory.get_integer((self.base as isize, off + 1).into()), + memory.get_integer((self.base as isize, off + 2).into()), + memory.get_integer((self.base as isize, off + 3).into()), + memory.get_integer((self.base as isize, off + 4).into()), + memory.get_integer((self.base as isize, off + 5).into()), + memory.get_integer((self.base as isize, off + 6).into()), + memory.get_integer((self.base as isize, off + 7).into()), + ) { + private_inputs.push(PrivateInput::KeccakState(PrivateInputKeccakState { + index, + input_s0: *input_s0, + input_s1: *input_s1, + input_s2: *input_s2, + input_s3: *input_s3, + input_s4: *input_s4, + input_s5: *input_s5, + input_s6: *input_s6, + input_s7: *input_s7, + })) + } + } + } + private_inputs + } } #[cfg(test)] @@ -680,4 +726,36 @@ mod tests { let output_bytes = KeccakBuiltinRunner::keccak_f(input_bytes); assert_eq!(output_bytes, Ok(expected_output_bytes.to_vec())); } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn get_air_private_input() { + let builtin: BuiltinRunner = + KeccakBuiltinRunner::new(&KeccakInstanceDef::default(), true).into(); + + let memory = memory![ + ((0, 0), 0), + ((0, 1), 1), + ((0, 2), 2), + ((0, 3), 3), + ((0, 4), 4), + ((0, 5), 5), + ((0, 6), 6), + ((0, 7), 7) + ]; + assert_eq!( + builtin.air_private_input(&memory), + (vec![PrivateInput::KeccakState(PrivateInputKeccakState { + index: 0, + input_s0: 0.into(), + input_s1: 1.into(), + input_s2: 2.into(), + input_s3: 3.into(), + input_s4: 4.into(), + input_s5: 5.into(), + input_s6: 6.into(), + input_s7: 7.into() + }),]), + ); + } } diff --git a/vm/src/vm/runners/builtin_runner/mod.rs b/vm/src/vm/runners/builtin_runner/mod.rs index 4b27c80eb4..99778c6333 100644 --- a/vm/src/vm/runners/builtin_runner/mod.rs +++ b/vm/src/vm/runners/builtin_runner/mod.rs @@ -1,3 +1,4 @@ +use crate::air_private_input::PrivateInput; use crate::math_utils::safe_div_usize; use crate::stdlib::prelude::*; use crate::types::relocatable::{MaybeRelocatable, Relocatable}; @@ -483,6 +484,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 { + 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), + _ => vec![], + } + } + #[cfg(test)] pub(crate) fn set_stop_ptr(&mut self, stop_ptr: usize) { match self { diff --git a/vm/src/vm/runners/builtin_runner/output.rs b/vm/src/vm/runners/builtin_runner/output.rs index 75fce41506..1e3bfe3ad4 100644 --- a/vm/src/vm/runners/builtin_runner/output.rs +++ b/vm/src/vm/runners/builtin_runner/output.rs @@ -450,4 +450,13 @@ mod tests { }) ) } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + 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()); + } } diff --git a/vm/src/vm/runners/builtin_runner/poseidon.rs b/vm/src/vm/runners/builtin_runner/poseidon.rs index 75c809cd7f..a668ba324b 100644 --- a/vm/src/vm/runners/builtin_runner/poseidon.rs +++ b/vm/src/vm/runners/builtin_runner/poseidon.rs @@ -1,3 +1,4 @@ +use crate::air_private_input::{PrivateInput, PrivateInputPoseidonState}; use crate::stdlib::{cell::RefCell, collections::HashMap, prelude::*}; use crate::types::errors::math_errors::MathError; use crate::types::instance_definitions::poseidon_instance_def::{ @@ -163,6 +164,32 @@ impl PoseidonBuiltinRunner { 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) { + let segment_len = segment.len(); + for (index, off) in (0..segment_len) + .step_by(CELLS_PER_POSEIDON as usize) + .enumerate() + { + // Add the input cells of each poseidon instance to the private inputs + if let (Ok(input_s0), Ok(input_s1), Ok(input_s2)) = ( + memory.get_integer((self.base as isize, off).into()), + memory.get_integer((self.base as isize, off + 1).into()), + memory.get_integer((self.base as isize, off + 2).into()), + ) { + private_inputs.push(PrivateInput::PoseidonState(PrivateInputPoseidonState { + index, + input_s0: *input_s0, + input_s1: *input_s1, + input_s2: *input_s2, + })) + } + } + } + private_inputs + } } #[cfg(test)] @@ -410,4 +437,42 @@ mod tests { Ok(None) ); } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn get_air_private_input() { + let builtin: BuiltinRunner = PoseidonBuiltinRunner::new(None, true).into(); + + let memory = memory![ + ((0, 0), 0), + ((0, 1), 1), + ((0, 2), 2), + ((0, 3), 3), + ((0, 4), 4), + ((0, 5), 5), + ((0, 6), 6), + ((0, 7), 7), + ((0, 8), 8), + ((0, 9), 9), + ((0, 10), 10), + ((0, 11), 11) + ]; + assert_eq!( + builtin.air_private_input(&memory), + (vec![ + PrivateInput::PoseidonState(PrivateInputPoseidonState { + index: 0, + input_s0: 0.into(), + input_s1: 1.into(), + input_s2: 2.into(), + }), + PrivateInput::PoseidonState(PrivateInputPoseidonState { + index: 1, + input_s0: 6.into(), + input_s1: 7.into(), + input_s2: 8.into(), + }), + ]), + ); + } } diff --git a/vm/src/vm/runners/builtin_runner/range_check.rs b/vm/src/vm/runners/builtin_runner/range_check.rs index 1bee53200a..ba07f6f253 100644 --- a/vm/src/vm/runners/builtin_runner/range_check.rs +++ b/vm/src/vm/runners/builtin_runner/range_check.rs @@ -1,6 +1,9 @@ -use crate::stdlib::{ - cmp::{max, min}, - prelude::*, +use crate::{ + air_private_input::{PrivateInput, PrivateInputValue}, + stdlib::{ + cmp::{max, min}, + prelude::*, + }, }; use crate::Felt252; @@ -192,6 +195,18 @@ impl RangeCheckBuiltinRunner { 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) { + for (index, val) in segment.iter().enumerate() { + if let Some(value) = val.as_ref().and_then(|cell| cell.get_value().get_int()) { + private_inputs.push(PrivateInput::Value(PrivateInputValue { index, value })) + } + } + } + private_inputs + } } #[cfg(test)] @@ -588,4 +603,29 @@ mod tests { vm.segments.segment_used_sizes = Some(vec![1]); assert_eq!(builtin_runner.get_used_perm_range_check_units(&vm), Ok(8)); } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + 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)]; + assert_eq!( + builtin.air_private_input(&memory), + (vec![ + PrivateInput::Value(PrivateInputValue { + index: 0, + value: 0.into(), + }), + PrivateInput::Value(PrivateInputValue { + index: 1, + value: 1.into(), + }), + PrivateInput::Value(PrivateInputValue { + index: 2, + value: 2.into(), + }), + ]), + ); + } } diff --git a/vm/src/vm/runners/builtin_runner/segment_arena.rs b/vm/src/vm/runners/builtin_runner/segment_arena.rs index fd8bd75599..82c9e72aea 100644 --- a/vm/src/vm/runners/builtin_runner/segment_arena.rs +++ b/vm/src/vm/runners/builtin_runner/segment_arena.rs @@ -496,4 +496,13 @@ mod tests { let builtin: BuiltinRunner = SegmentArenaBuiltinRunner::new(true).into(); assert_eq!(builtin.instances_per_component(), 1) } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + 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()); + } } diff --git a/vm/src/vm/runners/builtin_runner/signature.rs b/vm/src/vm/runners/builtin_runner/signature.rs index f5d256103a..d854afa05a 100644 --- a/vm/src/vm/runners/builtin_runner/signature.rs +++ b/vm/src/vm/runners/builtin_runner/signature.rs @@ -1,6 +1,9 @@ +use crate::air_private_input::{PrivateInput, PrivateInputSignature, SignatureInput}; +use crate::math_utils::div_mod; use crate::stdlib::{cell::RefCell, collections::HashMap, prelude::*, rc::Rc}; use crate::types::errors::math_errors::MathError; +use crate::types::instance_definitions::ecdsa_instance_def::CELLS_PER_SIGNATURE; use crate::vm::runners::cairo_pie::BuiltinAdditionalData; use crate::Felt252; use crate::{ @@ -16,9 +19,20 @@ use crate::{ }, }, }; +use lazy_static::lazy_static; +use num_bigint::{BigInt, Sign}; use num_integer::div_ceil; +use num_traits::{Num, One}; use starknet_crypto::{verify, FieldElement, Signature}; +lazy_static! { + static ref EC_ORDER: BigInt = BigInt::from_str_radix( + "3618502788666131213697322783095070105526743751716087489154079457884512865583", + 10 + ) + .unwrap(); +} + use super::SIGNATURE_BUILTIN_NAME; #[derive(Debug, Clone)] @@ -227,6 +241,36 @@ impl SignatureBuiltinRunner { .collect(); BuiltinAdditionalData::Signature(signatures) } + + pub fn air_private_input(&self, memory: &Memory) -> Vec { + let mut private_inputs = vec![]; + for (addr, signature) in self.signatures.borrow().iter() { + if let (Ok(pubkey), Ok(msg)) = (memory.get_integer(*addr), memory.get_integer(addr + 1)) + { + private_inputs.push(PrivateInput::Signature(PrivateInputSignature { + index: addr + .offset + .saturating_sub(self.base) + .checked_div(CELLS_PER_SIGNATURE as usize) + .unwrap_or_default(), + pubkey: *pubkey, + msg: *msg, + signature_input: SignatureInput { + r: Felt252::from_bytes_be(&signature.r.to_bytes_be()), + w: Felt252::from( + &div_mod( + &BigInt::one(), + &BigInt::from_bytes_be(Sign::Plus, &signature.s.to_bytes_be()), + &EC_ORDER, + ) + .unwrap_or_default(), + ), + }, + })) + } + } + private_inputs + } } #[cfg(test)] diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 57b72fe8b7..11cc89dc3c 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -1,4 +1,5 @@ use crate::{ + air_private_input::AirPrivateInput, air_public_input::{PublicInput, PublicInputError}, stdlib::{ any::Any, @@ -1408,6 +1409,17 @@ impl CairoRunner { .ok_or(PublicInputError::NoRangeCheckLimits)?, ) } + + 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), + ); + } + AirPrivateInput(private_inputs) + } } #[derive(Clone, Debug, Eq, PartialEq)] @@ -1510,6 +1522,7 @@ impl MulAssign for ExecutionResources { #[cfg(test)] mod tests { use super::*; + use crate::air_private_input::{PrivateInput, PrivateInputSignature, SignatureInput}; use crate::cairo_run::{cairo_run, CairoRunConfig}; use crate::stdlib::collections::{HashMap, HashSet}; use crate::vm::runners::builtin_runner::{ @@ -5435,4 +5448,45 @@ mod tests { // segment sizes vm.segments.segment_sizes = HashMap::from([(0, 0), (1, 2), (2, 0), (3, 0)]); } + + #[test] + fn get_air_private_input() { + let program_content = + include_bytes!("../../../../cairo_programs/proof_programs/common_signature.json"); + let (runner, vm) = crate::cairo_run::cairo_run( + program_content, + &CairoRunConfig { + proof_mode: true, + layout: "all_cairo", + ..Default::default() + }, + &mut BuiltinHintProcessor::new_empty(), + ) + .unwrap(); + let air_private_input = runner.get_air_private_input(&vm); + assert!(air_private_input.0[HASH_BUILTIN_NAME].is_empty()); + assert!(air_private_input.0[RANGE_CHECK_BUILTIN_NAME].is_empty()); + assert!(air_private_input.0[BITWISE_BUILTIN_NAME].is_empty()); + assert!(air_private_input.0[EC_OP_BUILTIN_NAME].is_empty()); + assert!(air_private_input.0[KECCAK_BUILTIN_NAME].is_empty()); + assert!(air_private_input.0[POSEIDON_BUILTIN_NAME].is_empty()); + assert_eq!( + air_private_input.0[SIGNATURE_BUILTIN_NAME], + vec![PrivateInput::Signature(PrivateInputSignature { + index: 0, + pubkey: felt_hex!( + "0x3d60886c2353d93ec2862e91e23036cd9999a534481166e5a616a983070434d" + ), + msg: felt_hex!("0xa9e"), + signature_input: SignatureInput { + r: felt_hex!( + "0x6d2e2e00dfceffd6a375db04764da249a5a1534c7584738dfe01cb3944a33ee" + ), + w: felt_hex!( + "0x396362a34ff391372fca63f691e27753ce8f0c2271a614cbd240e1dc1596b28" + ) + } + })] + ); + } }