shared_types/
transcript.rs

1//! Types for modeling and interacting with a transcript sponge when applying the
2//! Fiat-Shamir transformation on a an interactive protocol.
3
4use std::fmt::Display;
5
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8use tracing::warn;
9use utils::sha256_hash_chain_on_field_elems;
10
11use crate::Field;
12use std::fmt::Debug;
13
14pub mod counting_transcript;
15pub mod ec_transcript;
16pub mod keccak_transcript;
17pub mod poseidon_sponge;
18pub mod test_transcript;
19mod utils;
20
21use anyhow::{anyhow, Result};
22
23/// A `TranscriptSponge` provides the basic interface for a cryptographic sponge
24/// operating on field elements. It is typically used for representing the
25/// transcript of an interactive protocol turned non-interactive view
26/// Fiat-Shamir.
27pub trait TranscriptSponge<F>: Clone + Send + Sync + Default + Debug {
28    /// Absorb the initialization label.
29    fn absorb_initialization_label(&mut self, label: &str);
30
31    /// Absorb a single field element `elem`.
32    fn absorb(&mut self, elem: F);
33
34    /// Absorb a list of field elements sequentially.
35    fn absorb_elements(&mut self, elements: &[F]);
36
37    /// Generate a field element by squeezing the sponge. Internal state is
38    /// modified.
39    fn squeeze(&mut self) -> F;
40
41    /// Generate a sequence of field elements by squeezing the sponge
42    /// `num_elements` times. Internal state is modified.
43    fn squeeze_elements(&mut self, num_elements: usize) -> Vec<F>;
44}
45
46/// Describes an elementary operation on a transcript.
47#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
48enum Operation<F> {
49    /// An append operation consists of a label (used for debugging purposes)
50    /// and a vector of field elements to be appended in order to the
51    /// transcript.
52    Append(String, Vec<F>),
53
54    /// An `AppendInput` operation consists of the same things as an `Append`
55    /// operation, alongside the circuit-expensive hash (typically instantiated
56    /// with e.g. SHA-256) of the elements which are to be appended.
57    ///
58    /// Note that this is to defend against attacks of the form written about
59    /// within <https://eprint.iacr.org/2025/118>.
60    AppendInput(String, Vec<F>, Vec<F>),
61
62    /// A squeeze operation consists of a label (used for debugging purposes)
63    /// and a counter of how many elements are to be squeezed from the sponge.
64    Squeeze(String, usize),
65}
66
67/// A type used for storing an immutable version of the transcript.
68/// A `Transcript` is typically generated by the prover using a
69/// `TranscriptWriter`, and is then serialized and saved on disk as part of the
70/// generated proof. The verifier de-serializes the transcript and can access it
71/// through the `TranscriptReader` interface.
72#[derive(PartialEq, Clone, Serialize, Deserialize, Debug)]
73pub struct Transcript<T> {
74    /// A label used to identify this transcript. Used for debugging purposes.
75    label: String,
76
77    /// The content of the transcript represented as a sequence of operations
78    /// used to generate it.
79    operations: Vec<Operation<T>>,
80}
81
82impl<F: Clone> Transcript<F> {
83    /// Create an empty transcript with identifier `label`.
84    pub fn new(label: &str) -> Self {
85        Self {
86            label: String::from(label),
87            operations: vec![],
88        }
89    }
90
91    /// Record the operation of appending `elements` in the sponge.
92    /// `label` is an identifier for this operation that can be used for sanity
93    /// checking by the verifier.
94    pub fn append_elements(&mut self, label: &str, elements: &[F]) {
95        self.operations
96            .push(Operation::Append(String::from(label), elements.to_vec()));
97    }
98
99    /// Record the operation of appending circuit input `elements` into the
100    /// sponge. `label` is the same as that within [Transcript::append_elements()],
101    /// and `elements_hash` is the hash chain of `elements`, computed in such
102    /// a way that the GKR circuit cannot emulate such a hash chain.
103    pub fn append_input_elements(
104        &mut self,
105        label: &str,
106        elements: &[F],
107        elements_hash_chain_digest: &[F],
108    ) {
109        self.operations.push(Operation::AppendInput(
110            String::from(label),
111            elements.to_vec(),
112            elements_hash_chain_digest.to_vec(),
113        ));
114    }
115
116    /// Record the operation of squeezing `num_elements` from the sponge.
117    /// `label` is an identifier for this operation that can be used for sanity
118    /// checking by the verifier.
119    pub fn squeeze_elements(&mut self, label: &str, num_elements: usize) {
120        self.operations
121            .push(Operation::Squeeze(String::from(label), num_elements));
122    }
123}
124
125impl<F: Debug> Display for Transcript<F> {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        self.operations.iter().try_for_each(|op| match op {
128            Operation::Append(label, elements) => {
129                writeln!(f, "Append: \"{}\" with {} elements", label, elements.len())
130            }
131            Operation::Squeeze(label, num_elements) => {
132                writeln!(f, "Squeeze: \"{label}\" with {num_elements} elements")
133            }
134            Operation::AppendInput(label, elements, elements_hash_chain_digest) => {
135                writeln!(
136                    f,
137                    "Append input: \"{}\" with {} elements and digest {:?}",
138                    label,
139                    elements.len(),
140                    elements_hash_chain_digest
141                )
142            }
143        })
144    }
145}
146
147pub trait ProverTranscript<F> {
148    fn append(&mut self, label: &str, elem: F);
149
150    fn append_elements(&mut self, label: &str, elements: &[F]);
151
152    fn append_input_elements(&mut self, label: &str, elements: &[F]);
153
154    fn get_challenge(&mut self, label: &str) -> F;
155
156    fn get_challenges(&mut self, label: &str, num_elements: usize) -> Vec<F>;
157}
158
159/// The prover-side interface for interacting with a transcript sponge. A
160/// `TranscriptWriter` acts as a wrapper around a `TranscriptSponge` and
161/// additionally keeps track of all the append/squeeze operations to be able to
162/// generate a serializable `Transcript`.
163pub struct TranscriptWriter<F, T> {
164    /// The sponge that this writer is using to append/squeeze elements.
165    sponge: T,
166
167    /// A mutable transcript which keeps a record of all the append/squeeze
168    /// operations.
169    transcript: Transcript<F>,
170}
171
172impl<F: Field, Tr: TranscriptSponge<F>> ProverTranscript<F> for TranscriptWriter<F, Tr> {
173    /// Append an element to the sponge and record the operation to the
174    /// transcript. `label` is an identifier for this operation and is used for
175    /// sanity checking by the `TranscriptReader`.
176    fn append(&mut self, label: &str, elem: F) {
177        self.sponge.absorb(elem);
178        self.transcript.append_elements(label, &[elem]);
179    }
180
181    /// Append a slice of elements to the sponge sequentially and record the
182    /// operation on the transcript. If `elements` is the empty slice, the
183    /// operation is *not* recorded.
184    /// `label` is an identifier for this operation
185    /// and is used for sanity checking by the `TranscriptReader`.
186    fn append_elements(&mut self, label: &str, elements: &[F]) {
187        if !elements.is_empty() {
188            self.sponge.absorb_elements(elements);
189            self.transcript.append_elements(label, elements);
190        }
191    }
192
193    /// Squeezes the sponge once to get an element and records the squeeze
194    /// operation on the transcript.
195    /// `label` serves as sanity-check identifier by the `TranscriptReader`.
196    fn get_challenge(&mut self, label: &str) -> F {
197        let challenge = self.sponge.squeeze();
198        self.transcript.squeeze_elements(label, 1);
199        challenge
200    }
201
202    /// Squeezes the sponge `num_elements` times to get a sequence of elements
203    /// and records the operation on the transcript. If `num_elements == 0`, the
204    /// empty vector is returned and the operation is *not* recorded on the
205    /// transcript.
206    /// `label` serves as sanity-check identifier by the `TranscriptReader`.
207    fn get_challenges(&mut self, label: &str, num_elements: usize) -> Vec<F> {
208        if num_elements == 0 {
209            vec![]
210        } else {
211            let challenges = self.sponge.squeeze_elements(num_elements);
212            self.transcript.squeeze_elements(label, num_elements);
213            challenges
214        }
215    }
216
217    /// A function which MUST be used on circuit inputs as an additional
218    /// deterrant against the attack specified in <https://eprint.iacr.org/2025/118>,
219    /// by absorbing H(H(H(...H(x)...))) into the `Transcript`, where `x` is
220    /// a circuit input and `H` is the hash function defined in the below field.
221    fn append_input_elements(&mut self, label: &str, elements: &[F]) {
222        if !elements.is_empty() {
223            // First, compute the hash chain digest of `elements`.
224            let elements_hash_chain_digest = sha256_hash_chain_on_field_elems(elements);
225
226            // Append the operation to the internal `Transcript` object.
227            self.transcript
228                .append_input_elements(label, elements, &elements_hash_chain_digest);
229
230            // Absorb a series of elements as an input commitment, i.e. FIRST absorb a
231            // the elements normally, and NEXT the hash chain of the sequence of elements.
232            self.sponge.absorb_elements(elements);
233            self.sponge.absorb_elements(&elements_hash_chain_digest);
234        }
235    }
236}
237
238impl<F: Field, Tr: TranscriptSponge<F>> TranscriptWriter<F, Tr> {
239    /// Destructively extract the transcript produced by this writer.
240    /// This should be the last operation performed on a `TranscriptWriter`.
241    pub fn get_transcript(self) -> Transcript<F> {
242        self.transcript
243    }
244
245    /// Creates an empty sponge.
246    /// `label` is an identifier and will be absorbed into the
247    /// transcript upon intialization.
248    pub fn new(label: &str) -> Self {
249        let mut new_sponge = Self {
250            sponge: Tr::default(),
251            transcript: Transcript::new(label),
252        };
253        new_sponge.sponge.absorb_initialization_label(label);
254        new_sponge
255    }
256}
257
258pub trait VerifierTranscript<F> {
259    /// Circuit input elements are treated differently in order to mitigate
260    /// the Rothblum et. al. attack in <https://eprint.iacr.org/2025/118>.
261    ///
262    /// In particular, a hash chain of the elements is computed and absorbed
263    /// by the transcript sponge alongside each of the given elements.
264    fn consume_input_elements(
265        &mut self,
266        label: &'static str,
267        num_elements: usize,
268    ) -> Result<(Vec<F>, Vec<F>)>;
269
270    fn consume_element(&mut self, label: &'static str) -> Result<F>;
271
272    fn consume_elements(&mut self, label: &'static str, num_elements: usize) -> Result<Vec<F>>;
273
274    fn get_challenge(&mut self, label: &'static str) -> Result<F>;
275
276    fn get_challenges(&mut self, label: &'static str, num_elements: usize) -> Result<Vec<F>>;
277}
278
279/// Errors that a `TranscriptReader` may produce.
280#[derive(Error, Debug, Clone, PartialEq)]
281pub enum TranscriptReaderError {
282    #[error("An unexpected consume input operation was requested")]
283    ConsumeInputError,
284    #[error("An unexpected consume operation was requested")]
285    ConsumeError,
286    #[error("An unexpected squeeze operation was requested")]
287    SqueezeError,
288}
289
290/// A `TranscriptReader` is typically created using a `Transcript` produced by a
291/// `TranscriptWriter`.
292///
293/// Its operation is similar to that of the writer, except that instead of an
294/// "append" operation, it provides a "consume" operation which returns the next
295/// field element that was appended to the circuit by the prover. The user of a
296/// `TranscriptReader` (typically that is the verifier) is responsible for
297/// running any checks (if necessary) to verify the validity of the consumed
298/// elements. The "squeeze" operation behaves similarly to that of a
299/// `TranscriptWriter`.
300///
301/// Consume/Squeeze operations on a `TranscriptReader` may return errors when an
302/// unexpected operation is requested. An operation can be unexpected if it
303/// doesn't match the sequence of operations performed by the `TranscriptWriter`
304/// that produced the `Transcript` used in the initialization of the reader.
305pub struct TranscriptReader<F, T> {
306    /// The sponge that this reader is wrapping around.
307    sponge: T,
308
309    /// The transcript that this reader is using to consume elements from and
310    /// verify the order of operations is valid.
311    transcript: Transcript<F>,
312
313    /// An internal state representing the position of the next operation on the
314    /// transcript.
315    next_element: (usize, usize),
316}
317
318impl<F: Field, T: TranscriptSponge<F>> TranscriptReader<F, T> {
319    /// Reads off a single input element from the transcript and returns it
320    /// if it exists. Note that this is identical to [TranscriptReader::consume_element()],
321    /// but using [Operation::AppendInput] rather than [Operation::Append]. Note
322    /// that this function should *not* be called externally!
323    ///
324    /// The operation can fail with:
325    /// * `TranscriptReaderError::ConsumeInputError`: if there are no more elements
326    ///   to consume or if a squeeze or non-input consume was expected.
327    ///
328    /// `label` is as described in the other [TranscriptReader] functions.
329    fn consume_input_element(&mut self, label: &'static str) -> Result<F> {
330        let (operation_idx, element_idx) = self.next_element;
331
332        match self.transcript.operations.get(operation_idx) {
333            Some(Operation::AppendInput(expected_label, elements, _elements_hash_chain_digest)) => {
334                if label != expected_label {
335                    dbg!("MISMATCH");
336                    warn!("Label mismatch on TranscriptReader `consume_input_elements`. Expected \"{}\" but instead got \"{}\".", expected_label, label);
337                }
338                match elements.get(element_idx) {
339                    None => {
340                        // This should never happen if `self.advance_indices()` maintains its
341                        // invariants. Panicking because we reached an inconsistent state.
342                        panic!("Internal TranscriptReader Error: indices are in an inconsistent state (Operation::AppendInput)!");
343                    }
344                    Some(&element) => {
345                        self.advance_indices()?;
346                        self.sponge.absorb(element);
347                        Ok(element)
348                    }
349                }
350            }
351            _ => Err(anyhow!(TranscriptReaderError::ConsumeInputError)),
352        }
353    }
354
355    /// Reads off a single element from the hash chain digest elements in an
356    /// input commitment and returns it if it exists. Note that this function
357    /// should *not* be called externally!
358    fn consume_input_hash_chain_digest_element(&mut self, label: &'static str) -> Result<F> {
359        let (operation_idx, element_idx) = self.next_element;
360
361        match self.transcript.operations.get(operation_idx) {
362            Some(Operation::AppendInput(expected_label, elements, elements_hash_chain_digest)) => {
363                let hash_chain_digest_idx = element_idx - elements.len();
364                if label != expected_label {
365                    dbg!("MISMATCH");
366                    warn!("Label mismatch on TranscriptReader `consume_input_elements`. Expected \"{}\" but instead got \"{}\".", expected_label, label);
367                }
368                match elements_hash_chain_digest.get(hash_chain_digest_idx) {
369                    None => {
370                        // This should never happen if `self.advance_indices()` maintains its
371                        // invariants. Panicking because we reached an inconsistent state.
372                        panic!("Internal TranscriptReader Error: indices are in an inconsistent state (Operation::AppendInput)!");
373                    }
374                    Some(&hash_chain_digest_elem) => {
375                        self.advance_indices()?;
376                        self.sponge.absorb(hash_chain_digest_elem);
377                        Ok(hash_chain_digest_elem)
378                    }
379                }
380            }
381            _ => Err(anyhow!(TranscriptReaderError::ConsumeInputError)),
382        }
383    }
384}
385
386impl<F: Field, T: TranscriptSponge<F>> VerifierTranscript<F> for TranscriptReader<F, T> {
387    /// Reads off a single element from the transcript and returns it if
388    /// successful.
389    /// The operation can fail with:
390    /// * `TranscriptReaderError::ConsumeError`: if there are no more elements
391    ///   to consume or if a squeeze was expected.
392    ///
393    /// The `label` is used for sanity checking against the label that was used
394    /// by the `TranscriptWriter` for the corresponding operation. If the labels
395    /// don't match, a trace warning message is produced, but the caller is not
396    /// otherwise notified of this discrepancy.
397    fn consume_element(&mut self, label: &'static str) -> Result<F> {
398        let (operation_idx, element_idx) = self.next_element;
399
400        match self.transcript.operations.get(operation_idx) {
401            Some(Operation::Append(expected_label, v)) => {
402                if label != expected_label {
403                    dbg!("MISMATCH");
404                    warn!("Label mismatch on TranscriptReader consume_element. Expected \"{}\" but instead got \"{}\".", expected_label, label);
405                }
406                match v.get(element_idx) {
407                    None => {
408                        // This should never happen if `self.advance_indices()` maintains its
409                        // invariants. Panicking because we reached an inconsistent state.
410                        panic!("Internal TranscriptReader Error: indices are in an inconsistent state!");
411                    }
412                    Some(&element) => {
413                        self.advance_indices()?;
414                        self.sponge.absorb(element);
415                        Ok(element)
416                    }
417                }
418            }
419            _ => Err(anyhow!(TranscriptReaderError::ConsumeError)),
420        }
421    }
422
423    /// A multi-element version of the `consume_element` method. Reads off a
424    /// sequence of `num_elements` elements from the transcript and returns a
425    /// vector of them if successful.
426    /// The operation can fail with:
427    /// * `TranscriptReaderError::ConsumeError`: if less than `num_elements`
428    ///   elements remain in the transcript or if a squeeze operation was
429    ///   expected to occur at any point before the consumption of
430    ///   `num_elements` elements.
431    ///
432    /// The `label` is used for sanity checking against the label that was used
433    /// by the `TranscriptWriter` for the corresponding operations. In
434    /// particular, the `TranscriptWriter` may have appended either a sequence
435    /// of elements using `TranscriptWritter::append_elements` or may have
436    /// called `TranscriptWritter::append` multiple times. Both scenarios are
437    /// valid and in both cases, `label` should match with the corresponding
438    /// labels used on the writer side. If there is a label mismatch for any of
439    /// the `num_elements` elements, a trace warning message is produced, but
440    /// the caller is not otherwise notified of this discrepancy.
441    fn consume_elements(&mut self, label: &'static str, num_elements: usize) -> Result<Vec<F>> {
442        (0..num_elements)
443            .map(|_| self.consume_element(label))
444            .collect()
445    }
446
447    /// Reads off input elements from the transcript and returns them, alongside
448    /// their hash chain digest, if successful.
449    /// The operation can fail with:
450    /// * `TranscriptReaderError::ConsumeInputError`: if there are no more elements
451    ///   to consume or if a squeeze or non-input consume was expected.
452    ///
453    /// `label` is as described in the other [TranscriptReader] functions.
454    fn consume_input_elements(
455        &mut self,
456        label: &'static str,
457        num_elements: usize,
458    ) -> Result<(Vec<F>, Vec<F>)> {
459        // First, retrieve the input elements which were appended to the transcript
460        // by the prover.
461        let maybe_input_elems: Result<Vec<F>> = (0..num_elements)
462            .map(|_| self.consume_input_element(label))
463            .collect();
464        let input_elems = maybe_input_elems?;
465        // Next, independently compute the hash chain of these elements.
466        let input_elems_hash_chain = sha256_hash_chain_on_field_elems(&input_elems);
467        let maybe_claimed_input_elems_hash_chain: Result<Vec<F>> = (0..input_elems_hash_chain
468            .len())
469            .map(|_| self.consume_input_hash_chain_digest_element(label))
470            .collect();
471        // Read off the hash chain elements proposed by the prover and check
472        // that they are the same as what the verifier independently computed.
473        // (Note that this step is not strictly necessary as the sponge takes
474        // care of any discrepancies between the two)
475        let claimed_input_elems_hash_chain = maybe_claimed_input_elems_hash_chain?;
476        assert_eq!(input_elems_hash_chain, claimed_input_elems_hash_chain);
477        Ok((input_elems, input_elems_hash_chain))
478    }
479
480    /// Squeezes the sponge once and returns a single element if successful.
481    /// The operation can fail with:
482    /// * `TranscriptReaderError::SqueezeError`: if a squeeze is requested at a
483    ///   time when either a consume operation was expected or no more
484    ///   operations were expected.
485    ///
486    /// The `label` is used for sanity checking against the label that was used
487    /// by the `TranscriptWriter` for the corresponding operation. If the labels
488    /// don't match, a trace warning message is produced, but the caller is not
489    /// otherwise notified of this discrepancy.
490    fn get_challenge(&mut self, label: &'static str) -> Result<F> {
491        let (operation_idx, element_idx) = self.next_element;
492
493        match self.transcript.operations.get(operation_idx) {
494            Some(Operation::Squeeze(expected_label, num_elements)) => {
495                if label != expected_label {
496                    dbg!("MISMATCH");
497                    warn!("Label mismatch on TranscriptReader get_challenge. Expected \"{}\" but instead got \"{}\".", expected_label, label);
498                }
499                if element_idx >= *num_elements {
500                    Err(anyhow!(TranscriptReaderError::SqueezeError))
501                } else {
502                    self.advance_indices()?;
503                    Ok(self.sponge.squeeze())
504                }
505            }
506            _ => Err(anyhow!(TranscriptReaderError::SqueezeError)),
507        }
508    }
509
510    /// Squeezes the sponge `num_elements` times and returns a vector of the
511    /// resulting elements if successful.
512    /// The operation can fail with:
513    /// * `TranscriptReaderError::SqueezeError`: if any of the squeeze
514    ///   operations requested does not correspond to a squeeze operation
515    ///   performed by the `TranscriptWriter` that produced the transcript.
516    ///
517    /// The `label` is used for sanity checking against the label that was used
518    /// by the `TranscriptWriter` for the corresponding operations. In
519    /// particular, the `TranscriptWriter` may have squeezed either a sequence
520    /// of elements using `TranscriptWriter::get_challenges` or may have called
521    /// `TranscriptWriter::get_challenge` multiple times. Both scenarios are
522    /// valid and in both cases, `label` should match with the corresponding
523    /// labels used on the writer side. If there is a label mismatch for any of
524    /// the `num_elements` elements, a trace warning message is produced, but
525    /// the caller is not otherwise notified of this discrepancy.
526    fn get_challenges(&mut self, label: &'static str, num_elements: usize) -> Result<Vec<F>> {
527        (0..num_elements)
528            .map(|_| self.get_challenge(label))
529            .collect()
530    }
531}
532
533impl<F: std::fmt::Debug, Tr: TranscriptSponge<F>> TranscriptReader<F, Tr> {
534    /// Generate a new `TranscriptReader` to operate on a given `transcript`.
535    pub fn new(transcript: Transcript<F>) -> Self {
536        let label = &transcript.label.clone();
537        let mut new_sponge = Self {
538            sponge: Tr::default(),
539            transcript,
540            next_element: (0, 0),
541        };
542        new_sponge.sponge.absorb_initialization_label(label);
543        new_sponge
544    }
545
546    /// Internal method used to advance the internal state to the next
547    /// operation.
548    fn advance_indices(&mut self) -> Result<()> {
549        let (operation_idx, element_idx) = self.next_element;
550
551        match self.transcript.operations.get(operation_idx) {
552            None => {
553                // `advance_indices` should never be called in an already inconsistent state.
554                panic!("Internal TranscriptReader Error: attempt to advance indices in an already inconsistent state!");
555            }
556            Some(Operation::Append(_, v)) => {
557                if element_idx + 1 >= v.len() {
558                    self.next_element = (operation_idx + 1, 0);
559                } else {
560                    self.next_element = (operation_idx, element_idx + 1);
561                }
562                Ok(())
563            }
564            Some(Operation::Squeeze(_, num_elements)) => {
565                if element_idx + 1 >= *num_elements {
566                    self.next_element = (operation_idx + 1, 0);
567                } else {
568                    self.next_element = (operation_idx, element_idx + 1);
569                }
570                Ok(())
571            }
572            Some(Operation::AppendInput(_, elements, elements_hash_chain_digest)) => {
573                // The `element_idx` for an `Operation::AppendInput` is simply
574                // the index into the concatenation of the elements themselves
575                // and their hash chain digest.
576                if element_idx + 1 >= elements.len() + elements_hash_chain_digest.len() {
577                    self.next_element = (operation_idx + 1, 0);
578                } else {
579                    self.next_element = (operation_idx, element_idx + 1);
580                }
581                Ok(())
582            }
583        }
584    }
585}
586
587#[cfg(test)]
588mod tests {
589    use self::poseidon_sponge::PoseidonSponge;
590
591    use super::*;
592    use crate::Fr;
593
594    fn generate_test_transcript() -> Transcript<Fr> {
595        let mut transcript_writer = TranscriptWriter::<Fr, PoseidonSponge<Fr>>::new("New tw");
596
597        transcript_writer.append("A1", Fr::from(1));
598        transcript_writer.append("A2", Fr::from(2));
599        let _ = transcript_writer.get_challenge("C1");
600        transcript_writer.append_elements("A3", &[Fr::from(3), Fr::from(4)]);
601        // The following operation should be ignored.
602        transcript_writer.append_elements("A3*", &[]);
603        transcript_writer.append("A4", Fr::from(5));
604        let _ = transcript_writer.get_challenge("C2");
605        let _ = transcript_writer.get_challenges("C3", 5);
606        // The following operation should be ignored.
607        let _ = transcript_writer.get_challenges("C3*", 0);
608        transcript_writer.append_elements("A5", &[Fr::from(6), Fr::from(7), Fr::from(8)]);
609        let _ = transcript_writer.get_challenge("C4");
610
611        transcript_writer.get_transcript()
612    }
613
614    #[test]
615    fn test_transcript_writer_internal_state() {
616        let transcript = generate_test_transcript();
617
618        let appended_elements = vec![
619            Operation::Append(String::from("A1"), vec![Fr::from(1)]),
620            Operation::Append(String::from("A2"), vec![Fr::from(2)]),
621            Operation::Squeeze(String::from("C1"), 1),
622            Operation::Append(String::from("A3"), vec![Fr::from(3), Fr::from(4)]),
623            Operation::Append(String::from("A4"), vec![Fr::from(5)]),
624            Operation::Squeeze(String::from("C2"), 1),
625            Operation::Squeeze(String::from("C3"), 5),
626            Operation::Append(
627                String::from("A5"),
628                vec![Fr::from(6), Fr::from(7), Fr::from(8)],
629            ),
630            Operation::Squeeze(String::from("C4"), 1),
631        ];
632
633        let expected_transcript = Transcript::<Fr> {
634            label: String::from("New tw"),
635            operations: appended_elements,
636        };
637
638        assert_eq!(transcript, expected_transcript);
639    }
640
641    #[test]
642    fn test_transcript_reader() {
643        let transcript = generate_test_transcript();
644        let mut transcript_reader = TranscriptReader::<Fr, PoseidonSponge<Fr>>::new(transcript);
645
646        assert_eq!(
647            transcript_reader.consume_element("A1").unwrap(),
648            Fr::from(1)
649        );
650        assert_eq!(
651            transcript_reader.consume_element("A2").unwrap(),
652            Fr::from(2)
653        );
654        assert!(transcript_reader.get_challenge("C1").is_ok());
655        assert_eq!(
656            transcript_reader.consume_element("A3").unwrap(),
657            Fr::from(3)
658        );
659        assert_eq!(
660            transcript_reader.consume_element("A3").unwrap(),
661            Fr::from(4)
662        );
663        assert_eq!(
664            transcript_reader.consume_element("A4").unwrap(),
665            Fr::from(5)
666        );
667        assert!(transcript_reader.get_challenge("C2").is_ok());
668        assert!(transcript_reader.get_challenges("C3", 5).is_ok());
669        assert_eq!(
670            transcript_reader.consume_element("A5").unwrap(),
671            Fr::from(6)
672        );
673        assert_eq!(
674            transcript_reader.consume_element("A5").unwrap(),
675            Fr::from(7)
676        );
677        assert_eq!(
678            transcript_reader.consume_element("A5").unwrap(),
679            Fr::from(8)
680        );
681        assert!(transcript_reader.get_challenge("C4").is_ok());
682
683        assert!(transcript_reader.get_challenge("C5").is_err());
684        assert!(transcript_reader.consume_element("A6").is_err());
685    }
686}