Create multiopen abstraction

This commit is contained in:
therealyingtong 2020-09-29 15:23:41 +08:00 committed by therealyingtong
parent a8e0a5565e
commit c3d0a172a7
7 changed files with 476 additions and 316 deletions

View file

@ -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<C: CurveAffine> {
aux_evals: Vec<C::Scalar>,
fixed_evals: Vec<C::Scalar>,
h_evals: Vec<C::Scalar>,
f_commitment: C,
q_evals: Vec<C::Scalar>,
opening: commitment::Proof<C>,
multiopening: multiopen::Proof<C>,
}
/// This is an error that could occur during proving or circuit synthesis.
@ -96,7 +94,8 @@ impl<C: CurveAffine> VerifyingKey<C> {
}
}
fn hash_point<C: CurveAffine, H: Hasher<C::Base>>(
/// Hash a point into transcript
pub fn hash_point<C: CurveAffine, H: Hasher<C::Base>>(
transcript: &mut H,
point: &C,
) -> Result<(), Error> {

View file

@ -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<C: CurveAffine> Proof<C> {
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<C::Scalar, Coeff>,
Blind<C::Scalar>,
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<Option<Polynomial<C::Scalar, Coeff>>> =
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<Polynomial<C::Scalar, Coeff>> = None;
let mut points: Vec<C::Scalar> = 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(&params, &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<C: CurveAffine> Proof<C> {
fixed_evals,
aux_evals,
h_evals,
f_commitment,
q_evals,
opening,
multiopening,
})
}
}

View file

@ -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<C> {
/// Returns a boolean indicating whether or not the proof is valid
pub fn verify<HBase: Hasher<C::Base>, HScalar: Hasher<C::Scalar>>(
&self,
&'a self,
params: &'a Params<C>,
vk: &VerifyingKey<C>,
mut msm: MSM<'a, C>,
aux_commitments: &[C],
vk: &'a VerifyingKey<C>,
msm: MSM<'a, C>,
aux_commitments: &'a [C],
) -> Result<Guard<'a, C>, 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> {
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<VerifierQuery<'a, C>> = 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<C> {
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);
}

View file

@ -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::*;

23
src/poly/multiopen.rs Normal file
View file

@ -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<C: CurveAffine> {
/// A vector of evaluations at each set of query points
pub q_evals: Vec<C::Scalar>,
/// Commitment to final polynomial
pub f_commitment: C,
/// Commitment proof
pub opening: commitment::Proof<C>,
}

View file

@ -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<C: CurveAffine> Proof<C> {
/// Create a multi-opening proof
pub fn create<I, HBase: Hasher<C::Base>, HScalar: Hasher<C::Scalar>>(
params: &Params<C>,
transcript: &mut HBase,
transcript_scalar: &mut HScalar,
points: Vec<C::Scalar>,
instances: I,
) -> Result<Self, Error>
where
I: IntoIterator<
Item = (
usize,
Polynomial<C::Scalar, Coeff>,
Blind<C::Scalar>,
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<Option<Polynomial<C::Scalar, Coeff>>> = 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<C::Scalar, 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;
};
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<Polynomial<C::Scalar, Coeff>> = 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(&params, &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,
})
}
}

View file

@ -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<C> {
/// Verify a multi-opening proof
pub fn verify<I, HBase: Hasher<C::Base>, HScalar: Hasher<C::Scalar>>(
&self,
params: &'a Params<C>,
transcript: &mut HBase,
transcript_scalar: &mut HScalar,
points: Vec<C::Scalar>,
instances: I,
) -> (C::Scalar, MSM<'a, C>, C::Scalar)
where
I: IntoIterator<Item = (usize, C, C::Scalar)> + 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)
}
}