use comp_cat_rs::collapse::free_category::Path;
use crate::constraint::{Constraint, CopyConstraint, ConstraintSet};
use crate::error::Error;
use crate::expr::Expression;
use crate::field::Field;
use crate::gate::{PlonkishGraph, PrimitiveGate};
use crate::wire::{WireAllocator, WireCount, WireRange};
fn interpret_gate<F: Field>(
gate: &PrimitiveGate<F>,
input: &WireRange,
output: &WireRange,
) -> Result<ConstraintSet<F>, Error> {
match gate {
PrimitiveGate::Add => {
let in0 = Expression::wire(input.wire_at(0)?);
let in1 = Expression::wire(input.wire_at(1)?);
let out = Expression::wire(output.wire_at(0)?);
Ok(ConstraintSet::empty()
.with_constraint(Constraint::new(out - (in0 + in1))))
}
PrimitiveGate::Mul => {
let in0 = Expression::wire(input.wire_at(0)?);
let in1 = Expression::wire(input.wire_at(1)?);
let out = Expression::wire(output.wire_at(0)?);
Ok(ConstraintSet::empty()
.with_constraint(Constraint::new(out - in0 * in1)))
}
PrimitiveGate::Const(c) => {
let out = Expression::wire(output.wire_at(0)?);
Ok(ConstraintSet::empty()
.with_constraint(Constraint::new(out - Expression::constant(c.clone()))))
}
PrimitiveGate::Bool => {
let inp = Expression::wire(input.wire_at(0)?);
let one = Expression::constant(F::one());
let bool_check = inp.clone() * (one - inp);
Ok(ConstraintSet::empty()
.with_constraint(Constraint::new(bool_check))
.with_copy(CopyConstraint::new(
input.wire_at(0)?,
output.wire_at(0)?,
)))
}
PrimitiveGate::Dup => {
let inp = input.wire_at(0)?;
let out0 = output.wire_at(0)?;
let out1 = output.wire_at(1)?;
Ok(ConstraintSet::empty()
.with_copy(CopyConstraint::new(inp, out0))
.with_copy(CopyConstraint::new(inp, out1)))
}
}
}
struct CompileState<F: Field> {
allocator: WireAllocator,
constraints: ConstraintSet<F>,
prev_output: Option<WireRange>,
}
pub fn compile<F: Field>(
graph: &PlonkishGraph<F>,
path: &Path,
) -> Result<ConstraintSet<F>, Error> {
if path.is_empty() {
return Ok(ConstraintSet::empty());
}
let initial = CompileState {
allocator: WireAllocator::new(),
constraints: ConstraintSet::empty(),
prev_output: None,
};
let result = path
.edges()
.iter()
.try_fold(initial, |state, edge| -> Result<CompileState<F>, Error> {
let spec = graph.gate_spec_at(*edge)?;
let gate = spec.gate();
let (input, allocator) = state.prev_output.map_or_else(
|| -> Result<(WireRange, WireAllocator), Error> {
let input_count = graph.wire_count_at(spec.source())?;
Ok(state.allocator.allocate(input_count))
},
|prev| Ok((prev, state.allocator)),
)?;
let output_count = graph.wire_count_at(spec.target())?;
let (output, allocator) = allocator.allocate(output_count);
let gate_constraints = interpret_gate(gate, &input, &output)?;
Ok(CompileState {
allocator,
constraints: state.constraints.merge(gate_constraints),
prev_output: Some(output),
})
})?;
Ok(result.constraints)
}
pub fn compile_with_io<F: Field>(
graph: &PlonkishGraph<F>,
path: &Path,
) -> Result<(ConstraintSet<F>, WireRange, WireRange), Error> {
if path.is_empty() {
let empty_range = WireRange::new(crate::wire::Wire::new(0), WireCount::new(0));
return Ok((ConstraintSet::empty(), empty_range, empty_range));
}
let initial_input_count = path
.edges()
.first()
.map_or(Ok(WireCount::new(0)), |e| {
let spec = graph.gate_spec_at(*e)?;
graph.wire_count_at(spec.source())
})?;
let allocator = WireAllocator::new();
let (input_range, allocator) = allocator.allocate(initial_input_count);
let initial = CompileState {
allocator,
constraints: ConstraintSet::empty(),
prev_output: Some(input_range),
};
let result = path
.edges()
.iter()
.try_fold(initial, |state, edge| -> Result<CompileState<F>, Error> {
let spec = graph.gate_spec_at(*edge)?;
let gate = spec.gate();
let input = state.prev_output.unwrap_or(
WireRange::new(crate::wire::Wire::new(0), WireCount::new(0)),
);
let output_count = graph.wire_count_at(spec.target())?;
let (output, allocator) = state.allocator.allocate(output_count);
let gate_constraints = interpret_gate(gate, &input, &output)?;
Ok(CompileState {
allocator,
constraints: state.constraints.merge(gate_constraints),
prev_output: Some(output),
})
})?;
let output_range = result.prev_output.unwrap_or(
WireRange::new(crate::wire::Wire::new(0), WireCount::new(0)),
);
Ok((result.constraints, input_range, output_range))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field::F101;
use crate::wire::Wire;
use comp_cat_rs::collapse::free_category::{Edge, Path};
fn make_assignment(values: &[(usize, u64)]) -> impl Fn(Wire) -> Result<F101, Error> + '_ {
move |w| {
values
.iter()
.find(|(i, _)| *i == w.index())
.map(|(_, v)| F101::new(*v))
.ok_or(Error::WireOutOfBounds {
wire_index: w.index(),
allocated: values.len(),
})
}
}
#[test]
fn compile_identity_path() -> Result<(), Error> {
let graph = PlonkishGraph::<F101>::standard();
let path = Path::identity(comp_cat_rs::collapse::free_category::Vertex::new(1));
let cs = compile(&graph, &path)?;
assert_eq!(cs.constraints().len(), 0);
assert_eq!(cs.copy_constraints().len(), 0);
Ok(())
}
#[test]
fn compile_single_add_gate() -> Result<(), Error> {
let graph = PlonkishGraph::<F101>::standard();
let path = Path::singleton(&graph, Edge::new(0))?;
let (cs, input, output) = compile_with_io(&graph, &path)?;
assert_eq!(input.count(), WireCount::new(2));
assert_eq!(output.count(), WireCount::new(1));
assert_eq!(cs.constraints().len(), 1);
let assignment = make_assignment(&[(0, 3), (1, 5), (2, 8)]);
assert!(cs.is_satisfied(&assignment)?);
let bad_assignment = make_assignment(&[(0, 3), (1, 5), (2, 7)]);
assert!(!cs.is_satisfied(&bad_assignment)?);
Ok(())
}
#[test]
fn compile_single_mul_gate() -> Result<(), Error> {
let graph = PlonkishGraph::<F101>::standard();
let path = Path::singleton(&graph, Edge::new(1))?;
let cs = compile(&graph, &path)?;
let assignment = make_assignment(&[(0, 3), (1, 5), (2, 15)]);
assert!(cs.is_satisfied(&assignment)?);
Ok(())
}
#[test]
fn compile_dup_then_mul_is_square() -> Result<(), Error> {
let graph = PlonkishGraph::<F101>::standard();
let dup = Path::singleton(&graph, Edge::new(3))?; let mul = Path::singleton(&graph, Edge::new(1))?; let square = dup.compose(mul)?;
let (cs, input, output) = compile_with_io(&graph, &square)?;
assert_eq!(input.count(), WireCount::new(1));
assert_eq!(output.count(), WireCount::new(1));
assert_eq!(cs.copy_constraints().len(), 2);
assert_eq!(cs.constraints().len(), 1);
let assignment = make_assignment(&[(0, 3), (1, 3), (2, 3), (3, 9)]);
assert!(cs.is_satisfied(&assignment)?);
let bad = make_assignment(&[(0, 3), (1, 3), (2, 3), (3, 10)]);
assert!(!bad_satisfied(&cs, &bad)?);
Ok(())
}
fn bad_satisfied(
cs: &ConstraintSet<F101>,
assignment: &dyn Fn(Wire) -> Result<F101, Error>,
) -> Result<bool, Error> {
cs.is_satisfied(assignment)
}
#[test]
fn compile_const_gate() -> Result<(), Error> {
let graph = PlonkishGraph::<F101>::standard();
let (graph, const_edge) = graph.with_const(F101::new(42));
let path = Path::singleton(&graph, const_edge)?;
let cs = compile(&graph, &path)?;
let assignment = make_assignment(&[(0, 42)]);
assert!(cs.is_satisfied(&assignment)?);
let bad = make_assignment(&[(0, 41)]);
assert!(!cs.is_satisfied(&bad)?);
Ok(())
}
#[test]
fn compile_bool_gate() -> Result<(), Error> {
let graph = PlonkishGraph::<F101>::standard();
let path = Path::singleton(&graph, Edge::new(2))?;
let cs = compile(&graph, &path)?;
let zero = make_assignment(&[(0, 0), (1, 0)]);
assert!(cs.is_satisfied(&zero)?);
let one = make_assignment(&[(0, 1), (1, 1)]);
assert!(cs.is_satisfied(&one)?);
let two = make_assignment(&[(0, 2), (1, 2)]);
assert!(!cs.is_satisfied(&two)?);
Ok(())
}
}