mirror of
https://github.com/saymrwulf/pasta_curves-source.git
synced 2026-09-08 20:40:31 +00:00
Create multiopen abstraction
This commit is contained in:
parent
a8e0a5565e
commit
c3d0a172a7
7 changed files with 476 additions and 316 deletions
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
use crate::arithmetic::CurveAffine;
|
use crate::arithmetic::CurveAffine;
|
||||||
use crate::poly::{
|
use crate::poly::{
|
||||||
commitment, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial,
|
multiopen, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial,
|
||||||
};
|
};
|
||||||
use crate::transcript::Hasher;
|
use crate::transcript::Hasher;
|
||||||
|
|
||||||
|
|
@ -59,9 +59,7 @@ pub struct Proof<C: CurveAffine> {
|
||||||
aux_evals: Vec<C::Scalar>,
|
aux_evals: Vec<C::Scalar>,
|
||||||
fixed_evals: Vec<C::Scalar>,
|
fixed_evals: Vec<C::Scalar>,
|
||||||
h_evals: Vec<C::Scalar>,
|
h_evals: Vec<C::Scalar>,
|
||||||
f_commitment: C,
|
multiopening: multiopen::Proof<C>,
|
||||||
q_evals: Vec<C::Scalar>,
|
|
||||||
opening: commitment::Proof<C>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is an error that could occur during proving or circuit synthesis.
|
/// 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,
|
transcript: &mut H,
|
||||||
point: &C,
|
point: &C,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ use super::{
|
||||||
hash_point, Error, Proof, ProvingKey,
|
hash_point, Error, Proof, ProvingKey,
|
||||||
};
|
};
|
||||||
use crate::arithmetic::{
|
use crate::arithmetic::{
|
||||||
eval_polynomial, get_challenge_scalar, kate_division, parallelize, BatchInvert, Challenge,
|
eval_polynomial, get_challenge_scalar, parallelize, BatchInvert, Challenge, Curve, CurveAffine,
|
||||||
Curve, CurveAffine, Field,
|
Field,
|
||||||
};
|
};
|
||||||
use crate::poly::{
|
use crate::poly::{
|
||||||
commitment::{self, Blind, Params},
|
commitment::{Blind, Params},
|
||||||
Coeff, LagrangeCoeff, Polynomial, Rotation,
|
multiopen, Coeff, LagrangeCoeff, Polynomial, Rotation,
|
||||||
};
|
};
|
||||||
use crate::transcript::Hasher;
|
use crate::transcript::Hasher;
|
||||||
|
|
||||||
|
|
@ -457,194 +457,100 @@ impl<C: CurveAffine> Proof<C> {
|
||||||
C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap();
|
C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap();
|
||||||
transcript.absorb(transcript_scalar_point);
|
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
|
for (query_index, &(wire, ref at)) in meta.advice_queries.iter().enumerate() {
|
||||||
// x_4 challenge.
|
let point_index = (*meta.rotations.get(at).unwrap()).0;
|
||||||
let mut q_polys: Vec<Option<Polynomial<C::Scalar, Coeff>>> =
|
let poly = advice_polys[wire.0].clone();
|
||||||
vec![None; meta.rotations.len()];
|
let blind = advice_blinds[wire.0];
|
||||||
let mut q_blinds = vec![Blind(C::Scalar::zero()); meta.rotations.len()];
|
let eval = advice_evals[query_index];
|
||||||
let mut q_evals: Vec<_> = vec![C::Scalar::zero(); meta.rotations.len()];
|
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 =
|
instances.push((current_index, h_poly.clone(), *h_blind, *h_eval));
|
||||||
|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;
|
|
||||||
};
|
|
||||||
|
|
||||||
for (query_index, &(wire, ref at)) in meta.advice_queries.iter().enumerate() {
|
// Handle permutation arguments, if any exist
|
||||||
let point_index = (*meta.rotations.get(at).unwrap()).0;
|
if !pk.vk.cs.permutations.is_empty() {
|
||||||
|
// Open permutation product commitments at x_3
|
||||||
accumulate(
|
for ((poly, blind), eval) in permutation_product_polys
|
||||||
point_index,
|
.iter()
|
||||||
&advice_polys[wire.0],
|
.zip(permutation_product_blinds.iter())
|
||||||
advice_blinds[wire.0],
|
.zip(permutation_product_evals.iter())
|
||||||
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())
|
|
||||||
{
|
{
|
||||||
accumulate(current_index, &h_poly, *h_blind, *h_eval);
|
instances.push((current_index, poly.clone(), *blind, *eval));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle permutation arguments, if any exist
|
// Open permutation polynomial commitments at x_3
|
||||||
if !pk.vk.cs.permutations.is_empty() {
|
for (poly, eval) in pk
|
||||||
// Open permutation product commitments at x_3
|
.permutation_polys
|
||||||
for ((poly, blind), eval) in permutation_product_polys
|
.iter()
|
||||||
.iter()
|
.zip(permutation_evals.iter())
|
||||||
.zip(permutation_product_blinds.iter())
|
.flat_map(|(polys, evals)| polys.iter().zip(evals.iter()))
|
||||||
.zip(permutation_product_evals.iter())
|
{
|
||||||
{
|
instances.push((current_index, poly.clone(), Blind::default(), *eval));
|
||||||
accumulate(current_index, poly, *blind, *eval);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Open permutation polynomial commitments at x_3
|
let current_index = (*pk.vk.cs.rotations.get(&Rotation(-1)).unwrap()).0;
|
||||||
for (poly, eval) in pk
|
// Open permutation product commitments at \omega^{-1} x_3
|
||||||
.permutation_polys
|
for ((poly, blind), eval) in permutation_product_polys
|
||||||
.iter()
|
.iter()
|
||||||
.zip(permutation_evals.iter())
|
.zip(permutation_product_blinds.iter())
|
||||||
.flat_map(|(polys, evals)| polys.iter().zip(evals.iter()))
|
.zip(permutation_product_inv_evals.iter())
|
||||||
{
|
{
|
||||||
accumulate(current_index, poly, Blind::default(), *eval);
|
instances.push((current_index, poly.clone(), *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())
|
|
||||||
{
|
|
||||||
accumulate(current_index, poly, *blind, *eval);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
|
let mut points: Vec<C::Scalar> = vec![C::Scalar::zero(); meta.rotations.len()];
|
||||||
|
|
||||||
let mut f_poly: Option<Polynomial<C::Scalar, Coeff>> = None;
|
|
||||||
for (&row, &point_index) in meta.rotations.iter() {
|
for (&row, &point_index) in meta.rotations.iter() {
|
||||||
let mut poly = q_polys[point_index.0].as_ref().unwrap().clone();
|
points[point_index.0] = domain.rotate_omega(x_3, row);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let f_poly = f_poly.unwrap();
|
let multiopening = multiopen::Proof::create(
|
||||||
let mut f_blind = Blind(C::Scalar::random());
|
params,
|
||||||
let mut f_commitment = params.commit(&f_poly, f_blind).to_affine();
|
&mut transcript,
|
||||||
|
&mut transcript_scalar,
|
||||||
let (opening, q_evals) = loop {
|
points,
|
||||||
let mut transcript = transcript.clone();
|
instances,
|
||||||
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(); 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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Proof {
|
Ok(Proof {
|
||||||
advice_commitments,
|
advice_commitments,
|
||||||
|
|
@ -657,9 +563,7 @@ impl<C: CurveAffine> Proof<C> {
|
||||||
fixed_evals,
|
fixed_evals,
|
||||||
aux_evals,
|
aux_evals,
|
||||||
h_evals,
|
h_evals,
|
||||||
f_commitment,
|
multiopening,
|
||||||
q_evals,
|
|
||||||
opening,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use super::{hash_point, Error, Proof, VerifyingKey};
|
||||||
use crate::arithmetic::{get_challenge_scalar, Challenge, CurveAffine, Field};
|
use crate::arithmetic::{get_challenge_scalar, Challenge, CurveAffine, Field};
|
||||||
use crate::poly::{
|
use crate::poly::{
|
||||||
commitment::{Guard, Params, MSM},
|
commitment::{Guard, Params, MSM},
|
||||||
|
multiopen::VerifierQuery,
|
||||||
Rotation,
|
Rotation,
|
||||||
};
|
};
|
||||||
use crate::transcript::Hasher;
|
use crate::transcript::Hasher;
|
||||||
|
|
@ -9,18 +10,21 @@ use crate::transcript::Hasher;
|
||||||
impl<'a, C: CurveAffine> Proof<C> {
|
impl<'a, C: CurveAffine> Proof<C> {
|
||||||
/// Returns a boolean indicating whether or not the proof is valid
|
/// Returns a boolean indicating whether or not the proof is valid
|
||||||
pub fn verify<HBase: Hasher<C::Base>, HScalar: Hasher<C::Scalar>>(
|
pub fn verify<HBase: Hasher<C::Base>, HScalar: Hasher<C::Scalar>>(
|
||||||
&self,
|
&'a self,
|
||||||
params: &'a Params<C>,
|
params: &'a Params<C>,
|
||||||
vk: &VerifyingKey<C>,
|
vk: &'a VerifyingKey<C>,
|
||||||
mut msm: MSM<'a, C>,
|
msm: MSM<'a, C>,
|
||||||
aux_commitments: &[C],
|
aux_commitments: &'a [C],
|
||||||
) -> Result<Guard<'a, C>, Error> {
|
) -> Result<Guard<'a, C>, Error> {
|
||||||
self.check_lengths(vk, aux_commitments)?;
|
self.check_lengths(vk, aux_commitments)?;
|
||||||
|
|
||||||
// Scale the MSM by a random factor to ensure that if the existing MSM
|
// Check that aux_commitments matches the expected number of aux_wires
|
||||||
// has is_zero() == false then this argument won't be able to interfere
|
// and self.aux_evals
|
||||||
// with it to make it true, with high probability.
|
if aux_commitments.len() != vk.cs.num_aux_wires
|
||||||
msm.scale(C::Scalar::random());
|
|| self.aux_evals.len() != vk.cs.num_aux_wires
|
||||||
|
{
|
||||||
|
return Err(Error::IncompatibleParams);
|
||||||
|
}
|
||||||
|
|
||||||
// Create a transcript for obtaining Fiat-Shamir challenges.
|
// Create a transcript for obtaining Fiat-Shamir challenges.
|
||||||
let mut transcript = HBase::init(C::Base::one());
|
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();
|
C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap();
|
||||||
transcript.absorb(transcript_scalar_point);
|
transcript.absorb(transcript_scalar_point);
|
||||||
|
|
||||||
// Sample x_4 for compressing openings at the same points together
|
let mut queries: Vec<VerifierQuery<'a, C>> = Vec::new();
|
||||||
let x_4: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
|
|
||||||
|
|
||||||
// Compress the commitments and expected evaluations at x_3 together
|
for (query_index, &(wire, at)) in vk.cs.advice_queries.iter().enumerate() {
|
||||||
// using the challenge x_4
|
let point = vk.domain.rotate_omega(x_3, at);
|
||||||
let mut q_commitments: Vec<_> = vec![params.empty_msm(); vk.cs.rotations.len()];
|
queries.push(VerifierQuery {
|
||||||
let mut q_evals: Vec<_> = vec![C::Scalar::zero(); vk.cs.rotations.len()];
|
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| {
|
let commitment = &self.h_commitments[idx];
|
||||||
q_commitments[point_index].scale(x_4);
|
queries.push(VerifierQuery {
|
||||||
q_commitments[point_index].add_term(C::Scalar::one(), new_commitment);
|
point: x_3,
|
||||||
q_evals[point_index] *= &x_4;
|
commitment,
|
||||||
q_evals[point_index] += &eval;
|
eval,
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
|
||||||
for (query_index, &(wire, ref at)) in vk.cs.advice_queries.iter().enumerate() {
|
// Handle permutation arguments, if any exist
|
||||||
let point_index = (*vk.cs.rotations.get(at).unwrap()).0;
|
if !vk.cs.permutations.is_empty() {
|
||||||
accumulate(
|
// Open permutation product commitments at x_3
|
||||||
point_index,
|
for ((idx, _), &eval) in self
|
||||||
self.advice_commitments[wire.0],
|
.permutation_product_commitments
|
||||||
self.advice_evals[query_index],
|
.iter()
|
||||||
);
|
.enumerate()
|
||||||
|
.zip(self.permutation_product_evals.iter())
|
||||||
|
{
|
||||||
|
let commitment = &self.permutation_product_commitments[idx];
|
||||||
|
queries.push(VerifierQuery {
|
||||||
|
point: x_3,
|
||||||
|
commitment,
|
||||||
|
eval,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
// Open permutation commitments for each permutation argument at x_3
|
||||||
for (query_index, &(wire, ref at)) in vk.cs.aux_queries.iter().enumerate() {
|
for outer_idx in 0..vk.permutation_commitments.len() {
|
||||||
let point_index = (*vk.cs.rotations.get(at).unwrap()).0;
|
let inner_len = vk.permutation_commitments[outer_idx].len();
|
||||||
accumulate(
|
for inner_idx in 0..inner_len {
|
||||||
point_index,
|
let commitment = &vk.permutation_commitments[outer_idx][inner_idx];
|
||||||
aux_commitments[wire.0],
|
let eval = self.permutation_evals[outer_idx][inner_idx];
|
||||||
self.aux_evals[query_index],
|
queries.push(VerifierQuery {
|
||||||
);
|
point: x_3,
|
||||||
}
|
commitment,
|
||||||
|
eval,
|
||||||
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 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
|
// We are now convinced the circuit is satisfied so long as the
|
||||||
// polynomial terms linearly independent.
|
// polynomial commitments open to the correct values.
|
||||||
let x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
|
self.multiopening
|
||||||
|
.verify(
|
||||||
// Obtain the commitment to the multi-point quotient polynomial f(X).
|
params,
|
||||||
hash_point(&mut transcript, &self.f_commitment)?;
|
&mut transcript,
|
||||||
|
&mut transcript_scalar,
|
||||||
// Sample a challenge x_6 for checking that f(X) was committed to
|
queries,
|
||||||
// correctly.
|
msm,
|
||||||
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)
|
|
||||||
.map_err(|_| Error::OpeningError)
|
.map_err(|_| Error::OpeningError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,7 +201,7 @@ impl<'a, C: CurveAffine> Proof<C> {
|
||||||
return Err(Error::IncompatibleParams);
|
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);
|
return Err(Error::IncompatibleParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use std::ops::{Add, Deref, DerefMut, Index, IndexMut, Mul, RangeFrom, RangeFull,
|
||||||
|
|
||||||
pub mod commitment;
|
pub mod commitment;
|
||||||
mod domain;
|
mod domain;
|
||||||
|
pub mod multiopen;
|
||||||
|
|
||||||
pub use domain::*;
|
pub use domain::*;
|
||||||
|
|
||||||
|
|
|
||||||
23
src/poly/multiopen.rs
Normal file
23
src/poly/multiopen.rs
Normal 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>,
|
||||||
|
}
|
||||||
165
src/poly/multiopen/prover.rs
Normal file
165
src/poly/multiopen/prover.rs
Normal 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(¶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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
92
src/poly/multiopen/verifier.rs
Normal file
92
src/poly/multiopen/verifier.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue