diff --git a/crates/starknet_os/Cargo.toml b/crates/starknet_os/Cargo.toml index f7eab009a46..69f1a058663 100644 --- a/crates/starknet_os/Cargo.toml +++ b/crates/starknet_os/Cargo.toml @@ -15,6 +15,7 @@ deserialize = [ ] include_program_output = [] testing = [ + "apollo_starknet_os_program/test_programs", "blockifier/testing", "expect-test", "itertools", diff --git a/crates/starknet_os/src/proof_fact_fold_test.rs b/crates/starknet_os/src/proof_fact_fold_test.rs index 3731b743fac..c6a8eacde2d 100644 --- a/crates/starknet_os/src/proof_fact_fold_test.rs +++ b/crates/starknet_os/src/proof_fact_fold_test.rs @@ -2,7 +2,6 @@ use std::collections::HashMap; use apollo_starknet_os_program::test_programs::PROOF_FACT_FOLD_BYTES; use cairo_vm::types::builtin_name::BuiltinName; -use cairo_vm::types::layout_name::LayoutName; use cairo_vm::types::relocatable::MaybeRelocatable; use cairo_vm::vm::runners::cairo_runner::ExecutionResources; use expect_test::expect; @@ -23,74 +22,16 @@ use super::{ use crate::test_utils::cairo_runner::{ initialize_and_run_cairo_0_entry_point, EndpointArg, - EntryPointRunnerConfig, ImplicitArg, PointerArg, ValueArg, }; - -fn entrypoint_runner_config() -> EntryPointRunnerConfig { - EntryPointRunnerConfig { - layout: LayoutName::all_cairo, - trace_enabled: false, - verify_secure: false, - proof_mode: false, - add_main_prefix_to_entrypoint: true, - validate_builtins_offset: true, - } -} - -fn felt_array_arg(felts: &[Felt]) -> EndpointArg { - EndpointArg::Pointer(PointerArg::Array( - felts.iter().map(|felt| MaybeRelocatable::Int(*felt)).collect(), - )) -} - -fn run_cairo_function_returning_words( - function_name: &str, - explicit_args: &[EndpointArg], - implicit_args: &[ImplicitArg], - n_returned_words: usize, -) -> Vec { - let expected_return_values = vec![EndpointArg::Pointer(PointerArg::Array(vec![ - MaybeRelocatable::from(Felt::ZERO); - n_returned_words - ]))]; - let (_, explicit_return_values, _) = initialize_and_run_cairo_0_entry_point( - &entrypoint_runner_config(), - PROOF_FACT_FOLD_BYTES, - function_name, - explicit_args, - implicit_args, - &expected_return_values, - HashMap::new(), - None, - ) - .unwrap_or_else(|error| panic!("Failed to run Cairo function {function_name}: {error:?}")); - let [EndpointArg::Pointer(PointerArg::Array(returned_words))] = - explicit_return_values.as_slice() - else { - panic!("Expected {function_name} to return a single words-array pointer."); - }; - returned_words - .iter() - .map(|returned_word| { - let MaybeRelocatable::Int(word_felt) = returned_word else { - panic!("Expected a felt digest word, got {returned_word:?}."); - }; - u32::try_from(word_felt.to_biguint()).expect("A digest word must fit in a u32.") - }) - .collect() -} - -fn run_cairo_processed_proof_output_digest(proof_facts: &[Felt]) -> Vec { - run_cairo_function_returning_words( - "compute_processed_proof_output_digest", - &[EndpointArg::from(Felt::from(proof_facts.len())), felt_array_arg(proof_facts)], - &[ImplicitArg::Builtin(BuiltinName::range_check)], - BLAKE2S_DIGEST_N_WORDS, - ) -} +use crate::test_utils::proof_fact_fold_runner::{ + entrypoint_runner_config, + felt_array_arg, + run_cairo_function_returning_words, + run_cairo_processed_proof_output_digest, +}; fn run_cairo_processed_proof_output_digest_resources(proof_facts: &[Felt]) -> ExecutionResources { let expected_return_values = vec![EndpointArg::Pointer(PointerArg::Array(vec![ diff --git a/crates/starknet_os/src/test_utils.rs b/crates/starknet_os/src/test_utils.rs index 432bbdfe03f..42610253f18 100644 --- a/crates/starknet_os/src/test_utils.rs +++ b/crates/starknet_os/src/test_utils.rs @@ -10,6 +10,7 @@ pub mod cairo_dict; pub mod cairo_runner; pub mod coverage; pub mod errors; +pub mod proof_fact_fold_runner; #[cfg(test)] pub mod utils; pub mod validations; diff --git a/crates/starknet_os/src/test_utils/proof_fact_fold_runner.rs b/crates/starknet_os/src/test_utils/proof_fact_fold_runner.rs new file mode 100644 index 00000000000..5ab213966a5 --- /dev/null +++ b/crates/starknet_os/src/test_utils/proof_fact_fold_runner.rs @@ -0,0 +1,79 @@ +use std::collections::HashMap; + +use apollo_starknet_os_program::test_programs::PROOF_FACT_FOLD_BYTES; +use cairo_vm::types::builtin_name::BuiltinName; +use cairo_vm::types::layout_name::LayoutName; +use cairo_vm::types::relocatable::MaybeRelocatable; +use starknet_types_core::felt::Felt; + +use crate::proof_fact_fold::BLAKE2S_DIGEST_N_WORDS; +use crate::test_utils::cairo_runner::{ + initialize_and_run_cairo_0_entry_point, + EndpointArg, + EntryPointRunnerConfig, + ImplicitArg, + PointerArg, +}; + +pub fn entrypoint_runner_config() -> EntryPointRunnerConfig { + EntryPointRunnerConfig { + layout: LayoutName::all_cairo, + trace_enabled: false, + verify_secure: false, + proof_mode: false, + add_main_prefix_to_entrypoint: true, + validate_builtins_offset: true, + } +} + +pub fn felt_array_arg(felts: &[Felt]) -> EndpointArg { + EndpointArg::Pointer(PointerArg::Array( + felts.iter().map(|felt| MaybeRelocatable::Int(*felt)).collect(), + )) +} + +pub fn run_cairo_function_returning_words( + function_name: &str, + explicit_args: &[EndpointArg], + implicit_args: &[ImplicitArg], + n_returned_words: usize, +) -> Vec { + let expected_return_values = vec![EndpointArg::Pointer(PointerArg::Array(vec![ + MaybeRelocatable::from(Felt::ZERO); + n_returned_words + ]))]; + let (_, explicit_return_values, _) = initialize_and_run_cairo_0_entry_point( + &entrypoint_runner_config(), + PROOF_FACT_FOLD_BYTES, + function_name, + explicit_args, + implicit_args, + &expected_return_values, + HashMap::new(), + None, + ) + .unwrap_or_else(|error| panic!("Failed to run Cairo function {function_name}: {error:?}")); + let [EndpointArg::Pointer(PointerArg::Array(returned_words))] = + explicit_return_values.as_slice() + else { + panic!("Expected {function_name} to return a single words-array pointer."); + }; + returned_words + .iter() + .map(|returned_word| { + let MaybeRelocatable::Int(word_felt) = returned_word else { + panic!("Expected a felt digest word, got {returned_word:?}."); + }; + u32::try_from(word_felt.to_biguint()).expect("A digest word must fit in a u32.") + }) + .collect() +} + +pub fn run_cairo_processed_proof_output_digest(proof_facts: &[Felt]) -> Vec { + run_cairo_function_returning_words( + "compute_processed_proof_output_digest", + &[EndpointArg::from(Felt::from(proof_facts.len())), felt_array_arg(proof_facts)], + &[ImplicitArg::Builtin(BuiltinName::range_check)], + BLAKE2S_DIGEST_N_WORDS, + ) +} diff --git a/crates/starknet_os_flow_tests/Cargo.toml b/crates/starknet_os_flow_tests/Cargo.toml index a7170b2f59b..c1ba4f6ae06 100644 --- a/crates/starknet_os_flow_tests/Cargo.toml +++ b/crates/starknet_os_flow_tests/Cargo.toml @@ -36,7 +36,7 @@ starknet_committer = { workspace = true, features = ["testing"] } starknet_os = { workspace = true, features = ["include_program_output", "testing"] } starknet_patricia_storage = { workspace = true, features = ["testing"] } starknet_proof_verifier.workspace = true -starknet_transaction_prover.workspace = true +starknet_transaction_prover = { workspace = true, features = ["testing"] } strum.workspace = true tokio.workspace = true diff --git a/crates/starknet_os_flow_tests/src/lib.rs b/crates/starknet_os_flow_tests/src/lib.rs index 992736ca552..2247333e455 100644 --- a/crates/starknet_os_flow_tests/src/lib.rs +++ b/crates/starknet_os_flow_tests/src/lib.rs @@ -3,6 +3,7 @@ pub(crate) mod fuzz_tests; pub(crate) mod initial_state; pub(crate) mod os_resources_test; +pub(crate) mod proof_fact_verifier_test; pub(crate) mod special_contracts; pub(crate) mod test_manager; pub(crate) mod tests; diff --git a/crates/starknet_os_flow_tests/src/proof_fact_verifier_test.rs b/crates/starknet_os_flow_tests/src/proof_fact_verifier_test.rs new file mode 100644 index 00000000000..0219b5cb9f3 --- /dev/null +++ b/crates/starknet_os_flow_tests/src/proof_fact_verifier_test.rs @@ -0,0 +1,75 @@ +use blockifier::test_utils::get_valid_virtual_os_program_hash; +use blockifier_test_utils::cairo_versions::{CairoVersion, RunnableCairo1}; +use blockifier_test_utils::calldata::create_calldata; +use blockifier_test_utils::contracts::FeatureContract; +use starknet_api::transaction::fields::ProofFacts; +use starknet_api::{calldata, invoke_tx_args}; +use starknet_os::proof_fact_fold::{compute_processed_proof_output_digest, pack_output_digest}; +use starknet_os::test_utils::proof_fact_fold_runner::run_cairo_processed_proof_output_digest; +use starknet_transaction_prover::verifier_task::test_utils::{ + one_leaf_proof_facts, + run_one_leaf_verifier_task, + FIXTURE_VERIFIER_PROGRAM_HASH, +}; +use starknet_transaction_prover::verifier_task::{ + verify_circuit_verifier_task_output, + VerifierTaskError, +}; +use starknet_types_core::felt::Felt; + +use crate::test_manager::TestBuilder; + +#[test] +fn test_cairo_processed_proof_digest_matches_circuit_verifier_output() { + let task_output = run_one_leaf_verifier_task().unwrap(); + let cairo_output_digest: Vec = + run_cairo_processed_proof_output_digest(&one_leaf_proof_facts()); + let (packed_output_low, packed_output_high) = + pack_output_digest(&cairo_output_digest.try_into().unwrap()); + verify_circuit_verifier_task_output( + &task_output, + FIXTURE_VERIFIER_PROGRAM_HASH, + packed_output_low, + packed_output_high, + ) + .unwrap(); +} + +#[tokio::test] +async fn test_os_emitted_output_feeds_verifier_task_comparison() { + let test_contract = FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm)); + let (mut test_builder, [test_contract_address]) = + TestBuilder::create_standard([(test_contract, calldata![Felt::ZERO, Felt::ZERO])]).await; + let proof_facts = ProofFacts::custom_proof_facts_for_testing( + get_valid_virtual_os_program_hash(), + test_builder.compute_virtual_os_config_hash(), + ); + let calldata = create_calldata(test_contract_address, "empty_function", &[]); + test_builder.add_funded_account_invoke( + invoke_tx_args! { calldata: calldata.clone(), proof_facts: proof_facts.clone() }, + ); + + let test_output = test_builder.build_and_run().await; + test_output.perform_default_validations(); + let os_output = test_output + .runner_output + .get_os_output(test_output.private_keys.as_ref()) + .expect("Getting OsOutput from raw OS output should not fail."); + assert_eq!(os_output.common_os_output.n_proof_facts_transactions, 1); + + let expected_output_digest = compute_processed_proof_output_digest(&proof_facts.0); + let (expected_low, expected_high) = pack_output_digest(&expected_output_digest); + assert_eq!(os_output.common_os_output.processed_proof_output_low, expected_low); + assert_eq!(os_output.common_os_output.processed_proof_output_high, expected_high); + + let task_output = run_one_leaf_verifier_task().unwrap(); + assert!(matches!( + verify_circuit_verifier_task_output( + &task_output, + FIXTURE_VERIFIER_PROGRAM_HASH, + os_output.common_os_output.processed_proof_output_low, + os_output.common_os_output.processed_proof_output_high, + ), + Err(VerifierTaskError::VerificationDigestMismatch { .. }) + )); +} diff --git a/crates/starknet_transaction_prover/Cargo.toml b/crates/starknet_transaction_prover/Cargo.toml index 3d0f4da9a33..0da396dbc50 100644 --- a/crates/starknet_transaction_prover/Cargo.toml +++ b/crates/starknet_transaction_prover/Cargo.toml @@ -11,6 +11,7 @@ cairo_native = ["blockifier/cairo_native"] # Enables in-memory stwo proving. Requires a nightly Rust toolchain because the # stwo prover crate uses unstable features (array_chunks, portable_simd, …). stwo_proving = ["dep:privacy-prove"] +testing = ["dep:flate2"] [dependencies] anyhow.workspace = true @@ -24,6 +25,7 @@ cairo-program-runner-lib.workspace = true cairo-lang-utils.workspace = true cairo-vm.workspace = true clap = { workspace = true, features = ["derive", "env"] } +flate2 = { workspace = true, optional = true } futures.workspace = true http.workspace = true http-body-util.workspace = true diff --git a/crates/starknet_transaction_prover/src/verifier_task.rs b/crates/starknet_transaction_prover/src/verifier_task.rs index 10c9074a42c..e0a6ace12fa 100644 --- a/crates/starknet_transaction_prover/src/verifier_task.rs +++ b/crates/starknet_transaction_prover/src/verifier_task.rs @@ -14,6 +14,9 @@ use starknet_os::proof_fact_fold::{ }; use starknet_types_core::felt::Felt; +#[cfg(any(test, feature = "testing"))] +pub mod test_utils; + #[cfg(test)] #[path = "verifier_task_test.rs"] mod verifier_task_test; diff --git a/crates/starknet_transaction_prover/src/verifier_task/test_utils.rs b/crates/starknet_transaction_prover/src/verifier_task/test_utils.rs new file mode 100644 index 00000000000..49098654fef --- /dev/null +++ b/crates/starknet_transaction_prover/src/verifier_task/test_utils.rs @@ -0,0 +1,57 @@ +use std::io::Read; + +use flate2::read::GzDecoder; +use starknet_types_core::felt::Felt; + +use crate::verifier_task::{ + run_circuit_verifier_task, + CircuitVerifierTaskOutput, + VerifierTaskError, +}; + +pub const SIMPLE_BOOTLOADER_PROGRAM_GZ: &[u8] = + include_bytes!("../../resources/simple_bootloader_compiled.json.gz"); + +pub const VERIFIER_EXECUTABLE_GZ: &[u8] = + include_bytes!("../../resources/stwo_circuit_verifier_canonical_small.executable.json.gz"); + +pub const PROCESSED_PROOF_GZ: &[u8] = include_bytes!("../../resources/one_leaf_root_proof.json.gz"); + +pub const FIXTURE_VERIFIER_PROGRAM_HASH: Felt = + Felt::from_hex_unchecked("0x764dc214c7f45a6899b05d42ba4d23d5849e0bf0383ec951f000b1742107fdd"); + +pub fn gunzip(compressed_bytes: &[u8]) -> Vec { + let mut decompressed_bytes = Vec::new(); + GzDecoder::new(compressed_bytes).read_to_end(&mut decompressed_bytes).unwrap(); + decompressed_bytes +} + +pub fn processed_proof_felts() -> Vec { + let processed_proof_hex: Vec = + serde_json::from_slice(&gunzip(PROCESSED_PROOF_GZ)).unwrap(); + processed_proof_hex + .iter() + .map(|proof_felt_hex| Felt::from_hex(proof_felt_hex).unwrap()) + .collect() +} + +pub fn one_leaf_proof_facts() -> Vec { + [ + Felt::ZERO, + Felt::ZERO, + Felt::from_hex("0x32b88272d54b83880ebebd9c4292a650bee27d1575e82123391b6df2932e843") + .unwrap(), + Felt::from_hex("0xb").unwrap(), + Felt::from_hex("0xd").unwrap(), + Felt::from_hex("0x11").unwrap(), + ] + .to_vec() +} + +pub fn run_one_leaf_verifier_task() -> Result { + run_circuit_verifier_task( + &gunzip(SIMPLE_BOOTLOADER_PROGRAM_GZ), + &gunzip(VERIFIER_EXECUTABLE_GZ), + &processed_proof_felts(), + ) +} diff --git a/crates/starknet_transaction_prover/src/verifier_task_test.rs b/crates/starknet_transaction_prover/src/verifier_task_test.rs index 5f8f28d5b35..704dc43c14f 100644 --- a/crates/starknet_transaction_prover/src/verifier_task_test.rs +++ b/crates/starknet_transaction_prover/src/verifier_task_test.rs @@ -1,60 +1,20 @@ -use std::io::Read; - -use flate2::read::GzDecoder; use starknet_os::proof_fact_fold::{compute_processed_proof_output_digest, pack_output_digest}; use starknet_types_core::felt::Felt; +use super::test_utils::{ + gunzip, + one_leaf_proof_facts, + processed_proof_felts, + run_one_leaf_verifier_task, + FIXTURE_VERIFIER_PROGRAM_HASH, + SIMPLE_BOOTLOADER_PROGRAM_GZ, + VERIFIER_EXECUTABLE_GZ, +}; use super::{run_circuit_verifier_task, verify_circuit_verifier_task_output, VerifierTaskError}; -const SIMPLE_BOOTLOADER_PROGRAM_GZ: &[u8] = - include_bytes!("../resources/simple_bootloader_compiled.json.gz"); - -const VERIFIER_EXECUTABLE_GZ: &[u8] = - include_bytes!("../resources/stwo_circuit_verifier_canonical_small.executable.json.gz"); - -const PROCESSED_PROOF_GZ: &[u8] = include_bytes!("../resources/one_leaf_root_proof.json.gz"); - -const FIXTURE_VERIFIER_PROGRAM_HASH: Felt = - Felt::from_hex_unchecked("0x764dc214c7f45a6899b05d42ba4d23d5849e0bf0383ec951f000b1742107fdd"); - -fn gunzip(compressed_bytes: &[u8]) -> Vec { - let mut decompressed_bytes = Vec::new(); - GzDecoder::new(compressed_bytes).read_to_end(&mut decompressed_bytes).unwrap(); - decompressed_bytes -} - -fn processed_proof_felts() -> Vec { - let processed_proof_hex: Vec = - serde_json::from_slice(&gunzip(PROCESSED_PROOF_GZ)).unwrap(); - processed_proof_hex - .iter() - .map(|proof_felt_hex| Felt::from_hex(proof_felt_hex).unwrap()) - .collect() -} - -fn one_leaf_proof_facts() -> Vec { - [ - Felt::ZERO, - Felt::ZERO, - Felt::from_hex("0x32b88272d54b83880ebebd9c4292a650bee27d1575e82123391b6df2932e843") - .unwrap(), - Felt::from_hex("0xb").unwrap(), - Felt::from_hex("0xd").unwrap(), - Felt::from_hex("0x11").unwrap(), - ] - .to_vec() -} - #[test] fn test_run_and_verify_circuit_verifier_task() { - let simple_bootloader_program = gunzip(SIMPLE_BOOTLOADER_PROGRAM_GZ); - let verifier_executable = gunzip(VERIFIER_EXECUTABLE_GZ); - let task_output = run_circuit_verifier_task( - &simple_bootloader_program, - &verifier_executable, - &processed_proof_felts(), - ) - .unwrap(); + let task_output = run_one_leaf_verifier_task().unwrap(); assert_eq!(task_output.verifier_program_hash, FIXTURE_VERIFIER_PROGRAM_HASH); let output_digest = compute_processed_proof_output_digest(&one_leaf_proof_facts()); @@ -89,14 +49,12 @@ fn test_run_and_verify_circuit_verifier_task() { #[test] fn test_corrupted_processed_proof_fails_the_run() { - let simple_bootloader_program = gunzip(SIMPLE_BOOTLOADER_PROGRAM_GZ); - let verifier_executable = gunzip(VERIFIER_EXECUTABLE_GZ); let mut corrupted_processed_proof_felts = processed_proof_felts(); corrupted_processed_proof_felts[100] += Felt::ONE; assert!(matches!( run_circuit_verifier_task( - &simple_bootloader_program, - &verifier_executable, + &gunzip(SIMPLE_BOOTLOADER_PROGRAM_GZ), + &gunzip(VERIFIER_EXECUTABLE_GZ), &corrupted_processed_proof_felts, ), Err(VerifierTaskError::VerifierRun(_))