Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 2 additions & 3 deletions circuit-prover/src/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
//! detection of the binomial parameter `W` for extension-field multiplication.

use alloc::vec;
use alloc::vec::Vec;

use p3_circuit::tables::Traces;
use p3_circuit::{CircuitBuilderError, CircuitError};
Expand Down Expand Up @@ -209,7 +208,7 @@ where
fn prove_for_degree<EF, const D: usize>(
&self,
traces: &Traces<EF>,
pis: &Vec<Val<SC>>,
pis: &[Val<SC>],
Copy link
Contributor Author

@4l0n50 4l0n50 Nov 7, 2025

Choose a reason for hiding this comment

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

This (and other changes) have nothing to do with the PR, but clippy was complaining

Copy link
Contributor

Choose a reason for hiding this comment

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

yes it's been merged on the Plonky3 side

w_binomial: Option<Val<SC>>,
) -> Result<MultiTableProof<SC>, ProverError>
where
Expand Down Expand Up @@ -293,7 +292,7 @@ where
fn verify_for_degree<const D: usize>(
&self,
proof: &MultiTableProof<SC>,
pis: &Vec<Val<SC>>,
pis: &[Val<SC>],
w_binomial: Option<Val<SC>>,
) -> Result<(), ProverError> {
let table_packing = proof.table_packing;
Expand Down
1 change: 1 addition & 0 deletions circuit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ p3-koala-bear.workspace = true
p3-matrix.workspace = true
p3-symmetric.workspace = true
p3-uni-stark.workspace = true
p3-util.workspace = true
rand.workspace = true

# Other common dependencies
Expand Down
66 changes: 56 additions & 10 deletions circuit/src/builder/circuit_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ use itertools::zip_eq;
use p3_field::{Field, PrimeCharacteristicRing};

use super::compiler::{ExpressionLowerer, NonPrimitiveLowerer, Optimizer};
use super::{BuilderConfig, ExpressionBuilder, PublicInputTracker};
use super::{BuilderConfig, ExpressionBuilder};
use crate::CircuitBuilderError;
use crate::builder::public_input_tracker::PublicInputTracker;
use crate::circuit::Circuit;
use crate::op::NonPrimitiveOpType;
use crate::op::{DefaultHint, NonPrimitiveOpType, WitnessHintFiller};
use crate::ops::MmcsVerifyConfig;
use crate::types::{ExprId, NonPrimitiveOpId, WitnessAllocator, WitnessId};

Expand Down Expand Up @@ -120,16 +121,27 @@ where
self.public_tracker.count()
}

/// Allocates a witness hint (uninitialized witness slot set during non-primitive execution).
/// Allocates multiple witnesses. Witness hints are placeholders for values that will later be provided by a
/// `filler`.
#[must_use]
pub fn alloc_witness_hint(&mut self, label: &'static str) -> ExprId {
self.expr_builder.add_witness_hint(label)
pub fn alloc_witness_hints<W: 'static + WitnessHintFiller<F>>(
&mut self,
filler: W,
label: &'static str,
) -> Vec<ExprId> {
self.expr_builder.add_witness_hints(filler, label)
}

/// Allocates multiple witness hints.
/// Allocates multiple witness hints without saying how they should be filled
/// TODO: Remove this function
#[must_use]
pub fn alloc_witness_hints(&mut self, count: usize, label: &'static str) -> Vec<ExprId> {
self.expr_builder.add_witness_hints(count, label)
pub fn alloc_witness_hints_no_filler(
&mut self,
count: usize,
label: &'static str,
) -> Vec<ExprId> {
self.expr_builder
.add_witness_hints(DefaultHint { n_outputs: count }, label)
}

/// Adds a constant to the circuit (deduplicated).
Expand Down Expand Up @@ -394,6 +406,7 @@ where
self.expr_builder.graph(),
self.expr_builder.pending_connects(),
self.public_tracker.count(),
self.expr_builder.hints_with_fillers(),
self.witness_alloc,
);
let (primitive_ops, public_rows, expr_to_widx, public_mappings, witness_count) =
Expand Down Expand Up @@ -423,11 +436,9 @@ where
#[cfg(test)]
mod tests {
use alloc::vec;
use alloc::vec::Vec;

use p3_baby_bear::BabyBear;
use p3_field::PrimeCharacteristicRing;
use proptest::prelude::*;

use super::*;

Expand Down Expand Up @@ -700,6 +711,41 @@ mod tests {
assert_eq!(circuit.primitive_ops.len(), 2);
}

#[test]
fn test_build_with_witness_hint() {
let mut builder = CircuitBuilder::<BabyBear>::new();
let default_hint = DefaultHint { n_outputs: 1 };
let a = builder.alloc_witness_hints(default_hint, "a");
assert_eq!(a.len(), 1);
let circuit = builder
.build()
.expect("Circuit with operations should build");

assert_eq!(circuit.witness_count, 2);
assert_eq!(circuit.primitive_ops.len(), 2);

match &circuit.primitive_ops[1] {
crate::op::Op::Unconstrained {
inputs, outputs, ..
} => {
assert_eq!(*inputs, vec![]);
assert_eq!(*outputs, vec![WitnessId(1)]);
}
_ => panic!("Expected Unconstrained at index 0"),
}
}
}

#[cfg(test)]
mod proptests {
use alloc::vec;

use p3_baby_bear::BabyBear;
use p3_field::PrimeCharacteristicRing;
use proptest::prelude::*;

use super::*;

// Strategy for generating valid field elements
fn field_element() -> impl Strategy<Value = BabyBear> {
any::<u64>().prop_map(BabyBear::from_u64)
Expand Down
57 changes: 47 additions & 10 deletions circuit/src/builder/compiler/expression_lowerer.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use alloc::boxed::Box;
use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;

Expand All @@ -8,6 +10,7 @@ use crate::Op;
use crate::builder::CircuitBuilderError;
use crate::builder::compiler::get_witness_id;
use crate::expr::{Expr, ExpressionGraph};
use crate::op::WitnessHintFiller;
use crate::types::{ExprId, WitnessAllocator, WitnessId};

/// Sparse disjoint-set "find" with path compression over a HashMap (iterative).
Expand Down Expand Up @@ -70,6 +73,9 @@ pub struct ExpressionLowerer<'a, F> {
/// Number of public inputs
public_input_count: usize,

/// The hint witnesses with their respective filler
hints_fillers: &'a [Box<dyn WitnessHintFiller<F>>],

/// Witness allocator
witness_alloc: WitnessAllocator,
}
Expand All @@ -83,12 +89,14 @@ where
graph: &'a ExpressionGraph<F>,
pending_connects: &'a [(ExprId, ExprId)],
public_input_count: usize,
hints_fillers: &'a [Box<dyn WitnessHintFiller<F>>],
witness_alloc: WitnessAllocator,
) -> Self {
Self {
graph,
pending_connects,
public_input_count,
hints_fillers,
witness_alloc,
}
}
Expand Down Expand Up @@ -162,7 +170,7 @@ where
}
};

// Pass B: emit public inputs
// Pass B: emit public inputs and process witness hints
for (expr_idx, expr) in self.graph.nodes().iter().enumerate() {
if let Expr::Public(pos) = expr {
let id = ExprId(expr_idx as u32);
Expand All @@ -179,17 +187,42 @@ where
}
}

// Pass C: emit arithmetic ops in creation order; tie outputs to class slot if connected
// Pass C: emit arithmetic and unconstrained ops in creation order; tie outputs to class slot if connected
let mut hints_sequence = vec![];
let mut fillers_iter = self.hints_fillers.iter().cloned();
for (expr_idx, expr) in self.graph.nodes().iter().enumerate() {
let expr_id = ExprId(expr_idx as u32);
match expr {
Expr::Const(_) | Expr::Public(_) => { /* handled above */ }
Expr::Witness => {
// Allocate a fresh witness slot (non-primitive op)
// Allows non-primitive operations to set values during execution that
// are not part of the central Witness bus.
Expr::Witness { last_hint } => {
let expr_id = ExprId(expr_idx as u32);
let out_widx = alloc_witness_id_for_expr(expr_idx);
expr_to_widx.insert(expr_id, out_widx);
hints_sequence.push(out_widx);
if *last_hint {
let filler = fillers_iter.next().expect(
"By construction, every sequence of witness must haver one filler",
);
let inputs = filler
.inputs()
.iter()
.map(|expr_id| {
expr_to_widx
.get(expr_id)
.ok_or(CircuitBuilderError::MissingExprMapping {
expr_id: *expr_id,
context: "Unconstrained op".to_string(),
})
.copied()
})
.collect::<Result<Vec<WitnessId>, _>>()?;
primitive_ops.push(Op::Unconstrained {
inputs,
outputs: hints_sequence,
filler,
});
hints_sequence = vec![];
}
}
Expr::Add { lhs, rhs } => {
let out_widx = alloc_witness_id_for_expr(expr_idx);
Expand Down Expand Up @@ -394,9 +427,10 @@ mod tests {
let quot = graph.add_expr(Expr::Div { lhs: diff, rhs: p2 });

let connects = vec![];
let hints_fillers = vec![];
let alloc = WitnessAllocator::new();

let lowerer = ExpressionLowerer::new(&graph, &connects, 3, alloc);
let lowerer = ExpressionLowerer::new(&graph, &connects, 3, &hints_fillers, alloc);
let (prims, public_rows, expr_map, public_map, witness_count) = lowerer.lower().unwrap();

// Verify Primitives
Expand Down Expand Up @@ -563,9 +597,10 @@ mod tests {
// Group B: p1 ~ p2 ~ p3 (transitive)
// Group C: sum ~ p4 (operation result shared)
let connects = vec![(c_42, p0), (p1, p2), (p2, p3), (sum, p4)];
let hints_fillers = vec![];
let alloc = WitnessAllocator::new();

let lowerer = ExpressionLowerer::new(&graph, &connects, 5, alloc);
let lowerer = ExpressionLowerer::new(&graph, &connects, 5, &hints_fillers, alloc);
let (prims, public_rows, expr_map, public_map, witness_count) = lowerer.lower().unwrap();

// Verify Primitives
Expand Down Expand Up @@ -703,8 +738,9 @@ mod tests {
});

let connects = vec![];
let hints_fillers = vec![];
let alloc = WitnessAllocator::new();
let lowerer = ExpressionLowerer::new(&graph, &connects, 0, alloc);
let lowerer = ExpressionLowerer::new(&graph, &connects, 0, &hints_fillers, alloc);
let result = lowerer.lower();

assert!(result.is_err());
Expand All @@ -724,8 +760,9 @@ mod tests {
});

let connects = vec![];
let hints_fillers = vec![];
let alloc = WitnessAllocator::new();
let lowerer = ExpressionLowerer::new(&graph, &connects, 0, alloc);
let lowerer = ExpressionLowerer::new(&graph, &connects, 0, &hints_fillers, alloc);
let result = lowerer.lower();

assert!(result.is_err());
Expand Down
Loading
Loading