// Copyright (c) 2023 Espresso Systems (espressosys.com) // This file is part of the HyperPlonk library. // You should have received a copy of the MIT License // along with the HyperPlonk library. If not, see . //! Module for PolyIOP transcript. //! TODO(ZZ): move this module to HyperPlonk where the transcript will also be //! useful. //! TODO(ZZ): decide which APIs need to be public. mod errors; pub use errors::TranscriptError; use ark_ff::PrimeField; use ark_serialize::CanonicalSerialize; use merlin::Transcript; use std::marker::PhantomData; /// An IOP transcript consists of a Merlin transcript and a flag `is_empty` to /// indicate that if the transcript is empty. /// /// It is associated with a prime field `F` for which challenges are generated /// over. /// /// The `is_empty` flag is useful in the case where a protocol is initiated by /// the verifier, in which case the prover should start its phase by receiving a /// `non-empty` transcript. #[derive(Clone)] pub struct IOPTranscript { transcript: Transcript, is_empty: bool, #[doc(hidden)] phantom: PhantomData, } // TODO: Make this into a Trait impl IOPTranscript { /// Create a new IOP transcript. pub fn new(label: &'static [u8]) -> Self { Self { transcript: Transcript::new(label), is_empty: true, phantom: PhantomData::default(), } } // Append the message to the transcript. pub fn append_message( &mut self, label: &'static [u8], msg: &[u8], ) -> Result<(), TranscriptError> { self.transcript.append_message(label, msg); self.is_empty = false; Ok(()) } // Append the message to the transcript. pub fn append_field_element( &mut self, label: &'static [u8], field_elem: &F, ) -> Result<(), TranscriptError> { self.append_message(label, &to_bytes!(field_elem)?) } // Append the message to the transcript. pub fn append_serializable_element( &mut self, label: &'static [u8], group_elem: &S, ) -> Result<(), TranscriptError> { self.append_message(label, &to_bytes!(group_elem)?) } // Generate the challenge from the current transcript // and append it to the transcript. // // The output field element is statistical uniform as long // as the field has a size less than 2^384. pub fn get_and_append_challenge(&mut self, label: &'static [u8]) -> Result { // we need to reject when transcript is empty if self.is_empty { return Err(TranscriptError::InvalidTranscript( "transcript is empty".to_string(), )); } let mut buf = [0u8; 64]; self.transcript.challenge_bytes(label, &mut buf); let challenge = F::from_le_bytes_mod_order(&buf); self.append_serializable_element(label, &challenge)?; Ok(challenge) } // Generate a list of challenges from the current transcript // and append them to the transcript. // // The output field element are statistical uniform as long // as the field has a size less than 2^384. pub fn get_and_append_challenge_vectors( &mut self, label: &'static [u8], len: usize, ) -> Result, TranscriptError> { // we need to reject when transcript is empty if self.is_empty { return Err(TranscriptError::InvalidTranscript( "transcript is empty".to_string(), )); } let mut res = vec![]; for _ in 0..len { res.push(self.get_and_append_challenge(label)?) } Ok(res) } } /// Takes as input a struct, and converts them to a series of bytes. All traits /// that implement `CanonicalSerialize` can be automatically converted to bytes /// in this manner. #[macro_export] macro_rules! to_bytes { ($x:expr) => {{ let mut buf = ark_std::vec![]; ark_serialize::CanonicalSerialize::serialize($x, &mut buf).map(|_| buf) }}; }