Merge pull request #26 from zcash/multipoint-opening

Optimise multi-point opening
This commit is contained in:
ebfull 2020-10-20 09:08:11 -06:00 committed by GitHub
commit 18746f109c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1026 additions and 325 deletions

View File

@ -430,3 +430,79 @@ fn log2_floor(num: usize) -> u32 {
pow
}
/// Returns coefficients of an n - 1 degree polynomial given a set of n points
/// and their evaluations. This function will panic if two values in `points`
/// are the same.
pub fn lagrange_interpolate<F: Field>(points: &[F], evals: &[F]) -> Vec<F> {
assert_eq!(points.len(), evals.len());
if points.len() == 1 {
// Constant polynomial
return vec![evals[0]];
} else {
let mut denoms = Vec::with_capacity(points.len());
for (j, x_j) in points.iter().enumerate() {
let mut denom = Vec::with_capacity(points.len() - 1);
for x_k in points
.iter()
.enumerate()
.filter(|&(k, _)| k != j)
.map(|a| a.1)
{
denom.push(*x_j - x_k);
}
denoms.push(denom);
}
// Compute (x_j - x_k)^(-1) for each j != i
denoms.iter_mut().flat_map(|v| v.iter_mut()).batch_invert();
let mut final_poly = vec![F::zero(); points.len()];
for (j, (denoms, eval)) in denoms.into_iter().zip(evals.iter()).enumerate() {
let mut tmp: Vec<F> = Vec::with_capacity(points.len());
let mut product = Vec::with_capacity(points.len() - 1);
tmp.push(F::one());
for (x_k, denom) in points
.iter()
.enumerate()
.filter(|&(k, _)| k != j)
.map(|a| a.1)
.zip(denoms.into_iter())
{
product.resize(tmp.len() + 1, F::zero());
for ((a, b), product) in tmp
.iter()
.chain(std::iter::once(&F::zero()))
.zip(std::iter::once(&F::zero()).chain(tmp.iter()))
.zip(product.iter_mut())
{
*product = *a * (-denom * x_k) + *b * denom;
}
std::mem::swap(&mut tmp, &mut product);
}
assert_eq!(tmp.len(), points.len());
assert_eq!(product.len(), points.len() - 1);
for (final_coeff, interpolation_coeff) in final_poly.iter_mut().zip(tmp.into_iter()) {
*final_coeff += interpolation_coeff * eval;
}
}
final_poly
}
}
#[test]
fn test_lagrange_interpolate() {
let points = (0..5).map(|_| Fp::random()).collect::<Vec<_>>();
let evals = (0..5).map(|_| Fp::random()).collect::<Vec<_>>();
for coeffs in 0..5 {
let points = &points[0..coeffs];
let evals = &evals[0..coeffs];
let poly = lagrange_interpolate(points, evals);
assert_eq!(poly.len(), points.len());
for (point, eval) in points.iter().zip(evals) {
assert_eq!(eval_polynomial(&poly, *point), *eval);
}
}
}

View File

@ -36,6 +36,8 @@ pub trait Field:
+ for<'a> SubAssign<&'a Self>
+ PartialEq
+ Eq
+ PartialOrd
+ Ord
+ ConditionallySelectable
+ ConstantTimeEq
+ Group<Scalar = Self>

View File

@ -62,6 +62,27 @@ impl PartialEq for Fp {
}
}
impl std::cmp::Ord for Fp {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let left = self.to_bytes();
let right = other.to_bytes();
left.iter()
.zip(right.iter())
.rev()
.find_map(|(left_byte, right_byte)| match left_byte.cmp(right_byte) {
std::cmp::Ordering::Equal => None,
res => Some(res),
})
.unwrap_or(std::cmp::Ordering::Equal)
}
}
impl std::cmp::PartialOrd for Fp {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl ConditionallySelectable for Fp {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
Fp([

View File

@ -62,6 +62,27 @@ impl PartialEq for Fq {
}
}
impl std::cmp::Ord for Fq {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let left = self.to_bytes();
let right = other.to_bytes();
left.iter()
.zip(right.iter())
.rev()
.find_map(|(left_byte, right_byte)| match left_byte.cmp(right_byte) {
std::cmp::Ordering::Equal => None,
res => Some(res),
})
.unwrap_or(std::cmp::Ordering::Equal)
}
}
impl std::cmp::PartialOrd for Fq {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl ConditionallySelectable for Fq {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
Fq([

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> {
@ -409,9 +408,10 @@ fn test_proving() {
)
.expect("proof generation should not fail");
let pubinput_slice = &[pubinput];
let msm = params.empty_msm();
let guard = proof
.verify::<DummyHash<Fq>, DummyHash<Fp>>(&params, pk.get_vk(), msm, &[pubinput])
.verify::<DummyHash<Fq>, DummyHash<Fp>>(&params, pk.get_vk(), msm, pubinput_slice)
.unwrap();
{
let msm = guard.clone().use_challenges();
@ -425,7 +425,7 @@ fn test_proving() {
let msm = guard.clone().use_challenges();
assert!(msm.clone().eval());
let guard = proof
.verify::<DummyHash<Fq>, DummyHash<Fp>>(&params, pk.get_vk(), msm, &[pubinput])
.verify::<DummyHash<Fq>, DummyHash<Fp>>(&params, pk.get_vk(), msm, pubinput_slice)
.unwrap();
{
let msm = guard.clone().use_challenges();

View File

@ -3,12 +3,13 @@ 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::{self, ProverQuery},
LagrangeCoeff, Polynomial, Rotation,
};
use crate::transcript::Hasher;
@ -457,194 +458,102 @@ 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<ProverQuery<C>> = Vec::new();
// 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, at)) in pk.vk.cs.advice_queries.iter().enumerate() {
let point = domain.rotate_omega(x_3, at);
instances.push(ProverQuery {
point,
poly: &advice_polys[wire.0],
blind: advice_blinds[wire.0],
eval: advice_evals[query_index],
});
}
for (query_index, &(wire, at)) in pk.vk.cs.aux_queries.iter().enumerate() {
let point = domain.rotate_omega(x_3, at);
instances.push(ProverQuery {
point,
poly: &aux_polys[wire.0],
blind: Blind::default(),
eval: aux_evals[query_index],
});
}
for (query_index, &(wire, at)) in pk.vk.cs.fixed_queries.iter().enumerate() {
let point = domain.rotate_omega(x_3, at);
instances.push(ProverQuery {
point,
poly: &pk.fixed_polys[wire.0],
blind: Blind::default(),
eval: fixed_evals[query_index],
});
}
// We query the h(X) polynomial at x_3
for ((h_poly, h_blind), h_eval) in h_pieces.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(ProverQuery {
point: x_3,
poly: h_poly,
blind: *h_blind,
eval: *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);
}
// 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()))
{
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 x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
let mut f_poly: Option<Polynomial<C::Scalar, Coeff>> = None;
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));
}
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;
}
instances.push(ProverQuery {
point: x_3,
poly: poly,
blind: *blind,
eval: *eval,
});
}
if let Ok(opening) =
commitment::Proof::create(&params, &mut transcript, &f_poly, f_blind_dup, x_6)
// 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()))
{
break (opening, q_evals);
} else {
f_blind += C::Scalar::one();
f_commitment = (f_commitment + params.h).to_affine();
instances.push(ProverQuery {
point: x_3,
poly: poly,
blind: Blind::default(),
eval: *eval,
});
}
};
let x_3_inv = domain.rotate_omega(x_3, Rotation(-1));
// 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(ProverQuery {
point: x_3_inv,
poly: poly,
blind: *blind,
eval: *eval,
});
}
}
let multiopening =
multiopen::Proof::create(params, &mut transcript, &mut transcript_scalar, instances)
.map_err(|_| Error::OpeningError)?;
Ok(Proof {
advice_commitments,
@ -657,9 +566,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,10 +201,6 @@ impl<'a, C: CurveAffine> Proof<C> {
return Err(Error::IncompatibleParams);
}
if self.q_evals.len() != vk.cs.rotations.len() {
return Err(Error::IncompatibleParams);
}
// TODO: check h_evals
if self.fixed_evals.len() != vk.cs.fixed_queries.len() {

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

356
src/poly/multiopen.rs Normal file
View File

@ -0,0 +1,356 @@
//! This module contains an optimisation of the polynomial commitment opening
//! scheme described in the [Halo][halo] paper.
//!
//! [halo]: https://eprint.iacr.org/2019/1021
use std::collections::{BTreeMap, BTreeSet};
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
q_evals: Vec<C::Scalar>,
// Commitment to final polynomial
f_commitment: C,
// Commitment proof
opening: commitment::Proof<C>,
}
/// A polynomial query at a point
#[derive(Debug, Clone)]
pub struct ProverQuery<'a, C: CurveAffine> {
/// point at which polynomial is queried
pub point: C::Scalar,
/// coefficients of polynomial
pub poly: &'a Polynomial<C::Scalar, Coeff>,
/// blinding factor of polynomial
pub blind: commitment::Blind<C::Scalar>,
/// evaluation of polynomial at query point
pub eval: C::Scalar,
}
/// A polynomial query at a point
#[derive(Debug, Clone)]
pub struct VerifierQuery<'a, C: CurveAffine> {
/// point at which polynomial is queried
pub point: C::Scalar,
/// commitment to polynomial
pub commitment: &'a C,
/// evaluation of polynomial at query point
pub eval: C::Scalar,
}
struct CommitmentData<F: Field, T: PartialEq> {
commitment: T,
set_index: usize,
point_indices: Vec<usize>,
evals: Vec<F>,
}
impl<F: Field, T: PartialEq> CommitmentData<F, T> {
fn new(commitment: T) -> Self {
CommitmentData {
commitment,
set_index: 0,
point_indices: vec![],
evals: vec![],
}
}
}
trait Query<F>: Sized {
type Commitment: PartialEq + Copy;
fn get_point(&self) -> F;
fn get_eval(&self) -> F;
fn get_commitment(&self) -> Self::Commitment;
}
fn construct_intermediate_sets<F: Field, I, Q: Query<F>>(
queries: I,
) -> (Vec<CommitmentData<F, Q::Commitment>>, Vec<Vec<F>>)
where
I: IntoIterator<Item = Q> + Clone,
{
// Construct sets of unique commitments and corresponding information about
// their queries.
let mut commitment_map: Vec<CommitmentData<F, Q::Commitment>> = vec![];
// Also construct mapping from a unique point to a point_index. This defines
// an ordering on the points.
let mut point_index_map = BTreeMap::new();
// Iterate over all of the queries, computing the ordering of the points
// while also creating new commitment data.
for query in queries.clone() {
let num_points = point_index_map.len();
let point_idx = point_index_map
.entry(query.get_point())
.or_insert(num_points);
if let Some(pos) = commitment_map
.iter()
.position(|comm| comm.commitment == query.get_commitment())
{
commitment_map[pos].point_indices.push(*point_idx);
} else {
let mut tmp = CommitmentData::new(query.get_commitment());
tmp.point_indices.push(*point_idx);
commitment_map.push(tmp);
}
}
// Also construct inverse mapping from point_index to the point
let mut inverse_point_index_map = BTreeMap::new();
for (&point, &point_index) in point_index_map.iter() {
inverse_point_index_map.insert(point_index, point);
}
// Construct map of unique ordered point_idx_sets to their set_idx
let mut point_idx_sets = BTreeMap::new();
// Also construct mapping from commitment to point_idx_set
let mut commitment_set_map = Vec::new();
for commitment_data in commitment_map.iter() {
let mut point_index_set = BTreeSet::new();
// Note that point_index_set is ordered, unlike point_indices
for &point_index in commitment_data.point_indices.iter() {
point_index_set.insert(point_index);
}
// Push point_index_set to CommitmentData for the relevant commitment
commitment_set_map.push((commitment_data.commitment, point_index_set.clone()));
let num_sets = point_idx_sets.len();
point_idx_sets.entry(point_index_set).or_insert(num_sets);
}
// Initialise empty evals vec for each unique commitment
for commitment_data in commitment_map.iter_mut() {
let len = commitment_data.point_indices.len();
commitment_data.evals = vec![F::zero(); len];
}
// Populate set_index, evals and points for each commitment using point_idx_sets
for query in queries {
// The index of the point at which the commitment is queried
let point_index = point_index_map.get(&query.get_point()).unwrap();
// The point_index_set at which the commitment was queried
let mut point_index_set = BTreeSet::new();
for (commitment, point_idx_set) in commitment_set_map.iter() {
if query.get_commitment() == *commitment {
point_index_set = point_idx_set.clone();
}
}
assert!(!point_index_set.is_empty());
// The set_index of the point_index_set
let set_index = point_idx_sets.get(&point_index_set).unwrap();
for commitment_data in commitment_map.iter_mut() {
if query.get_commitment() == commitment_data.commitment {
commitment_data.set_index = *set_index;
}
}
let point_index_set: Vec<usize> = point_index_set.iter().cloned().collect();
// The offset of the point_index in the point_index_set
let point_index_in_set = point_index_set
.iter()
.position(|i| i == point_index)
.unwrap();
for commitment_data in commitment_map.iter_mut() {
if query.get_commitment() == commitment_data.commitment {
// Insert the eval using the ordering of the point_index_set
commitment_data.evals[point_index_in_set] = query.get_eval();
}
}
}
// Get actual points in each point set
let mut point_sets: Vec<Vec<F>> = vec![Vec::new(); point_idx_sets.len()];
for (point_idx_set, &set_idx) in point_idx_sets.iter() {
for &point_idx in point_idx_set.iter() {
let point = inverse_point_index_map.get(&point_idx).unwrap();
point_sets[set_idx].push(*point);
}
}
(commitment_map, point_sets)
}
#[cfg(test)]
mod tests {
use super::{construct_intermediate_sets, Query};
use crate::arithmetic::{Field, Fp};
#[derive(Clone)]
struct MyQuery<F> {
commitment: usize,
point: F,
eval: F,
}
impl<F: Copy> Query<F> for MyQuery<F> {
type Commitment = usize;
fn get_point(&self) -> F {
self.point
}
fn get_eval(&self) -> F {
self.eval
}
fn get_commitment(&self) -> Self::Commitment {
self.commitment
}
}
#[test]
fn test_coherence() {
let points = &[
Fp::random(),
Fp::random(),
Fp::random(),
Fp::random(),
Fp::random(),
];
let build_queries = || {
vec![
MyQuery {
commitment: 0,
point: points[0],
eval: Fp::random(),
},
MyQuery {
commitment: 0,
point: points[1],
eval: Fp::random(),
},
MyQuery {
commitment: 1,
point: points[0],
eval: Fp::random(),
},
MyQuery {
commitment: 1,
point: points[1],
eval: Fp::random(),
},
MyQuery {
commitment: 2,
point: points[0],
eval: Fp::random(),
},
MyQuery {
commitment: 2,
point: points[1],
eval: Fp::random(),
},
MyQuery {
commitment: 2,
point: points[2],
eval: Fp::random(),
},
MyQuery {
commitment: 3,
point: points[0],
eval: Fp::random(),
},
MyQuery {
commitment: 3,
point: points[3],
eval: Fp::random(),
},
MyQuery {
commitment: 4,
point: points[4],
eval: Fp::random(),
},
]
};
let queries = build_queries();
let (commitment_data, point_sets) = construct_intermediate_sets(queries);
// It shouldn't matter what the point or eval values are; we should get
// the same exact point sets again.
{
let new_queries = build_queries();
let (_, new_point_sets) = construct_intermediate_sets(new_queries);
assert_eq!(point_sets, new_point_sets);
}
let mut a = false;
let mut a_set = 0;
let mut b = false;
let mut b_set = 0;
let mut c = false;
let mut c_set = 0;
let mut d = false;
let mut d_set = 0;
for (i, mut point_set) in point_sets.into_iter().enumerate() {
point_set.sort();
if point_set.len() == 1 {
assert_eq!(point_set[0], points[4]);
assert!(!a);
a = true;
a_set = i;
} else if point_set.len() == 2 {
let mut v0 = [points[0], points[1]];
let mut v1 = [points[0], points[3]];
v0.sort();
v1.sort();
if &point_set[..] == &v0[..] {
assert!(!b);
b = true;
b_set = i;
} else if &point_set[..] == &v1[..] {
assert!(!c);
c = true;
c_set = i;
} else {
panic!("unexpected");
}
} else if point_set.len() == 3 {
let mut v = [points[0], points[1], points[2]];
v.sort();
assert_eq!(&point_set[..], &v[..]);
assert!(!d);
d = true;
d_set = i;
} else {
panic!("unexpected");
}
}
assert!(a & b & c & d);
for commitment_data in commitment_data {
assert_eq!(
commitment_data.set_index,
match commitment_data.commitment {
0 => b_set,
1 => b_set,
2 => d_set,
3 => c_set,
4 => a_set,
_ => unreachable!(),
}
);
}
}
}

View File

@ -0,0 +1,191 @@
use super::super::{
commitment::{self, Blind, Params},
Coeff, Error, Polynomial,
};
use super::{construct_intermediate_sets, Proof, ProverQuery, Query};
use crate::arithmetic::{
eval_polynomial, get_challenge_scalar, kate_division, lagrange_interpolate, Challenge, Curve,
CurveAffine, Field,
};
use crate::plonk::hash_point;
use crate::transcript::Hasher;
use std::marker::PhantomData;
#[derive(Debug, Clone)]
struct CommitmentData<C: CurveAffine> {
set_index: usize,
blind: Blind<C::Scalar>,
point_indices: Vec<usize>,
evals: Vec<C::Scalar>,
}
impl<C: CurveAffine> Proof<C> {
/// Create a multi-opening proof
pub fn create<'a, I, HBase: Hasher<C::Base>, HScalar: Hasher<C::Scalar>>(
params: &Params<C>,
transcript: &mut HBase,
transcript_scalar: &mut HScalar,
queries: I,
) -> Result<Self, Error>
where
I: IntoIterator<Item = ProverQuery<'a, C>> + Clone,
{
let x_4: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
let x_5: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
let (poly_map, point_sets) = construct_intermediate_sets(queries);
// Collapse openings at same point sets together into single openings using
// x_4 challenge.
let mut q_polys: Vec<Option<Polynomial<C::Scalar, Coeff>>> = vec![None; point_sets.len()];
let mut q_blinds = vec![Blind(C::Scalar::zero()); point_sets.len()];
// A vec of vecs of evals. The outer vec corresponds to the point set,
// while the inner vec corresponds to the points in a particular set.
let mut q_eval_sets = Vec::with_capacity(point_sets.len());
for point_set in point_sets.iter() {
q_eval_sets.push(vec![C::Scalar::zero(); point_set.len()]);
}
{
let mut accumulate = |set_idx: usize,
new_poly: &Polynomial<C::Scalar, Coeff>,
blind: Blind<C::Scalar>,
evals: Vec<C::Scalar>| {
if let Some(poly) = &q_polys[set_idx] {
q_polys[set_idx] = Some(poly.clone() * x_4 + new_poly);
} else {
q_polys[set_idx] = Some(new_poly.clone());
}
q_blinds[set_idx] *= x_4;
q_blinds[set_idx] += blind;
// Each polynomial is evaluated at a set of points. For each set,
// we collapse each polynomial's evals pointwise.
for (eval, set_eval) in evals.iter().zip(q_eval_sets[set_idx].iter_mut()) {
*set_eval *= &x_4;
*set_eval += eval;
}
};
for commitment_data in poly_map.into_iter() {
accumulate(
commitment_data.set_index, // set_idx,
commitment_data.commitment.poly, // poly,
commitment_data.commitment.blind, // blind,
commitment_data.evals, // evals
);
}
}
let f_poly = point_sets
.iter()
.zip(q_eval_sets.iter())
.zip(q_polys.iter())
.fold(None, |f_poly, ((points, evals), poly)| {
let mut poly = poly.clone().unwrap().values;
// TODO: makes implicit asssumption that poly degree is smaller than interpolation poly degree
for (p, r) in poly.iter_mut().zip(lagrange_interpolate(points, evals)) {
*p -= &r;
}
let mut poly = points
.iter()
.fold(poly, |poly, point| kate_division(&poly, *point));
poly.resize(params.n as usize, C::Scalar::zero());
let poly = Polynomial {
values: poly,
_marker: PhantomData,
};
if f_poly.is_none() {
Some(poly)
} else {
f_poly.map(|f_poly| f_poly * x_5 + &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 q_evals: Vec<C::Scalar> = q_polys
.iter()
.map(|poly| eval_polynomial(poly.as_ref().unwrap(), x_6))
.collect();
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 (f_poly, f_blind_try) = q_polys.iter().zip(q_blinds.iter()).fold(
(f_poly.clone(), f_blind),
|(f_poly, f_blind), (poly, blind)| {
(
f_poly * x_7 + poly.as_ref().unwrap(),
Blind((f_blind.0 * &x_7) + &blind.0),
)
},
);
if let Ok(opening) =
commitment::Proof::create(&params, &mut transcript, &f_poly, f_blind_try, 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,
})
}
}
#[doc(hidden)]
#[derive(Copy, Clone)]
pub struct PolynomialPointer<'a, C: CurveAffine> {
poly: &'a Polynomial<C::Scalar, Coeff>,
blind: commitment::Blind<C::Scalar>,
}
impl<'a, C: CurveAffine> PartialEq for PolynomialPointer<'a, C> {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.poly, other.poly)
}
}
impl<'a, C: CurveAffine> Query<C::Scalar> for ProverQuery<'a, C> {
type Commitment = PolynomialPointer<'a, C>;
fn get_point(&self) -> C::Scalar {
self.point
}
fn get_eval(&self) -> C::Scalar {
self.eval
}
fn get_commitment(&self) -> Self::Commitment {
PolynomialPointer {
poly: self.poly,
blind: self.blind,
}
}
}

View File

@ -0,0 +1,154 @@
use super::super::{
commitment::{Guard, Params, MSM},
Error,
};
use super::{construct_intermediate_sets, Proof, Query, VerifierQuery};
use crate::arithmetic::{
eval_polynomial, get_challenge_scalar, lagrange_interpolate, Challenge, CurveAffine, Field,
};
use crate::plonk::hash_point;
use crate::transcript::Hasher;
#[derive(Debug, Clone)]
struct CommitmentData<C: CurveAffine> {
set_index: usize,
point_indices: Vec<usize>,
evals: Vec<C::Scalar>,
}
impl<C: CurveAffine> Proof<C> {
/// Verify a multi-opening proof
pub fn verify<'a, I, HBase: Hasher<C::Base>, HScalar: Hasher<C::Scalar>>(
&self,
params: &'a Params<C>,
transcript: &mut HBase,
transcript_scalar: &mut HScalar,
queries: I,
mut msm: MSM<'a, C>,
) -> Result<Guard<'a, C>, Error>
where
I: IntoIterator<Item = VerifierQuery<'a, C>> + Clone,
{
// 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());
// Sample x_4 for compressing openings at the same point sets together
let x_4: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128()));
// 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()));
let (commitment_map, point_sets) = construct_intermediate_sets(queries);
// Compress the commitments and expected evaluations at x_3 together.
// using the challenge x_4
let mut q_commitments: Vec<_> = vec![params.empty_msm(); point_sets.len()];
// A vec of vecs of evals. The outer vec corresponds to the point set,
// while the inner vec corresponds to the points in a particular set.
let mut q_eval_sets = Vec::with_capacity(point_sets.len());
for point_set in point_sets.iter() {
q_eval_sets.push(vec![C::Scalar::zero(); point_set.len()]);
}
{
let mut accumulate = |set_idx: usize, new_commitment, evals: Vec<C::Scalar>| {
q_commitments[set_idx].scale(x_4);
q_commitments[set_idx].add_term(C::Scalar::one(), new_commitment);
for (eval, set_eval) in evals.iter().zip(q_eval_sets[set_idx].iter_mut()) {
*set_eval *= &x_4;
*set_eval += eval;
}
};
// Each commitment corresponds to evaluations at a set of points.
// For each set, we collapse each commitment's evals pointwise.
for commitment_data in commitment_map.into_iter() {
accumulate(
commitment_data.set_index, // set_idx,
*commitment_data.commitment.0, // commitment,
commitment_data.evals, // evals
);
}
}
// 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 msm_eval = point_sets
.iter()
.zip(q_eval_sets.iter())
.zip(self.q_evals.iter())
.fold(
C::Scalar::zero(),
|msm_eval, ((points, evals), proof_eval)| {
let r_poly = lagrange_interpolate(points, evals);
let r_eval = eval_polynomial(&r_poly, x_6);
let eval = points.iter().fold(*proof_eval - &r_eval, |eval, point| {
eval * &(x_6 - &point).invert().unwrap()
});
msm_eval * &x_5 + &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);
let (commitment_msm, msm_eval) = q_commitments.into_iter().zip(self.q_evals.iter()).fold(
(commitment_msm, msm_eval),
|(mut commitment_msm, msm_eval), (q_commitment, q_eval)| {
commitment_msm.scale(x_7);
commitment_msm.add_msm(&q_commitment);
(commitment_msm, msm_eval * &x_7 + &q_eval)
},
);
// Verify the opening proof
self.opening
.verify(params, msm, transcript, x_6, commitment_msm, msm_eval)
}
}
#[doc(hidden)]
#[derive(Copy, Clone)]
pub struct CommitmentPointer<'a, C>(&'a C);
impl<'a, C> PartialEq for CommitmentPointer<'a, C> {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.0, other.0)
}
}
impl<'a, C: CurveAffine> Query<C::Scalar> for VerifierQuery<'a, C> {
type Commitment = CommitmentPointer<'a, C>;
fn get_point(&self) -> C::Scalar {
self.point
}
fn get_eval(&self) -> C::Scalar {
self.eval
}
fn get_commitment(&self) -> Self::Commitment {
CommitmentPointer(self.commitment)
}
}