hyperplonk/transcript/src/lib.rs
transcript/src/lib.rsRaw
1// Copyright (c) 2023 Espresso Systems (espressosys.com)
2// This file is part of the HyperPlonk library.
3
4// You should have received a copy of the MIT License
5// along with the HyperPlonk library. If not, see <https://mit-license.org/>.
6
7//! Module for PolyIOP transcript.
8//! TODO(ZZ): move this module to HyperPlonk where the transcript will also be
9//! useful.
10//! TODO(ZZ): decide which APIs need to be public.
11
12mod errors;
13pub use errors::TranscriptError;
14
15use ark_ff::PrimeField;
16use ark_serialize::CanonicalSerialize;
17use merlin::Transcript;
18use std::marker::PhantomData;
19
20/// An IOP transcript consists of a Merlin transcript and a flag `is_empty` to
21/// indicate that if the transcript is empty.
22///
23/// It is associated with a prime field `F` for which challenges are generated
24/// over.
25///
26/// The `is_empty` flag is useful in the case where a protocol is initiated by
27/// the verifier, in which case the prover should start its phase by receiving a
28/// `non-empty` transcript.
29#[derive(Clone)]
30pub struct IOPTranscript<F: PrimeField> {
31 transcript: Transcript,
32 is_empty: bool,
33 #[doc(hidden)]
34 phantom: PhantomData<F>,
35}
36
37// TODO: Make this into a Trait
38impl<F: PrimeField> IOPTranscript<F> {
39 /// Create a new IOP transcript.
40 pub fn new(label: &'static [u8]) -> Self {
41 Self {
42 transcript: Transcript::new(label),
43 is_empty: true,
44 phantom: PhantomData::default(),
45 }
46 }
47
48 // Append the message to the transcript.
49 pub fn append_message(
50 &mut self,
51 label: &'static [u8],
52 msg: &[u8],
53 ) -> Result<(), TranscriptError> {
54 self.transcript.append_message(label, msg);
55 self.is_empty = false;
56 Ok(())
57 }
58
59 // Append the message to the transcript.
60 pub fn append_field_element(
61 &mut self,
62 label: &'static [u8],
63 field_elem: &F,
64 ) -> Result<(), TranscriptError> {
65 self.append_message(label, &to_bytes!(field_elem)?)
66 }
67
68 // Append the message to the transcript.
69 pub fn append_serializable_element<S: CanonicalSerialize>(
70 &mut self,
71 label: &'static [u8],
72 group_elem: &S,
73 ) -> Result<(), TranscriptError> {
74 self.append_message(label, &to_bytes!(group_elem)?)
75 }
76
77 // Generate the challenge from the current transcript
78 // and append it to the transcript.
79 //
80 // The output field element is statistical uniform as long
81 // as the field has a size less than 2^384.
82 pub fn get_and_append_challenge(&mut self, label: &'static [u8]) -> Result<F, TranscriptError> {
83 // we need to reject when transcript is empty
84 if self.is_empty {
85 return Err(TranscriptError::InvalidTranscript(
86 "transcript is empty".to_string(),
87 ));
88 }
89
90 let mut buf = [0u8; 64];
91 self.transcript.challenge_bytes(label, &mut buf);
92 let challenge = F::from_le_bytes_mod_order(&buf);
93 self.append_serializable_element(label, &challenge)?;
94 Ok(challenge)
95 }
96
97 // Generate a list of challenges from the current transcript
98 // and append them to the transcript.
99 //
100 // The output field element are statistical uniform as long
101 // as the field has a size less than 2^384.
102 pub fn get_and_append_challenge_vectors(
103 &mut self,
104 label: &'static [u8],
105 len: usize,
106 ) -> Result<Vec<F>, TranscriptError> {
107 // we need to reject when transcript is empty
108 if self.is_empty {
109 return Err(TranscriptError::InvalidTranscript(
110 "transcript is empty".to_string(),
111 ));
112 }
113
114 let mut res = vec![];
115 for _ in 0..len {
116 res.push(self.get_and_append_challenge(label)?)
117 }
118 Ok(res)
119 }
120}
121
122/// Takes as input a struct, and converts them to a series of bytes. All traits
123/// that implement `CanonicalSerialize` can be automatically converted to bytes
124/// in this manner.
125#[macro_export]
126macro_rules! to_bytes {
127 ($x:expr) => {{
128 let mut buf = ark_std::vec![];
129 ark_serialize::CanonicalSerialize::serialize_compressed($x, &mut buf).map(|_| buf)
130 }};
131}