diff --git a/Cargo.toml b/Cargo.toml index 856a151a..b3926b75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,10 +3,14 @@ members = [ "crates/cairo-program-runner", "crates/cairo-program-runner-lib", "crates/stwo_run_and_prove", + "crates/proving_service", "crates/vm_runner", ] resolver = "2" +[profile.release] +debug = true + [workspace.package] version = "1.1.0" edition = "2024" @@ -16,7 +20,7 @@ repository = "https://github.com/starkware-libs/proving-utils" [workspace.dependencies] anyhow = "1.0.98" bincode = { version = "2.0.1", features = ["serde"] } -cairo-air = "1.1.0" +cairo-air = { git = "https://github.com/starkware-libs/stwo-cairo", rev = "319e2e96" } cairo-lang-executable = "2.16.0" cairo-lang-runner = "2.16.0" cairo-lang-casm = "2.16.0" @@ -40,13 +44,23 @@ sonic-rs = "0.3.17" starknet-crypto = "=0.8.1" starknet-ff = "0.3.7" starknet-types-core = "=0.2.4" -stwo-cairo-utils = "1.1.0" -stwo-cairo-adapter = "1.1.0" -stwo-cairo-prover = "1.1.0" -stwo-cairo-serialize = "1.1.0" +stwo = { git = "https://github.com/starkware-libs/stwo", rev = "3428a95e", features = [ + "parallel", +], default-features = false } +stwo-cairo-utils = { package = "stwo-cairo-utils", git = "https://github.com/starkware-libs/stwo-cairo", rev = "319e2e96" } +stwo-cairo-adapter = { package = "stwo-cairo-adapter", git = "https://github.com/starkware-libs/stwo-cairo", rev = "319e2e96" } +stwo-cairo-prover = { package = "stwo-cairo-prover", git = "https://github.com/starkware-libs/stwo-cairo", rev = "319e2e96" } +stwo-cairo-serialize = { package = "stwo-cairo-serialize", git = "https://github.com/starkware-libs/stwo-cairo", rev = "319e2e96" } +circuit-air = { git = "https://github.com/starkware-libs/stwo-circuits", rev = "c6aea41" } +circuit-cairo-air = { git = "https://github.com/starkware-libs/stwo-circuits", rev = "c6aea41" } +circuit-prover = { git = "https://github.com/starkware-libs/stwo-circuits", rev = "c6aea41" } +circuits = { git = "https://github.com/starkware-libs/stwo-circuits", rev = "c6aea41" } +circuits-stark-verifier = { git = "https://github.com/starkware-libs/stwo-circuits", rev = "c6aea41" } tempfile = "3.10.1" thiserror = "1.0.61" thiserror-no-std = "2.0.2" + tracing = "0.1.40" mockall = "0.13.1" rstest = "0.21" + diff --git a/crates/proving_service/Cargo.toml b/crates/proving_service/Cargo.toml new file mode 100644 index 00000000..cb4e3838 --- /dev/null +++ b/crates/proving_service/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "proving_service" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "Proving service for Cairo programs" + +[dependencies] +anyhow.workspace = true +cairo-air.workspace = true +cairo-program-runner-lib.workspace = true +cairo-vm.workspace = true +clap.workspace = true +env_logger.workspace = true +log.workspace = true +serde.workspace = true +serde_json.workspace = true +sonic-rs.workspace = true +starknet-ff.workspace = true +stwo-cairo-prover.workspace = true +stwo-cairo-utils.workspace = true +stwo-cairo-adapter.workspace = true +stwo-cairo-serialize.workspace = true +circuit-air.workspace = true +circuit-cairo-air.workspace = true +circuit-prover.workspace = true +circuits.workspace = true +circuits-stark-verifier.workspace = true +itertools = "0.12" +stwo.workspace = true +stwo-run-and-prove = { path = "../stwo_run_and_prove" } +thiserror.workspace = true +tracing.workspace = true + +[dev-dependencies] +ctor.workspace = true +mockall.workspace = true +rstest.workspace = true +tempfile.workspace = true diff --git a/crates/proving_service/src/lib.rs b/crates/proving_service/src/lib.rs new file mode 100644 index 00000000..f17b066e --- /dev/null +++ b/crates/proving_service/src/lib.rs @@ -0,0 +1,129 @@ +use cairo_air::utils::ProofFormat; +use circuit_air::statement::all_circuit_components; +use circuit_cairo_air::statement::MEMORY_VALUES_LIMBS; +use circuit_cairo_air::verify::{ + CairoVerifierConfig, build_fixed_cairo_circuit, prepare_cairo_proof_for_circuit_verifier, +}; +use circuit_prover::finalize::finalize_context; +use circuit_prover::prover::BaseColumnPool; +use circuit_prover::prover::preprare_circuit_proof_for_circuit_verifier; +use circuit_prover::prover::{SimdBackend, prove_circuit_with_precompute}; +use circuit_prover::witness::preprocessed::PreprocessedCircuit; +use circuits_stark_verifier::proof::ProofConfig; +use itertools::Itertools; +use std::array; +use std::sync::Arc; +use std::{fs::read_to_string, path::PathBuf}; +use stwo::core::fields::m31::M31; +use stwo::core::fields::qm31::QM31; +use stwo::core::pcs::PcsConfig; +use stwo::core::utils::MaybeOwned; +use stwo::prover::CommitmentTreeProver; +use stwo::prover::poly::twiddles::TwiddleTree; +use stwo_cairo_adapter::ProverInput; +use stwo_cairo_prover::prover::{ProverParameters, prove_cairo_with_precompute}; +use stwo_cairo_prover::stwo::core::vcs_lifted::blake2_merkle::Blake2sM31MerkleChannel; +use stwo_cairo_prover::witness::prelude::PreProcessedTrace; + +use tracing::{Level, span}; + +pub use stwo_run_and_prove::{ + ProveConfig, ProverTrait, RunConfig, StwoProverEntryPoint, StwoRunAndProveError, + stwo_run_and_prove, +}; + +pub struct ProvingServiceEntryPoint { + pub base_column_pool: BaseColumnPool, + pub preprocessed_circuit: PreprocessedCircuit, + pub privacy_verifier_config: CairoVerifierConfig, + pub twiddles: TwiddleTree, + pub cairo_preprocessed_trace: Arc, + pub cairo_preprocessed_tree: CommitmentTreeProver, + pub circuit_preprocessed_tree: CommitmentTreeProver, +} + +impl ProverTrait for ProvingServiceEntryPoint { + fn create_and_serialize_proof( + &self, + prover_input: ProverInput, + _verify: bool, + _proof_path: PathBuf, + _proof_format: ProofFormat, + proof_params_json: Option, + ) -> Result<(), StwoRunAndProveError> { + let proof_params: ProverParameters = if let Some(proof_params_json) = proof_params_json { + let s = read_to_string(&proof_params_json) + .map_err(|e| StwoRunAndProveError::PathIO(e, proof_params_json.clone()))?; + serde_json::from_str(&s) + .map_err(|e| StwoRunAndProveError::Anyhow(anyhow::Error::from(e)))? + } else { + panic!("Proof parameters JSON file is required"); + }; + + let span = span!(Level::INFO, "proving cairo").entered(); + let cairo_proof = prove_cairo_with_precompute::( + &self.base_column_pool, + &self.twiddles, + self.cairo_preprocessed_trace.clone(), + MaybeOwned::Borrowed(&self.cairo_preprocessed_tree), + prover_input, + proof_params, + ) + .map_err(|e| StwoRunAndProveError::Anyhow(anyhow::Error::from(e)))?; + span.exit(); + + let (proof, public_data) = prepare_cairo_proof_for_circuit_verifier( + &cairo_proof, + &self.privacy_verifier_config.proof_config, + ); + + let (public_claim, outputs, _program) = public_data.pack_into_u32s(); + let outputs: Vec<[M31; 28]> = outputs + .chunks_exact(MEMORY_VALUES_LIMBS) + .map(|chunk| array::from_fn(|i| M31::from_u32_unchecked(chunk[i]))) + .collect_vec(); + + let span = span!(Level::INFO, "building verification context").entered(); + + let mut context = + build_fixed_cairo_circuit(&self.privacy_verifier_config, proof, public_claim, outputs); + span.exit(); + + finalize_context(&mut context); + let context_values = context.values(); + + let span = span!(Level::INFO, "proving verification circuit").entered(); + + let mut pcs_config = PcsConfig::default(); + let lifting_log_size = self.preprocessed_circuit.params.trace_log_size + + pcs_config.fri_config.log_blowup_factor; + pcs_config.lifting_log_size = Some(lifting_log_size); + + let circuit_proof = prove_circuit_with_precompute( + &self.base_column_pool, + &self.twiddles, + &self.preprocessed_circuit, + MaybeOwned::Borrowed(&self.circuit_preprocessed_tree), + context_values, + pcs_config, + ); + span.exit(); + + let preprocessed_column_ids = self.preprocessed_circuit.preprocessed_trace.ids(); + let proof_config = ProofConfig::from_components( + &all_circuit_components::(), + preprocessed_column_ids.len(), + &circuit_proof.pcs_config, + circuit_air::statement::INTERACTION_POW_BITS, + ); + let (_proof, _public_data) = + preprare_circuit_proof_for_circuit_verifier(circuit_proof, proof_config); + + // Sleep for 1 second to create a seperation between the proving and clean up. + std::thread::sleep(std::time::Duration::from_secs(1)); + + // TODO: Serialize the proof to a file. + + Ok(()) + } +} diff --git a/crates/proving_service/src/main.rs b/crates/proving_service/src/main.rs new file mode 100644 index 00000000..a3ff1dbb --- /dev/null +++ b/crates/proving_service/src/main.rs @@ -0,0 +1,159 @@ +use cairo_air::{PreProcessedTraceVariant, utils::ProofFormat}; +use cairo_program_runner_lib::utils::get_program_input_from_path; +use circuit_cairo_air::privacy::privacy_cairo_verifier_config; +use circuit_cairo_air::verify::build_cairo_verifier_circuit; +use circuit_prover::prover::BaseColumnPool; +use circuit_prover::witness::preprocessed::PreprocessedCircuit; +use clap::Parser; +use proving_service::ProvingServiceEntryPoint; +use std::process::ExitCode; +use std::{cmp::max, path::PathBuf, sync::Arc}; +use stwo::{ + core::vcs_lifted::blake2_merkle::Blake2sM31MerkleChannel, + prover::{CommitmentTreeProver, poly::circle::PolyOps}, +}; +use stwo_cairo_prover::witness::{ + prelude::{CanonicCoset, SimdBackend}, + preprocessed_trace::gen_trace, +}; +use stwo_cairo_utils::binary_utils::run_binary; +use stwo_run_and_prove::{ProveConfig, RunConfig, StwoRunAndProveError, stwo_run_and_prove}; +use tracing::{Level, span}; + +/// This binary runs a cairo program and generates a Stwo proof for it. +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + #[clap(long = "program", help = "Absolute path to the compiled program.")] + program: PathBuf, + #[clap( + long = "program_input", + help = "Absolute path to the program input file." + )] + program_input: Option, + #[clap( + long = "prover_params_json", + help = "Absolute path to the JSON file containing the prover parameters." + )] + prover_params_json: Option, + #[clap( + long = "proof_path", + help = "Absolute path where the generated proof will be saved." + )] + proof_path: PathBuf, + #[clap(long, value_enum, default_value_t = ProofFormat::CairoSerde, help = "Json or cairo-serde.")] + proof_format: ProofFormat, + #[clap(long = "verify", help = "Should verify the generated proof.")] + verify: bool, + #[clap( + long = "program_output", + help = "Optional absolute path where the program's output will be saved." + )] + program_output: Option, + #[clap( + long = "save_debug_data", + help = "Should save the ProverInput to a file in `debug_data_dir` for both success and failure." + )] + save_debug_data: bool, + #[clap( + long = "debug_data_dir", + help = "Absolute path to the output directory where the ProverInput will be saved in the + case of a proving error, or when the save_debug_data flag is enabled." + )] + debug_data_dir: Option, +} + +fn main() -> ExitCode { + run_binary(run, "proving_service") +} + +fn run() -> Result<(), StwoRunAndProveError> { + let _span = span!(Level::INFO, "run").entered(); + let args = Args::parse(); + let prove_config = ProveConfig { + verify: args.verify, + proof_path: args.proof_path, + proof_format: args.proof_format, + prover_params_json: args.prover_params_json, + }; + + let privacy_verifier_config = privacy_cairo_verifier_config(); + let proof_config = &privacy_verifier_config.proof_config; + let mut novalue_context = build_cairo_verifier_circuit(&privacy_verifier_config); + let preprocessed_circuit = PreprocessedCircuit::preprocess_circuit(&mut novalue_context); + + let cairo_evaluation_domain_log_size = proof_config + .log_evaluation_domain_size() + .try_into() + .unwrap(); + + let circuit_proof_log_blowup_factor = 1; + let max_domain_size = max( + preprocessed_circuit.params.trace_log_size + circuit_proof_log_blowup_factor, + cairo_evaluation_domain_log_size, + ); + + // Precompute twiddles. + // Account for blowup factor and for composition polynomial calculation (taking the max since + // the composition polynomial is split prior to LDE). + let twiddles = SimdBackend::precompute_twiddles( + CanonicCoset::new(max_domain_size) + .circle_domain() + .half_coset, + ); + + let preprocessed_trace = + Arc::new(PreProcessedTraceVariant::CanonicalSmall.to_preprocessed_trace()); + let preprocessed_trace_polys = + SimdBackend::interpolate_columns(gen_trace(preprocessed_trace.clone()), &twiddles); + + let store_polynomials_coefficients = true; + + let base_column_pool = BaseColumnPool::::new(); + let cairo_preprocessed_tree = CommitmentTreeProver::::new( + preprocessed_trace_polys, + proof_config.fri.log_blowup_factor.try_into().unwrap(), + &twiddles, + store_polynomials_coefficients, + Some(cairo_evaluation_domain_log_size), + &base_column_pool, + ); + + let circuit_preprocessed_trace = preprocessed_circuit + .preprocessed_trace + .get_trace::(); + let circuit_preprocessed_trace_polys = + SimdBackend::interpolate_columns(circuit_preprocessed_trace, &twiddles); + + let circuit_preprocessed_tree = + CommitmentTreeProver::::new( + circuit_preprocessed_trace_polys, + circuit_proof_log_blowup_factor, + &twiddles, + store_polynomials_coefficients, + None, + &base_column_pool, + ); + + let prover = Box::new(ProvingServiceEntryPoint { + base_column_pool, + preprocessed_circuit, + privacy_verifier_config, + twiddles, + cairo_preprocessed_trace: preprocessed_trace, + cairo_preprocessed_tree, + circuit_preprocessed_tree, + }); + let run_config = RunConfig { + program_path: args.program, + program_input: get_program_input_from_path(&args.program_input)?, + program_output: args.program_output, + debug_data_dir: args.debug_data_dir, + save_debug_data: args.save_debug_data, + extra_hint_processor: None, + }; + // Sleep for 1 second to create a seperation between the preproccessing and the proving. + std::thread::sleep(std::time::Duration::from_secs(1)); + stwo_run_and_prove(run_config, prove_config, prover)?; + Ok(()) +}