Skip to content

Commit

Permalink
Introduce transcript serialization for Merlin.
Browse files Browse the repository at this point in the history
  • Loading branch information
mmaker committed Sep 10, 2023
1 parent 8729ffa commit 4c14e39
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 64 deletions.
51 changes: 30 additions & 21 deletions src/arthur.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use rand::{CryptoRng, RngCore};

use crate::hash::Unit;
use crate::IOPattern;
use crate::{IOPattern, Safe};

use super::hash::{DuplexHash, Keccak};
use super::{DefaultHash, DefaultRng, InvalidTag, Merlin};
use super::{DefaultHash, DefaultRng, InvalidTag};
/// Arthur is a cryptographically-secure random number generator that is bound to the protocol transcript.
///
/// For most public-coin protocols it is *vital* not to have two commitments for the same challenge.
Expand Down Expand Up @@ -37,10 +37,8 @@ impl<R: RngCore + CryptoRng> RngCore for ProverRng<R> {
let len = usize::min(dest.len(), 32);
self.csrng.fill_bytes(&mut dest[..len]);
self.sponge.absorb_unchecked(&dest[..len]);

// fill `dest` with the output of the sponge
self.sponge.squeeze_unchecked(dest);

// erase the state from the sponge so that it can't be reverted
self.sponge.ratchet_unchecked();
}
Expand All @@ -56,18 +54,18 @@ pub struct ArthurBuilder<H: DuplexHash<U = U>, U: Unit>
where
H: DuplexHash,
{
merlin: Merlin<H, U>,
safe: Safe<H>,
u8sponge: Keccak,
}

impl<H: DuplexHash<U = U>, U: Unit> ArthurBuilder<H, U> {
pub(crate) fn new(io_pattern: &IOPattern<H>) -> Self {
let merlin = Merlin::new(io_pattern);
let safe = Safe::new(io_pattern);

let mut u8sponge = Keccak::default();
u8sponge.absorb_unchecked(io_pattern.as_bytes());

Self { u8sponge, merlin }
Self { u8sponge, safe }
}

// rekey the private sponge with some additional secrets (i.e. with the witness)
Expand All @@ -81,14 +79,14 @@ impl<H: DuplexHash<U = U>, U: Unit> ArthurBuilder<H, U> {
// Finalize the state integrating a cryptographically-secure
// random number generator that will be used to seed the state before future squeezes.
pub fn finalize_with_rng<R: RngCore + CryptoRng>(self, csrng: R) -> Arthur<H, R, H::U> {
let arthur = ProverRng {
let rng = ProverRng {
sponge: self.u8sponge,
csrng,
};

Arthur {
merlin: self.merlin,
arthur,
safe: self.safe,
rng,
transcript: Vec::new(),
}
}
Expand All @@ -109,9 +107,9 @@ where
U: Unit,
{
/// The randomness state of the prover.
pub(crate) arthur: ProverRng<R>,
pub(crate) rng: ProverRng<R>,
/// The public coins for the protocol
pub(crate) merlin: Merlin<H, H::U>,
pub(crate) safe: Safe<H>,
/// The encoded data.
transcript: Vec<u8>,
}
Expand All @@ -121,29 +119,40 @@ impl<R: RngCore + CryptoRng, H: DuplexHash> Arthur<H, R, H::U> {
ArthurBuilder::new(io_pattern).finalize_with_rng(csrng)
}

#[inline]
pub fn absorb_native(&mut self, input: &[H::U]) -> Result<(), InvalidTag> {
#[inline(always)]
pub fn absorb(&mut self, input: &[H::U]) -> Result<(), InvalidTag> {
// let serialized = bincode::serialize(input).unwrap();
// self.arthur.sponge.absorb_unchecked(&serialized);
let old_len = self.transcript.len();
// write never fails on Vec<u8>
H::U::write(input, &mut self.transcript).unwrap();
self.arthur
self.rng
.sponge
.absorb_unchecked(&self.transcript[old_len..]);
self.merlin.absorb_native(input)?;
self.safe.absorb(input)?;

Ok(())
}

fn absorb_common(&mut self, input: &[H::U]) -> Result<(), InvalidTag> {
let len = self.transcript.len();
self.absorb(input)?;
self.transcript.truncate(len);
Ok(())
}

pub fn squeeze_native(&mut self, output: &mut [H::U]) -> Result<(), InvalidTag> {
self.safe.squeeze(output)
}

#[inline(always)]
pub fn ratchet(&mut self) -> Result<(), InvalidTag> {
self.merlin.ratchet()
self.safe.ratchet()
}

#[inline(always)]
pub fn rng<'a>(&'a mut self) -> &'a mut (impl CryptoRng + RngCore) {
&mut self.arthur
&mut self.rng
}

pub fn transcript(&self) -> &[u8] {
Expand All @@ -155,18 +164,18 @@ impl<R: RngCore + CryptoRng> CryptoRng for ProverRng<R> {}

impl<R: RngCore + CryptoRng, H: DuplexHash> core::fmt::Debug for Arthur<H, R, H::U> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.merlin.fmt(f)
self.safe.fmt(f)
}
}

impl<H: DuplexHash<U = u8>, R: RngCore + CryptoRng> Arthur<H, R, u8> {
#[inline(always)]
pub fn absorb_bytes(&mut self, input: &[u8]) -> Result<(), InvalidTag> {
self.absorb_native(input)
self.absorb(input)
}

#[inline(always)]
pub fn squeeze_bytes(&mut self, output: &mut [u8]) -> Result<(), InvalidTag> {
self.merlin.squeeze_native(output)
self.safe.squeeze(output)
}
}
3 changes: 0 additions & 3 deletions src/hash/keccak.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ impl Sponge for AlignedKeccakState {
}

fn permute(&mut self) {
// self.state[self.pos as usize] ^= self.pos_begin;
// self.state[(self.pos + 1) as usize] ^= 0x04;
// self.state[(RATE + 1) as usize] ^= 0x80;
keccak::f1600(transmute_state(self));
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/hash/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub use keccak::Keccak;
pub trait Unit: Clone + Sized + zeroize::Zeroize {
/// Write a bunch of units in the wire.
fn write(bunch: &[Self], w: &mut impl std::io::Write) -> Result<(), std::io::Error>;
/// Read a bunch of units from the wire
fn read(r: &mut impl std::io::Read, bunch: &mut [Self]) -> Result<(), std::io::Error>;
}

/// A DuplexHash is an abstract interface for absorbing and squeezing data.
Expand Down Expand Up @@ -58,6 +60,10 @@ impl Unit for u8 {
fn write(bunch: &[Self], w: &mut impl std::io::Write) -> Result<(), std::io::Error> {
w.write_all(bunch)
}

fn read(r: &mut impl std::io::Read, bunch: &mut [Self]) -> Result<(), std::io::Error> {
r.read_exact(bunch)
}
}

// /// Return the number of random bytes that can be extracted from a random lane.
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,4 @@ pub type DefaultRng = rand::rngs::OsRng;

/// Default hash function used ([`hash::Keccak`])
pub type DefaultHash = hash::Keccak;

27 changes: 20 additions & 7 deletions src/merlin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,7 @@ where
U: Unit,
{
safe: Safe<H>,
}

impl<H: DuplexHash> From<&IOPattern<H>> for Merlin<H, H::U> {
fn from(io_pattern: &IOPattern<H>) -> Self {
Merlin::new(io_pattern)
}
transcript: Vec<u8>
}

impl<U: Unit, H: DuplexHash<U = U>> Merlin<H, U> {
Expand All @@ -28,7 +23,21 @@ impl<U: Unit, H: DuplexHash<U = U>> Merlin<H, U> {
/// The resulting object will act as the verifier in a zero-knowledge protocol.
pub fn new(io_pattern: &IOPattern<H>) -> Self {
let safe = Safe::new(io_pattern);
Self { safe }
let transcript = Vec::new();
Self { safe, transcript }
}

fn absorb(&mut self, input: &mut [H::U]) -> Result<(), InvalidTag> {
H::U::read(&mut self.transcript.as_slice(), input).unwrap();
self.safe.absorb(input)
}

fn absorb_common(&mut self, input: &[H::U], ) -> Result<(), InvalidTag> {
self.safe.absorb(input)
}

fn squeeze(&mut self, input: &mut [H::U]) -> Result<(), InvalidTag> {
self.safe.squeeze(input)
}

/// Absorb a slice of lanes into the sponge.
Expand All @@ -44,11 +53,13 @@ impl<U: Unit, H: DuplexHash<U = U>> Merlin<H, U> {
}

/// Signals the end of the statement and returns the (compressed) sponge state.
#[inline(always)]
pub fn ratchet_and_store(self) -> Result<Vec<H::U>, InvalidTag> {
self.safe.ratchet_and_store()
}

/// Get a challenge of `count` elements.
#[inline(always)]
pub fn squeeze_native(&mut self, output: &mut [H::U]) -> Result<(), InvalidTag> {
self.safe.squeeze(output)
}
Expand All @@ -61,10 +72,12 @@ impl<H: DuplexHash<U = U>, U: Unit> core::fmt::Debug for Merlin<H, U> {
}

impl<H: DuplexHash<U = u8>> Merlin<H, u8> {
#[inline(always)]
pub fn absorb_bytes(&mut self, input: &[u8]) -> Result<(), InvalidTag> {
self.absorb_native(input)
}

#[inline(always)]
pub fn squeeze_bytes(&mut self, output: &mut [u8]) -> Result<(), InvalidTag> {
self.squeeze_native(output)
}
Expand Down
44 changes: 30 additions & 14 deletions src/plugins/arkworks/mod.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,52 @@
use crate::{errors::InvalidTag, hash::Unit, Arthur, DuplexHash, IOPattern, Merlin};
use crate::{errors::InvalidTag, hash::Unit, Arthur, DuplexHash, IOPattern, Merlin, Safe};

pub mod prelude;

// this module contains experiments for a more deep integration into arkworks.
// It doesn't work and is left here in this repository only for backlog.
// mod hazmat;

use std::io;
use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::{Fp, FpConfig, PrimeField};
use ark_serialize::CanonicalSerialize;
use ark_serialize::{CanonicalSerialize, CanonicalDeserialize};
use prelude::*;
use rand::{CryptoRng, RngCore};

const fn f_bytes<F: PrimeField>() -> usize {
(F::MODULUS_BIT_SIZE as usize + 128) / 8
}

impl<C: FpConfig<N>, const N: usize> Unit for Fp<C, N> {
fn write(bunch: &[Self], w: &mut impl std::io::Write) -> Result<(), std::io::Error> {
bunch
.serialize_compressed(w)
.map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "oh no!"))
fn write(bunch: &[Self], w: &mut impl io::Write) -> Result<(), io::Error> {
bunch.iter().map(|b| {
b.serialize_compressed(w)
.map_err(|_| io::Error::new(io::ErrorKind::Other, "oh no!"))
}).collect()
}

fn read(r: &mut impl std::io::Read, bunch: &mut [Self]) -> Result<(), std::io::Error> {
for b in bunch.iter_mut() {
*b = ark_ff::Fp::<C, N>::deserialize_compressed(r).map_err(|_| io::Error::new(io::ErrorKind::Other, "oh no!"))?
}
Ok(())
}
}

impl<H: DuplexHash<U = u8>> Bridgeu8 for Merlin<H, u8> {
impl<H: DuplexHash<U = u8>> Bridgeu8 for Safe<H> {
fn absorb_serializable<S: CanonicalSerialize>(&mut self, input: &[S]) -> Result<(), SerTagErr> {
let mut u8input = Vec::new();
input
.iter()
.map(|s| s.serialize_compressed(&mut u8input))
.collect::<Result<(), _>>()
.map_err(|e| SerTagErr::Ser(e))?;
self.absorb_native(&u8input).map_err(|e| SerTagErr::Tag(e))
self.absorb(&u8input).map_err(|e| SerTagErr::Tag(e))
}

fn squeeze_pfelt<F: PrimeField>(&mut self) -> Result<F, InvalidTag> {
let len = ((F::BasePrimeField::MODULUS_BIT_SIZE + 128) / 8) as usize;
let mut bytes = vec![0; len];
self.squeeze_bytes(&mut bytes)?;
let mut bytes = vec![0; f_bytes::<F>()];
self.squeeze(&mut bytes)?;
Ok(F::from_le_bytes_mod_order(&bytes))
}
}
Expand All @@ -47,11 +59,14 @@ impl<H: DuplexHash<U = u8>, R: RngCore + CryptoRng> Bridgeu8 for Arthur<H, R, u8
.map(|s| s.serialize_compressed(&mut u8input))
.collect::<Result<(), _>>()
.map_err(|e| SerTagErr::Ser(e))?;
self.absorb_native(&u8input).map_err(|e| SerTagErr::Tag(e))
match self.absorb(&u8input) {
Err(e) => Err(SerTagErr::Tag(e)),
Ok(()) => Ok(())
}
}

fn squeeze_pfelt<F: PrimeField>(&mut self) -> Result<F, InvalidTag> {
self.merlin.squeeze_pfelt()
self.safe.squeeze_pfelt()
}
}

Expand Down Expand Up @@ -98,6 +113,7 @@ impl<H: DuplexHash> ArkIOPattern for IOPattern<H> {
}

fn squeeze_pfelt<F: PrimeField>(self, count: usize, label: &'static str) -> Self {
self.absorb(count * (F::MODULUS_BIT_SIZE as usize + 128) / 8, label)
println!("IO: {} * {}", f_bytes::<F>(), count);
self.squeeze(count * f_bytes::<F>(), label)
}
}
Loading

0 comments on commit 4c14e39

Please sign in to comment.