Refactor Transcript API

- remove method to return a challenge in the base field
- let ChallengeSpace decide length of raw challenge instead of limiting it to u128

Co-authored-by: Daira Hopwood <daira@jacaranda.org>
This commit is contained in:
therealyingtong 2021-04-23 13:14:42 +08:00
parent cd3cc543cf
commit 85c5f4412d
5 changed files with 111 additions and 64 deletions

View File

@ -16,7 +16,7 @@ pub fn verify_proof<'a, C: CurveAffine, S: ChallengeSpace<C>, T: TranscriptRead<
msm: MSM<'a, C>,
instance_commitments: &[&[C]],
transcript: &mut T,
) -> Result<Guard<'a, C>, Error> {
) -> Result<Guard<'a, C, S>, Error> {
// Check that instance_commitments matches the expected number of instance columns
for instance_commitments in instance_commitments.iter() {
if instance_commitments.len() != vk.cs.num_instance_columns {

View File

@ -354,7 +354,7 @@ fn test_opening_proof() {
commitment_msm.append_term(Field::one(), p);
let guard = verify_proof(&params, commitment_msm, &mut transcript, *x, v).unwrap();
let ch_verifier = transcript.squeeze_challenge();
assert_eq!(ch_prover, ch_verifier);
assert_eq!(*ch_prover, *ch_verifier);
// Test guard behavior prior to checking another proof
{

View File

@ -3,31 +3,31 @@ use group::Curve;
use super::super::Error;
use super::{Params, MSM};
use crate::transcript::{Challenge, ChallengeSpace, TranscriptRead};
use crate::transcript::{ChallengeSpace, TranscriptRead};
use crate::arithmetic::{best_multiexp, BatchInvert, CurveAffine};
/// A guard returned by the verifier
#[derive(Debug, Clone)]
pub struct Guard<'a, C: CurveAffine> {
pub struct Guard<'a, C: CurveAffine, S: ChallengeSpace<C>> {
msm: MSM<'a, C>,
neg_a: C::Scalar,
challenges: Vec<C::Scalar>,
challenges_packed: Vec<Challenge>,
challenges_packed: Vec<S::Challenge>,
}
/// An accumulator instance consisting of an evaluation claim and a proof.
#[derive(Debug, Clone)]
pub struct Accumulator<C: CurveAffine> {
pub struct Accumulator<C: CurveAffine, S: ChallengeSpace<C>> {
/// The claimed output of the linear-time polycommit opening protocol
pub g: C,
/// A vector of 128-bit challenges sampled by the verifier, to be used in
/// computing g.
pub challenges_packed: Vec<Challenge>,
pub challenges_packed: Vec<S::Challenge>,
}
impl<'a, C: CurveAffine> Guard<'a, C> {
impl<'a, C: CurveAffine, S: ChallengeSpace<C>> Guard<'a, C, S> {
/// Lets caller supply the challenges and obtain an MSM with updated
/// scalars and points.
pub fn use_challenges(mut self) -> MSM<'a, C> {
@ -40,7 +40,7 @@ impl<'a, C: CurveAffine> Guard<'a, C> {
/// Lets caller supply the purported G point and simply appends
/// [-a] G to return an updated MSM.
pub fn use_g(mut self, g: C) -> (MSM<'a, C>, Accumulator<C>) {
pub fn use_g(mut self, g: C) -> (MSM<'a, C>, Accumulator<C, S>) {
self.msm.append_term(self.neg_a, g);
let accumulator = Accumulator {
@ -70,7 +70,7 @@ pub fn verify_proof<'a, C: CurveAffine, S: ChallengeSpace<C>, T: TranscriptRead<
transcript: &mut T,
x: C::Scalar,
v: C::Scalar,
) -> Result<Guard<'a, C>, Error> {
) -> Result<Guard<'a, C, S>, Error> {
let k = params.k as usize;
// P - [v] G_0 + S * iota
@ -90,8 +90,8 @@ pub fn verify_proof<'a, C: CurveAffine, S: ChallengeSpace<C>, T: TranscriptRead<
let l = transcript.read_point().map_err(|_| Error::OpeningError)?;
let r = transcript.read_point().map_err(|_| Error::OpeningError)?;
let challenge_packed = transcript.squeeze_challenge_128();
let challenge = *transcript.to_challenge_scalar::<()>(challenge_packed);
let challenge_packed = transcript.squeeze_challenge();
let challenge = *S::to_challenge_scalar::<()>(challenge_packed);
rounds.push((
l,
@ -108,7 +108,7 @@ pub fn verify_proof<'a, C: CurveAffine, S: ChallengeSpace<C>, T: TranscriptRead<
.batch_invert();
let mut challenges = Vec::with_capacity(k);
let mut challenges_packed: Vec<Challenge> = Vec::with_capacity(k);
let mut challenges_packed: Vec<S::Challenge> = Vec::with_capacity(k);
for (l, r, challenge, challenge_inv, challenge_packed) in rounds {
msm.append_term(challenge_inv, l);
msm.append_term(challenge, r);

View File

@ -23,7 +23,7 @@ pub fn verify_proof<'b, 'a: 'b, I, C: CurveAffine, S: ChallengeSpace<C>, T: Tran
transcript: &mut T,
queries: I,
mut msm: MSM<'a, C>,
) -> Result<Guard<'a, C>, Error>
) -> Result<Guard<'a, C, S>, Error>
where
I: IntoIterator<Item = VerifierQuery<'b, C>> + Clone,
{

View File

@ -4,7 +4,6 @@
use blake2b_simd::{Params as Blake2bParams, State as Blake2bState};
use ff::Field;
use std::convert::TryInto;
use std::ops::Deref;
use crate::arithmetic::{Coordinates, CurveAffine, FieldExt};
@ -13,22 +12,13 @@ use std::marker::PhantomData;
/// Generic transcript view (from either the prover or verifier's perspective)
pub trait Transcript<C: CurveAffine, S: ChallengeSpace<C>> {
/// Squeeze a challenge (in the base field) from the transcript.
fn squeeze_challenge(&mut self) -> C::Base;
/// Squeeze a verifier challenge from the transcript. The length of the
/// challenge is determined by the `ChallengeSpace`.
fn squeeze_challenge(&mut self) -> S::Challenge;
/// Squeeze a 128-bit verifier challenge from the transcript.
fn squeeze_challenge_128(&mut self) -> Challenge {
Challenge(self.squeeze_challenge().get_lower_128())
}
/// Get a scalar challenge from a 128-bit challenge.
fn to_challenge_scalar<T>(&self, challenge: Challenge) -> ChallengeScalar<C, T> {
S::to_challenge_scalar::<T>(challenge)
}
/// Squeeze a scalar challenge from the transcript.
/// Squeeze a challenge (in the scalar field) from the transcript.
fn squeeze_challenge_scalar<T>(&mut self) -> ChallengeScalar<C, T> {
S::to_challenge_scalar::<T>(self.squeeze_challenge_128())
S::to_challenge_scalar(self.squeeze_challenge())
}
/// Writing the point to the transcript without writing it to the proof,
@ -112,6 +102,15 @@ impl<R: Read, C: CurveAffine, S: ChallengeSpace<C>> TranscriptRead<C, S> for Bla
}
impl<R: Read, C: CurveAffine, S: ChallengeSpace<C>> Transcript<C, S> for Blake2bRead<R, C, S> {
fn squeeze_challenge(&mut self) -> S::Challenge {
let hasher = self.state.clone();
let mut result: [u8; 64] = hasher.finalize().as_bytes().try_into().unwrap();
result[S::NUM_BYTES - 1] &= S::BYTE_MASK;
// self.state.update(&result[..S::NUM_BYTES]);
self.state.update(&result[..]);
S::Challenge::new(&result[..S::NUM_BYTES])
}
fn common_point(&mut self, point: C) -> io::Result<()> {
let coords: Coordinates<C> = Option::from(point.coordinates()).ok_or_else(|| {
io::Error::new(
@ -130,13 +129,6 @@ impl<R: Read, C: CurveAffine, S: ChallengeSpace<C>> Transcript<C, S> for Blake2b
Ok(())
}
fn squeeze_challenge(&mut self) -> C::Base {
let hasher = self.state.clone();
let result: [u8; 64] = hasher.finalize().as_bytes().try_into().unwrap();
self.state.update(&result[..]);
C::Base::from_bytes_wide(&result)
}
}
/// We will replace BLAKE2b with an algebraic hash function in a later version.
@ -185,6 +177,15 @@ impl<W: Write, C: CurveAffine, S: ChallengeSpace<C>> TranscriptWrite<C, S>
}
impl<W: Write, C: CurveAffine, S: ChallengeSpace<C>> Transcript<C, S> for Blake2bWrite<W, C, S> {
fn squeeze_challenge(&mut self) -> S::Challenge {
let hasher = self.state.clone();
let mut result: [u8; 64] = hasher.finalize().as_bytes().try_into().unwrap();
result[S::NUM_BYTES - 1] &= S::BYTE_MASK;
// self.state.update(&result[..S::NUM_BYTES]);
self.state.update(&result[..]);
S::Challenge::new(&result[..S::NUM_BYTES])
}
fn common_point(&mut self, point: C) -> io::Result<()> {
let coords: Coordinates<C> = Option::from(point.coordinates()).ok_or_else(|| {
io::Error::new(
@ -203,18 +204,49 @@ impl<W: Write, C: CurveAffine, S: ChallengeSpace<C>> Transcript<C, S> for Blake2
Ok(())
}
}
fn squeeze_challenge(&mut self) -> C::Base {
let hasher = self.state.clone();
let result: [u8; 64] = hasher.finalize().as_bytes().try_into().unwrap();
self.state.update(&result[..]);
C::Base::from_bytes_wide(&result)
/// `Challenge` trait implemented for challenges of different lengths
pub trait Challenge: Copy + Clone + std::fmt::Debug {
/// Try to create challenge of appropriate length.
fn new(challenge: &[u8]) -> Self;
}
/// This is a 16-byte verifier challenge.
#[derive(Copy, Clone, Debug)]
pub struct Challenge16(pub(crate) [u8; 16]);
impl Challenge for Challenge16 {
fn new(challenge: &[u8]) -> Self {
Self(challenge.try_into().unwrap())
}
}
/// This is a 128-bit verifier challenge.
impl std::ops::Deref for Challenge16 {
type Target = [u8; 16];
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// This is a 64-byte verifier challenge.
#[derive(Copy, Clone, Debug)]
pub struct Challenge(pub(crate) u128);
pub struct Challenge64(pub(crate) [u8; 64]);
impl Challenge for Challenge64 {
fn new(challenge: &[u8]) -> Self {
Self(challenge.try_into().unwrap())
}
}
impl std::ops::Deref for Challenge64 {
type Target = [u8; 64];
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// The scalar representation of a verifier challenge.
///
@ -226,29 +258,50 @@ pub struct ChallengeScalar<C: CurveAffine, T> {
_marker: PhantomData<T>,
}
/// The challenge space used to sample a scalar from a 128-bit challenge.
impl<C: CurveAffine, T> std::ops::Deref for ChallengeScalar<C, T> {
type Target = C::Scalar;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
/// The challenge space used to sample a scalar from a 512-bit challenge.
/// This protocol supports implementations for `ChallengeScalarEndo`, which
/// uses an endomorphism, and `ChallengeScalarFull`, which samples the
/// full-width field.
pub trait ChallengeSpace<C: CurveAffine> {
/// Derive a scalar from a 128-bit challenge in a certain challenge space.
fn to_challenge_scalar<T>(challenge: Challenge) -> ChallengeScalar<C, T>;
pub trait ChallengeSpace<C: CurveAffine>: Copy + Clone + std::fmt::Debug {
/// TODO
const NUM_BYTES: usize;
/// TODO
const BYTE_MASK: u8;
/// TODO
type Challenge: Challenge;
/// Derive a scalar from a challenge in a certain challenge space.
fn to_challenge_scalar<T>(challenge: Self::Challenge) -> ChallengeScalar<C, T>;
}
/// The scalar challenge space that applies the mapping of Algorithm 1 from the
/// [Halo](https://eprint.iacr.org/2019/1021) paper.
#[derive(Debug)]
#[derive(Copy, Clone, Debug)]
pub struct ChallengeScalarEndo<C: CurveAffine> {
_marker: PhantomData<C>,
}
impl<C: CurveAffine> ChallengeSpace<C> for ChallengeScalarEndo<C> {
fn to_challenge_scalar<T>(challenge: Challenge) -> ChallengeScalar<C, T> {
const NUM_BYTES: usize = 16;
const BYTE_MASK: u8 = 0b11111111;
type Challenge = Challenge16;
fn to_challenge_scalar<T>(challenge: Self::Challenge) -> ChallengeScalar<C, T> {
let mut acc = (C::Scalar::ZETA + &C::Scalar::one()).double();
let challenge: u128 = u128::from_le_bytes(challenge.0);
for i in (0..64).rev() {
let should_negate = ((challenge.0 >> ((i << 1) + 1)) & 1) == 1;
let should_endo = ((challenge.0 >> (i << 1)) & 1) == 1;
let should_negate = ((challenge >> ((i << 1) + 1)) & 1) == 1;
let should_endo = ((challenge >> (i << 1)) & 1) == 1;
let q = if should_negate {
-C::Scalar::one()
@ -267,30 +320,24 @@ impl<C: CurveAffine> ChallengeSpace<C> for ChallengeScalarEndo<C> {
}
/// The scalar challenge space that samples from the full-width field.
#[derive(Debug)]
#[derive(Copy, Clone, Debug)]
pub struct ChallengeScalarFull<C: CurveAffine> {
_marker: PhantomData<C>,
}
impl<C: CurveAffine> ChallengeSpace<C> for ChallengeScalarFull<C> {
/// This algorithm applies the mapping of Algorithm 1 from the
/// [Halo](https://eprint.iacr.org/2019/1021) paper.
fn to_challenge_scalar<T>(challenge: Challenge) -> ChallengeScalar<C, T> {
const NUM_BYTES: usize = 64;
const BYTE_MASK: u8 = 0b111111;
type Challenge = Challenge64;
fn to_challenge_scalar<T>(challenge: Self::Challenge) -> ChallengeScalar<C, T> {
ChallengeScalar {
inner: C::Scalar::from_u128(challenge.0),
inner: C::Scalar::from_bytes_wide(&challenge.0),
_marker: PhantomData,
}
}
}
impl<C: CurveAffine, T> Deref for ChallengeScalar<C, T> {
type Target = C::Scalar;
fn deref(&self) -> &C::Scalar {
&self.inner
}
}
pub(crate) fn read_n_points<C: CurveAffine, S: ChallengeSpace<C>, T: TranscriptRead<C, S>>(
transcript: &mut T,
n: usize,