Merge pull request #13 from zcash/accumulator

Support batching and accumulation in polynomial opening argument
This commit is contained in:
ebfull 2020-09-13 10:25:24 -06:00 committed by GitHub
commit 626ef64e47
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 284 additions and 66 deletions

View file

@ -345,6 +345,10 @@ fn test_proving() {
let proof = Proof::create::<DummyHash<Fq>, DummyHash<Fp>, _>(&params, &srs, &circuit)
.expect("proof generation should not fail");
assert!(proof.verify::<DummyHash<Fq>, DummyHash<Fp>>(&params, &srs));
let msm_default = params.empty_msm();
let msm = proof
.verify::<DummyHash<Fq>, DummyHash<Fp>>(&params, &srs, msm_default)
.unwrap();
assert!(msm.is_zero())
}
}

View file

@ -1,15 +1,19 @@
use super::{hash_point, Proof, SRS};
use super::{hash_point, Error, Proof, SRS};
use crate::arithmetic::{get_challenge_scalar, Challenge, Curve, CurveAffine, Field};
use crate::poly::{commitment::Params, Rotation};
use crate::poly::{
commitment::{Params, MSM},
Rotation,
};
use crate::transcript::Hasher;
impl<C: CurveAffine> Proof<C> {
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,
params: &Params<C>,
params: &'a Params<C>,
srs: &SRS<C>,
) -> bool {
msm: MSM<'a, C>,
) -> Result<MSM<'a, C>, Error> {
// Create a transcript for obtaining Fiat-Shamir challenges.
let mut transcript = HBase::init(C::Base::one());
@ -133,7 +137,7 @@ impl<C: CurveAffine> Proof<C> {
}
if h_eval != (expected_h_eval * &(x_3n - &C::Scalar::one())) {
return false;
return Err(Error::ConstraintSystemFailure);
}
// We are now convinced the circuit is satisfied so long as the
@ -261,12 +265,20 @@ impl<C: CurveAffine> Proof<C> {
}
// Verify the opening proof
self.opening.verify(
params,
&mut transcript,
x_6,
&f_commitment.to_affine(),
f_eval,
)
let guard = self
.opening
.verify(
params,
msm,
&mut transcript,
x_6,
&f_commitment.to_affine(),
f_eval,
)
.unwrap();
let msm_challenges = guard.use_challenges();
Ok(msm_challenges)
}
}

View file

@ -13,6 +13,14 @@ mod domain;
pub use domain::*;
/// This is an error that could occur during proving or circuit synthesis.
// TODO: these errors need to be cleaned up
#[derive(Debug)]
pub enum Error {
/// OpeningProof is not well-formed
OpeningError,
}
/// The basis over which a polynomial is described.
pub trait Basis: Clone + Debug + Send + Sync {}

View file

@ -4,7 +4,9 @@
//! [halo]: https://eprint.iacr.org/2019/1021
use super::{Coeff, LagrangeCoeff, Polynomial};
use crate::arithmetic::{best_fft, best_multiexp, parallelize, Curve, CurveAffine, Field};
use crate::arithmetic::{
best_fft, best_multiexp, parallelize, Challenge, Curve, CurveAffine, Field,
};
use crate::transcript::Hasher;
use std::ops::{Add, AddAssign, Mul, MulAssign};
@ -21,6 +23,96 @@ pub struct OpeningProof<C: CurveAffine> {
z2: C::Scalar,
}
/// An accumulator instance consisting of an evaluation claim and a proof.
#[derive(Debug, Clone)]
pub struct Accumulator<C: CurveAffine> {
/// The claimed output of the linear-time polycommit opening protocol
pub g: C,
/// A vector of 128-bit challenges sampled by the verifier, to be used in
/// computing g.
pub challenges_sq_packed: Vec<Challenge>,
}
/// A multiscalar multiplication in the polynomial commitment scheme
#[derive(Debug, Clone)]
pub struct MSM<'a, C: CurveAffine> {
params: &'a Params<C>,
g_scalars: Option<Vec<C::Scalar>>,
h_scalar: Option<C::Scalar>,
other_scalars: Vec<C::Scalar>,
other_bases: Vec<C>,
}
impl<'a, C: CurveAffine> MSM<'a, C> {
/// Add arbitrary term (the scalar and the point)
pub fn add_term(&mut self, scalar: C::Scalar, point: C) {
&self.other_scalars.push(scalar);
&self.other_bases.push(point);
}
/// Add a vector of scalars to `g_scalars`. This function will panic if the
/// caller provides a slice of scalars that is not of length `params.n`.
// TODO: parallelize
pub fn add_to_g(&mut self, scalars: &[C::Scalar]) {
assert_eq!(scalars.len(), self.params.n as usize);
if let Some(g_scalars) = &mut self.g_scalars {
for (g_scalar, scalar) in g_scalars.iter_mut().zip(scalars.iter()) {
*g_scalar += &scalar;
}
} else {
self.g_scalars = Some(scalars.to_vec());
}
}
/// Add term to h
pub fn add_to_h(&mut self, scalar: C::Scalar) {
self.h_scalar = self.h_scalar.map_or(Some(scalar), |a| Some(a + &scalar));
}
/// Scale all scalars in the MSM by some scaling factor
// TODO: parallelize
pub fn scale(&mut self, factor: C::Scalar) {
if let Some(g_scalars) = &mut self.g_scalars {
for g_scalar in g_scalars.iter_mut() {
*g_scalar *= &factor;
}
}
// TODO: parallelize
for other_scalar in self.other_scalars.iter_mut() {
*other_scalar *= &factor;
}
self.h_scalar = self.h_scalar.map(|a| a * &factor);
}
/// Perform multiexp and check that it results in zero
pub fn is_zero(self) -> bool {
let len = self.g_scalars.as_ref().map(|v| v.len()).unwrap_or(0)
+ self.h_scalar.map(|_| 1).unwrap_or(0)
+ self.other_scalars.len();
let mut scalars: Vec<C::Scalar> = Vec::with_capacity(len);
let mut bases: Vec<C> = Vec::with_capacity(len);
scalars.extend(&self.other_scalars);
bases.extend(&self.other_bases);
if let Some(h_scalar) = self.h_scalar {
scalars.push(h_scalar);
bases.push(self.params.h);
}
if let Some(g_scalars) = &self.g_scalars {
scalars.extend(g_scalars);
bases.extend(self.params.g.iter());
}
assert_eq!(scalars.len(), len);
bool::from(best_multiexp(&scalars, &bases).is_zero())
}
}
/// These are the public parameters for the polynomial commitment scheme.
#[derive(Debug)]
pub struct Params<C: CurveAffine> {
@ -152,6 +244,63 @@ impl<C: CurveAffine> Params<C> {
best_multiexp::<C>(&tmp_scalars, &tmp_bases)
}
/// Generates an empty multiscalar multiplication struct using the
/// appropriate params.
pub fn empty_msm(&self) -> MSM<C> {
let g_scalars = None;
let h_scalar = None;
let other_scalars = vec![];
let other_bases = vec![];
MSM {
params: &self,
g_scalars,
h_scalar,
other_scalars,
other_bases,
}
}
}
/// A guard returned by the verifier
#[derive(Debug, Clone)]
pub struct Guard<'a, C: CurveAffine> {
msm: MSM<'a, C>,
neg_z1: C::Scalar,
allinv: C::Scalar,
challenges_sq: Vec<C::Scalar>,
challenges_sq_packed: Vec<Challenge>,
}
impl<'a, C: CurveAffine> Guard<'a, C> {
/// Lets caller supply the challenges and obtain an MSM with updated
/// scalars and points.
pub fn use_challenges(mut self) -> MSM<'a, C> {
let s = compute_s(&self.challenges_sq, self.allinv * &self.neg_z1);
self.msm.add_to_g(&s);
self.msm
}
/// Lets caller supply the purported G point and simply appends it to
/// return an updated MSM.
pub fn use_g(mut self, g: C) -> (MSM<'a, C>, Accumulator<C>) {
&self.msm.add_term(self.neg_z1, g);
let accumulator = Accumulator {
g,
challenges_sq_packed: self.challenges_sq_packed,
};
(self.msm, accumulator)
}
/// Computes the g value when given a potential scalar as input.
pub fn compute_g(&self) -> C {
let s = compute_s(&self.challenges_sq, self.allinv);
best_multiexp(&s, &self.msm.params.g).to_affine()
}
}
/// Wrapper type around a blinding factor.
@ -265,7 +414,7 @@ fn test_opening_proof() {
transcript.absorb(Fp::from_bytes(&v.to_bytes()).unwrap()); // unlikely to fail since p ~ q
loop {
let mut transcript_dup = transcript.clone();
let transcript_dup = transcript.clone();
let opening_proof = OpeningProof::create(&params, &mut transcript, &px, blind, x);
if opening_proof.is_err() {
@ -273,8 +422,63 @@ fn test_opening_proof() {
transcript.absorb(Field::one());
} else {
let opening_proof = opening_proof.unwrap();
assert!(opening_proof.verify(&params, &mut transcript_dup, x, &p, v));
// Verify the opening proof
let guard = opening_proof
.verify(
&params,
params.empty_msm(),
&mut transcript_dup.clone(),
x,
&p,
v,
)
.unwrap();
// Test guard behavior prior to checking another proof
{
// Test use_challenges()
let msm_challenges = guard.clone().use_challenges();
assert!(msm_challenges.is_zero());
// Test use_g()
let g = guard.compute_g();
let (msm_g, _accumulator) = guard.clone().use_g(g);
assert!(msm_g.is_zero());
}
// Check another proof to populate `msm.g_scalars`
let msm = guard.use_challenges();
let guard = opening_proof
.verify(&params, msm, &mut transcript_dup.clone(), x, &p, v)
.unwrap();
// Test use_challenges()
let msm_challenges = guard.clone().use_challenges();
assert!(msm_challenges.is_zero());
// Test use_g()
let g = guard.compute_g();
let (msm_g, _accumulator) = guard.clone().use_g(g);
assert!(msm_g.is_zero());
break;
}
}
}
// TODO: parallelize
fn compute_s<F: Field>(challenges_sq: &[F], allinv: F) -> Vec<F> {
let lg_n = challenges_sq.len();
let n = 1 << lg_n;
let mut s = Vec::with_capacity(n);
s.push(allinv);
for i in 1..n {
let lg_i = (32 - 1 - (i as u32).leading_zeros()) as usize;
let k = 1 << lg_i;
let u_lg_i_sq = challenges_sq[(lg_n - 1) - lg_i];
s.push(s[i - k] * u_lg_i_sq);
}
s
}

View file

@ -1,25 +1,25 @@
use super::{OpeningProof, Params};
use super::super::Error;
use super::{Guard, OpeningProof, Params, MSM};
use crate::transcript::Hasher;
use crate::arithmetic::{
best_multiexp, get_challenge_scalar, Challenge, Curve, CurveAffine, Field,
};
use crate::arithmetic::{get_challenge_scalar, Challenge, CurveAffine, Field};
impl<C: CurveAffine> OpeningProof<C> {
/// Checks to see if an [`OpeningProof`] is valid given the current
/// `transcript`, and a point `x` that the polynomial commitment `p` opens
/// purportedly to the value `v`.
pub fn verify<H: Hasher<C::Base>>(
pub fn verify<'a, H: Hasher<C::Base>>(
&self,
params: &Params<C>,
params: &'a Params<C>,
mut msm: MSM<'a, C>,
transcript: &mut H,
x: C::Scalar,
p: &C,
v: C::Scalar,
) -> bool {
) -> Result<Guard<'a, C>, Error> {
// Check for well-formedness
if self.rounds.len() != params.k as usize {
return false;
return Err(Error::OpeningError);
}
transcript.absorb(C::Base::from_u64(self.fork as u64));
@ -31,7 +31,7 @@ impl<C: CurveAffine> OpeningProof<C> {
let u_y2 = u_x.square() * &u_x + &C::b();
let u_y = u_y2.deterministic_sqrt();
if u_y.is_none() {
return false;
return Err(Error::OpeningError);
}
let u_y = u_y.unwrap();
@ -45,14 +45,15 @@ impl<C: CurveAffine> OpeningProof<C> {
let mut challenges = Vec::with_capacity(self.rounds.len());
let mut challenges_inv = Vec::with_capacity(self.rounds.len());
let mut challenges_sq = Vec::with_capacity(self.rounds.len());
let mut allinv = Field::one();
let mut challenges_sq_packed: Vec<Challenge> = Vec::with_capacity(self.rounds.len());
let mut allinv = C::Scalar::one();
for round in &self.rounds {
// Feed L and R into the transcript.
let l = round.0.get_xy();
let r = round.1.get_xy();
if bool::from(l.is_none() | r.is_none()) {
return false;
return Err(Error::OpeningError);
}
let l = l.unwrap();
let r = r.unwrap();
@ -66,7 +67,7 @@ impl<C: CurveAffine> OpeningProof<C> {
let challenge = challenge_sq.deterministic_sqrt();
if challenge.is_none() {
// We didn't sample a square.
return false;
return Err(Error::OpeningError);
}
let challenge = challenge.unwrap();
@ -74,10 +75,10 @@ impl<C: CurveAffine> OpeningProof<C> {
if bool::from(challenge_inv.is_none()) {
// We sampled zero for some reason, unlikely to happen by
// chance.
return false;
return Err(Error::OpeningError);
}
let challenge_inv = challenge_inv.unwrap();
allinv *= challenge_inv;
allinv *= &challenge_inv;
let challenge_sq_inv = challenge_inv.square();
@ -89,11 +90,12 @@ impl<C: CurveAffine> OpeningProof<C> {
challenges.push(challenge);
challenges_inv.push(challenge_inv);
challenges_sq.push(challenge_sq);
challenges_sq_packed.push(Challenge(challenge_sq_packed));
}
let delta = self.delta.get_xy();
if bool::from(delta.is_none()) {
return false;
return Err(Error::OpeningError);
}
let delta = delta.unwrap();
@ -109,40 +111,45 @@ impl<C: CurveAffine> OpeningProof<C> {
// [c] P + [c * v] U + [c] sum(L_i * u_i^2) + [c] sum(R_i * u_i^-2) + delta - [z1] G - [z1 * b] U - [z2] H
// = 0
// 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. It's a way of keeping the MSM's linearly
// independent.
msm.scale(C::Scalar::random());
for scalar in &mut extra_scalars {
*scalar *= &c;
}
for (scalar, base) in extra_scalars.iter().zip(extra_bases.iter()) {
msm.add_term(*scalar, *base);
}
let b = compute_b(x, &challenges, &challenges_inv);
let neg_z1 = -self.z1;
// [c] P
extra_bases.push(*p);
extra_scalars.push(c);
msm.add_term(c, *p);
// [c * v] U - [z1 * b] U
extra_bases.push(u);
extra_scalars.push((c * &v) + &(neg_z1 * &b));
msm.add_term((c * &v) + &(neg_z1 * &b), u);
// delta
extra_bases.push(self.delta);
extra_scalars.push(Field::one());
msm.add_term(Field::one(), self.delta);
// - [z2] H
extra_bases.push(params.h);
extra_scalars.push(-self.z2);
msm.add_to_h(-self.z2);
// - [z1] G
extra_bases.extend(&params.g);
let mut s = compute_s(&challenges_sq, allinv);
// TODO: parallelize
for s in &mut s {
*s *= &neg_z1;
}
extra_scalars.extend(s);
let guard = Guard {
msm,
neg_z1,
allinv,
challenges_sq,
challenges_sq_packed,
};
bool::from(best_multiexp(&extra_scalars, &extra_bases).is_zero())
Ok(guard)
}
}
@ -160,20 +167,3 @@ fn compute_b<F: Field>(x: F, challenges: &[F], challenges_inv: &[F]) -> F {
)
}
}
// TODO: parallelize
fn compute_s<F: Field>(challenges_sq: &[F], allinv: F) -> Vec<F> {
let lg_n = challenges_sq.len();
let n = 1 << lg_n;
let mut s = Vec::with_capacity(n);
s.push(allinv);
for i in 1..n {
let lg_i = (32 - 1 - (i as u32).leading_zeros()) as usize;
let k = 1 << lg_i;
let u_lg_i_sq = challenges_sq[(lg_n - 1) - lg_i];
s.push(s[i - k] * u_lg_i_sq);
}
s
}