Modification of the polynomial commitment scheme to compensate for Transcript API changes.

This commit is contained in:
Sean Bowe 2020-12-21 12:34:51 -07:00
parent fb232ddec0
commit d30c6b62e4
No known key found for this signature in database
GPG Key ID: 95684257D8F8B031
4 changed files with 383 additions and 379 deletions

View File

@ -5,9 +5,10 @@
use super::{Coeff, LagrangeCoeff, Polynomial};
use crate::arithmetic::{best_fft, best_multiexp, parallelize, Curve, CurveAffine, FieldExt};
use crate::transcript::Hasher;
use crate::transcript::{Transcript, TranscriptWrite};
use ff::{Field, PrimeField};
use std::io;
use std::ops::{Add, AddAssign, Mul, MulAssign};
mod msm;
@ -15,7 +16,8 @@ mod prover;
mod verifier;
pub use msm::MSM;
pub use verifier::{Accumulator, Guard};
pub use prover::create_proof;
pub use verifier::{verify_proof, Accumulator, Guard};
/// These are the public parameters for the polynomial commitment scheme.
#[derive(Debug)]
@ -25,21 +27,13 @@ pub struct Params<C: CurveAffine> {
pub(crate) g: Vec<C>,
pub(crate) g_lagrange: Vec<C>,
pub(crate) h: C,
}
/// This is a proof object for the polynomial commitment scheme opening.
#[derive(Debug, Clone)]
pub struct Proof<C: CurveAffine> {
rounds: Vec<(C, C)>,
delta: C,
z1: C::Scalar,
z2: C::Scalar,
pub(crate) u: C,
}
impl<C: CurveAffine> Params<C> {
/// Initializes parameters for the curve, given a random oracle to draw
/// points from.
pub fn new<H: Hasher<C::Base>>(k: u32) -> Self {
pub fn new<T: TranscriptWrite<io::Sink, C> + Sync>(k: u32) -> Self {
// This is usually a limitation on the curve, but we also want 32-bit
// architectures to be supported.
assert!(k < 32);
@ -49,19 +43,20 @@ impl<C: CurveAffine> Params<C> {
let n: u64 = 1 << k;
let g = {
let hasher = &H::init(C::Base::zero());
let hasher = &T::init(io::sink(), C::Base::one());
let mut g = Vec::with_capacity(n as usize);
g.resize(n as usize, C::zero());
parallelize(&mut g, move |g, start| {
let mut cur_value = C::Base::from(start as u64);
let mut cur_value = C::Scalar::from(start as u64);
for g in g.iter_mut() {
let mut hasher = hasher.clone();
hasher.absorb(cur_value);
cur_value += &C::Base::one();
let mut hasher = hasher.fork();
hasher.write_scalar(cur_value).unwrap();
cur_value += &C::Scalar::one();
loop {
let x = hasher.squeeze().to_bytes();
let x: C::Base = hasher.squeeze_challenge();
let x = x.to_bytes();
let p = C::from_bytes(&x);
if bool::from(p.is_some()) {
*g = p.unwrap();
@ -102,8 +97,15 @@ impl<C: CurveAffine> Params<C> {
};
let h = {
let mut hasher = H::init(C::Base::zero());
let x = hasher.squeeze().to_bytes();
let mut hasher = T::init(io::sink(), C::Base::from_u64(2));
let x = hasher.squeeze_challenge().to_bytes();
let p = C::from_bytes(&x);
p.unwrap()
};
let u = {
let mut hasher = T::init(io::sink(), C::Base::from_u64(3));
let x = hasher.squeeze_challenge().to_bytes();
let p = C::from_bytes(&x);
p.unwrap()
};
@ -114,6 +116,7 @@ impl<C: CurveAffine> Params<C> {
g,
g_lagrange,
h,
u,
}
}
@ -222,12 +225,12 @@ impl<F: FieldExt> MulAssign<F> for Blind<F> {
}
#[test]
fn test_commit_lagrange() {
fn test_commit_lagrange_epaffine() {
const K: u32 = 6;
use crate::pasta::{EpAffine, Fp, Fq};
use crate::transcript::DummyHash;
let params = Params::<EpAffine>::new::<DummyHash<Fp>>(K);
use crate::pasta::{EpAffine, Fq};
use crate::transcript::DummyHashWriter;
let params = Params::<EpAffine>::new::<DummyHashWriter<std::io::Sink, _>>(K);
let domain = super::EvaluationDomain::new(1, K);
let mut a = domain.empty_lagrange();
@ -243,6 +246,28 @@ fn test_commit_lagrange() {
assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha));
}
#[test]
fn test_commit_lagrange_eqaffine() {
const K: u32 = 6;
use crate::pasta::{EqAffine, Fp};
use crate::transcript::DummyHashWriter;
let params = Params::<EqAffine>::new::<DummyHashWriter<std::io::Sink, _>>(K);
let domain = super::EvaluationDomain::new(1, K);
let mut a = domain.empty_lagrange();
for (i, a) in a.iter_mut().enumerate() {
*a = Fp::from(i as u64);
}
let b = domain.lagrange_to_coeff(a.clone());
let alpha = Blind(Fp::rand());
assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha));
}
#[test]
fn test_opening_proof() {
const K: u32 = 6;
@ -254,10 +279,13 @@ fn test_opening_proof() {
EvaluationDomain,
};
use crate::arithmetic::{eval_polynomial, Curve, FieldExt};
use crate::pasta::{EpAffine, Fp, Fq};
use crate::transcript::{ChallengeScalar, DummyHash, Transcript};
use crate::pasta::{EpAffine, Fq};
use crate::transcript::{
ChallengeScalar, DummyHashReader, DummyHashWriter, Transcript, TranscriptRead,
TranscriptWrite,
};
let params = Params::<EpAffine>::new::<DummyHash<Fp>>(K);
let params = Params::<EpAffine>::new::<DummyHashWriter<std::io::Sink, _>>(K);
let domain = EvaluationDomain::new(1, K);
let mut px = domain.empty_coeff();
@ -270,49 +298,51 @@ fn test_opening_proof() {
let p = params.commit(&px, blind).to_affine();
let mut transcript = Transcript::<_, DummyHash<_>, DummyHash<_>>::new();
transcript.absorb_point(&p).unwrap();
let mut transcript = DummyHashWriter::<Vec<u8>, EpAffine>::init(vec![], Field::zero());
transcript.write_point(p).unwrap();
let x = ChallengeScalar::<_, ()>::get(&mut transcript);
// Evaluate the polynomial
let v = eval_polynomial(&px, *x);
transcript.write_scalar(v).unwrap();
transcript.absorb_base(Fp::from_bytes(&v.to_bytes()).unwrap()); // unlikely to fail since p ~ q
let (proof, ch_prover) = {
create_proof(&params, &mut transcript, &px, blind, *x).unwrap();
let ch_prover = transcript.squeeze_challenge();
(transcript.finalize(), ch_prover)
};
loop {
let mut transcript_dup = transcript.clone();
// Verify the opening proof
let mut transcript = DummyHashReader::<&[u8], EpAffine>::init(&proof[..], Field::zero());
let p_prime = transcript.read_point().unwrap();
assert_eq!(p, p_prime);
let x_prime = ChallengeScalar::<_, ()>::get(&mut transcript);
assert_eq!(*x, *x_prime);
let v_prime = transcript.read_scalar().unwrap();
assert_eq!(v, v_prime);
let opening_proof = Proof::create(&params, &mut transcript, &px, blind, *x);
if let Ok(opening_proof) = opening_proof {
// Verify the opening proof
let mut commitment_msm = params.empty_msm();
commitment_msm.append_term(Field::one(), p);
let guard = opening_proof
.verify(
&params,
params.empty_msm(),
&mut transcript_dup,
*x,
commitment_msm,
v,
)
.unwrap();
let mut commitment_msm = params.empty_msm();
commitment_msm.append_term(Field::one(), p);
let guard = verify_proof(
&params,
params.empty_msm(),
&mut transcript,
*x,
commitment_msm,
v,
)
.unwrap();
let ch_verifier = transcript.squeeze_challenge();
assert_eq!(ch_prover, ch_verifier);
// Test guard behavior prior to checking another proof
{
// Test use_challenges()
let msm_challenges = guard.clone().use_challenges();
assert!(msm_challenges.eval());
// Test guard behavior prior to checking another proof
{
// Test use_challenges()
let msm_challenges = guard.clone().use_challenges();
assert!(msm_challenges.eval());
// Test use_g()
let g = guard.compute_g();
let (msm_g, _accumulator) = guard.clone().use_g(g);
assert!(msm_g.eval());
break;
}
} else {
transcript = transcript_dup;
transcript.absorb_base(Field::one());
}
// Test use_g()
let g = guard.compute_g();
let (msm_g, _accumulator) = guard.clone().use_g(g);
assert!(msm_g.eval());
}
}

View File

@ -1,5 +1,6 @@
use super::Params;
use crate::arithmetic::{best_multiexp, parallelize, Curve, CurveAffine};
use ff::Field;
/// A multiscalar multiplication in the polynomial commitment scheme
#[derive(Debug, Clone)]
@ -7,6 +8,7 @@ pub struct MSM<'a, C: CurveAffine> {
pub(crate) params: &'a Params<C>,
g_scalars: Option<Vec<C::Scalar>>,
h_scalar: Option<C::Scalar>,
u_scalar: Option<C::Scalar>,
other_scalars: Vec<C::Scalar>,
other_bases: Vec<C>,
}
@ -16,6 +18,7 @@ impl<'a, C: CurveAffine> MSM<'a, C> {
pub fn new(params: &'a Params<C>) -> Self {
let g_scalars = None;
let h_scalar = None;
let u_scalar = None;
let other_scalars = vec![];
let other_bases = vec![];
@ -23,6 +26,7 @@ impl<'a, C: CurveAffine> MSM<'a, C> {
params,
g_scalars,
h_scalar,
u_scalar,
other_scalars,
other_bases,
}
@ -40,6 +44,10 @@ impl<'a, C: CurveAffine> MSM<'a, C> {
if let Some(h_scalar) = &other.h_scalar {
self.add_to_h_scalar(*h_scalar);
}
if let Some(u_scalar) = &other.u_scalar {
self.add_to_u_scalar(*u_scalar);
}
}
/// Add arbitrary term (the scalar and the point)
@ -48,6 +56,17 @@ impl<'a, C: CurveAffine> MSM<'a, C> {
self.other_bases.push(point);
}
/// Add a value to the first entry of `g_scalars`.
pub fn add_constant_term(&mut self, constant: C::Scalar) {
if let Some(g_scalars) = self.g_scalars.as_mut() {
g_scalars[0] += &constant;
} else {
let mut g_scalars = vec![C::Scalar::zero(); self.params.n as usize];
g_scalars[0] += &constant;
self.g_scalars = Some(g_scalars);
}
}
/// 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`.
pub fn add_to_g_scalars(&mut self, scalars: &[C::Scalar]) {
@ -68,6 +87,11 @@ impl<'a, C: CurveAffine> MSM<'a, C> {
self.h_scalar = self.h_scalar.map_or(Some(scalar), |a| Some(a + &scalar));
}
/// Add to `u_scalar`
pub fn add_to_u_scalar(&mut self, scalar: C::Scalar) {
self.u_scalar = self.u_scalar.map_or(Some(scalar), |a| Some(a + &scalar));
}
/// Scale all scalars in the MSM by some scaling factor
pub fn scale(&mut self, factor: C::Scalar) {
if let Some(g_scalars) = &mut self.g_scalars {
@ -87,12 +111,14 @@ impl<'a, C: CurveAffine> MSM<'a, C> {
}
self.h_scalar = self.h_scalar.map(|a| a * &factor);
self.u_scalar = self.u_scalar.map(|a| a * &factor);
}
/// Perform multiexp and check that it results in zero
pub fn eval(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.u_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);
@ -105,6 +131,11 @@ impl<'a, C: CurveAffine> MSM<'a, C> {
bases.push(self.params.h);
}
if let Some(u_scalar) = self.u_scalar {
scalars.push(u_scalar);
bases.push(self.params.u);
}
if let Some(g_scalars) = &self.g_scalars {
scalars.extend(g_scalars);
bases.extend(self.params.g.iter());

View File

@ -1,202 +1,183 @@
use ff::Field;
use super::super::{Coeff, Error, Polynomial};
use super::{Blind, Params, Proof};
use super::super::{Coeff, Polynomial};
use super::{Blind, Params};
use crate::arithmetic::{
best_multiexp, compute_inner_product, parallelize, small_multiexp, Curve, CurveAffine, FieldExt,
best_multiexp, compute_inner_product, eval_polynomial, parallelize, small_multiexp, Curve,
CurveAffine, FieldExt,
};
use crate::transcript::{Challenge, ChallengeScalar, Hasher, Transcript};
use crate::transcript::{Challenge, ChallengeScalar, TranscriptWrite};
use std::io::{self, Write};
impl<C: CurveAffine> Proof<C> {
/// Create a polynomial commitment opening proof for the polynomial defined
/// by the coefficients `px`, the blinding factor `blind` used for the
/// polynomial commitment, and the point `x` that the polynomial is
/// evaluated at.
///
/// This function will panic if the provided polynomial is too large with
/// respect to the polynomial commitment parameters.
///
/// **Important:** This function assumes that the provided `transcript` has
/// already seen the common inputs: the polynomial commitment P, the claimed
/// opening v, and the point x. It's probably also nice for the transcript
/// to have seen the elliptic curve description and the SRS, if you want to
/// be rigorous.
pub fn create<HBase, HScalar>(
params: &Params<C>,
transcript: &mut Transcript<C, HBase, HScalar>,
px: &Polynomial<C::Scalar, Coeff>,
blind: Blind<C::Scalar>,
x: C::Scalar,
) -> Result<Self, Error>
where
HBase: Hasher<C::Base>,
HScalar: Hasher<C::Scalar>,
/// Create a polynomial commitment opening proof for the polynomial defined
/// by the coefficients `px`, the blinding factor `blind` used for the
/// polynomial commitment, and the point `x` that the polynomial is
/// evaluated at.
///
/// This function will panic if the provided polynomial is too large with
/// respect to the polynomial commitment parameters.
///
/// **Important:** This function assumes that the provided `transcript` has
/// already seen the common inputs: the polynomial commitment P, the claimed
/// opening v, and the point x. It's probably also nice for the transcript
/// to have seen the elliptic curve description and the SRS, if you want to
/// be rigorous.
pub fn create_proof<C: CurveAffine, W: Write, T: TranscriptWrite<W, C>>(
params: &Params<C>,
transcript: &mut T,
px: &Polynomial<C::Scalar, Coeff>,
blind: Blind<C::Scalar>,
x: C::Scalar,
) -> io::Result<()> {
// We're limited to polynomials of degree n - 1.
assert!(px.len() <= params.n as usize);
// Sample a random polynomial (of same degree) that has a root at x, first
// by setting all coefficients to random values.
let mut s_poly = (*px).clone();
for coeff in s_poly.iter_mut() {
*coeff = C::Scalar::rand();
}
// Evaluate the random polynomial at x
let v_prime = eval_polynomial(&s_poly[..], x);
// Subtract constant coefficient to get a random polynomial with a root at x
s_poly[0] = s_poly[0] - &v_prime;
// And sample a random blind
let s_poly_blind = Blind(C::Scalar::rand());
// Write a commitment to the random polynomial to the transcript
let s_poly_commitment = params.commit(&s_poly, s_poly_blind).to_affine();
transcript.write_point(s_poly_commitment)?;
// Challenge that will ensure that the prover cannot change P but can only
// witness a random polynomial commitment that agrees with P at x, with high
// probability.
let iota = *ChallengeScalar::<C, ()>::get(transcript);
// Challenge that ensures that the prover did not interfere with the U term
// in their commitments.
let z = *ChallengeScalar::<C, ()>::get(transcript);
// We'll be opening `s_poly_commitment * iota + P - [v] G_0` to ensure it
// has a root at zero.
let mut final_poly = s_poly * iota + px;
let v = eval_polynomial(&final_poly, x);
final_poly[0] = final_poly[0] - &v;
drop(px);
let blind = s_poly_blind * Blind(iota) + blind;
let mut blind = blind.0;
drop(s_poly_blind);
// Initialize the vector `a` as the coefficients of the polynomial,
// rounding up to the parameters.
let mut a = final_poly.values;
a.resize(params.n as usize, C::Scalar::zero());
// Initialize the vector `b` as the powers of `x`. The inner product of
// `a` and `b` is the evaluation of the polynomial at `x`.
let mut b = Vec::with_capacity(1 << params.k);
{
let mut blind = blind.0;
let mut cur = C::Scalar::one();
for _ in 0..(1 << params.k) {
b.push(cur);
cur *= &x;
}
}
// We're limited to polynomials of degree n - 1.
assert!(px.len() <= params.n as usize);
// 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();
// Compute U
let u = {
let u_x = transcript.squeeze();
// y^2 = x^3 + B
let u_y2 = u_x.square() * &u_x + &C::b();
if let Some(u_y) = u_y2.deterministic_sqrt() {
C::from_xy(u_x, u_y).unwrap()
// Perform the inner product argument, round by round.
for k in (1..=params.k).rev() {
let half = 1 << (k - 1); // half the length of `a`, `b`, `G`
// Compute L, R
//
// 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 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 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 {
return Err(Error::SamplingError);
// Try again, with slightly different randomness
l = (l + params.h).to_affine();
l_randomness += &C::Scalar::one();
}
};
// Initialize the vector `a` as the coefficients of the polynomial,
// rounding up to the parameters.
let mut a = px.to_vec();
a.resize(params.n as usize, C::Scalar::zero());
// Challenge is unlikely to be zero.
let challenge_inv = challenge.invert().unwrap();
let challenge_sq_inv = challenge_inv.square();
let challenge_sq = challenge.square();
// Initialize the vector `b` as the powers of `x`. The inner product of
// `a` and `b` is the evaluation of the polynomial at `x`.
let mut b = Vec::with_capacity(1 << params.k);
// 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 mut cur = C::Scalar::one();
for _ in 0..(1 << params.k) {
b.push(cur);
cur *= &x;
}
let challenge_sq_expected = ChallengeScalar::<_, ()>::get(transcript);
assert_eq!(challenge_sq, *challenge_sq_expected);
}
// 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.
let mut rounds = Vec::with_capacity(params.k as usize);
for k in (1..=params.k).rev() {
let half = 1 << (k - 1); // half the length of `a`, `b`, `G`
// Compute L, R
//
// 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 r_randomness = C::Scalar::rand();
metrics::counter!("multiexp", 2, "val" => "l/r", "size" => "2");
let l = l + &best_multiexp(&[value_l, l_randomness], &[u, params.h]);
let r = r + &best_multiexp(&[value_r, r_randomness], &[u, params.h]);
let mut 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.clone();
// Feed L and R into the cloned transcript.
// We expect these to not be points at infinity due to the randomness.
transcript
.absorb_point(&l)
.map_err(|_| Error::SamplingError)?;
transcript
.absorb_point(&r)
.map_err(|_| Error::SamplingError)?;
// ... and get the squared challenge.
let challenge_sq_packed = Challenge::get(&mut transcript);
let challenge_sq: C::Scalar = *ChallengeScalar::<_, ()>::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
.absorb_point(&l)
.map_err(|_| Error::SamplingError)?;
transcript
.absorb_point(&r)
.map_err(|_| Error::SamplingError)?;
// 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);
}
// Done with this round.
rounds.push((l, r));
// 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.truncate(half);
b.truncate(half);
// Collapse `G`
parallel_generator_collapse(&mut g, challenge, challenge_inv);
g.truncate(half);
// Update randomness (the synthetic blinding factor at the end)
blind += &(l_randomness * &challenge_sq);
blind += &(r_randomness * &challenge_sq_inv);
// 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.truncate(half);
b.truncate(half);
// We have fully collapsed `a`, `b`, `G`
assert_eq!(a.len(), 1);
let a = a[0];
assert_eq!(b.len(), 1);
let b = b[0];
assert_eq!(g.len(), 1);
let g = g[0];
// Collapse `G`
parallel_generator_collapse(&mut g, challenge, challenge_inv);
g.truncate(half);
// Random nonces for the zero-knowledge opening
let d = C::Scalar::rand();
let s = C::Scalar::rand();
metrics::increment_counter!("multiexp", "val" => "delta", "size" => "3");
let delta = best_multiexp(&[d, d * &b, s], &[g, u, params.h]).to_affine();
// Feed delta into the transcript
transcript
.absorb_point(&delta)
.map_err(|_| Error::SamplingError)?;
// Obtain the challenge c.
let c = ChallengeScalar::<C::Scalar, ()>::get(transcript);
// Compute z1 and z2 as described in the Halo paper.
let z1 = a * &*c + &d;
let z2 = *c * &blind + &s;
Ok(Proof {
rounds,
delta,
z1,
z2,
})
// Update randomness (the synthetic blinding factor at the end)
blind += &(l_randomness * &challenge_sq);
blind += &(r_randomness * &challenge_sq_inv);
}
// We have fully collapsed `a`, `b`, `G`
assert_eq!(a.len(), 1);
let a = a[0];
transcript.write_scalar(a)?;
transcript.write_scalar(blind)?;
Ok(())
}
fn parallel_generator_collapse<C: CurveAffine>(

View File

@ -1,16 +1,18 @@
use ff::Field;
use super::super::Error;
use super::{Params, Proof, MSM};
use crate::transcript::{Challenge, ChallengeScalar, Hasher, Transcript};
use super::{Params, MSM};
use crate::transcript::{Challenge, ChallengeScalar, TranscriptRead};
use crate::arithmetic::{best_multiexp, Curve, CurveAffine, FieldExt};
use std::io::Read;
/// A guard returned by the verifier
#[derive(Debug, Clone)]
pub struct Guard<'a, C: CurveAffine> {
msm: MSM<'a, C>,
neg_z1: C::Scalar,
neg_a: C::Scalar,
allinv: C::Scalar,
challenges_sq: Vec<C::Scalar>,
challenges_sq_packed: Vec<Challenge>,
@ -31,17 +33,17 @@ 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);
let s = compute_s(&self.challenges_sq, self.allinv * &self.neg_a);
self.msm.add_to_g_scalars(&s);
self.msm.add_to_h_scalar(self.neg_z1);
self.msm.add_to_h_scalar(self.neg_a);
self.msm
}
/// Lets caller supply the purported G point and simply appends
/// [-z1] G to return an updated MSM.
/// [-a] G to return an updated MSM.
pub fn use_g(mut self, g: C) -> (MSM<'a, C>, Accumulator<C>) {
self.msm.append_term(self.neg_z1, g);
self.msm.append_term(self.neg_a, g);
let accumulator = Accumulator {
g,
@ -62,144 +64,104 @@ impl<'a, C: CurveAffine> Guard<'a, C> {
}
}
impl<C: CurveAffine> Proof<C> {
/// Checks to see if an [`Proof`] is valid given the current `transcript`,
/// and a point `x` that the polynomial commitment `p` opens purportedly to
/// the value `v`.
pub fn verify<'a, HBase, HScalar>(
&self,
params: &'a Params<C>,
mut msm: MSM<'a, C>,
transcript: &mut Transcript<C, HBase, HScalar>,
x: C::Scalar,
mut commitment_msm: MSM<'a, C>,
v: C::Scalar,
) -> Result<Guard<'a, C>, Error>
where
HBase: Hasher<C::Base>,
HScalar: Hasher<C::Scalar>,
{
// Check for well-formedness
if self.rounds.len() != params.k as usize {
/// Checks to see if an [`Proof`] is valid given the current `transcript`, and a
/// point `x` that the polynomial commitment `P` opens purportedly to the value
/// `v`. The provided `commitment_msm` should evaluate to the commitment `P`
/// being opened.
pub fn verify_proof<'a, C: CurveAffine, R: Read, T: TranscriptRead<R, C>>(
params: &'a Params<C>,
mut msm: MSM<'a, C>,
transcript: &mut T,
x: C::Scalar,
mut commitment_msm: MSM<'a, C>,
v: C::Scalar,
) -> Result<Guard<'a, C>, Error> {
let k = params.k as usize;
// P - [v] G_0 + S * iota
// + \sum(L_i * u_i^2) + \sum(R_i * u_i^-2)
commitment_msm.add_constant_term(-v);
let s_poly_commitment = transcript.read_point().map_err(|_| Error::OpeningError)?;
let iota = *ChallengeScalar::<C, ()>::get(transcript);
commitment_msm.append_term(iota, s_poly_commitment);
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();
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 = challenge_sq.deterministic_sqrt();
if challenge.is_none() {
// We didn't sample a square.
return Err(Error::OpeningError);
}
let challenge = challenge.unwrap();
// Compute U
let u = {
let u_x = transcript.squeeze();
// y^2 = x^3 + B
let u_y2 = u_x.square() * &u_x + &C::b();
let u_y = u_y2.deterministic_sqrt();
if u_y.is_none() {
return Err(Error::OpeningError);
}
let u_y = u_y.unwrap();
C::from_xy(u_x, u_y).unwrap()
};
let mut extra_scalars = Vec::with_capacity(self.rounds.len() * 2 + 4 + params.n as usize);
let mut extra_bases = Vec::with_capacity(self.rounds.len() * 2 + 4 + params.n as usize);
// Data about the challenges from each of the rounds.
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 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;
let r = round.1;
transcript
.absorb_point(&l)
.map_err(|_| Error::OpeningError)?;
transcript
.absorb_point(&r)
.map_err(|_| Error::OpeningError)?;
let challenge_sq_packed = Challenge::get(transcript);
let challenge_sq: C::Scalar = *ChallengeScalar::<_, ()>::from(challenge_sq_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();
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;
let challenge_sq_inv = challenge_inv.square();
extra_scalars.push(challenge_sq);
extra_bases.push(round.0);
extra_scalars.push(challenge_sq_inv);
extra_bases.push(round.1);
challenges.push(challenge);
challenges_inv.push(challenge_inv);
challenges_sq.push(challenge_sq);
challenges_sq_packed.push(challenge_sq_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;
// Feed delta into the transcript
transcript
.absorb_point(&self.delta)
.map_err(|_| Error::OpeningError)?;
let challenge_sq_inv = challenge_inv.square();
// Get the challenge `c`
let c = ChallengeScalar::<_, ()>::get(transcript);
commitment_msm.append_term(challenge_sq, l);
commitment_msm.append_term(challenge_sq_inv, r);
// Construct
// [c] P + [c * v] U + [c] sum(L_i * u_i^2) + [c] sum(R_i * u_i^-2) + delta - [z1 * b] U + [z1 - z2] H
// = [z1] (G + H)
// The computation of [z1] (G + H) happens in either Guard::use_challenges()
// or Guard::use_g().
let b = compute_b(x, &challenges, &challenges_inv);
let neg_z1 = -self.z1;
// [c] P
commitment_msm.scale(*c);
msm.add_msm(&commitment_msm);
// [c] sum(L_i * u_i^2) + [c] sum(R_i * u_i^-2)
for scalar in &mut extra_scalars {
*scalar *= &(*c);
}
for (scalar, base) in extra_scalars.iter().zip(extra_bases.iter()) {
msm.append_term(*scalar, *base);
}
// [c * v] U - [z1 * b] U
msm.append_term((*c * &v) + &(neg_z1 * &b), u);
// delta
msm.append_term(Field::one(), self.delta);
// + [z1 - z2] H
msm.add_to_h_scalar(self.z1 - &self.z2);
let guard = Guard {
msm,
neg_z1,
allinv,
challenges_sq,
challenges_sq_packed,
};
Ok(guard)
challenges.push(challenge);
challenges_inv.push(challenge_inv);
challenges_sq.push(challenge_sq);
challenges_sq_packed.push(challenge_sq_packed);
}
// Our goal is to open
// commitment_msm - [v] G_0 + random_poly_commitment * iota
// + \sum(L_i * u_i^2) + \sum(R_i * u_i^-2)
// at x to 0, by asking the prover to supply (a, h) such that it equals
// = [a] (G + [b * z] U) + [h] H
// except that we wish for the prover to supply G as Commit(g(X); 1) so
// we must substitute to get
// = [a] ((G - H) + [b * z] U) + [h] H
// = [a] G + [-a] H + [abz] U + [h] H
// = [a] G + [abz] U + [h - a] H
// but subtracting to get the desired equality
// ... + [-a] G + [-abz] U + [a - h] H = 0
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);
commitment_msm.add_to_u_scalar(neg_a * &b * &z);
commitment_msm.add_to_h_scalar(a - &h);
msm.add_msm(&commitment_msm);
let guard = Guard {
msm,
neg_a,
allinv,
challenges_sq,
challenges_sq_packed,
};
Ok(guard)
}
fn compute_b<F: Field>(x: F, challenges: &[F], challenges_inv: &[F]) -> F {