diff --git a/src/plonk.rs b/src/plonk.rs index 7ce0de3..5595bcd 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -9,7 +9,6 @@ use crate::arithmetic::CurveAffine; use crate::poly::{ multiopen, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, }; -use crate::transcript::Hasher; mod circuit; mod keygen; @@ -78,6 +77,8 @@ pub enum Error { BoundsFailure, /// Opening error OpeningError, + /// Transcript error + TranscriptError, } impl ProvingKey { @@ -94,21 +95,6 @@ impl VerifyingKey { } } -/// Hash a point into transcript -pub fn hash_point>( - transcript: &mut H, - point: &C, -) -> Result<(), Error> { - let tmp = point.get_xy(); - if bool::from(tmp.is_none()) { - return Err(Error::SynthesisError); - }; - let tmp = tmp.unwrap(); - transcript.absorb(tmp.0); - transcript.absorb(tmp.1); - Ok(()) -} - #[test] fn test_proving() { use crate::arithmetic::{Curve, Field}; diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 020c5b2..54c15f3 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -1,6 +1,6 @@ use super::{ circuit::{Advice, Assignment, Circuit, Column, ConstraintSystem, Fixed}, - hash_point, Error, Proof, ProvingKey, + Error, Proof, ProvingKey, }; use crate::arithmetic::{ eval_polynomial, get_challenge_scalar, parallelize, BatchInvert, Challenge, Curve, CurveAffine, @@ -11,7 +11,7 @@ use crate::poly::{ multiopen::{self, ProverQuery}, LagrangeCoeff, Polynomial, Rotation, }; -use crate::transcript::Hasher; +use crate::transcript::{Hasher, Transcript}; impl Proof { /// This creates a proof for the provided `circuit` when given the public @@ -92,7 +92,7 @@ impl Proof { let witness = witness; // Create a transcript for obtaining Fiat-Shamir challenges. - let mut transcript = HBase::init(C::Base::one()); + let mut transcript = Transcript::::new(); // Compute commitments to aux column polynomials let aux_commitments_projective: Vec<_> = aux @@ -105,7 +105,9 @@ impl Proof { drop(aux_commitments_projective); for commitment in &aux_commitments { - hash_point(&mut transcript, commitment)?; + transcript + .absorb_point(commitment) + .map_err(|_| Error::TranscriptError)?; } let aux_polys: Vec<_> = aux @@ -143,7 +145,9 @@ impl Proof { drop(advice_commitments_projective); for commitment in &advice_commitments { - hash_point(&mut transcript, commitment)?; + transcript + .absorb_point(commitment) + .map_err(|_| Error::TranscriptError)?; } let advice_polys: Vec<_> = witness @@ -277,7 +281,9 @@ impl Proof { // Hash each permutation product commitment for c in &permutation_product_commitments { - hash_point(&mut transcript, c)?; + transcript + .absorb_point(c) + .map_err(|_| Error::TranscriptError)?; } // Obtain challenge for keeping all separate gates linearly independent @@ -385,7 +391,9 @@ impl Proof { // Hash each h(X) piece for c in h_commitments.iter() { - hash_point(&mut transcript, c)?; + transcript + .absorb_point(c) + .map_err(|_| Error::TranscriptError)?; } let x_3: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); @@ -444,10 +452,6 @@ impl Proof { .map(|poly| eval_polynomial(poly, x_3)) .collect(); - // We set up a second transcript on the scalar field to hash in openings of - // our polynomial commitments. - let mut transcript_scalar = HScalar::init(C::Scalar::one()); - // Hash each advice evaluation for eval in advice_evals .iter() @@ -458,13 +462,9 @@ impl Proof { .chain(permutation_product_inv_evals.iter()) .chain(permutation_evals.iter().flat_map(|evals| evals.iter())) { - transcript_scalar.absorb(*eval); + transcript.absorb_scalar(*eval); } - let transcript_scalar_point = - C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); - transcript.absorb(transcript_scalar_point); - let mut instances: Vec> = Vec::new(); for (query_index, &(column, at)) in pk.vk.cs.advice_queries.iter().enumerate() { @@ -558,9 +558,8 @@ impl Proof { } } - let multiopening = - multiopen::Proof::create(params, &mut transcript, &mut transcript_scalar, instances) - .map_err(|_| Error::OpeningError)?; + let multiopening = multiopen::Proof::create(params, &mut transcript, instances) + .map_err(|_| Error::OpeningError)?; Ok(Proof { advice_commitments, diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index e6dec8e..23fd8f6 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -1,11 +1,11 @@ -use super::{hash_point, Error, Proof, VerifyingKey}; +use super::{Error, Proof, VerifyingKey}; use crate::arithmetic::{get_challenge_scalar, Challenge, CurveAffine, Field}; use crate::poly::{ commitment::{Guard, Params, MSM}, multiopen::VerifierQuery, Rotation, }; -use crate::transcript::Hasher; +use crate::transcript::{Hasher, Transcript}; impl<'a, C: CurveAffine> Proof { /// Returns a boolean indicating whether or not the proof is valid @@ -27,16 +27,20 @@ impl<'a, C: CurveAffine> Proof { } // Create a transcript for obtaining Fiat-Shamir challenges. - let mut transcript = HBase::init(C::Base::one()); + let mut transcript = Transcript::::new(); // Hash the aux (external) commitments into the transcript for commitment in aux_commitments { - hash_point(&mut transcript, commitment)?; + transcript + .absorb_point(commitment) + .map_err(|_| Error::TranscriptError)?; } // Hash the prover's advice commitments into the transcript for commitment in &self.advice_commitments { - hash_point(&mut transcript, commitment)?; + transcript + .absorb_point(commitment) + .map_err(|_| Error::TranscriptError)?; } // Sample x_0 challenge @@ -47,7 +51,9 @@ impl<'a, C: CurveAffine> Proof { // Hash each permutation product commitment for c in &self.permutation_product_commitments { - hash_point(&mut transcript, c)?; + transcript + .absorb_point(c) + .map_err(|_| Error::TranscriptError)?; } // Sample x_2 challenge, which keeps the gates linearly independent. @@ -55,7 +61,9 @@ impl<'a, C: CurveAffine> Proof { // Obtain a commitment to h(X) in the form of multiple pieces of degree n - 1 for c in &self.h_commitments { - hash_point(&mut transcript, c)?; + transcript + .absorb_point(c) + .map_err(|_| Error::TranscriptError)?; } // Sample x_3 challenge, which is used to ensure the circuit is @@ -66,10 +74,6 @@ impl<'a, C: CurveAffine> Proof { // commitments open to the correct values. self.check_hx(params, vk, x_0, x_1, x_2, x_3)?; - // Hash together all the openings provided by the prover into a new - // transcript on the scalar field. - let mut transcript_scalar = HScalar::init(C::Scalar::one()); - for eval in self .advice_evals .iter() @@ -80,13 +84,9 @@ impl<'a, C: CurveAffine> Proof { .chain(self.permutation_product_inv_evals.iter()) .chain(self.permutation_evals.iter().flat_map(|evals| evals.iter())) { - transcript_scalar.absorb(*eval); + transcript.absorb_scalar(*eval); } - let transcript_scalar_point = - C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); - transcript.absorb(transcript_scalar_point); - let mut queries: Vec> = Vec::new(); for (query_index, &(column, at)) in vk.cs.advice_queries.iter().enumerate() { @@ -180,13 +180,7 @@ impl<'a, C: CurveAffine> Proof { // We are now convinced the circuit is satisfied so long as the // polynomial commitments open to the correct values. self.multiopening - .verify( - params, - &mut transcript, - &mut transcript_scalar, - queries, - msm, - ) + .verify(params, &mut transcript, queries, msm) .map_err(|_| Error::OpeningError) } diff --git a/src/poly/commitment.rs b/src/poly/commitment.rs index c135ad0..cc30ee2 100644 --- a/src/poly/commitment.rs +++ b/src/poly/commitment.rs @@ -248,8 +248,10 @@ fn test_opening_proof() { commitment::{Blind, Params}, EvaluationDomain, }; - use crate::arithmetic::{eval_polynomial, get_challenge_scalar, Challenge, Curve, Field}; - use crate::transcript::{DummyHash, Hasher}; + use crate::arithmetic::{ + eval_polynomial, get_challenge_scalar, Challenge, Curve, CurveAffine, Field, + }; + use crate::transcript::{DummyHash, Hasher, Transcript}; use crate::tweedle::{EpAffine, Fp, Fq}; let params = Params::::new::>(K); @@ -265,17 +267,18 @@ fn test_opening_proof() { let p = params.commit(&px, blind).to_affine(); - let mut transcript = DummyHash::init(Field::one()); + let mut hasher = DummyHash::init(Field::one()); let (p_x, p_y) = p.get_xy().unwrap(); - transcript.absorb(p_x); - transcript.absorb(p_y); - let x_packed = transcript.squeeze().get_lower_128(); + hasher.absorb(p_x); + hasher.absorb(p_y); + let x_packed = hasher.squeeze().get_lower_128(); let x: Fq = get_challenge_scalar(Challenge(x_packed)); - // Evaluate the polynomial let v = eval_polynomial(&px, x); - transcript.absorb(Fp::from_bytes(&v.to_bytes()).unwrap()); // unlikely to fail since p ~ q + hasher.absorb(Fp::from_bytes(&v.to_bytes()).unwrap()); // unlikely to fail since p ~ q + let scalar_hasher = DummyHash::init(Fq::one()); + let mut transcript = Transcript::init_with_hashers(&hasher, &scalar_hasher); loop { let transcript_dup = transcript.clone(); @@ -283,7 +286,7 @@ fn test_opening_proof() { let opening_proof = Proof::create(¶ms, &mut transcript, &px, blind, x); if opening_proof.is_err() { transcript = transcript_dup; - transcript.absorb(Field::one()); + transcript.absorb_base(Field::one()); } else { let opening_proof = opening_proof.unwrap(); // Verify the opening proof diff --git a/src/poly/commitment/prover.rs b/src/poly/commitment/prover.rs index 1147c1e..8b87e05 100644 --- a/src/poly/commitment/prover.rs +++ b/src/poly/commitment/prover.rs @@ -4,7 +4,7 @@ use crate::arithmetic::{ best_multiexp, compute_inner_product, get_challenge_scalar, parallelize, small_multiexp, Challenge, Curve, CurveAffine, Field, }; -use crate::transcript::Hasher; +use crate::transcript::{Hasher, Transcript}; impl Proof { /// Create a polynomial commitment opening proof for the polynomial defined @@ -20,13 +20,17 @@ impl Proof { /// 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>( + pub fn create( params: &Params, - transcript: &mut H, + transcript: &mut Transcript, px: &Polynomial, blind: Blind, x: C::Scalar, - ) -> Result { + ) -> Result + where + HBase: Hasher, + HScalar: Hasher, + { let mut blind = blind.0; // We're limited to polynomials of degree n - 1. @@ -90,15 +94,14 @@ impl Proof { // 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. - let (l_x, l_y) = l.get_xy().unwrap(); - let (r_x, r_y) = r.get_xy().unwrap(); - - // Feed L and R into the cloned transcript... - transcript.absorb(l_x); - transcript.absorb(l_y); - transcript.absorb(r_x); - transcript.absorb(r_y); + 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 = transcript.squeeze().get_lower_128(); @@ -122,12 +125,12 @@ impl Proof { let challenge_sq = challenge.square(); // Feed L and R into the real transcript - let (l_x, l_y) = l.get_xy().unwrap(); - let (r_x, r_y) = r.get_xy().unwrap(); - transcript.absorb(l_x); - transcript.absorb(l_y); - transcript.absorb(r_x); - transcript.absorb(r_y); + 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. @@ -172,11 +175,10 @@ impl Proof { let delta = best_multiexp(&[d, d * &b, s], &[g, u, params.h]).to_affine(); - let (delta_x, delta_y) = delta.get_xy().unwrap(); - // Feed delta into the transcript - transcript.absorb(delta_x); - transcript.absorb(delta_y); + transcript + .absorb_point(&delta) + .map_err(|_| Error::SamplingError)?; // Obtain the challenge c. let c_packed = transcript.squeeze().get_lower_128(); diff --git a/src/poly/commitment/verifier.rs b/src/poly/commitment/verifier.rs index 649c2c3..e6552f1 100644 --- a/src/poly/commitment/verifier.rs +++ b/src/poly/commitment/verifier.rs @@ -1,6 +1,6 @@ use super::super::Error; use super::{Params, Proof, MSM}; -use crate::transcript::Hasher; +use crate::transcript::{Hasher, Transcript}; use crate::arithmetic::{ best_multiexp, get_challenge_scalar, Challenge, Curve, CurveAffine, Field, @@ -65,15 +65,19 @@ 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, H: Hasher>( + pub fn verify<'a, HBase, HScalar>( &self, params: &'a Params, mut msm: MSM<'a, C>, - transcript: &mut H, + transcript: &mut Transcript, x: C::Scalar, mut commitment_msm: MSM<'a, C>, v: C::Scalar, - ) -> Result, Error> { + ) -> Result, Error> + where + HBase: Hasher, + HScalar: Hasher, + { // Check for well-formedness if self.rounds.len() != params.k as usize { return Err(Error::OpeningError); @@ -105,17 +109,14 @@ impl Proof { for round in &self.rounds { // Feed L and R into the transcript. - let l = round.0.get_xy(); - let r = round.1.get_xy(); - if bool::from(l.is_none() | r.is_none()) { - return Err(Error::OpeningError); - } - let l = l.unwrap(); - let r = r.unwrap(); - transcript.absorb(l.0); - transcript.absorb(l.1); - transcript.absorb(r.0); - transcript.absorb(r.1); + 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 = transcript.squeeze().get_lower_128(); let challenge_sq: C::Scalar = get_challenge_scalar(Challenge(challenge_sq_packed)); @@ -148,15 +149,10 @@ impl Proof { challenges_sq_packed.push(Challenge(challenge_sq_packed)); } - let delta = self.delta.get_xy(); - if bool::from(delta.is_none()) { - return Err(Error::OpeningError); - } - let delta = delta.unwrap(); - // Feed delta into the transcript - transcript.absorb(delta.0); - transcript.absorb(delta.1); + transcript + .absorb_point(&self.delta) + .map_err(|_| Error::OpeningError)?; // Get the challenge `c` let c_packed = transcript.squeeze().get_lower_128(); diff --git a/src/poly/multiopen/prover.rs b/src/poly/multiopen/prover.rs index fbe7dc9..c0e49a5 100644 --- a/src/poly/multiopen/prover.rs +++ b/src/poly/multiopen/prover.rs @@ -8,8 +8,7 @@ use crate::arithmetic::{ eval_polynomial, get_challenge_scalar, kate_division, lagrange_interpolate, Challenge, Curve, CurveAffine, Field, }; -use crate::plonk::hash_point; -use crate::transcript::Hasher; +use crate::transcript::{Hasher, Transcript}; use std::marker::PhantomData; #[derive(Debug, Clone)] @@ -24,8 +23,7 @@ impl Proof { /// Create a multi-opening proof pub fn create<'a, I, HBase: Hasher, HScalar: Hasher>( params: &Params, - transcript: &mut HBase, - transcript_scalar: &mut HScalar, + transcript: &mut Transcript, queries: I, ) -> Result where @@ -110,8 +108,9 @@ impl Proof { let (opening, q_evals) = loop { let mut transcript = transcript.clone(); - let mut transcript_scalar = transcript_scalar.clone(); - hash_point(&mut transcript, &f_commitment).unwrap(); + transcript + .absorb_point(&f_commitment) + .map_err(|_| Error::SamplingError)?; let x_6: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); @@ -122,13 +121,9 @@ impl Proof { .collect(); for eval in q_evals.iter() { - transcript_scalar.absorb(*eval); + transcript.absorb_scalar(*eval); } - let transcript_scalar_point = - C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); - transcript.absorb(transcript_scalar_point); - let x_7: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); diff --git a/src/poly/multiopen/verifier.rs b/src/poly/multiopen/verifier.rs index bcdc30a..57713f8 100644 --- a/src/poly/multiopen/verifier.rs +++ b/src/poly/multiopen/verifier.rs @@ -6,8 +6,7 @@ use super::{construct_intermediate_sets, Proof, Query, VerifierQuery}; use crate::arithmetic::{ eval_polynomial, get_challenge_scalar, lagrange_interpolate, Challenge, CurveAffine, Field, }; -use crate::plonk::hash_point; -use crate::transcript::Hasher; +use crate::transcript::{Hasher, Transcript}; #[derive(Debug, Clone)] struct CommitmentData { @@ -21,8 +20,7 @@ impl Proof { pub fn verify<'a, I, HBase: Hasher, HScalar: Hasher>( &self, params: &'a Params, - transcript: &mut HBase, - transcript_scalar: &mut HScalar, + transcript: &mut Transcript, queries: I, mut msm: MSM<'a, C>, ) -> Result, Error> @@ -75,20 +73,18 @@ impl Proof { } // Obtain the commitment to the multi-point quotient polynomial f(X). - hash_point(transcript, &self.f_commitment).unwrap(); + transcript + .absorb_point(&self.f_commitment) + .map_err(|_| Error::SamplingError)?; // Sample a challenge x_6 for checking that f(X) was committed to // correctly. let x_6: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); for eval in self.q_evals.iter() { - transcript_scalar.absorb(*eval); + transcript.absorb_scalar(*eval); } - let transcript_scalar_point = - C::Base::from_bytes(&(transcript_scalar.squeeze()).to_bytes()).unwrap(); - transcript.absorb(transcript_scalar_point); - // We can compute the expected msm_eval at x_6 using the q_evals provided // by the prover and from x_5 let msm_eval = point_sets diff --git a/src/transcript.rs b/src/transcript.rs index 8d4fde0..5fc5488 100644 --- a/src/transcript.rs +++ b/src/transcript.rs @@ -1,7 +1,8 @@ //! This module contains utilities and traits for dealing with Fiat-Shamir //! transcripts. -use crate::arithmetic::Field; +use crate::arithmetic::{CurveAffine, Field}; +use std::marker::PhantomData; /// This is a generic interface for a sponge function that can be used for /// Fiat-Shamir transformations. @@ -23,9 +24,9 @@ pub struct DummyHash { } impl Hasher for DummyHash { - fn init(value: F) -> Self { + fn init(key: F) -> Self { DummyHash { - power: F::ZETA + F::one() + value, + power: F::ZETA + F::one() + key, state: F::ZETA, } } @@ -43,3 +44,91 @@ impl Hasher for DummyHash { tmp } } + +/// A transcript that can absorb points from both the base field and scalar +/// field of a curve +#[derive(Debug, Clone)] +pub struct Transcript +where + HBase: Hasher, + HScalar: Hasher, +{ + // Hasher over the base field + base_hasher: HBase, + // Hasher over the scalar field + scalar_hasher: HScalar, + // Indicates if scalar(s) has been hashed but not squeezed + scalar_needs_squeezing: bool, + // PhantomData + _marker: PhantomData, +} + +impl, HScalar: Hasher> + Transcript +{ + /// Initialise a new transcript with Field::one() as keys + /// in both the base_hasher and scalar_hasher + pub fn new() -> Self { + let base_hasher = HBase::init(C::Base::one()); + let scalar_hasher = HScalar::init(C::Scalar::one()); + Transcript { + base_hasher, + scalar_hasher, + scalar_needs_squeezing: false, + _marker: PhantomData, + } + } + + /// Initialise a new transcript with some given base_hasher and + /// scalar_hasher + #[cfg(test)] + pub(crate) fn init_with_hashers(base_hasher: &HBase, scalar_hasher: &HScalar) -> Self { + Transcript { + base_hasher: base_hasher.clone(), + scalar_hasher: scalar_hasher.clone(), + scalar_needs_squeezing: false, + _marker: PhantomData, + } + } + + fn conditional_scalar_squeeze(&mut self) { + if self.scalar_needs_squeezing { + let transcript_scalar_point = + C::Base::from_bytes(&(self.scalar_hasher.squeeze()).to_bytes()).unwrap(); + self.base_hasher.absorb(transcript_scalar_point); + self.scalar_needs_squeezing = false; + } + } + + /// Absorb a curve point into the transcript by absorbing + /// its x and y coordinates + pub fn absorb_point(&mut self, point: &C) -> Result<(), ()> { + self.conditional_scalar_squeeze(); + let tmp = point.get_xy(); + if bool::from(tmp.is_none()) { + return Err(()); + }; + let tmp = tmp.unwrap(); + self.base_hasher.absorb(tmp.0); + self.base_hasher.absorb(tmp.1); + Ok(()) + } + + /// Absorb a base into the base_hasher + pub fn absorb_base(&mut self, base: C::Base) { + self.conditional_scalar_squeeze(); + self.base_hasher.absorb(base); + } + + /// Absorb a scalar into the scalar_hasher + pub fn absorb_scalar(&mut self, scalar: C::Scalar) { + self.scalar_hasher.absorb(scalar); + self.scalar_needs_squeezing = true; + } + + /// Squeeze the transcript to obtain a C::Base value. + pub fn squeeze(&mut self) -> C::Base { + self.conditional_scalar_squeeze(); + self.base_hasher.squeeze() + } +}