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:
Sean Bowe 2020-12-24 12:27:05 -07:00
parent c8dedf2ec3
commit 98c1d80c90
No known key found for this signature in database
GPG Key ID: 95684257D8F8B031
2 changed files with 78 additions and 126 deletions

View File

@ -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);
});

View File

@ -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);
}
let challenge = challenge.unwrap();
rounds.push((
l,
r,
challenge,
/* to be inverted */ challenge,
challenge_packed,
));
}
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
}