diff --git a/crates/core/src/constraint_system.rs b/crates/core/src/constraint_system.rs index 8334d27ba..34f7c5b31 100644 --- a/crates/core/src/constraint_system.rs +++ b/crates/core/src/constraint_system.rs @@ -654,6 +654,12 @@ impl<'a> std::ops::Deref for ValuesData<'a> { } } +impl<'a> From> for Vec { + fn from(value: ValuesData<'a>) -> Self { + value.into_owned() + } +} + /// A zero-knowledge proof that can be serialized for cross-host verification. /// /// This structure contains the complete proof transcript generated by the prover, diff --git a/crates/examples/Cargo.toml b/crates/examples/Cargo.toml index 009ee2003..c385c8ce2 100644 --- a/crates/examples/Cargo.toml +++ b/crates/examples/Cargo.toml @@ -38,4 +38,8 @@ harness = false [features] default = ["rayon"] perfetto = ["tracing-profile/perfetto"] -rayon = ["binius-prover/rayon"] \ No newline at end of file +rayon = ["binius-prover/rayon"] + +[[example]] +name = "prover" +path = "examples/prover.rs" diff --git a/crates/examples/README.md b/crates/examples/README.md index 08c3b4199..371102dc0 100644 --- a/crates/examples/README.md +++ b/crates/examples/README.md @@ -332,6 +332,35 @@ Look at these examples for reference: - `sha256.rs` - Shows parameter/instance separation, random data generation - `zklogin.rs` - Shows complex witness population with external data generation +## Prover binary + +The `prover` example binary reads a constraint system and witnesses from disk and produces a serialized proof. This is useful for cross-host proof generation pipelines. + +Arguments: +- `--cs-path PATH`: path to the constraint system binary +- `--pub-witness-path PATH`: path to the public values binary (ValuesData) +- `--non-pub-data-path PATH`: path to the non-public values binary (ValuesData) +- `--proof-path PATH`: path to write the proof binary +- `-l, --log-inv-rate N`: log of the inverse rate (default: 1) + +Usage: + +```bash +# 1) Generate artifacts from an example circuit (e.g., sha256) +cargo run --release --example sha256 -- save \ + --cs-path out/sha256/cs.bin \ + --pub-witness-path out/sha256/public.bin \ + --non-pub-data-path out/sha256/non_public.bin + +# 2) Produce a proof from those files +cargo run --release --example prover -- \ + --cs-path out/sha256/cs.bin \ + --pub-witness-path out/sha256/public.bin \ + --non-pub-data-path out/sha256/non_public.bin \ + --proof-path out/sha256/proof.bin \ + --log-inv-rate 1 +``` + ## Tips 1. **Keep it simple**: The main function should just create the CLI and run it diff --git a/crates/examples/examples/prover.rs b/crates/examples/examples/prover.rs new file mode 100644 index 000000000..ccc0d6cf8 --- /dev/null +++ b/crates/examples/examples/prover.rs @@ -0,0 +1,103 @@ +use std::{fs, path::PathBuf}; + +use anyhow::{Context, Result}; +use binius_core::constraint_system::{ConstraintSystem, Proof, ValueVec, ValuesData}; +use binius_examples::setup; +use binius_utils::serialization::{DeserializeBytes, SerializeBytes}; +use binius_verifier::{ + config::{ChallengerWithName, StdChallenger}, + transcript::ProverTranscript, +}; +use clap::Parser; + +/// Prover CLI: generate a proof from a serialized constraint system and witnesses. +#[derive(Debug, Parser)] +#[command( + name = "prover", + about = "Generate and save a proof from CS and witnesses" +)] +struct Args { + /// Path to the constraint system binary + #[arg(long = "cs-path")] + cs_path: PathBuf, + + /// Path to the public values (ValuesData) binary + #[arg(long = "pub-witness-path")] + pub_witness_path: PathBuf, + + /// Path to the non-public values (ValuesData) binary + #[arg(long = "non-pub-data-path")] + non_pub_data_path: PathBuf, + + /// Path to write the proof binary + #[arg(long = "proof-path")] + proof_path: PathBuf, + + /// Log of the inverse rate for the proof system + #[arg(short = 'l', long = "log-inv-rate", default_value_t = 1, value_parser = clap::value_parser!(u32).range(1..))] + log_inv_rate: u32, +} + +fn main() -> Result<()> { + let _tracing_guard = tracing_profile::init_tracing().ok(); + let args = Args::parse(); + + // Read and deserialize constraint system + let cs_bytes = fs::read(&args.cs_path).with_context(|| { + format!("Failed to read constraint system from {}", args.cs_path.display()) + })?; + let cs = ConstraintSystem::deserialize(&mut cs_bytes.as_slice()) + .context("Failed to deserialize ConstraintSystem")?; + + // Read and deserialize public values + let pub_bytes = fs::read(&args.pub_witness_path).with_context(|| { + format!("Failed to read public values from {}", args.pub_witness_path.display()) + })?; + let public = ValuesData::deserialize(&mut pub_bytes.as_slice()) + .context("Failed to deserialize public ValuesData")?; + + // Read and deserialize non-public values + let non_pub_bytes = fs::read(&args.non_pub_data_path).with_context(|| { + format!("Failed to read non-public values from {}", args.non_pub_data_path.display()) + })?; + let non_public = ValuesData::deserialize(&mut non_pub_bytes.as_slice()) + .context("Failed to deserialize non-public ValuesData")?; + + // Reconstruct the full ValueVec + // Take ownership of the underlying vectors without extra copies + let public: Vec<_> = public.into(); + let non_public: Vec<_> = non_public.into(); + let witness = ValueVec::new_from_data(cs.value_vec_layout.clone(), public, non_public) + .context("Failed to reconstruct ValueVec from provided values")?; + + // Setup prover (verifier is not used here) + let (_verifier, prover) = setup(cs, args.log_inv_rate as usize)?; + + // Prove + let mut prover_transcript = ProverTranscript::new(StdChallenger::default()); + prover + .prove(witness, &mut prover_transcript) + .context("Proving failed")?; + let transcript = prover_transcript.finalize(); + + // Wrap into serializable Proof with a stable challenger type identifier. + // NOTE: Avoid std::any::type_name for cross-platform stability; use a constant instead. + let proof = Proof::owned(transcript, StdChallenger::NAME.to_string()); + + // Serialize and save the proof + if let Some(parent) = args.proof_path.parent() + && !parent.as_os_str().is_empty() + { + fs::create_dir_all(parent) + .with_context(|| format!("Failed to create parent directory {}", parent.display()))?; + } + let mut buf = Vec::new(); + proof + .serialize(&mut buf) + .context("Failed to serialize proof")?; + fs::write(&args.proof_path, &buf) + .with_context(|| format!("Failed to write proof to {}", args.proof_path.display()))?; + + tracing::info!("Saved proof to {} ({} bytes)", args.proof_path.display(), buf.len()); + Ok(()) +} diff --git a/crates/verifier/src/config.rs b/crates/verifier/src/config.rs index 46b85f73c..788bef51a 100644 --- a/crates/verifier/src/config.rs +++ b/crates/verifier/src/config.rs @@ -1,7 +1,7 @@ //! Specifies standard trait implementations and parameters. use binius_field::{AESTowerField8b, BinaryField, BinaryField1b, BinaryField128bGhash}; -use binius_transcript::fiat_shamir::HasherChallenger; +use binius_transcript::fiat_shamir::{Challenger, HasherChallenger}; use binius_utils::checked_arithmetics::{checked_int_div, checked_log_2}; use super::hash::StdDigest; @@ -10,6 +10,15 @@ use super::hash::StdDigest; pub type B1 = BinaryField1b; pub type B128 = BinaryField128bGhash; +/// The intention of this trait is to capture the moment when a StandardChallenger type is changed. +pub trait ChallengerWithName: Challenger { + const NAME: &'static str; +} + +impl ChallengerWithName for HasherChallenger { + const NAME: &'static str = "HasherChallenger"; +} + /// The default [`binius_transcript::fiat_shamir::Challenger`] implementation. pub type StdChallenger = HasherChallenger;