Skip to main content

binius_examples/
lib.rs

1// Copyright 2025 Irreducible Inc.
2// Copyright 2026 The Binius Developers
3
4//! Example circuits and the harness that runs them.
5//!
6//! Each module under [`circuits`] builds one circuit and knows how to fill its witness; [`cli`]
7//! turns that into a runnable binary and [`snapshot`] records the circuit's statistics so a change
8//! in size shows up as a diff.
9
10pub mod circuits;
11pub mod cli;
12pub mod snapshot;
13
14use anyhow::Result;
15use binius_core::constraint_system::{ConstraintSystem, ValueVec};
16use binius_frontend::{CircuitBuilder, WitnessFiller};
17use binius_hash::sha256::Sha256HashSuite;
18use binius_hash_prover::ParallelHashSuite;
19use binius_prover::{KeyCollection, OptimalPackedB128, Prover, zk_config::ZKProver};
20use binius_utils::{DeserializeBytes, SerializeBytes};
21use binius_verifier::{
22	Verifier,
23	config::StdChallenger,
24	transcript::{ProverTranscript, VerifierTranscript},
25	zk_config::ZKVerifier,
26};
27use clap::ValueEnum;
28pub use cli::Cli;
29use digest::Output;
30use tracing::level_filters::LevelFilter;
31use tracing_forest::ForestLayer;
32use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
33
34/// Initialize tracing with `tracing-forest`'s tree-formatted profiling output.
35///
36/// Installs a [`ForestLayer`], which prints a timing tree (span durations and their share of
37/// the total) for each root span as it closes. This replaces the human-readable
38/// `PrintTreeLayer` from `tracing-profile`.
39///
40/// Span verbosity defaults to `DEBUG` and can be overridden with `RUST_LOG`
41/// (e.g. `RUST_LOG=binius=trace`), matching the previous behavior.
42///
43/// Calling this when a global subscriber is already installed is a no-op, so it is safe to
44/// invoke from every example entry point.
45pub fn init_tracing() {
46	// `ureq` logs every HTTP request at DEBUG, which would bury the trace tree of any circuit
47	// that fetches its instance data over the network.
48	let env_filter = EnvFilter::builder()
49		.with_default_directive(LevelFilter::DEBUG.into())
50		.from_env_lossy()
51		.add_directive("ureq=off".parse().expect("literal directive"));
52
53	let _ = tracing_subscriber::registry()
54		.with(env_filter)
55		.with(ForestLayer::default())
56		.try_init();
57}
58
59/// Selects which Merkle hash suite the prover and verifier use.
60///
61/// A hash suite fixes both halves of the Merkle tree at once:
62/// - the leaf hash applied to the committed values, and
63/// - the two-to-one compression that folds a pair of child nodes into their parent.
64///
65/// It does not select a compression function alone, which is why the flag is `--hash-suite`.
66#[derive(Debug, Clone, Copy, ValueEnum)]
67pub enum HashSuiteType {
68	/// SHA-256 leaves with a SHA-256 two-to-one compression.
69	Sha256,
70	/// Blake3 leaves with a Blake3 two-to-one compression.
71	Blake3,
72}
73
74/// Standard verifier using SHA256 compression
75pub type StdVerifier = Verifier<Sha256HashSuite>;
76/// Standard prover using SHA256 compression
77pub type StdProver = Prover<OptimalPackedB128, Sha256HashSuite>;
78/// Standard ZK verifier using SHA256 compression
79pub type StdZKVerifier = ZKVerifier<Sha256HashSuite>;
80/// Standard ZK prover using SHA256 compression
81pub type StdZKProver = ZKProver<OptimalPackedB128, Sha256HashSuite>;
82
83/// Set up a non-ZK prover and verifier for the given constraint system using `H` as the
84/// Merkle hash suite.
85///
86/// Providing `key_collection` skips the expensive key-collection building phase during prover
87/// setup.
88pub fn setup<H>(
89	cs: ConstraintSystem,
90	log_inv_rate: usize,
91	key_collection: Option<KeyCollection>,
92) -> Result<(Verifier<H>, Prover<OptimalPackedB128, H>)>
93where
94	H: ParallelHashSuite + Clone,
95	Output<H::LeafHash>: SerializeBytes + DeserializeBytes,
96{
97	let _setup_guard = tracing::info_span!("Setup", log_inv_rate).entered();
98	let verifier = Verifier::<H>::setup(cs, log_inv_rate)?;
99	let prover = if let Some(key_collection) = key_collection {
100		Prover::setup_with_key_collection(verifier.clone(), key_collection)?
101	} else {
102		Prover::setup(verifier.clone())?
103	};
104	Ok((verifier, prover))
105}
106
107/// Set up a ZK prover and verifier for the given constraint system using `H` as the Merkle
108/// hash suite.
109pub fn setup_zk<H>(
110	cs: ConstraintSystem,
111	log_inv_rate: usize,
112) -> Result<(ZKVerifier<H>, ZKProver<OptimalPackedB128, H>)>
113where
114	H: ParallelHashSuite + Clone,
115	Output<H::LeafHash>: SerializeBytes + DeserializeBytes,
116{
117	let _setup_guard = tracing::info_span!("ZK setup", log_inv_rate).entered();
118	let verifier = ZKVerifier::<H>::setup(cs, log_inv_rate)?;
119	let prover = ZKProver::setup(&verifier)?;
120	Ok((verifier, prover))
121}
122
123/// Set up only the verifier (no prover) for the given constraint system using `H` as the Merkle
124/// hash suite. Cheaper than `setup` when proving is not needed.
125pub fn setup_verifier<H>(cs: ConstraintSystem, log_inv_rate: usize) -> Result<Verifier<H>>
126where
127	H: ParallelHashSuite + Clone,
128	Output<H::LeafHash>: SerializeBytes + DeserializeBytes,
129{
130	let _setup_guard = tracing::info_span!("Setup", log_inv_rate).entered();
131	Ok(Verifier::<H>::setup(cs, log_inv_rate)?)
132}
133
134/// Set up only the ZK verifier (no prover) for the given constraint system using `H` as the
135/// Merkle hash suite. Cheaper than `setup_zk` when proving is not needed.
136pub fn setup_zk_verifier<H>(cs: ConstraintSystem, log_inv_rate: usize) -> Result<ZKVerifier<H>>
137where
138	H: ParallelHashSuite + Clone,
139	Output<H::LeafHash>: SerializeBytes + DeserializeBytes,
140{
141	let _setup_guard = tracing::info_span!("ZK setup", log_inv_rate).entered();
142	Ok(ZKVerifier::<H>::setup(cs, log_inv_rate)?)
143}
144
145/// Run the prover and return the raw proof transcript bytes.
146pub fn create_proof<H>(prover: &Prover<OptimalPackedB128, H>, witness: &ValueVec) -> Result<Vec<u8>>
147where
148	H: ParallelHashSuite,
149	Output<H::LeafHash>: SerializeBytes + DeserializeBytes,
150{
151	let challenger = StdChallenger::default();
152	let mut prover_transcript = ProverTranscript::new(challenger);
153	prover.prove(witness, &mut prover_transcript)?;
154	Ok(prover_transcript.finalize())
155}
156
157/// Run the ZK prover and return the raw proof transcript bytes.
158pub fn create_proof_zk<H>(
159	prover: &ZKProver<OptimalPackedB128, H>,
160	witness: &ValueVec,
161	message: Option<&[u8]>,
162) -> Result<Vec<u8>>
163where
164	H: ParallelHashSuite,
165	Output<H::LeafHash>: SerializeBytes + DeserializeBytes,
166{
167	let challenger = StdChallenger::default();
168	let _scope = tracing::info_span!("Prove").entered();
169	let mut prover_transcript = ProverTranscript::new(challenger);
170	let mut rng = rand::rng();
171	match message {
172		Some(message) => prover.prove_sig(witness, message, &mut rng, &mut prover_transcript)?,
173		None => prover.prove(witness, &mut rng, &mut prover_transcript)?,
174	}
175	Ok(prover_transcript.finalize())
176}
177
178/// Verify a proof given its raw transcript bytes.
179pub fn check_proof<H>(
180	verifier: &Verifier<H>,
181	witness: &ValueVec,
182	proof_bytes: Vec<u8>,
183) -> Result<()>
184where
185	H: ParallelHashSuite,
186	Output<H::LeafHash>: SerializeBytes + DeserializeBytes,
187{
188	let challenger = StdChallenger::default();
189	let mut verifier_transcript = VerifierTranscript::new(challenger, proof_bytes);
190	verifier.verify(witness.inout(), &mut verifier_transcript)?;
191	verifier_transcript.finalize()?;
192	Ok(())
193}
194
195/// Verify a ZK proof given its raw transcript bytes.
196pub fn check_proof_zk<H>(
197	verifier: &ZKVerifier<H>,
198	witness: &ValueVec,
199	proof_bytes: Vec<u8>,
200	message: Option<&[u8]>,
201) -> Result<()>
202where
203	H: ParallelHashSuite,
204	Output<H::LeafHash>: SerializeBytes + DeserializeBytes,
205{
206	let challenger = StdChallenger::default();
207	let _scope = tracing::info_span!("Verify").entered();
208	let mut verifier_transcript = VerifierTranscript::new(challenger, proof_bytes);
209	match message {
210		Some(message) => verifier.verify_sig(witness.inout(), message, &mut verifier_transcript)?,
211		None => verifier.verify(witness.inout(), &mut verifier_transcript)?,
212	}
213	verifier_transcript.finalize()?;
214	Ok(())
215}
216
217pub fn prove_verify<H>(
218	verifier: &Verifier<H>,
219	prover: &Prover<OptimalPackedB128, H>,
220	witness: &ValueVec,
221) -> Result<()>
222where
223	H: ParallelHashSuite,
224	Output<H::LeafHash>: SerializeBytes + DeserializeBytes,
225{
226	let proof_bytes = create_proof(prover, witness)?;
227	tracing::info!("Proof size: {} KiB", proof_bytes.len() / 1024);
228	check_proof(verifier, witness, proof_bytes)?;
229	Ok(())
230}
231
232pub fn prove_verify_zk<H>(
233	verifier: &ZKVerifier<H>,
234	prover: &ZKProver<OptimalPackedB128, H>,
235	witness: &ValueVec,
236	message: Option<&[u8]>,
237) -> Result<()>
238where
239	H: ParallelHashSuite,
240	Output<H::LeafHash>: SerializeBytes + DeserializeBytes,
241{
242	let proof_bytes = create_proof_zk(prover, witness, message)?;
243	tracing::info!("Proof size: {} KiB", proof_bytes.len() / 1024);
244	check_proof_zk(verifier, witness, proof_bytes, message)?;
245	Ok(())
246}
247
248/// Trait for standardizing circuit examples in the Binius framework.
249///
250/// This trait provides a common pattern for implementing circuit examples by separating:
251/// - **Circuit parameters** (`Params`): compile-time configuration that affects circuit structure
252/// - **Instance data** (`Instance`): runtime data used to populate the witness
253/// - **Circuit building**: logic to construct the circuit based on parameters
254/// - **Witness population**: logic to fill in witness values based on instance data
255///
256/// # Example Implementation
257///
258/// ```rust,ignore
259/// struct MyExample {
260///     params: MyParams,
261///     // Store any gadgets or wire references needed for witness population
262/// }
263///
264/// #[derive(clap::Args)]
265/// struct MyParams {
266///     #[arg(long)]
267///     max_size: usize,
268/// }
269///
270/// #[derive(clap::Args)]
271/// struct MyInstance {
272///     #[arg(long)]
273///     input_value: Option<String>,
274/// }
275///
276/// impl ExampleCircuit for MyExample {
277///     type Params = MyParams;
278///     type Instance = MyInstance;
279///
280///     fn build(params: MyParams, builder: &mut CircuitBuilder) -> Result<Self> {
281///         // Construct circuit based on parameters
282///         Ok(Self { params })
283///     }
284///
285///     fn populate_witness(&self, instance: MyInstance, filler: &mut WitnessFiller) -> Result<()> {
286///         // Fill witness values based on instance data
287///         Ok(())
288///     }
289/// }
290/// ```
291///
292/// # Lifecycle
293///
294/// 1. Parse CLI arguments to get `Params` and `Instance`
295/// 2. Call `build()` with parameters to construct the circuit
296/// 3. Build the constraint system
297/// 4. Set up prover and verifier
298/// 5. Call `populate_witness()` to fill witness values
299/// 6. Generate and verify proof
300pub trait ExampleCircuit: Sized {
301	/// Circuit parameters that affect the structure of the circuit.
302	/// These are typically compile-time constants or bounds.
303	type Params: clap::Args;
304
305	/// Instance data used to populate the witness.
306	/// This represents the actual input values for a specific proof.
307	type Instance: clap::Args;
308
309	/// Build the circuit with the given parameters.
310	///
311	/// This method should:
312	/// - Add witnesses, constants, and constraints to the builder
313	/// - Store any wire references needed for witness population
314	/// - Return a Self instance that can later populate witness values
315	fn build(params: Self::Params, builder: &mut CircuitBuilder) -> Result<Self>;
316
317	/// Populate witness values for a specific instance.
318	///
319	/// This method should:
320	/// - Process the instance data (e.g., parse inputs, compute hashes)
321	/// - Fill all witness values using the provided filler
322	/// - Validate that instance data is compatible with circuit parameters
323	fn populate_witness(
324		&self,
325		instance: Self::Instance,
326		filler: &mut WitnessFiller<'_>,
327	) -> Result<()>;
328
329	/// Generate a concise parameter summary for perfetto trace filenames.
330	///
331	/// This method should return a short string (5-10 chars max) that captures
332	/// the most important parameters for this circuit configuration.
333	/// Used to differentiate traces with different parameter settings.
334	///
335	/// Format suggestions:
336	/// - Bytes: "2048b", "4096b"
337	/// - Counts: "10p" (permutations), "5s" (signatures)
338	///
339	/// Returns None if no meaningful parameters to include in filename.
340	fn param_summary(params: &Self::Params) -> Option<String> {
341		let _ = params;
342		None
343	}
344}