Skip to main content

sigma_fun/
transcript.rs

1use core::marker::PhantomData;
2
3use crate::{
4    Sigma, Writable,
5    rand_core::{CryptoRng, RngCore, SeedableRng},
6    typenum::{
7        PartialDiv, U32, U64, Unsigned, marker_traits::NonZero, type_operators::IsLessOrEqual,
8    },
9};
10use digest::{FixedOutput, Update, crypto_common::BlockSizeUser};
11use generic_array::GenericArray;
12
13/// A trait for a Fiat-Shamir proof transcript.
14///
15/// Really this is just a trait around a cryptographic hash that can produce a Fiat-Shamir challenge
16/// from the statement and the announcement. The usual workflow is to call `add_name` and then clone
17/// the transcript for each new statement.
18pub trait Transcript<S: Sigma>: Clone {
19    /// The name unambigiously determines the semantics of the statement and announcement which
20    /// are subsequently added to the transcript.
21    fn add_name<N: Writable + ?Sized>(&mut self, name: &N);
22
23    /// Adds the prover's statement to the transcript. This must be called before [`get_challenge`].
24    ///
25    /// [`get_challenge`]: Self::get_challenge
26    fn add_statement(&mut self, sigma: &S, statement: &S::Statement);
27
28    /// Gets the verifier's synthetic challenge for the non-interactive proof.
29    fn get_challenge(
30        self,
31        sigma: &S,
32        announcement: &S::Announcement,
33    ) -> GenericArray<u8, S::ChallengeLength>;
34}
35
36/// A `Transcript` that can also generate a rng.
37///
38/// The prover needs an rng to generate it's [`AnnounceSecret`].
39///
40/// [`AnnounceSecret`]: crate::Sigma::AnnounceSecret
41pub trait ProverTranscript<S: Sigma> {
42    /// The type of Rng the transcript generates.
43    type Rng: CryptoRng + RngCore;
44    /// Generates an RNG from the transcript state and an input rng (`in_rng`) which should provide
45    /// system randomness.
46    fn gen_rng<R: CryptoRng + RngCore>(
47        &self,
48        sigma: &S,
49        witness: &S::Witness,
50        in_rng: Option<&mut R>,
51    ) -> Self::Rng;
52}
53
54#[derive(Clone, Debug)]
55/// A transcript which consists of a hash with fixed length output and a seedable RNG.
56///
57/// The [`SeedableRng`] specified must have the same seed length as the hash's output length.
58/// `R` may be set to `()` but the it won't implement [`ProverTranscript`].
59///
60/// [`SeedableRng`]: rand_core::SeedableRng
61pub struct HashTranscript<H, R = ()> {
62    hash: H,
63    rng: PhantomData<R>,
64}
65
66impl<H: Default, R> Default for HashTranscript<H, R> {
67    fn default() -> Self {
68        HashTranscript {
69            hash: H::default(),
70            rng: PhantomData,
71        }
72    }
73}
74
75#[derive(Clone)]
76struct WriteHash<H>(H);
77
78impl<H: Update> core::fmt::Write for WriteHash<H> {
79    fn write_str(&mut self, s: &str) -> core::fmt::Result {
80        self.0.update(s.as_bytes());
81        Ok(())
82    }
83}
84
85/// Implements a transcript for any hash that outputs 32 bytes but with a block size of 64 bytes (e.g. SHA256).
86///
87/// The implementation first [BIP-340] tags the SHA256 instance with the Sigma protocol's name.
88///
89/// [BIP-340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
90impl<H, S: Sigma, R: Clone> Transcript<S> for HashTranscript<H, R>
91where
92    S::ChallengeLength: IsLessOrEqual<U32>,
93    <S::ChallengeLength as IsLessOrEqual<U32>>::Output: NonZero,
94    H: BlockSizeUser<BlockSize = U64> + FixedOutput<OutputSize = U32> + Update + Default + Clone,
95{
96    fn add_name<N: Writable + ?Sized>(&mut self, name: &N) {
97        let hashed_tag = {
98            let mut hash = WriteHash(H::default());
99            name.write_to(&mut hash)
100                .expect("writing to hash won't fail");
101            hash.0.finalize_fixed()
102        };
103        // I started doing this with plans to make this more generic than it is.
104        // This loop will always run twice
105        let fill_block =
106            <<H::BlockSize as PartialDiv<H::OutputSize>>::Output as Unsigned>::to_usize();
107        for _ in 0..fill_block {
108            self.hash.update(&hashed_tag[..]);
109        }
110    }
111
112    fn add_statement(&mut self, sigma: &S, statement: &S::Statement) {
113        sigma.hash_statement(&mut self.hash, statement);
114    }
115
116    fn get_challenge(
117        mut self,
118        sigma: &S,
119        announce: &S::Announcement,
120    ) -> GenericArray<u8, S::ChallengeLength> {
121        sigma.hash_announcement(&mut self.hash, announce);
122        let challenge_bytes = self.hash.finalize_fixed();
123        // truncate the hash output
124        GenericArray::clone_from_slice(&challenge_bytes[..S::ChallengeLength::to_usize()])
125    }
126}
127
128/// Implements a prover transcript for a 32-byte hash with a rng that takes a 32-byte seed.
129impl<S, H, R> ProverTranscript<S> for HashTranscript<H, R>
130where
131    S: Sigma,
132    H: Update + FixedOutput<OutputSize = U32> + Clone,
133    R: SeedableRng + CryptoRng + RngCore + Clone,
134    R::Seed: From<GenericArray<u8, U32>>,
135{
136    type Rng = R;
137
138    fn gen_rng<SysRng: CryptoRng + RngCore>(
139        &self,
140        sigma: &S,
141        witness: &S::Witness,
142        in_rng: Option<&mut SysRng>,
143    ) -> Self::Rng {
144        let mut rng_hash = self.hash.clone();
145        sigma.hash_witness(&mut rng_hash, witness);
146        if let Some(rng) = in_rng {
147            let mut randomness = [0u8; 32];
148            rng.fill_bytes(&mut randomness);
149            rng_hash.update(&randomness);
150        }
151        let secret_seed = rng_hash.finalize_fixed();
152        R::from_seed(secret_seed.into())
153    }
154}