From 116659c1ba88d03178952945f1252d8b3f54cd70 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 7 Sep 2020 10:22:25 -0600 Subject: [PATCH 1/3] Refactor module tree. --- src/lib.rs | 4 +- src/plonk.rs | 23 +- src/plonk/circuit.rs | 72 ++-- src/plonk/prover.rs | 177 ++++------ src/plonk/srs.rs | 44 +-- src/plonk/verifier.rs | 8 +- src/poly.rs | 178 ++++++++++ src/poly/commitment.rs | 280 +++++++++++++++ src/poly/commitment/prover.rs | 234 +++++++++++++ src/poly/commitment/verifier.rs | 179 ++++++++++ src/{plonk => poly}/domain.rs | 166 ++++++--- src/polycommit.rs | 600 -------------------------------- 12 files changed, 1138 insertions(+), 827 deletions(-) create mode 100644 src/poly.rs create mode 100644 src/poly/commitment.rs create mode 100644 src/poly/commitment/prover.rs create mode 100644 src/poly/commitment/verifier.rs rename src/{plonk => poly}/domain.rs (62%) delete mode 100644 src/polycommit.rs diff --git a/src/lib.rs b/src/lib.rs index 78f23640..6533fad5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,6 @@ #![deny(unsafe_code)] pub mod arithmetic; -pub mod plonk; -pub mod polycommit; +pub mod poly; pub mod transcript; +pub mod plonk; diff --git a/src/plonk.rs b/src/plonk.rs index 6bdf8f49..b0dc869d 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -6,12 +6,13 @@ //! [plonk]: https://eprint.iacr.org/2019/953 use crate::arithmetic::CurveAffine; -use crate::polycommit::OpeningProof; +use crate::poly::{ + commitment::OpeningProof, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, + Polynomial, +}; use crate::transcript::Hasher; -#[macro_use] mod circuit; -mod domain; mod prover; mod srs; mod verifier; @@ -21,22 +22,20 @@ pub use prover::*; pub use srs::*; pub use verifier::*; -use domain::EvaluationDomain; - /// 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 { domain: EvaluationDomain, - l0: Vec, + l0: Polynomial, fixed_commitments: Vec, - fixed_polys: Vec>, - fixed_cosets: Vec>, + fixed_polys: Vec>, + fixed_cosets: Vec>, permutation_commitments: Vec>, - permutations: Vec>>, - permutation_polys: Vec>>, - permutation_cosets: Vec>>, + permutations: Vec>>, + permutation_polys: Vec>>, + permutation_cosets: Vec>>, meta: MetaCircuit, } @@ -91,7 +90,7 @@ fn hash_point>( #[test] fn test_proving() { use crate::arithmetic::{EqAffine, Field, Fp, Fq}; - use crate::polycommit::Params; + use crate::poly::commitment::Params; use crate::transcript::DummyHash; use std::marker::PhantomData; const K: u32 = 5; diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index 89a5f570..5537e796 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -5,7 +5,7 @@ use std::collections::BTreeMap; use super::Error; use crate::arithmetic::Field; -use super::domain::Rotation; +use crate::poly::Rotation; /// This represents a wire which has a fixed (permanent) value #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct FixedWire(pub usize); @@ -65,22 +65,22 @@ pub trait Circuit { ) -> Result<(), Error>; } -/// Low-degree polynomial representing an identity that must hold over the committed wires. +/// Low-degree expression representing an identity that must hold over the committed wires. #[derive(Clone, Debug)] -pub enum Polynomial { +pub enum Expression { /// 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>), + Sum(Box>, Box>), /// This is the product of two polynomials - Product(Box>, Box>), + Product(Box>, Box>), /// This is a scaled polynomial - Scaled(Box>, F), + Scaled(Box>, F), } -impl Polynomial { +impl Expression { /// Evaluate the polynomial using the provided closures to perform the /// operations. pub fn evaluate( @@ -92,19 +92,19 @@ impl Polynomial { 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) => { + Expression::Fixed(index) => fixed_wire(*index), + Expression::Advice(index) => advice_wire(*index), + Expression::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) => { + Expression::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) => { + Expression::Scaled(a, f) => { let a = a.evaluate(fixed_wire, advice_wire, sum, product, scaled); scaled(a, *f) } @@ -114,40 +114,40 @@ impl Polynomial { /// 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(), + Expression::Fixed(_) => 1, + Expression::Advice(_) => 1, + Expression::Sum(a, b) => max(a.degree(), b.degree()), + Expression::Product(a, b) => a.degree() + b.degree(), + Expression::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 Add for Expression { + type Output = Expression; + fn add(self, rhs: Expression) -> Expression { + Expression::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 Expression { + type Output = Expression; + fn mul(self, rhs: Expression) -> Expression { + Expression::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) +impl Mul for Expression { + type Output = Expression; + fn mul(self, rhs: F) -> Expression { + Expression::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); +pub(crate) struct PointIndex(pub usize); /// This is a description of the circuit environment, such as the gate, wire and /// permutation arrangements. @@ -155,7 +155,7 @@ pub struct PointIndex(pub usize); pub struct MetaCircuit { pub(crate) num_fixed_wires: usize, pub(crate) num_advice_wires: usize, - pub(crate) gates: Vec>, + pub(crate) gates: Vec>, pub(crate) advice_queries: Vec<(AdviceWire, Rotation)>, pub(crate) fixed_queries: Vec<(FixedWire, Rotation)>, @@ -229,8 +229,8 @@ impl MetaCircuit { } /// Query a fixed wire at a relative position - pub fn query_fixed(&mut self, wire: FixedWire, at: i32) -> Polynomial { - Polynomial::Fixed(self.query_fixed_index(wire, at)) + pub fn query_fixed(&mut self, wire: FixedWire, at: i32) -> Expression { + Expression::Fixed(self.query_fixed_index(wire, at)) } fn query_advice_index(&mut self, wire: AdviceWire, at: i32) -> usize { @@ -255,12 +255,12 @@ impl MetaCircuit { } /// Query an advice wire at a relative position - pub fn query_advice(&mut self, wire: AdviceWire, at: i32) -> Polynomial { - Polynomial::Advice(self.query_advice_index(wire, at)) + pub fn query_advice(&mut self, wire: AdviceWire, at: i32) -> Expression { + Expression::Advice(self.query_advice_index(wire, at)) } /// Create a new gate - pub fn create_gate(&mut self, f: impl FnOnce(&mut Self) -> Polynomial) { + pub fn create_gate(&mut self, f: impl FnOnce(&mut Self) -> Expression) { let poly = f(self); self.gates.push(poly); } diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 7da6fe86..a2d9301a 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -1,13 +1,15 @@ use super::{ circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit}, - domain::Rotation, hash_point, Error, Proof, SRS, }; use crate::arithmetic::{ eval_polynomial, get_challenge_scalar, kate_division, parallelize, BatchInvert, Challenge, Curve, CurveAffine, Field, }; -use crate::polycommit::Params; +use crate::poly::{ + commitment::{Blind, OpeningProof, Params}, + Coeff, LagrangeCoeff, Polynomial, Rotation, +}; use crate::transcript::Hasher; impl Proof { @@ -24,7 +26,8 @@ impl Proof { circuit: &ConcreteCircuit, ) -> Result { struct WitnessCollection { - advice: Vec>, + advice: Vec>, + _marker: std::marker::PhantomData, } impl ConstraintSystem for WitnessCollection { @@ -68,11 +71,13 @@ impl Proof { } } + let domain = &srs.domain; let mut meta = MetaCircuit::default(); let config = ConcreteCircuit::configure(&mut meta); let mut witness = WitnessCollection { - advice: vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_advice_wires], + advice: vec![domain.empty_lagrange(); meta.num_advice_wires], + _marker: std::marker::PhantomData, }; // Synthesize the circuit to obtain the witness and other information. @@ -84,7 +89,11 @@ impl Proof { let mut transcript = HBase::init(C::Base::one()); // Compute commitments to advice wire polynomials - let advice_blinds: Vec<_> = witness.advice.iter().map(|_| C::Scalar::random()).collect(); + let advice_blinds: Vec<_> = witness + .advice + .iter() + .map(|_| Blind(C::Scalar::random())) + .collect(); let advice_commitments_projective: Vec<_> = witness .advice .iter() @@ -100,13 +109,11 @@ impl Proof { hash_point(&mut transcript, commitment)?; } - let domain = &srs.domain; - let advice_polys: Vec<_> = witness .advice .clone() .into_iter() - .map(|poly| domain.obtain_poly(poly)) + .map(|poly| domain.lagrange_to_coeff(poly)) .collect(); let advice_cosets: Vec<_> = meta @@ -114,7 +121,7 @@ impl Proof { .iter() .map(|&(wire, at)| { let poly = advice_polys[wire.0].clone(); - domain.obtain_coset(poly, at) + domain.coeff_to_extended(poly, at) }) .collect(); @@ -209,15 +216,17 @@ impl Proof { tmp *= &modified_advice[row]; z.push(tmp); } + let z = domain.lagrange_from_vec(z); - let blind = C::Scalar::random(); + let blind = Blind(C::Scalar::random()); permutation_product_commitments_projective.push(params.commit_lagrange(&z, blind)); permutation_product_blinds.push(blind); - let z = domain.obtain_poly(z); + let z = domain.lagrange_to_coeff(z); permutation_product_polys.push(z.clone()); - permutation_product_cosets.push(domain.obtain_coset(z.clone(), Rotation::default())); - permutation_product_cosets_inv.push(domain.obtain_coset(z, Rotation(-1))); + permutation_product_cosets + .push(domain.coeff_to_extended(z.clone(), Rotation::default())); + permutation_product_cosets_inv.push(domain.coeff_to_extended(z, Rotation(-1))); } let mut permutation_product_commitments = vec![C::zero(); permutation_product_commitments_projective.len()]; @@ -237,56 +246,19 @@ impl Proof { 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 { - parallelize(&mut h_poly, |a, _| { - for a in a.iter_mut() { - *a *= &x_2; - } - }); - } + let mut h_poly = domain.empty_extended(); + for poly in meta.gates.iter() { + h_poly = h_poly * x_2; - let evaluation: Vec = poly.evaluate( + let evaluation = 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 - }, + &|a, b| a + &b, + &|a, b| a * &b, + &|a, scalar| a * scalar, ); - assert_eq!(h_poly.len(), evaluation.len()); - - if i == 0 { - h_poly = evaluation; - } else { - parallelize(&mut h_poly, |a, start| { - for (a, b) in a.iter_mut().zip(evaluation[start..].iter()) { - *a += b; - } - }); - } + h_poly = h_poly + &evaluation; } // l_0(X) * (1 - z(X)) = 0 @@ -305,11 +277,7 @@ impl Proof { // z(X) \prod (p(X) + \beta s_i(X) + \gamma) - z(omega^{-1} X) \prod (p(X) + \delta^i \beta X + \gamma) for (permutation_index, wires) in srs.meta.permutations.iter().enumerate() { - parallelize(&mut h_poly, |a, _| { - for a in a.iter_mut() { - *a *= &x_2; - } - }); + h_poly = h_poly * x_2; let mut left = permutation_product_cosets[permutation_index].clone(); for (advice, permutation) in wires @@ -342,31 +310,25 @@ impl Proof { current_delta *= &C::Scalar::DELTA; } - parallelize(&mut h_poly, |a, start| { - for ((h, left), right) in a - .iter_mut() - .zip(left[start..].iter()) - .zip(right[start..].iter()) - { - *h += &left; - *h -= &right; - } - }); + h_poly = h_poly + &left - &right; } // Divide by t(X) = X^{params.n} - 1. let h_poly = domain.divide_by_vanishing_poly(h_poly); // Obtain final h(X) polynomial - let h_poly = domain.from_coset(h_poly); + let h_poly = domain.extended_to_coeff(h_poly); // Split h(X) up into pieces let h_pieces = h_poly .chunks_exact(params.n as usize) - .map(|v| v.to_vec()) + .map(|v| domain.coeff_from_vec(v.to_vec())) .collect::>(); drop(h_poly); - let h_blinds: Vec<_> = h_pieces.iter().map(|_| C::Scalar::random()).collect(); + let h_blinds: Vec<_> = h_pieces + .iter() + .map(|_| Blind(C::Scalar::random())) + .collect(); // Compute commitments to each h(X) piece let h_commitments_projective: Vec<_> = h_pieces @@ -451,30 +413,32 @@ impl Proof { // 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_polys: Vec>> = + vec![None; meta.rotations.len()]; + let mut q_blinds = vec![Blind(C::Scalar::zero()); meta.rotations.len()]; let mut q_evals: Vec<_> = vec![C::Scalar::zero(); meta.rotations.len()]; { - 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; - } + let mut accumulate = + |point_index: usize, new_poly: &Polynomial<_, Coeff>, 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(()) }); - }) - .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; - }; + q_blinds[point_index] *= x_4; + q_blinds[point_index] += blind; + q_evals[point_index] *= &x_4; + q_evals[point_index] += &eval; + }; for (query_index, &(wire, ref at)) in meta.advice_queries.iter().enumerate() { let point_index = (*meta.rotations.get(at).unwrap()).0; @@ -493,7 +457,7 @@ impl Proof { accumulate( point_index, &srs.fixed_polys[wire.0], - C::Scalar::one(), + Blind::default(), fixed_evals[query_index], ); } @@ -526,7 +490,7 @@ impl Proof { .zip(permutation_evals.iter()) .flat_map(|(polys, evals)| polys.iter().zip(evals.iter())) { - accumulate(current_index, poly, C::Scalar::one(), *eval); + accumulate(current_index, poly, Blind::default(), *eval); } let current_index = (*srs.meta.rotations.get(&Rotation(-1)).unwrap()).0; @@ -543,13 +507,15 @@ impl Proof { let x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - let mut f_poly: Option> = None; + 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); + // TODO: change kate_division interface? + let mut poly = kate_division(&poly[..], point); poly.push(C::Scalar::zero()); + let poly = domain.coeff_from_vec(poly); f_poly = f_poly .map(|mut f_poly| { @@ -564,7 +530,7 @@ impl Proof { .or_else(|| Some(poly)); } let mut f_poly = f_poly.unwrap(); - let mut f_blind = C::Scalar::random(); + let mut f_blind = Blind(C::Scalar::random()); let f_commitment = params.commit(&f_poly, f_blind).to_affine(); @@ -590,8 +556,8 @@ impl Proof { 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]; + f_blind *= x_7; + f_blind += q_blinds[point_index.0]; parallelize(&mut f_poly, |f, start| { for (f, a) in f @@ -605,8 +571,7 @@ impl Proof { } // Let's prove that the q_commitment opens at x to the expected value. - let opening = params - .create_proof(&mut transcript, &f_poly, f_blind, x_6) + let opening = OpeningProof::create(¶ms, &mut transcript, &f_poly, f_blind, x_6) .map_err(|_| Error::ConstraintSystemFailure)?; Ok(Proof { diff --git a/src/plonk/srs.rs b/src/plonk/srs.rs index 86567c1e..ce545ef0 100644 --- a/src/plonk/srs.rs +++ b/src/plonk/srs.rs @@ -1,10 +1,12 @@ use super::{ circuit::{AdviceWire, Circuit, ConstraintSystem, FixedWire, MetaCircuit}, - domain::{EvaluationDomain, Rotation}, Error, SRS, }; use crate::arithmetic::{Curve, CurveAffine, Field}; -use crate::polycommit::Params; +use crate::poly::{ + commitment::{Blind, Params}, + EvaluationDomain, LagrangeCoeff, Polynomial, Rotation, +}; impl SRS { /// This generates a structured reference string for the provided `circuit` @@ -14,10 +16,11 @@ impl SRS { circuit: &ConcreteCircuit, ) -> Result { struct Assembly { - fixed: Vec>, + fixed: Vec>, mapping: Vec>>, aux: Vec>>, sizes: Vec>>, + _marker: std::marker::PhantomData, } impl ConstraintSystem for Assembly { @@ -147,10 +150,11 @@ impl SRS { } let mut assembly: Assembly = Assembly { - fixed: vec![vec![C::Scalar::zero(); params.n as usize]; meta.num_fixed_wires], + fixed: vec![domain.empty_lagrange(); meta.num_fixed_wires], mapping: vec![], aux: vec![], sizes: vec![], + _marker: std::marker::PhantomData, }; // Initialize the copy vector to keep track of copy constraints in all @@ -185,27 +189,23 @@ impl SRS { for i in 0..permutation.len() { // Computes the permutation polynomial based on the permutation // description in the assembly. - let permutation_poly: Vec<_> = (0..params.n as usize) - .map(|j| { - // assembly.copy[permutation_index] is indexed by wire - // i, and then indexed by row j, obtaining the index of - // the permuted value in deltaomega. - let (permuted_i, permuted_j) = assembly.mapping[permutation_index][i][j]; - deltaomega[permuted_i][permuted_j] - }) - .collect(); + let mut permutation_poly = domain.empty_lagrange(); + for (j, p) in permutation_poly.iter_mut().enumerate() { + let (permuted_i, permuted_j) = assembly.mapping[permutation_index][i][j]; + *p = deltaomega[permuted_i][permuted_j]; + } // Compute commitment to permutation polynomial commitments.push( params - .commit_lagrange(&permutation_poly, C::Scalar::one()) + .commit_lagrange(&permutation_poly, Blind::default()) .to_affine(), ); // Store permutation polynomial and precompute its coset evaluation inner_permutations.push(permutation_poly.clone()); - let poly = domain.obtain_poly(permutation_poly); + let poly = domain.lagrange_to_coeff(permutation_poly); polys.push(poly.clone()); - cosets.push(domain.obtain_coset(poly, Rotation::default())); + cosets.push(domain.coeff_to_extended(poly, Rotation::default())); } permutation_commitments.push(commitments); permutations.push(inner_permutations); @@ -216,13 +216,13 @@ impl SRS { let fixed_commitments = assembly .fixed .iter() - .map(|poly| params.commit_lagrange(poly, C::Scalar::one()).to_affine()) + .map(|poly| params.commit_lagrange(poly, Blind::default()).to_affine()) .collect(); let fixed_polys: Vec<_> = assembly .fixed .into_iter() - .map(|poly| domain.obtain_poly(poly)) + .map(|poly| domain.lagrange_to_coeff(poly)) .collect(); let fixed_cosets = meta @@ -230,16 +230,16 @@ impl SRS { .iter() .map(|&(wire, at)| { let poly = fixed_polys[wire.0].clone(); - domain.obtain_coset(poly, at) + domain.coeff_to_extended(poly, at) }) .collect(); // Compute l_0(X) // TODO: this can be done more efficiently - let mut l0 = vec![C::Scalar::zero(); params.n as usize]; + let mut l0 = domain.empty_lagrange(); l0[0] = C::Scalar::one(); - let l0 = domain.obtain_poly(l0); - let l0 = domain.obtain_coset(l0, Rotation::default()); + let l0 = domain.lagrange_to_coeff(l0); + let l0 = domain.coeff_to_extended(l0, Rotation::default()); Ok(SRS { domain, diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 95743c06..13796563 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -1,6 +1,6 @@ -use super::{domain::Rotation, hash_point, Proof, SRS}; +use super::{hash_point, Proof, SRS}; use crate::arithmetic::{get_challenge_scalar, Challenge, Curve, CurveAffine, Field}; -use crate::polycommit::Params; +use crate::poly::{commitment::Params, Rotation}; use crate::transcript::Hasher; impl Proof { @@ -261,8 +261,8 @@ impl Proof { } // Verify the opening proof - params.verify_proof( - &self.opening, + self.opening.verify( + params, &mut transcript, x_6, &f_commitment.to_affine(), diff --git a/src/poly.rs b/src/poly.rs new file mode 100644 index 00000000..eef53565 --- /dev/null +++ b/src/poly.rs @@ -0,0 +1,178 @@ +//! Contains utilities for performing arithmetic over univariate polynomials in +//! various forms, including computing commitments to them and provably opening +//! the committed polynomials at arbitrary points. + +use crate::arithmetic::{parallelize, Field}; + +use std::fmt::Debug; +use std::marker::PhantomData; +use std::ops::{Add, Deref, DerefMut, Index, IndexMut, Mul, RangeFrom, RangeFull, Sub}; + +pub mod commitment; +mod domain; + +pub use domain::*; + +/// The basis over which a polynomial is described. +pub trait Basis: Clone + Debug + Send + Sync {} + +/// The polynomial is defined as coefficients +#[derive(Clone, Debug)] +pub struct Coeff; +impl Basis for Coeff {} + +/// The polynomial is defined as coefficients of Lagrange basis polynomials +#[derive(Clone, Debug)] +pub struct LagrangeCoeff; +impl Basis for LagrangeCoeff {} + +/// The polynomial is defined as coefficients of Lagrange basis polynomials in +/// an extended size domain which supports multiplication +#[derive(Clone, Debug)] +pub struct ExtendedLagrangeCoeff; +impl Basis for ExtendedLagrangeCoeff {} + +/// Represents a univariate polynomial defined over a field and a particular +/// basis. +#[derive(Clone, Debug)] +pub struct Polynomial { + values: Vec, + _marker: PhantomData, +} + +impl Index for Polynomial { + type Output = F; + + fn index(&self, index: usize) -> &F { + self.values.index(index) + } +} + +impl IndexMut for Polynomial { + fn index_mut(&mut self, index: usize) -> &mut F { + self.values.index_mut(index) + } +} + +impl Index> for Polynomial { + type Output = [F]; + + fn index(&self, index: RangeFrom) -> &[F] { + self.values.index(index) + } +} + +impl IndexMut> for Polynomial { + fn index_mut(&mut self, index: RangeFrom) -> &mut [F] { + self.values.index_mut(index) + } +} + +impl Index for Polynomial { + type Output = [F]; + + fn index(&self, index: RangeFull) -> &[F] { + self.values.index(index) + } +} + +impl IndexMut for Polynomial { + fn index_mut(&mut self, index: RangeFull) -> &mut [F] { + self.values.index_mut(index) + } +} + +impl Deref for Polynomial { + type Target = [F]; + + fn deref(&self) -> &[F] { + &self.values[..] + } +} + +impl DerefMut for Polynomial { + fn deref_mut(&mut self) -> &mut [F] { + &mut self.values[..] + } +} + +impl Polynomial { + /// Iterate over the values, which are either in coefficient or evaluation + /// form depending on the basis `B`. + pub fn iter(&self) -> impl Iterator { + self.values.iter() + } + + /// Iterate over the values mutably, which are either in coefficient or + /// evaluation form depending on the basis `B`. + pub fn iter_mut(&mut self) -> impl Iterator { + self.values.iter_mut() + } + + /// Gets the length of this polynomial in terms of the number of + /// coefficients used to describe it. + pub fn len(&self) -> usize { + self.values.len() + } +} + +impl<'a, F: Field, B: Basis> Add<&'a Polynomial> for Polynomial { + type Output = Polynomial; + + fn add(mut self, rhs: &'a Polynomial) -> Polynomial { + parallelize(&mut self.values, |lhs, start| { + for (lhs, rhs) in lhs.iter_mut().zip(rhs.values[start..].iter()) { + *lhs += *rhs; + } + }); + + self + } +} + +impl<'a, F: Field, B: Basis> Sub<&'a Polynomial> for Polynomial { + type Output = Polynomial; + + fn sub(mut self, rhs: &'a Polynomial) -> Polynomial { + parallelize(&mut self.values, |lhs, start| { + for (lhs, rhs) in lhs.iter_mut().zip(rhs.values[start..].iter()) { + *lhs -= *rhs; + } + }); + + self + } +} + +impl<'a, F: Field> Mul<&'a Polynomial> + for Polynomial +{ + type Output = Polynomial; + + fn mul( + mut self, + rhs: &'a Polynomial, + ) -> Polynomial { + parallelize(&mut self.values, |lhs, start| { + for (lhs, rhs) in lhs.iter_mut().zip(rhs.values[start..].iter()) { + *lhs *= *rhs; + } + }); + + self + } +} + +impl<'a, F: Field, B: Basis> Mul for Polynomial { + type Output = Polynomial; + + fn mul(mut self, rhs: F) -> Polynomial { + parallelize(&mut self.values, |lhs, _| { + for lhs in lhs.iter_mut() { + *lhs *= rhs; + } + }); + + self + } +} diff --git a/src/poly/commitment.rs b/src/poly/commitment.rs new file mode 100644 index 00000000..1a985484 --- /dev/null +++ b/src/poly/commitment.rs @@ -0,0 +1,280 @@ +//! This module contains an implementation of the polynomial commitment scheme +//! described in the [Halo][halo] paper. +//! +//! [halo]: https://eprint.iacr.org/2019/1021 + +use super::{Coeff, LagrangeCoeff, Polynomial}; +use crate::arithmetic::{best_fft, best_multiexp, parallelize, Curve, CurveAffine, Field}; +use crate::transcript::Hasher; +use std::ops::{Add, AddAssign, Mul, MulAssign}; + +mod prover; +mod verifier; + +/// This is a proof object for the polynomial commitment scheme opening. +#[derive(Debug, Clone)] +pub struct OpeningProof { + fork: u8, + rounds: Vec<(C, C)>, + delta: C, + z1: C::Scalar, + z2: C::Scalar, +} + +/// These are the public parameters for the polynomial commitment scheme. +#[derive(Debug)] +pub struct Params { + pub(crate) k: u32, + pub(crate) n: u64, + pub(crate) g: Vec, + pub(crate) g_lagrange: Vec, + pub(crate) h: C, +} + +impl Params { + /// Initializes parameters for the curve, given a random oracle to draw + /// points from. + pub fn new>(k: u32) -> Self { + // This is usually a limitation on the curve, but we also want 32-bit + // architectures to be supported. + assert!(k < 32); + // No goofy hardware please. + assert!(core::mem::size_of::() >= 4); + + let n: u64 = 1 << k; + + let g = { + let hasher = &H::init(C::Base::zero()); + + let mut g = Vec::with_capacity(n as usize); + g.resize(n as usize, C::zero()); + + parallelize(&mut g, move |g, start| { + let mut cur_value = C::Base::from(start as u64); + for g in g.iter_mut() { + let mut hasher = hasher.clone(); + hasher.absorb(cur_value); + cur_value += &C::Base::one(); + loop { + let x = hasher.squeeze().to_bytes(); + let p = C::from_bytes(&x); + if bool::from(p.is_some()) { + *g = p.unwrap(); + break; + } + } + } + }); + + g + }; + + // Let's evaluate all of the Lagrange basis polynomials + // using an inverse FFT. + let mut alpha_inv = C::Scalar::ROOT_OF_UNITY_INV; + for _ in k..C::Scalar::S { + alpha_inv = alpha_inv.square(); + } + let mut g_lagrange_projective = g.iter().map(|g| g.to_projective()).collect::>(); + best_fft(&mut g_lagrange_projective, alpha_inv, k); + let minv = C::Scalar::TWO_INV.pow_vartime(&[k as u64, 0, 0, 0]); + parallelize(&mut g_lagrange_projective, |g, _| { + for g in g.iter_mut() { + *g *= minv; + } + }); + + let g_lagrange = { + let mut g_lagrange = vec![C::zero(); n as usize]; + parallelize(&mut g_lagrange, |g_lagrange, starts| { + C::Projective::batch_to_affine( + &g_lagrange_projective[starts..(starts + g_lagrange.len())], + g_lagrange, + ); + }); + drop(g_lagrange_projective); + g_lagrange + }; + + let h = { + let mut hasher = H::init(C::Base::zero()); + hasher.absorb(-C::Base::one()); + let x = hasher.squeeze().to_bytes(); + let p = C::from_bytes(&x); + p.unwrap() + }; + + Params { + k, + n, + g, + g_lagrange, + h, + } + } + + /// This computes a commitment to a polynomial described by the provided + /// slice of coefficients. The commitment will be blinded by the blinding + /// factor `r`. + pub fn commit( + &self, + poly: &Polynomial, + r: Blind, + ) -> C::Projective { + let mut tmp_scalars = Vec::with_capacity(poly.len() + 1); + let mut tmp_bases = Vec::with_capacity(poly.len() + 1); + + tmp_scalars.extend(poly.iter()); + tmp_scalars.push(r.0); + + tmp_bases.extend(self.g.iter()); + tmp_bases.push(self.h); + + best_multiexp::(&tmp_scalars, &tmp_bases) + } + + /// This commits to a polynomial using its evaluations over the $2^k$ size + /// evaluation domain. The commitment will be blinded by the blinding factor + /// `r`. + pub fn commit_lagrange( + &self, + poly: &Polynomial, + r: Blind, + ) -> C::Projective { + let mut tmp_scalars = Vec::with_capacity(poly.len() + 1); + let mut tmp_bases = Vec::with_capacity(poly.len() + 1); + + tmp_scalars.extend(poly.iter()); + tmp_scalars.push(r.0); + + tmp_bases.extend(self.g_lagrange.iter()); + tmp_bases.push(self.h); + + best_multiexp::(&tmp_scalars, &tmp_bases) + } +} + +/// Wrapper type around a blinding factor. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct Blind(pub F); + +impl Default for Blind { + fn default() -> Self { + Blind(F::one()) + } +} + +impl Add for Blind { + type Output = Self; + + fn add(self, rhs: Blind) -> Self { + Blind(self.0 + rhs.0) + } +} + +impl Mul for Blind { + type Output = Self; + + fn mul(self, rhs: Blind) -> Self { + Blind(self.0 * rhs.0) + } +} + +impl AddAssign for Blind { + fn add_assign(&mut self, rhs: Blind) { + self.0 += rhs.0; + } +} + +impl MulAssign for Blind { + fn mul_assign(&mut self, rhs: Blind) { + self.0 *= rhs.0; + } +} + +impl AddAssign for Blind { + fn add_assign(&mut self, rhs: F) { + self.0 += rhs; + } +} + +impl MulAssign for Blind { + fn mul_assign(&mut self, rhs: F) { + self.0 *= rhs; + } +} + +#[test] +fn test_commit_lagrange() { + const K: u32 = 6; + + use crate::arithmetic::{EpAffine, Fp, Fq}; + use crate::transcript::DummyHash; + let params = Params::::new::>(K); + let domain = super::EvaluationDomain::new(1, K); + + let mut a = domain.empty_lagrange(); + + for (i, a) in a.iter_mut().enumerate() { + *a = Fq::from(i as u64); + } + + let b = domain.lagrange_to_coeff(a.clone()); + + let alpha = Blind(Fq::random()); + + assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha)); +} + +#[test] +fn test_opening_proof() { + const K: u32 = 6; + + use super::{ + commitment::{Blind, Params}, + EvaluationDomain, + }; + use crate::arithmetic::{ + eval_polynomial, get_challenge_scalar, Challenge, Curve, EpAffine, Field, Fp, Fq, + }; + use crate::transcript::{DummyHash, Hasher}; + + let params = Params::::new::>(K); + let domain = EvaluationDomain::new(1, K); + + let mut px = domain.empty_coeff(); + + for (i, a) in px.iter_mut().enumerate() { + *a = Fq::from(i as u64); + } + + let blind = Blind(Fq::random()); + + let p = params.commit(&px, blind).to_affine(); + + let mut transcript = DummyHash::init(Field::one()); + let (p_x, p_y) = p.get_xy().unwrap(); + transcript.absorb(p_x); + transcript.absorb(p_y); + let x_packed = transcript.squeeze().get_lower_128(); + let x: Fq = get_challenge_scalar(Challenge(x_packed)); + + // Evaluate the polynomial + let v = eval_polynomial(&px, x); + + transcript.absorb(Fp::from_bytes(&v.to_bytes()).unwrap()); // unlikely to fail since p ~ q + + loop { + let mut transcript_dup = transcript.clone(); + + let opening_proof = OpeningProof::create(¶ms, &mut transcript, &px, blind, x); + if opening_proof.is_err() { + transcript = transcript_dup; + transcript.absorb(Field::one()); + } else { + let opening_proof = opening_proof.unwrap(); + assert!(opening_proof.verify(¶ms, &mut transcript_dup, x, &p, v)); + break; + } + } +} diff --git a/src/poly/commitment/prover.rs b/src/poly/commitment/prover.rs new file mode 100644 index 00000000..3acddc2a --- /dev/null +++ b/src/poly/commitment/prover.rs @@ -0,0 +1,234 @@ +use super::super::{Coeff, Polynomial}; +use super::{Blind, OpeningProof, Params}; +use crate::arithmetic::{ + best_multiexp, compute_inner_product, get_challenge_scalar, parallelize, Challenge, Curve, + CurveAffine, Field, +}; +use crate::transcript::Hasher; + +impl OpeningProof { + /// Create a polynomial commitment opening proof for the polynomial defined + /// by the coefficients `px`, the blinding factor `blind` used for the + /// polynomial commitment, and the point `x` that the polynomial is + /// evaluated at. + /// + /// This function will panic if the provided polynomial is too large with + /// respect to the polynomial commitment parameters. + /// + /// **Important:** This function assumes that the provided `transcript` has + /// already seen the common inputs: the polynomial commitment P, the claimed + /// opening v, and the point x. It's probably also nice for the transcript + /// to have seen the elliptic curve description and the SRS, if you want to + /// be rigorous. + pub fn create>( + params: &Params, + transcript: &mut H, + px: &Polynomial, + blind: Blind, + x: C::Scalar, + ) -> Result { + let mut blind = blind.0; + + // We're limited to polynomials of degree n - 1. + assert!(px.len() <= params.n as usize); + + let mut fork = 0; + + // TODO: remove this hack and force the caller to deal with it + loop { + let mut transcript = transcript.clone(); + transcript.absorb(C::Base::from_u64(fork as u64)); + let u_x = transcript.squeeze(); + // y^2 = x^3 + B + let u_y2 = u_x.square() * &u_x + &C::b(); + let u_y = u_y2.deterministic_sqrt(); + + if u_y.is_none() { + fork += 1; + } else { + break; + } + } + + transcript.absorb(C::Base::from_u64(fork as u64)); + + // Compute U + let u = { + let u_x = transcript.squeeze(); + // y^2 = x^3 + B + let u_y2 = u_x.square() * &u_x + &C::b(); + let u_y = u_y2.deterministic_sqrt().unwrap(); + + C::from_xy(u_x, u_y).unwrap() + }; + + // Initialize the vector `a` as the coefficients of the polynomial, + // rounding up to the parameters. + let mut a = px.to_vec(); + a.resize(params.n as usize, C::Scalar::zero()); + + // Initialize the vector `b` as the powers of `x`. The inner product of + // `a` and `b` is the evaluation of the polynomial at `x`. + let mut b = Vec::with_capacity(1 << params.k); + { + let mut cur = C::Scalar::one(); + for _ in 0..(1 << params.k) { + b.push(cur); + cur *= &x; + } + } + + // Initialize the vector `G` from the SRS. We'll be progressively + // collapsing this vector into smaller and smaller vectors until it is + // of length 1. + let mut g = params.g.clone(); + + // Perform the inner product argument, round by round. + let mut rounds = Vec::with_capacity(params.k as usize); + for k in (1..=params.k).rev() { + let half = 1 << (k - 1); // half the length of `a`, `b`, `G` + + // Compute L, R + // + // TODO: If we modify multiexp to take "extra" bases, we could speed + // this piece up a bit by combining the multiexps. + let l = best_multiexp(&a[0..half], &g[half..]); + let r = best_multiexp(&a[half..], &g[0..half]); + let value_l = compute_inner_product(&a[0..half], &b[half..]); + let value_r = compute_inner_product(&a[half..], &b[0..half]); + let mut l_randomness = C::Scalar::random(); + let r_randomness = C::Scalar::random(); + let l = l + &best_multiexp(&[value_l, l_randomness], &[u, params.h]); + let r = r + &best_multiexp(&[value_r, r_randomness], &[u, params.h]); + let mut l = l.to_affine(); + let r = r.to_affine(); + + let challenge = loop { + // We'll fork the transcript and adjust our randomness + // until the challenge is a square. + let mut transcript = transcript.clone(); + + // We expect these to not be points at infinity due to the randomness. + let (l_x, l_y) = l.get_xy().unwrap(); + let (r_x, r_y) = r.get_xy().unwrap(); + + // Feed L and R into the cloned transcript... + transcript.absorb(l_x); + transcript.absorb(l_y); + transcript.absorb(r_x); + transcript.absorb(r_y); + + // ... and get the squared challenge. + let challenge_sq_packed = transcript.squeeze().get_lower_128(); + let challenge_sq: C::Scalar = get_challenge_scalar(Challenge(challenge_sq_packed)); + + // There might be no square root, in which case we'll fork the + // transcript. + let challenge = challenge_sq.deterministic_sqrt(); + if let Some(challenge) = challenge { + break challenge; + } else { + // Try again, with slightly different randomness + l = (l + params.h).to_affine(); + l_randomness += &C::Scalar::one(); + } + }; + + // Challenge is unlikely to be zero. + let challenge_inv = challenge.invert().unwrap(); + let challenge_sq_inv = challenge_inv.square(); + let challenge_sq = challenge.square(); + + // Feed L and R into the real transcript + let (l_x, l_y) = l.get_xy().unwrap(); + let (r_x, r_y) = r.get_xy().unwrap(); + transcript.absorb(l_x); + transcript.absorb(l_y); + transcript.absorb(r_x); + transcript.absorb(r_y); + + // And obtain the challenge, even though we already have it, since + // squeezing affects the transcript. + { + let challenge_sq_packed = transcript.squeeze().get_lower_128(); + let challenge_sq_expected = get_challenge_scalar(Challenge(challenge_sq_packed)); + assert_eq!(challenge_sq, challenge_sq_expected); + } + + // Done with this round. + rounds.push((l, r)); + + // Collapse `a` and `b`. + // TODO: parallelize + for i in 0..half { + a[i] = (a[i] * &challenge) + &(a[i + half] * &challenge_inv); + b[i] = (b[i] * &challenge_inv) + &(b[i + half] * &challenge); + } + a.truncate(half); + b.truncate(half); + + // Collapse `G` + parallel_generator_collapse(&mut g, challenge, challenge_inv); + g.truncate(half); + + // Update randomness (the synthetic blinding factor at the end) + blind += &(l_randomness * &challenge_sq); + blind += &(r_randomness * &challenge_sq_inv); + } + + // We have fully collapsed `a`, `b`, `G` + assert_eq!(a.len(), 1); + let a = a[0]; + assert_eq!(b.len(), 1); + let b = b[0]; + assert_eq!(g.len(), 1); + let g = g[0]; + + // Random nonces for the zero-knowledge opening + let d = C::Scalar::random(); + let s = C::Scalar::random(); + + let delta = best_multiexp(&[d, d * &b, s], &[g, u, params.h]).to_affine(); + + let (delta_x, delta_y) = delta.get_xy().unwrap(); + + // Feed delta into the transcript + transcript.absorb(delta_x); + transcript.absorb(delta_y); + + // Obtain the challenge c. + let c_packed = transcript.squeeze().get_lower_128(); + let c: C::Scalar = get_challenge_scalar(Challenge(c_packed)); + + // Compute z1 and z2 as described in the Halo paper. + let z1 = a * &c + &d; + let z2 = c * &blind + &s; + + Ok(OpeningProof { + fork, + rounds, + delta, + z1, + z2, + }) + } +} + +fn parallel_generator_collapse( + g: &mut [C], + challenge: C::Scalar, + challenge_inv: C::Scalar, +) { + let len = g.len() / 2; + let (mut g_lo, g_hi) = g.split_at_mut(len); + + parallelize(&mut g_lo, |g_lo, start| { + let g_hi = &g_hi[start..]; + let mut tmp = Vec::with_capacity(g_lo.len()); + for (g_lo, g_hi) in g_lo.iter().zip(g_hi.iter()) { + // TODO: could use multiexp + tmp.push(((*g_lo) * challenge_inv) + &((*g_hi) * challenge)); + } + C::Projective::batch_to_affine(&tmp, g_lo); + }); +} diff --git a/src/poly/commitment/verifier.rs b/src/poly/commitment/verifier.rs new file mode 100644 index 00000000..a3d7e4da --- /dev/null +++ b/src/poly/commitment/verifier.rs @@ -0,0 +1,179 @@ +use super::{OpeningProof, Params}; +use crate::transcript::Hasher; + +use crate::arithmetic::{ + best_multiexp, get_challenge_scalar, Challenge, Curve, CurveAffine, Field, +}; + +impl OpeningProof { + /// Checks to see if an [`OpeningProof`] is valid given the current + /// `transcript`, and a point `x` that the polynomial commitment `p` opens + /// purportedly to the value `v`. + pub fn verify>( + &self, + params: &Params, + transcript: &mut H, + x: C::Scalar, + p: &C, + v: C::Scalar, + ) -> bool { + // Check for well-formedness + if self.rounds.len() != params.k as usize { + return false; + } + + transcript.absorb(C::Base::from_u64(self.fork as u64)); + + // Compute U + let u = { + let u_x = transcript.squeeze(); + // y^2 = x^3 + B + let u_y2 = u_x.square() * &u_x + &C::b(); + let u_y = u_y2.deterministic_sqrt(); + if u_y.is_none() { + return false; + } + let u_y = u_y.unwrap(); + + C::from_xy(u_x, u_y).unwrap() + }; + + let mut extra_scalars = Vec::with_capacity(self.rounds.len() * 2 + 4 + params.n as usize); + let mut extra_bases = Vec::with_capacity(self.rounds.len() * 2 + 4 + params.n as usize); + + // Data about the challenges from each of the rounds. + let mut challenges = Vec::with_capacity(self.rounds.len()); + let mut challenges_inv = Vec::with_capacity(self.rounds.len()); + let mut challenges_sq = Vec::with_capacity(self.rounds.len()); + let mut allinv = Field::one(); + + for round in &self.rounds { + // Feed L and R into the transcript. + let l = round.0.get_xy(); + let r = round.1.get_xy(); + if bool::from(l.is_none() | r.is_none()) { + return false; + } + let l = l.unwrap(); + let r = r.unwrap(); + transcript.absorb(l.0); + transcript.absorb(l.1); + transcript.absorb(r.0); + transcript.absorb(r.1); + let challenge_sq_packed = transcript.squeeze().get_lower_128(); + let challenge_sq: C::Scalar = get_challenge_scalar(Challenge(challenge_sq_packed)); + + let challenge = challenge_sq.deterministic_sqrt(); + if challenge.is_none() { + // We didn't sample a square. + return false; + } + let challenge = challenge.unwrap(); + + let challenge_inv = challenge.invert(); + if bool::from(challenge_inv.is_none()) { + // We sampled zero for some reason, unlikely to happen by + // chance. + return false; + } + let challenge_inv = challenge_inv.unwrap(); + allinv *= challenge_inv; + + let challenge_sq_inv = challenge_inv.square(); + + extra_scalars.push(challenge_sq); + extra_bases.push(round.0); + extra_scalars.push(challenge_sq_inv); + extra_bases.push(round.1); + + challenges.push(challenge); + challenges_inv.push(challenge_inv); + challenges_sq.push(challenge_sq); + } + + let delta = self.delta.get_xy(); + if bool::from(delta.is_none()) { + return false; + } + let delta = delta.unwrap(); + + // Feed delta into the transcript + transcript.absorb(delta.0); + transcript.absorb(delta.1); + + // Get the challenge `c` + let c_packed = transcript.squeeze().get_lower_128(); + let c: C::Scalar = get_challenge_scalar(Challenge(c_packed)); + + // Check + // [c] P + [c * v] U + [c] sum(L_i * u_i^2) + [c] sum(R_i * u_i^-2) + delta - [z1] G - [z1 * b] U - [z2] H + // = 0 + + for scalar in &mut extra_scalars { + *scalar *= &c; + } + + let b = compute_b(x, &challenges, &challenges_inv); + + let neg_z1 = -self.z1; + + // [c] P + extra_bases.push(*p); + extra_scalars.push(c); + + // [c * v] U - [z1 * b] U + extra_bases.push(u); + extra_scalars.push((c * &v) + &(neg_z1 * &b)); + + // delta + extra_bases.push(self.delta); + extra_scalars.push(Field::one()); + + // - [z2] H + extra_bases.push(params.h); + extra_scalars.push(-self.z2); + + // - [z1] G + extra_bases.extend(¶ms.g); + let mut s = compute_s(&challenges_sq, allinv); + // TODO: parallelize + for s in &mut s { + *s *= &neg_z1; + } + extra_scalars.extend(s); + + bool::from(best_multiexp(&extra_scalars, &extra_bases).is_zero()) + } +} + +fn compute_b(x: F, challenges: &[F], challenges_inv: &[F]) -> F { + assert!(!challenges.is_empty()); + assert_eq!(challenges.len(), challenges_inv.len()); + if challenges.len() == 1 { + *challenges_inv.last().unwrap() + *challenges.last().unwrap() * x + } else { + (*challenges_inv.last().unwrap() + *challenges.last().unwrap() * x) + * compute_b( + x.square(), + &challenges[0..(challenges.len() - 1)], + &challenges_inv[0..(challenges.len() - 1)], + ) + } +} + +// TODO: parallelize +fn compute_s(challenges_sq: &[F], allinv: F) -> Vec { + let lg_n = challenges_sq.len(); + let n = 1 << lg_n; + + let mut s = Vec::with_capacity(n); + s.push(allinv); + for i in 1..n { + let lg_i = (32 - 1 - (i as u32).leading_zeros()) as usize; + let k = 1 << lg_i; + let u_lg_i_sq = challenges_sq[(lg_n - 1) - lg_i]; + s.push(s[i - k] * u_lg_i_sq); + } + + s +} diff --git a/src/plonk/domain.rs b/src/poly/domain.rs similarity index 62% rename from src/plonk/domain.rs rename to src/poly/domain.rs index 52a79b06..5eed56fc 100644 --- a/src/plonk/domain.rs +++ b/src/poly/domain.rs @@ -1,5 +1,11 @@ +//! Contains utilities for performing polynomial arithmetic over an evaluation +//! domain that is of a suitable size for the application. + use crate::arithmetic::{best_fft, parallelize, BatchInvert, Field, Group}; +use super::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial}; +use std::marker::PhantomData; + /// 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, Ord, PartialOrd)] @@ -12,8 +18,8 @@ impl Default for Rotation { } /// 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. +/// performing operations on an evaluation domain of size $2^k$ and an extended +/// domain of size $2^{k} * j$ with $j \neq 0$. #[derive(Debug)] pub struct EvaluationDomain { n: u64, @@ -33,14 +39,11 @@ pub struct EvaluationDomain { } impl EvaluationDomain { - /// This constructs a new evaluation domain object (containing precomputed - /// constants) for operating on an evaluation domain of size $2^k$ and for - /// some operations over an extended domain of size $2^{k + j}$ where $j$ is - /// sufficiently large to describe the quotient polynomial depending on the - /// maximum degree of all PLONK gates. - pub fn new(gate_degree: u32, k: u32) -> Self { + /// This constructs a new evaluation domain object based on the provided + /// values $j, k$. + pub fn new(j: u32, k: u32) -> Self { // quotient_poly_degree * params.n - 1 is the degree of the quotient polynomial - let quotient_poly_degree = (gate_degree - 1) as u64; + let quotient_poly_degree = (j - 1) as u64; let n = 1u64 << k; @@ -131,32 +134,85 @@ impl EvaluationDomain { } } + /// Obtains a polynomial in Lagrange form when given a vector of Lagrange + /// coefficients of size `n`; panics if the provided vector is the wrong + /// length. + pub fn lagrange_from_vec(&self, values: Vec) -> Polynomial { + assert_eq!(values.len(), self.n as usize); + + Polynomial { + values, + _marker: PhantomData, + } + } + + /// Obtains a polynomial in coefficient form when given a vector of + /// coefficients of size `n`; panics if the provided vector is the wrong + /// length. + pub fn coeff_from_vec(&self, values: Vec) -> Polynomial { + assert_eq!(values.len(), self.n as usize); + + Polynomial { + values, + _marker: PhantomData, + } + } + + /// Returns an empty (zero) polynomial in the coefficient basis + pub fn empty_coeff(&self) -> Polynomial { + Polynomial { + values: vec![G::group_zero(); self.n as usize], + _marker: PhantomData, + } + } + + /// Returns an empty (zero) polynomial in the Lagrange coefficient basis + pub fn empty_lagrange(&self) -> Polynomial { + Polynomial { + values: vec![G::group_zero(); self.n as usize], + _marker: PhantomData, + } + } + + /// Returns an empty (zero) polynomial in the extended Lagrange coefficient + /// basis + pub fn empty_extended(&self) -> Polynomial { + Polynomial { + values: vec![G::group_zero(); self.extended_len()], + _marker: PhantomData, + } + } + /// 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_poly(&self, mut a: Vec) -> Vec { - assert_eq!(a.len(), 1 << self.k); + pub fn lagrange_to_coeff(&self, mut a: Polynomial) -> Polynomial { + assert_eq!(a.values.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); + Self::ifft(&mut a.values, self.omega_inv, self.k, self.ifft_divisor); - a + Polynomial { + values: a.values, + _marker: PhantomData, + } } - /// This takes us from an n-length coefficient vector into the coset + /// This takes us from an n-length coefficient vector into the extended /// 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); + pub fn coeff_to_extended( + &self, + mut a: Polynomial, + rotation: Rotation, + ) -> Polynomial { + assert_eq!(a.values.len(), 1 << self.k); 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); + Self::distribute_powers_zeta(&mut a.values, self.g_coset); } else { let mut g = G::Scalar::ZETA; if rotation.0 > 0 { @@ -166,24 +222,29 @@ impl EvaluationDomain { .omega_inv .pow_vartime(&[rotation.0.abs() as u64, 0, 0, 0]); } - Self::distribute_powers(&mut a, g); + Self::distribute_powers(&mut a.values, g); + } + a.values.resize(self.extended_len(), G::group_zero()); + best_fft(&mut a.values, self.extended_omega, self.extended_k); + + Polynomial { + values: a.values, + _marker: PhantomData, } - a.resize(self.coset_len(), G::group_zero()); - best_fft(&mut a, self.extended_omega, self.extended_k); - a } - /// This takes us from the coset evaluation domain and gets us the quotient - /// polynomial coefficients. + /// This takes us from the extended evaluation domain and gets us the + /// quotient polynomial coefficients. /// /// 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(), self.coset_len()); + // TODO/FIXME: caller should be responsible for truncating + pub fn extended_to_coeff(&self, mut a: Polynomial) -> Vec { + assert_eq!(a.values.len(), self.extended_len()); // Inverse FFT Self::ifft( - &mut a, + &mut a.values, self.extended_omega_inv, self.extended_k, self.extended_ifft_divisor, @@ -191,31 +252,38 @@ impl EvaluationDomain { // Distribute powers to move from coset; opposite from the // transformation we performed earlier. - Self::distribute_powers(&mut a, self.g_coset_inv); + Self::distribute_powers(&mut a.values, self.g_coset_inv); // Truncate it to match the size of the quotient polynomial; the // evaluation domain might be slightly larger than necessary because // it always lies on a power-of-two boundary. - a.truncate((&self.n * self.quotient_poly_degree) as usize); + a.values + .truncate((&self.n * self.quotient_poly_degree) as usize); - a + a.values } - /// 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(), self.coset_len()); + /// This divides the polynomial (in the extended domain) by the vanishing + /// polynomial of the $2^k$ size domain. + pub fn divide_by_vanishing_poly( + &self, + mut a: Polynomial, + ) -> Polynomial { + assert_eq!(a.values.len(), self.extended_len()); // Divide to obtain the quotient polynomial in the coset evaluation // domain. - parallelize(&mut h_poly, |h, mut index| { + parallelize(&mut a.values, |h, mut index| { for h in h { h.group_scale(&self.t_evaluations[index % self.t_evaluations.len()]); index += 1; } }); - h_poly + Polynomial { + values: a.values, + _marker: PhantomData, + } } fn distribute_powers_zeta(mut a: &mut [G], g: G::Scalar) { @@ -252,24 +320,31 @@ impl EvaluationDomain { }); } - pub fn coset_len(&self) -> usize { + /// Get the size of the extended domain + pub fn extended_len(&self) -> usize { 1 << self.extended_k } + /// Get $\omega$, the generator of the $2^k$ order multiplicative subgroup. pub fn get_omega(&self) -> G::Scalar { self.omega } - pub fn get_extended_omega(&self) -> G::Scalar { - self.extended_omega - } - + /// Get $\omega^{-1}$, the inverse of the generator of the $2^k$ order + /// multiplicative subgroup. 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; + /// Get the generator of the extended domain's multiplicative subgroup. + pub fn get_extended_omega(&self) -> G::Scalar { + self.extended_omega + } + + /// Multiplies a value by some power of $\omega$, essentially rotating over + /// the domain. + pub fn rotate_omega(&self, value: G::Scalar, rotation: Rotation) -> G::Scalar { + let mut point = value; if rotation.0 >= 0 { point *= &self.get_omega().pow(&[rotation.0 as u64, 0, 0, 0]); } else { @@ -280,6 +355,7 @@ impl EvaluationDomain { point } + /// Gets the barycentric weight of $1$ over the $2^k$ size domain. pub fn get_barycentric_weight(&self) -> G::Scalar { self.barycentric_weight } diff --git a/src/polycommit.rs b/src/polycommit.rs deleted file mode 100644 index 2adeb151..00000000 --- a/src/polycommit.rs +++ /dev/null @@ -1,600 +0,0 @@ -//! This module contains an implementation of the polynomial commitment scheme -//! described in the [Halo][halo] paper. -//! -//! [halo]: https://eprint.iacr.org/2019/1021 - -use crate::arithmetic::{ - best_fft, best_multiexp, compute_inner_product, get_challenge_scalar, parallelize, Challenge, - Curve, CurveAffine, Field, -}; -use crate::transcript::Hasher; - -/// This is a proof object for the polynomial commitment scheme opening. -#[derive(Debug, Clone)] -pub struct OpeningProof { - fork: u8, - rounds: Vec<(C, C)>, - delta: C, - z1: C::Scalar, - z2: C::Scalar, -} - -/// These are the public parameters for the polynomial commitment scheme. -#[derive(Debug)] -pub struct Params { - pub(crate) k: u32, - pub(crate) n: u64, - pub(crate) g: Vec, - pub(crate) g_lagrange: Vec, - pub(crate) h: C, -} - -impl Params { - /// Initializes parameters for the curve, given a random oracle to draw - /// points from. - pub fn new>(k: u32) -> Self { - // This is usually a limitation on the curve, but we also want 32-bit - // architectures to be supported. - assert!(k < 32); - // No goofy hardware please. - assert!(core::mem::size_of::() >= 4); - - let n: u64 = 1 << k; - - let g = { - let hasher = &H::init(C::Base::zero()); - - let mut g = Vec::with_capacity(n as usize); - g.resize(n as usize, C::zero()); - - parallelize(&mut g, move |g, start| { - let mut cur_value = C::Base::from(start as u64); - for g in g.iter_mut() { - let mut hasher = hasher.clone(); - hasher.absorb(cur_value); - cur_value += &C::Base::one(); - loop { - let x = hasher.squeeze().to_bytes(); - let p = C::from_bytes(&x); - if bool::from(p.is_some()) { - *g = p.unwrap(); - break; - } - } - } - }); - - g - }; - - // Let's evaluate all of the Lagrange basis polynomials - // using an inverse FFT. - let mut alpha_inv = C::Scalar::ROOT_OF_UNITY_INV; - for _ in k..C::Scalar::S { - alpha_inv = alpha_inv.square(); - } - let mut g_lagrange_projective = g.iter().map(|g| g.to_projective()).collect::>(); - best_fft(&mut g_lagrange_projective, alpha_inv, k); - let minv = C::Scalar::TWO_INV.pow_vartime(&[k as u64, 0, 0, 0]); - parallelize(&mut g_lagrange_projective, |g, _| { - for g in g.iter_mut() { - *g *= minv; - } - }); - - let g_lagrange = { - let mut g_lagrange = vec![C::zero(); n as usize]; - parallelize(&mut g_lagrange, |g_lagrange, starts| { - C::Projective::batch_to_affine( - &g_lagrange_projective[starts..(starts + g_lagrange.len())], - g_lagrange, - ); - }); - drop(g_lagrange_projective); - g_lagrange - }; - - let h = { - let mut hasher = H::init(C::Base::zero()); - hasher.absorb(-C::Base::one()); - let x = hasher.squeeze().to_bytes(); - let p = C::from_bytes(&x); - p.unwrap() - }; - - Params { - k, - n, - g, - g_lagrange, - h, - } - } - - /// This computes a commitment to a polynomial described by the provided - /// slice of coefficients. The commitment will be blinded by the blinding - /// factor `r`. - pub fn commit(&self, poly: &[C::Scalar], r: C::Scalar) -> C::Projective { - let mut tmp_scalars = Vec::with_capacity(poly.len() + 1); - let mut tmp_bases = Vec::with_capacity(poly.len() + 1); - - tmp_scalars.extend(poly.iter()); - tmp_scalars.push(r); - - tmp_bases.extend(self.g.iter()); - tmp_bases.push(self.h); - - best_multiexp::(&tmp_scalars, &tmp_bases) - } - - /// This commits to a polynomial using its evaluations over the $2^k$ size - /// evaluation domain. The commitment will be blinded by the blinding factor - /// `r`. - pub fn commit_lagrange(&self, poly: &[C::Scalar], r: C::Scalar) -> C::Projective { - let mut tmp_scalars = Vec::with_capacity(poly.len() + 1); - let mut tmp_bases = Vec::with_capacity(poly.len() + 1); - - tmp_scalars.extend(poly.iter()); - tmp_scalars.push(r); - - tmp_bases.extend(self.g_lagrange.iter()); - tmp_bases.push(self.h); - - best_multiexp::(&tmp_scalars, &tmp_bases) - } - - /// Create a polynomial commitment opening proof for the polynomial defined - /// by the coefficients `px`, the blinding factor `blind` used for the - /// polynomial commitment, and the point `x` that the polynomial is - /// evaluated at. - /// - /// This function will panic if the provided polynomial is too large with - /// respect to the polynomial commitment parameters. - /// - /// **Important:** This function assumes that the provided `transcript` has - /// already seen the common inputs: the polynomial commitment P, the claimed - /// opening v, and the point x. It's probably also nice for the transcript - /// to have seen the elliptic curve description and the SRS, if you want to - /// be rigorous. - pub fn create_proof>( - &self, - transcript: &mut H, - px: &[C::Scalar], - mut blind: C::Scalar, - x: C::Scalar, - ) -> Result, ()> { - // We're limited to polynomials of degree n - 1. - assert!(px.len() <= self.n as usize); - - let mut fork = 0; - - // TODO: remove this hack and force the caller to deal with it - loop { - let mut transcript = transcript.clone(); - transcript.absorb(C::Base::from_u64(fork as u64)); - let u_x = transcript.squeeze(); - // y^2 = x^3 + B - let u_y2 = u_x.square() * &u_x + &C::b(); - let u_y = u_y2.deterministic_sqrt(); - - if u_y.is_none() { - fork += 1; - } else { - break; - } - } - - transcript.absorb(C::Base::from_u64(fork as u64)); - - // Compute U - let u = { - let u_x = transcript.squeeze(); - // y^2 = x^3 + B - let u_y2 = u_x.square() * &u_x + &C::b(); - let u_y = u_y2.deterministic_sqrt().unwrap(); - - C::from_xy(u_x, u_y).unwrap() - }; - - // Initialize the vector `a` as the coefficients of the polynomial, - // rounding up to the parameters. - let mut a = px.to_vec(); - a.resize(self.n as usize, C::Scalar::zero()); - - // Initialize the vector `b` as the powers of `x`. The inner product of - // `a` and `b` is the evaluation of the polynomial at `x`. - let mut b = Vec::with_capacity(1 << self.k); - { - let mut cur = C::Scalar::one(); - for _ in 0..(1 << self.k) { - b.push(cur); - cur *= &x; - } - } - - // Initialize the vector `G` from the SRS. We'll be progressively - // collapsing this vector into smaller and smaller vectors until it is - // of length 1. - let mut g = self.g.clone(); - - // Perform the inner product argument, round by round. - let mut rounds = Vec::with_capacity(self.k as usize); - for k in (1..=self.k).rev() { - let half = 1 << (k - 1); // half the length of `a`, `b`, `G` - - // Compute L, R - // - // TODO: If we modify multiexp to take "extra" bases, we could speed - // this piece up a bit by combining the multiexps. - let l = best_multiexp(&a[0..half], &g[half..]); - let r = best_multiexp(&a[half..], &g[0..half]); - let value_l = compute_inner_product(&a[0..half], &b[half..]); - let value_r = compute_inner_product(&a[half..], &b[0..half]); - let mut l_randomness = C::Scalar::random(); - let r_randomness = C::Scalar::random(); - let l = l + &best_multiexp(&[value_l, l_randomness], &[u, self.h]); - let r = r + &best_multiexp(&[value_r, r_randomness], &[u, self.h]); - let mut l = l.to_affine(); - let r = r.to_affine(); - - let challenge = loop { - // We'll fork the transcript and adjust our randomness - // until the challenge is a square. - let mut transcript = transcript.clone(); - - // We expect these to not be points at infinity due to the randomness. - let (l_x, l_y) = l.get_xy().unwrap(); - let (r_x, r_y) = r.get_xy().unwrap(); - - // Feed L and R into the cloned transcript... - transcript.absorb(l_x); - transcript.absorb(l_y); - transcript.absorb(r_x); - transcript.absorb(r_y); - - // ... and get the squared challenge. - let challenge_sq_packed = transcript.squeeze().get_lower_128(); - let challenge_sq: C::Scalar = get_challenge_scalar(Challenge(challenge_sq_packed)); - - // There might be no square root, in which case we'll fork the - // transcript. - let challenge = challenge_sq.deterministic_sqrt(); - if let Some(challenge) = challenge { - break challenge; - } else { - // Try again, with slightly different randomness - l = (l + self.h).to_affine(); - l_randomness += &C::Scalar::one(); - } - }; - - // Challenge is unlikely to be zero. - let challenge_inv = challenge.invert().unwrap(); - let challenge_sq_inv = challenge_inv.square(); - let challenge_sq = challenge.square(); - - // Feed L and R into the real transcript - let (l_x, l_y) = l.get_xy().unwrap(); - let (r_x, r_y) = r.get_xy().unwrap(); - transcript.absorb(l_x); - transcript.absorb(l_y); - transcript.absorb(r_x); - transcript.absorb(r_y); - - // And obtain the challenge, even though we already have it, since - // squeezing affects the transcript. - { - let challenge_sq_packed = transcript.squeeze().get_lower_128(); - let challenge_sq_expected = get_challenge_scalar(Challenge(challenge_sq_packed)); - assert_eq!(challenge_sq, challenge_sq_expected); - } - - // Done with this round. - rounds.push((l, r)); - - // Collapse `a` and `b`. - // TODO: parallelize - for i in 0..half { - a[i] = (a[i] * &challenge) + &(a[i + half] * &challenge_inv); - b[i] = (b[i] * &challenge_inv) + &(b[i + half] * &challenge); - } - a.truncate(half); - b.truncate(half); - - // Collapse `G` - parallel_generator_collapse(&mut g, challenge, challenge_inv); - g.truncate(half); - - // Update randomness (the synthetic blinding factor at the end) - blind += &(l_randomness * &challenge_sq); - blind += &(r_randomness * &challenge_sq_inv); - } - - // We have fully collapsed `a`, `b`, `G` - assert_eq!(a.len(), 1); - let a = a[0]; - assert_eq!(b.len(), 1); - let b = b[0]; - assert_eq!(g.len(), 1); - let g = g[0]; - - // Random nonces for the zero-knowledge opening - let d = C::Scalar::random(); - let s = C::Scalar::random(); - - let delta = best_multiexp(&[d, d * &b, s], &[g, u, self.h]).to_affine(); - - let (delta_x, delta_y) = delta.get_xy().unwrap(); - - // Feed delta into the transcript - transcript.absorb(delta_x); - transcript.absorb(delta_y); - - // Obtain the challenge c. - let c_packed = transcript.squeeze().get_lower_128(); - let c: C::Scalar = get_challenge_scalar(Challenge(c_packed)); - - // Compute z1 and z2 as described in the Halo paper. - let z1 = a * &c + &d; - let z2 = c * &blind + &s; - - Ok(OpeningProof { - fork, - rounds, - delta, - z1, - z2, - }) - } - - /// Checks to see if an [`OpeningProof`] is valid given the current - /// `transcript`, and a point `x` that the polynomial commitment `p` opens - /// purportedly to the value `v`. - pub fn verify_proof>( - &self, - proof: &OpeningProof, - transcript: &mut H, - x: C::Scalar, - p: &C, - v: C::Scalar, - ) -> bool { - // Check for well-formedness - if proof.rounds.len() != self.k as usize { - return false; - } - - transcript.absorb(C::Base::from_u64(proof.fork as u64)); - - // Compute U - let u = { - let u_x = transcript.squeeze(); - // y^2 = x^3 + B - let u_y2 = u_x.square() * &u_x + &C::b(); - let u_y = u_y2.deterministic_sqrt(); - if u_y.is_none() { - return false; - } - let u_y = u_y.unwrap(); - - C::from_xy(u_x, u_y).unwrap() - }; - - let mut extra_scalars = Vec::with_capacity(proof.rounds.len() * 2 + 4 + self.n as usize); - let mut extra_bases = Vec::with_capacity(proof.rounds.len() * 2 + 4 + self.n as usize); - - // Data about the challenges from each of the rounds. - let mut challenges = Vec::with_capacity(proof.rounds.len()); - let mut challenges_inv = Vec::with_capacity(proof.rounds.len()); - let mut challenges_sq = Vec::with_capacity(proof.rounds.len()); - let mut allinv = Field::one(); - - for round in &proof.rounds { - // Feed L and R into the transcript. - let l = round.0.get_xy(); - let r = round.1.get_xy(); - if bool::from(l.is_none() | r.is_none()) { - return false; - } - let l = l.unwrap(); - let r = r.unwrap(); - transcript.absorb(l.0); - transcript.absorb(l.1); - transcript.absorb(r.0); - transcript.absorb(r.1); - let challenge_sq_packed = transcript.squeeze().get_lower_128(); - let challenge_sq: C::Scalar = get_challenge_scalar(Challenge(challenge_sq_packed)); - - let challenge = challenge_sq.deterministic_sqrt(); - if challenge.is_none() { - // We didn't sample a square. - return false; - } - let challenge = challenge.unwrap(); - - let challenge_inv = challenge.invert(); - if bool::from(challenge_inv.is_none()) { - // We sampled zero for some reason, unlikely to happen by - // chance. - return false; - } - let challenge_inv = challenge_inv.unwrap(); - allinv *= challenge_inv; - - let challenge_sq_inv = challenge_inv.square(); - - extra_scalars.push(challenge_sq); - extra_bases.push(round.0); - extra_scalars.push(challenge_sq_inv); - extra_bases.push(round.1); - - challenges.push(challenge); - challenges_inv.push(challenge_inv); - challenges_sq.push(challenge_sq); - } - - let delta = proof.delta.get_xy(); - if bool::from(delta.is_none()) { - return false; - } - let delta = delta.unwrap(); - - // Feed delta into the transcript - transcript.absorb(delta.0); - transcript.absorb(delta.1); - - // Get the challenge `c` - let c_packed = transcript.squeeze().get_lower_128(); - let c: C::Scalar = get_challenge_scalar(Challenge(c_packed)); - - // Check - // [c] P + [c * v] U + [c] sum(L_i * u_i^2) + [c] sum(R_i * u_i^-2) + delta - [z1] G - [z1 * b] U - [z2] H - // = 0 - - for scalar in &mut extra_scalars { - *scalar *= &c; - } - - let b = compute_b(x, &challenges, &challenges_inv); - - let neg_z1 = -proof.z1; - - // [c] P - extra_bases.push(*p); - extra_scalars.push(c); - - // [c * v] U - [z1 * b] U - extra_bases.push(u); - extra_scalars.push((c * &v) + &(neg_z1 * &b)); - - // delta - extra_bases.push(proof.delta); - extra_scalars.push(Field::one()); - - // - [z2] H - extra_bases.push(self.h); - extra_scalars.push(-proof.z2); - - // - [z1] G - extra_bases.extend(&self.g); - let mut s = compute_s(&challenges_sq, allinv); - // TODO: parallelize - for s in &mut s { - *s *= &neg_z1; - } - extra_scalars.extend(s); - - bool::from(best_multiexp(&extra_scalars, &extra_bases).is_zero()) - } -} - -fn compute_b(x: F, challenges: &[F], challenges_inv: &[F]) -> F { - assert!(!challenges.is_empty()); - assert_eq!(challenges.len(), challenges_inv.len()); - if challenges.len() == 1 { - *challenges_inv.last().unwrap() + *challenges.last().unwrap() * x - } else { - (*challenges_inv.last().unwrap() + *challenges.last().unwrap() * x) - * compute_b( - x.square(), - &challenges[0..(challenges.len() - 1)], - &challenges_inv[0..(challenges.len() - 1)], - ) - } -} - -// TODO: parallelize -fn compute_s(challenges_sq: &[F], allinv: F) -> Vec { - let lg_n = challenges_sq.len(); - let n = 1 << lg_n; - - let mut s = Vec::with_capacity(n); - s.push(allinv); - for i in 1..n { - let lg_i = (32 - 1 - (i as u32).leading_zeros()) as usize; - let k = 1 << lg_i; - let u_lg_i_sq = challenges_sq[(lg_n - 1) - lg_i]; - s.push(s[i - k] * u_lg_i_sq); - } - - s -} - -fn parallel_generator_collapse( - g: &mut [C], - challenge: C::Scalar, - challenge_inv: C::Scalar, -) { - let len = g.len() / 2; - let (mut g_lo, g_hi) = g.split_at_mut(len); - - parallelize(&mut g_lo, |g_lo, start| { - let g_hi = &g_hi[start..]; - let mut tmp = Vec::with_capacity(g_lo.len()); - for (g_lo, g_hi) in g_lo.iter().zip(g_hi.iter()) { - // TODO: could use multiexp - tmp.push(((*g_lo) * challenge_inv) + &((*g_hi) * challenge)); - } - C::Projective::batch_to_affine(&tmp, g_lo); - }); -} - -#[test] -fn test_commit_lagrange() { - const K: u32 = 6; - - use crate::arithmetic::{EpAffine, Fp, Fq}; - use crate::transcript::DummyHash; - let params = Params::::new::>(K); - - let a = (0..(1 << K)).map(|l| Fq::from(l)).collect::>(); - let mut b = a.clone(); - let mut alpha = Fq::ROOT_OF_UNITY; - for _ in K..Fq::S { - alpha = alpha.square(); - } - best_fft(&mut b, alpha, K); - - assert_eq!(params.commit(&a, alpha), params.commit_lagrange(&b, alpha)); -} - -#[test] -fn test_opening_proof() { - const K: u32 = 6; - - use crate::arithmetic::{eval_polynomial, EpAffine, Fp, Fq}; - use crate::transcript::DummyHash; - let params = Params::::new::>(K); - - let px = (0..(1 << K)) - .map(|l| Fq::from(l + 1) * Fq::ZETA) - .collect::>(); - let blind = Fq::random(); - - let p = params.commit(&px, blind).to_affine(); - - let mut transcript = DummyHash::init(Field::one()); - let (p_x, p_y) = p.get_xy().unwrap(); - transcript.absorb(p_x); - transcript.absorb(p_y); - let x_packed = transcript.squeeze().get_lower_128(); - let x: Fq = get_challenge_scalar(Challenge(x_packed)); - - // Evaluate the polynomial - let v = eval_polynomial(&px, x); - - transcript.absorb(Fp::from_bytes(&v.to_bytes()).unwrap()); // unlikely to fail since p ~ q - - loop { - let mut transcript_dup = transcript.clone(); - - let opening_proof = params.create_proof(&mut transcript, &px, blind, x); - if opening_proof.is_err() { - transcript = transcript_dup; - transcript.absorb(Field::one()); - } else { - let opening_proof = opening_proof.unwrap(); - assert!(params.verify_proof(&opening_proof, &mut transcript_dup, x, &p, v)); - break; - } - } -} From 4572e809d138553dd9972d2f74845ac5baa98bd7 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 7 Sep 2020 13:09:25 -0600 Subject: [PATCH 2/3] cargo fmt --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 6533fad5..ede9adc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,6 @@ #![deny(unsafe_code)] pub mod arithmetic; +pub mod plonk; pub mod poly; pub mod transcript; -pub mod plonk; From 549232234f9a4ce1798234d31a9dd5565da0bb42 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 7 Sep 2020 16:34:40 -0600 Subject: [PATCH 3/3] Finish comment on Proof::verify. --- src/plonk/verifier.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 13796563..9c3adcac 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -4,7 +4,7 @@ use crate::poly::{commitment::Params, Rotation}; use crate::transcript::Hasher; impl Proof { - /// Returns + /// Returns a boolean indicating whether or not the proof is valid pub fn verify, HScalar: Hasher>( &self, params: &Params,