Skip to main content

zinc_transcript/
lib.rs

1pub mod traits;
2
3use crate::traits::{ConstTranscribable, GenTranscribable, Transcript};
4use crypto_primitives::{BaseFieldConfig, ConstIntSemiring};
5use std::io::ErrorKind;
6use thiserror::Error;
7use zinc_primality::PrimalityTest;
8
9/// A cryptographic transcript implementation using the BLAKE3 hash
10/// function. Used for Fiat-Shamir transformations in zero-knowledge proof
11/// systems.
12#[derive(Debug, Clone)]
13pub struct Blake3Transcript {
14    /// The underlying BLAKE3 hasher that maintains the transcript state.
15    hasher: blake3::Hasher,
16}
17
18impl Default for Blake3Transcript {
19    fn default() -> Self {
20        Self::new()
21    }
22}
23
24/// Domain-separation label bound into every transcript at construction.
25///
26/// Absorbed before any protocol data, so transcripts belonging to different
27/// protocols, or to different versions of this one, cannot coincide even on
28/// byte-identical prover messages. The per-message tags applied by
29/// [`Transcript::absorb_bytes`] delimit values within a transcript; this label
30/// separates one transcript's whole message schedule from another's.
31///
32/// Bump the version suffix on any change to the wire encoding or to the
33/// message schedule of the protocol.
34const DOMAIN_SEPARATOR: &[u8] = b"zinc-plus/transcript/v1";
35
36impl Blake3Transcript {
37    pub fn new() -> Self {
38        let mut hasher = blake3::Hasher::new();
39        hasher.update(DOMAIN_SEPARATOR);
40        Self { hasher }
41    }
42
43    /// Generates a specified number of pseudorandom bytes based on the current
44    /// transcript state. Uses a counter-based approach to generate enough
45    /// bytes from the hasher.
46    ///
47    /// Note that this does NOT update the internal state of the hasher
48    #[allow(clippy::arithmetic_side_effects)]
49    fn fill_with_random_bytes(&mut self, buf: &mut [u8]) {
50        self.hasher.finalize_xof().fill(buf);
51    }
52
53    fn gen_random<R: ConstTranscribable>(&mut self, buf: &mut [u8]) -> R {
54        self.fill_with_random_bytes(buf);
55        self.absorb_bytes(buf);
56        R::read_transcription_bytes_exact(buf)
57    }
58}
59
60impl Transcript for Blake3Transcript {
61    fn get_challenge<T: ConstTranscribable>(&mut self) -> T {
62        let mut buf = vec![0u8; T::NUM_BYTES];
63        self.fill_with_random_bytes(&mut buf);
64        self.hasher.update(&[0x12]);
65        self.hasher.update(&buf);
66        self.hasher.update(&[0x34]);
67        T::read_transcription_bytes_exact(&buf)
68    }
69
70    #[allow(clippy::arithmetic_side_effects)]
71    fn get_prime<R: ConstIntSemiring + ConstTranscribable, T: PrimalityTest<R>>(&mut self) -> R {
72        let buf = &mut vec![0u8; R::NUM_BYTES];
73        loop {
74            let mut prime_candidate: R = self.gen_random(buf);
75            if prime_candidate.is_zero() {
76                continue;
77            }
78            if prime_candidate.is_even() {
79                prime_candidate -= R::ONE;
80            }
81            if T::is_probably_prime(&prime_candidate) {
82                return prime_candidate;
83            }
84        }
85    }
86
87    fn absorb_inner(&mut self, v: &[u8]) {
88        self.hasher.update(v);
89    }
90}
91
92pub fn read_field_cfg<C>(bytes: &[u8]) -> C
93where
94    C: BaseFieldConfig,
95    C::Integer: ConstTranscribable,
96{
97    let mod_size = C::Integer::NUM_BYTES;
98    let modulus = C::Integer::read_transcription_bytes_exact(&bytes[..mod_size]);
99    C::new(&modulus).expect("valid field modulus in proof transcription")
100}
101
102pub fn append_field_cfg<'a, C>(buf: &'a mut [u8], modulus: &C::Integer) -> &'a mut [u8]
103where
104    C: BaseFieldConfig,
105    C::Integer: ConstTranscribable,
106{
107    let mod_size = C::Integer::NUM_BYTES;
108    let (buf, rest) = buf.split_at_mut(mod_size);
109    modulus.write_transcription_bytes_exact(buf);
110    rest
111}
112
113#[derive(Clone, Debug, PartialEq, Error)]
114#[error("{1}")]
115pub struct TranscriptError(pub ErrorKind, pub String);