Skip to main content

whir/transcript/
mod.rs

1//! Wrapper around Spongefish to add out-of-band hint messages.
2//!
3//! We need these for the Merkle tree proofs as doing them in-transcript
4//! would roughly double the verifier cost.
5
6pub mod codecs;
7mod mock_sponge;
8
9#[cfg(debug_assertions)]
10use std::any::type_name;
11use std::fmt::Debug;
12
13use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
14use ark_std::rand::{rngs::StdRng, CryptoRng, RngCore};
15use serde::{Deserialize, Serialize};
16use sha3::{Digest, Sha3_256, Sha3_512};
17use spongefish::StdHash;
18pub use spongefish::{
19    Codec, Decoding, DuplexSpongeInterface, Encoding, NargDeserialize, NargSerialize,
20    VerificationError, VerificationResult,
21};
22
23#[cfg(test)]
24pub use self::mock_sponge::MockSponge;
25
26#[macro_export]
27macro_rules! verify {
28    ($cond:expr) => {
29        #[allow(clippy::neg_cmp_op_on_partial_ord)]
30        if !$cond {
31            #[cfg(feature = "verifier_panics")]
32            panic!("Verification failed: {}", stringify!($cond));
33
34            #[cfg(not(feature = "verifier_panics"))]
35            return Err(spongefish::VerificationError);
36        };
37    };
38}
39
40/// Marker trait for types that can be used as prover messages.
41///
42/// Like [`spongefish::Codec`], but without the [`Encoding<T>`] requirement.
43pub trait ProverMessage<U = [u8]>: NargDeserialize + NargSerialize + Encoding<U>
44where
45    U: ?Sized,
46{
47}
48
49#[derive(Clone, Copy, Debug)]
50pub struct DomainSeparator<'a, I> {
51    protocol_id: [u8; 64],
52    session_id: [u8; 32],
53    instance: &'a I,
54}
55
56#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
57pub enum Interaction {
58    ProverMessage(String),
59    VerifierMessage(String),
60    Hint(String),
61}
62
63#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
64pub struct Proof {
65    pub narg_string: Vec<u8>,
66    pub hints: Vec<u8>,
67
68    #[cfg(debug_assertions)]
69    pub pattern: Vec<Interaction>,
70}
71
72pub struct ProverState<H = StdHash, R = StdRng>
73where
74    H: DuplexSpongeInterface,
75    R: RngCore + CryptoRng,
76{
77    inner: spongefish::ProverState<H, R>,
78    hints: Vec<u8>,
79
80    #[cfg(debug_assertions)]
81    pattern: Vec<Interaction>,
82}
83
84pub struct VerifierState<'a, H = StdHash>
85where
86    H: DuplexSpongeInterface,
87{
88    inner: spongefish::VerifierState<'a, H>,
89    hints: &'a [u8],
90
91    #[cfg(debug_assertions)]
92    pattern: &'a [Interaction],
93}
94
95pub trait VerifierMessage {
96    type U;
97
98    fn verifier_message<T>(&mut self) -> T
99    where
100        T: Decoding<[Self::U]>;
101
102    fn verifier_message_vec<T>(&mut self, count: usize) -> Vec<T>
103    where
104        T: Decoding<[Self::U]>,
105    {
106        (0..count).map(|_| self.verifier_message()).collect()
107    }
108}
109
110impl DomainSeparator<'static, ()> {
111    pub fn protocol<C: Serialize>(config: &C) -> Self {
112        const INSTANCE: &() = &();
113        let mut hash = Sha3_512::new();
114        ciborium::into_writer(config, &mut hash).expect("Computing protocol hash failed");
115        let protocol_id: [u8; 64] = hash.finalize().into();
116        Self {
117            protocol_id,
118            session_id: [0; 32],
119            instance: INSTANCE,
120        }
121    }
122
123    #[must_use]
124    pub fn session<S: Serialize>(self, session: &S) -> Self {
125        let mut hash = Sha3_256::new();
126        ciborium::into_writer(session, &mut hash).expect("Computing session hash failed");
127        let session_id: [u8; 32] = hash.finalize().into();
128        Self { session_id, ..self }
129    }
130
131    pub const fn instance<I>(self, instance: &I) -> DomainSeparator<'_, I> {
132        DomainSeparator {
133            protocol_id: self.protocol_id,
134            session_id: self.session_id,
135            instance,
136        }
137    }
138}
139
140impl<T, U> ProverMessage<U> for T
141where
142    T: NargSerialize + NargDeserialize + Encoding<U>,
143    U: ?Sized,
144{
145}
146
147impl<H> ProverState<H, StdRng>
148where
149    H: DuplexSpongeInterface,
150{
151    /// Construct a new prover state with a custom duplex hash function.
152    ///
153    /// **Note.** The `spongefish` API currently does not allow creating an
154    /// instance with a non-standard random number generator.
155    pub fn new<I>(ds: &DomainSeparator<'_, I>, duplex: H) -> Self
156    where
157        u8: Encoding<[H::U]>,
158        I: Encoding<[H::U]>,
159    {
160        Self {
161            inner: spongefish::DomainSeparator::new(ds.protocol_id)
162                .session(ds.session_id)
163                .instance(ds.instance)
164                .to_prover(duplex),
165            hints: Vec::new(),
166
167            #[cfg(debug_assertions)]
168            pattern: Vec::new(),
169        }
170    }
171}
172
173impl ProverState<StdHash, StdRng> {
174    /// Construct a new prover state with the standard duplex hash function.
175    pub fn new_std<I>(ds: &DomainSeparator<'_, I>) -> Self
176    where
177        I: Encoding<[u8]>,
178    {
179        Self::new(ds, StdHash::default())
180    }
181}
182
183impl<H, R> ProverState<H, R>
184where
185    H: DuplexSpongeInterface,
186    R: RngCore + CryptoRng,
187{
188    /// Access the prover's private transcript-bound RNG.
189    pub fn rng(&mut self) -> &mut (impl RngCore + CryptoRng) {
190        self.inner.rng()
191    }
192
193    #[cfg_attr(test, track_caller)]
194    pub fn prover_message<T>(&mut self, message: &T)
195    where
196        T: Encoding<[H::U]> + NargSerialize + ?Sized,
197    {
198        #[cfg(debug_assertions)]
199        self.push(Interaction::ProverMessage(type_name::<T>().to_owned()));
200        self.inner.prover_message(message);
201    }
202
203    #[cfg_attr(test, track_caller)]
204    pub fn prover_hint<T>(&mut self, hint: &T)
205    where
206        T: NargSerialize,
207    {
208        #[cfg(debug_assertions)]
209        self.push(Interaction::Hint(type_name::<T>().to_owned()));
210        hint.serialize_into_narg(&mut self.hints);
211    }
212
213    #[cfg_attr(test, track_caller)]
214    pub fn prover_hint_ark<T>(&mut self, value: &T)
215    where
216        T: CanonicalSerialize + ?Sized,
217    {
218        #[cfg(debug_assertions)]
219        self.push(Interaction::Hint(type_name::<T>().to_owned()));
220        value
221            .serialize_compressed(&mut self.hints)
222            .expect("Failed to serialize hint");
223    }
224
225    pub fn proof(self) -> Proof {
226        Proof {
227            narg_string: self.inner.narg_string().to_owned(),
228            hints: self.hints,
229
230            #[cfg(debug_assertions)]
231            pattern: self.pattern,
232        }
233    }
234
235    #[cfg(debug_assertions)]
236    fn push(&mut self, interaction: Interaction) {
237        self.pattern.push(interaction);
238    }
239}
240
241impl<H, R> VerifierMessage for ProverState<H, R>
242where
243    H: DuplexSpongeInterface,
244    R: RngCore + CryptoRng,
245{
246    type U = H::U;
247
248    #[cfg_attr(test, track_caller)]
249    fn verifier_message<T>(&mut self) -> T
250    where
251        T: Decoding<[H::U]>,
252    {
253        #[cfg(debug_assertions)]
254        self.push(Interaction::VerifierMessage(type_name::<T>().to_owned()));
255        self.inner.verifier_message()
256    }
257}
258
259impl<'a, H> VerifierState<'a, H>
260where
261    H: DuplexSpongeInterface,
262{
263    pub fn new<'b, I>(ds: &DomainSeparator<'b, I>, proof: &'a Proof, duplex: H) -> Self
264    where
265        u8: Encoding<[H::U]>,
266        I: Encoding<[H::U]>,
267    {
268        Self {
269            inner: spongefish::DomainSeparator::new(ds.protocol_id)
270                .session(ds.session_id)
271                .instance(ds.instance)
272                .to_verifier(duplex, &proof.narg_string),
273            hints: &proof.hints,
274            #[cfg(debug_assertions)]
275            pattern: &proof.pattern,
276        }
277    }
278
279    pub const fn as_spongefish(&mut self) -> &mut spongefish::VerifierState<'a, H> {
280        &mut self.inner
281    }
282
283    #[cfg_attr(debug_assertions, track_caller)]
284    pub fn check_eof(self) -> VerificationResult<()> {
285        #[cfg(debug_assertions)]
286        assert!(self.pattern.is_empty());
287        verify!(self.inner.check_eof().is_ok());
288        verify!(self.hints.is_empty());
289        Ok(())
290    }
291
292    #[cfg_attr(test, track_caller)]
293    pub fn prover_message<T>(&mut self) -> VerificationResult<T>
294    where
295        T: Encoding<[H::U]> + NargDeserialize,
296    {
297        #[cfg(debug_assertions)]
298        self.pop_pattern(&Interaction::ProverMessage(type_name::<T>().to_owned()));
299        self.inner.prover_message()
300    }
301
302    #[cfg_attr(test, track_caller)]
303    pub fn prover_messages_vec<T>(&mut self, len: usize) -> VerificationResult<Vec<T>>
304    where
305        T: Encoding<[H::U]> + NargDeserialize,
306    {
307        (0..len).map(|_| self.prover_message()).collect()
308    }
309
310    #[cfg_attr(test, track_caller)]
311    pub fn prover_hint<T>(&mut self) -> VerificationResult<T>
312    where
313        T: NargDeserialize,
314    {
315        #[cfg(debug_assertions)]
316        self.pop_pattern(&Interaction::Hint(type_name::<T>().to_owned()));
317        T::deserialize_from_narg(&mut self.hints)
318    }
319
320    #[cfg_attr(test, track_caller)]
321    pub fn prover_hint_ark<T>(&mut self) -> VerificationResult<T>
322    where
323        T: CanonicalDeserialize,
324    {
325        #[cfg(debug_assertions)]
326        self.pop_pattern(&Interaction::Hint(type_name::<T>().to_owned()));
327        T::deserialize_compressed(&mut self.hints).map_err(|_| VerificationError)
328    }
329
330    #[cfg(debug_assertions)]
331    #[track_caller]
332    fn pop_pattern(&mut self, interaction: &Interaction) {
333        assert!(!self.pattern.is_empty());
334        let (expected, tail) = self.pattern.split_first().unwrap();
335        assert_eq!(
336            interaction, expected,
337            "Transcript error: Expected interaction {expected:?} got {interaction:?}"
338        );
339        self.pattern = tail;
340    }
341}
342
343impl<'a> VerifierState<'a, StdHash> {
344    /// Construct a new verifier state with the standard duplex hash function.
345    pub fn new_std<'b, I>(ds: &DomainSeparator<'b, I>, proof: &'a Proof) -> Self
346    where
347        I: Encoding<[u8]>,
348    {
349        Self::new(ds, proof, StdHash::default())
350    }
351}
352
353impl<H> VerifierMessage for VerifierState<'_, H>
354where
355    H: DuplexSpongeInterface,
356{
357    type U = H::U;
358
359    #[cfg_attr(test, track_caller)]
360    fn verifier_message<T>(&mut self) -> T
361    where
362        T: Decoding<[H::U]>,
363    {
364        #[cfg(debug_assertions)]
365        self.pop_pattern(&Interaction::VerifierMessage(type_name::<T>().to_owned()));
366        self.inner.verifier_message()
367    }
368}