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::super::{Coeff, Polynomial};
use super::{Blind, Params}; use super::{Blind, Params};
use crate::arithmetic::{ use crate::arithmetic::{
best_multiexp, compute_inner_product, eval_polynomial, parallelize, small_multiexp, Curve, best_multiexp, compute_inner_product, eval_polynomial, parallelize, Curve, CurveAffine,
CurveAffine, FieldExt, FieldExt,
}; };
use crate::transcript::{Challenge, ChallengeScalar, TranscriptWrite}; use crate::transcript::{Challenge, ChallengeScalar, TranscriptWrite};
use std::io; 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 // Initialize the vector `G` from the SRS. We'll be progressively collapsing
// collapsing this vector into smaller and smaller vectors until it is // this vector into smaller and smaller vectors until it is of length 1.
// of length 1.
let mut g = params.g.clone(); let mut g = params.g.clone();
// Perform the inner product argument, round by round. // 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 // TODO: If we modify multiexp to take "extra" bases, we could speed
// this piece up a bit by combining the multiexps. // this piece up a bit by combining the multiexps.
metrics::counter!("multiexp", 2, "val" => "l/r", "size" => format!("{}", half)); metrics::counter!("multiexp", 2, "val" => "l/r", "size" => format!("{}", half));
let l = best_multiexp(&a[0..half], &g[half..]); let l = best_multiexp(&a[half..], &g[0..half]);
let r = best_multiexp(&a[half..], &g[0..half]); let r = best_multiexp(&a[0..half], &g[half..]);
let value_l = compute_inner_product(&a[0..half], &b[half..]); let value_l = compute_inner_product(&a[half..], &b[0..half]);
let value_r = compute_inner_product(&a[half..], &b[0..half]); let value_r = compute_inner_product(&a[0..half], &b[half..]);
let mut l_randomness = C::Scalar::rand(); let l_randomness = C::Scalar::rand();
let r_randomness = C::Scalar::rand(); let r_randomness = C::Scalar::rand();
metrics::counter!("multiexp", 2, "val" => "l/r", "size" => "2"); metrics::counter!("multiexp", 2, "val" => "l/r", "size" => "2");
let l = l + &best_multiexp(&[value_l * &z, l_randomness], &[params.u, params.h]); 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 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 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 // Feed L and R into the real transcript
transcript.write_point(l)?; transcript.write_point(l)?;
transcript.write_point(r)?; transcript.write_point(r)?;
// And obtain the challenge, even though we already have it, since let challenge_packed = Challenge::get(transcript);
// squeezing affects the transcript. let challenge = *ChallengeScalar::<C, ()>::from(challenge_packed);
{ let challenge_inv = challenge.invert().unwrap(); // TODO, bubble this up
let challenge_sq_expected = ChallengeScalar::<_, ()>::get(transcript);
assert_eq!(challenge_sq, *challenge_sq_expected);
}
// Collapse `a` and `b`. // Collapse `a` and `b`.
// TODO: parallelize // TODO: parallelize
for i in 0..half { for i in 0..half {
a[i] = (a[i] * &challenge) + &(a[i + half] * &challenge_inv); a[i] = a[i] + &(a[i + half] * &challenge_inv);
b[i] = (b[i] * &challenge_inv) + &(b[i + half] * &challenge); b[i] = b[i] + &(b[i + half] * &challenge);
} }
a.truncate(half); a.truncate(half);
b.truncate(half); b.truncate(half);
// Collapse `G` // Collapse `G`
parallel_generator_collapse(&mut g, challenge, challenge_inv); parallel_generator_collapse(&mut g, challenge);
g.truncate(half); g.truncate(half);
// Update randomness (the synthetic blinding factor at the end) // Update randomness (the synthetic blinding factor at the end)
blind += &(l_randomness * &challenge_sq); blind += &(l_randomness * &challenge_inv);
blind += &(r_randomness * &challenge_sq_inv); blind += &(r_randomness * &challenge);
} }
// We have fully collapsed `a`, `b`, `G` // We have fully collapsed `a`, `b`, `G`
@ -180,20 +145,16 @@ pub fn create_proof<C: CurveAffine, T: TranscriptWrite<C>>(
Ok(()) Ok(())
} }
fn parallel_generator_collapse<C: CurveAffine>( fn parallel_generator_collapse<C: CurveAffine>(g: &mut [C], challenge: C::Scalar) {
g: &mut [C],
challenge: C::Scalar,
challenge_inv: C::Scalar,
) {
let len = g.len() / 2; let len = g.len() / 2;
let (mut g_lo, g_hi) = g.split_at_mut(len); 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| { parallelize(&mut g_lo, |g_lo, start| {
let g_hi = &g_hi[start..]; let g_hi = &g_hi[start..];
let mut tmp = Vec::with_capacity(g_lo.len()); let mut tmp = Vec::with_capacity(g_lo.len());
for (g_lo, g_hi) in g_lo.iter().zip(g_hi.iter()) { 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); C::Projective::batch_to_affine(&tmp, g_lo);
}); });

View File

@ -4,16 +4,15 @@ use super::super::Error;
use super::{Params, MSM}; use super::{Params, MSM};
use crate::transcript::{Challenge, ChallengeScalar, TranscriptRead}; 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 /// A guard returned by the verifier
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Guard<'a, C: CurveAffine> { pub struct Guard<'a, C: CurveAffine> {
msm: MSM<'a, C>, msm: MSM<'a, C>,
neg_a: C::Scalar, neg_a: C::Scalar,
allinv: C::Scalar, challenges: Vec<C::Scalar>,
challenges_sq: Vec<C::Scalar>, challenges_packed: Vec<Challenge>,
challenges_sq_packed: Vec<Challenge>,
} }
/// An accumulator instance consisting of an evaluation claim and a proof. /// 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 /// A vector of 128-bit challenges sampled by the verifier, to be used in
/// computing g. /// computing g.
pub challenges_sq_packed: Vec<Challenge>, pub challenges_packed: Vec<Challenge>,
} }
impl<'a, C: CurveAffine> Guard<'a, C> { impl<'a, C: CurveAffine> Guard<'a, C> {
/// Lets caller supply the challenges and obtain an MSM with updated /// Lets caller supply the challenges and obtain an MSM with updated
/// scalars and points. /// scalars and points.
pub fn use_challenges(mut self) -> MSM<'a, C> { 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_g_scalars(&s);
self.msm.add_to_h_scalar(self.neg_a); self.msm.add_to_h_scalar(self.neg_a);
@ -45,7 +44,7 @@ impl<'a, C: CurveAffine> Guard<'a, C> {
let accumulator = Accumulator { let accumulator = Accumulator {
g, g,
challenges_sq_packed: self.challenges_sq_packed, challenges_packed: self.challenges_packed,
}; };
(self.msm, accumulator) (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 /// Computes G + H, where G = ⟨s, params.g⟩ and H is used for blinding
pub fn compute_g(&self) -> C { 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"); metrics::increment_counter!("multiexp", "size" => format!("{}", s.len()), "fn" => "compute_g");
let mut tmp = best_multiexp(&s, &self.msm.params.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); let z = *ChallengeScalar::<C, ()>::get(transcript);
// Data about the challenges from each of the rounds. let mut rounds = vec![];
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();
for _ in 0..k { for _ in 0..k {
// Read L and R from the proof and write them to the transcript // Read L and R from the proof and write them to the transcript
let l = transcript.read_point().map_err(|_| Error::OpeningError)?; let l = transcript.read_point().map_err(|_| Error::OpeningError)?;
let r = 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_packed = Challenge::get(transcript);
let challenge_sq = *ChallengeScalar::<C, ()>::from(challenge_sq_packed); let challenge = *ChallengeScalar::<C, ()>::from(challenge_packed);
let challenge = challenge_sq.deterministic_sqrt(); rounds.push((
if challenge.is_none() { l,
// We didn't sample a square. r,
return Err(Error::OpeningError); challenge,
} /* to be inverted */ challenge,
let challenge = challenge.unwrap(); challenge_packed,
));
}
let challenge_inv = challenge.invert(); rounds
if bool::from(challenge_inv.is_none()) { .iter_mut()
// We sampled zero for some reason, unlikely to happen by .map(|&mut (_, _, _, ref mut challenge, _)| challenge)
// chance. .batch_invert();
return Err(Error::OpeningError);
}
let challenge_inv = challenge_inv.unwrap();
allinv *= &challenge_inv;
let challenge_sq_inv = challenge_inv.square(); let mut challenges = Vec::with_capacity(k);
let mut challenges_packed: Vec<Challenge> = Vec::with_capacity(k);
msm.append_term(challenge_sq, l); for (l, r, challenge, challenge_inv, challenge_packed) in rounds {
msm.append_term(challenge_sq_inv, r); msm.append_term(challenge_inv, l);
msm.append_term(challenge, r);
challenges.push(challenge); challenges.push(challenge);
challenges_inv.push(challenge_inv); challenges_packed.push(challenge_packed);
challenges_sq.push(challenge_sq);
challenges_sq_packed.push(challenge_sq_packed);
} }
// Our goal is to open // 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 a = transcript.read_scalar().map_err(|_| Error::SamplingError)?;
let neg_a = -a; let neg_a = -a;
let h = transcript.read_scalar().map_err(|_| Error::SamplingError)?; 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_u_scalar(neg_a * &b * &z);
msm.add_to_h_scalar(a - &h); msm.add_to_h_scalar(a - &h);
@ -150,42 +140,43 @@ pub fn verify_proof<'a, C: CurveAffine, T: TranscriptRead<C>>(
let guard = Guard { let guard = Guard {
msm, msm,
neg_a, neg_a,
allinv, challenges,
challenges_sq, challenges_packed,
challenges_sq_packed,
}; };
Ok(guard) Ok(guard)
} }
fn compute_b<F: Field>(x: F, challenges: &[F], challenges_inv: &[F]) -> F { /// Computes $\prod\limits_{i=0}^{k-1} (1 + u_i x^{2^i})$.
assert!(!challenges.is_empty()); fn compute_b<F: Field>(x: F, challenges: &[F]) -> F {
assert_eq!(challenges.len(), challenges_inv.len()); let mut tmp = F::one();
if challenges.len() == 1 { let mut cur = x;
*challenges_inv.last().unwrap() + *challenges.last().unwrap() * x for challenge in challenges.iter().rev() {
} else { tmp *= F::one() + &(*challenge * &cur);
(*challenges_inv.last().unwrap() + *challenges.last().unwrap() * x) cur *= cur;
* compute_b(
x.square(),
&challenges[0..(challenges.len() - 1)],
&challenges_inv[0..(challenges.len() - 1)],
)
} }
tmp
} }
// TODO: parallelize /// Computes the coefficients of $g(X) = \prod\limits_{i=0}^{k-1} (1 + u_i X^{2^i})$.
fn compute_s<F: Field>(challenges_sq: &[F], allinv: F) -> Vec<F> { fn compute_s<F: Field>(challenges: &[F], init: F) -> Vec<F> {
let lg_n = challenges_sq.len(); assert!(challenges.len() > 0);
let n = 1 << lg_n; let mut v = vec![F::zero(); 1 << challenges.len()];
v[0] = init;
let mut s = Vec::with_capacity(n); for (len, challenge) in challenges
s.push(allinv); .iter()
for i in 1..n { .rev()
let lg_i = (32 - 1 - (i as u32).leading_zeros()) as usize; .enumerate()
let k = 1 << lg_i; .map(|(i, challenge)| (1 << i, challenge))
let u_lg_i_sq = challenges_sq[(lg_n - 1) - lg_i]; {
s.push(s[i - k] * u_lg_i_sq); 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
} }