mirror of https://github.com/zcash/halo2.git
Merge pull request #26 from zcash/multipoint-opening
Optimise multi-point opening
This commit is contained in:
commit
18746f109c
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ pub trait Field:
|
|||
+ for<'a> SubAssign<&'a Self>
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ PartialOrd
|
||||
+ Ord
|
||||
+ ConditionallySelectable
|
||||
+ ConstantTimeEq
|
||||
+ Group<Scalar = Self>
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -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([
|
||||
|
|
14
src/plonk.rs
14
src/plonk.rs
|
@ -7,7 +7,7 @@
|
|||
|
||||
use crate::arithmetic::CurveAffine;
|
||||
use crate::poly::{
|
||||
commitment, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial,
|
||||
multiopen, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial,
|
||||
};
|
||||
use crate::transcript::Hasher;
|
||||
|
||||
|
@ -59,9 +59,7 @@ pub struct Proof<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>>(¶ms, pk.get_vk(), msm, &[pubinput])
|
||||
.verify::<DummyHash<Fq>, DummyHash<Fp>>(¶ms, 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>>(¶ms, pk.get_vk(), msm, &[pubinput])
|
||||
.verify::<DummyHash<Fq>, DummyHash<Fp>>(¶ms, pk.get_vk(), msm, pubinput_slice)
|
||||
.unwrap();
|
||||
{
|
||||
let msm = guard.clone().use_challenges();
|
||||
|
|
|
@ -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(¶ms, &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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
|
|
@ -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!(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(¶ms, &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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue