diff --git a/src/plonk.rs b/src/plonk.rs index b0dc869..e62409f 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -345,6 +345,10 @@ fn test_proving() { let proof = Proof::create::, DummyHash, _>(¶ms, &srs, &circuit) .expect("proof generation should not fail"); - assert!(proof.verify::, DummyHash>(¶ms, &srs)); + let msm_default = params.empty_msm(); + let msm = proof + .verify::, DummyHash>(¶ms, &srs, msm_default) + .unwrap(); + assert!(msm.is_zero()) } } diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 9c3adca..5197acd 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -1,15 +1,19 @@ -use super::{hash_point, Proof, SRS}; +use super::{hash_point, Error, 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 { +impl<'a, C: CurveAffine> Proof { /// Returns a boolean indicating whether or not the proof is valid pub fn verify, HScalar: Hasher>( &self, - params: &Params, + params: &'a Params, srs: &SRS, - ) -> bool { + msm: MSM<'a, C>, + ) -> Result, Error> { // Create a transcript for obtaining Fiat-Shamir challenges. let mut transcript = HBase::init(C::Base::one()); @@ -133,7 +137,7 @@ impl Proof { } if h_eval != (expected_h_eval * &(x_3n - &C::Scalar::one())) { - return false; + return Err(Error::ConstraintSystemFailure); } // We are now convinced the circuit is satisfied so long as the @@ -261,12 +265,20 @@ impl Proof { } // Verify the opening proof - self.opening.verify( - params, - &mut transcript, - x_6, - &f_commitment.to_affine(), - f_eval, - ) + let guard = self + .opening + .verify( + params, + msm, + &mut transcript, + x_6, + &f_commitment.to_affine(), + f_eval, + ) + .unwrap(); + + let msm_challenges = guard.use_challenges(); + + Ok(msm_challenges) } } diff --git a/src/poly.rs b/src/poly.rs index eef5356..07f32e3 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 1a98548..eb24af9 100644 --- a/src/poly/commitment.rs +++ b/src/poly/commitment.rs @@ -4,7 +4,9 @@ //! [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::arithmetic::{ + best_fft, best_multiexp, parallelize, Challenge, Curve, CurveAffine, Field, +}; use crate::transcript::Hasher; use std::ops::{Add, AddAssign, Mul, MulAssign}; @@ -21,6 +23,96 @@ pub struct OpeningProof { z2: C::Scalar, } +/// An accumulator instance consisting of an evaluation claim and a proof. +#[derive(Debug, Clone)] +pub struct Accumulator { + /// The claimed output of the linear-time polycommit opening protocol + pub g: C, + + /// A vector of 128-bit challenges sampled by the verifier, to be used in + /// computing g. + pub challenges_sq_packed: Vec, +} + +/// A multiscalar multiplication in the polynomial commitment scheme +#[derive(Debug, Clone)] +pub struct MSM<'a, C: CurveAffine> { + params: &'a Params, + g_scalars: Option>, + h_scalar: Option, + other_scalars: Vec, + other_bases: Vec, +} + +impl<'a, C: CurveAffine> MSM<'a, C> { + /// Add arbitrary term (the scalar and the point) + pub fn add_term(&mut self, scalar: C::Scalar, point: C) { + &self.other_scalars.push(scalar); + &self.other_bases.push(point); + } + + /// Add a vector of scalars to `g_scalars`. This function will panic if the + /// caller provides a slice of scalars that is not of length `params.n`. + // TODO: parallelize + pub fn add_to_g(&mut self, scalars: &[C::Scalar]) { + assert_eq!(scalars.len(), self.params.n as usize); + if let Some(g_scalars) = &mut self.g_scalars { + for (g_scalar, scalar) in g_scalars.iter_mut().zip(scalars.iter()) { + *g_scalar += &scalar; + } + } else { + self.g_scalars = Some(scalars.to_vec()); + } + } + + /// Add term to h + pub fn add_to_h(&mut self, scalar: C::Scalar) { + self.h_scalar = self.h_scalar.map_or(Some(scalar), |a| Some(a + &scalar)); + } + + /// Scale all scalars in the MSM by some scaling factor + // TODO: parallelize + pub fn scale(&mut self, factor: C::Scalar) { + if let Some(g_scalars) = &mut self.g_scalars { + for g_scalar in g_scalars.iter_mut() { + *g_scalar *= &factor; + } + } + + // TODO: parallelize + for other_scalar in self.other_scalars.iter_mut() { + *other_scalar *= &factor; + } + self.h_scalar = self.h_scalar.map(|a| a * &factor); + } + + /// Perform multiexp and check that it results in zero + pub fn is_zero(self) -> bool { + let len = self.g_scalars.as_ref().map(|v| v.len()).unwrap_or(0) + + self.h_scalar.map(|_| 1).unwrap_or(0) + + self.other_scalars.len(); + let mut scalars: Vec = Vec::with_capacity(len); + let mut bases: Vec = Vec::with_capacity(len); + + scalars.extend(&self.other_scalars); + bases.extend(&self.other_bases); + + if let Some(h_scalar) = self.h_scalar { + scalars.push(h_scalar); + bases.push(self.params.h); + } + + if let Some(g_scalars) = &self.g_scalars { + scalars.extend(g_scalars); + bases.extend(self.params.g.iter()); + } + + assert_eq!(scalars.len(), len); + + bool::from(best_multiexp(&scalars, &bases).is_zero()) + } +} + /// These are the public parameters for the polynomial commitment scheme. #[derive(Debug)] pub struct Params { @@ -152,6 +244,63 @@ impl Params { best_multiexp::(&tmp_scalars, &tmp_bases) } + + /// Generates an empty multiscalar multiplication struct using the + /// appropriate params. + pub fn empty_msm(&self) -> MSM { + let g_scalars = None; + let h_scalar = None; + let other_scalars = vec![]; + let other_bases = vec![]; + + MSM { + params: &self, + g_scalars, + h_scalar, + other_scalars, + other_bases, + } + } +} + +/// A guard returned by the verifier +#[derive(Debug, Clone)] +pub struct Guard<'a, C: CurveAffine> { + msm: MSM<'a, C>, + neg_z1: C::Scalar, + allinv: C::Scalar, + challenges_sq: Vec, + challenges_sq_packed: 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) -> MSM<'a, C> { + let s = compute_s(&self.challenges_sq, self.allinv * &self.neg_z1); + self.msm.add_to_g(&s); + + self.msm + } + + /// Lets caller supply the purported G point and simply appends it to + /// return an updated MSM. + pub fn use_g(mut self, g: C) -> (MSM<'a, C>, Accumulator) { + &self.msm.add_term(self.neg_z1, g); + + let accumulator = Accumulator { + g, + challenges_sq_packed: self.challenges_sq_packed, + }; + + (self.msm, accumulator) + } + + /// Computes the g value when given a potential scalar as input. + pub fn compute_g(&self) -> C { + let s = compute_s(&self.challenges_sq, self.allinv); + best_multiexp(&s, &self.msm.params.g).to_affine() + } } /// Wrapper type around a blinding factor. @@ -265,7 +414,7 @@ fn test_opening_proof() { transcript.absorb(Fp::from_bytes(&v.to_bytes()).unwrap()); // unlikely to fail since p ~ q loop { - let mut transcript_dup = transcript.clone(); + let transcript_dup = transcript.clone(); let opening_proof = OpeningProof::create(¶ms, &mut transcript, &px, blind, x); if opening_proof.is_err() { @@ -273,8 +422,63 @@ 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 guard = opening_proof + .verify( + ¶ms, + params.empty_msm(), + &mut transcript_dup.clone(), + x, + &p, + v, + ) + .unwrap(); + + // Test guard behavior prior to checking another proof + { + // Test use_challenges() + let msm_challenges = guard.clone().use_challenges(); + assert!(msm_challenges.is_zero()); + + // Test use_g() + let g = guard.compute_g(); + let (msm_g, _accumulator) = guard.clone().use_g(g); + assert!(msm_g.is_zero()); + } + + // Check another proof to populate `msm.g_scalars` + let msm = guard.use_challenges(); + let guard = opening_proof + .verify(¶ms, msm, &mut transcript_dup.clone(), x, &p, v) + .unwrap(); + + // Test use_challenges() + let msm_challenges = guard.clone().use_challenges(); + assert!(msm_challenges.is_zero()); + + // Test use_g() + let g = guard.compute_g(); + let (msm_g, _accumulator) = guard.clone().use_g(g); + assert!(msm_g.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 a3d7e4d..2e937f9 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, + mut msm: MSM<'a, C>, transcript: &mut H, x: C::Scalar, p: &C, v: C::Scalar, - ) -> bool { + ) -> Result, 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,7 +31,7 @@ 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(); @@ -45,14 +45,15 @@ impl OpeningProof { 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()); + let mut allinv = C::Scalar::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; + return Err(Error::OpeningError); } let l = l.unwrap(); let r = r.unwrap(); @@ -66,7 +67,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,10 +75,10 @@ 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; + allinv *= &challenge_inv; let challenge_sq_inv = challenge_inv.square(); @@ -89,11 +90,12 @@ impl OpeningProof { 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,40 +111,45 @@ 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 + // Scale the MSM by a random factor to ensure that if the existing MSM + // has is_zero() == false then this argument won't be able to interfere + // with it to make it true. It's a way of keeping the MSM's linearly + // independent. + msm.scale(C::Scalar::random()); + for scalar in &mut extra_scalars { *scalar *= &c; } + for (scalar, base) in extra_scalars.iter().zip(extra_bases.iter()) { + msm.add_term(*scalar, *base); + } + let b = compute_b(x, &challenges, &challenges_inv); let neg_z1 = -self.z1; // [c] P - extra_bases.push(*p); - extra_scalars.push(c); + msm.add_term(c, *p); // [c * v] U - [z1 * b] U - extra_bases.push(u); - extra_scalars.push((c * &v) + &(neg_z1 * &b)); + msm.add_term((c * &v) + &(neg_z1 * &b), u); // delta - extra_bases.push(self.delta); - extra_scalars.push(Field::one()); + msm.add_term(Field::one(), self.delta); // - [z2] H - extra_bases.push(params.h); - extra_scalars.push(-self.z2); + msm.add_to_h(-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 { + msm, + neg_z1, + allinv, + challenges_sq, + challenges_sq_packed, + }; - bool::from(best_multiexp(&extra_scalars, &extra_bases).is_zero()) + Ok(guard) } } @@ -160,20 +167,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 -}