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}