Change API for dealing with transcripts to integrate proof reading/writing.

This commit is contained in:
Sean Bowe 2020-12-21 11:01:22 -07:00
parent fb37172ffa
commit fb232ddec0
No known key found for this signature in database
GPG key ID: 95684257D8F8B031

View file

@ -2,125 +2,221 @@
//! transcripts. //! transcripts.
use ff::Field; use ff::Field;
use std::marker::PhantomData;
use std::ops::Deref; use std::ops::Deref;
use crate::arithmetic::{CurveAffine, FieldExt}; use crate::arithmetic::{CurveAffine, FieldExt};
/// This is a generic interface for a sponge function that can be used for use std::io::{self, Read, Write};
/// Fiat-Shamir transformations. use std::marker::PhantomData;
pub trait Hasher<F: FieldExt>: Clone + Send + Sync + 'static {
/// Initialize the sponge with some key. /// Generic transcript view (from either the prover or verifier's perspective)
fn init(key: F) -> Self; pub trait Transcript<C: CurveAffine> {
/// Absorb a field element into the sponge. /// Squeeze a challenge (in the base field) from the transcript.
fn absorb(&mut self, value: F); fn squeeze_challenge(&mut self) -> C::Base;
/// Square a field element out of the sponge.
fn squeeze(&mut self) -> F; /// Writing the point to the transcript without writing it to the proof,
/// treating it as a common input.
fn common_point(&mut self, point: C) -> io::Result<()>;
} }
/// This is just a simple (and completely broken) hash function, standing in for /// Transcript view from the perspective of a verifier that has access to an
/// some algebraic hash function that we'll switch to later. /// input stream of data from the prover to the verifier.
pub trait TranscriptRead<R: Read, C: CurveAffine>: Transcript<C> {
/// Initialize the transcript with a key and an input stream.
fn init(reader: R, key: C::Base) -> Self;
/// Read a curve point from the prover.
fn read_point(&mut self) -> io::Result<C>;
/// Read a curve scalar from the prover.
fn read_scalar(&mut self) -> io::Result<C::Scalar>;
}
/// Transcript view from the perspective of a prover that has access to an
/// output stream of messages from the prover to the verifier.
pub trait TranscriptWrite<W: Write, C: CurveAffine>: Transcript<C> {
/// Forked transcript that does not write to the proof structure.
type ForkedTranscript: TranscriptWrite<io::Sink, C>;
/// Initialize the transcript with a key and an output stream.
fn init(writer: W, key: C::Base) -> Self;
/// Write a curve point to the proof and the transcript.
fn write_point(&mut self, point: C) -> io::Result<()>;
/// Write a scalar to the proof and the transcript.
fn write_scalar(&mut self, scalar: C::Scalar) -> io::Result<()>;
/// Fork the transcript, creating a variant of this `TranscriptWrite` which
/// does not output anything to the writer.
fn fork(&self) -> Self::ForkedTranscript;
/// Return the writer to conclude the interaction and take possession of the
/// proof.
fn finalize(self) -> W;
}
/// This is just a simple (and completely broken) transcript reader
/// implementation, standing in for some algebraic hash function that we'll
/// switch to later.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct DummyHash<F: FieldExt> { pub struct DummyHashReader<R: Read, C: CurveAffine> {
power: F, base_state: C::Base,
state: F, scalar_state: C::Scalar,
read_scalar: bool,
reader: R,
} }
impl<F: FieldExt> Hasher<F> for DummyHash<F> { impl<R: Read, C: CurveAffine> TranscriptRead<R, C> for DummyHashReader<R, C> {
fn init(key: F) -> Self { fn init(reader: R, key: C::Base) -> Self {
DummyHash { DummyHashReader {
power: F::ZETA + F::one() + key, base_state: key + &C::Base::from_u64(1013),
state: F::ZETA, scalar_state: C::Scalar::from_u64(1013),
read_scalar: false,
reader,
} }
} }
fn absorb(&mut self, value: F) {
for _ in 0..10 { fn read_point(&mut self) -> io::Result<C> {
self.state += value; let mut compressed = [0u8; 32];
self.state *= self.power; self.reader.read_exact(&mut compressed[..])?;
self.power += self.power.square(); let point: C = Option::from(C::from_bytes(&compressed)).ok_or(io::Error::new(
self.state += self.power; io::ErrorKind::Other,
"invalid point encoding in proof",
))?;
self.common_point(point)?;
Ok(point)
}
fn read_scalar(&mut self) -> io::Result<C::Scalar> {
let mut data = [0u8; 32];
self.reader.read_exact(&mut data)?;
let scalar = Option::from(C::Scalar::from_bytes(&data)).ok_or(io::Error::new(
io::ErrorKind::Other,
"invalid field element encoding in proof",
))?;
self.scalar_state += &(scalar * &C::Scalar::ZETA);
self.scalar_state = self.scalar_state.square();
self.read_scalar = true;
Ok(scalar)
} }
} }
fn squeeze(&mut self) -> F {
let tmp = self.state; impl<R: Read, C: CurveAffine> Transcript<C> for DummyHashReader<R, C> {
self.absorb(tmp); fn common_point(&mut self, point: C) -> io::Result<()> {
let (x, y) = Option::from(point.get_xy()).ok_or(io::Error::new(
io::ErrorKind::Other,
"cannot write points at infinity to the transcript",
))?;
self.base_state += &(x * &C::Base::ZETA);
self.base_state = self.base_state.square();
self.base_state += &(y * &C::Base::ZETA);
self.base_state = self.base_state.square();
Ok(())
}
fn squeeze_challenge(&mut self) -> C::Base {
if self.read_scalar {
let x = C::Base::from_bytes(&self.scalar_state.to_bytes()).unwrap();
self.base_state += &(x * &C::Base::ZETA);
self.base_state = self.base_state.square();
self.scalar_state = self.scalar_state.square();
self.read_scalar = false;
}
let tmp = self.base_state;
for _ in 0..5 {
self.base_state *= &(C::Base::ZETA + &C::Base::ZETA);
self.base_state += &C::Base::ZETA;
self.base_state = self.base_state.square();
}
tmp tmp
} }
} }
/// A transcript that can absorb points from both the base field and scalar /// This is just a simple (and completely broken) transcript writer
/// field of a curve /// implementation, standing in for some algebraic hash function that we'll
/// switch to later.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Transcript<C: CurveAffine, HBase, HScalar> pub struct DummyHashWriter<W: Write, C: CurveAffine> {
where base_state: C::Base,
HBase: Hasher<C::Base>, scalar_state: C::Scalar,
HScalar: Hasher<C::Scalar>, written_scalar: bool,
{ writer: W,
// 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<C>,
} }
impl<C: CurveAffine, HBase: Hasher<C::Base>, HScalar: Hasher<C::Scalar>> impl<W: Write, C: CurveAffine> TranscriptWrite<W, C> for DummyHashWriter<W, C> {
Transcript<C, HBase, HScalar> type ForkedTranscript = DummyHashWriter<io::Sink, C>;
{
/// Initialise a new transcript with Field::one() as keys fn init(writer: W, key: C::Base) -> Self {
/// in both the base_hasher and scalar_hasher DummyHashWriter {
pub fn new() -> Self { base_state: key + &C::Base::from_u64(1013),
let base_hasher = HBase::init(C::Base::one()); scalar_state: C::Scalar::from_u64(1013),
let scalar_hasher = HScalar::init(C::Scalar::one()); written_scalar: false,
Transcript { writer,
base_hasher, }
scalar_hasher, }
scalar_needs_squeezing: false, fn write_point(&mut self, point: C) -> io::Result<()> {
_marker: PhantomData, self.common_point(point)?;
let compressed = point.to_bytes();
self.writer.write_all(&compressed[..])
}
fn write_scalar(&mut self, scalar: C::Scalar) -> io::Result<()> {
self.scalar_state += &(scalar * &C::Scalar::ZETA);
self.scalar_state = self.scalar_state.square();
self.written_scalar = true;
let data = scalar.to_bytes();
self.writer.write_all(&data[..])
}
fn fork(&self) -> Self::ForkedTranscript {
DummyHashWriter {
base_state: self.base_state,
scalar_state: self.scalar_state,
written_scalar: self.written_scalar,
writer: io::sink(),
}
}
fn finalize(self) -> W {
// TODO: handle outstanding scalars?
self.writer
} }
} }
fn conditional_scalar_squeeze(&mut self) { impl<W: Write, C: CurveAffine> Transcript<C> for DummyHashWriter<W, C> {
if self.scalar_needs_squeezing { fn common_point(&mut self, point: C) -> io::Result<()> {
let transcript_scalar_point = let (x, y) = Option::from(point.get_xy()).ok_or(io::Error::new(
C::Base::from_bytes(&(self.scalar_hasher.squeeze()).to_bytes()).unwrap(); io::ErrorKind::Other,
self.base_hasher.absorb(transcript_scalar_point); "cannot write points at infinity to the transcript",
self.scalar_needs_squeezing = false; ))?;
} self.base_state += &(x * &C::Base::ZETA);
} self.base_state = self.base_state.square();
self.base_state += &(y * &C::Base::ZETA);
self.base_state = self.base_state.square();
/// 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(()) Ok(())
} }
/// Absorb a base into the base_hasher fn squeeze_challenge(&mut self) -> C::Base {
pub fn absorb_base(&mut self, base: C::Base) { if self.written_scalar {
self.conditional_scalar_squeeze(); let x = C::Base::from_bytes(&self.scalar_state.to_bytes()).unwrap();
self.base_hasher.absorb(base); self.base_state += &(x * &C::Base::ZETA);
self.base_state = self.base_state.square();
self.scalar_state = self.scalar_state.square();
self.written_scalar = false;
} }
/// Absorb a scalar into the scalar_hasher let tmp = self.base_state;
pub fn absorb_scalar(&mut self, scalar: C::Scalar) { for _ in 0..5 {
self.scalar_hasher.absorb(scalar); self.base_state *= &(C::Base::ZETA + &C::Base::ZETA);
self.scalar_needs_squeezing = true; self.base_state += &C::Base::ZETA;
self.base_state = self.base_state.square();
} }
/// Squeeze the transcript to obtain a C::Base value. tmp
pub fn squeeze(&mut self) -> C::Base {
self.conditional_scalar_squeeze();
self.base_hasher.squeeze()
} }
} }
@ -130,64 +226,61 @@ pub struct Challenge(pub(crate) u128);
impl Challenge { impl Challenge {
/// Obtains a new challenge from the transcript. /// Obtains a new challenge from the transcript.
pub fn get<C, HBase, HScalar>(transcript: &mut Transcript<C, HBase, HScalar>) -> Challenge pub fn get<C: CurveAffine, T: Transcript<C>>(transcript: &mut T) -> Challenge {
where Challenge(transcript.squeeze_challenge().get_lower_128())
C: CurveAffine,
HBase: Hasher<C::Base>,
HScalar: Hasher<C::Scalar>,
{
Challenge(transcript.squeeze().get_lower_128())
} }
} }
/// The scalar representation of a verifier challenge. /// The scalar representation of a verifier challenge.
/// ///
/// The `T` type can be used to scope the challenge to a specific context, or set to `()` /// The `Type` type can be used to scope the challenge to a specific context, or
/// if no context is required. /// set to `()` if no context is required.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct ChallengeScalar<F: FieldExt, T> { pub struct ChallengeScalar<C: CurveAffine, Type> {
inner: F, inner: C::Scalar,
_marker: PhantomData<T>, _marker: PhantomData<Type>,
} }
impl<F: FieldExt, T> From<Challenge> for ChallengeScalar<F, T> { impl<C: CurveAffine, Type> From<Challenge> for ChallengeScalar<C, Type> {
/// This algorithm applies the mapping of Algorithm 1 from the /// This algorithm applies the mapping of Algorithm 1 from the
/// [Halo](https://eprint.iacr.org/2019/1021) paper. /// [Halo](https://eprint.iacr.org/2019/1021) paper.
fn from(challenge: Challenge) -> Self { fn from(challenge: Challenge) -> Self {
let mut acc = (F::ZETA + F::one()).double(); let mut acc = (C::Scalar::ZETA + &C::Scalar::one()).double();
for i in (0..64).rev() { for i in (0..64).rev() {
let should_negate = ((challenge.0 >> ((i << 1) + 1)) & 1) == 1; let should_negate = ((challenge.0 >> ((i << 1) + 1)) & 1) == 1;
let should_endo = ((challenge.0 >> (i << 1)) & 1) == 1; let should_endo = ((challenge.0 >> (i << 1)) & 1) == 1;
let q = if should_negate { -F::one() } else { F::one() }; let q = if should_negate {
let q = if should_endo { q * F::ZETA } else { q }; -C::Scalar::one()
acc = acc + q + acc; } else {
C::Scalar::one()
};
let q = if should_endo { q * &C::Scalar::ZETA } else { q };
acc = acc + &q + &acc;
} }
ChallengeScalar { ChallengeScalar {
inner: acc, inner: acc,
_marker: PhantomData::default(), _marker: PhantomData,
} }
} }
} }
impl<F: FieldExt, T> ChallengeScalar<F, T> { impl<C: CurveAffine, Type> ChallengeScalar<C, Type> {
/// Obtains a new challenge from the transcript. /// Obtains a new challenge from the transcript.
pub fn get<C, HBase, HScalar>(transcript: &mut Transcript<C, HBase, HScalar>) -> Self pub fn get<T: Transcript<C>>(transcript: &mut T) -> Self
where where
C: CurveAffine, C: CurveAffine,
HBase: Hasher<C::Base>,
HScalar: Hasher<C::Scalar>,
{ {
Challenge::get(transcript).into() Challenge::get(transcript).into()
} }
} }
impl<F: FieldExt, T> Deref for ChallengeScalar<F, T> { impl<C: CurveAffine, Type> Deref for ChallengeScalar<C, Type> {
type Target = F; type Target = C::Scalar;
fn deref(&self) -> &F { fn deref(&self) -> &C::Scalar {
&self.inner &self.inner
} }
} }