Skip to content
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions circuit-prover/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,7 @@ path = "examples/fibonacci.rs"
[[example]]
name = "fake_merkle_verify"
path = "examples/fake_merkle_verify.rs"

[[example]]
name = "hash"
path = "examples/hash.rs"
3 changes: 2 additions & 1 deletion circuit-prover/examples/fake_merkle_verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::env;
/// Private inputs: merkle path (siblings + directions)
use p3_baby_bear::BabyBear;
use p3_circuit::builder::CircuitBuilder;
use p3_circuit::tables::DummyPerm;
use p3_circuit::{FakeMerklePrivateData, MerkleOps, NonPrimitiveOpPrivateData};
use p3_circuit_prover::MultiTableProver;
use p3_circuit_prover::config::babybear_config::build_standard_config_babybear;
Expand Down Expand Up @@ -42,7 +43,7 @@ fn main() -> Result<(), ProverError> {
NonPrimitiveOpPrivateData::FakeMerkleVerify(private_data),
)?;

let traces = runner.run()?;
let traces = runner.run::<DummyPerm, 0, 0, 0>(DummyPerm::default())?;
let config = build_standard_config_babybear();
let multi_prover = MultiTableProver::new(config);
let proof = multi_prover.prove_all_tables(&traces)?;
Expand Down
3 changes: 2 additions & 1 deletion circuit-prover/examples/fibonacci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::env;
/// Public input: expected_result (F(n))
use p3_baby_bear::BabyBear;
use p3_circuit::builder::CircuitBuilder;
use p3_circuit::tables::DummyPerm;
use p3_circuit_prover::config::babybear_config::build_standard_config_babybear;
use p3_circuit_prover::prover::ProverError;
use p3_circuit_prover::{MultiTableProver, TablePacking};
Expand Down Expand Up @@ -42,7 +43,7 @@ fn main() -> Result<(), ProverError> {
let expected_fib = compute_fibonacci_classical(n);
runner.set_public_inputs(&[expected_fib])?;

let traces = runner.run()?;
let traces = runner.run::<DummyPerm, 0, 0, 0>(DummyPerm::default())?;
let config = build_standard_config_babybear();
let table_packing = TablePacking::from_counts(4, 1);
let multi_prover = MultiTableProver::new(config).with_table_packing(table_packing);
Expand Down
62 changes: 62 additions & 0 deletions circuit-prover/examples/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use core::array;

/// Hash verification circuit: prove the correct computation of a hash.
/// Public inputs: inputs, output
use p3_baby_bear::{BabyBear, Poseidon2BabyBear};
use p3_circuit::Challenger;
use p3_circuit::builder::CircuitBuilder;
use p3_circuit_prover::MultiTableProver;
use p3_circuit_prover::config::babybear_config::build_standard_config_babybear;
use p3_circuit_prover::prover::{ProverError, TablePacking};
use p3_symmetric::{CryptographicHasher, PaddingFreeSponge};
use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};

type F = BabyBear;

const HASH_RATE: usize = 8;
const HASH_CAPACITY: usize = 8;
const HASH_STATE_SIZE: usize = HASH_RATE + HASH_CAPACITY;

fn main() -> Result<(), ProverError> {
let mut rng = SmallRng::seed_from_u64(1);
let perm = Poseidon2BabyBear::<HASH_STATE_SIZE>::new_from_rng_128(&mut rng);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could also do default_babybear_poseidon2_16()


let mut builder = CircuitBuilder::<F>::new();

let mut challenger = Challenger::default();

// Public inputs: hash inputs and hash output
let inputs: [_; HASH_STATE_SIZE] = array::from_fn(|_| builder.add_public_input());
let outputs: [_; HASH_RATE] = array::from_fn(|_| builder.add_public_input());

// Add hash operations
challenger.add_inputs(&inputs);
challenger.squeeze(&mut builder, &outputs);

let circuit = builder.build()?;
let mut runner = circuit.runner();

// Generate random inputs
let input_values: [_; 16] = array::from_fn(|_| rng.random::<F>());

// Set public inputs
let hasher = PaddingFreeSponge::<_, HASH_STATE_SIZE, HASH_RATE, HASH_RATE>::new(perm.clone());
let output_values = hasher.hash_iter(input_values);
let public_inputs = input_values
.into_iter()
.chain(output_values)
.collect::<Vec<_>>();
runner.set_public_inputs(&public_inputs)?;

let traces = runner.run::<_, HASH_STATE_SIZE, HASH_RATE, HASH_CAPACITY>(perm)?;
let config = build_standard_config_babybear();
let table_packing = TablePacking::from_counts(4, 1);
let multi_prover = MultiTableProver::new(config).with_table_packing(table_packing);
let proof = multi_prover.prove_all_tables(&traces)?;
multi_prover.verify_all_tables(&proof)?;

println!("✅ Verified hash({:?}) = {:?}", input_values, output_values);

Ok(())
}
11 changes: 6 additions & 5 deletions circuit-prover/src/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ where
mod tests {
use p3_baby_bear::BabyBear;
use p3_circuit::builder::CircuitBuilder;
use p3_circuit::tables::DummyPerm;
use p3_field::extension::BinomialExtensionField;
use p3_field::{BasedVectorSpace, PrimeCharacteristicRing};
use p3_goldilocks::Goldilocks;
Expand Down Expand Up @@ -388,7 +389,7 @@ mod tests {
let x_val = BabyBear::from_u64(7);
let expected_val = BabyBear::from_u64(13); // 7 + 10 - 3 - 1 = 13
runner.set_public_inputs(&[x_val, expected_val])?;
let traces = runner.run()?;
let traces = runner.run::<DummyPerm, 0, 0, 0>(DummyPerm::default())?;

// Create BabyBear prover and prove all tables
let config = build_standard_config_babybear();
Expand Down Expand Up @@ -467,7 +468,7 @@ mod tests {
let expected_val = add_expected - w_val;

runner.set_public_inputs(&[x_val, y_val, z_val, expected_val])?;
let traces = runner.run()?;
let traces = runner.run::<DummyPerm, 0, 0, 0>(DummyPerm::default())?;

// Create BabyBear prover for extension field (D=4)
let config = build_standard_config_babybear();
Expand Down Expand Up @@ -511,7 +512,7 @@ mod tests {
let b_val = KoalaBear::from_u64(13);
let expected_val = KoalaBear::from_u64(647); // 42*13 + 100 - (-1) = 647
runner.set_public_inputs(&[a_val, b_val, expected_val])?;
let traces = runner.run()?;
let traces = runner.run::<DummyPerm, 0, 0, 0>(DummyPerm::default())?;

// Create KoalaBear prover
let config = build_standard_config_koalabear();
Expand Down Expand Up @@ -595,7 +596,7 @@ mod tests {
let expected_val = xy_expected * z_val;

runner.set_public_inputs(&[x_val, y_val, expected_val])?;
let traces = runner.run()?;
let traces = runner.run::<DummyPerm, 0, 0, 0>(DummyPerm::default())?;

// Create KoalaBear prover for extension field (D=8)
let config = build_standard_config_koalabear();
Expand Down Expand Up @@ -650,7 +651,7 @@ mod tests {
let expected_val = x_val * y_val + z_val;
runner.set_public_inputs(&[x_val, y_val, z_val, expected_val])?;

let traces = runner.run()?;
let traces = runner.run::<DummyPerm, 0, 0, 0>(DummyPerm::default())?;

// Build Goldilocks config with challenge degree 2 (Poseidon2)
let config = build_standard_config_goldilocks();
Expand Down
2 changes: 2 additions & 0 deletions circuit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ categories.workspace = true
p3-air.workspace = true
p3-field.workspace = true
p3-matrix.workspace = true
p3-symmetric.workspace = true
p3-uni-stark.workspace = true

# Other common dependencies
hashbrown = "0.16.0"
rand.workspace = true
serde.workspace = true
thiserror.workspace = true

Expand Down
77 changes: 68 additions & 9 deletions circuit/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,26 @@ where
op_id
}

/// Add a hash absorb operation (non-primitive operation)
pub fn add_hash_absorb(&mut self, input_exprs: &[ExprId], reset: bool) -> NonPrimitiveOpId {
let op_id = NonPrimitiveOpId(self.non_primitive_ops.len() as u32);
let witness_exprs = input_exprs.to_vec();
self.non_primitive_ops
.push((op_id, NonPrimitiveOpType::HashAbsorb(reset), witness_exprs));

op_id
}

/// Add a hash squeeze operation (non-primitive operation)
pub fn add_hash_squeeze(&mut self, output_exprs: &[ExprId]) -> NonPrimitiveOpId {
let op_id = NonPrimitiveOpId(self.non_primitive_ops.len() as u32);
let witness_exprs = output_exprs.to_vec();
self.non_primitive_ops
.push((op_id, NonPrimitiveOpType::HashSqueeze, witness_exprs));

op_id
}

/// Check whether an op type is enabled on this builder.
fn is_op_enabled(&self, op: &NonPrimitiveOpType) -> bool {
self.enabled_ops.contains(op)
Expand Down Expand Up @@ -533,7 +553,7 @@ where
let leaf_widx = Self::get_witness_id(
expr_to_widx,
witness_exprs[0],
"FakeMerkleVerify leaf input",
"FakeMerkl let multi_prover = MultiTableProver::new();eVerify leaf input",
)?;
let root_widx = Self::get_witness_id(
expr_to_widx,
Expand All @@ -546,6 +566,27 @@ where
root: root_widx,
});
}
NonPrimitiveOpType::HashAbsorb(reset) => {
let inputs_widx = witness_exprs
.iter()
.map(|expr| Self::get_witness_id(expr_to_widx, *expr, "HashAbsorb input"))
.collect::<Vec<_>>();

lowered_ops.push(NonPrimitiveOp::HashAbsorb {
reset_flag: *reset,
inputs: inputs_widx.into_iter().collect::<Result<Vec<_>, _>>()?,
});
}
NonPrimitiveOpType::HashSqueeze => {
let outputs_widx = witness_exprs
.iter()
.map(|expr| Self::get_witness_id(expr_to_widx, *expr, "HashSqueeze output"))
.collect::<Vec<_>>();

lowered_ops.push(NonPrimitiveOp::HashSqueeze {
outputs: outputs_widx.into_iter().collect::<Result<Vec<_>, _>>()?,
});
}
NonPrimitiveOpType::FriVerify => {
todo!() // TODO: Add FRIVerify when it lands
} // Add more variants here as needed
Expand Down Expand Up @@ -573,6 +614,7 @@ mod tests {

use super::*;
use crate::op::NonPrimitiveOpType;
use crate::tables::DummyPerm;
use crate::{CircuitError, MerkleOps, NonPrimitiveOp};

#[test]
Expand Down Expand Up @@ -688,7 +730,9 @@ mod tests {

runner.set_public_inputs(&[BabyBear::from_u64(5)]).unwrap();
// Should succeed; both write the same value into the shared slot
runner.run().unwrap();
runner
.run::<DummyPerm, 0, 0, 0>(DummyPerm::default())
.unwrap();
}

#[test]
Expand Down Expand Up @@ -858,7 +902,9 @@ mod tests {
runner.set_public_inputs(&[x_val, expected_val]).unwrap();

// Should succeed - constraint is satisfied
let traces = runner.run().unwrap();
let traces = runner
.run::<DummyPerm, 0, 0, 0>(DummyPerm::default())
.unwrap();

// Verify we have the expected number of operations in traces
assert_eq!(traces.add_trace.lhs_values.len(), 2); // Two adds: x+5 and internal sub encoding
Expand Down Expand Up @@ -896,7 +942,9 @@ mod tests {
runner
.set_public_inputs(&[x_val, y_val, z_val, expected_val])
.unwrap();
let traces = runner.run().unwrap();
let traces = runner
.run::<DummyPerm, 0, 0, 0>(DummyPerm::default())
.unwrap();

// Verify traces: should have 2 multiplications (x*y and the div encoding z*result=xy)
assert_eq!(traces.mul_trace.lhs_values.len(), 2);
Expand All @@ -922,7 +970,9 @@ mod tests {
// Test case 1: Equal values - should succeed
let equal_val = BabyBear::from_u64(15);
runner.set_public_inputs(&[equal_val, equal_val]).unwrap();
runner.run().unwrap(); // Should succeed
runner
.run::<DummyPerm, 0, 0, 0>(DummyPerm::default())
.unwrap(); // Should succeed

// Test case 2: Different values - should fail
let mut builder2 = CircuitBuilder::<BabyBear>::new();
Expand All @@ -937,7 +987,9 @@ mod tests {
runner2.set_public_inputs(&[val1, val2]).unwrap();

// Should fail because difference is not zero
let err = runner2.run().unwrap_err();
let err = runner2
.run::<DummyPerm, 0, 0, 0>(DummyPerm::default())
.unwrap_err();
match err {
CircuitError::WitnessConflict { .. } => {} // Expected: can't satisfy x-y=0 when x≠y
other => panic!("Expected WitnessConflict, got {:?}", other),
Expand All @@ -960,6 +1012,7 @@ mod tests {
assert_eq!(circuit.non_primitive_ops.len(), 1);
match &circuit.non_primitive_ops[0] {
NonPrimitiveOp::FakeMerkleVerify { .. } => {}
_ => panic!("Expected FakeMerkleVerify operation"),
}
}

Expand Down Expand Up @@ -999,7 +1052,9 @@ mod tests {
runner
.set_public_inputs(&[shared_val, shared_val, shared_val, shared_val, shared_val])
.unwrap();
runner.run().unwrap(); // Should succeed
runner
.run::<DummyPerm, 0, 0, 0>(DummyPerm::default())
.unwrap(); // Should succeed

// Test with different values - should fail - create new circuit
let mut builder2 = CircuitBuilder::<BabyBear>::new();
Expand Down Expand Up @@ -1063,7 +1118,9 @@ mod tests {

// Should work with any value since x + 0 = x
runner.set_public_inputs(&[BabyBear::from_u64(42)]).unwrap();
runner.run().unwrap();
runner
.run::<DummyPerm, 0, 0, 0>(DummyPerm::default())
.unwrap();
}

#[test]
Expand All @@ -1087,7 +1144,9 @@ mod tests {
// Should enforce x = y
let val = BabyBear::from_u64(123);
runner.set_public_inputs(&[val, val]).unwrap();
runner.run().unwrap(); // Should succeed
runner
.run::<DummyPerm, 0, 0, 0>(DummyPerm::default())
.unwrap(); // Should succeed

// Different values should fail - create new circuit
let mut builder2 = CircuitBuilder::<BabyBear>::new();
Expand Down
Loading
Loading