diff --git a/src/arithmetic.rs b/src/arithmetic.rs index 39d3920..49f13c8 100644 --- a/src/arithmetic.rs +++ b/src/arithmetic.rs @@ -313,6 +313,29 @@ pub fn compute_inner_product(a: &[F], b: &[F]) -> F { acc } +/// Divides polynomial `a` in `X` by `X - b` with +/// no remainder. +pub fn kate_division<'a, F: Field, I: IntoIterator>(a: I, mut b: F) -> Vec +where + I::IntoIter: DoubleEndedIterator + ExactSizeIterator, +{ + b = -b; + let a = a.into_iter(); + + let mut q = vec![F::zero(); a.len() - 1]; + + let mut tmp = F::zero(); + for (q, r) in q.iter_mut().rev().zip(a.rev()) { + let mut lead_coeff = *r; + lead_coeff.sub_assign(&tmp); + *q = lead_coeff; + tmp = lead_coeff; + tmp.mul_assign(&b); + } + + q +} + /// This simple utility function will parallelize an operation that is to be /// performed over a mutable slice. pub fn parallelize(v: &mut [T], f: F) { diff --git a/src/plonk.rs b/src/plonk.rs index 66f3e32..7de9c61 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -23,45 +23,29 @@ pub use verifier::*; use domain::EvaluationDomain; -// TODO: remove this -const GATE_DEGREE: u32 = 3; - /// This is a structured reference string (SRS) that is (deterministically) /// computed from a specific circuit and parameters for the polynomial /// commitment scheme. #[derive(Debug)] pub struct SRS { - sa: (Vec, Vec), - sb: (Vec, Vec), - sc: (Vec, Vec), - sd: (Vec, Vec), - sm: (Vec, Vec), - sa_commitment: C, - sb_commitment: C, - sc_commitment: C, - sd_commitment: C, - sm_commitment: C, domain: EvaluationDomain, + fixed_commitments: Vec, + fixed_polys: Vec>, + fixed_cosets: Vec>, + meta: MetaCircuit, } /// This is an object which represents a (Turbo)PLONK proof. +// This structure must never allow points at infinity. #[derive(Debug, Clone)] pub struct Proof { - a_commitment: C, - b_commitment: C, - c_commitment: C, - d_commitment: C, + advice_commitments: Vec, h_commitments: Vec, - a_eval_x: C::Scalar, - b_eval_x: C::Scalar, - c_eval_x: C::Scalar, - d_eval_x: C::Scalar, - sa_eval_x: C::Scalar, - sb_eval_x: C::Scalar, - sc_eval_x: C::Scalar, - sd_eval_x: C::Scalar, - sm_eval_x: C::Scalar, - h_evals_x: Vec, + advice_evals: Vec, + fixed_evals: Vec, + h_evals: Vec, + f_commitment: C, + q_evals: Vec, opening: OpeningProof, } @@ -77,6 +61,8 @@ pub enum Error { IncompatibleParams, /// The constraint system is not satisfied. ConstraintSystemFailure, + /// Out of bounds index passed to a backend + BoundsFailure, } fn hash_point>( @@ -98,32 +84,186 @@ fn test_proving() { use crate::arithmetic::{EqAffine, Field, Fp, Fq}; use crate::polycommit::Params; use crate::transcript::DummyHash; + use std::marker::PhantomData; const K: u32 = 5; // Initialize the polynomial commitment parameters let params: Params = Params::new::>(K); + struct PLONKConfig { + a: AdviceWire, + b: AdviceWire, + c: AdviceWire, + + sa: FixedWire, + sb: FixedWire, + sc: FixedWire, + sm: FixedWire, + } + + #[derive(Copy, Clone)] + struct Variable(AdviceWire, usize); + + trait StandardCS { + fn raw_multiply(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error> + where + F: FnOnce() -> Result<(FF, FF, FF), Error>; + fn raw_add(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error> + where + F: FnOnce() -> Result<(FF, FF, FF), Error>; + } + struct MyCircuit { a: Option, } + struct StandardPLONK<'a, F: Field, CS: ConstraintSystem + 'a> { + cs: &'a mut CS, + config: PLONKConfig, + current_gate: usize, + _marker: PhantomData, + } + + impl<'a, FF: Field, CS: ConstraintSystem> StandardPLONK<'a, FF, CS> { + fn new(cs: &'a mut CS, config: PLONKConfig) -> Self { + StandardPLONK { + cs, + config, + current_gate: 0, + _marker: PhantomData, + } + } + } + + impl<'a, FF: Field, CS: ConstraintSystem> StandardCS for StandardPLONK<'a, FF, CS> { + fn raw_multiply(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error> + where + F: FnOnce() -> Result<(FF, FF, FF), Error>, + { + let index = self.current_gate; + self.current_gate += 1; + let mut value = None; + self.cs.assign_advice(self.config.a, index, || { + value = Some(f()?); + Ok(value.ok_or(Error::SynthesisError)?.0) + })?; + self.cs.assign_advice(self.config.b, index, || { + Ok(value.ok_or(Error::SynthesisError)?.1) + })?; + self.cs.assign_advice(self.config.c, index, || { + Ok(value.ok_or(Error::SynthesisError)?.2) + })?; + + self.cs + .assign_fixed(self.config.sa, index, || Ok(FF::zero()))?; + self.cs + .assign_fixed(self.config.sb, index, || Ok(FF::zero()))?; + self.cs + .assign_fixed(self.config.sc, index, || Ok(FF::one()))?; + self.cs + .assign_fixed(self.config.sm, index, || Ok(FF::one()))?; + Ok(( + Variable(self.config.a, index), + Variable(self.config.b, index), + Variable(self.config.c, index), + )) + } + fn raw_add(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error> + where + F: FnOnce() -> Result<(FF, FF, FF), Error>, + { + let index = self.current_gate; + self.current_gate += 1; + let mut value = None; + self.cs.assign_advice(self.config.a, index, || { + value = Some(f()?); + Ok(value.ok_or(Error::SynthesisError)?.0) + })?; + self.cs.assign_advice(self.config.b, index, || { + Ok(value.ok_or(Error::SynthesisError)?.1) + })?; + self.cs.assign_advice(self.config.c, index, || { + Ok(value.ok_or(Error::SynthesisError)?.2) + })?; + + self.cs + .assign_fixed(self.config.sa, index, || Ok(FF::one()))?; + self.cs + .assign_fixed(self.config.sb, index, || Ok(FF::one()))?; + self.cs + .assign_fixed(self.config.sc, index, || Ok(FF::one()))?; + self.cs + .assign_fixed(self.config.sm, index, || Ok(FF::zero()))?; + Ok(( + Variable(self.config.a, index), + Variable(self.config.b, index), + Variable(self.config.c, index), + )) + } + } + impl Circuit for MyCircuit { - fn synthesize(&self, cs: &mut impl ConstraintSystem) -> Result<(), Error> { + type Config = PLONKConfig; + + fn configure(meta: &mut MetaCircuit) -> PLONKConfig { + let a = meta.advice_wire(); + let b = meta.advice_wire(); + let c = meta.advice_wire(); + + let sa = meta.fixed_wire(); + let sb = meta.fixed_wire(); + let sc = meta.fixed_wire(); + let sm = meta.fixed_wire(); + + meta.create_gate(|meta| { + let a = meta.query_advice(a, 0); + let b = meta.query_advice(b, 0); + let c = meta.query_advice(c, 0); + + let sa = meta.query_fixed(sa, 0); + let sb = meta.query_fixed(sb, 0); + let sc = meta.query_fixed(sc, 0); + let sm = meta.query_fixed(sm, 0); + + a.clone() * sa + b.clone() * sb + a * b * sm + (c * sc * (-F::one())) + }); + + PLONKConfig { + a, + b, + c, + sa, + sb, + sc, + sm, + } + } + + fn synthesize( + &self, + cs: &mut impl ConstraintSystem, + config: PLONKConfig, + ) -> Result<(), Error> { + let mut cs = StandardPLONK::new(cs, config); + for _ in 0..10 { - let (_, _, _, _) = cs.multiply(|| { - let a = self.a.ok_or(Error::SynthesisError)?; - let a2 = a.square(); - Ok((a, a, a2)) + let mut a_squared = None; + let (_, _, _) = cs.raw_multiply(|| { + a_squared = self.a.map(|a| a.square()); + Ok(( + self.a.ok_or(Error::SynthesisError)?, + self.a.ok_or(Error::SynthesisError)?, + a_squared.ok_or(Error::SynthesisError)?, + )) })?; - //cs.copy(a, b); - let (_, _, _, _) = cs.add(|| { - let a = self.a.ok_or(Error::SynthesisError)?; - let a2 = a.square(); - let a3 = a + a2; - Ok((a, a2, a3)) + let (_, _, _) = cs.raw_add(|| { + let fin = a_squared.and_then(|a2| self.a.map(|a| a + a2)); + Ok(( + self.a.ok_or(Error::SynthesisError)?, + a_squared.ok_or(Error::SynthesisError)?, + fin.ok_or(Error::SynthesisError)?, + )) })?; - //cs.copy(a, d); - //cs.copy(c, e); } Ok(()) @@ -134,8 +274,10 @@ fn test_proving() { a: Some((-Fp::from_u64(2) + Fp::ROOT_OF_UNITY).pow(&[100, 0, 0, 0])), }; + let empty_circuit: MyCircuit = MyCircuit { a: None }; + // Initialize the SRS - let srs = SRS::generate(¶ms, &circuit).expect("SRS generation should not fail"); + let srs = SRS::generate(¶ms, &empty_circuit).expect("SRS generation should not fail"); // Create a proof let proof = Proof::create::, DummyHash, _>(¶ms, &srs, &circuit) diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index c0168d2..d1923b6 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -1,56 +1,37 @@ -use super::Error; +use core::cmp::max; +use core::ops::{Add, Mul}; +use std::collections::HashMap; +use super::Error; use crate::arithmetic::Field; -/// This represents a PLONK wire, which could be a fixed (selector) wire or an -/// advice wire. -#[derive(Debug)] -pub enum Wire { - /// A wires - A(usize), - /// B wires - B(usize), - /// C wires - C(usize), - /// D wires - D(usize), -} +use super::domain::Rotation; +/// This represents a wire which has a fixed (permanent) value +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct FixedWire(pub usize); + +/// This represents a wire which has a witness-specific value +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct AdviceWire(pub usize); /// This trait allows a [`Circuit`] to direct some backend to assign a witness /// for a constraint system. pub trait ConstraintSystem { - /// Creates a gate. - fn create_gate( + /// Assign an advice wire value (witness) + fn assign_advice( &mut self, - sa: F, - sb: F, - sc: F, - sd: F, - sm: F, - f: impl Fn() -> Result<(F, F, F, F), Error>, - ) -> Result<(Wire, Wire, Wire, Wire), Error>; + wire: AdviceWire, + row: usize, + to: impl FnOnce() -> Result, + ) -> Result<(), Error>; - /// a * b - c = 0 - fn multiply( + /// Assign a fixed value + fn assign_fixed( &mut self, - f: impl Fn() -> Result<(F, F, F), Error>, - ) -> Result<(Wire, Wire, Wire, Wire), Error> { - self.create_gate(F::zero(), F::zero(), F::one(), F::zero(), F::one(), || { - let (a, b, c) = f()?; - Ok((a, b, c, F::zero())) - }) - } - - /// a + b - c = 0 - fn add( - &mut self, - f: impl Fn() -> Result<(F, F, F), Error>, - ) -> Result<(Wire, Wire, Wire, Wire), Error> { - self.create_gate(F::one(), F::one(), F::one(), F::zero(), F::zero(), || { - let (a, b, c) = f()?; - Ok((a, b, c, F::zero())) - }) - } + wire: FixedWire, + row: usize, + to: impl FnOnce() -> Result, + ) -> Result<(), Error>; // fn copy(&mut self, left: Wire, right: Wire); } @@ -59,8 +40,186 @@ pub trait ConstraintSystem { /// backend prover can ask the circuit to synthesize using some given /// [`ConstraintSystem`] implementation. pub trait Circuit { + /// This is a configuration object that stores things like wires. + type Config; + + /// The circuit is given an opportunity to describe the exact gate + /// arrangement, wire arrangement, etc. + fn configure(meta: &mut MetaCircuit) -> Self::Config; + /// Given the provided `cs`, synthesize the circuit. The concrete type of /// the caller will be different depending on the context, and they may or /// may not expect to have a witness present. - fn synthesize(&self, cs: &mut impl ConstraintSystem) -> Result<(), Error>; + fn synthesize( + &self, + cs: &mut impl ConstraintSystem, + config: Self::Config, + ) -> Result<(), Error>; +} + +/// Low-degree polynomial representing an identity that must hold over the committed wires. +#[derive(Clone, Debug)] +pub enum Polynomial { + /// This is a fixed wire queried at a certain relative location + Fixed(usize), + /// This is an advice (witness) wire queried at a certain relative location + Advice(usize), + /// This is the sum of two polynomials + Sum(Box>, Box>), + /// This is the product of two polynomials + Product(Box>, Box>), + /// This is a scaled polynomial + Scaled(Box>, F), +} + +impl Polynomial { + /// Evaluate the polynomial using the provided closures to perform the + /// operations. + pub fn evaluate( + &self, + fixed_wire: &impl Fn(usize) -> T, + advice_wire: &impl Fn(usize) -> T, + sum: &impl Fn(T, T) -> T, + product: &impl Fn(T, T) -> T, + scaled: &impl Fn(T, F) -> T, + ) -> T { + match self { + Polynomial::Fixed(index) => fixed_wire(*index), + Polynomial::Advice(index) => advice_wire(*index), + Polynomial::Sum(a, b) => { + let a = a.evaluate(fixed_wire, advice_wire, sum, product, scaled); + let b = b.evaluate(fixed_wire, advice_wire, sum, product, scaled); + sum(a, b) + } + Polynomial::Product(a, b) => { + let a = a.evaluate(fixed_wire, advice_wire, sum, product, scaled); + let b = b.evaluate(fixed_wire, advice_wire, sum, product, scaled); + product(a, b) + } + Polynomial::Scaled(a, f) => { + let a = a.evaluate(fixed_wire, advice_wire, sum, product, scaled); + scaled(a, *f) + } + } + } + + /// Compute the degree of this polynomial + pub fn degree(&self) -> usize { + match self { + Polynomial::Fixed(_) => 1, + Polynomial::Advice(_) => 1, + Polynomial::Sum(a, b) => max(a.degree(), b.degree()), + Polynomial::Product(a, b) => a.degree() + b.degree(), + Polynomial::Scaled(poly, _) => poly.degree(), + } + } +} + +impl Add for Polynomial { + type Output = Polynomial; + fn add(self, rhs: Polynomial) -> Polynomial { + Polynomial::Sum(Box::new(self), Box::new(rhs)) + } +} + +impl Mul for Polynomial { + type Output = Polynomial; + fn mul(self, rhs: Polynomial) -> Polynomial { + Polynomial::Product(Box::new(self), Box::new(rhs)) + } +} + +impl Mul for Polynomial { + type Output = Polynomial; + fn mul(self, rhs: F) -> Polynomial { + Polynomial::Scaled(Box::new(self), rhs) + } +} + +/// Represents an index into a vector where each entry corresponds to a distinct +/// point that polynomials are queried at. +#[derive(Copy, Clone, Debug)] +pub struct PointIndex(pub usize); + +/// This is a description of the circuit environment, such as the gate, wire and +/// permutation arrangements. +#[derive(Debug, Clone)] +pub struct MetaCircuit { + pub(crate) num_fixed_wires: usize, + pub(crate) num_advice_wires: usize, + // permutations: Vec>, + pub(crate) gates: Vec>, + pub(crate) advice_queries: Vec<(AdviceWire, Rotation)>, + pub(crate) fixed_queries: Vec<(FixedWire, Rotation)>, + + // Mapping from a witness vector rotation to the index in the point vector. + pub(crate) rotations: HashMap, +} + +impl Default for MetaCircuit { + fn default() -> MetaCircuit { + let mut rotations = HashMap::new(); + rotations.insert(Rotation::default(), PointIndex(0)); + + MetaCircuit { + num_fixed_wires: 0, + num_advice_wires: 0, + gates: vec![], + fixed_queries: Vec::new(), + advice_queries: Vec::new(), + rotations, + } + } +} + +impl MetaCircuit { + /// Query a fixed wire at a relative position + pub fn query_fixed(&mut self, wire: FixedWire, at: i32) -> Polynomial { + let at = Rotation(at); + { + let len = self.rotations.len(); + self.rotations.entry(at).or_insert(PointIndex(len)); + } + + // TODO: check for existing query so we don't make redundant queries + let index = self.fixed_queries.len(); + self.fixed_queries.push((wire, at)); + + Polynomial::Fixed(index) + } + + /// Query an advice wire at a relative position + pub fn query_advice(&mut self, wire: AdviceWire, at: i32) -> Polynomial { + let at = Rotation(at); + { + let len = self.rotations.len(); + self.rotations.entry(at).or_insert(PointIndex(len)); + } + + // TODO: check for existing query so we don't make redundant queries + let index = self.advice_queries.len(); + self.advice_queries.push((wire, at)); + + Polynomial::Advice(index) + } + + /// Create a new gate + pub fn create_gate(&mut self, f: impl FnOnce(&mut Self) -> Polynomial) { + let poly = f(self); + self.gates.push(poly); + } + + /// Allocate a new fixed wire + pub fn fixed_wire(&mut self) -> FixedWire { + let tmp = FixedWire(self.num_fixed_wires); + self.num_fixed_wires += 1; + tmp + } + + /// Allocate a new advice wire + pub fn advice_wire(&mut self) -> AdviceWire { + let tmp = AdviceWire(self.num_advice_wires); + self.num_advice_wires += 1; + tmp + } } diff --git a/src/plonk/domain.rs b/src/plonk/domain.rs index 1aa70fa..6ebfb5c 100644 --- a/src/plonk/domain.rs +++ b/src/plonk/domain.rs @@ -1,5 +1,16 @@ use crate::arithmetic::{best_fft, parallelize, Field, Group}; +/// Describes a relative location in the evaluation domain; applying a rotation +/// by i will rotate the vector in the evaluation domain by i. +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct Rotation(pub i32); + +impl Default for Rotation { + fn default() -> Rotation { + Rotation(0) + } +} + /// This structure contains precomputed constants and other details needed for /// performing operations on an evaluation domain of size $2^k$ in the context /// of PLONK. @@ -8,6 +19,7 @@ pub struct EvaluationDomain { n: u64, k: u32, extended_k: u32, + omega: G::Scalar, omega_inv: G::Scalar, extended_omega: G::Scalar, extended_omega_inv: G::Scalar, @@ -91,6 +103,7 @@ impl EvaluationDomain { n, k, extended_k, + omega, omega_inv, extended_omega, extended_omega_inv, @@ -103,32 +116,46 @@ impl EvaluationDomain { } } - /// This takes us from an n-length vector into the coset evaluation domain. - /// Also returns the polynomial. + /// This takes us from an n-length vector into the coefficient form. /// /// This function will panic if the provided vector is not the correct /// length. - pub fn obtain_coset(&self, mut a: Vec) -> (Vec, Vec) { + pub fn obtain_poly(&self, mut a: Vec) -> Vec { assert_eq!(a.len(), 1 << self.k); // Perform inverse FFT to obtain the polynomial in coefficient form Self::ifft(&mut a, self.omega_inv, self.k, self.ifft_divisor); - // Keep this polynomial around; we'll need to evaluate it at arbitrary - // points later. - let old = a.clone(); + a + } - // Distributes powers so that an FFT will move us into the coset - // evaluation domain. - Self::distribute_powers(&mut a, self.g_coset); + /// This takes us from an n-length coefficient vector into the coset + /// evaluation domain, rotating by `rotation` if desired. + /// + /// This function will panic if the provided vector is not the correct + /// length. + pub fn obtain_coset(&self, mut a: Vec, rotation: Rotation) -> Vec { + assert_eq!(a.len(), 1 << self.k); - // Resize to account for the quotient polynomial's size - a.resize(1 << self.extended_k, G::group_zero()); - - // Move into coset evaluation domain + assert!(rotation.0 != i32::MIN); + if rotation.0 == 0 { + // In this special case, the powers of zeta repeat so we do not need + // to compute them. + Self::distribute_powers_zeta(&mut a, self.g_coset); + } else { + let mut g = G::Scalar::ZETA; + if rotation.0 > 0 { + g *= &self.omega.pow_vartime(&[rotation.0 as u64, 0, 0, 0]); + } else { + g *= &self + .omega_inv + .pow_vartime(&[rotation.0.abs() as u64, 0, 0, 0]); + } + Self::distribute_powers(&mut a, g); + } + a.resize(self.coset_len(), G::group_zero()); best_fft(&mut a, self.extended_omega, self.extended_k); - - (a, old) + a } /// This takes us from the coset evaluation domain and gets us the quotient @@ -137,7 +164,7 @@ impl EvaluationDomain { /// This function will panic if the provided vector is not the correct /// length. pub fn from_coset(&self, mut a: Vec) -> Vec { - assert_eq!(a.len(), 1 << self.extended_k); + assert_eq!(a.len(), self.coset_len()); // Inverse FFT Self::ifft( @@ -162,7 +189,7 @@ impl EvaluationDomain { /// This divides the polynomial (in the coset domain) by the vanishing /// polynomial. pub fn divide_by_vanishing_poly(&self, mut h_poly: Vec) -> Vec { - assert_eq!(h_poly.len(), 1 << self.extended_k); + assert_eq!(h_poly.len(), self.coset_len()); // Divide to obtain the quotient polynomial in the coset evaluation // domain. @@ -176,7 +203,7 @@ impl EvaluationDomain { h_poly } - fn distribute_powers(mut a: &mut [G], g: G::Scalar) { + fn distribute_powers_zeta(mut a: &mut [G], g: G::Scalar) { let coset_powers = [g, g.square()]; parallelize(&mut a, |a, mut index| { for a in a { @@ -190,6 +217,16 @@ impl EvaluationDomain { }); } + fn distribute_powers(mut a: &mut [G], g: G::Scalar) { + parallelize(&mut a, |a, index| { + let mut cur = g.pow_vartime(&[index as u64, 0, 0, 0]); + for a in a { + a.group_scale(&cur); + cur *= &g; + } + }); + } + fn ifft(a: &mut [G], omega_inv: G::Scalar, log_n: u32, divisor: G::Scalar) { best_fft(a, omega_inv, log_n); parallelize(a, |a, _| { @@ -199,4 +236,28 @@ impl EvaluationDomain { } }); } + + pub fn coset_len(&self) -> usize { + 1 << self.extended_k + } + + pub fn get_omega(&self) -> G::Scalar { + self.omega + } + + pub fn get_omega_inv(&self) -> G::Scalar { + self.omega_inv + } + + pub fn rotate_omega(&self, constant: G::Scalar, rotation: Rotation) -> G::Scalar { + let mut point = constant; + if rotation.0 >= 0 { + point *= &self.get_omega().pow(&[rotation.0 as u64, 0, 0, 0]); + } else { + point *= &self + .get_omega_inv() + .pow(&[rotation.0.abs() as u64, 0, 0, 0]); + } + point + } } diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index d06119d..70f006c 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -1,9 +1,11 @@ use super::{ - circuit::{Circuit, ConstraintSystem, Wire}, + circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit}, + domain::Rotation, hash_point, Error, Proof, SRS, }; use crate::arithmetic::{ - eval_polynomial, get_challenge_scalar, Challenge, Curve, CurveAffine, Field, + eval_polynomial, get_challenge_scalar, kate_division, parallelize, Challenge, Curve, + CurveAffine, Field, }; use crate::polycommit::Params; use crate::transcript::Hasher; @@ -22,121 +24,130 @@ impl Proof { circuit: &ConcreteCircuit, ) -> Result { struct WitnessCollection { - a: Vec, - b: Vec, - c: Vec, - d: Vec, - sa: Vec, - sb: Vec, - sc: Vec, - sd: Vec, - sm: Vec, + advice: Vec>, } impl ConstraintSystem for WitnessCollection { - fn create_gate( + fn assign_advice( &mut self, - sa: F, - sb: F, - sc: F, - sd: F, - sm: F, - f: impl Fn() -> Result<(F, F, F, F), Error>, - ) -> Result<(Wire, Wire, Wire, Wire), Error> { - let (a, b, c, d) = f()?; - let tmp = Ok(( - Wire::A(self.a.len()), - Wire::B(self.a.len()), - Wire::C(self.a.len()), - Wire::D(self.a.len()), - )); - self.a.push(a); - self.b.push(b); - self.c.push(c); - self.d.push(d); - self.sa.push(sa); - self.sb.push(sb); - self.sc.push(sc); - self.sd.push(sd); - self.sm.push(sm); - tmp + wire: AdviceWire, + row: usize, + to: impl FnOnce() -> Result, + ) -> Result<(), Error> { + *self + .advice + .get_mut(wire.0) + .and_then(|v| v.get_mut(row)) + .ok_or(Error::BoundsFailure)? = to()?; + + Ok(()) + } + + fn assign_fixed( + &mut self, + _: FixedWire, + _: usize, + _: impl FnOnce() -> Result, + ) -> Result<(), Error> { + // We only care about advice wires here + + Ok(()) } - // fn copy(&mut self, left: Wire, right: Wire) { - // unimplemented!() - // } } + let mut meta = MetaCircuit::default(); + let config = ConcreteCircuit::configure(&mut meta); + let mut witness = WitnessCollection { - a: vec![], - b: vec![], - c: vec![], - d: vec![], - sa: vec![], - sb: vec![], - sc: vec![], - sd: vec![], - sm: vec![], + advice: vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_advice_wires], }; // Synthesize the circuit to obtain the witness and other information. - circuit.synthesize(&mut witness)?; + circuit.synthesize(&mut witness, config)?; // Create a transcript for obtaining Fiat-Shamir challenges. let mut transcript = HBase::init(C::Base::one()); - if witness.a.len() > params.n as usize { - // The polynomial commitment does not support a high enough degree - // polynomial to commit to our wires because this circuit has too - // many gates. - return Err(Error::IncompatibleParams); + // Compute commitments to advice wire polynomials + let advice_blinds: Vec<_> = witness.advice.iter().map(|_| C::Scalar::random()).collect(); + let advice_commitments = witness + .advice + .iter() + .zip(advice_blinds.iter()) + .map(|(poly, blind)| params.commit_lagrange(poly, *blind).to_affine()) + .collect(); + + for commitment in &advice_commitments { + hash_point(&mut transcript, commitment)?; } - witness.a.resize(params.n as usize, C::Scalar::zero()); - witness.b.resize(params.n as usize, C::Scalar::zero()); - witness.c.resize(params.n as usize, C::Scalar::zero()); - witness.d.resize(params.n as usize, C::Scalar::zero()); - witness.sa.resize(params.n as usize, C::Scalar::zero()); - witness.sb.resize(params.n as usize, C::Scalar::zero()); - witness.sc.resize(params.n as usize, C::Scalar::zero()); - witness.sd.resize(params.n as usize, C::Scalar::zero()); - witness.sm.resize(params.n as usize, C::Scalar::zero()); - - // Compute commitments to the various wire values - let a_blind = C::Scalar::one(); // TODO: not random - let b_blind = C::Scalar::one(); // TODO: not random - let c_blind = C::Scalar::one(); // TODO: not random - let d_blind = C::Scalar::one(); // TODO: not random - let a_commitment = params.commit_lagrange(&witness.a, a_blind).to_affine(); - let b_commitment = params.commit_lagrange(&witness.b, b_blind).to_affine(); - let c_commitment = params.commit_lagrange(&witness.c, c_blind).to_affine(); - let d_commitment = params.commit_lagrange(&witness.d, d_blind).to_affine(); - - hash_point(&mut transcript, &a_commitment)?; - hash_point(&mut transcript, &b_commitment)?; - hash_point(&mut transcript, &c_commitment)?; - hash_point(&mut transcript, &d_commitment)?; - let domain = &srs.domain; - let (a_coset, a_poly) = domain.obtain_coset(witness.a); - let (b_coset, b_poly) = domain.obtain_coset(witness.b); - let (c_coset, c_poly) = domain.obtain_coset(witness.c); - let (d_coset, d_poly) = domain.obtain_coset(witness.d); + let advice_polys: Vec<_> = witness + .advice + .into_iter() + .map(|poly| domain.obtain_poly(poly)) + .collect(); - // (a * sa) + (b * sb) + (a * sm * b) + (d * sd) - (c * sc) - let mut h_poly = Vec::with_capacity(a_coset.len()); - for ((((((((a, b), c), d), sa), sb), sc), sd), sm) in a_coset + let advice_cosets: Vec<_> = meta + .advice_queries .iter() - .zip(b_coset.iter()) - .zip(c_coset.iter()) - .zip(d_coset.iter()) - .zip(srs.sa.0.iter()) - .zip(srs.sb.0.iter()) - .zip(srs.sc.0.iter()) - .zip(srs.sd.0.iter()) - .zip(srs.sm.0.iter()) - { - h_poly.push((*a) * sa + &((*b) * sb) + &((*a) * sm * b) + &((*d) * sd) - &((*c) * sc)); + .map(|&(wire, at)| { + let poly = advice_polys[wire.0].clone(); + domain.obtain_coset(poly, at) + }) + .collect(); + + // Obtain challenge for keeping all separate gates linearly independent + let x_2: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + // Evaluate the circuit using the custom gates provided + let mut h_poly = vec![C::Scalar::zero(); domain.coset_len()]; + for (i, poly) in meta.gates.iter().enumerate() { + if i != 0 { + for h in h_poly.iter_mut() { + *h *= &x_2; + } + } + + let evaluation: Vec = poly.evaluate( + &|index| srs.fixed_cosets[index].clone(), + &|index| advice_cosets[index].clone(), + &|mut a, b| { + parallelize(&mut a, |a, start| { + for (a, b) in a.iter_mut().zip(b[start..].iter()) { + *a += b; + } + }); + a + }, + &|mut a, b| { + parallelize(&mut a, |a, start| { + for (a, b) in a.iter_mut().zip(b[start..].iter()) { + *a *= b; + } + }); + a + }, + &|mut a, scalar| { + parallelize(&mut a, |a, _| { + for a in a { + *a *= &scalar; + } + }); + a + }, + ); + + assert_eq!(h_poly.len(), evaluation.len()); + + if i == 0 { + h_poly = evaluation; + } else { + for (h, e) in h_poly.iter_mut().zip(evaluation.into_iter()) { + *h += &e; + } + } } // Divide by t(X) = X^{params.n} - 1. @@ -151,7 +162,7 @@ impl Proof { .map(|v| v.to_vec()) .collect::>(); drop(h_poly); - let h_blinds = vec![C::Scalar::one(); h_pieces.len()]; // TODO: not random + let h_blinds: Vec<_> = h_pieces.iter().map(|_| C::Scalar::random()).collect(); // Compute commitments to each h(X) piece let h_commitments: Vec<_> = h_pieces @@ -165,39 +176,44 @@ impl Proof { hash_point(&mut transcript, c)?; } - let x: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + let x_3: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - // Evaluate polynomials at x - let a_eval_x = eval_polynomial(&a_poly, x); - let b_eval_x = eval_polynomial(&b_poly, x); - let c_eval_x = eval_polynomial(&c_poly, x); - let d_eval_x = eval_polynomial(&d_poly, x); - let sa_eval_x = eval_polynomial(&srs.sa.1, x); - let sb_eval_x = eval_polynomial(&srs.sb.1, x); - let sc_eval_x = eval_polynomial(&srs.sc.1, x); - let sd_eval_x = eval_polynomial(&srs.sd.1, x); - let sm_eval_x = eval_polynomial(&srs.sm.1, x); - - let h_evals_x: Vec<_> = h_pieces + // Evaluate polynomials at omega^i x_3 + let advice_evals: Vec<_> = meta + .advice_queries .iter() - .map(|poly| eval_polynomial(poly, x)) + .map(|&(wire, at)| eval_polynomial(&advice_polys[wire.0], domain.rotate_omega(x_3, at))) + .collect(); + + let fixed_evals: Vec<_> = meta + .fixed_queries + .iter() + .map(|&(wire, at)| { + eval_polynomial(&srs.fixed_polys[wire.0], domain.rotate_omega(x_3, at)) + }) + .collect(); + + let h_evals: Vec<_> = h_pieces + .iter() + .map(|poly| eval_polynomial(poly, x_3)) .collect(); // We set up a second transcript on the scalar field to hash in openings of // our polynomial commitments. let mut transcript_scalar = HScalar::init(C::Scalar::one()); - transcript_scalar.absorb(a_eval_x); - transcript_scalar.absorb(b_eval_x); - transcript_scalar.absorb(c_eval_x); - transcript_scalar.absorb(d_eval_x); - transcript_scalar.absorb(sa_eval_x); - transcript_scalar.absorb(sb_eval_x); - transcript_scalar.absorb(sc_eval_x); - transcript_scalar.absorb(sd_eval_x); - transcript_scalar.absorb(sm_eval_x); - // Hash each h(x) piece - for eval in h_evals_x.iter() { + // Hash each advice evaluation + for eval in advice_evals.iter() { + transcript_scalar.absorb(*eval); + } + + // Hash each fixed evaluation + for eval in fixed_evals.iter() { + transcript_scalar.absorb(*eval); + } + + // Hash each h(x) piece evaluation + for eval in h_evals.iter() { transcript_scalar.absorb(*eval); } @@ -205,61 +221,146 @@ impl Proof { C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); transcript.absorb(transcript_scalar_point); - let y: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + let x_4: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - let mut q_commitment = h_commitments[0].clone().to_projective(); - let mut q_poly = h_pieces[0].clone(); - let mut q_blind = h_blinds[0]; + // Collapse openings at same points together into single openings using + // x_4 challenge. + let mut q_polys: Vec>> = vec![None; meta.rotations.len()]; + let mut q_blinds = vec![C::Scalar::zero(); meta.rotations.len()]; + let mut q_evals: Vec<_> = vec![C::Scalar::zero(); meta.rotations.len()]; { - let mut accumulate = |poly: &[_], blind: &C::Scalar, commitment: C| { - for (a, q) in poly.iter().zip(q_poly.iter_mut()) { - *q = (*q * &y) + a; - } - q_commitment = (q_commitment * y) + &commitment.to_projective(); - q_blind = (q_blind * &y) + blind; + let mut accumulate = |point_index: usize, new_poly: &Vec<_>, blind, eval| { + q_polys[point_index] + .as_mut() + .map(|poly| { + parallelize(poly, |q, start| { + for (q, a) in q.iter_mut().zip(new_poly[start..].iter()) { + *q *= &x_4; + *q += a; + } + }); + }) + .or_else(|| { + q_polys[point_index] = Some(new_poly.clone()); + Some(()) + }); + q_blinds[point_index] *= &x_4; + q_blinds[point_index] += &blind; + q_evals[point_index] *= &x_4; + q_evals[point_index] += &eval; }; - for ((poly, blind), commitment) in h_pieces - .iter() - .zip(h_blinds.iter()) - .zip(h_commitments.iter()) - .skip(1) - { - accumulate(&poly, blind, *commitment); + for (query_index, &(wire, ref at)) in meta.advice_queries.iter().enumerate() { + let point_index = (*meta.rotations.get(at).unwrap()).0; + + accumulate( + point_index, + &advice_polys[wire.0], + advice_blinds[wire.0], + advice_evals[query_index], + ); } - accumulate(&a_poly, &a_blind, a_commitment); - accumulate(&b_poly, &b_blind, b_commitment); - accumulate(&c_poly, &c_blind, c_commitment); - accumulate(&d_poly, &d_blind, d_commitment); - accumulate(&srs.sa.1, &Field::one(), srs.sa_commitment); - accumulate(&srs.sb.1, &Field::one(), srs.sb_commitment); - accumulate(&srs.sc.1, &Field::one(), srs.sc_commitment); - accumulate(&srs.sd.1, &Field::one(), srs.sd_commitment); - accumulate(&srs.sm.1, &Field::one(), srs.sm_commitment); + for (query_index, &(wire, ref at)) in meta.fixed_queries.iter().enumerate() { + let point_index = (*meta.rotations.get(at).unwrap()).0; + + accumulate( + point_index, + &srs.fixed_polys[wire.0], + C::Scalar::one(), + fixed_evals[query_index], + ); + } + + // We query the h(X) polynomial at x_3 + let current_index = (*meta.rotations.get(&Rotation::default()).unwrap()).0; + for ((h_poly, h_blind), h_eval) in h_pieces + .into_iter() + .zip(h_blinds.iter()) + .zip(h_evals.iter()) + { + accumulate(current_index, &h_poly, *h_blind, *h_eval); + } + } + + let x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + let mut f_poly: Option> = None; + for (&row, &point_index) in meta.rotations.iter() { + let mut poly = q_polys[point_index.0].as_ref().unwrap().clone(); + let point = domain.rotate_omega(x_3, row); + poly[0] -= &q_evals[point_index.0]; + let mut poly = kate_division(&poly, point); + poly.push(C::Scalar::zero()); + + f_poly = f_poly + .map(|mut f_poly| { + parallelize(&mut f_poly, |q, start| { + for (q, a) in q.iter_mut().zip(poly[start..].iter()) { + *q *= &x_5; + *q += a; + } + }); + f_poly + }) + .or_else(|| Some(poly)); + } + let mut f_poly = f_poly.unwrap(); + let mut f_blind = C::Scalar::random(); + + let f_commitment = params.commit(&f_poly, f_blind).to_affine(); + + hash_point(&mut transcript, &f_commitment)?; + + let x_6: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + let mut q_evals = vec![]; + + for (_, &point_index) in meta.rotations.iter() { + q_evals.push(eval_polynomial( + &q_polys[point_index.0].as_ref().unwrap(), + x_6, + )); + } + + for eval in q_evals.iter() { + transcript_scalar.absorb(*eval); + } + + let transcript_scalar_point = + C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); + transcript.absorb(transcript_scalar_point); + + let x_7: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + for (_, &point_index) in meta.rotations.iter() { + f_blind *= &x_7; + f_blind += &q_blinds[point_index.0]; + + parallelize(&mut f_poly, |f, start| { + for (f, a) in f + .iter_mut() + .zip(q_polys[point_index.0].as_ref().unwrap()[start..].iter()) + { + *f *= &x_7; + *f += a; + } + }); } // Let's prove that the q_commitment opens at x to the expected value. let opening = params - .create_proof(&mut transcript, &q_poly, q_blind, x) + .create_proof(&mut transcript, &f_poly, f_blind, x_6) .map_err(|_| Error::ConstraintSystemFailure)?; Ok(Proof { - a_commitment, - b_commitment, - c_commitment, - d_commitment, + advice_commitments, h_commitments, - a_eval_x, - b_eval_x, - c_eval_x, - d_eval_x, - sa_eval_x, - sb_eval_x, - sc_eval_x, - sd_eval_x, - sm_eval_x, - h_evals_x, + advice_evals, + fixed_evals, + h_evals, + f_commitment, + q_evals, opening, }) } diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index 0a9acdb..98bdc13 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -1,7 +1,7 @@ use super::{ - circuit::{Circuit, ConstraintSystem, Wire}, + circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit}, domain::EvaluationDomain, - Error, GATE_DEGREE, SRS, + Error, SRS, }; use crate::arithmetic::{Curve, CurveAffine, Field}; use crate::polycommit::Params; @@ -14,92 +14,80 @@ impl SRS { circuit: &ConcreteCircuit, ) -> Result { struct Assembly { - sa: Vec, - sb: Vec, - sc: Vec, - sd: Vec, - sm: Vec, + fixed: Vec>, } impl ConstraintSystem for Assembly { - fn create_gate( + fn assign_advice( &mut self, - sa: F, - sb: F, - sc: F, - sd: F, - sm: F, - _: impl Fn() -> Result<(F, F, F, F), Error>, - ) -> Result<(Wire, Wire, Wire, Wire), Error> { - let tmp = Ok(( - Wire::A(self.sa.len()), - Wire::B(self.sa.len()), - Wire::C(self.sa.len()), - Wire::D(self.sa.len()), - )); - self.sa.push(sa); - self.sb.push(sb); - self.sc.push(sc); - self.sd.push(sd); - self.sm.push(sm); - tmp + _: AdviceWire, + _: usize, + _: impl FnOnce() -> Result, + ) -> Result<(), Error> { + // We only care about fixed wires here + Ok(()) + } + + fn assign_fixed( + &mut self, + wire: FixedWire, + row: usize, + to: impl FnOnce() -> Result, + ) -> Result<(), Error> { + *self + .fixed + .get_mut(wire.0) + .and_then(|v| v.get_mut(row)) + .ok_or(Error::BoundsFailure)? = to()?; + + Ok(()) } } + let mut meta = MetaCircuit::default(); + let config = ConcreteCircuit::configure(&mut meta); + let mut assembly: Assembly = Assembly { - sa: vec![], - sb: vec![], - sc: vec![], - sd: vec![], - sm: vec![], + fixed: vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_fixed_wires], }; // Synthesize the circuit to obtain SRS - circuit.synthesize(&mut assembly)?; + circuit.synthesize(&mut assembly, config)?; - assembly.sa.resize(params.n as usize, C::Scalar::zero()); - assembly.sb.resize(params.n as usize, C::Scalar::zero()); - assembly.sc.resize(params.n as usize, C::Scalar::zero()); - assembly.sd.resize(params.n as usize, C::Scalar::zero()); - assembly.sm.resize(params.n as usize, C::Scalar::zero()); + let fixed_commitments = assembly + .fixed + .iter() + .map(|poly| params.commit_lagrange(poly, C::Scalar::one()).to_affine()) + .collect(); - // Compute commitments to the fixed wire values - let sa_commitment = params - .commit_lagrange(&assembly.sa, C::Scalar::one()) - .to_affine(); - let sb_commitment = params - .commit_lagrange(&assembly.sb, C::Scalar::one()) - .to_affine(); - let sc_commitment = params - .commit_lagrange(&assembly.sc, C::Scalar::one()) - .to_affine(); - let sd_commitment = params - .commit_lagrange(&assembly.sd, C::Scalar::one()) - .to_affine(); - let sm_commitment = params - .commit_lagrange(&assembly.sm, C::Scalar::one()) - .to_affine(); + let mut degree = 1; + for poly in meta.gates.iter() { + degree = std::cmp::max(degree, poly.degree()); + } - let domain = EvaluationDomain::new(GATE_DEGREE, params.k); + let domain = EvaluationDomain::new(degree as u32, params.k); - let sa = domain.obtain_coset(assembly.sa); - let sb = domain.obtain_coset(assembly.sb); - let sc = domain.obtain_coset(assembly.sc); - let sd = domain.obtain_coset(assembly.sd); - let sm = domain.obtain_coset(assembly.sm); + let fixed_polys: Vec<_> = assembly + .fixed + .into_iter() + .map(|poly| domain.obtain_poly(poly)) + .collect(); + + let fixed_cosets = meta + .fixed_queries + .iter() + .map(|&(wire, at)| { + let poly = fixed_polys[wire.0].clone(); + domain.obtain_coset(poly, at) + }) + .collect(); Ok(SRS { - sa, - sb, - sc, - sd, - sm, - sa_commitment, - sb_commitment, - sc_commitment, - sd_commitment, - sm_commitment, domain, + fixed_commitments, + fixed_polys, + fixed_cosets, + meta, }) } } diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 4759fd4..d8560d1 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -1,4 +1,4 @@ -use super::{hash_point, Proof, SRS}; +use super::{domain::Rotation, hash_point, Proof, SRS}; use crate::arithmetic::{get_challenge_scalar, Challenge, Curve, CurveAffine, Field}; use crate::polycommit::Params; use crate::transcript::Hasher; @@ -13,35 +13,34 @@ impl Proof { // Create a transcript for obtaining Fiat-Shamir challenges. let mut transcript = HBase::init(C::Base::one()); - hash_point(&mut transcript, &self.a_commitment) - .expect("proof cannot contain points at infinity"); - hash_point(&mut transcript, &self.b_commitment) - .expect("proof cannot contain points at infinity"); - hash_point(&mut transcript, &self.c_commitment) - .expect("proof cannot contain points at infinity"); - hash_point(&mut transcript, &self.d_commitment) - .expect("proof cannot contain points at infinity"); + // Hash the prover's advice commitments into the transcript + for commitment in &self.advice_commitments { + hash_point(&mut transcript, commitment) + .expect("proof cannot contain points at infinity"); + } + // Sample x_2 challenge, which keeps the gates linearly independent. + let x_2: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + // Obtain a commitment to h(X) in the form of multiple pieces of degree n - 1 for c in &self.h_commitments { hash_point(&mut transcript, c).expect("proof cannot contain points at infinity"); } - let x: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + // Sample x_3 challenge, which is used to ensure the circuit is + // satisfied with high probability. + let x_3: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - // We set up a second transcript on the scalar field to hash in openings of - // our polynomial commitments. + // Hash together all the openings provided by the prover into a new + // transcript on the scalar field. let mut transcript_scalar = HScalar::init(C::Scalar::one()); - transcript_scalar.absorb(self.a_eval_x); - transcript_scalar.absorb(self.b_eval_x); - transcript_scalar.absorb(self.c_eval_x); - transcript_scalar.absorb(self.d_eval_x); - transcript_scalar.absorb(self.sa_eval_x); - transcript_scalar.absorb(self.sb_eval_x); - transcript_scalar.absorb(self.sc_eval_x); - transcript_scalar.absorb(self.sd_eval_x); - transcript_scalar.absorb(self.sm_eval_x); - for eval in &self.h_evals_x { + for eval in self + .advice_evals + .iter() + .chain(self.fixed_evals.iter()) + .chain(self.h_evals.iter()) + { transcript_scalar.absorb(*eval); } @@ -49,60 +48,136 @@ impl Proof { C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); transcript.absorb(transcript_scalar_point); - let y: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + // Evaluate the circuit using the custom gates provided + let mut h_eval = C::Scalar::zero(); + for poly in srs.meta.gates.iter() { + h_eval *= &x_2; - let mut q_commitment = self.h_commitments[0].clone().to_projective(); - let mut expected_opening = self.h_evals_x[0]; - { - let mut accumulate = |commitment: C, opening: C::Scalar| { - q_commitment = commitment.to_projective() + &(q_commitment * y); - expected_opening = opening + &(expected_opening * &y); - }; + let evaluation: C::Scalar = poly.evaluate( + &|index| self.fixed_evals[index], + &|index| self.advice_evals[index], + &|a, b| a + &b, + &|a, b| a * &b, + &|a, scalar| a * &scalar, + ); - for (commitment, eval) in self.h_commitments.iter().zip(self.h_evals_x.iter()).skip(1) { - accumulate(*commitment, *eval); - } - - accumulate(self.a_commitment, self.a_eval_x); - accumulate(self.b_commitment, self.b_eval_x); - accumulate(self.c_commitment, self.c_eval_x); - accumulate(self.d_commitment, self.d_eval_x); - accumulate(srs.sa_commitment, self.sa_eval_x); - accumulate(srs.sb_commitment, self.sb_eval_x); - accumulate(srs.sc_commitment, self.sc_eval_x); - accumulate(srs.sd_commitment, self.sd_eval_x); - accumulate(srs.sm_commitment, self.sm_eval_x); + h_eval += &evaluation; } - let q_commitment = q_commitment.to_affine(); - - let xn = x.pow(&[params.n as u64, 0, 0, 0]); + let xn = x_3.pow(&[params.n as u64, 0, 0, 0]); // Compute the expected h(x) value - let mut h_eval_x = C::Scalar::zero(); + let mut expected_h_eval = C::Scalar::zero(); let mut cur = C::Scalar::one(); - for eval in &self.h_evals_x { - h_eval_x += &(cur * eval); + for eval in &self.h_evals { + expected_h_eval += &(cur * eval); cur *= &xn; } - // Check that the circuit is satisfied. - // (a * sa) + (b * sb) + (a * sm * b) + (d * sd) - (c * sc) - if self.a_eval_x * &self.sa_eval_x - + &(self.b_eval_x * &self.sb_eval_x) - + &(self.a_eval_x * &self.sm_eval_x * &self.b_eval_x) - + &(self.d_eval_x * &self.sd_eval_x) - - &(self.c_eval_x * &self.sc_eval_x) - != h_eval_x * &(xn - &C::Scalar::one()) - { + if h_eval != (expected_h_eval * &(xn - &C::Scalar::one())) { return false; } + // We are now convinced the circuit is satisfied so long as the + // polynomial commitments open to the correct values. + + // Sample x_4 for compressing openings at the same points together + let x_4: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + // Compress the commitments and expected evaluations at x_3 together + // using the challenge x_4 + let mut q_commitments: Vec> = vec![None; srs.meta.rotations.len()]; + let mut q_evals: Vec<_> = vec![C::Scalar::zero(); srs.meta.rotations.len()]; + { + let mut accumulate = |point_index: usize, new_commitment, eval| { + q_commitments[point_index] = q_commitments[point_index] + .map(|mut commitment| { + commitment *= x_4; + commitment += new_commitment; + commitment + }) + .or_else(|| Some(new_commitment.to_projective())); + q_evals[point_index] *= &x_4; + q_evals[point_index] += &eval; + }; + + for (query_index, &(wire, ref at)) in srs.meta.advice_queries.iter().enumerate() { + let point_index = (*srs.meta.rotations.get(at).unwrap()).0; + accumulate( + point_index, + self.advice_commitments[wire.0], + self.advice_evals[query_index], + ); + } + + for (query_index, &(wire, ref at)) in srs.meta.fixed_queries.iter().enumerate() { + let point_index = (*srs.meta.rotations.get(at).unwrap()).0; + accumulate( + point_index, + srs.fixed_commitments[wire.0], + self.fixed_evals[query_index], + ); + } + + let current_index = (*srs.meta.rotations.get(&Rotation::default()).unwrap()).0; + for (h_commitment, h_eval) in self.h_commitments.iter().zip(self.h_evals.iter()) { + accumulate(current_index, *h_commitment, *h_eval); + } + } + + // Sample a challenge x_5 for keeping the multi-point quotient + // polynomial terms linearly independent. + let x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + // Obtain the commitment to the multi-point quotient polynomial f(X). + hash_point(&mut transcript, &self.f_commitment) + .expect("proof cannot contain points at infinity"); + + // Sample a challenge x_6 for checking that f(X) was committed to + // correctly. + let x_6: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + for eval in self.q_evals.iter() { + transcript_scalar.absorb(*eval); + } + + let transcript_scalar_point = + C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); + transcript.absorb(transcript_scalar_point); + + // We can compute the expected f_eval at x_6 using the q_evals provided + // by the prover and from x_5 + let mut f_eval = C::Scalar::zero(); + for (&row, &point_index) in srs.meta.rotations.iter() { + let mut eval = self.q_evals[point_index.0]; + + let point = srs.domain.rotate_omega(x_3, row); + eval = eval - &q_evals[point_index.0]; + eval = eval * &(x_6 - &point).invert().unwrap(); + + f_eval *= &x_5; + f_eval += &eval; + } + + // Sample a challenge x_7 that we will use to collapse the openings of + // the various remaining polynomials at x_6 together. + let x_7: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + // Compute the final commitment that has to be opened + let mut f_commitment: C::Projective = self.f_commitment.to_projective(); + for (_, &point_index) in srs.meta.rotations.iter() { + f_commitment *= x_7; + f_commitment = f_commitment + &q_commitments[point_index.0].as_ref().unwrap(); + f_eval *= &x_7; + f_eval += &self.q_evals[point_index.0]; + } + + // Verify the opening proof params.verify_proof( &self.opening, &mut transcript, - x, - &q_commitment, - expected_opening, + x_6, + &f_commitment.to_affine(), + f_eval, ) } }