To create a new circuit example in Binius, implement the ExampleCircuit trait. This requires defining two associated types, Params (for compile-time configuration like maximum sizes) and Instance (for runtime witness data like user inputs), and implementing two methods: build and populate_witness.
Implementation Steps:
- Define
Params: A struct using #[derive(Args)] for circuit structure configuration (e.g., max_size). - Define
Instance: A struct using #[derive(Args)] for runtime witness data (e.g., input values). - Implement
build: Use the CircuitBuilder to add witnesses, constants, and gadgets. Store any necessary references in your main example struct for later use. - Implement
populate_witness: Process the Instance data, validate it against Params, and populate the witness using the stored references. - Register: Add the circuit to your
main.rs using .circuit::<MyCircuitExample>("name", "description").
use anyhow::{ensure, Result};
use binius_examples::ExampleCircuit;
use binius_frontend::compiler::{circuit::WitnessFiller, CircuitBuilder, Wire};
use clap::Args;
struct MyCircuitExample {
params: Params,
}
#[derive(Args, Debug)]
struct Params {
#[arg(long, default_value_t = 1024)]
max_size: usize,
#[arg(long, default_value_t = false)]
optimized: bool,
}
#[derive(Args, Debug)]
struct Instance {
#[arg(long)]
input: Option<String>,
#[arg(long)]
size: Option<usize>,
}
impl ExampleCircuit for MyCircuitExample {
type Params = Params;
type Instance = Instance;
fn build(params: Params, builder: &mut CircuitBuilder) -> Result<Self> {
// 1. Add witnesses: builder.add_witness()
// 2. Add constants: builder.add_constant_64()
// 3. Create inouts: builder.add_inout()
Ok(Self { params })
}
fn populate_witness(&self, instance: Instance, w: &mut WitnessFiller) -> Result<()> {
// Process and validate instance data, then populate witness
Ok(())
}
}