From c3d0a172a7944b7cbb11c93702f946b5a49748b8 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 29 Sep 2020 15:23:41 +0800 Subject: [PATCH] Create multiopen abstraction --- src/plonk.rs | 9 +- src/plonk/prover.rs | 268 +++++++++++---------------------- src/plonk/verifier.rs | 234 +++++++++++++--------------- src/poly.rs | 1 + src/poly/multiopen.rs | 23 +++ src/poly/multiopen/prover.rs | 165 ++++++++++++++++++++ src/poly/multiopen/verifier.rs | 92 +++++++++++ 7 files changed, 476 insertions(+), 316 deletions(-) create mode 100644 src/poly/multiopen.rs create mode 100644 src/poly/multiopen/prover.rs create mode 100644 src/poly/multiopen/verifier.rs diff --git a/src/plonk.rs b/src/plonk.rs index 4e4de2e..45238c3 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -7,7 +7,7 @@ use crate::arithmetic::CurveAffine; use crate::poly::{ - commitment, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, + multiopen, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, }; use crate::transcript::Hasher; @@ -59,9 +59,7 @@ pub struct Proof { aux_evals: Vec, fixed_evals: Vec, h_evals: Vec, - f_commitment: C, - q_evals: Vec, - opening: commitment::Proof, + multiopening: multiopen::Proof, } /// This is an error that could occur during proving or circuit synthesis. @@ -96,7 +94,8 @@ impl VerifyingKey { } } -fn hash_point>( +/// Hash a point into transcript +pub fn hash_point>( transcript: &mut H, point: &C, ) -> Result<(), Error> { diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 0805238..90e2c22 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -3,12 +3,12 @@ use super::{ hash_point, Error, Proof, ProvingKey, }; use crate::arithmetic::{ - eval_polynomial, get_challenge_scalar, kate_division, parallelize, BatchInvert, Challenge, - Curve, CurveAffine, Field, + eval_polynomial, get_challenge_scalar, parallelize, BatchInvert, Challenge, Curve, CurveAffine, + Field, }; use crate::poly::{ - commitment::{self, Blind, Params}, - Coeff, LagrangeCoeff, Polynomial, Rotation, + commitment::{Blind, Params}, + multiopen, Coeff, LagrangeCoeff, Polynomial, Rotation, }; use crate::transcript::Hasher; @@ -457,194 +457,100 @@ impl Proof { C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); transcript.absorb(transcript_scalar_point); - let x_4: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + let mut instances: Vec<( + usize, + Polynomial, + Blind, + C::Scalar, + )> = Vec::with_capacity( + meta.advice_queries.len() + + meta.aux_queries.len() + + meta.fixed_queries.len() + + h_pieces.len() + + permutation_product_polys.len() + + permutation_product_polys.len() + + pk.permutation_polys.len(), + ); - // 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![Blind(C::Scalar::zero()); meta.rotations.len()]; - let mut q_evals: Vec<_> = vec![C::Scalar::zero(); meta.rotations.len()]; + for (query_index, &(wire, ref at)) in meta.advice_queries.iter().enumerate() { + let point_index = (*meta.rotations.get(at).unwrap()).0; + let poly = advice_polys[wire.0].clone(); + let blind = advice_blinds[wire.0]; + let eval = advice_evals[query_index]; + instances.push((point_index, poly, blind, eval)); + } + + for (query_index, &(wire, ref at)) in meta.aux_queries.iter().enumerate() { + let point_index = (*meta.rotations.get(at).unwrap()).0; + let poly = aux_polys[wire.0].clone(); + let blind = Blind::default(); + let eval = aux_evals[query_index]; + instances.push((point_index, poly, blind, eval)); + } + + for (query_index, &(wire, ref at)) in meta.fixed_queries.iter().enumerate() { + let point_index = (*meta.rotations.get(at).unwrap()).0; + let poly = pk.fixed_polys[wire.0].clone(); + let blind = Blind::default(); + let eval = fixed_evals[query_index]; + instances.push((point_index, poly, blind, eval)); + } + + // We query the h(X) polynomial at x_3 + let current_index = (*meta.rotations.get(&Rotation::default()).unwrap()).0; + for ((h_poly, h_blind), h_eval) in h_pieces + .into_iter() + .zip(h_blinds.iter()) + .zip(h_evals.iter()) { - 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(()) - }); - q_blinds[point_index] *= x_4; - q_blinds[point_index] += blind; - q_evals[point_index] *= &x_4; - q_evals[point_index] += &eval; - }; + instances.push((current_index, h_poly.clone(), *h_blind, *h_eval)); + } - for (query_index, &(wire, ref at)) in meta.advice_queries.iter().enumerate() { - let point_index = (*meta.rotations.get(at).unwrap()).0; - - accumulate( - point_index, - &advice_polys[wire.0], - advice_blinds[wire.0], - advice_evals[query_index], - ); - } - - for (query_index, &(wire, ref at)) in meta.aux_queries.iter().enumerate() { - let point_index = (*meta.rotations.get(at).unwrap()).0; - - accumulate( - point_index, - &aux_polys[wire.0], - Blind::default(), - aux_evals[query_index], - ); - } - - for (query_index, &(wire, ref at)) in meta.fixed_queries.iter().enumerate() { - let point_index = (*meta.rotations.get(at).unwrap()).0; - - accumulate( - point_index, - &pk.fixed_polys[wire.0], - Blind::default(), - fixed_evals[query_index], - ); - } - - // We query the h(X) polynomial at x_3 - let current_index = (*meta.rotations.get(&Rotation::default()).unwrap()).0; - for ((h_poly, h_blind), h_eval) in h_pieces - .into_iter() - .zip(h_blinds.iter()) - .zip(h_evals.iter()) + // Handle permutation arguments, if any exist + if !pk.vk.cs.permutations.is_empty() { + // Open permutation product commitments at x_3 + for ((poly, blind), eval) in permutation_product_polys + .iter() + .zip(permutation_product_blinds.iter()) + .zip(permutation_product_evals.iter()) { - accumulate(current_index, &h_poly, *h_blind, *h_eval); + instances.push((current_index, poly.clone(), *blind, *eval)); } - // Handle permutation arguments, if any exist - if !pk.vk.cs.permutations.is_empty() { - // Open permutation product commitments at x_3 - for ((poly, blind), eval) in permutation_product_polys - .iter() - .zip(permutation_product_blinds.iter()) - .zip(permutation_product_evals.iter()) - { - accumulate(current_index, poly, *blind, *eval); - } + // Open permutation polynomial commitments at x_3 + for (poly, eval) in pk + .permutation_polys + .iter() + .zip(permutation_evals.iter()) + .flat_map(|(polys, evals)| polys.iter().zip(evals.iter())) + { + instances.push((current_index, poly.clone(), Blind::default(), *eval)); + } - // Open permutation polynomial commitments at x_3 - for (poly, eval) in pk - .permutation_polys - .iter() - .zip(permutation_evals.iter()) - .flat_map(|(polys, evals)| polys.iter().zip(evals.iter())) - { - accumulate(current_index, poly, Blind::default(), *eval); - } - - let current_index = (*pk.vk.cs.rotations.get(&Rotation(-1)).unwrap()).0; - // Open permutation product commitments at \omega^{-1} x_3 - for ((poly, blind), eval) in permutation_product_polys - .iter() - .zip(permutation_product_blinds.iter()) - .zip(permutation_product_inv_evals.iter()) - { - accumulate(current_index, poly, *blind, *eval); - } + let current_index = (*pk.vk.cs.rotations.get(&Rotation(-1)).unwrap()).0; + // Open permutation product commitments at \omega^{-1} x_3 + for ((poly, blind), eval) in permutation_product_polys + .iter() + .zip(permutation_product_blinds.iter()) + .zip(permutation_product_inv_evals.iter()) + { + instances.push((current_index, poly.clone(), *blind, *eval)); } } - let x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - - let mut f_poly: Option> = None; + let mut points: Vec = vec![C::Scalar::zero(); meta.rotations.len()]; 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]; - // 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| { - parallelize(&mut f_poly, |q, start| { - for (q, a) in q.iter_mut().zip(poly[start..].iter()) { - *q *= &x_5; - *q += a; - } - }); - f_poly - }) - .or_else(|| Some(poly)); + points[point_index.0] = domain.rotate_omega(x_3, row); } - let f_poly = f_poly.unwrap(); - let mut f_blind = Blind(C::Scalar::random()); - let mut f_commitment = params.commit(&f_poly, f_blind).to_affine(); - - let (opening, q_evals) = loop { - let mut transcript = transcript.clone(); - let mut transcript_scalar = transcript_scalar.clone(); - hash_point(&mut transcript, &f_commitment)?; - - let x_6: C::Scalar = - get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - - let mut q_evals = vec![C::Scalar::zero(); meta.rotations.len()]; - - for (_, &point_index) in meta.rotations.iter() { - q_evals[point_index.0] = - eval_polynomial(&q_polys[point_index.0].as_ref().unwrap(), x_6); - } - - for eval in q_evals.iter() { - transcript_scalar.absorb(*eval); - } - - let transcript_scalar_point = - C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); - transcript.absorb(transcript_scalar_point); - - let x_7: C::Scalar = - get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - - let mut f_blind_dup = f_blind; - let mut f_poly = f_poly.clone(); - for (_, &point_index) in meta.rotations.iter() { - f_blind_dup *= x_7; - f_blind_dup += q_blinds[point_index.0]; - - parallelize(&mut f_poly, |f, start| { - for (f, a) in f - .iter_mut() - .zip(q_polys[point_index.0].as_ref().unwrap()[start..].iter()) - { - *f *= &x_7; - *f += a; - } - }); - } - - if let Ok(opening) = - commitment::Proof::create(¶ms, &mut transcript, &f_poly, f_blind_dup, x_6) - { - break (opening, q_evals); - } else { - f_blind += C::Scalar::one(); - f_commitment = (f_commitment + params.h).to_affine(); - } - }; + let multiopening = multiopen::Proof::create( + params, + &mut transcript, + &mut transcript_scalar, + points, + instances, + ) + .unwrap(); Ok(Proof { advice_commitments, @@ -657,9 +563,7 @@ impl Proof { fixed_evals, aux_evals, h_evals, - f_commitment, - q_evals, - opening, + multiopening, }) } } diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index e7866a3..f7adbc0 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -2,6 +2,7 @@ use super::{hash_point, Error, Proof, VerifyingKey}; use crate::arithmetic::{get_challenge_scalar, Challenge, CurveAffine, Field}; use crate::poly::{ commitment::{Guard, Params, MSM}, + multiopen::VerifierQuery, Rotation, }; use crate::transcript::Hasher; @@ -9,18 +10,21 @@ use crate::transcript::Hasher; impl<'a, C: CurveAffine> Proof { /// Returns a boolean indicating whether or not the proof is valid pub fn verify, HScalar: Hasher>( - &self, + &'a self, params: &'a Params, - vk: &VerifyingKey, - mut msm: MSM<'a, C>, - aux_commitments: &[C], + vk: &'a VerifyingKey, + msm: MSM<'a, C>, + aux_commitments: &'a [C], ) -> Result, Error> { self.check_lengths(vk, aux_commitments)?; - // 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, with high probability. - msm.scale(C::Scalar::random()); + // Check that aux_commitments matches the expected number of aux_wires + // and self.aux_evals + if aux_commitments.len() != vk.cs.num_aux_wires + || self.aux_evals.len() != vk.cs.num_aux_wires + { + return Err(Error::IncompatibleParams); + } // Create a transcript for obtaining Fiat-Shamir challenges. let mut transcript = HBase::init(C::Base::one()); @@ -83,134 +87,106 @@ impl<'a, C: CurveAffine> Proof { C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); transcript.absorb(transcript_scalar_point); - // Sample x_4 for compressing openings at the same points together - let x_4: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + let mut queries: Vec> = Vec::new(); - // Compress the commitments and expected evaluations at x_3 together - // using the challenge x_4 - let mut q_commitments: Vec<_> = vec![params.empty_msm(); vk.cs.rotations.len()]; - let mut q_evals: Vec<_> = vec![C::Scalar::zero(); vk.cs.rotations.len()]; + for (query_index, &(wire, at)) in vk.cs.advice_queries.iter().enumerate() { + let point = vk.domain.rotate_omega(x_3, at); + queries.push(VerifierQuery { + point, + commitment: &self.advice_commitments[wire.0], + eval: self.advice_evals[query_index], + }); + } + + for (query_index, &(wire, at)) in vk.cs.aux_queries.iter().enumerate() { + let point = vk.domain.rotate_omega(x_3, at); + queries.push(VerifierQuery { + point, + commitment: &aux_commitments[wire.0], + eval: self.aux_evals[query_index], + }); + } + + for (query_index, &(wire, at)) in vk.cs.fixed_queries.iter().enumerate() { + let point = vk.domain.rotate_omega(x_3, at); + queries.push(VerifierQuery { + point, + commitment: &vk.fixed_commitments[wire.0], + eval: self.fixed_evals[query_index], + }); + } + + for ((idx, _), &eval) in self + .h_commitments + .iter() + .enumerate() + .zip(self.h_evals.iter()) { - let mut accumulate = |point_index: usize, new_commitment, eval| { - q_commitments[point_index].scale(x_4); - q_commitments[point_index].add_term(C::Scalar::one(), new_commitment); - q_evals[point_index] *= &x_4; - q_evals[point_index] += &eval; - }; + let commitment = &self.h_commitments[idx]; + queries.push(VerifierQuery { + point: x_3, + commitment, + eval, + }); + } - for (query_index, &(wire, ref at)) in vk.cs.advice_queries.iter().enumerate() { - let point_index = (*vk.cs.rotations.get(at).unwrap()).0; - accumulate( - point_index, - self.advice_commitments[wire.0], - self.advice_evals[query_index], - ); + // Handle permutation arguments, if any exist + if !vk.cs.permutations.is_empty() { + // Open permutation product commitments at x_3 + for ((idx, _), &eval) in self + .permutation_product_commitments + .iter() + .enumerate() + .zip(self.permutation_product_evals.iter()) + { + let commitment = &self.permutation_product_commitments[idx]; + queries.push(VerifierQuery { + point: x_3, + commitment, + eval, + }); } - - for (query_index, &(wire, ref at)) in vk.cs.aux_queries.iter().enumerate() { - let point_index = (*vk.cs.rotations.get(at).unwrap()).0; - accumulate( - point_index, - aux_commitments[wire.0], - self.aux_evals[query_index], - ); - } - - for (query_index, &(wire, ref at)) in vk.cs.fixed_queries.iter().enumerate() { - let point_index = (*vk.cs.rotations.get(at).unwrap()).0; - accumulate( - point_index, - vk.fixed_commitments[wire.0], - self.fixed_evals[query_index], - ); - } - - let current_index = (*vk.cs.rotations.get(&Rotation::default()).unwrap()).0; - for (commitment, eval) in self.h_commitments.iter().zip(self.h_evals.iter()) { - accumulate(current_index, *commitment, *eval); - } - - // Handle permutation arguments, if any exist - if !vk.cs.permutations.is_empty() { - // Open permutation product commitments at x_3 - for (commitment, eval) in self - .permutation_product_commitments - .iter() - .zip(self.permutation_product_evals.iter()) - { - accumulate(current_index, *commitment, *eval); - } - // Open permutation commitments for each permutation argument at x_3 - for (commitment, eval) in vk - .permutation_commitments - .iter() - .zip(self.permutation_evals.iter()) - .flat_map(|(commitments, evals)| commitments.iter().zip(evals.iter())) - { - accumulate(current_index, *commitment, *eval); - } - let current_index = (*vk.cs.rotations.get(&Rotation(-1)).unwrap()).0; - // Open permutation product commitments at \omega^{-1} x_3 - for (commitment, eval) in self - .permutation_product_commitments - .iter() - .zip(self.permutation_product_inv_evals.iter()) - { - accumulate(current_index, *commitment, *eval); + // Open permutation commitments for each permutation argument at x_3 + for outer_idx in 0..vk.permutation_commitments.len() { + let inner_len = vk.permutation_commitments[outer_idx].len(); + for inner_idx in 0..inner_len { + let commitment = &vk.permutation_commitments[outer_idx][inner_idx]; + let eval = self.permutation_evals[outer_idx][inner_idx]; + queries.push(VerifierQuery { + point: x_3, + commitment, + eval, + }); } } + + // Open permutation product commitments at \omega^{-1} x_3 + let x_3_inv = vk.domain.rotate_omega(x_3, Rotation(-1)); + for ((idx, _), &eval) in self + .permutation_product_commitments + .iter() + .enumerate() + .zip(self.permutation_product_inv_evals.iter()) + { + let commitment = &self.permutation_product_commitments[idx]; + queries.push(VerifierQuery { + point: x_3_inv, + commitment, + eval, + }); + } } - // Sample a challenge x_5 for keeping the multi-point quotient - // polynomial terms linearly independent. - let x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - - // Obtain the commitment to the multi-point quotient polynomial f(X). - hash_point(&mut transcript, &self.f_commitment)?; - - // Sample a challenge x_6 for checking that f(X) was committed to - // correctly. - let x_6: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - - for eval in self.q_evals.iter() { - transcript_scalar.absorb(*eval); - } - - let transcript_scalar_point = - C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); - transcript.absorb(transcript_scalar_point); - - // We can compute the expected msm_eval at x_6 using the q_evals provided - // by the prover and from x_5 - let mut msm_eval = C::Scalar::zero(); - for (&row, point_index) in vk.cs.rotations.iter() { - let mut eval = self.q_evals[point_index.0]; - - let point = vk.domain.rotate_omega(x_3, row); - eval = eval - &q_evals[point_index.0]; - eval = eval * &(x_6 - &point).invert().unwrap(); - - msm_eval *= &x_5; - msm_eval += &eval; - } - - // Sample a challenge x_7 that we will use to collapse the openings of - // the various remaining polynomials at x_6 together. - let x_7: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - - // Compute the final commitment that has to be opened - let mut commitment_msm = params.empty_msm(); - commitment_msm.add_term(C::Scalar::one(), self.f_commitment); - for (_, &point_index) in vk.cs.rotations.iter() { - commitment_msm.scale(x_7); - commitment_msm.add_msm(&q_commitments[point_index.0]); - msm_eval *= &x_7; - msm_eval += &self.q_evals[point_index.0]; - } - - // Verify the opening proof - self.opening - .verify(params, msm, &mut transcript, x_6, commitment_msm, msm_eval) + // We are now convinced the circuit is satisfied so long as the + // polynomial commitments open to the correct values. + self.multiopening + .verify( + params, + &mut transcript, + &mut transcript_scalar, + queries, + msm, + ) .map_err(|_| Error::OpeningError) } @@ -225,7 +201,7 @@ impl<'a, C: CurveAffine> Proof { return Err(Error::IncompatibleParams); } - if self.q_evals.len() != vk.cs.rotations.len() { + if self.opening.q_evals.len() != vk.cs.rotations.len() { return Err(Error::IncompatibleParams); } diff --git a/src/poly.rs b/src/poly.rs index 2467599..fa2fc0f 100644 --- a/src/poly.rs +++ b/src/poly.rs @@ -10,6 +10,7 @@ use std::ops::{Add, Deref, DerefMut, Index, IndexMut, Mul, RangeFrom, RangeFull, pub mod commitment; mod domain; +pub mod multiopen; pub use domain::*; diff --git a/src/poly/multiopen.rs b/src/poly/multiopen.rs new file mode 100644 index 0000000..5850fca --- /dev/null +++ b/src/poly/multiopen.rs @@ -0,0 +1,23 @@ +//! This module contains an implementation of the multipoint opening polynomial +//! commitment scheme described in the [Halo][halo] paper. +//! +//! [halo]: https://eprint.iacr.org/2019/1021 + +use super::*; +use crate::arithmetic::CurveAffine; + +mod prover; +mod verifier; + +/// This is a multi-point opening proof used in the polynomial commitment scheme opening. +#[derive(Debug, Clone)] +pub struct Proof { + /// A vector of evaluations at each set of query points + pub q_evals: Vec, + + /// Commitment to final polynomial + pub f_commitment: C, + + /// Commitment proof + pub opening: commitment::Proof, +} diff --git a/src/poly/multiopen/prover.rs b/src/poly/multiopen/prover.rs new file mode 100644 index 0000000..48390b0 --- /dev/null +++ b/src/poly/multiopen/prover.rs @@ -0,0 +1,165 @@ +use std::marker::PhantomData; + +use super::super::{ + commitment::{self, Blind, Params}, + Coeff, Error, Polynomial, +}; +use super::Proof; + +use crate::arithmetic::{ + eval_polynomial, get_challenge_scalar, kate_division, parallelize, Challenge, Curve, + CurveAffine, Field, +}; +use crate::plonk::hash_point; +use crate::transcript::Hasher; + +impl Proof { + /// Create a multi-opening proof + pub fn create, HScalar: Hasher>( + params: &Params, + transcript: &mut HBase, + transcript_scalar: &mut HScalar, + points: Vec, + instances: I, + ) -> Result + where + I: IntoIterator< + Item = ( + usize, + Polynomial, + Blind, + C::Scalar, + ), + > + Clone, + { + let x_4: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + // Collapse openings at same points together into single openings using + // x_4 challenge. + let mut q_polys: Vec>> = vec![None; points.len()]; + let mut q_blinds = vec![Blind(C::Scalar::zero()); points.len()]; + let mut q_evals: Vec<_> = vec![C::Scalar::zero(); points.len()]; + { + let mut accumulate = + |point_index: usize, new_poly: Polynomial, blind, eval| { + q_polys[point_index] + .as_mut() + .map(|poly| { + parallelize(poly, |q, start| { + for (q, a) in q.iter_mut().zip(new_poly[start..].iter()) { + *q *= &x_4; + *q += a; + } + }); + }) + .or_else(|| { + q_polys[point_index] = Some(new_poly.clone()); + Some(()) + }); + q_blinds[point_index] *= x_4; + q_blinds[point_index] += blind; + q_evals[point_index] *= &x_4; + q_evals[point_index] += &eval; + }; + + for instance in instances.clone() { + accumulate( + instance.0, // point_index, + instance.1, // poly, + instance.2, // blind, + instance.3, // eval + ); + } + } + + let x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + let mut f_poly: Option> = None; + for (point_index, &point) in points.iter().enumerate() { + let mut poly = q_polys[point_index].as_ref().unwrap().clone(); + poly[0] -= &q_evals[point_index]; + // TODO: change kate_division interface? + let mut poly = kate_division(&poly[..], point); + poly.push(C::Scalar::zero()); + let poly = Polynomial { + values: poly, + _marker: PhantomData, + }; + + f_poly = f_poly + .map(|mut f_poly| { + parallelize(&mut f_poly, |q, start| { + for (q, a) in q.iter_mut().zip(poly[start..].iter()) { + *q *= &x_5; + *q += a; + } + }); + f_poly + }) + .or_else(|| Some(poly)); + } + + let f_poly = f_poly.unwrap(); + let mut f_blind = Blind(C::Scalar::random()); + let mut f_commitment = params.commit(&f_poly, f_blind).to_affine(); + + let (opening, q_evals) = loop { + let mut transcript = transcript.clone(); + let mut transcript_scalar = transcript_scalar.clone(); + hash_point(&mut transcript, &f_commitment).unwrap(); + + let x_6: C::Scalar = + get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + let mut q_evals = vec![C::Scalar::zero(); points.len()]; + + for (point_index, _) in points.iter().enumerate() { + q_evals[point_index] = + eval_polynomial(&q_polys[point_index].as_ref().unwrap(), x_6); + } + + for eval in q_evals.iter() { + transcript_scalar.absorb(*eval); + } + + let transcript_scalar_point = + C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); + transcript.absorb(transcript_scalar_point); + + let x_7: C::Scalar = + get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + let mut f_blind_dup = f_blind; + let mut f_poly = f_poly.clone(); + for (point_index, _) in points.iter().enumerate() { + f_blind_dup *= x_7; + f_blind_dup += q_blinds[point_index]; + + parallelize(&mut f_poly, |f, start| { + for (f, a) in f + .iter_mut() + .zip(q_polys[point_index].as_ref().unwrap()[start..].iter()) + { + *f *= &x_7; + *f += a; + } + }); + } + + if let Ok(opening) = + commitment::Proof::create(¶ms, &mut transcript, &f_poly, f_blind_dup, x_6) + { + break (opening, q_evals); + } else { + f_blind += C::Scalar::one(); + f_commitment = (f_commitment + params.h).to_affine(); + } + }; + + Ok(Proof { + q_evals, + f_commitment, + opening, + }) + } +} diff --git a/src/poly/multiopen/verifier.rs b/src/poly/multiopen/verifier.rs new file mode 100644 index 0000000..ea6f9a6 --- /dev/null +++ b/src/poly/multiopen/verifier.rs @@ -0,0 +1,92 @@ +use super::super::commitment::{Params, MSM}; +use super::Proof; +use crate::arithmetic::{get_challenge_scalar, Challenge, CurveAffine, Field}; +use crate::plonk::hash_point; +use crate::transcript::Hasher; + +impl<'a, C: CurveAffine> Proof { + /// Verify a multi-opening proof + pub fn verify, HScalar: Hasher>( + &self, + params: &'a Params, + transcript: &mut HBase, + transcript_scalar: &mut HScalar, + points: Vec, + instances: I, + ) -> (C::Scalar, MSM<'a, C>, C::Scalar) + where + I: IntoIterator + Clone, + { + // Sample x_4 for compressing openings at the same points together + let x_4: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + // Compress the commitments and expected evaluations at x_3 together + // using the challenge x_4 + let mut q_commitments: Vec<_> = vec![params.empty_msm(); points.len()]; + let mut q_evals: Vec<_> = vec![C::Scalar::zero(); points.len()]; + { + let mut accumulate = |point_index: usize, new_commitment, eval| { + q_commitments[point_index].scale(x_4); + q_commitments[point_index].add_term(C::Scalar::one(), new_commitment); + q_evals[point_index] *= &x_4; + q_evals[point_index] += &eval; + }; + + for instance in instances.clone() { + accumulate( + instance.0, // point_index, + instance.1, // commitment, + instance.2, // eval, + ); + } + } + + // Sample a challenge x_5 for keeping the multi-point quotient + // polynomial terms linearly independent. + let x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + // Obtain the commitment to the multi-point quotient polynomial f(X). + hash_point(transcript, &self.f_commitment).unwrap(); + + // Sample a challenge x_6 for checking that f(X) was committed to + // correctly. + let x_6: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + for eval in self.q_evals.iter() { + transcript_scalar.absorb(*eval); + } + + let transcript_scalar_point = + C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); + transcript.absorb(transcript_scalar_point); + + // We can compute the expected msm_eval at x_6 using the q_evals provided + // by the prover and from x_5 + let mut msm_eval = C::Scalar::zero(); + for (point_index, point) in points.iter().enumerate() { + let mut eval = self.q_evals[point_index]; + + eval = eval - &q_evals[point_index]; + eval = eval * &(x_6 - &point).invert().unwrap(); + + msm_eval *= &x_5; + msm_eval += &eval; + } + + // Sample a challenge x_7 that we will use to collapse the openings of + // the various remaining polynomials at x_6 together. + let x_7: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); + + // Compute the final commitment that has to be opened + let mut commitment_msm = params.empty_msm(); + commitment_msm.add_term(C::Scalar::one(), self.f_commitment); + for (point_index, _) in points.iter().enumerate() { + commitment_msm.scale(x_7); + commitment_msm.add_msm(&q_commitments[point_index]); + msm_eval *= &x_7; + msm_eval += &self.q_evals[point_index]; + } + + (x_6, commitment_msm, msm_eval) + } +}