chaum_pedersen/primitives/
transcript.rs

1//! Fiat-Shamir transcript for non-interactive proofs.
2//!
3//! Provides domain-separated, transcript-based challenge generation using Merlin.
4
5use curve25519_dalek::scalar::Scalar as DalekScalar;
6use merlin::Transcript as MerlinTranscript;
7
8use super::Scalar;
9
10/// Protocol label for transcript initialization.
11const PROTOCOL_LABEL: &[u8] = b"Chaum-Pedersen ZKP v1.0.0";
12
13/// Domain separation tag for protocol name.
14const PROTOCOL_DST: &[u8] = b"chaum-pedersen-ristretto255";
15
16/// Domain separation tag for challenge generation.
17const CHALLENGE_DST: &[u8] = b"challenge";
18
19/// Number of bytes for wide reduction when generating Ristretto scalars.
20const WIDE_REDUCTION_BYTES: usize = 64;
21
22/// Transcript wrapper for Fiat-Shamir transformation.
23///
24/// Provides domain-separated, transcript-based challenge generation using Merlin.
25pub struct Transcript(MerlinTranscript);
26
27impl Transcript {
28    /// Creates a new transcript for the Chaum-Pedersen protocol.
29    pub fn new() -> Self {
30        let mut transcript = MerlinTranscript::new(PROTOCOL_LABEL);
31        transcript.append_message(b"protocol", PROTOCOL_DST);
32        Self(transcript)
33    }
34
35    /// Appends application-specific context to prevent cross-protocol attacks.
36    ///
37    /// # Security
38    ///
39    /// This should be called before generating proofs in application-specific
40    /// contexts to ensure proofs from one context cannot be replayed in another.
41    /// Examples: session ID, domain separator, purpose string.
42    pub fn append_context(&mut self, context: &[u8]) {
43        self.0.append_message(b"context", context);
44    }
45
46    /// Appends protocol parameters (generators) to the transcript.
47    pub fn append_parameters(&mut self, generator_g: &[u8], generator_h: &[u8]) {
48        self.0.append_message(b"generator-g", generator_g);
49        self.0.append_message(b"generator-h", generator_h);
50    }
51
52    /// Appends the statement (public values) to the transcript.
53    pub fn append_statement(&mut self, y1: &[u8], y2: &[u8]) {
54        self.0.append_message(b"y1", y1);
55        self.0.append_message(b"y2", y2);
56    }
57
58    /// Appends the commitment values to the transcript.
59    pub fn append_commitment(&mut self, r1: &[u8], r2: &[u8]) {
60        self.0.append_message(b"r1", r1);
61        self.0.append_message(b"r2", r2);
62    }
63
64    /// Generates a challenge scalar for Ristretto255.
65    ///
66    /// Uses wide reduction (64 bytes) to ensure uniform distribution.
67    pub fn challenge_scalar(&mut self) -> Scalar {
68        let mut buf = [0u8; WIDE_REDUCTION_BYTES];
69        self.0.challenge_bytes(CHALLENGE_DST, &mut buf);
70        Scalar::new(DalekScalar::from_bytes_mod_order_wide(&buf))
71    }
72}
73
74impl Default for Transcript {
75    fn default() -> Self {
76        Self::new()
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn transcript_creation() {
86        let transcript = Transcript::new();
87        assert!(core::mem::size_of_val(&transcript) > 0);
88    }
89
90    #[test]
91    fn challenge_scalar_deterministic() {
92        let mut t1 = Transcript::new();
93        t1.append_parameters(b"g", b"h");
94        t1.append_statement(b"y1", b"y2");
95        t1.append_commitment(b"r1", b"r2");
96        let c1 = t1.challenge_scalar();
97
98        let mut t2 = Transcript::new();
99        t2.append_parameters(b"g", b"h");
100        t2.append_statement(b"y1", b"y2");
101        t2.append_commitment(b"r1", b"r2");
102        let c2 = t2.challenge_scalar();
103
104        assert_eq!(c1, c2);
105    }
106
107    #[test]
108    fn challenge_scalar_different_inputs() {
109        let mut t1 = Transcript::new();
110        t1.append_commitment(b"r1", b"r2");
111        let c1 = t1.challenge_scalar();
112
113        let mut t2 = Transcript::new();
114        t2.append_commitment(b"r1_different", b"r2");
115        let c2 = t2.challenge_scalar();
116
117        assert_ne!(c1, c2);
118    }
119}