From ad106f111962df7782fc2f310fe588f170a41c71 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 27 Aug 2020 10:10:55 -0600 Subject: [PATCH] (WIP) broken implementation of generalized PLONK --- src/arithmetic.rs | 23 +++ src/plonk.rs | 53 +----- src/plonk/circuit.rs | 117 +++++------- src/plonk/domain.rs | 18 +- src/plonk/prover.rs | 426 +++++++++++++++++++++++++----------------- src/plonk/srs.rs | 103 ++-------- src/plonk/verifier.rs | 182 +++++++++++++++--- 7 files changed, 509 insertions(+), 413 deletions(-) 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 a10e168..d2217da 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -23,28 +23,15 @@ 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<(Vec, Vec)>, + fixed_polys: Vec>, + fixed_cosets: Vec>, meta: MetaCircuit, } @@ -52,22 +39,13 @@ pub struct SRS { // 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, + advice_evals_x: Vec, + fixed_evals_x: Vec, h_evals_x: Vec, + f_commitment: C, + q_evals: Vec, opening: OpeningProof, } @@ -167,30 +145,13 @@ fn test_proving() { cs: &mut impl ConstraintSystem, config: MyConfig, ) -> Result<(), Error> { - for _ in 0..10 { - let (_, _, _, _) = cs.multiply(|| { - let a = self.a.ok_or(Error::SynthesisError)?; - let a2 = a.square(); - Ok((a, a, a2)) - })?; - //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)) - })?; - //cs.copy(a, d); - //cs.copy(c, e); - } - // Similar to the above... let mut row = 0; for _ in 0..10 { cs.assign_advice(config.a, row, || self.a.ok_or(Error::SynthesisError))?; cs.assign_advice(config.b, row, || self.a.ok_or(Error::SynthesisError))?; let a_squared = self.a.map(|a| a.square()); - cs.assign_advice(config.c, row, || self.a.ok_or(Error::SynthesisError))?; + cs.assign_advice(config.c, row, || a_squared.ok_or(Error::SynthesisError))?; // Multiplication gate cs.assign_fixed(config.sa, row, || Ok(Field::zero()))?; cs.assign_fixed(config.sb, row, || Ok(Field::zero()))?; diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index 066b48d..5466260 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -5,20 +5,6 @@ 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(Copy, Clone, Debug)] -pub enum Wire { - /// A wires - A, - /// B wires - B, - /// C wires - C, - /// D wires - D, -} - /// This represents a wire which has a fixed (permanent) value #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct FixedWire(pub usize); @@ -27,10 +13,6 @@ pub struct FixedWire(pub usize); #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct AdviceWire(pub usize); -/// Represents a pointer to a value in the constraint system. -#[derive(Clone, Debug)] -pub struct Variable(pub Wire, pub usize); - /// This trait allows a [`Circuit`] to direct some backend to assign a witness /// for a constraint system. pub trait ConstraintSystem { @@ -50,39 +32,6 @@ pub trait ConstraintSystem { to: impl FnOnce() -> Result, ) -> Result<(), Error>; - /// Creates a gate. - fn create_gate( - &mut self, - sa: F, - sb: F, - sc: F, - sd: F, - sm: F, - f: impl Fn() -> Result<(F, F, F, F), Error>, - ) -> Result<(Variable, Variable, Variable, Variable), Error>; - - /// a * b - c = 0 - fn multiply( - &mut self, - f: impl Fn() -> Result<(F, F, F), Error>, - ) -> Result<(Variable, Variable, Variable, Variable), 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<(Variable, Variable, Variable, Variable), 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())) - }) - } - // fn copy(&mut self, left: Wire, right: Wire); } @@ -111,9 +60,9 @@ pub trait Circuit { #[derive(Clone, Debug)] pub enum Polynomial { /// This is a fixed wire queried at a certain relative location - Fixed(FixedWire, i32), + Fixed(usize), /// This is an advice (witness) wire queried at a certain relative location - Advice(AdviceWire, i32), + Advice(usize), /// This is the sum of two polynomials Sum(Box>, Box>), /// This is the product of two polynomials @@ -123,17 +72,19 @@ pub enum Polynomial { } impl Polynomial { - fn evaluate( + /// Evaluate the polynomial using the provided closures to perform the + /// operations. + pub fn evaluate( &self, - fixed_wire: &impl Fn(FixedWire, i32) -> T, - advice_wire: &impl Fn(AdviceWire, i32) -> T, + 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(a, location) => fixed_wire(*a, *location), - Polynomial::Advice(a, location) => advice_wire(*a, *location), + 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); @@ -150,13 +101,12 @@ impl Polynomial { } } } -} -impl Polynomial { - fn degree(&self) -> usize { + /// Compute the degree of this polynomial + pub fn degree(&self) -> usize { match self { - Polynomial::Fixed(_, _) => 1, - Polynomial::Advice(_, _) => 1, + 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(), @@ -192,20 +142,24 @@ pub struct MetaCircuit { pub(crate) num_fixed_wires: usize, pub(crate) num_advice_wires: usize, // permutations: Vec>, - gates: Vec>, - advice_queries: HashMap<(AdviceWire, i32), usize>, - fixed_queries: HashMap<(FixedWire, i32), usize>, - // num_queries: usize, + pub(crate) gates: Vec>, + pub(crate) advice_queries: Vec<(AdviceWire, i32)>, + pub(crate) fixed_queries: Vec<(FixedWire, i32)>, + pub(crate) query_rows: HashMap, } impl Default for MetaCircuit { fn default() -> MetaCircuit { + let mut query_rows = HashMap::new(); + query_rows.insert(0, 0); + MetaCircuit { num_fixed_wires: 0, num_advice_wires: 0, gates: vec![], - fixed_queries: HashMap::new(), - advice_queries: HashMap::new(), + fixed_queries: Vec::new(), + advice_queries: Vec::new(), + query_rows, } } } @@ -213,18 +167,30 @@ impl Default for MetaCircuit { impl MetaCircuit { /// Query a fixed wire at a relative position pub fn query_fixed(&mut self, wire: FixedWire, at: i32) -> Polynomial { - let len = self.fixed_queries.len(); - self.fixed_queries.entry((wire, at)).or_insert_with(|| len); + { + let len = self.query_rows.len(); + self.query_rows.entry(at).or_insert(len); + } - Polynomial::Fixed(wire, at) + // 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 len = self.advice_queries.len(); - self.advice_queries.entry((wire, at)).or_insert_with(|| len); + { + let len = self.query_rows.len(); + self.query_rows.entry(at).or_insert(len); + } - Polynomial::Advice(wire, at) + // 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 @@ -239,6 +205,7 @@ impl MetaCircuit { 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); diff --git a/src/plonk/domain.rs b/src/plonk/domain.rs index cf1fde7..07ac13a 100644 --- a/src/plonk/domain.rs +++ b/src/plonk/domain.rs @@ -138,7 +138,7 @@ impl EvaluationDomain { } Self::distribute_powers(&mut a, g); } - a.resize(1 << self.extended_k, G::group_zero()); + a.resize(self.coset_len(), G::group_zero()); best_fft(&mut a, self.extended_omega, self.extended_k); a } @@ -149,7 +149,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( @@ -174,7 +174,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. @@ -221,4 +221,16 @@ 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 + } } diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 012887e..2a356d9 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -1,9 +1,10 @@ use super::{ - circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit, Variable, Wire}, + circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit}, 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,15 +23,6 @@ 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>, } @@ -60,52 +52,12 @@ impl Proof { Ok(()) } - - fn create_gate( - &mut self, - sa: F, - sb: F, - sc: F, - sd: F, - sm: F, - f: impl Fn() -> Result<(F, F, F, F), Error>, - ) -> Result<(Variable, Variable, Variable, Variable), Error> { - let (a, b, c, d) = f()?; - let tmp = Ok(( - Variable(Wire::A, self.a.len()), - Variable(Wire::B, self.a.len()), - Variable(Wire::C, self.a.len()), - Variable(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 - } - // 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], }; @@ -115,34 +67,8 @@ impl Proof { // 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); - } - - 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(); - - let advice_blinds = vec![C::Scalar::one(); witness.advice.len()]; // TODO: not random + // Compute commitments to advice wire polynomials + let advice_blinds: Vec<_> = witness.advice.iter().map(|_| C::Scalar::random()).collect(); let advice_commitments = witness .advice .iter() @@ -150,50 +76,77 @@ impl Proof { .map(|(poly, blind)| params.commit_lagrange(poly, *blind).to_affine()) .collect(); - hash_point(&mut transcript, &a_commitment)?; - hash_point(&mut transcript, &b_commitment)?; - hash_point(&mut transcript, &c_commitment)?; - hash_point(&mut transcript, &d_commitment)?; for commitment in &advice_commitments { hash_point(&mut transcript, commitment)?; } let domain = &srs.domain; - let a_poly = domain.obtain_poly(witness.a); - let b_poly = domain.obtain_poly(witness.b); - let c_poly = domain.obtain_poly(witness.c); - let d_poly = domain.obtain_poly(witness.d); - - let a_coset = domain.obtain_coset(a_poly.clone(), 0); - let b_coset = domain.obtain_coset(b_poly.clone(), 0); - let c_coset = domain.obtain_coset(c_poly.clone(), 0); - let d_coset = domain.obtain_coset(d_poly.clone(), 0); - let advice_polys: Vec<_> = witness .advice .into_iter() - .map(|poly| { - let poly = domain.obtain_poly(poly); - let coset = domain.obtain_coset(poly.clone(), 0); - (poly, coset) + .map(|poly| domain.obtain_poly(poly)) + .collect(); + + let advice_cosets: Vec<_> = meta + .advice_queries + .iter() + .map(|&(wire, at)| { + let poly = advice_polys[wire.0].clone(); + domain.obtain_coset(poly, at) }) .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 - .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)); + // 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.into_iter().zip(b[start..].iter()) { + *a += b; + } + }); + a + }, + &|mut a, b| { + parallelize(&mut a, |a, start| { + for (a, b) in a.into_iter().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. @@ -208,7 +161,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 @@ -222,38 +175,59 @@ 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); + // Evaluate polynomials at omega^i x_3 + let advice_evals_x: Vec<_> = meta + .advice_queries + .iter() + .map(|&(wire, at)| { + let mut point = x_3; + if at >= 0 { + point *= &domain.get_omega().pow(&[at as u64, 0, 0, 0]); + } else { + point *= &domain.get_omega_inv().pow(&[at.abs() as u64, 0, 0, 0]); + } + + eval_polynomial(&advice_polys[wire.0], point) + }) + .collect(); + + let fixed_evals_x: Vec<_> = meta + .fixed_queries + .iter() + .map(|&(wire, at)| { + let mut point = x_3; + if at >= 0 { + point *= &domain.get_omega().pow(&[at as u64, 0, 0, 0]); + } else { + point *= &domain.get_omega_inv().pow(&[at.abs() as u64, 0, 0, 0]); + } + + eval_polynomial(&srs.fixed_polys[wire.0], point) + }) + .collect(); let h_evals_x: Vec<_> = h_pieces .iter() - .map(|poly| eval_polynomial(poly, x)) + .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 + // Hash each advice evaluation + for eval in advice_evals_x.iter() { + transcript_scalar.absorb(*eval); + } + + // Hash each fixed evaluation + for eval in fixed_evals_x.iter() { + transcript_scalar.absorb(*eval); + } + + // Hash each h(x) piece evaluation for eval in h_evals_x.iter() { transcript_scalar.absorb(*eval); } @@ -262,62 +236,164 @@ 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.query_rows.len()]; + let mut q_blinds = vec![C::Scalar::zero(); meta.query_rows.len()]; + let mut q_evals: Vec<_> = vec![C::Scalar::zero(); meta.query_rows.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; - }; + for (i, &(wire, ref at)) in meta.advice_queries.iter().enumerate() { + let query_row = *meta.query_rows.get(at).unwrap(); - for ((poly, blind), commitment) in h_pieces - .iter() - .zip(h_blinds.iter()) - .zip(h_commitments.iter()) - .skip(1) - { - accumulate(&poly, blind, *commitment); + if q_polys[query_row].is_none() { + q_polys[query_row] = Some(advice_polys[wire.0].clone()); + q_blinds[query_row] = advice_blinds[wire.0]; + q_evals[query_row] = advice_evals_x[i]; + } else { + parallelize(q_polys[query_row].as_mut().unwrap(), |q, start| { + for (q, a) in q.iter_mut().zip(advice_polys[wire.0][start..].iter()) { + *q *= &x_4; + *q += a; + } + }); + q_blinds[query_row] *= &x_4; + q_blinds[query_row] += &advice_blinds[wire.0]; + q_evals[query_row] *= &x_4; + q_evals[query_row] += &advice_evals_x[i]; + } } - 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 (i, &(wire, ref at)) in meta.fixed_queries.iter().enumerate() { + let query_row = *meta.query_rows.get(at).unwrap(); + + if q_polys[query_row].is_none() { + q_polys[query_row] = Some(srs.fixed_polys[wire.0].clone()); + q_blinds[query_row] = C::Scalar::one(); + q_evals[query_row] = fixed_evals_x[i]; + } else { + parallelize(q_polys[query_row].as_mut().unwrap(), |q, start| { + for (q, a) in q.iter_mut().zip(srs.fixed_polys[wire.0][start..].iter()) { + *q *= &x_4; + *q += a; + } + }); + q_blinds[query_row] *= &x_4; + q_blinds[query_row] += &C::Scalar::one(); + q_evals[query_row] *= &x_4; + q_evals[query_row] += &fixed_evals_x[i]; + } + } + + for ((h_poly, h_blind), h_eval) in h_pieces + .into_iter() + .zip(h_blinds.iter()) + .zip(h_evals_x.iter()) + { + // We query the h(X) polynomial at x_3 + let cur_row = *meta.query_rows.get(&0).unwrap(); + + if q_polys[cur_row].is_none() { + q_polys[cur_row] = Some(h_poly); + q_blinds[cur_row] = *h_blind; + q_evals[cur_row] = *h_eval; + } else { + parallelize(q_polys[cur_row].as_mut().unwrap(), |q, start| { + for (q, a) in q.iter_mut().zip(h_poly[start..].iter()) { + *q *= &x_4; + *q += a; + } + }); + q_blinds[cur_row] *= &x_4; + q_blinds[cur_row] += h_blind; + q_evals[cur_row] *= &x_4; + q_evals[cur_row] += h_eval; + } + } + } + + let x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + let mut f_poly = None; + for (&row, &col) in meta.query_rows.iter() { + let mut poly = q_polys[col].as_ref().unwrap().clone(); + let mut point = x_3; + if row >= 0 { + point *= &domain.get_omega().pow_vartime(&[row as u64, 0, 0, 0]); + } else { + point *= &domain + .get_omega_inv() + .pow_vartime(&[row.abs() as u64, 0, 0, 0]); + } + poly[0] -= &q_evals[col]; + let mut poly = kate_division(&poly, point); + poly.push(C::Scalar::zero()); + + if f_poly.is_none() { + f_poly = Some(poly); + } else { + parallelize(f_poly.as_mut().unwrap(), |q, start| { + for (q, a) in q.iter_mut().zip(poly[start..].iter()) { + *q *= &x_5; + *q += a; + } + }); + } + } + 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 (_, &col) in meta.query_rows.iter() { + q_evals.push(eval_polynomial(&q_polys[col].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 (_, &col) in meta.query_rows.iter() { + f_blind *= &x_7; + f_blind += &q_blinds[col]; + + parallelize(&mut f_poly, |f, start| { + for (f, a) in f + .iter_mut() + .zip(q_polys[col].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, + advice_evals_x, + fixed_evals_x, h_evals_x, + f_commitment, + q_evals, opening, }) } diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index 8ce141b..98bdc13 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -1,7 +1,7 @@ use super::{ - circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit, Variable, 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,11 +14,6 @@ impl SRS { circuit: &ConcreteCircuit, ) -> Result { struct Assembly { - sa: Vec, - sb: Vec, - sc: Vec, - sd: Vec, - sm: Vec, fixed: Vec>, } @@ -47,113 +42,51 @@ impl SRS { Ok(()) } - - fn create_gate( - &mut self, - sa: F, - sb: F, - sc: F, - sd: F, - sm: F, - _: impl Fn() -> Result<(F, F, F, F), Error>, - ) -> Result<(Variable, Variable, Variable, Variable), Error> { - let tmp = Ok(( - Variable(Wire::A, self.sa.len()), - Variable(Wire::B, self.sa.len()), - Variable(Wire::C, self.sa.len()), - Variable(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 - } } 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, 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()); - - // 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 fixed_commitments = assembly .fixed .iter() .map(|poly| params.commit_lagrange(poly, C::Scalar::one()).to_affine()) .collect(); - let domain = EvaluationDomain::new(GATE_DEGREE, params.k); + let mut degree = 1; + for poly in meta.gates.iter() { + degree = std::cmp::max(degree, poly.degree()); + } - let sa_poly = domain.obtain_poly(assembly.sa); - let sb_poly = domain.obtain_poly(assembly.sb); - let sc_poly = domain.obtain_poly(assembly.sc); - let sd_poly = domain.obtain_poly(assembly.sd); - let sm_poly = domain.obtain_poly(assembly.sm); - let sa_coset = domain.obtain_coset(sa_poly.clone(), 0); - let sb_coset = domain.obtain_coset(sb_poly.clone(), 0); - let sc_coset = domain.obtain_coset(sc_poly.clone(), 0); - let sd_coset = domain.obtain_coset(sd_poly.clone(), 0); - let sm_coset = domain.obtain_coset(sm_poly.clone(), 0); + let domain = EvaluationDomain::new(degree as u32, params.k); - let fixed_polys = assembly + let fixed_polys: Vec<_> = assembly .fixed .into_iter() - .map(|poly| { - let coeffs = domain.obtain_poly(poly); - let coset = domain.obtain_coset(coeffs.clone(), 0); - (coeffs, coset) + .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: (sa_coset, sa_poly), - sb: (sb_coset, sb_poly), - sc: (sc_coset, sc_poly), - sd: (sd_coset, sd_poly), - sm: (sm_coset, sm_poly), - 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 e155cb1..10777e9 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -13,48 +13,178 @@ 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"); - for commitment in &self.advice_commitments { hash_point(&mut transcript, commitment) .expect("proof cannot contain points at infinity"); } + let x_2: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + 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())); + 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. 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.advice_evals_x.iter() { + transcript_scalar.absorb(*eval); + } + + for eval in self.fixed_evals_x.iter() { + transcript_scalar.absorb(*eval); + } for eval in &self.h_evals_x { transcript_scalar.absorb(*eval); } + // 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 evaluation: C::Scalar = poly.evaluate( + &|index| self.fixed_evals_x[index], + &|index| self.advice_evals_x[index], + &|a, b| a + &b, + &|a, b| a * &b, + &|a, scalar| a * &scalar, + ); + + h_eval += &evaluation; + } + let xn = x_3.pow(&[params.n as u64, 0, 0, 0]); + h_eval *= &(xn - &C::Scalar::one()); + + // Compute the expected h(x) value + let mut expected_h_eval = C::Scalar::zero(); + let mut cur = C::Scalar::one(); + for eval in &self.h_evals_x { + expected_h_eval += &(cur * eval); + cur *= &xn; + } + + if h_eval != expected_h_eval { + return false; + } + + let transcript_scalar_point = + C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); + transcript.absorb(transcript_scalar_point); + + let x_4: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + let mut q_commitments: Vec<_> = vec![None; srs.meta.query_rows.len()]; + let mut q_evals: Vec<_> = vec![C::Scalar::zero(); srs.meta.query_rows.len()]; + + { + for (i, &(wire, ref at)) in srs.meta.advice_queries.iter().enumerate() { + let query_row = *srs.meta.query_rows.get(at).unwrap(); + + if q_commitments[query_row].is_none() { + q_commitments[query_row] = + Some(self.advice_commitments[wire.0].to_projective()); + q_evals[query_row] = self.advice_evals_x[i]; + } else { + q_commitments[query_row].as_mut().map(|commitment| { + *commitment *= x_4; + *commitment += self.advice_commitments[wire.0]; + }); + q_evals[query_row] *= &x_4; + q_evals[query_row] += &self.advice_evals_x[i]; + } + } + + for (i, &(wire, ref at)) in srs.meta.fixed_queries.iter().enumerate() { + let query_row = *srs.meta.query_rows.get(at).unwrap(); + + if q_commitments[query_row].is_none() { + q_commitments[query_row] = Some(srs.fixed_commitments[wire.0].to_projective()); + q_evals[query_row] = self.fixed_evals_x[i]; + } else { + q_commitments[query_row].as_mut().map(|commitment| { + *commitment *= x_4; + *commitment += srs.fixed_commitments[wire.0]; + }); + q_evals[query_row] *= &x_4; + q_evals[query_row] += &self.fixed_evals_x[i]; + } + } + + for (h_commitment, h_eval) in self.h_commitments.iter().zip(self.h_evals_x.iter()) { + // We query the h(X) polynomial at x_3 + let cur_row = *srs.meta.query_rows.get(&0).unwrap(); + + if q_commitments[cur_row].is_none() { + q_commitments[cur_row] = Some(h_commitment.to_projective()); + q_evals[cur_row] = *h_eval; + } else { + q_commitments[cur_row].as_mut().map(|commitment| { + *commitment *= x_4; + *commitment += *h_commitment; + }); + q_evals[cur_row] *= &x_4; + q_evals[cur_row] += h_eval; + } + } + } + + let x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + hash_point(&mut transcript, &self.f_commitment) + .expect("proof cannot contain points at infinity"); + + let x_6: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + // We can compute the expected f_eval from x_5 + let mut f_eval = C::Scalar::zero(); + for (&row, &col) in srs.meta.query_rows.iter() { + let mut eval: C::Scalar = self.q_evals[col].clone(); + let mut point = x_3; + if row >= 0 { + point *= &srs.domain.get_omega().pow_vartime(&[row as u64, 0, 0, 0]); + } else { + point *= &srs + .domain + .get_omega_inv() + .pow_vartime(&[row.abs() as u64, 0, 0, 0]); + } + eval = eval - &q_evals[col]; + eval = eval * &(x_6 - &point).invert().unwrap(); + + f_eval *= &x_5; + f_eval += &eval; + } + + 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); - let y: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + let x_7: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + let mut f_commitment: C::Projective = self.f_commitment.to_projective(); + for (_, &col) in srs.meta.query_rows.iter() { + f_commitment *= x_7; + f_commitment = f_commitment + &q_commitments[col].as_ref().unwrap(); + f_eval *= &x_7; + f_eval += &self.q_evals[col]; + } + + params.verify_proof( + &self.opening, + &mut transcript, + x_6, + &f_commitment.to_affine(), + f_eval, + ) + + /* let mut q_commitment = self.h_commitments[0].clone().to_projective(); let mut expected_opening = self.h_evals_x[0]; @@ -102,12 +232,6 @@ impl Proof { return false; } - params.verify_proof( - &self.opening, - &mut transcript, - x, - &q_commitment, - expected_opening, - ) + */ } }