From d30c6b62e4b7860f0f2602f2f1b8c2149cb15f53 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 21 Dec 2020 12:34:51 -0700 Subject: [PATCH] Modification of the polynomial commitment scheme to compensate for Transcript API changes. --- src/poly/commitment.rs | 156 +++++++++------ src/poly/commitment/msm.rs | 31 +++ src/poly/commitment/prover.rs | 339 +++++++++++++++----------------- src/poly/commitment/verifier.rs | 236 ++++++++++------------ 4 files changed, 383 insertions(+), 379 deletions(-) diff --git a/src/poly/commitment.rs b/src/poly/commitment.rs index a4066c23..d337daea 100644 --- a/src/poly/commitment.rs +++ b/src/poly/commitment.rs @@ -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 { pub(crate) g: Vec, pub(crate) g_lagrange: Vec, pub(crate) h: C, -} - -/// This is a proof object for the polynomial commitment scheme opening. -#[derive(Debug, Clone)] -pub struct Proof { - rounds: Vec<(C, C)>, - delta: C, - z1: C::Scalar, - z2: C::Scalar, + pub(crate) u: C, } impl Params { /// Initializes parameters for the curve, given a random oracle to draw /// points from. - pub fn new>(k: u32) -> Self { + pub fn new + 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 Params { 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 Params { }; 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 Params { g, g_lagrange, h, + u, } } @@ -222,12 +225,12 @@ impl MulAssign for Blind { } #[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::::new::>(K); + use crate::pasta::{EpAffine, Fq}; + use crate::transcript::DummyHashWriter; + let params = Params::::new::>(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::::new::>(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::::new::>(K); + let params = Params::::new::>(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::, 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(¶ms, &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(¶ms, &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( - ¶ms, - 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( + ¶ms, + 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()); } } diff --git a/src/poly/commitment/msm.rs b/src/poly/commitment/msm.rs index 098c1cd2..75d7b256 100644 --- a/src/poly/commitment/msm.rs +++ b/src/poly/commitment/msm.rs @@ -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, g_scalars: Option>, h_scalar: Option, + u_scalar: Option, other_scalars: Vec, other_bases: Vec, } @@ -16,6 +18,7 @@ impl<'a, C: CurveAffine> MSM<'a, C> { pub fn new(params: &'a Params) -> 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 = Vec::with_capacity(len); let mut bases: Vec = 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()); diff --git a/src/poly/commitment/prover.rs b/src/poly/commitment/prover.rs index 608c57ae..bbe14455 100644 --- a/src/poly/commitment/prover.rs +++ b/src/poly/commitment/prover.rs @@ -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 Proof { - /// 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( - params: &Params, - transcript: &mut Transcript, - px: &Polynomial, - blind: Blind, - x: C::Scalar, - ) -> Result - where - HBase: Hasher, - HScalar: Hasher, +/// 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>( + params: &Params, + transcript: &mut T, + px: &Polynomial, + blind: Blind, + 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::::get(transcript); + + // Challenge that ensures that the prover did not interfere with the U term + // in their commitments. + let z = *ChallengeScalar::::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::::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::::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( diff --git a/src/poly/commitment/verifier.rs b/src/poly/commitment/verifier.rs index 55b6d6eb..f60dc927 100644 --- a/src/poly/commitment/verifier.rs +++ b/src/poly/commitment/verifier.rs @@ -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, challenges_sq_packed: Vec, @@ -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) { - 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 Proof { - /// 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, - mut msm: MSM<'a, C>, - transcript: &mut Transcript, - x: C::Scalar, - mut commitment_msm: MSM<'a, C>, - v: C::Scalar, - ) -> Result, Error> - where - HBase: Hasher, - HScalar: Hasher, - { - // 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>( + params: &'a Params, + mut msm: MSM<'a, C>, + transcript: &mut T, + x: C::Scalar, + mut commitment_msm: MSM<'a, C>, + v: C::Scalar, +) -> Result, 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::::get(transcript); + commitment_msm.append_term(iota, s_poly_commitment); + + let z = *ChallengeScalar::::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 = 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::::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 = 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(x: F, challenges: &[F], challenges_inv: &[F]) -> F {