jf_pcs/
transcript.rs

1// Copyright (c) 2022 Espresso Systems (espressosys.com)
2// This file is part of the Jellyfish library.
3
4// You should have received a copy of the MIT License
5// along with the Jellyfish library. If not, see <https://mit-license.org/>.
6
7//! Module for PolyIOP transcript.
8
9mod errors {
10    use ark_std::string::String;
11    use displaydoc::Display;
12
13    /// A `enum` specifying the possible failure modes of the Transcript.
14    #[derive(Display, Debug)]
15    pub enum TranscriptError {
16        /// Invalid Transcript: {0}
17        InvalidTranscript(String),
18        /// An error during (de)serialization: {0}
19        SerializationError(ark_serialize::SerializationError),
20    }
21
22    impl From<ark_serialize::SerializationError> for TranscriptError {
23        fn from(e: ark_serialize::SerializationError) -> Self {
24            Self::SerializationError(e)
25        }
26    }
27}
28
29pub use errors::TranscriptError;
30
31use ark_ff::PrimeField;
32use ark_serialize::CanonicalSerialize;
33use ark_std::{marker::PhantomData, string::ToString};
34use jf_utils::to_bytes;
35use merlin::Transcript;
36
37/// An IOP transcript consists of a Merlin transcript and a flag `is_empty` to
38/// indicate that if the transcript is empty.
39///
40/// It is associated with a prime field `F` for which challenges are generated
41/// over.
42///
43/// The `is_empty` flag is useful in the case where a protocol is initiated by
44/// the verifier, in which case the prover should start its phase by receiving a
45/// `non-empty` transcript.
46#[derive(Clone)]
47pub struct IOPTranscript<F: PrimeField> {
48    transcript: Transcript,
49    is_empty: bool,
50    #[doc(hidden)]
51    phantom: PhantomData<F>,
52}
53
54// TODO: merge this with jf_plonk::transcript
55impl<F: PrimeField> IOPTranscript<F> {
56    /// Create a new IOP transcript.
57    pub fn new(label: &'static [u8]) -> Self {
58        Self {
59            transcript: Transcript::new(label),
60            is_empty: true,
61            phantom: PhantomData,
62        }
63    }
64
65    /// Append the message to the transcript.
66    pub fn append_message(
67        &mut self,
68        label: &'static [u8],
69        msg: &[u8],
70    ) -> Result<(), TranscriptError> {
71        self.transcript.append_message(label, msg);
72        self.is_empty = false;
73        Ok(())
74    }
75
76    /// Append the message to the transcript.
77    pub fn append_serializable_element<S: CanonicalSerialize>(
78        &mut self,
79        label: &'static [u8],
80        group_elem: &S,
81    ) -> Result<(), TranscriptError> {
82        self.append_message(label, &to_bytes!(group_elem)?)
83    }
84
85    /// Generate the challenge from the current transcript
86    /// and append it to the transcript.
87    ///
88    /// The output field element is statistical uniform as long
89    /// as the field has a size less than 2^384.
90    pub fn get_and_append_challenge(&mut self, label: &'static [u8]) -> Result<F, TranscriptError> {
91        //  we need to reject when transcript is empty
92        if self.is_empty {
93            return Err(TranscriptError::InvalidTranscript(
94                "transcript is empty".to_string(),
95            ));
96        }
97
98        let mut buf = [0u8; 64];
99        self.transcript.challenge_bytes(label, &mut buf);
100        let challenge = F::from_le_bytes_mod_order(&buf);
101        self.append_serializable_element(label, &challenge)?;
102        Ok(challenge)
103    }
104
105    /// Generate the challenge from the current transcript
106    /// and append it to the transcript.
107    ///
108    /// Without exposing the internal field `transcript`,
109    /// this is a wrapper around getting bytes as opposed to field elements.
110    pub fn get_and_append_byte_challenge(
111        &mut self,
112        label: &'static [u8],
113        dest: &mut [u8],
114    ) -> Result<(), TranscriptError> {
115        //  we need to reject when transcript is empty
116        if self.is_empty {
117            return Err(TranscriptError::InvalidTranscript(
118                "transcript is empty".to_string(),
119            ));
120        }
121
122        self.transcript.challenge_bytes(label, dest);
123        self.append_message(label, dest)?;
124        Ok(())
125    }
126}