mirror of
https://github.com/logos-storage/plonky2.git
synced 2026-01-07 16:23:12 +00:00
No more polynomial programming abstraction
It was too expensive.
This commit is contained in:
parent
ba96ab4e99
commit
f42120482a
@ -6,17 +6,17 @@ use log::info;
|
||||
use crate::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData, ProverCircuitData, ProverOnlyCircuitData, VerifierCircuitData, VerifierOnlyCircuitData};
|
||||
use crate::field::fft::lde_multiple;
|
||||
use crate::field::field::Field;
|
||||
use crate::gates::constant::ConstantGate2;
|
||||
use crate::gates::gate::{GateInstance, GateRef};
|
||||
use crate::gates::constant::ConstantGate;
|
||||
use crate::gates::gate::{Gate, GateInstance, GateRef};
|
||||
use crate::gates::noop::NoopGate;
|
||||
use crate::generator::{CopyGenerator, WitnessGenerator};
|
||||
use crate::hash::merkle_root_bit_rev_order;
|
||||
use crate::target::Target;
|
||||
use crate::util::{transpose, log2_strict};
|
||||
use crate::wire::Wire;
|
||||
use crate::gates::noop::NoopGate;
|
||||
use crate::util::transpose;
|
||||
|
||||
pub struct CircuitBuilder<F: Field> {
|
||||
config: CircuitConfig,
|
||||
pub(crate) config: CircuitConfig,
|
||||
|
||||
/// The types of gates used in this circuit.
|
||||
gates: HashSet<GateRef<F>>,
|
||||
@ -55,7 +55,7 @@ impl<F: Field> CircuitBuilder<F> {
|
||||
}
|
||||
|
||||
fn check_gate_compatibility(&self, gate: &GateRef<F>) {
|
||||
assert!(gate.0.min_wires(self.config) <= self.config.num_wires);
|
||||
assert!(gate.0.num_wires() <= self.config.num_wires);
|
||||
}
|
||||
|
||||
/// Shorthand for `generate_copy` and `assert_equal`.
|
||||
@ -103,8 +103,8 @@ impl<F: Field> CircuitBuilder<F> {
|
||||
|
||||
/// Returns a routable target with the given constant value.
|
||||
pub fn constant(&mut self, c: F) -> Target {
|
||||
let gate = self.add_gate(ConstantGate2::get(), vec![c]);
|
||||
Target::Wire(Wire { gate, input: ConstantGate2::WIRE_OUTPUT })
|
||||
let gate = self.add_gate(ConstantGate::get(), vec![c]);
|
||||
Target::Wire(Wire { gate, input: ConstantGate::WIRE_OUTPUT })
|
||||
}
|
||||
|
||||
fn blind_and_pad(&mut self) {
|
||||
@ -119,10 +119,9 @@ impl<F: Field> CircuitBuilder<F> {
|
||||
self.gate_instances.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(gate_index, gate_inst)| gate_inst.gate_type.0.generators(
|
||||
self.config,
|
||||
gate_index,
|
||||
gate_inst.constants.clone(),
|
||||
vec![])) // TODO: Not supporting next_const for now.
|
||||
&gate_inst.constants,
|
||||
&[])) // TODO: Not supporting next_const for now.
|
||||
.collect()
|
||||
}
|
||||
|
||||
@ -157,20 +156,32 @@ impl<F: Field> CircuitBuilder<F> {
|
||||
|
||||
let constant_vecs = self.constant_vecs();
|
||||
let constant_ldes = lde_multiple(constant_vecs, self.config.rate_bits);
|
||||
let constants_root = merkle_root_bit_rev_order(constant_ldes);
|
||||
let constant_ldes_t = transpose(&constant_ldes);
|
||||
let constants_root = merkle_root_bit_rev_order(constant_ldes_t.clone());
|
||||
|
||||
let sigma_vecs = self.sigma_vecs();
|
||||
let sigma_ldes = lde_multiple(sigma_vecs, self.config.rate_bits);
|
||||
let sigmas_root = merkle_root_bit_rev_order(sigma_ldes);
|
||||
let sigmas_root = merkle_root_bit_rev_order(transpose(&sigma_ldes));
|
||||
|
||||
let generators = self.get_generators();
|
||||
let prover_only = ProverOnlyCircuitData { generators };
|
||||
let prover_only = ProverOnlyCircuitData { generators, constant_ldes_t };
|
||||
let verifier_only = VerifierOnlyCircuitData {};
|
||||
|
||||
// The HashSet of gates will have a non-deterministic order. When converting to a Vec, we
|
||||
// sort by ID to make the ordering deterministic.
|
||||
let mut gates = self.gates.iter().cloned().collect::<Vec<_>>();
|
||||
gates.sort_unstable_by_key(|gate| gate.0.id());
|
||||
|
||||
let num_gate_constraints = gates.iter()
|
||||
.map(|gate| gate.0.num_constraints())
|
||||
.max()
|
||||
.expect("No gates?");
|
||||
|
||||
let common = CommonCircuitData {
|
||||
config: self.config,
|
||||
degree,
|
||||
gates: self.gates.iter().cloned().collect(),
|
||||
degree_bits: log2_strict(degree),
|
||||
gates,
|
||||
num_gate_constraints,
|
||||
constants_root,
|
||||
sigmas_root,
|
||||
};
|
||||
|
||||
@ -5,6 +5,8 @@ use crate::prover::prove;
|
||||
use crate::verifier::verify;
|
||||
use crate::witness::PartialWitness;
|
||||
use crate::gates::gate::{GateRef};
|
||||
use crate::constraint_polynomial::{EvaluationVars, EvaluationTargets};
|
||||
use crate::target::Target;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct CircuitConfig {
|
||||
@ -12,6 +14,20 @@ pub struct CircuitConfig {
|
||||
pub num_routed_wires: usize,
|
||||
pub security_bits: usize,
|
||||
pub rate_bits: usize,
|
||||
/// The number of times to repeat checks that have soundness errors of (roughly) `degree / |F|`.
|
||||
pub num_checks: usize,
|
||||
}
|
||||
|
||||
impl Default for CircuitConfig {
|
||||
fn default() -> Self {
|
||||
CircuitConfig {
|
||||
num_wires: 3,
|
||||
num_routed_wires: 3,
|
||||
security_bits: 128,
|
||||
rate_bits: 3,
|
||||
num_checks: 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CircuitConfig {
|
||||
@ -64,6 +80,7 @@ impl<F: Field> VerifierCircuitData<F> {
|
||||
/// Circuit data required by the prover, but not the verifier.
|
||||
pub(crate) struct ProverOnlyCircuitData<F: Field> {
|
||||
pub generators: Vec<Box<dyn WitnessGenerator<F>>>,
|
||||
pub constant_ldes_t: Vec<Vec<F>>,
|
||||
}
|
||||
|
||||
/// Circuit data required by the verifier, but not the prover.
|
||||
@ -73,11 +90,13 @@ pub(crate) struct VerifierOnlyCircuitData {}
|
||||
pub(crate) struct CommonCircuitData<F: Field> {
|
||||
pub(crate) config: CircuitConfig,
|
||||
|
||||
pub(crate) degree: usize,
|
||||
pub(crate) degree_bits: usize,
|
||||
|
||||
/// The types of gates used in this circuit.
|
||||
pub(crate) gates: Vec<GateRef<F>>,
|
||||
|
||||
pub(crate) num_gate_constraints: usize,
|
||||
|
||||
/// A commitment to each constant polynomial.
|
||||
pub(crate) constants_root: Hash<F>,
|
||||
|
||||
@ -86,10 +105,42 @@ pub(crate) struct CommonCircuitData<F: Field> {
|
||||
}
|
||||
|
||||
impl<F: Field> CommonCircuitData<F> {
|
||||
pub fn constraint_degree(&self, config: CircuitConfig) -> usize {
|
||||
pub fn degree(&self) -> usize {
|
||||
1 << self.degree_bits
|
||||
}
|
||||
|
||||
pub fn lde_size(&self) -> usize {
|
||||
1 << (self.degree_bits + self.config.rate_bits)
|
||||
}
|
||||
|
||||
pub fn lde_generator(&self) -> F {
|
||||
F::primitive_root_of_unity(self.degree_bits + self.config.rate_bits)
|
||||
}
|
||||
|
||||
pub fn constraint_degree(&self) -> usize {
|
||||
self.gates.iter()
|
||||
.map(|g| g.0.degree(config))
|
||||
.map(|g| g.0.degree())
|
||||
.max()
|
||||
.expect("No gates?")
|
||||
}
|
||||
|
||||
pub fn total_constraints(&self) -> usize {
|
||||
// 2 constraints for each Z check.
|
||||
self.config.num_checks * 2 + self.num_gate_constraints
|
||||
}
|
||||
|
||||
pub fn evaluate(&self, vars: EvaluationVars<F>) -> Vec<F> {
|
||||
let mut constraints = vec![F::ZERO; self.num_gate_constraints];
|
||||
for gate in &self.gates {
|
||||
let gate_constraints = gate.0.eval_filtered(vars);
|
||||
for (i, c) in gate_constraints.into_iter().enumerate() {
|
||||
constraints[i] += c;
|
||||
}
|
||||
}
|
||||
constraints
|
||||
}
|
||||
|
||||
pub fn evaluate_recursive(&self, vars: EvaluationTargets) -> Vec<Target> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,570 +10,20 @@ use std::rc::Rc;
|
||||
use num::{BigUint, FromPrimitive, One, Zero};
|
||||
|
||||
use crate::field::field::Field;
|
||||
use crate::gates::output_graph::GateOutputLocation;
|
||||
use crate::target::Target;
|
||||
use crate::wire::Wire;
|
||||
|
||||
pub(crate) struct EvaluationVars<'a, F: Field> {
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct EvaluationVars<'a, F: Field> {
|
||||
pub(crate) local_constants: &'a [F],
|
||||
pub(crate) next_constants: &'a [F],
|
||||
pub(crate) local_wires: &'a [F],
|
||||
pub(crate) next_wires: &'a [F],
|
||||
}
|
||||
|
||||
/// A polynomial over all the variables that are subject to constraints (local constants, next
|
||||
/// constants, local wire values, and next wire values). This representation does not require any
|
||||
/// particular form; it permits arbitrary forms such as `(x + 1)^3 + y z`.
|
||||
///
|
||||
/// This type implements `Hash` and `Eq` based on references rather
|
||||
/// than content. This is useful when we want to use constraint polynomials as `HashMap` keys, but
|
||||
/// we want address-based hashing for performance reasons.
|
||||
#[derive(Clone)]
|
||||
pub struct ConstraintPolynomial<F: Field>(pub(crate) Rc<ConstraintPolynomialInner<F>>);
|
||||
|
||||
impl<F: Field> ConstraintPolynomial<F> {
|
||||
pub fn constant(c: F) -> Self {
|
||||
Self::from_inner(ConstraintPolynomialInner::Constant(c))
|
||||
}
|
||||
|
||||
pub fn constant_usize(c: usize) -> Self {
|
||||
Self::constant(F::from_canonical_usize(c))
|
||||
}
|
||||
|
||||
// TODO: const?
|
||||
pub fn zero() -> Self {
|
||||
Self::constant(F::ZERO)
|
||||
}
|
||||
|
||||
pub fn one() -> Self {
|
||||
Self::constant(F::ONE)
|
||||
}
|
||||
|
||||
pub fn local_constant(index: usize) -> Self {
|
||||
Self::from_inner(ConstraintPolynomialInner::LocalConstant(index))
|
||||
}
|
||||
|
||||
pub fn next_constant(index: usize) -> Self {
|
||||
Self::from_inner(ConstraintPolynomialInner::NextConstant(index))
|
||||
}
|
||||
|
||||
pub fn local_wire(index: usize) -> Self {
|
||||
Self::from_inner(ConstraintPolynomialInner::LocalWireValue(index))
|
||||
}
|
||||
|
||||
pub fn next_wire(index: usize) -> Self {
|
||||
Self::from_inner(ConstraintPolynomialInner::NextWireValue(index))
|
||||
}
|
||||
|
||||
pub fn from_gate_output(gate_output: GateOutputLocation) -> Self {
|
||||
match gate_output {
|
||||
GateOutputLocation::LocalWire(i) => Self::local_wire(i),
|
||||
GateOutputLocation::NextWire(i) => Self::next_wire(i),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Have these take references?
|
||||
pub fn add(&self, rhs: &Self) -> Self {
|
||||
// TODO: Special case for either operand being 0.
|
||||
Self::from_inner(ConstraintPolynomialInner::Sum {
|
||||
lhs: self.clone(),
|
||||
rhs: rhs.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn sub(&self, rhs: &Self) -> Self {
|
||||
// TODO: Special case for either operand being 0.
|
||||
// TODO: Faster to have a dedicated ConstraintPolynomialInner::Difference?
|
||||
// TODO: `self + -rhs`?
|
||||
self.add(&rhs.neg())
|
||||
}
|
||||
|
||||
pub fn double(&self) -> Self {
|
||||
self.clone().add(self)
|
||||
}
|
||||
|
||||
pub fn triple(&self) -> Self {
|
||||
self * 3
|
||||
}
|
||||
|
||||
pub fn mul(&self, rhs: &Self) -> Self {
|
||||
// TODO: Special case for either operand being 1.
|
||||
Self::from_inner(ConstraintPolynomialInner::Product {
|
||||
lhs: self.clone(),
|
||||
rhs: rhs.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn exp(&self, exponent: usize) -> Self {
|
||||
Self::from_inner(ConstraintPolynomialInner::Exponentiation {
|
||||
base: self.clone(),
|
||||
exponent,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn square(&self) -> Self {
|
||||
self * self
|
||||
}
|
||||
|
||||
pub fn cube(&self) -> Self {
|
||||
self * self * self
|
||||
}
|
||||
|
||||
pub fn degree(&self) -> BigUint {
|
||||
self.0.degree()
|
||||
}
|
||||
|
||||
/// Returns the set of wires that this constraint would depend on if it were applied at a
|
||||
/// certain gate index.
|
||||
pub(crate) fn dependencies(&self, gate: usize) -> Vec<Wire> {
|
||||
let mut deps = HashSet::new();
|
||||
self.0.add_dependencies(gate, &mut deps);
|
||||
deps.into_iter().collect()
|
||||
}
|
||||
|
||||
/// Find the largest input index among the wires this constraint depends on.
|
||||
pub(crate) fn max_wire_input_index(&self) -> Option<usize> {
|
||||
self.dependencies(0)
|
||||
.into_iter()
|
||||
.map(|wire| wire.input)
|
||||
.max()
|
||||
}
|
||||
|
||||
pub(crate) fn max_constant_index(&self) -> Option<usize> {
|
||||
let mut indices = HashSet::new();
|
||||
self.0.add_constant_indices(&mut indices);
|
||||
indices.into_iter().max()
|
||||
}
|
||||
|
||||
pub(crate) fn evaluate(&self, vars: EvaluationVars<F>) -> F {
|
||||
let results = Self::evaluate_all(&[self.clone()], vars);
|
||||
assert_eq!(results.len(), 1);
|
||||
results[0]
|
||||
}
|
||||
|
||||
/// Evaluate multiple constraint polynomials simultaneously. This can be more efficient than
|
||||
/// evaluating them sequentially, since shared intermediate results will only be computed once.
|
||||
pub(crate) fn evaluate_all(
|
||||
polynomials: &[ConstraintPolynomial<F>],
|
||||
vars: EvaluationVars<F>,
|
||||
) -> Vec<F> {
|
||||
let mut mem = HashMap::new();
|
||||
polynomials.iter()
|
||||
.map(|p| p.evaluate_memoized(&vars, &mut mem))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn evaluate_memoized(
|
||||
&self,
|
||||
vars: &EvaluationVars<F>,
|
||||
mem: &mut HashMap<Self, F>,
|
||||
) -> F {
|
||||
if let Some(&result) = mem.get(self) {
|
||||
result
|
||||
} else {
|
||||
let result = self.0.evaluate(vars, mem);
|
||||
mem.insert(self.clone(), result);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace all occurrences of `from` with `to` in this polynomial graph.
|
||||
pub(crate) fn replace_all(
|
||||
&self,
|
||||
from: Self,
|
||||
to: Self,
|
||||
) -> Self {
|
||||
self.replace_all_helper(from, to, &mut HashMap::new())
|
||||
}
|
||||
|
||||
/// Replace all occurrences of `from` with `to` in this polynomial graph. In order to preserve
|
||||
/// the structure of the graph, we keep track of any `ConstraintPolynomial`s that have been
|
||||
/// replaced already.
|
||||
fn replace_all_helper(
|
||||
&self, from: Self,
|
||||
to: Self,
|
||||
replacements: &mut HashMap<Self, Self>,
|
||||
) -> Self {
|
||||
if *self == from {
|
||||
return to;
|
||||
}
|
||||
|
||||
if let Some(replacement) = replacements.get(self) {
|
||||
return replacement.clone();
|
||||
}
|
||||
|
||||
match self.0.borrow() {
|
||||
ConstraintPolynomialInner::Constant(_) => self.clone(),
|
||||
ConstraintPolynomialInner::LocalConstant(_) => self.clone(),
|
||||
ConstraintPolynomialInner::NextConstant(_) => self.clone(),
|
||||
ConstraintPolynomialInner::LocalWireValue(_) => self.clone(),
|
||||
ConstraintPolynomialInner::NextWireValue(_) => self.clone(),
|
||||
ConstraintPolynomialInner::Sum { lhs, rhs } => {
|
||||
let lhs = lhs.replace_all_helper(from.clone(), to.clone(), replacements);
|
||||
let rhs = rhs.replace_all_helper(from, to, replacements);
|
||||
let replacement = Self::from_inner(ConstraintPolynomialInner::Sum { lhs, rhs });
|
||||
debug_assert!(!replacements.contains_key(self));
|
||||
replacements.insert(self.clone(), replacement.clone());
|
||||
replacement
|
||||
}
|
||||
ConstraintPolynomialInner::Product { lhs, rhs } => {
|
||||
let lhs = lhs.replace_all_helper(from.clone(), to.clone(), replacements);
|
||||
let rhs = rhs.replace_all_helper(from, to, replacements);
|
||||
let replacement = Self::from_inner(ConstraintPolynomialInner::Product { lhs, rhs });
|
||||
debug_assert!(!replacements.contains_key(self));
|
||||
replacements.insert(self.clone(), replacement.clone());
|
||||
replacement
|
||||
}
|
||||
ConstraintPolynomialInner::Exponentiation { base, exponent } => {
|
||||
let base = base.replace_all_helper(from, to, replacements);
|
||||
let replacement = Self::from_inner(
|
||||
ConstraintPolynomialInner::Exponentiation { base, exponent: *exponent });
|
||||
debug_assert!(!replacements.contains_key(self));
|
||||
replacements.insert(self.clone(), replacement.clone());
|
||||
replacement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn from_inner(inner: ConstraintPolynomialInner<F>) -> Self {
|
||||
Self(Rc::new(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> Debug for ConstraintPolynomial<F> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> Display for ConstraintPolynomial<F> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> PartialEq for ConstraintPolynomial<F> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
ptr::eq(&*self.0, &*other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> Eq for ConstraintPolynomial<F> {}
|
||||
|
||||
impl<F: Field> Hash for ConstraintPolynomial<F> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
ptr::hash(&*self.0, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> Neg for ConstraintPolynomial<F> {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
// TODO: Faster to have a dedicated ConstraintPolynomialInner::Negation?
|
||||
self * ConstraintPolynomial::constant(F::NEG_ONE)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> Neg for &ConstraintPolynomial<F> {
|
||||
type Output = ConstraintPolynomial<F>;
|
||||
|
||||
fn neg(self) -> ConstraintPolynomial<F> {
|
||||
self.clone().neg()
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the following variants of a binary operation:
|
||||
/// - `Self . Self`
|
||||
/// - `&Self . Self`
|
||||
/// - `Self . &Self`
|
||||
/// - `&Self . &Self`
|
||||
/// - `Self . F`
|
||||
/// - `&Self . F`
|
||||
/// - `Self . usize`
|
||||
/// - `&Self . usize`
|
||||
/// where `Self` is `ConstraintPolynomial<F>`.
|
||||
///
|
||||
/// Takes the following arguments:
|
||||
/// - `$trait`: the name of the binary operation trait to implement
|
||||
/// - `$method`: the name of the method in the trait. It is assumed that `ConstraintPolynomial`
|
||||
/// contains a method with the same name, implementing the `&Self . &Self` variant.
|
||||
macro_rules! binop_variants {
|
||||
($trait:ident, $method:ident) => {
|
||||
impl<F: Field> $trait<Self> for ConstraintPolynomial<F> {
|
||||
type Output = Self;
|
||||
|
||||
fn $method(self, rhs: Self) -> Self {
|
||||
ConstraintPolynomial::$method(&self, &rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> $trait<&Self> for ConstraintPolynomial<F> {
|
||||
type Output = Self;
|
||||
|
||||
fn $method(self, rhs: &Self) -> Self {
|
||||
ConstraintPolynomial::$method(&self, rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> $trait<ConstraintPolynomial<F>> for &ConstraintPolynomial<F> {
|
||||
type Output = ConstraintPolynomial<F>;
|
||||
|
||||
fn $method(self, rhs: ConstraintPolynomial<F>) -> Self::Output {
|
||||
ConstraintPolynomial::$method(self, &rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> $trait for &ConstraintPolynomial<F> {
|
||||
type Output = ConstraintPolynomial<F>;
|
||||
|
||||
fn $method(self, rhs: Self) -> Self::Output {
|
||||
ConstraintPolynomial::$method(self, rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> $trait<F> for ConstraintPolynomial<F> {
|
||||
type Output = Self;
|
||||
|
||||
fn $method(self, rhs: F) -> Self {
|
||||
ConstraintPolynomial::$method(&self, &ConstraintPolynomial::constant(rhs))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> $trait<F> for &ConstraintPolynomial<F> {
|
||||
type Output = ConstraintPolynomial<F>;
|
||||
|
||||
fn $method(self, rhs: F) -> Self::Output {
|
||||
ConstraintPolynomial::$method(self, &ConstraintPolynomial::constant(rhs))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> $trait<usize> for ConstraintPolynomial<F> {
|
||||
type Output = Self;
|
||||
|
||||
fn $method(self, rhs: usize) -> Self {
|
||||
ConstraintPolynomial::$method(&self, &ConstraintPolynomial::constant_usize(rhs))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> $trait<usize> for &ConstraintPolynomial<F> {
|
||||
type Output = ConstraintPolynomial<F>;
|
||||
|
||||
fn $method(self, rhs: usize) -> Self::Output {
|
||||
ConstraintPolynomial::$method(self, &ConstraintPolynomial::constant_usize(rhs))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Generates the following variants of a binary operation assignment:
|
||||
/// - `.= Self`
|
||||
/// - `.= &Self`
|
||||
/// - `.= F`
|
||||
/// - `.= usize`
|
||||
/// where `Self` is `ConstraintPolynomial<F>`.
|
||||
///
|
||||
/// Takes the following arguments:
|
||||
/// - `$trait`: the name of the binary operation trait to implement
|
||||
/// - `$assign_method`: the name of the method in the trait
|
||||
/// - `$binop_method`: the name of a method in `ConstraintPolynomial`
|
||||
/// which implements the `&Self . &Self` operation.
|
||||
macro_rules! binop_assign_variants {
|
||||
($trait:ident, $assign_method:ident, $binop_method:ident) => {
|
||||
impl<F: Field> $trait<Self> for ConstraintPolynomial<F> {
|
||||
fn $assign_method(&mut self, rhs: Self) {
|
||||
*self = ConstraintPolynomial::$binop_method(self, &rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> $trait<&Self> for ConstraintPolynomial<F> {
|
||||
fn $assign_method(&mut self, rhs: &Self) {
|
||||
*self = ConstraintPolynomial::$binop_method(self, rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> $trait<F> for ConstraintPolynomial<F> {
|
||||
fn $assign_method(&mut self, rhs: F) {
|
||||
*self = ConstraintPolynomial::$binop_method(self, &ConstraintPolynomial::constant(rhs));
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> $trait<usize> for ConstraintPolynomial<F> {
|
||||
fn $assign_method(&mut self, rhs: usize) {
|
||||
*self = ConstraintPolynomial::$binop_method(self, &ConstraintPolynomial::constant_usize(rhs));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binop_variants!(Add, add);
|
||||
binop_variants!(Sub, sub);
|
||||
binop_variants!(Mul, mul);
|
||||
|
||||
binop_assign_variants!(AddAssign, add_assign, add);
|
||||
binop_assign_variants!(SubAssign, sub_assign, sub);
|
||||
binop_assign_variants!(MulAssign, mul_assign, mul);
|
||||
|
||||
impl<F: Field> Sum for ConstraintPolynomial<F> {
|
||||
fn sum<I: Iterator<Item=Self>>(iter: I) -> Self {
|
||||
iter.fold(
|
||||
ConstraintPolynomial::zero(),
|
||||
|sum, x| sum + x)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> Product for ConstraintPolynomial<F> {
|
||||
fn product<I: Iterator<Item=Self>>(iter: I) -> Self {
|
||||
iter.fold(
|
||||
ConstraintPolynomial::one(),
|
||||
|product, x| product * x)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum ConstraintPolynomialInner<F: Field> {
|
||||
Constant(F),
|
||||
|
||||
LocalConstant(usize),
|
||||
NextConstant(usize),
|
||||
LocalWireValue(usize),
|
||||
NextWireValue(usize),
|
||||
|
||||
Sum {
|
||||
lhs: ConstraintPolynomial<F>,
|
||||
rhs: ConstraintPolynomial<F>,
|
||||
},
|
||||
Product {
|
||||
lhs: ConstraintPolynomial<F>,
|
||||
rhs: ConstraintPolynomial<F>,
|
||||
},
|
||||
Exponentiation {
|
||||
base: ConstraintPolynomial<F>,
|
||||
exponent: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl<F: Field> Display for ConstraintPolynomialInner<F> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ConstraintPolynomialInner::Constant(c) =>
|
||||
write!(f, "{}", c),
|
||||
ConstraintPolynomialInner::LocalConstant(i) =>
|
||||
write!(f, "local_const_{}", i),
|
||||
ConstraintPolynomialInner::NextConstant(i) =>
|
||||
write!(f, "next_const_{}", i),
|
||||
ConstraintPolynomialInner::LocalWireValue(i) =>
|
||||
write!(f, "local_wire_{}", i),
|
||||
ConstraintPolynomialInner::NextWireValue(i) =>
|
||||
write!(f, "next_wire_{}", i),
|
||||
ConstraintPolynomialInner::Sum { lhs, rhs } =>
|
||||
write!(f, "({} + {})", lhs, rhs),
|
||||
ConstraintPolynomialInner::Product { lhs, rhs } =>
|
||||
write!(f, "({} * {})", lhs, rhs),
|
||||
ConstraintPolynomialInner::Exponentiation { base, exponent } =>
|
||||
write!(f, "({} ^ {})", base, exponent),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> ConstraintPolynomialInner<F> {
|
||||
fn add_dependencies(&self, gate: usize, deps: &mut HashSet<Wire>) {
|
||||
match self {
|
||||
ConstraintPolynomialInner::Constant(_) => (),
|
||||
ConstraintPolynomialInner::LocalConstant(_) => (),
|
||||
ConstraintPolynomialInner::NextConstant(_) => (),
|
||||
ConstraintPolynomialInner::LocalWireValue(i) =>
|
||||
{ deps.insert(Wire { gate, input: *i }); }
|
||||
ConstraintPolynomialInner::NextWireValue(i) =>
|
||||
{ deps.insert(Wire { gate: gate + 1, input: *i }); }
|
||||
ConstraintPolynomialInner::Sum { lhs, rhs } => {
|
||||
lhs.0.add_dependencies(gate, deps);
|
||||
rhs.0.add_dependencies(gate, deps);
|
||||
}
|
||||
ConstraintPolynomialInner::Product { lhs, rhs } => {
|
||||
lhs.0.add_dependencies(gate, deps);
|
||||
rhs.0.add_dependencies(gate, deps);
|
||||
}
|
||||
ConstraintPolynomialInner::Exponentiation { base, exponent: _ } => {
|
||||
base.0.add_dependencies(gate, deps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_constant_indices(&self, indices: &mut HashSet<usize>) {
|
||||
match self {
|
||||
ConstraintPolynomialInner::Constant(_) => (),
|
||||
ConstraintPolynomialInner::LocalConstant(i) => { indices.insert(*i); }
|
||||
ConstraintPolynomialInner::NextConstant(i) => { indices.insert(*i); }
|
||||
ConstraintPolynomialInner::LocalWireValue(_) => (),
|
||||
ConstraintPolynomialInner::NextWireValue(_) => (),
|
||||
ConstraintPolynomialInner::Sum { lhs, rhs } => {
|
||||
lhs.0.add_constant_indices(indices);
|
||||
rhs.0.add_constant_indices(indices);
|
||||
}
|
||||
ConstraintPolynomialInner::Product { lhs, rhs } => {
|
||||
lhs.0.add_constant_indices(indices);
|
||||
rhs.0.add_constant_indices(indices);
|
||||
}
|
||||
ConstraintPolynomialInner::Exponentiation { base, exponent: _ } => {
|
||||
base.0.add_constant_indices(indices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate(
|
||||
&self,
|
||||
vars: &EvaluationVars<F>,
|
||||
mem: &mut HashMap<ConstraintPolynomial<F>, F>,
|
||||
) -> F {
|
||||
match self {
|
||||
ConstraintPolynomialInner::Constant(c) => *c,
|
||||
ConstraintPolynomialInner::LocalConstant(i) => vars.local_constants[*i],
|
||||
ConstraintPolynomialInner::NextConstant(i) => vars.next_constants[*i],
|
||||
ConstraintPolynomialInner::LocalWireValue(i) => vars.local_wires[*i],
|
||||
ConstraintPolynomialInner::NextWireValue(i) => vars.next_wires[*i],
|
||||
ConstraintPolynomialInner::Sum { lhs, rhs } => {
|
||||
let lhs = lhs.evaluate_memoized(vars, mem);
|
||||
let rhs = rhs.evaluate_memoized(vars, mem);
|
||||
lhs + rhs
|
||||
}
|
||||
ConstraintPolynomialInner::Product { lhs, rhs } => {
|
||||
let lhs = lhs.evaluate_memoized(vars, mem);
|
||||
let rhs = rhs.evaluate_memoized(vars, mem);
|
||||
lhs * rhs
|
||||
}
|
||||
ConstraintPolynomialInner::Exponentiation { base, exponent } => {
|
||||
let base = base.evaluate_memoized(vars, mem);
|
||||
base.exp_usize(*exponent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn degree(&self) -> BigUint {
|
||||
match self {
|
||||
ConstraintPolynomialInner::Constant(_) => BigUint::zero(),
|
||||
ConstraintPolynomialInner::LocalConstant(_) => BigUint::one(),
|
||||
ConstraintPolynomialInner::NextConstant(_) => BigUint::one(),
|
||||
ConstraintPolynomialInner::LocalWireValue(_) => BigUint::one(),
|
||||
ConstraintPolynomialInner::NextWireValue(_) => BigUint::one(),
|
||||
ConstraintPolynomialInner::Sum { lhs, rhs } => lhs.0.degree().max(rhs.0.degree()),
|
||||
ConstraintPolynomialInner::Product { lhs, rhs } => lhs.0.degree() + rhs.0.degree(),
|
||||
ConstraintPolynomialInner::Exponentiation { base, exponent } =>
|
||||
base.0.degree() * BigUint::from_usize(*exponent).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::constraint_polynomial::ConstraintPolynomial;
|
||||
use crate::field::crandall_field::CrandallField;
|
||||
|
||||
#[test]
|
||||
fn equality() {
|
||||
type F = CrandallField;
|
||||
let wire0 = ConstraintPolynomial::<F>::local_wire(0);
|
||||
// == should compare the pointers, and the clone should point to the same underlying
|
||||
// ConstraintPolynomialInner.
|
||||
assert_eq!(wire0.clone(), wire0);
|
||||
}
|
||||
pub struct EvaluationTargets<'a> {
|
||||
pub(crate) local_constants: &'a [Target],
|
||||
pub(crate) next_constants: &'a [Target],
|
||||
pub(crate) local_wires: &'a [Target],
|
||||
pub(crate) next_wires: &'a [Target],
|
||||
}
|
||||
|
||||
21
src/gadgets/arithmetic.rs
Normal file
21
src/gadgets/arithmetic.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use crate::circuit_builder::CircuitBuilder;
|
||||
use crate::field::field::Field;
|
||||
use crate::target::Target;
|
||||
|
||||
impl<F: Field> CircuitBuilder<F> {
|
||||
pub fn add(&mut self, x: Target, y: Target) -> Target {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn sub(&mut self, x: Target, y: Target) -> Target {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn mul(&mut self, x: Target, y: Target) -> Target {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn div(&mut self, x: Target, y: Target) -> Target {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
use crate::constraint_polynomial::ConstraintPolynomial;
|
||||
use crate::field::field::Field;
|
||||
|
||||
/// Evaluates to `x` if `c == 0`, or `x * y` if `c == 1`.
|
||||
pub fn conditional_multiply_poly<F: Field>(
|
||||
x: &ConstraintPolynomial<F>,
|
||||
y: &ConstraintPolynomial<F>,
|
||||
c: &ConstraintPolynomial<F>,
|
||||
) -> ConstraintPolynomial<F> {
|
||||
let product = x * y;
|
||||
let delta = product - x;
|
||||
x + c * delta
|
||||
}
|
||||
@ -1,2 +1,2 @@
|
||||
pub(crate) mod arithmetic;
|
||||
pub(crate) mod split_join;
|
||||
pub(crate) mod conditionals;
|
||||
|
||||
@ -1,25 +1,24 @@
|
||||
use std::iter;
|
||||
|
||||
use crate::constraint_polynomial::ConstraintPolynomial;
|
||||
use crate::field::field::Field;
|
||||
use crate::generator::{SimpleGenerator, WitnessGenerator};
|
||||
use crate::target::Target;
|
||||
use crate::witness::PartialWitness;
|
||||
use crate::wire::Wire;
|
||||
|
||||
/// Constraints for a little-endian split.
|
||||
pub fn split_le_constraints<F: Field>(
|
||||
integer: ConstraintPolynomial<F>,
|
||||
bits: &[ConstraintPolynomial<F>],
|
||||
) -> Vec<ConstraintPolynomial<F>> {
|
||||
let weighted_sum = bits.iter()
|
||||
.fold(ConstraintPolynomial::zero(), |acc, b| acc.double() + b);
|
||||
bits.iter()
|
||||
.rev()
|
||||
.map(|b| b * (b - 1))
|
||||
.chain(iter::once(weighted_sum - integer))
|
||||
.collect()
|
||||
}
|
||||
// /// Constraints for a little-endian split.
|
||||
// pub fn split_le_constraints<F: Field>(
|
||||
// integer: ConstraintPolynomial<F>,
|
||||
// bits: &[ConstraintPolynomial<F>],
|
||||
// ) -> Vec<ConstraintPolynomial<F>> {
|
||||
// let weighted_sum = bits.iter()
|
||||
// .fold(ConstraintPolynomial::zero(), |acc, b| acc.double() + b);
|
||||
// bits.iter()
|
||||
// .rev()
|
||||
// .map(|b| b * (b - 1))
|
||||
// .chain(iter::once(weighted_sum - integer))
|
||||
// .collect()
|
||||
// }
|
||||
|
||||
/// Generator for a little-endian split.
|
||||
pub fn split_le_generator<F: Field>(
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
use crate::circuit_data::CircuitConfig;
|
||||
use crate::constraint_polynomial::ConstraintPolynomial;
|
||||
use crate::circuit_builder::CircuitBuilder;
|
||||
use crate::constraint_polynomial::{EvaluationTargets, EvaluationVars};
|
||||
use crate::field::field::Field;
|
||||
use crate::gates::deterministic_gate::{DeterministicGate, DeterministicGateAdapter};
|
||||
use crate::gates::gate::GateRef;
|
||||
use crate::gates::output_graph::{GateOutputLocation, OutputGraph};
|
||||
use crate::gates::gate::{Gate, GateRef};
|
||||
use crate::generator::{SimpleGenerator, WitnessGenerator};
|
||||
use crate::target::Target;
|
||||
use crate::witness::PartialWitness;
|
||||
use crate::wire::Wire;
|
||||
|
||||
/// A gate which takes a single constant parameter and outputs that value.
|
||||
pub struct ConstantGate2;
|
||||
pub struct ConstantGate;
|
||||
|
||||
impl ConstantGate2 {
|
||||
impl ConstantGate {
|
||||
pub fn get<F: Field>() -> GateRef<F> {
|
||||
GateRef::new(DeterministicGateAdapter::new(ConstantGate2))
|
||||
GateRef::new(ConstantGate)
|
||||
}
|
||||
|
||||
pub const CONST_INPUT: usize = 0;
|
||||
@ -18,14 +20,70 @@ impl ConstantGate2 {
|
||||
pub const WIRE_OUTPUT: usize = 0;
|
||||
}
|
||||
|
||||
impl<F: Field> DeterministicGate<F> for ConstantGate2 {
|
||||
impl<F: Field> Gate<F> for ConstantGate {
|
||||
fn id(&self) -> String {
|
||||
"ConstantGate".into()
|
||||
}
|
||||
|
||||
fn outputs(&self, _config: CircuitConfig) -> OutputGraph<F> {
|
||||
let loc = GateOutputLocation::LocalWire(Self::WIRE_OUTPUT);
|
||||
let out = ConstraintPolynomial::local_constant(Self::CONST_INPUT);
|
||||
OutputGraph::single_output(loc, out)
|
||||
fn eval_unfiltered(&self, vars: EvaluationVars<F>) -> Vec<F> {
|
||||
let input = vars.local_constants[Self::CONST_INPUT];
|
||||
let output = vars.local_wires[Self::WIRE_OUTPUT];
|
||||
vec![output - input]
|
||||
}
|
||||
|
||||
fn eval_unfiltered_recursively(
|
||||
&self,
|
||||
builder: &mut CircuitBuilder<F>,
|
||||
vars: EvaluationTargets,
|
||||
) -> Vec<Target> {
|
||||
let input = vars.local_constants[Self::CONST_INPUT];
|
||||
let output = vars.local_wires[Self::WIRE_OUTPUT];
|
||||
vec![builder.sub(output, input)]
|
||||
}
|
||||
|
||||
fn generators(
|
||||
&self,
|
||||
gate_index: usize,
|
||||
local_constants: &[F],
|
||||
next_constants: &[F],
|
||||
) -> Vec<Box<dyn WitnessGenerator<F>>> {
|
||||
let gen = ConstantGenerator {
|
||||
gate_index,
|
||||
constant: local_constants[0],
|
||||
};
|
||||
vec![Box::new(gen)]
|
||||
}
|
||||
|
||||
fn num_wires(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn num_constants(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn degree(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn num_constraints(&self) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ConstantGenerator<F: Field> {
|
||||
gate_index: usize,
|
||||
constant: F,
|
||||
}
|
||||
|
||||
impl<F: Field> SimpleGenerator<F> for ConstantGenerator<F> {
|
||||
fn dependencies(&self) -> Vec<Target> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn run_once(&self, witness: &PartialWitness<F>) -> PartialWitness<F> {
|
||||
let wire = Wire { gate: self.gate_index, input: ConstantGate::WIRE_OUTPUT };
|
||||
PartialWitness::singleton(Target::Wire(wire), self.constant)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,157 +0,0 @@
|
||||
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<F: Field>: '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<F>;
|
||||
|
||||
/// Any additional constraints to be enforced, besides the (automatically provided) ones that
|
||||
/// constraint output values.
|
||||
fn additional_constraints(&self, _config: CircuitConfig) -> Vec<ConstraintPolynomial<F>> {
|
||||
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<F>,
|
||||
next_constants: Vec<F>,
|
||||
) -> Vec<Box<dyn WitnessGenerator<F>>> {
|
||||
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<F: Field, DG: DeterministicGate<F> + ?Sized> {
|
||||
gate: Box<DG>,
|
||||
_phantom: PhantomData<F>,
|
||||
}
|
||||
|
||||
impl<F: Field, DG: DeterministicGate<F>> DeterministicGateAdapter<F, DG> {
|
||||
pub fn new(gate: DG) -> Self {
|
||||
Self { gate: Box::new(gate), _phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field, DG: DeterministicGate<F>> Gate<F> for DeterministicGateAdapter<F, DG> {
|
||||
fn id(&self) -> String {
|
||||
self.gate.id()
|
||||
}
|
||||
|
||||
fn constraints(&self, config: CircuitConfig) -> Vec<ConstraintPolynomial<F>> {
|
||||
// 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<F>,
|
||||
next_constants: Vec<F>,
|
||||
) -> Vec<Box<dyn WitnessGenerator<F>>> {
|
||||
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<F>`, rather
|
||||
// than a boxed `OutputGenerator<F>`.
|
||||
let b: Box::<dyn WitnessGenerator<F>> = Box::new(og);
|
||||
b
|
||||
})
|
||||
.chain(self.gate.additional_generators(
|
||||
config, gate_index, local_constants.clone(), next_constants.clone()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OutputGenerator<F: Field> {
|
||||
gate_index: usize,
|
||||
location: GateOutputLocation,
|
||||
out: ConstraintPolynomial<F>,
|
||||
local_constants: Vec<F>,
|
||||
next_constants: Vec<F>,
|
||||
}
|
||||
|
||||
impl<F: Field> SimpleGenerator<F> for OutputGenerator<F> {
|
||||
fn dependencies(&self) -> Vec<Target> {
|
||||
self.out.dependencies(self.gate_index)
|
||||
.into_iter()
|
||||
.map(Target::Wire)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn run_once(&self, witness: &PartialWitness<F>) -> PartialWitness<F> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -1,372 +1,372 @@
|
||||
use crate::circuit_data::CircuitConfig;
|
||||
use crate::constraint_polynomial::ConstraintPolynomial;
|
||||
use crate::field::fft::coset_ifft;
|
||||
use crate::field::field::Field;
|
||||
use crate::gadgets::split_join::{split_le_constraints, split_le_generator_local_wires};
|
||||
use crate::gates::deterministic_gate::{DeterministicGate, DeterministicGateAdapter};
|
||||
use crate::gates::gate::GateRef;
|
||||
use crate::gates::output_graph::{OutputGraph, GateOutputLocation, ExpandableOutputGraph};
|
||||
use crate::generator::{SimpleGenerator, WitnessGenerator};
|
||||
use crate::target::Target;
|
||||
use crate::wire::Wire;
|
||||
use crate::witness::PartialWitness;
|
||||
use crate::gadgets::conditionals::conditional_multiply_poly;
|
||||
|
||||
/// Performs a FRI consistency check. The goal is to check consistency between polynomials `f^(i)`
|
||||
/// and `f^(i + 1)`, where `f^(i + 1)` is supposed to be an `arity`-to-one reduction of `f^(i)`. See
|
||||
/// the FRI paper for details.
|
||||
///
|
||||
/// This check involves `arity` openings of `f^(i)` and a single opening of `f^(i + 1)`. Let's call
|
||||
/// the set of `f^(i)` opening locations `{s_j}`, and the `f^(i + 1)` opening location `y`. Note
|
||||
/// that all of these points can be derived from the query path. So, we take as input an integer
|
||||
/// whose binary decomposition represents the left and right turns in the query path.
|
||||
///
|
||||
/// Since our protocol uses a variant of FRI with `k` commit phases, this gate takes `k` opening
|
||||
/// sets for `f^(i)`, and `k` openings for `f^(i + 1)`.
|
||||
///
|
||||
/// Putting it together, this gate does the following:
|
||||
/// - Computes the binary decomposition of the input representing the query path.
|
||||
/// - Computes `{s_j}` and `y` from the query path.
|
||||
/// - For each commit phase:
|
||||
/// - Non-deterministically interpolates a polynomial through each `(s_j, f^(i))`.
|
||||
/// - Evaluates the purported interpolant at each `s_j`, and checks that the result matches the
|
||||
/// associated opening of `f^(i)(s_j)`.
|
||||
/// - Evaluates the purported interpolant at `y`, and checks that the result matches the opening
|
||||
/// of `f^(i + 1)(y)`.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) struct FriConsistencyGate {
|
||||
/// The arity of this reduction step.
|
||||
arity_bits: usize,
|
||||
|
||||
/// The number of commit phases.
|
||||
num_commits: usize,
|
||||
|
||||
/// The maximum number of bits in any query path.
|
||||
max_path_bits: usize,
|
||||
}
|
||||
|
||||
impl FriConsistencyGate {
|
||||
pub fn new<F: Field>(
|
||||
arity_bits: usize,
|
||||
num_commits: usize,
|
||||
max_path_bits: usize,
|
||||
) -> GateRef<F> {
|
||||
let gate = Self { arity_bits, num_commits, max_path_bits };
|
||||
GateRef::new(DeterministicGateAdapter::new(gate))
|
||||
}
|
||||
|
||||
fn arity(&self) -> usize {
|
||||
1 << self.arity_bits
|
||||
}
|
||||
|
||||
/// Generator for the `i`'th layer of FRI.
|
||||
pub const CONST_GENERATOR_I: usize = 0;
|
||||
|
||||
// Note: These methods relating to wire indices are ordered by wire index. The index
|
||||
// calculations are a little hairy since there are quite a few different "sections" of wires,
|
||||
// and each section has to calculate where the previous sections left off. To make it more
|
||||
// manageable, we have separate functions to calculate the start of each section. There is also
|
||||
// a test to make sure that they behave as expected, with no overlapping indices or what not.
|
||||
|
||||
/// An integer representing the location of the `f^(i + 1)` node in the reduction tree. Its
|
||||
/// `i`th bit in little-endian form represents the direction of the `i`th turn in the query
|
||||
/// path, starting from the root.
|
||||
pub const WIRE_PATH: usize = 0;
|
||||
|
||||
fn start_wire_f_i(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
pub fn wire_f_i(&self, commit_idx: usize, j: usize) -> usize {
|
||||
debug_assert!(commit_idx < self.num_commits);
|
||||
debug_assert!(j < self.arity());
|
||||
self.start_wire_f_i() + self.arity() * commit_idx + j
|
||||
}
|
||||
|
||||
fn start_wire_f_i_plus_1(&self) -> usize {
|
||||
self.start_wire_f_i() + self.arity() * self.num_commits
|
||||
}
|
||||
|
||||
pub fn wire_f_i_plus_1(&self, commit_idx: usize) -> usize {
|
||||
debug_assert!(commit_idx < self.num_commits);
|
||||
self.start_wire_f_i_plus_1() + commit_idx
|
||||
}
|
||||
|
||||
fn start_wire_path_bits(&self) -> usize {
|
||||
self.start_wire_f_i_plus_1() + self.num_commits
|
||||
}
|
||||
|
||||
/// The `i`th bit of the path.
|
||||
fn wire_path_bit_i(&self, i: usize) -> usize {
|
||||
self.start_wire_path_bits() + i
|
||||
}
|
||||
|
||||
fn start_wire_s_j(&self) -> usize {
|
||||
self.start_wire_path_bits() + self.max_path_bits
|
||||
}
|
||||
|
||||
/// The input index of `s_j` (see the FRI paper).
|
||||
fn wire_s_j(&self, j: usize) -> usize {
|
||||
debug_assert!(j < self.arity());
|
||||
self.start_wire_s_j() + j
|
||||
}
|
||||
|
||||
fn start_wire_y(&self) -> usize {
|
||||
self.start_wire_s_j() + self.arity()
|
||||
}
|
||||
|
||||
/// The input index of `y` (see the FRI paper).
|
||||
fn wire_y(&self) -> usize {
|
||||
self.start_wire_y()
|
||||
}
|
||||
|
||||
fn start_wire_coefficient(&self) -> usize {
|
||||
self.start_wire_y() + 1
|
||||
}
|
||||
|
||||
/// The wire input index of the j'th coefficient of the interpolant.
|
||||
fn wire_coefficient(&self, commit_idx: usize, j: usize) -> usize {
|
||||
debug_assert!(commit_idx < self.num_commits);
|
||||
debug_assert!(j < self.arity());
|
||||
self.start_wire_coefficient() + commit_idx * self.arity() + j
|
||||
}
|
||||
|
||||
fn start_unnamed_wires(&self) -> usize {
|
||||
self.start_wire_coefficient() + self.num_commits * self.arity()
|
||||
}
|
||||
|
||||
fn add_s_j_outputs<F: Field>(&self, output_graph: &mut ExpandableOutputGraph<F>) {
|
||||
// Each s_j = g^path, where g is the generator for s_j's layer (see CONST_GENERATOR), and
|
||||
// path is an integer representing the location of s_j in the reduction tree. This assumes
|
||||
// that path is encoded such that its less significant bits are closer to the root of the
|
||||
// tree.
|
||||
|
||||
// Note about bit ordering: in a FRI reduction tree, the `j`th node in a layer can be
|
||||
// written as `g^rev(j)`, where `rev` reverses the bits of its inputs. One way to think of
|
||||
// this is that squaring left-shifts the exponent, so for adjacent nodes to have the same
|
||||
// square, they must differ only in the left-most bit (which will "overflow" after the
|
||||
// left-shift). FFT trees have the same property.
|
||||
|
||||
// We start by computing g^0, g^10, g^100, g^1000, ...
|
||||
let mut squares = vec![ConstraintPolynomial::local_constant(0)];
|
||||
for _ in 1..self.max_path_bits {
|
||||
let prev_square = squares.last().unwrap();
|
||||
let next_square = output_graph.add(prev_square.square());
|
||||
squares.push(next_square)
|
||||
}
|
||||
|
||||
// We can think of path as having two parts: a less significant part that is common to all
|
||||
// {s_j}, and a more significant part that depends on j. We start by computing
|
||||
// g^path_common:
|
||||
let mut g_exp_path_common = ConstraintPolynomial::zero();
|
||||
let shared_path_bits = self.max_path_bits - self.arity_bits;
|
||||
for i in 0..shared_path_bits {
|
||||
let bit = ConstraintPolynomial::local_wire(self.wire_path_bit_i(i));
|
||||
g_exp_path_common = conditional_multiply_poly(&g_exp_path_common, &squares[i], &bit);
|
||||
g_exp_path_common = output_graph.add(g_exp_path_common);
|
||||
}
|
||||
|
||||
// Then, we factor in the "extra" powers of g specific to each child.
|
||||
for j in 0..self.arity() {
|
||||
let mut s_j = g_exp_path_common.clone();
|
||||
for bit_index in 0..self.arity_bits {
|
||||
let bit = (j >> bit_index & 1) != 0;
|
||||
if bit {
|
||||
// See the comment near the top about bit ordering.
|
||||
s_j *= &squares[shared_path_bits + self.arity_bits - 1 - bit_index];
|
||||
}
|
||||
}
|
||||
let s_j_loc = GateOutputLocation::LocalWire(self.wire_s_j(j));
|
||||
output_graph.output_graph.add(s_j_loc, s_j);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_y_output<F: Field>(&self, output_graph: &mut ExpandableOutputGraph<F>) {
|
||||
let loc = GateOutputLocation::LocalWire(self.wire_y());
|
||||
// We can start with any s_j and repeatedly square it. We arbitrary pick s_0.
|
||||
let mut out = ConstraintPolynomial::local_wire(self.wire_s_j(0));
|
||||
for _ in 0..self.arity_bits {
|
||||
out = out.square();
|
||||
}
|
||||
output_graph.output_graph.add(loc, out);
|
||||
}
|
||||
|
||||
fn evaluate_each_poly<F: Field>(&self, commit_idx: usize) -> Vec<ConstraintPolynomial<F>> {
|
||||
let coefficients = (0..self.arity())
|
||||
.map(|i| ConstraintPolynomial::local_wire(self.wire_coefficient(commit_idx, i)))
|
||||
.collect::<Vec<ConstraintPolynomial<F>>>();
|
||||
let mut constraints = Vec::new();
|
||||
|
||||
for j in 0..self.arity() {
|
||||
// Check the evaluation of f^(i) at s_j.
|
||||
let expected = ConstraintPolynomial::local_wire(self.wire_f_i(commit_idx, j));
|
||||
let actual = self.evaluate_poly(&coefficients,
|
||||
ConstraintPolynomial::local_wire(self.wire_s_j(j)));
|
||||
constraints.push(actual - expected);
|
||||
}
|
||||
|
||||
// Check the evaluation of f^(i + 1) at y.
|
||||
let expected = ConstraintPolynomial::local_wire(self.wire_f_i_plus_1(commit_idx));
|
||||
let actual = self.evaluate_poly(&coefficients,
|
||||
ConstraintPolynomial::local_wire(self.wire_y()));
|
||||
constraints.push(actual - expected);
|
||||
|
||||
constraints
|
||||
}
|
||||
|
||||
/// Given a polynomial's coefficients, naively evaluate it at a point.
|
||||
fn evaluate_poly<F: Field>(
|
||||
&self,
|
||||
coefficients: &[ConstraintPolynomial<F>],
|
||||
point: ConstraintPolynomial<F>,
|
||||
) -> ConstraintPolynomial<F> {
|
||||
coefficients.iter()
|
||||
.enumerate()
|
||||
.map(|(i, coeff)| coeff * point.exp(i))
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> DeterministicGate<F> for FriConsistencyGate {
|
||||
fn id(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
|
||||
fn outputs(&self, _config: CircuitConfig) -> OutputGraph<F> {
|
||||
let mut output_graph = ExpandableOutputGraph::new(self.start_unnamed_wires());
|
||||
self.add_s_j_outputs(&mut output_graph);
|
||||
self.add_y_output(&mut output_graph);
|
||||
output_graph.output_graph
|
||||
}
|
||||
|
||||
fn additional_constraints(&self, _config: CircuitConfig) -> Vec<ConstraintPolynomial<F>> {
|
||||
let mut constraints = Vec::new();
|
||||
|
||||
// Add constraints for splitting the path into its binary representation.
|
||||
let bits = (0..self.max_path_bits)
|
||||
.map(|i| ConstraintPolynomial::local_wire(self.wire_path_bit_i(i)))
|
||||
.collect::<Vec<_>>();
|
||||
let split_constraints = split_le_constraints(
|
||||
ConstraintPolynomial::local_wire(Self::WIRE_PATH),
|
||||
&bits);
|
||||
constraints.extend(split_constraints);
|
||||
|
||||
// Add constraints for checking each polynomial evaluation.
|
||||
for commit_idx in 0..self.num_commits {
|
||||
constraints.extend(self.evaluate_each_poly(commit_idx));
|
||||
}
|
||||
|
||||
constraints
|
||||
}
|
||||
|
||||
fn additional_generators(
|
||||
&self,
|
||||
_config: CircuitConfig,
|
||||
gate_index: usize,
|
||||
local_constants: Vec<F>,
|
||||
_next_constants: Vec<F>,
|
||||
) -> Vec<Box<dyn WitnessGenerator<F>>> {
|
||||
let interpolant_generator = Box::new(
|
||||
InterpolantGenerator {
|
||||
gate: *self,
|
||||
gate_index,
|
||||
generator_i: local_constants[Self::CONST_GENERATOR_I],
|
||||
}
|
||||
);
|
||||
|
||||
let bit_input_indices = (0..self.max_path_bits)
|
||||
.map(|i| self.wire_path_bit_i(i))
|
||||
.collect::<Vec<_>>();
|
||||
let split_generator = split_le_generator_local_wires(
|
||||
gate_index, Self::WIRE_PATH, &bit_input_indices);
|
||||
|
||||
vec![interpolant_generator, split_generator]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InterpolantGenerator<F: Field> {
|
||||
gate: FriConsistencyGate,
|
||||
gate_index: usize,
|
||||
generator_i: F,
|
||||
}
|
||||
|
||||
impl<F: Field> InterpolantGenerator<F> {
|
||||
/// Convenience method for converting a wire input index into a Target with our gate index.
|
||||
fn local_wire(&self, input: usize) -> Target {
|
||||
Target::Wire(Wire { gate: self.gate_index, input })
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> SimpleGenerator<F> for InterpolantGenerator<F> {
|
||||
fn dependencies(&self) -> Vec<Target> {
|
||||
let mut deps = vec![self.local_wire(FriConsistencyGate::WIRE_PATH)];
|
||||
for i in 0..self.gate.arity() {
|
||||
deps.push(self.local_wire(self.gate.wire_s_j(i)));
|
||||
for commit_idx in 0..self.gate.num_commits {
|
||||
deps.push(self.local_wire(self.gate.wire_f_i(commit_idx, i)));
|
||||
}
|
||||
}
|
||||
deps
|
||||
}
|
||||
|
||||
fn run_once(&self, witness: &PartialWitness<F>) -> PartialWitness<F> {
|
||||
let mut result = PartialWitness::new();
|
||||
|
||||
for commit_idx in 0..self.gate.num_commits {
|
||||
let values = (0..self.gate.arity())
|
||||
.map(|j| witness.get_target(self.local_wire(self.gate.wire_f_i(commit_idx, j))))
|
||||
.collect();
|
||||
|
||||
let path = witness.get_target(self.local_wire(FriConsistencyGate::WIRE_PATH));
|
||||
let shift = self.generator_i.exp(path);
|
||||
let coeffs = coset_ifft(values, shift);
|
||||
|
||||
for (i, coeff) in coeffs.into_iter().enumerate() {
|
||||
result.set_target(
|
||||
self.local_wire(self.gate.wire_coefficient(commit_idx, i)),
|
||||
coeff);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::gates::fri_consistency_gate::FriConsistencyGate;
|
||||
|
||||
#[test]
|
||||
fn wire_indices() {
|
||||
let gate = FriConsistencyGate {
|
||||
arity_bits: 1,
|
||||
num_commits: 2,
|
||||
max_path_bits: 4,
|
||||
};
|
||||
|
||||
// The actual indices aren't really important, but we want to make sure that
|
||||
// - there are no overlaps
|
||||
// - there are no gaps
|
||||
// - the routed inputs come first
|
||||
assert_eq!(0, FriConsistencyGate::WIRE_PATH);
|
||||
assert_eq!(1, gate.wire_f_i(0, 0));
|
||||
assert_eq!(2, gate.wire_f_i(0, 1));
|
||||
assert_eq!(3, gate.wire_f_i(1, 0));
|
||||
assert_eq!(4, gate.wire_f_i(1, 1));
|
||||
assert_eq!(5, gate.wire_f_i_plus_1(0));
|
||||
assert_eq!(6, gate.wire_f_i_plus_1(1));
|
||||
assert_eq!(7, gate.wire_path_bit_i(0));
|
||||
assert_eq!(8, gate.wire_path_bit_i(1));
|
||||
assert_eq!(9, gate.wire_path_bit_i(2));
|
||||
assert_eq!(10, gate.wire_path_bit_i(3));
|
||||
assert_eq!(11, gate.wire_s_j(0));
|
||||
assert_eq!(12, gate.wire_s_j(1));
|
||||
assert_eq!(13, gate.wire_y());
|
||||
assert_eq!(14, gate.wire_coefficient(0, 0));
|
||||
assert_eq!(15, gate.wire_coefficient(0, 1));
|
||||
assert_eq!(16, gate.wire_coefficient(1, 0));
|
||||
assert_eq!(17, gate.wire_coefficient(1, 1));
|
||||
assert_eq!(18, gate.start_unnamed_wires());
|
||||
}
|
||||
}
|
||||
// use crate::circuit_data::CircuitConfig;
|
||||
// use crate::constraint_polynomial::ConstraintPolynomial;
|
||||
// use crate::field::fft::coset_ifft;
|
||||
// use crate::field::field::Field;
|
||||
// use crate::gadgets::split_join::{split_le_constraints, split_le_generator_local_wires};
|
||||
// use crate::gates::deterministic_gate::{DeterministicGate, DeterministicGateAdapter};
|
||||
// use crate::gates::gate::GateRef;
|
||||
// use crate::gates::output_graph::{OutputGraph, GateOutputLocation, ExpandableOutputGraph};
|
||||
// use crate::generator::{SimpleGenerator, WitnessGenerator};
|
||||
// use crate::target::Target;
|
||||
// use crate::wire::Wire;
|
||||
// use crate::witness::PartialWitness;
|
||||
// use crate::gadgets::conditionals::conditional_multiply_poly;
|
||||
//
|
||||
// /// Performs a FRI consistency check. The goal is to check consistency between polynomials `f^(i)`
|
||||
// /// and `f^(i + 1)`, where `f^(i + 1)` is supposed to be an `arity`-to-one reduction of `f^(i)`. See
|
||||
// /// the FRI paper for details.
|
||||
// ///
|
||||
// /// This check involves `arity` openings of `f^(i)` and a single opening of `f^(i + 1)`. Let's call
|
||||
// /// the set of `f^(i)` opening locations `{s_j}`, and the `f^(i + 1)` opening location `y`. Note
|
||||
// /// that all of these points can be derived from the query path. So, we take as input an integer
|
||||
// /// whose binary decomposition represents the left and right turns in the query path.
|
||||
// ///
|
||||
// /// Since our protocol uses a variant of FRI with `k` commit phases, this gate takes `k` opening
|
||||
// /// sets for `f^(i)`, and `k` openings for `f^(i + 1)`.
|
||||
// ///
|
||||
// /// Putting it together, this gate does the following:
|
||||
// /// - Computes the binary decomposition of the input representing the query path.
|
||||
// /// - Computes `{s_j}` and `y` from the query path.
|
||||
// /// - For each commit phase:
|
||||
// /// - Non-deterministically interpolates a polynomial through each `(s_j, f^(i))`.
|
||||
// /// - Evaluates the purported interpolant at each `s_j`, and checks that the result matches the
|
||||
// /// associated opening of `f^(i)(s_j)`.
|
||||
// /// - Evaluates the purported interpolant at `y`, and checks that the result matches the opening
|
||||
// /// of `f^(i + 1)(y)`.
|
||||
// #[derive(Debug, Copy, Clone)]
|
||||
// pub(crate) struct FriConsistencyGate {
|
||||
// /// The arity of this reduction step.
|
||||
// arity_bits: usize,
|
||||
//
|
||||
// /// The number of commit phases.
|
||||
// num_commits: usize,
|
||||
//
|
||||
// /// The maximum number of bits in any query path.
|
||||
// max_path_bits: usize,
|
||||
// }
|
||||
//
|
||||
// impl FriConsistencyGate {
|
||||
// pub fn new<F: Field>(
|
||||
// arity_bits: usize,
|
||||
// num_commits: usize,
|
||||
// max_path_bits: usize,
|
||||
// ) -> GateRef<F> {
|
||||
// let gate = Self { arity_bits, num_commits, max_path_bits };
|
||||
// GateRef::new(DeterministicGateAdapter::new(gate))
|
||||
// }
|
||||
//
|
||||
// fn arity(&self) -> usize {
|
||||
// 1 << self.arity_bits
|
||||
// }
|
||||
//
|
||||
// /// Generator for the `i`'th layer of FRI.
|
||||
// pub const CONST_GENERATOR_I: usize = 0;
|
||||
//
|
||||
// // Note: These methods relating to wire indices are ordered by wire index. The index
|
||||
// // calculations are a little hairy since there are quite a few different "sections" of wires,
|
||||
// // and each section has to calculate where the previous sections left off. To make it more
|
||||
// // manageable, we have separate functions to calculate the start of each section. There is also
|
||||
// // a test to make sure that they behave as expected, with no overlapping indices or what not.
|
||||
//
|
||||
// /// An integer representing the location of the `f^(i + 1)` node in the reduction tree. Its
|
||||
// /// `i`th bit in little-endian form represents the direction of the `i`th turn in the query
|
||||
// /// path, starting from the root.
|
||||
// pub const WIRE_PATH: usize = 0;
|
||||
//
|
||||
// fn start_wire_f_i(&self) -> usize {
|
||||
// 1
|
||||
// }
|
||||
//
|
||||
// pub fn wire_f_i(&self, commit_idx: usize, j: usize) -> usize {
|
||||
// debug_assert!(commit_idx < self.num_commits);
|
||||
// debug_assert!(j < self.arity());
|
||||
// self.start_wire_f_i() + self.arity() * commit_idx + j
|
||||
// }
|
||||
//
|
||||
// fn start_wire_f_i_plus_1(&self) -> usize {
|
||||
// self.start_wire_f_i() + self.arity() * self.num_commits
|
||||
// }
|
||||
//
|
||||
// pub fn wire_f_i_plus_1(&self, commit_idx: usize) -> usize {
|
||||
// debug_assert!(commit_idx < self.num_commits);
|
||||
// self.start_wire_f_i_plus_1() + commit_idx
|
||||
// }
|
||||
//
|
||||
// fn start_wire_path_bits(&self) -> usize {
|
||||
// self.start_wire_f_i_plus_1() + self.num_commits
|
||||
// }
|
||||
//
|
||||
// /// The `i`th bit of the path.
|
||||
// fn wire_path_bit_i(&self, i: usize) -> usize {
|
||||
// self.start_wire_path_bits() + i
|
||||
// }
|
||||
//
|
||||
// fn start_wire_s_j(&self) -> usize {
|
||||
// self.start_wire_path_bits() + self.max_path_bits
|
||||
// }
|
||||
//
|
||||
// /// The input index of `s_j` (see the FRI paper).
|
||||
// fn wire_s_j(&self, j: usize) -> usize {
|
||||
// debug_assert!(j < self.arity());
|
||||
// self.start_wire_s_j() + j
|
||||
// }
|
||||
//
|
||||
// fn start_wire_y(&self) -> usize {
|
||||
// self.start_wire_s_j() + self.arity()
|
||||
// }
|
||||
//
|
||||
// /// The input index of `y` (see the FRI paper).
|
||||
// fn wire_y(&self) -> usize {
|
||||
// self.start_wire_y()
|
||||
// }
|
||||
//
|
||||
// fn start_wire_coefficient(&self) -> usize {
|
||||
// self.start_wire_y() + 1
|
||||
// }
|
||||
//
|
||||
// /// The wire input index of the j'th coefficient of the interpolant.
|
||||
// fn wire_coefficient(&self, commit_idx: usize, j: usize) -> usize {
|
||||
// debug_assert!(commit_idx < self.num_commits);
|
||||
// debug_assert!(j < self.arity());
|
||||
// self.start_wire_coefficient() + commit_idx * self.arity() + j
|
||||
// }
|
||||
//
|
||||
// fn start_unnamed_wires(&self) -> usize {
|
||||
// self.start_wire_coefficient() + self.num_commits * self.arity()
|
||||
// }
|
||||
//
|
||||
// fn add_s_j_outputs<F: Field>(&self, output_graph: &mut ExpandableOutputGraph<F>) {
|
||||
// // Each s_j = g^path, where g is the generator for s_j's layer (see CONST_GENERATOR), and
|
||||
// // path is an integer representing the location of s_j in the reduction tree. This assumes
|
||||
// // that path is encoded such that its less significant bits are closer to the root of the
|
||||
// // tree.
|
||||
//
|
||||
// // Note about bit ordering: in a FRI reduction tree, the `j`th node in a layer can be
|
||||
// // written as `g^rev(j)`, where `rev` reverses the bits of its inputs. One way to think of
|
||||
// // this is that squaring left-shifts the exponent, so for adjacent nodes to have the same
|
||||
// // square, they must differ only in the left-most bit (which will "overflow" after the
|
||||
// // left-shift). FFT trees have the same property.
|
||||
//
|
||||
// // We start by computing g^0, g^10, g^100, g^1000, ...
|
||||
// let mut squares = vec![ConstraintPolynomial::local_constant(0)];
|
||||
// for _ in 1..self.max_path_bits {
|
||||
// let prev_square = squares.last().unwrap();
|
||||
// let next_square = output_graph.add(prev_square.square());
|
||||
// squares.push(next_square)
|
||||
// }
|
||||
//
|
||||
// // We can think of path as having two parts: a less significant part that is common to all
|
||||
// // {s_j}, and a more significant part that depends on j. We start by computing
|
||||
// // g^path_common:
|
||||
// let mut g_exp_path_common = ConstraintPolynomial::zero();
|
||||
// let shared_path_bits = self.max_path_bits - self.arity_bits;
|
||||
// for i in 0..shared_path_bits {
|
||||
// let bit = ConstraintPolynomial::local_wire(self.wire_path_bit_i(i));
|
||||
// g_exp_path_common = conditional_multiply_poly(&g_exp_path_common, &squares[i], &bit);
|
||||
// g_exp_path_common = output_graph.add(g_exp_path_common);
|
||||
// }
|
||||
//
|
||||
// // Then, we factor in the "extra" powers of g specific to each child.
|
||||
// for j in 0..self.arity() {
|
||||
// let mut s_j = g_exp_path_common.clone();
|
||||
// for bit_index in 0..self.arity_bits {
|
||||
// let bit = (j >> bit_index & 1) != 0;
|
||||
// if bit {
|
||||
// // See the comment near the top about bit ordering.
|
||||
// s_j *= &squares[shared_path_bits + self.arity_bits - 1 - bit_index];
|
||||
// }
|
||||
// }
|
||||
// let s_j_loc = GateOutputLocation::LocalWire(self.wire_s_j(j));
|
||||
// output_graph.output_graph.add(s_j_loc, s_j);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fn add_y_output<F: Field>(&self, output_graph: &mut ExpandableOutputGraph<F>) {
|
||||
// let loc = GateOutputLocation::LocalWire(self.wire_y());
|
||||
// // We can start with any s_j and repeatedly square it. We arbitrary pick s_0.
|
||||
// let mut out = ConstraintPolynomial::local_wire(self.wire_s_j(0));
|
||||
// for _ in 0..self.arity_bits {
|
||||
// out = out.square();
|
||||
// }
|
||||
// output_graph.output_graph.add(loc, out);
|
||||
// }
|
||||
//
|
||||
// fn evaluate_each_poly<F: Field>(&self, commit_idx: usize) -> Vec<ConstraintPolynomial<F>> {
|
||||
// let coefficients = (0..self.arity())
|
||||
// .map(|i| ConstraintPolynomial::local_wire(self.wire_coefficient(commit_idx, i)))
|
||||
// .collect::<Vec<ConstraintPolynomial<F>>>();
|
||||
// let mut constraints = Vec::new();
|
||||
//
|
||||
// for j in 0..self.arity() {
|
||||
// // Check the evaluation of f^(i) at s_j.
|
||||
// let expected = ConstraintPolynomial::local_wire(self.wire_f_i(commit_idx, j));
|
||||
// let actual = self.evaluate_poly(&coefficients,
|
||||
// ConstraintPolynomial::local_wire(self.wire_s_j(j)));
|
||||
// constraints.push(actual - expected);
|
||||
// }
|
||||
//
|
||||
// // Check the evaluation of f^(i + 1) at y.
|
||||
// let expected = ConstraintPolynomial::local_wire(self.wire_f_i_plus_1(commit_idx));
|
||||
// let actual = self.evaluate_poly(&coefficients,
|
||||
// ConstraintPolynomial::local_wire(self.wire_y()));
|
||||
// constraints.push(actual - expected);
|
||||
//
|
||||
// constraints
|
||||
// }
|
||||
//
|
||||
// /// Given a polynomial's coefficients, naively evaluate it at a point.
|
||||
// fn evaluate_poly<F: Field>(
|
||||
// &self,
|
||||
// coefficients: &[ConstraintPolynomial<F>],
|
||||
// point: ConstraintPolynomial<F>,
|
||||
// ) -> ConstraintPolynomial<F> {
|
||||
// coefficients.iter()
|
||||
// .enumerate()
|
||||
// .map(|(i, coeff)| coeff * point.exp(i))
|
||||
// .sum()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl<F: Field> DeterministicGate<F> for FriConsistencyGate {
|
||||
// fn id(&self) -> String {
|
||||
// format!("{:?}", self)
|
||||
// }
|
||||
//
|
||||
// fn outputs(&self, _config: CircuitConfig) -> OutputGraph<F> {
|
||||
// let mut output_graph = ExpandableOutputGraph::new(self.start_unnamed_wires());
|
||||
// self.add_s_j_outputs(&mut output_graph);
|
||||
// self.add_y_output(&mut output_graph);
|
||||
// output_graph.output_graph
|
||||
// }
|
||||
//
|
||||
// fn additional_constraints(&self, _config: CircuitConfig) -> Vec<ConstraintPolynomial<F>> {
|
||||
// let mut constraints = Vec::new();
|
||||
//
|
||||
// // Add constraints for splitting the path into its binary representation.
|
||||
// let bits = (0..self.max_path_bits)
|
||||
// .map(|i| ConstraintPolynomial::local_wire(self.wire_path_bit_i(i)))
|
||||
// .collect::<Vec<_>>();
|
||||
// let split_constraints = split_le_constraints(
|
||||
// ConstraintPolynomial::local_wire(Self::WIRE_PATH),
|
||||
// &bits);
|
||||
// constraints.extend(split_constraints);
|
||||
//
|
||||
// // Add constraints for checking each polynomial evaluation.
|
||||
// for commit_idx in 0..self.num_commits {
|
||||
// constraints.extend(self.evaluate_each_poly(commit_idx));
|
||||
// }
|
||||
//
|
||||
// constraints
|
||||
// }
|
||||
//
|
||||
// fn additional_generators(
|
||||
// &self,
|
||||
// _config: CircuitConfig,
|
||||
// gate_index: usize,
|
||||
// local_constants: Vec<F>,
|
||||
// _next_constants: Vec<F>,
|
||||
// ) -> Vec<Box<dyn WitnessGenerator<F>>> {
|
||||
// let interpolant_generator = Box::new(
|
||||
// InterpolantGenerator {
|
||||
// gate: *self,
|
||||
// gate_index,
|
||||
// generator_i: local_constants[Self::CONST_GENERATOR_I],
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// let bit_input_indices = (0..self.max_path_bits)
|
||||
// .map(|i| self.wire_path_bit_i(i))
|
||||
// .collect::<Vec<_>>();
|
||||
// let split_generator = split_le_generator_local_wires(
|
||||
// gate_index, Self::WIRE_PATH, &bit_input_indices);
|
||||
//
|
||||
// vec![interpolant_generator, split_generator]
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[derive(Debug)]
|
||||
// struct InterpolantGenerator<F: Field> {
|
||||
// gate: FriConsistencyGate,
|
||||
// gate_index: usize,
|
||||
// generator_i: F,
|
||||
// }
|
||||
//
|
||||
// impl<F: Field> InterpolantGenerator<F> {
|
||||
// /// Convenience method for converting a wire input index into a Target with our gate index.
|
||||
// fn local_wire(&self, input: usize) -> Target {
|
||||
// Target::Wire(Wire { gate: self.gate_index, input })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl<F: Field> SimpleGenerator<F> for InterpolantGenerator<F> {
|
||||
// fn dependencies(&self) -> Vec<Target> {
|
||||
// let mut deps = vec![self.local_wire(FriConsistencyGate::WIRE_PATH)];
|
||||
// for i in 0..self.gate.arity() {
|
||||
// deps.push(self.local_wire(self.gate.wire_s_j(i)));
|
||||
// for commit_idx in 0..self.gate.num_commits {
|
||||
// deps.push(self.local_wire(self.gate.wire_f_i(commit_idx, i)));
|
||||
// }
|
||||
// }
|
||||
// deps
|
||||
// }
|
||||
//
|
||||
// fn run_once(&self, witness: &PartialWitness<F>) -> PartialWitness<F> {
|
||||
// let mut result = PartialWitness::new();
|
||||
//
|
||||
// for commit_idx in 0..self.gate.num_commits {
|
||||
// let values = (0..self.gate.arity())
|
||||
// .map(|j| witness.get_target(self.local_wire(self.gate.wire_f_i(commit_idx, j))))
|
||||
// .collect();
|
||||
//
|
||||
// let path = witness.get_target(self.local_wire(FriConsistencyGate::WIRE_PATH));
|
||||
// let shift = self.generator_i.exp(path);
|
||||
// let coeffs = coset_ifft(values, shift);
|
||||
//
|
||||
// for (i, coeff) in coeffs.into_iter().enumerate() {
|
||||
// result.set_target(
|
||||
// self.local_wire(self.gate.wire_coefficient(commit_idx, i)),
|
||||
// coeff);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// result
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use crate::gates::fri_consistency_gate::FriConsistencyGate;
|
||||
//
|
||||
// #[test]
|
||||
// fn wire_indices() {
|
||||
// let gate = FriConsistencyGate {
|
||||
// arity_bits: 1,
|
||||
// num_commits: 2,
|
||||
// max_path_bits: 4,
|
||||
// };
|
||||
//
|
||||
// // The actual indices aren't really important, but we want to make sure that
|
||||
// // - there are no overlaps
|
||||
// // - there are no gaps
|
||||
// // - the routed inputs come first
|
||||
// assert_eq!(0, FriConsistencyGate::WIRE_PATH);
|
||||
// assert_eq!(1, gate.wire_f_i(0, 0));
|
||||
// assert_eq!(2, gate.wire_f_i(0, 1));
|
||||
// assert_eq!(3, gate.wire_f_i(1, 0));
|
||||
// assert_eq!(4, gate.wire_f_i(1, 1));
|
||||
// assert_eq!(5, gate.wire_f_i_plus_1(0));
|
||||
// assert_eq!(6, gate.wire_f_i_plus_1(1));
|
||||
// assert_eq!(7, gate.wire_path_bit_i(0));
|
||||
// assert_eq!(8, gate.wire_path_bit_i(1));
|
||||
// assert_eq!(9, gate.wire_path_bit_i(2));
|
||||
// assert_eq!(10, gate.wire_path_bit_i(3));
|
||||
// assert_eq!(11, gate.wire_s_j(0));
|
||||
// assert_eq!(12, gate.wire_s_j(1));
|
||||
// assert_eq!(13, gate.wire_y());
|
||||
// assert_eq!(14, gate.wire_coefficient(0, 0));
|
||||
// assert_eq!(15, gate.wire_coefficient(0, 1));
|
||||
// assert_eq!(16, gate.wire_coefficient(1, 0));
|
||||
// assert_eq!(17, gate.wire_coefficient(1, 1));
|
||||
// assert_eq!(18, gate.start_unnamed_wires());
|
||||
// }
|
||||
// }
|
||||
|
||||
@ -1,62 +1,48 @@
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::circuit_data::CircuitConfig;
|
||||
use crate::constraint_polynomial::{ConstraintPolynomial, EvaluationVars};
|
||||
use crate::field::field::Field;
|
||||
use crate::generator::WitnessGenerator;
|
||||
use num::ToPrimitive;
|
||||
|
||||
use crate::circuit_builder::CircuitBuilder;
|
||||
use crate::constraint_polynomial::{EvaluationTargets, EvaluationVars};
|
||||
use crate::field::field::Field;
|
||||
use crate::generator::WitnessGenerator;
|
||||
use crate::target::Target;
|
||||
|
||||
/// A custom gate.
|
||||
// TODO: Remove CircuitConfig params? Could just use fields within each struct.
|
||||
pub trait Gate<F: Field>: 'static {
|
||||
fn id(&self) -> String;
|
||||
|
||||
/// A set of expressions which must evaluate to zero.
|
||||
fn constraints(&self, config: CircuitConfig) -> Vec<ConstraintPolynomial<F>>;
|
||||
fn eval_unfiltered(&self, vars: EvaluationVars<F>) -> Vec<F>;
|
||||
|
||||
// fn eval_constraints(&self, config: CircuitConfig, vars: EvaluationVars<F>) -> Vec<F> {
|
||||
// self.constraints(config)
|
||||
// .into_iter()
|
||||
// .map(|c| c.evaluate(vars))
|
||||
// .collect()
|
||||
// }
|
||||
fn eval_unfiltered_recursively(
|
||||
&self,
|
||||
builder: &mut CircuitBuilder<F>,
|
||||
vars: EvaluationTargets,
|
||||
) -> Vec<Target>;
|
||||
|
||||
fn eval_filtered(&self, vars: EvaluationVars<F>) -> Vec<F> {
|
||||
// TODO: Filter
|
||||
self.eval_unfiltered(vars)
|
||||
}
|
||||
|
||||
fn generators(
|
||||
&self,
|
||||
config: CircuitConfig,
|
||||
gate_index: usize,
|
||||
// TODO: Switch to slices?
|
||||
local_constants: Vec<F>,
|
||||
next_constants: Vec<F>,
|
||||
local_constants: &[F],
|
||||
next_constants: &[F],
|
||||
) -> Vec<Box<dyn WitnessGenerator<F>>>;
|
||||
|
||||
/// The number of constants used by this gate.
|
||||
fn num_constants(&self, config: CircuitConfig) -> usize {
|
||||
self.constraints(config)
|
||||
.into_iter()
|
||||
.map(|c| c.max_constant_index().map_or(0, |i| i + 1))
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
/// The number of wires used by this gate.
|
||||
fn num_wires(&self) -> usize;
|
||||
|
||||
/// The minimum number of wires required to use this gate.
|
||||
fn min_wires(&self, config: CircuitConfig) -> usize {
|
||||
self.constraints(config)
|
||||
.into_iter()
|
||||
.map(|c| c.max_wire_input_index().map_or(0, |i| i + 1))
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
/// The number of constants used by this gate.
|
||||
fn num_constants(&self) -> usize;
|
||||
|
||||
/// The maximum degree among this gate's constraint polynomials.
|
||||
fn degree(&self, config: CircuitConfig) -> usize {
|
||||
self.constraints(config)
|
||||
.into_iter()
|
||||
.map(|c| c.degree().to_usize().expect("degree too large"))
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
fn degree(&self) -> usize;
|
||||
|
||||
fn num_constraints(&self) -> usize;
|
||||
}
|
||||
|
||||
/// A wrapper around an `Rc<Gate>` which implements `PartialEq`, `Eq` and `Hash` based on gate IDs.
|
||||
|
||||
@ -2,25 +2,31 @@ use std::sync::Arc;
|
||||
|
||||
use num::{BigUint, One};
|
||||
|
||||
use crate::circuit_builder::CircuitBuilder;
|
||||
use crate::circuit_data::CircuitConfig;
|
||||
use crate::constraint_polynomial::ConstraintPolynomial;
|
||||
use crate::constraint_polynomial::{EvaluationTargets, EvaluationVars};
|
||||
use crate::field::field::Field;
|
||||
use crate::gates::deterministic_gate::{DeterministicGate, DeterministicGateAdapter};
|
||||
use crate::gates::gate::GateRef;
|
||||
use crate::gates::output_graph::{GateOutputLocation, OutputGraph};
|
||||
use crate::gates::gate::{Gate, GateRef};
|
||||
use crate::generator::{SimpleGenerator, WitnessGenerator};
|
||||
use crate::target::Target;
|
||||
use crate::witness::PartialWitness;
|
||||
use crate::wire::Wire;
|
||||
use crate::gates::noop::NoopGate;
|
||||
|
||||
/// Evaluates a full GMiMC permutation, and writes the output to the next gate's first `width`
|
||||
/// wires (which could be the input of another `GMiMCGate`).
|
||||
/// The width of the permutation, in field elements.
|
||||
const W: usize = 12;
|
||||
|
||||
/// Evaluates a full GMiMC permutation with 12 state elements, and writes the output to the next
|
||||
/// gate's first `width` wires (which could be the input of another `GMiMCGate`).
|
||||
#[derive(Debug)]
|
||||
pub struct GMiMCGate<F: Field, const W: usize, const R: usize> {
|
||||
pub struct GMiMCGate<F: Field, const R: usize> {
|
||||
constants: Arc<[F; R]>,
|
||||
}
|
||||
|
||||
impl<F: Field, const W: usize, const R: usize> GMiMCGate<F, W, R> {
|
||||
impl<F: Field, const R: usize> GMiMCGate<F, R> {
|
||||
pub fn with_constants(constants: Arc<[F; R]>) -> GateRef<F> {
|
||||
let gate = GMiMCGate::<F, W, R> { constants };
|
||||
let adapter = DeterministicGateAdapter::new(gate);
|
||||
GateRef::new(adapter)
|
||||
let gate = GMiMCGate::<F, R> { constants };
|
||||
GateRef::new(gate)
|
||||
}
|
||||
|
||||
pub fn with_automatic_constants() -> GateRef<F> {
|
||||
@ -42,79 +48,163 @@ impl<F: Field, const W: usize, const R: usize> GMiMCGate<F, W, R> {
|
||||
i
|
||||
}
|
||||
|
||||
/// Adds a local wire output to this output graph. Returns a `ConstraintPolynomial` which
|
||||
/// references the newly-created wire.
|
||||
///
|
||||
/// This may seem like it belongs in `OutputGraph`, but it is not that general, since it uses
|
||||
/// a notion of "next available" local wire indices specific to this gate.
|
||||
// TODO: Switch to `ExpandableOutputGraph`.
|
||||
fn add_local(
|
||||
outputs: &mut OutputGraph<F>,
|
||||
poly: ConstraintPolynomial<F>,
|
||||
) -> ConstraintPolynomial<F> {
|
||||
let index = outputs.max_local_output_index().map_or(W + 1, |i| i + 1);
|
||||
outputs.add(GateOutputLocation::LocalWire(index), poly);
|
||||
ConstraintPolynomial::local_wire(index)
|
||||
/// A wire which stores the input to the `i`th cubing.
|
||||
fn wire_cubing_input(i: usize) -> usize {
|
||||
W + 1 + i
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field, const W: usize, const R: usize> DeterministicGate<F> for GMiMCGate<F, W, R> {
|
||||
impl<F: Field, const R: usize> Gate<F> for GMiMCGate<F, R> {
|
||||
fn id(&self) -> String {
|
||||
// TODO: This won't include generic params?
|
||||
format!("{:?}", self)
|
||||
}
|
||||
|
||||
fn outputs(&self, _config: CircuitConfig) -> OutputGraph<F> {
|
||||
let original_inputs = (0..W)
|
||||
.map(|i| ConstraintPolynomial::local_wire(Self::wire_input(i)))
|
||||
fn eval_unfiltered(&self, vars: EvaluationVars<F>) -> Vec<F> {
|
||||
let mut constraints = Vec::with_capacity(W + R);
|
||||
|
||||
// Value that is implicitly added to each element.
|
||||
// See https://affine.group/2020/02/starkware-challenge
|
||||
let mut addition_buffer = F::ZERO;
|
||||
|
||||
let switch = vars.local_wires[Self::WIRE_SWITCH];
|
||||
let mut state = Vec::with_capacity(12);
|
||||
for i in 0..4 {
|
||||
let a = vars.local_wires[i];
|
||||
let b = vars.local_wires[i + 4];
|
||||
state.push(a + switch * (b - a));
|
||||
}
|
||||
for i in 0..4 {
|
||||
let a = vars.local_wires[i + 4];
|
||||
let b = vars.local_wires[i];
|
||||
state.push(a + switch * (b - a));
|
||||
}
|
||||
for i in 8..12 {
|
||||
state.push(vars.local_wires[i]);
|
||||
}
|
||||
|
||||
for r in 0..R {
|
||||
let active = r % W;
|
||||
let cubing_input = state[active] + addition_buffer + self.constants[r];
|
||||
let cubing_input_wire = vars.local_wires[Self::wire_cubing_input(r)];
|
||||
constraints.push(cubing_input - cubing_input_wire);
|
||||
let f = cubing_input_wire.cube();
|
||||
addition_buffer += f;
|
||||
state[active] -= f;
|
||||
}
|
||||
|
||||
for i in 0..W {
|
||||
state[i] += addition_buffer;
|
||||
constraints.push(state[i] - vars.next_wires[i]);
|
||||
}
|
||||
|
||||
constraints
|
||||
}
|
||||
|
||||
fn eval_unfiltered_recursively(
|
||||
&self,
|
||||
builder: &mut CircuitBuilder<F>,
|
||||
vars: EvaluationTargets,
|
||||
) -> Vec<Target> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn generators(
|
||||
&self,
|
||||
gate_index: usize,
|
||||
_local_constants: &[F],
|
||||
_next_constants: &[F],
|
||||
) -> Vec<Box<dyn WitnessGenerator<F>>> {
|
||||
let gen = GMiMCGenerator {
|
||||
gate_index,
|
||||
constants: self.constants.clone(),
|
||||
};
|
||||
vec![Box::new(gen)]
|
||||
}
|
||||
|
||||
fn num_wires(&self) -> usize {
|
||||
W + 1 + R
|
||||
}
|
||||
|
||||
fn num_constants(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn degree(&self) -> usize {
|
||||
3
|
||||
}
|
||||
|
||||
fn num_constraints(&self) -> usize {
|
||||
R + W
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct GMiMCGenerator<F: Field, const R: usize> {
|
||||
gate_index: usize,
|
||||
constants: Arc<[F; R]>,
|
||||
}
|
||||
|
||||
impl<F: Field, const R: usize> SimpleGenerator<F> for GMiMCGenerator<F, R> {
|
||||
fn dependencies(&self) -> Vec<Target> {
|
||||
(0..W)
|
||||
.map(|i| Target::Wire(Wire {
|
||||
gate: self.gate_index,
|
||||
input: GMiMCGate::<F, R>::wire_input(i),
|
||||
}))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn run_once(&self, witness: &PartialWitness<F>) -> PartialWitness<F> {
|
||||
let mut result = PartialWitness::new();
|
||||
|
||||
let mut state = (0..W)
|
||||
.map(|i| witness.get_wire(Wire {
|
||||
gate: self.gate_index,
|
||||
input: GMiMCGate::<F, R>::wire_input(i),
|
||||
}))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut outputs = OutputGraph::new();
|
||||
|
||||
// Conditionally switch inputs based on the (boolean) switch wire.
|
||||
let switch = ConstraintPolynomial::local_wire(Self::WIRE_SWITCH);
|
||||
let mut state = Vec::new();
|
||||
for i in 0..4 {
|
||||
let a = &original_inputs[i];
|
||||
let b = &original_inputs[i + 4];
|
||||
state.push(a + &switch * (b - a));
|
||||
}
|
||||
for i in 0..4 {
|
||||
let a = &original_inputs[i + 4];
|
||||
let b = &original_inputs[i];
|
||||
state.push(a + &switch * (b - a));
|
||||
}
|
||||
for i in 8..W {
|
||||
state.push(original_inputs[i].clone());
|
||||
let switch_value = witness.get_wire(Wire {
|
||||
gate: self.gate_index,
|
||||
input: GMiMCGate::<F, R>::WIRE_SWITCH,
|
||||
});
|
||||
debug_assert!(switch_value == F::ZERO || switch_value == F::ONE);
|
||||
if switch_value == F::ONE {
|
||||
for i in 0..4 {
|
||||
state.swap(i, 4 + i);
|
||||
}
|
||||
}
|
||||
|
||||
// Value that is implicitly added to each element.
|
||||
// See https://affine.group/2020/02/starkware-challenge
|
||||
let mut addition_buffer = ConstraintPolynomial::zero();
|
||||
let mut addition_buffer = F::ZERO;
|
||||
|
||||
for r in 0..R {
|
||||
let active = r % W;
|
||||
let round_constant = ConstraintPolynomial::constant(self.constants[r]);
|
||||
let mut f_input = &state[active] + &addition_buffer + round_constant;
|
||||
if f_input.degree() > BigUint::one() {
|
||||
f_input = Self::add_local(&mut outputs, f_input);
|
||||
}
|
||||
let f_output = f_input.cube();
|
||||
addition_buffer += &f_output;
|
||||
state[active] -= f_output;
|
||||
let cubing_input = state[active] + addition_buffer + self.constants[r];
|
||||
result.set_wire(
|
||||
Wire {
|
||||
gate: self.gate_index,
|
||||
input: GMiMCGate::<F, R>::wire_cubing_input(r),
|
||||
},
|
||||
cubing_input);
|
||||
let f = cubing_input.cube();
|
||||
addition_buffer += f;
|
||||
state[active] -= f;
|
||||
}
|
||||
|
||||
for i in 0..W {
|
||||
outputs.add(GateOutputLocation::NextWire(i), &state[i] + &addition_buffer);
|
||||
state[i] += addition_buffer;
|
||||
result.set_wire(
|
||||
Wire {
|
||||
gate: self.gate_index + 1,
|
||||
input: GMiMCGate::<F, R>::wire_output(i),
|
||||
},
|
||||
state[i]);
|
||||
}
|
||||
|
||||
outputs
|
||||
}
|
||||
|
||||
fn additional_constraints(&self, _config: CircuitConfig) -> Vec<ConstraintPolynomial<F>> {
|
||||
let switch = ConstraintPolynomial::local_wire(Self::WIRE_SWITCH);
|
||||
let switch_bool_constraint = &switch * (&switch - 1);
|
||||
vec![switch_bool_constraint]
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,40 +217,24 @@ mod tests {
|
||||
use crate::field::crandall_field::CrandallField;
|
||||
use crate::field::field::Field;
|
||||
use crate::gates::deterministic_gate::DeterministicGate;
|
||||
use crate::gates::gmimc::GMiMCGate;
|
||||
use crate::gates::gmimc::{GMiMCGate, W};
|
||||
use crate::generator::generate_partial_witness;
|
||||
use crate::gmimc::gmimc_permute_naive;
|
||||
use crate::wire::Wire;
|
||||
use crate::witness::PartialWitness;
|
||||
|
||||
#[test]
|
||||
fn degree() {
|
||||
type F = CrandallField;
|
||||
const W: usize = 12;
|
||||
const R: usize = 101;
|
||||
let gate = GMiMCGate::<F, W, R> { constants: Arc::new([F::TWO; R]) };
|
||||
let config = CircuitConfig {
|
||||
num_wires: 200,
|
||||
num_routed_wires: 200,
|
||||
security_bits: 128,
|
||||
};
|
||||
let outs = gate.outputs(config);
|
||||
assert_eq!(outs.degree(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_output() {
|
||||
type F = CrandallField;
|
||||
const W: usize = 12;
|
||||
const R: usize = 101;
|
||||
let constants = Arc::new([F::TWO; R]);
|
||||
type Gate = GMiMCGate::<F, W, R>;
|
||||
type Gate = GMiMCGate::<F, R>;
|
||||
let gate = Gate::with_constants(constants.clone());
|
||||
|
||||
let config = CircuitConfig {
|
||||
num_wires: 200,
|
||||
num_routed_wires: 200,
|
||||
security_bits: 128,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let permutation_inputs = (0..W)
|
||||
@ -175,7 +249,7 @@ mod tests {
|
||||
permutation_inputs[i]);
|
||||
}
|
||||
|
||||
let generators = gate.0.generators(config, 0, vec![], vec![]);
|
||||
let generators = gate.0.generators(0, &[], &[]);
|
||||
generate_partial_witness(&mut witness, &generators);
|
||||
|
||||
let expected_outputs: [F; W] = gmimc_permute_naive(
|
||||
|
||||
81
src/gates/gmimc_eval.rs
Normal file
81
src/gates/gmimc_eval.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use crate::circuit_builder::CircuitBuilder;
|
||||
use crate::constraint_polynomial::{EvaluationTargets, EvaluationVars};
|
||||
use crate::field::field::Field;
|
||||
use crate::gates::gate::{Gate, GateRef};
|
||||
use crate::generator::{SimpleGenerator, WitnessGenerator};
|
||||
use crate::target::Target;
|
||||
use crate::witness::PartialWitness;
|
||||
|
||||
/// Performs some arithmetic involved in the evaluation of GMiMC's constraint polynomials for one
|
||||
/// round.
|
||||
#[derive(Debug)]
|
||||
pub struct GMiMCEvalGate;
|
||||
|
||||
impl GMiMCEvalGate {
|
||||
pub fn get<F: Field>() -> GateRef<F> {
|
||||
GateRef::new(GMiMCEvalGate)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> Gate<F> for GMiMCEvalGate {
|
||||
fn id(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
|
||||
fn eval_unfiltered(&self, vars: EvaluationVars<F>) -> Vec<F> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn eval_unfiltered_recursively(
|
||||
&self,
|
||||
builder: &mut CircuitBuilder<F>,
|
||||
vars: EvaluationTargets,
|
||||
) -> Vec<Target> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn generators(
|
||||
&self,
|
||||
gate_index: usize,
|
||||
local_constants: &[F],
|
||||
_next_constants: &[F],
|
||||
) -> Vec<Box<dyn WitnessGenerator<F>>> {
|
||||
let gen = GMiMCEvalGenerator::<F> {
|
||||
gate_index,
|
||||
constant: local_constants[0],
|
||||
};
|
||||
vec![Box::new(gen)]
|
||||
}
|
||||
|
||||
fn num_wires(&self) -> usize {
|
||||
6
|
||||
}
|
||||
|
||||
fn num_constants(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn degree(&self) -> usize {
|
||||
3
|
||||
}
|
||||
|
||||
fn num_constraints(&self) -> usize {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct GMiMCEvalGenerator<F: Field> {
|
||||
gate_index: usize,
|
||||
constant: F,
|
||||
}
|
||||
|
||||
impl<F: Field> SimpleGenerator<F> for GMiMCEvalGenerator<F> {
|
||||
fn dependencies(&self) -> Vec<Target> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn run_once(&self, witness: &PartialWitness<F>) -> PartialWitness<F> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
pub(crate) mod constant;
|
||||
pub(crate) mod deterministic_gate;
|
||||
pub(crate) mod fri_consistency_gate;
|
||||
pub(crate) mod gate;
|
||||
pub(crate) mod gmimc;
|
||||
pub(crate) mod gmimc_eval;
|
||||
pub(crate) mod noop;
|
||||
pub(crate) mod output_graph;
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
use crate::circuit_data::CircuitConfig;
|
||||
use crate::constraint_polynomial::ConstraintPolynomial;
|
||||
use crate::constraint_polynomial::{EvaluationVars, EvaluationTargets};
|
||||
use crate::field::field::Field;
|
||||
use crate::gates::deterministic_gate::{DeterministicGate, DeterministicGateAdapter};
|
||||
use crate::gates::gate::{Gate, GateRef};
|
||||
use crate::generator::WitnessGenerator;
|
||||
use crate::target::Target;
|
||||
use crate::circuit_builder::CircuitBuilder;
|
||||
|
||||
/// A gate which takes a single constant parameter and outputs that value.
|
||||
pub struct NoopGate;
|
||||
@ -19,17 +20,40 @@ impl<F: Field> Gate<F> for NoopGate {
|
||||
"NoopGate".into()
|
||||
}
|
||||
|
||||
fn constraints(&self, _config: CircuitConfig) -> Vec<ConstraintPolynomial<F>> {
|
||||
fn eval_unfiltered(&self, vars: EvaluationVars<F>) -> Vec<F> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn eval_unfiltered_recursively(
|
||||
&self,
|
||||
_builder: &mut CircuitBuilder<F>,
|
||||
vars: EvaluationTargets,
|
||||
) -> Vec<Target> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn generators(
|
||||
&self,
|
||||
_config: CircuitConfig,
|
||||
_gate_index: usize,
|
||||
_local_constants: Vec<F>,
|
||||
_next_constants: Vec<F>
|
||||
gate_index: usize,
|
||||
local_constants: &[F],
|
||||
next_constants: &[F],
|
||||
) -> Vec<Box<dyn WitnessGenerator<F>>> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn num_wires(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn num_constants(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn degree(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn num_constraints(&self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,113 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use num::ToPrimitive;
|
||||
|
||||
use crate::constraint_polynomial::ConstraintPolynomial;
|
||||
use crate::field::field::Field;
|
||||
|
||||
/// Represents a set of deterministic gate outputs, expressed as polynomials over witness
|
||||
/// values.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OutputGraph<F: Field> {
|
||||
pub(crate) outputs: Vec<(GateOutputLocation, ConstraintPolynomial<F>)>
|
||||
}
|
||||
|
||||
impl<F: Field> OutputGraph<F> {
|
||||
/// Creates a new output graph with no outputs.
|
||||
pub fn new() -> Self {
|
||||
Self { outputs: Vec::new() }
|
||||
}
|
||||
|
||||
/// Creates an output graph with a single output.
|
||||
pub fn single_output(loc: GateOutputLocation, out: ConstraintPolynomial<F>) -> Self {
|
||||
Self { outputs: vec![(loc, out)] }
|
||||
}
|
||||
|
||||
pub fn add(&mut self, location: GateOutputLocation, poly: ConstraintPolynomial<F>) {
|
||||
self.outputs.push((location, poly))
|
||||
}
|
||||
|
||||
/// The largest polynomial degree among all polynomials in this graph.
|
||||
pub fn degree(&self) -> usize {
|
||||
self.outputs.iter()
|
||||
.map(|(_loc, out)| out.degree().to_usize().unwrap())
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// The largest local wire index among this graph's output locations.
|
||||
pub fn max_local_output_index(&self) -> Option<usize> {
|
||||
self.outputs.iter()
|
||||
.filter_map(|(loc, _out)| match loc {
|
||||
GateOutputLocation::LocalWire(i) => Some(*i),
|
||||
GateOutputLocation::NextWire(_) => None
|
||||
})
|
||||
.max()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Field> Display for OutputGraph<F> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
for (loc, out) in &self.outputs {
|
||||
write!(f, "{} := {}, ", loc, out)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an output location of a deterministic gate.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum GateOutputLocation {
|
||||
/// A wire belonging to the gate itself.
|
||||
LocalWire(usize),
|
||||
/// A wire belonging to the following gate.
|
||||
NextWire(usize),
|
||||
}
|
||||
|
||||
impl Display for GateOutputLocation {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
GateOutputLocation::LocalWire(i) => write!(f, "local_wire_{}", i),
|
||||
GateOutputLocation::NextWire(i) => write!(f, "next_wire_{}", i),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `OutputGraph`, but tracks the index of the next available local wire.
|
||||
///
|
||||
/// Many gates have special wires, such as inputs and outputs, which must be placed at specific,
|
||||
/// known indices. Many gates also use some wires for "intermediate values" which are internal to
|
||||
/// the gate, and so can be placed at any available index.
|
||||
///
|
||||
/// This object helps to track which wire indices are available to be used for intermediate values.
|
||||
/// It starts placing intermediate values at a given initial index, and repeatedly increments the
|
||||
/// index as more intermediate values are added.
|
||||
///
|
||||
/// This assumes that there are no "reserved" indices greater than the given initial value. It also
|
||||
/// assumes that there is no better place to store these intermediate values (such as wires of the
|
||||
/// next gate). So this model may not make sense for all gates, which is why this is provided as an
|
||||
/// optional utility.
|
||||
pub struct ExpandableOutputGraph<F: Field> {
|
||||
pub(crate) output_graph: OutputGraph<F>,
|
||||
next_unused_index: usize,
|
||||
}
|
||||
|
||||
impl<F: Field> ExpandableOutputGraph<F> {
|
||||
pub(crate) fn new(next_unused_index: usize) -> Self {
|
||||
ExpandableOutputGraph {
|
||||
output_graph: OutputGraph::new(),
|
||||
next_unused_index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an intermediate value at the next available wire index, and returns a
|
||||
/// `ConstraintPolynomial` pointing to the newly created wire.
|
||||
pub(crate) fn add(&mut self, poly: ConstraintPolynomial<F>) -> ConstraintPolynomial<F> {
|
||||
let index = self.next_unused_index;
|
||||
self.next_unused_index += 1;
|
||||
|
||||
self.output_graph.add(GateOutputLocation::LocalWire(index), poly);
|
||||
ConstraintPolynomial::local_wire(index)
|
||||
}
|
||||
}
|
||||
@ -79,6 +79,7 @@ pub trait WitnessGenerator<F: Field>: 'static + Debug {
|
||||
}
|
||||
|
||||
/// A generator which runs once after a list of dependencies is present in the witness.
|
||||
// TODO: Remove Debug. Here temporarily to debug generator issues.
|
||||
pub trait SimpleGenerator<F: Field>: 'static + Debug {
|
||||
fn dependencies(&self) -> Vec<Target>;
|
||||
|
||||
|
||||
13
src/hash.rs
13
src/hash.rs
@ -8,7 +8,7 @@ use rayon::prelude::*;
|
||||
use crate::field::field::Field;
|
||||
use crate::gmimc::{gmimc_compress, gmimc_permute_array};
|
||||
use crate::proof::Hash;
|
||||
use crate::util::{reverse_index_bits, transpose};
|
||||
use crate::util::{reverse_index_bits, transpose, reverse_index_bits_in_place};
|
||||
|
||||
const RATE: usize = 8;
|
||||
const CAPACITY: usize = 4;
|
||||
@ -80,11 +80,9 @@ pub fn hash_n_to_1<F: Field>(inputs: Vec<F>, pad: bool) -> F {
|
||||
}
|
||||
|
||||
/// Like `merkle_root`, but first reorders each vector so that `new[i] = old[i.reverse_bits()]`.
|
||||
pub(crate) fn merkle_root_bit_rev_order<F: Field>(vecs: Vec<Vec<F>>) -> Hash<F> {
|
||||
let vecs_reordered = vecs.into_par_iter()
|
||||
.map(reverse_index_bits)
|
||||
.collect();
|
||||
merkle_root(vecs_reordered)
|
||||
pub(crate) fn merkle_root_bit_rev_order<F: Field>(mut vecs: Vec<Vec<F>>) -> Hash<F> {
|
||||
reverse_index_bits_in_place(&mut vecs);
|
||||
merkle_root(vecs)
|
||||
}
|
||||
|
||||
/// Given `n` vectors, each of length `l`, constructs a Merkle tree with `l` leaves, where each leaf
|
||||
@ -92,8 +90,7 @@ pub(crate) fn merkle_root_bit_rev_order<F: Field>(vecs: Vec<Vec<F>>) -> Hash<F>
|
||||
/// is skipped, as there is no need to compress leaf data.
|
||||
pub(crate) fn merkle_root<F: Field>(vecs: Vec<Vec<F>>) -> Hash<F> {
|
||||
// TODO: Parallelize.
|
||||
let mut vecs_t = transpose(&vecs);
|
||||
let mut hashes = vecs_t.into_iter()
|
||||
let mut hashes = vecs.into_iter()
|
||||
.map(|leaf_set| hash_or_noop(leaf_set))
|
||||
.collect::<Vec<_>>();
|
||||
while hashes.len() > 1 {
|
||||
|
||||
18
src/main.rs
18
src/main.rs
@ -14,11 +14,11 @@ use crate::util::log2_ceil;
|
||||
use crate::circuit_builder::CircuitBuilder;
|
||||
use crate::circuit_data::CircuitConfig;
|
||||
use crate::witness::PartialWitness;
|
||||
use crate::gates::fri_consistency_gate::FriConsistencyGate;
|
||||
use env_logger::Env;
|
||||
use crate::gates::gmimc::GMiMCGate;
|
||||
use std::sync::Arc;
|
||||
use std::convert::TryInto;
|
||||
use crate::gates::constant::ConstantGate;
|
||||
|
||||
mod circuit_builder;
|
||||
mod circuit_data;
|
||||
@ -29,6 +29,7 @@ mod gadgets;
|
||||
mod gates;
|
||||
mod generator;
|
||||
mod gmimc;
|
||||
mod plonk_common;
|
||||
mod proof;
|
||||
mod prover;
|
||||
mod recursive_verifier;
|
||||
@ -69,7 +70,7 @@ fn bench_prove<F: Field>() {
|
||||
for i in 0..GMIMC_ROUNDS {
|
||||
gmimc_constants[i] = F::from_canonical_u64(GMIMC_CONSTANTS[i]);
|
||||
}
|
||||
let gmimc_gate = GMiMCGate::<F, 12, GMIMC_ROUNDS>::with_constants(
|
||||
let gmimc_gate = GMiMCGate::<F, GMIMC_ROUNDS>::with_constants(
|
||||
Arc::new(gmimc_constants));
|
||||
|
||||
let config = CircuitConfig {
|
||||
@ -77,6 +78,7 @@ fn bench_prove<F: Field>() {
|
||||
num_routed_wires: 12,
|
||||
security_bits: 128,
|
||||
rate_bits: 3,
|
||||
num_checks: 3,
|
||||
};
|
||||
|
||||
let mut builder = CircuitBuilder::<F>::new(config);
|
||||
@ -85,11 +87,13 @@ fn bench_prove<F: Field>() {
|
||||
builder.add_gate_no_constants(gmimc_gate.clone());
|
||||
}
|
||||
|
||||
for _ in 0..(40 * 5) {
|
||||
builder.add_gate(
|
||||
FriConsistencyGate::new(2, 3, 13),
|
||||
vec![F::primitive_root_of_unity(13)]);
|
||||
}
|
||||
builder.add_gate(ConstantGate::get(), vec![F::NEG_ONE]);
|
||||
|
||||
// for _ in 0..(40 * 5) {
|
||||
// builder.add_gate(
|
||||
// FriConsistencyGate::new(2, 3, 13),
|
||||
// vec![F::primitive_root_of_unity(13)]);
|
||||
// }
|
||||
|
||||
let prover = builder.build_prover();
|
||||
let inputs = PartialWitness::new();
|
||||
|
||||
19
src/plonk_common.rs
Normal file
19
src/plonk_common.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use crate::field::field::Field;
|
||||
use crate::target::Target;
|
||||
use crate::circuit_builder::CircuitBuilder;
|
||||
|
||||
pub(crate) fn reduce_with_powers<F: Field>(terms: Vec<F>, alpha: F) -> F {
|
||||
let mut sum = F::ZERO;
|
||||
for &term in terms.iter().rev() {
|
||||
sum = sum * alpha + term;
|
||||
}
|
||||
sum
|
||||
}
|
||||
|
||||
pub(crate) fn reduce_with_powers_recursive<F: Field>(
|
||||
builder: &mut CircuitBuilder<F>,
|
||||
terms: Vec<Target>,
|
||||
alpha: Target,
|
||||
) -> Target {
|
||||
todo!()
|
||||
}
|
||||
103
src/prover.rs
103
src/prover.rs
@ -1,17 +1,20 @@
|
||||
use std::env::var;
|
||||
use std::time::Instant;
|
||||
|
||||
use log::info;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::circuit_data::{CommonCircuitData, ProverOnlyCircuitData};
|
||||
use crate::field::fft::{fft, ifft, lde};
|
||||
use crate::circuit_data::{CommonCircuitData, ProverCircuitData, ProverOnlyCircuitData};
|
||||
use crate::constraint_polynomial::EvaluationVars;
|
||||
use crate::field::fft::{fft, ifft, lde, lde_multiple};
|
||||
use crate::field::field::Field;
|
||||
use crate::generator::generate_partial_witness;
|
||||
use crate::hash::{compress, hash_n_to_hash, hash_n_to_m, hash_or_noop, merkle_root_bit_rev_order};
|
||||
use crate::plonk_common::reduce_with_powers;
|
||||
use crate::proof::{Hash, Proof2};
|
||||
use crate::util::{log2_ceil, reverse_index_bits};
|
||||
use crate::util::{log2_ceil, reverse_index_bits, transpose};
|
||||
use crate::wire::Wire;
|
||||
use crate::witness::PartialWitness;
|
||||
use rayon::prelude::*;
|
||||
|
||||
pub(crate) fn prove<F: Field>(
|
||||
prover_data: &ProverOnlyCircuitData<F>,
|
||||
@ -31,21 +34,33 @@ pub(crate) fn prove<F: Field>(
|
||||
// TODO: Simplify using lde_multiple.
|
||||
// TODO: Parallelize.
|
||||
let wire_ldes = (0..num_wires)
|
||||
.map(|i| compute_wire_lde(i, &witness, common_data.degree, config.rate_bits))
|
||||
.map(|i| compute_wire_lde(i, &witness, common_data.degree(), config.rate_bits))
|
||||
.collect::<Vec<_>>();
|
||||
info!("Computing wire LDEs took {}s", start_wire_ldes.elapsed().as_secs_f32());
|
||||
|
||||
let start_wires_root = Instant::now();
|
||||
let wires_root = merkle_root_bit_rev_order(wire_ldes);
|
||||
let wire_ldes_t = transpose(&wire_ldes);
|
||||
// TODO: Could avoid cloning if it's significant?
|
||||
let wires_root = merkle_root_bit_rev_order(wire_ldes_t.clone());
|
||||
info!("Merklizing wire LDEs took {}s", start_wires_root.elapsed().as_secs_f32());
|
||||
|
||||
let plonk_z_vecs = todo!();
|
||||
let plonk_z_ldes = todo!();
|
||||
let plonk_z_root = merkle_root_bit_rev_order(plonk_z_ldes);
|
||||
let plonk_z_vecs = compute_zs(&common_data);
|
||||
let plonk_z_ldes = lde_multiple(plonk_z_vecs, config.rate_bits);
|
||||
let plonk_z_ldes_t = transpose(&plonk_z_ldes);
|
||||
let plonk_z_root = merkle_root_bit_rev_order(plonk_z_ldes_t.clone());
|
||||
|
||||
let plonk_t_vecs = todo!();
|
||||
let plonk_t_ldes = todo!();
|
||||
let plonk_t_root = merkle_root_bit_rev_order(plonk_t_ldes);
|
||||
let alpha = F::ZERO; // TODO
|
||||
|
||||
let start_vanishing_poly = Instant::now();
|
||||
let vanishing_poly = compute_vanishing_poly(
|
||||
common_data, prover_data, wire_ldes_t, plonk_z_ldes_t, alpha);
|
||||
info!("Computing vanishing poly took {}s", start_vanishing_poly.elapsed().as_secs_f32());
|
||||
|
||||
let plonk_t: Vec<F> = todo!(); // vanishing_poly / Z_H
|
||||
// Need to convert to coeff form and back?
|
||||
let plonk_t_parts = todo!();
|
||||
let plonk_t_ldes = lde_multiple(plonk_t_parts, config.rate_bits);
|
||||
let plonk_t_root = merkle_root_bit_rev_order(transpose(&plonk_t_ldes));
|
||||
|
||||
let openings = todo!();
|
||||
|
||||
@ -57,6 +72,70 @@ pub(crate) fn prove<F: Field>(
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_zs<F: Field>(common_data: &CommonCircuitData<F>) -> Vec<Vec<F>> {
|
||||
(0..common_data.config.num_checks)
|
||||
.map(|i| compute_z(common_data, i))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn compute_z<F: Field>(common_data: &CommonCircuitData<F>, i: usize) -> Vec<F> {
|
||||
vec![F::ZERO; common_data.degree()] // TODO
|
||||
}
|
||||
|
||||
fn compute_vanishing_poly<F: Field>(
|
||||
common_data: &CommonCircuitData<F>,
|
||||
prover_data: &ProverOnlyCircuitData<F>,
|
||||
wire_ldes_t: Vec<Vec<F>>,
|
||||
plonk_z_lde_t: Vec<Vec<F>>,
|
||||
alpha: F,
|
||||
) -> Vec<F> {
|
||||
let lde_size = common_data.lde_size();
|
||||
let lde_gen = common_data.lde_generator();
|
||||
|
||||
let mut result = Vec::with_capacity(lde_size);
|
||||
let mut point = F::ONE;
|
||||
for i in 0..lde_size {
|
||||
debug_assert!(point != F::ONE);
|
||||
|
||||
let i_next = (i + 1) % lde_size;
|
||||
let local_wires = &wire_ldes_t[i];
|
||||
let next_wires = &wire_ldes_t[i_next];
|
||||
let local_constants = &prover_data.constant_ldes_t[i];
|
||||
let next_constants = &prover_data.constant_ldes_t[i_next];
|
||||
let local_plonk_zs = &plonk_z_lde_t[i];
|
||||
let next_plonk_zs = &plonk_z_lde_t[i_next];
|
||||
|
||||
debug_assert_eq!(local_wires.len(), common_data.config.num_wires);
|
||||
debug_assert_eq!(local_plonk_zs.len(), common_data.config.num_checks);
|
||||
|
||||
let vars = EvaluationVars {
|
||||
local_constants,
|
||||
next_constants,
|
||||
local_wires,
|
||||
next_wires,
|
||||
};
|
||||
result.push(compute_vanishing_poly_entry(
|
||||
common_data, vars, local_plonk_zs, next_plonk_zs, alpha));
|
||||
|
||||
point *= lde_gen;
|
||||
}
|
||||
debug_assert_eq!(point, F::ONE);
|
||||
result
|
||||
}
|
||||
|
||||
fn compute_vanishing_poly_entry<F: Field>(
|
||||
common_data: &CommonCircuitData<F>,
|
||||
vars: EvaluationVars<F>,
|
||||
local_plonk_zs: &[F],
|
||||
next_plonk_zs: &[F],
|
||||
alpha: F,
|
||||
) -> F {
|
||||
let mut constraints = Vec::with_capacity(common_data.total_constraints());
|
||||
// TODO: Add Z constraints.
|
||||
constraints.extend(common_data.evaluate(vars));
|
||||
reduce_with_powers(constraints, alpha)
|
||||
}
|
||||
|
||||
fn compute_wire_lde<F: Field>(
|
||||
input: usize,
|
||||
witness: &PartialWitness<F>,
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
use crate::circuit_builder::CircuitBuilder;
|
||||
use crate::field::field::Field;
|
||||
|
||||
const MIN_WIRES: usize = 120; // TODO: Double check.
|
||||
const MIN_ROUTED_WIRES: usize = 12; // TODO: Double check.
|
||||
|
||||
pub fn add_recursive_verifier<F: Field>(builder: &mut CircuitBuilder<F>) {
|
||||
assert!(builder.config.num_wires >= MIN_WIRES);
|
||||
assert!(builder.config.num_wires >= MIN_ROUTED_WIRES);
|
||||
}
|
||||
|
||||
16
src/util.rs
16
src/util.rs
@ -30,17 +30,29 @@ pub(crate) fn transpose<T: Clone>(matrix: &[Vec<T>]) -> Vec<Vec<T>> {
|
||||
}
|
||||
|
||||
/// Permutes `arr` such that each index is mapped to its reverse in binary.
|
||||
pub(crate) fn reverse_index_bits<T: Copy>(arr: Vec<T>) -> Vec<T> {
|
||||
pub(crate) fn reverse_index_bits<T: Clone>(arr: Vec<T>) -> Vec<T> {
|
||||
let n = arr.len();
|
||||
let n_power = log2_strict(n);
|
||||
|
||||
let mut result = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
result.push(arr[reverse_bits(i, n_power)]);
|
||||
result.push(arr[reverse_bits(i, n_power)].clone());
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub(crate) fn reverse_index_bits_in_place<T>(arr: &mut Vec<T>) {
|
||||
let n = arr.len();
|
||||
let n_power = log2_strict(n);
|
||||
|
||||
for src in 0..n {
|
||||
let dst = reverse_bits(src, n_power);
|
||||
if src < dst {
|
||||
arr.swap(src, dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reverse_bits(n: usize, num_bits: usize) -> usize {
|
||||
let mut result = 0;
|
||||
for i in 0..num_bits {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user