use std::marker::PhantomData; use crate::circuit_data::CircuitConfig; use crate::constraint_polynomial::{ConstraintPolynomial, EvaluationVars}; use crate::field::field::Field; use crate::gates::gate::Gate; use crate::generator::{SimpleGenerator, WitnessGenerator}; use crate::target::Target; use crate::wire::Wire; use crate::witness::PartialWitness; use crate::gates::output_graph::{OutputGraph, GateOutputLocation}; /// A deterministic gate. Each entry in `outputs()` describes how that output is evaluated; this is /// used to create both the constraint set and the generator set. /// /// `DeterministicGate`s do not automatically implement `Gate`; they should instead be wrapped in /// `DeterministicGateAdapter`. pub trait DeterministicGate: 'static { /// A unique identifier for this gate. fn id(&self) -> String; /// A vector of `(loc, out)` pairs, where `loc` is the location of an output and `out` is a /// polynomial defining how that output is evaluated. fn outputs(&self, config: CircuitConfig) -> OutputGraph; /// Any additional constraints to be enforced, besides the (automatically provided) ones that /// constraint output values. fn additional_constraints(&self, _config: CircuitConfig) -> Vec> { Vec::new() } /// Any additional generators, besides the (automatically provided) ones that generate output /// values. fn additional_generators( &self, _config: CircuitConfig, _gate_index: usize, local_constants: Vec, next_constants: Vec, ) -> Vec>> { Vec::new() } } /// A wrapper around `DeterministicGate` which implements `Gate`. Note that a blanket implementation /// is not possible in this context given Rust's coherence rules. pub struct DeterministicGateAdapter + ?Sized> { gate: Box, _phantom: PhantomData, } impl> DeterministicGateAdapter { pub fn new(gate: DG) -> Self { Self { gate: Box::new(gate), _phantom: PhantomData } } } impl> Gate for DeterministicGateAdapter { fn id(&self) -> String { self.gate.id() } fn constraints(&self, config: CircuitConfig) -> Vec> { // For each output, we add a constraint of the form `out - expression = 0`, // then we append any additional constraints that the gate defines. self.gate.outputs(config).outputs.into_iter() .map(|(output_loc, out)| out - ConstraintPolynomial::from_gate_output(output_loc)) .chain(self.gate.additional_constraints(config).into_iter()) .collect() } fn generators( &self, config: CircuitConfig, gate_index: usize, local_constants: Vec, next_constants: Vec, ) -> Vec>> { self.gate.outputs(config).outputs .into_iter() .map(|(location, out)| { let og = OutputGenerator { gate_index, location, out, local_constants: local_constants.clone(), next_constants: next_constants.clone(), }; // We need the type system to treat this as a boxed `WitnessGenerator2`, rather // than a boxed `OutputGenerator`. let b: Box::> = Box::new(og); b }) .chain(self.gate.additional_generators( config, gate_index, local_constants.clone(), next_constants.clone())) .collect() } } #[derive(Debug)] struct OutputGenerator { gate_index: usize, location: GateOutputLocation, out: ConstraintPolynomial, local_constants: Vec, next_constants: Vec, } impl SimpleGenerator for OutputGenerator { fn dependencies(&self) -> Vec { self.out.dependencies(self.gate_index) .into_iter() .map(Target::Wire) .collect() } fn run_once(&self, witness: &PartialWitness) -> PartialWitness { let mut local_wire_values = Vec::new(); let mut next_wire_values = Vec::new(); // Get an exclusive upper bound on the largest input index in this constraint. let input_limit_exclusive = self.out.max_wire_input_index() .map_or(0, |i| i + 1); for input in 0..input_limit_exclusive { let local_wire = Wire { gate: self.gate_index, input }; let next_wire = Wire { gate: self.gate_index + 1, input }; // Lookup the values if they exist. If not, we can just insert a zero, knowing // that it will not be used. (If it was used, it would have been included in our // dependencies, and this generator would not have run yet.) let local_value = witness.try_get_target(Target::Wire(local_wire)).unwrap_or(F::ZERO); let next_value = witness.try_get_target(Target::Wire(next_wire)).unwrap_or(F::ZERO); local_wire_values.push(local_value); next_wire_values.push(next_value); } let vars = EvaluationVars { local_constants: &self.local_constants, next_constants: &self.next_constants, local_wires: &local_wire_values, next_wires: &next_wire_values, }; let result_wire = match self.location { GateOutputLocation::LocalWire(input) => Wire { gate: self.gate_index, input }, GateOutputLocation::NextWire(input) => Wire { gate: self.gate_index + 1, input }, }; let result_value = self.out.evaluate(vars); PartialWitness::singleton(Target::Wire(result_wire), result_value) } }