diff --git a/src/arithmetic.rs b/src/arithmetic.rs index 312cfe57..e74cfee4 100644 --- a/src/arithmetic.rs +++ b/src/arithmetic.rs @@ -70,6 +70,10 @@ where } /// This is a 128-bit verifier challenge. +/// +/// The verifier samples its challenge here as u^2, i.e. the square of the +/// actual challenge. This is an optimisation that is documented in Section 6.3 +/// of the [Halo](https://eprint.iacr.org/2019/1021) paper. #[derive(Copy, Clone, Debug)] pub struct Challenge(pub(crate) u128); diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 9c3adcac..6250397b 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -1,6 +1,9 @@ use super::{hash_point, Proof, SRS}; use crate::arithmetic::{get_challenge_scalar, Challenge, Curve, CurveAffine, Field}; -use crate::poly::{commitment::Params, Rotation}; +use crate::poly::{ + commitment::{Params, MSM}, + Rotation, +}; use crate::transcript::Hasher; impl Proof { @@ -261,12 +264,20 @@ impl Proof { } // Verify the opening proof - self.opening.verify( - params, - &mut transcript, - x_6, - &f_commitment.to_affine(), - f_eval, - ) + let (challenges, mut guard) = self + .opening + .verify( + params, + &mut MSM::default(¶ms), + &mut transcript, + x_6, + &f_commitment.to_affine(), + f_eval, + ) + .unwrap(); + + let msm: MSM = guard.use_challenges(challenges).unwrap(); + + msm.is_zero() } } diff --git a/src/poly.rs b/src/poly.rs index eef53565..07f32e34 100644 --- a/src/poly.rs +++ b/src/poly.rs @@ -13,6 +13,14 @@ mod domain; pub use domain::*; +/// This is an error that could occur during proving or circuit synthesis. +// TODO: these errors need to be cleaned up +#[derive(Debug)] +pub enum Error { + /// OpeningProof is not well-formed + OpeningError, +} + /// The basis over which a polynomial is described. pub trait Basis: Clone + Debug + Send + Sync {} diff --git a/src/poly/commitment.rs b/src/poly/commitment.rs index 1a985484..e9189d04 100644 --- a/src/poly/commitment.rs +++ b/src/poly/commitment.rs @@ -3,8 +3,11 @@ //! //! [halo]: https://eprint.iacr.org/2019/1021 -use super::{Coeff, LagrangeCoeff, Polynomial}; -use crate::arithmetic::{best_fft, best_multiexp, parallelize, Curve, CurveAffine, Field}; +use super::{Coeff, Error, LagrangeCoeff, Polynomial}; +use crate::arithmetic::{ + best_fft, best_multiexp, get_challenge_scalar, parallelize, Challenge, Curve, CurveAffine, + Field, +}; use crate::transcript::Hasher; use std::ops::{Add, AddAssign, Mul, MulAssign}; @@ -21,6 +24,49 @@ pub struct OpeningProof { z2: C::Scalar, } +/// A multiscalar multiplication in the polynomial commitment scheme +#[derive(Debug)] +pub struct MSM { + /// Scalars in the multiscalar multiplication + pub scalars: Vec, + + /// Points in the multiscalar multiplication + pub bases: Vec, +} + +impl<'a, C: CurveAffine> MSM { + /// Empty MSM + pub fn default(params: &'a Params) -> Self { + let scalars: Vec = + Vec::with_capacity(params.k as usize * 2 + 4 + params.n as usize); + let bases: Vec = Vec::with_capacity(params.k as usize * 2 + 4 + params.n as usize); + + MSM { scalars, bases } + } + + /// Add arbitrary term (the scalar and the point) + pub fn add_term(&mut self, scalar: C::Scalar, point: C) { + &self.scalars.push(scalar); + &self.bases.push(point); + } + /// Add term to g + pub fn mutate_g(&mut self, scalar: C::Scalar, point: C) -> Self { + unimplemented!() + } + /// Add term to h + pub fn mutate_h(&mut self, scalar: C::Scalar, point: C) -> Self { + unimplemented!() + } + /// Scale by a random blinding factor + pub fn scale(&self, scalar: C::Scalar) -> Self { + unimplemented!() + } + /// Perform multiexp and check that it results in zero + pub fn is_zero(&self) -> bool { + bool::from(best_multiexp(&self.scalars, &self.bases).is_zero()) + } +} + /// These are the public parameters for the polynomial commitment scheme. #[derive(Debug)] pub struct Params { @@ -154,6 +200,85 @@ impl Params { } } +/// A guard returned by the verifier +#[derive(Debug)] +pub struct Guard<'a, C: CurveAffine> { + /// Negation of z1 value in the OpeningProof + pub neg_z1: C::Scalar, + + /// Params that were used by the verifier + pub params: &'a Params, + + /// Scalars produced by the verifier for multiscalar multiplication + pub scalars: Vec, + + /// Points produced by the verifier for multiscalar multiplication + pub bases: Vec, +} + +impl<'a, C: CurveAffine> Guard<'a, C> { + /// Lets caller supply the challenges and obtain an MSM with updated + /// scalars and points. + pub fn use_challenges( + &mut self, + challenges_sq_packed: Vec, + ) -> Result, Error> { + // - [z1] G + let mut allinv = C::Scalar::one(); + let mut challenges_sq = Vec::with_capacity(self.params.k as usize); + + for challenge_sq_packed in challenges_sq_packed { + let challenge_sq: C::Scalar = get_challenge_scalar(challenge_sq_packed); + challenges_sq.push(challenge_sq); + + let challenge = challenge_sq.deterministic_sqrt(); + if challenge.is_none() { + // We didn't sample a square. + return Err(Error::OpeningError); + } + 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 Err(Error::OpeningError); + } + let challenge_inv = challenge_inv.unwrap(); + allinv *= &challenge_inv; + } + + self.bases.extend(&self.params.g); + let mut s = compute_s(&challenges_sq, allinv); + // TODO: parallelize + for s in &mut s { + *s *= &self.neg_z1; + } + self.scalars.extend(s); + + Ok(MSM { + scalars: self.scalars.clone(), + bases: self.bases.clone(), + }) + } + + /// Lets caller supply the purported G point and simply appends it to + /// return an updated MSM. + pub fn use_s(&mut self, mut s: Vec) -> Result, Error> { + // - [z1] G + self.bases.extend(&self.params.g); + for s in &mut s { + *s *= &self.neg_z1; + } + self.scalars.extend(s); + + Ok(MSM { + scalars: self.scalars.clone(), + bases: self.bases.clone(), + }) + } +} + /// Wrapper type around a blinding factor. #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub struct Blind(pub F); @@ -273,8 +398,39 @@ fn test_opening_proof() { transcript.absorb(Field::one()); } else { let opening_proof = opening_proof.unwrap(); - assert!(opening_proof.verify(¶ms, &mut transcript_dup, x, &p, v)); + // Verify the opening proof + let (challenges, mut guard) = opening_proof + .verify( + ¶ms, + &mut MSM::default(¶ms), + &mut transcript_dup, + x, + &p, + v, + ) + .unwrap(); + + let msm = guard.use_challenges(challenges).unwrap(); + + assert!(msm.is_zero()); break; } } } + +// 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/poly/commitment/verifier.rs b/src/poly/commitment/verifier.rs index a3d7e4da..9351c92b 100644 --- a/src/poly/commitment/verifier.rs +++ b/src/poly/commitment/verifier.rs @@ -1,25 +1,25 @@ -use super::{OpeningProof, Params}; +use super::super::Error; +use super::{Guard, OpeningProof, Params, MSM}; use crate::transcript::Hasher; -use crate::arithmetic::{ - best_multiexp, get_challenge_scalar, Challenge, Curve, CurveAffine, Field, -}; +use crate::arithmetic::{get_challenge_scalar, Challenge, 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>( + pub fn verify<'a, H: Hasher>( &self, - params: &Params, + params: &'a Params, + msm: &mut MSM, transcript: &mut H, x: C::Scalar, p: &C, v: C::Scalar, - ) -> bool { + ) -> Result<(Vec, Guard<'a, C>), Error> { // Check for well-formedness if self.rounds.len() != params.k as usize { - return false; + return Err(Error::OpeningError); } transcript.absorb(C::Base::from_u64(self.fork as u64)); @@ -31,28 +31,25 @@ impl OpeningProof { let u_y2 = u_x.square() * &u_x + &C::b(); let u_y = u_y2.deterministic_sqrt(); if u_y.is_none() { - return false; + return Err(Error::OpeningError); } 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(); + let mut challenges_sq_packed: Vec = Vec::with_capacity(self.rounds.len()); 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; + return Err(Error::OpeningError); } let l = l.unwrap(); let r = r.unwrap(); @@ -66,7 +63,7 @@ impl OpeningProof { let challenge = challenge_sq.deterministic_sqrt(); if challenge.is_none() { // We didn't sample a square. - return false; + return Err(Error::OpeningError); } let challenge = challenge.unwrap(); @@ -74,26 +71,26 @@ impl OpeningProof { if bool::from(challenge_inv.is_none()) { // We sampled zero for some reason, unlikely to happen by // chance. - return false; + return Err(Error::OpeningError); } 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); + msm.scalars.push(challenge_sq); + msm.bases.push(round.0); + msm.scalars.push(challenge_sq_inv); + msm.bases.push(round.1); challenges.push(challenge); challenges_inv.push(challenge_inv); challenges_sq.push(challenge_sq); + challenges_sq_packed.push(Challenge(challenge_sq_packed)); } let delta = self.delta.get_xy(); if bool::from(delta.is_none()) { - return false; + return Err(Error::OpeningError); } let delta = delta.unwrap(); @@ -109,7 +106,7 @@ impl OpeningProof { // [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 { + for scalar in &mut msm.scalars { *scalar *= &c; } @@ -118,31 +115,29 @@ impl OpeningProof { let neg_z1 = -self.z1; // [c] P - extra_bases.push(*p); - extra_scalars.push(c); + msm.bases.push(*p); + msm.scalars.push(c); // [c * v] U - [z1 * b] U - extra_bases.push(u); - extra_scalars.push((c * &v) + &(neg_z1 * &b)); + msm.bases.push(u); + msm.scalars.push((c * &v) + &(neg_z1 * &b)); // delta - extra_bases.push(self.delta); - extra_scalars.push(Field::one()); + msm.bases.push(self.delta); + msm.scalars.push(Field::one()); // - [z2] H - extra_bases.push(params.h); - extra_scalars.push(-self.z2); + msm.bases.push(params.h); + msm.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); + let guard = Guard::<'a, _> { + neg_z1, + params, + scalars: msm.scalars.clone(), + bases: msm.bases.clone(), + }; - bool::from(best_multiexp(&extra_scalars, &extra_bases).is_zero()) + Ok((challenges_sq_packed, guard)) } } @@ -160,20 +155,3 @@ fn compute_b(x: F, challenges: &[F], challenges_inv: &[F]) -> F { ) } } - -// 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 -}