mirror of https://github.com/zcash/halo2.git
Avoid square challenges and forking in inner product argument
This modifies the scheme to be almost identical to the construction outlined in Appenix A.2 of "Proof-Carrying Data from Accumulation Schemes" (https://eprint.iacr.org/2020/499). The only remaining difference is that we do not compute [v] U but instead subtract [v] G_0 from the commitment before opening.
This commit is contained in:
parent
c8dedf2ec3
commit
98c1d80c90
|
@ -3,8 +3,8 @@ use ff::Field;
|
|||
use super::super::{Coeff, Polynomial};
|
||||
use super::{Blind, Params};
|
||||
use crate::arithmetic::{
|
||||
best_multiexp, compute_inner_product, eval_polynomial, parallelize, small_multiexp, Curve,
|
||||
CurveAffine, FieldExt,
|
||||
best_multiexp, compute_inner_product, eval_polynomial, parallelize, Curve, CurveAffine,
|
||||
FieldExt,
|
||||
};
|
||||
use crate::transcript::{Challenge, ChallengeScalar, TranscriptWrite};
|
||||
use std::io;
|
||||
|
@ -84,9 +84,8 @@ pub fn create_proof<C: CurveAffine, T: TranscriptWrite<C>>(
|
|||
}
|
||||
}
|
||||
|
||||
// Initialize the vector `G` from the SRS. We'll be progressively
|
||||
// collapsing this vector into smaller and smaller vectors until it is
|
||||
// of length 1.
|
||||
// Initialize the vector `G` from the SRS. We'll be progressively collapsing
|
||||
// this vector into smaller and smaller vectors until it is of length 1.
|
||||
let mut g = params.g.clone();
|
||||
|
||||
// Perform the inner product argument, round by round.
|
||||
|
@ -98,76 +97,42 @@ pub fn create_proof<C: CurveAffine, T: TranscriptWrite<C>>(
|
|||
// TODO: If we modify multiexp to take "extra" bases, we could speed
|
||||
// this piece up a bit by combining the multiexps.
|
||||
metrics::counter!("multiexp", 2, "val" => "l/r", "size" => format!("{}", half));
|
||||
let l = best_multiexp(&a[0..half], &g[half..]);
|
||||
let r = best_multiexp(&a[half..], &g[0..half]);
|
||||
let value_l = compute_inner_product(&a[0..half], &b[half..]);
|
||||
let value_r = compute_inner_product(&a[half..], &b[0..half]);
|
||||
let mut l_randomness = C::Scalar::rand();
|
||||
let l = best_multiexp(&a[half..], &g[0..half]);
|
||||
let r = best_multiexp(&a[0..half], &g[half..]);
|
||||
let value_l = compute_inner_product(&a[half..], &b[0..half]);
|
||||
let value_r = compute_inner_product(&a[0..half], &b[half..]);
|
||||
let l_randomness = C::Scalar::rand();
|
||||
let r_randomness = C::Scalar::rand();
|
||||
metrics::counter!("multiexp", 2, "val" => "l/r", "size" => "2");
|
||||
let l = l + &best_multiexp(&[value_l * &z, l_randomness], &[params.u, params.h]);
|
||||
let r = r + &best_multiexp(&[value_r * &z, r_randomness], &[params.u, params.h]);
|
||||
let mut l = l.to_affine();
|
||||
let l = l.to_affine();
|
||||
let r = r.to_affine();
|
||||
|
||||
let challenge = loop {
|
||||
// We'll fork the transcript and adjust our randomness
|
||||
// until the challenge is a square.
|
||||
let mut transcript = transcript.fork();
|
||||
|
||||
// Feed L and R into the cloned transcript.
|
||||
// We expect these to not be points at infinity due to the randomness.
|
||||
transcript.write_point(l)?;
|
||||
transcript.write_point(r)?;
|
||||
|
||||
// ... and get the squared challenge.
|
||||
let challenge_sq_packed = Challenge::get(&mut transcript);
|
||||
let challenge_sq = *ChallengeScalar::<C, ()>::from(challenge_sq_packed);
|
||||
|
||||
// There might be no square root, in which case we'll fork the
|
||||
// transcript.
|
||||
let challenge = challenge_sq.deterministic_sqrt();
|
||||
if let Some(challenge) = challenge {
|
||||
break challenge;
|
||||
} else {
|
||||
// Try again, with slightly different randomness
|
||||
l = (l + params.h).to_affine();
|
||||
l_randomness += &C::Scalar::one();
|
||||
}
|
||||
};
|
||||
|
||||
// Challenge is unlikely to be zero.
|
||||
let challenge_inv = challenge.invert().unwrap();
|
||||
let challenge_sq_inv = challenge_inv.square();
|
||||
let challenge_sq = challenge.square();
|
||||
|
||||
// Feed L and R into the real transcript
|
||||
transcript.write_point(l)?;
|
||||
transcript.write_point(r)?;
|
||||
|
||||
// And obtain the challenge, even though we already have it, since
|
||||
// squeezing affects the transcript.
|
||||
{
|
||||
let challenge_sq_expected = ChallengeScalar::<_, ()>::get(transcript);
|
||||
assert_eq!(challenge_sq, *challenge_sq_expected);
|
||||
}
|
||||
let challenge_packed = Challenge::get(transcript);
|
||||
let challenge = *ChallengeScalar::<C, ()>::from(challenge_packed);
|
||||
let challenge_inv = challenge.invert().unwrap(); // TODO, bubble this up
|
||||
|
||||
// Collapse `a` and `b`.
|
||||
// TODO: parallelize
|
||||
for i in 0..half {
|
||||
a[i] = (a[i] * &challenge) + &(a[i + half] * &challenge_inv);
|
||||
b[i] = (b[i] * &challenge_inv) + &(b[i + half] * &challenge);
|
||||
a[i] = a[i] + &(a[i + half] * &challenge_inv);
|
||||
b[i] = b[i] + &(b[i + half] * &challenge);
|
||||
}
|
||||
a.truncate(half);
|
||||
b.truncate(half);
|
||||
|
||||
// Collapse `G`
|
||||
parallel_generator_collapse(&mut g, challenge, challenge_inv);
|
||||
parallel_generator_collapse(&mut g, challenge);
|
||||
g.truncate(half);
|
||||
|
||||
// Update randomness (the synthetic blinding factor at the end)
|
||||
blind += &(l_randomness * &challenge_sq);
|
||||
blind += &(r_randomness * &challenge_sq_inv);
|
||||
blind += &(l_randomness * &challenge_inv);
|
||||
blind += &(r_randomness * &challenge);
|
||||
}
|
||||
|
||||
// We have fully collapsed `a`, `b`, `G`
|
||||
|
@ -180,20 +145,16 @@ pub fn create_proof<C: CurveAffine, T: TranscriptWrite<C>>(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn parallel_generator_collapse<C: CurveAffine>(
|
||||
g: &mut [C],
|
||||
challenge: C::Scalar,
|
||||
challenge_inv: C::Scalar,
|
||||
) {
|
||||
fn parallel_generator_collapse<C: CurveAffine>(g: &mut [C], challenge: C::Scalar) {
|
||||
let len = g.len() / 2;
|
||||
let (mut g_lo, g_hi) = g.split_at_mut(len);
|
||||
metrics::counter!("multiexp", len as u64, "size" => "2", "fn" => "parallel_generator_collapse");
|
||||
metrics::counter!("scalar_multiplication", len as u64, "fn" => "parallel_generator_collapse");
|
||||
|
||||
parallelize(&mut g_lo, |g_lo, start| {
|
||||
let g_hi = &g_hi[start..];
|
||||
let mut tmp = Vec::with_capacity(g_lo.len());
|
||||
for (g_lo, g_hi) in g_lo.iter().zip(g_hi.iter()) {
|
||||
tmp.push(small_multiexp(&[challenge_inv, challenge], &[*g_lo, *g_hi]));
|
||||
tmp.push(g_lo.to_projective() + &(*g_hi * challenge));
|
||||
}
|
||||
C::Projective::batch_to_affine(&tmp, g_lo);
|
||||
});
|
||||
|
|
|
@ -4,16 +4,15 @@ use super::super::Error;
|
|||
use super::{Params, MSM};
|
||||
use crate::transcript::{Challenge, ChallengeScalar, TranscriptRead};
|
||||
|
||||
use crate::arithmetic::{best_multiexp, Curve, CurveAffine, FieldExt};
|
||||
use crate::arithmetic::{best_multiexp, BatchInvert, Curve, CurveAffine};
|
||||
|
||||
/// A guard returned by the verifier
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Guard<'a, C: CurveAffine> {
|
||||
msm: MSM<'a, C>,
|
||||
neg_a: C::Scalar,
|
||||
allinv: C::Scalar,
|
||||
challenges_sq: Vec<C::Scalar>,
|
||||
challenges_sq_packed: Vec<Challenge>,
|
||||
challenges: Vec<C::Scalar>,
|
||||
challenges_packed: Vec<Challenge>,
|
||||
}
|
||||
|
||||
/// An accumulator instance consisting of an evaluation claim and a proof.
|
||||
|
@ -24,14 +23,14 @@ pub struct Accumulator<C: CurveAffine> {
|
|||
|
||||
/// A vector of 128-bit challenges sampled by the verifier, to be used in
|
||||
/// computing g.
|
||||
pub challenges_sq_packed: Vec<Challenge>,
|
||||
pub challenges_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_a);
|
||||
let s = compute_s(&self.challenges, self.neg_a);
|
||||
self.msm.add_to_g_scalars(&s);
|
||||
self.msm.add_to_h_scalar(self.neg_a);
|
||||
|
||||
|
@ -45,7 +44,7 @@ impl<'a, C: CurveAffine> Guard<'a, C> {
|
|||
|
||||
let accumulator = Accumulator {
|
||||
g,
|
||||
challenges_sq_packed: self.challenges_sq_packed,
|
||||
challenges_packed: self.challenges_packed,
|
||||
};
|
||||
|
||||
(self.msm, accumulator)
|
||||
|
@ -53,7 +52,7 @@ impl<'a, C: CurveAffine> Guard<'a, C> {
|
|||
|
||||
/// Computes G + H, where G = ⟨s, params.g⟩ and H is used for blinding
|
||||
pub fn compute_g(&self) -> C {
|
||||
let s = compute_s(&self.challenges_sq, self.allinv);
|
||||
let s = compute_s(&self.challenges, C::Scalar::one());
|
||||
|
||||
metrics::increment_counter!("multiexp", "size" => format!("{}", s.len()), "fn" => "compute_g");
|
||||
let mut tmp = best_multiexp(&s, &self.msm.params.g);
|
||||
|
@ -84,46 +83,37 @@ pub fn verify_proof<'a, C: CurveAffine, T: TranscriptRead<C>>(
|
|||
|
||||
let z = *ChallengeScalar::<C, ()>::get(transcript);
|
||||
|
||||
// Data about the challenges from each of the rounds.
|
||||
let mut challenges = Vec::with_capacity(k);
|
||||
let mut challenges_inv = Vec::with_capacity(k);
|
||||
let mut challenges_sq = Vec::with_capacity(k);
|
||||
let mut challenges_sq_packed: Vec<Challenge> = Vec::with_capacity(k);
|
||||
let mut allinv = C::Scalar::one();
|
||||
|
||||
let mut rounds = vec![];
|
||||
for _ in 0..k {
|
||||
// Read L and R from the proof and write them to the transcript
|
||||
let l = transcript.read_point().map_err(|_| Error::OpeningError)?;
|
||||
let r = transcript.read_point().map_err(|_| Error::OpeningError)?;
|
||||
|
||||
let challenge_sq_packed = Challenge::get(transcript);
|
||||
let challenge_sq = *ChallengeScalar::<C, ()>::from(challenge_sq_packed);
|
||||
let challenge_packed = Challenge::get(transcript);
|
||||
let challenge = *ChallengeScalar::<C, ()>::from(challenge_packed);
|
||||
|
||||
let challenge = challenge_sq.deterministic_sqrt();
|
||||
if challenge.is_none() {
|
||||
// We didn't sample a square.
|
||||
return Err(Error::OpeningError);
|
||||
rounds.push((
|
||||
l,
|
||||
r,
|
||||
challenge,
|
||||
/* to be inverted */ challenge,
|
||||
challenge_packed,
|
||||
));
|
||||
}
|
||||
let challenge = challenge.unwrap();
|
||||
|
||||
let challenge_inv = challenge.invert();
|
||||
if bool::from(challenge_inv.is_none()) {
|
||||
// We sampled zero for some reason, unlikely to happen by
|
||||
// chance.
|
||||
return Err(Error::OpeningError);
|
||||
}
|
||||
let challenge_inv = challenge_inv.unwrap();
|
||||
allinv *= &challenge_inv;
|
||||
rounds
|
||||
.iter_mut()
|
||||
.map(|&mut (_, _, _, ref mut challenge, _)| challenge)
|
||||
.batch_invert();
|
||||
|
||||
let challenge_sq_inv = challenge_inv.square();
|
||||
|
||||
msm.append_term(challenge_sq, l);
|
||||
msm.append_term(challenge_sq_inv, r);
|
||||
let mut challenges = Vec::with_capacity(k);
|
||||
let mut challenges_packed: Vec<Challenge> = Vec::with_capacity(k);
|
||||
for (l, r, challenge, challenge_inv, challenge_packed) in rounds {
|
||||
msm.append_term(challenge_inv, l);
|
||||
msm.append_term(challenge, r);
|
||||
|
||||
challenges.push(challenge);
|
||||
challenges_inv.push(challenge_inv);
|
||||
challenges_sq.push(challenge_sq);
|
||||
challenges_sq_packed.push(challenge_sq_packed);
|
||||
challenges_packed.push(challenge_packed);
|
||||
}
|
||||
|
||||
// Our goal is to open
|
||||
|
@ -142,7 +132,7 @@ pub fn verify_proof<'a, C: CurveAffine, T: TranscriptRead<C>>(
|
|||
let a = transcript.read_scalar().map_err(|_| Error::SamplingError)?;
|
||||
let neg_a = -a;
|
||||
let h = transcript.read_scalar().map_err(|_| Error::SamplingError)?;
|
||||
let b = compute_b(x, &challenges, &challenges_inv);
|
||||
let b = compute_b(x, &challenges);
|
||||
|
||||
msm.add_to_u_scalar(neg_a * &b * &z);
|
||||
msm.add_to_h_scalar(a - &h);
|
||||
|
@ -150,42 +140,43 @@ pub fn verify_proof<'a, C: CurveAffine, T: TranscriptRead<C>>(
|
|||
let guard = Guard {
|
||||
msm,
|
||||
neg_a,
|
||||
allinv,
|
||||
challenges_sq,
|
||||
challenges_sq_packed,
|
||||
challenges,
|
||||
challenges_packed,
|
||||
};
|
||||
|
||||
Ok(guard)
|
||||
}
|
||||
|
||||
fn compute_b<F: Field>(x: F, challenges: &[F], challenges_inv: &[F]) -> F {
|
||||
assert!(!challenges.is_empty());
|
||||
assert_eq!(challenges.len(), challenges_inv.len());
|
||||
if challenges.len() == 1 {
|
||||
*challenges_inv.last().unwrap() + *challenges.last().unwrap() * x
|
||||
} else {
|
||||
(*challenges_inv.last().unwrap() + *challenges.last().unwrap() * x)
|
||||
* compute_b(
|
||||
x.square(),
|
||||
&challenges[0..(challenges.len() - 1)],
|
||||
&challenges_inv[0..(challenges.len() - 1)],
|
||||
)
|
||||
/// Computes $\prod\limits_{i=0}^{k-1} (1 + u_i x^{2^i})$.
|
||||
fn compute_b<F: Field>(x: F, challenges: &[F]) -> F {
|
||||
let mut tmp = F::one();
|
||||
let mut cur = x;
|
||||
for challenge in challenges.iter().rev() {
|
||||
tmp *= F::one() + &(*challenge * &cur);
|
||||
cur *= cur;
|
||||
}
|
||||
tmp
|
||||
}
|
||||
|
||||
// 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;
|
||||
/// Computes the coefficients of $g(X) = \prod\limits_{i=0}^{k-1} (1 + u_i X^{2^i})$.
|
||||
fn compute_s<F: Field>(challenges: &[F], init: F) -> Vec<F> {
|
||||
assert!(challenges.len() > 0);
|
||||
let mut v = vec![F::zero(); 1 << challenges.len()];
|
||||
v[0] = init;
|
||||
|
||||
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);
|
||||
for (len, challenge) in challenges
|
||||
.iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.map(|(i, challenge)| (1 << i, challenge))
|
||||
{
|
||||
let (left, right) = v.split_at_mut(len);
|
||||
let right = &mut right[0..len];
|
||||
right.copy_from_slice(&left);
|
||||
for v in right {
|
||||
*v *= challenge;
|
||||
}
|
||||
}
|
||||
|
||||
s
|
||||
v
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue