Skip to main content

primitives/transcripts/
mod.rs

1use crate::{
2    random::{CryptoRngCore, Seed},
3    types::{identifiers::ProtocolInfo, SessionId},
4};
5
6pub mod folklore;
7pub mod merlin;
8pub mod protocol_transcript;
9
10pub use folklore::FolkloreTranscript;
11pub use merlin::MerlinTranscript;
12pub use protocol_transcript::ProtocolTranscript;
13
14/// A trait for succinct transcripts (via some form of hashing) used in cryptographic protocols.
15/// The transcript should be able to:
16/// - Absorb messages with associated labels.
17/// - Produce pseudorandom outputs (e.g., via a CSPRNG) based on the absorbed messages.
18/// - Ensure that the order and content of messages affect the outputs, providing domain separation.
19pub trait Transcript: Clone {
20    /// The type of the RNG derived from the transcript.
21    type Rng: CryptoRngCore;
22
23    /// Create a new transcript for a specific protocol with a session ID for domain separation.
24    fn new(protocol_info: &'static ProtocolInfo, session_id: &SessionId) -> Self;
25
26    /// Append a message with a label to the transcript.
27    fn append_with<S: AsRef<[u8]>>(&mut self, label: &'static [u8], message: &S);
28
29    /// Append multiple messages with a common label to the transcript.
30    fn append_many_with<S: AsRef<[u8]>>(&mut self, label: &'static [u8], messages: &[S]);
31
32    /// Extract pseudorandom bytes based on the transcript state.
33    /// Note: two consecutive extractions must yield different outputs.
34    fn extract(&mut self, label: &'static [u8]) -> Seed;
35
36    /// Derive a CSPRNG from the transcript state with a specific label. Allows
37    /// arbitrary-length output generation
38    fn extract_rng(&mut self, label: &'static [u8]) -> Self::Rng;
39}
40
41/// A trait for transcripts that do not use labels when appending messages.
42/// WARNING! This should only be implemented by transcripts that are designed
43/// to automatically manage the labels internally at runtime.
44pub trait AutoTranscript: Transcript + Sized {
45    /// The type of the label used for messages in this transcript.
46    type Label: AsRef<[u8]>;
47
48    #[inline]
49    /// Create a new transcript for a specific protocol with a session ID for domain separation.
50    fn new(protocol_info: &'static ProtocolInfo, session_id: &SessionId) -> Self {
51        Transcript::new(protocol_info, session_id)
52    }
53
54    /// Get the label that was used for the last appended message.
55    /// If no messages have been appended yet, returns the initial label.
56    fn get_current_label(&self) -> Self::Label;
57
58    /// Get the label that will be used for the next appended message.
59    fn next_label(&mut self) -> Self::Label;
60
61    /// Append a message to the transcript with an automatically generated label.
62    fn append<S: AsRef<[u8]>>(&mut self, message: &S);
63
64    /// Append multiple messages to the transcript with a common automatically generated label.
65    fn append_many<S: AsRef<[u8]>>(&mut self, values: &[S]);
66
67    #[inline]
68    /// Extract pseudorandom bytes based on the transcript state.
69    /// Note: two consecutive extractions must yield different outputs.
70    fn extract(&mut self, label: &'static [u8]) -> Seed {
71        Transcript::extract(self, label)
72    }
73
74    #[inline]
75    /// Derive a CSPRNG from the transcript state with a specific label. Allows
76    /// arbitrary-length output generation
77    fn extract_rng(&mut self, label: &'static [u8]) -> Self::Rng {
78        Transcript::extract_rng(self, label)
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use rand::RngCore;
85
86    use crate::{
87        random::{test_rng, Random},
88        transcripts::Transcript,
89        types::{identifiers::ProtocolInfo, SessionId},
90    };
91
92    // Appending a message and extracting it in two identical transcripts should
93    // yield the same result.
94    pub fn test_happypath_same_transcripts_yield_same_extracts<T: Transcript>() {
95        let message = b"hello world";
96        let session_id = SessionId::random(&mut test_rng());
97
98        static TEST_PROTOCOL_INFO: ProtocolInfo = ProtocolInfo::new("test_protocol", 42);
99        let mut t1 = T::new(&TEST_PROTOCOL_INFO, &session_id);
100        t1.append_with(b"message_1", &message);
101        let mut t2 = T::new(&TEST_PROTOCOL_INFO, &session_id);
102        t2.append_with(b"message_1", &message);
103
104        let dest1 = t1.extract(b"value_1");
105        let dest2 = t2.extract(b"value_1");
106        assert_eq!(dest1, dest2);
107
108        let mut bytes1 = [0u8; 100];
109        let mut bytes2 = [0u8; 100];
110        t1.extract_rng(b"stream").fill_bytes(&mut bytes1);
111        t2.extract_rng(b"stream").fill_bytes(&mut bytes2);
112        assert_eq!(bytes1, bytes2);
113    }
114
115    // Appending a message and extracting it in two different transcripts should
116    // yield different results.
117    pub fn test_unhappypath_different_transcripts_yield_different_extracts<T: Transcript>() {
118        // Two transcripts that differ only in their init labels.
119        let message = b"hello world";
120        let session_id = SessionId::random(&mut test_rng());
121
122        static PROTOCOL_1: ProtocolInfo = ProtocolInfo::new("test_protocol_1", 42);
123        let mut t1 = T::new(&PROTOCOL_1, &session_id);
124        t1.append_with(b"message_1", &message);
125
126        static PROTOCOL_2: ProtocolInfo = ProtocolInfo::new("test_protocol_2", 43);
127        let mut t2 = T::new(&PROTOCOL_2, &session_id);
128        t2.append_with(b"message_1", &message);
129
130        let dest1 = t1.extract(b"value_1");
131        let dest2 = t2.extract(b"value_1");
132        assert_ne!(dest1, dest2);
133
134        let mut bytes1 = [0u8; 100];
135        let mut bytes2 = [0u8; 100];
136        t1.extract_rng(b"stream").fill_bytes(&mut bytes1);
137        t2.extract_rng(b"stream").fill_bytes(&mut bytes2);
138        assert_ne!(bytes1, bytes2);
139
140        // Two transcripts that differ in their message ordering.
141        let message1 = b"hello world";
142        let message2 = b"goodbye world";
143
144        let mut t1 = T::new(&PROTOCOL_1, &session_id);
145        t1.append_with(b"message_1", &message1);
146        t1.append_with(b"message_2", &message2);
147        let mut t2 = T::new(&PROTOCOL_1, &session_id);
148        t2.append_with(b"message_2", &message2);
149        t2.append_with(b"message_1", &message1);
150
151        let dest1 = t1.extract(b"value_1");
152        let dest2 = t2.extract(b"value_1");
153        assert_ne!(dest1, dest2);
154
155        let mut bytes1 = [0u8; 100];
156        let mut bytes2 = [0u8; 100];
157        t1.extract_rng(b"stream").fill_bytes(&mut bytes1);
158        t2.extract_rng(b"stream").fill_bytes(&mut bytes2);
159        assert_ne!(bytes1, bytes2);
160    }
161
162    #[test]
163    fn test_folklore_transcript() {
164        test_happypath_same_transcripts_yield_same_extracts::<super::folklore::FolkloreTranscript>(
165        );
166        test_unhappypath_different_transcripts_yield_different_extracts::<
167            super::folklore::FolkloreTranscript,
168        >();
169    }
170
171    #[test]
172    fn test_merlin_transcript() {
173        test_happypath_same_transcripts_yield_same_extracts::<super::merlin::MerlinTranscript>();
174        test_unhappypath_different_transcripts_yield_different_extracts::<
175            super::merlin::MerlinTranscript,
176        >();
177    }
178}