sigma_protocols/
sigma.rs

1//! Core traits and types for SIGMA protocols.
2//!
3//! This module defines the fundamental traits and implementations for
4//! zero-knowledge proof protocols following the SIGMA protocol structure.
5
6use crate::error::{Error, Result};
7use curve25519_dalek::ristretto::RistrettoPoint;
8use curve25519_dalek::scalar::Scalar;
9
10/// Trait for protocol commitments (first message in SIGMA protocol).
11pub trait Commitment: Clone + Send + Sync {
12    fn to_bytes(&self) -> Vec<u8>;
13    fn from_bytes(bytes: &[u8]) -> Result<Self>
14    where
15        Self: Sized;
16}
17
18/// Trait for protocol challenges (second message in SIGMA protocol).
19pub trait Challenge: Clone + Send + Sync {
20    fn to_bytes(&self) -> Vec<u8>;
21    fn from_bytes(bytes: &[u8]) -> Result<Self>
22    where
23        Self: Sized;
24}
25
26/// Trait for protocol responses (third message in SIGMA protocol).
27pub trait Response: Clone + Send + Sync {
28    fn to_bytes(&self) -> Vec<u8>;
29    fn from_bytes(bytes: &[u8]) -> Result<Self>
30    where
31        Self: Sized;
32}
33
34/// Core trait for SIGMA protocols.
35///
36/// A SIGMA protocol is a three-round interactive proof system with:
37/// 1. Commitment: Prover sends initial commitment
38/// 2. Challenge: Verifier sends random challenge
39/// 3. Response: Prover responds to challenge
40pub trait SigmaProtocol {
41    /// The public statement to be proven.
42    type Statement: Clone + Send + Sync;
43    /// The private witness known only to the prover.
44    type Witness: Clone + Send + Sync;
45    /// The commitment message type.
46    type Commitment: Commitment;
47    /// The challenge message type.
48    type Challenge: Challenge;
49    /// The response message type.
50    type Response: Response;
51
52    /// Generate the prover's initial commitment.
53    ///
54    /// Returns the commitment and internal state for later use.
55    fn prover_commit(
56        statement: &Self::Statement,
57        witness: &Self::Witness,
58    ) -> (Self::Commitment, Vec<u8>);
59
60    /// Generate the prover's response to a challenge.
61    ///
62    /// Uses the internal state from commitment phase.
63    fn prover_response(
64        statement: &Self::Statement,
65        witness: &Self::Witness,
66        state: &[u8],
67        challenge: &Self::Challenge,
68    ) -> Result<Self::Response>;
69
70    /// Verify a proof transcript.
71    ///
72    /// Returns `Ok(())` if the proof is valid, `Err` otherwise.
73    fn verifier(
74        statement: &Self::Statement,
75        commitment: &Self::Commitment,
76        challenge: &Self::Challenge,
77        response: &Self::Response,
78    ) -> Result<()>;
79}
80
81/// A challenge consisting of a single scalar value.
82#[derive(Clone, Debug)]
83pub struct ScalarChallenge(pub Scalar);
84
85impl Challenge for ScalarChallenge {
86    fn to_bytes(&self) -> Vec<u8> {
87        self.0.to_bytes().to_vec()
88    }
89
90    fn from_bytes(bytes: &[u8]) -> Result<Self> {
91        if bytes.len() != 32 {
92            return Err(Error::InvalidChallenge);
93        }
94        let mut array = [0u8; 32];
95        array.copy_from_slice(bytes);
96
97        // Use from_bytes_mod_order for challenge generation (non-canonical is OK for challenges)
98        Ok(ScalarChallenge(Scalar::from_bytes_mod_order(array)))
99    }
100}
101
102/// A commitment consisting of a single elliptic curve point.
103#[derive(Clone, Debug)]
104pub struct PointCommitment(pub RistrettoPoint);
105
106impl Commitment for PointCommitment {
107    fn to_bytes(&self) -> Vec<u8> {
108        self.0.compress().to_bytes().to_vec()
109    }
110
111    fn from_bytes(bytes: &[u8]) -> Result<Self> {
112        if bytes.len() != 32 {
113            return Err(Error::InvalidCommitment);
114        }
115        let mut array = [0u8; 32];
116        array.copy_from_slice(bytes);
117
118        let compressed = curve25519_dalek::ristretto::CompressedRistretto::from_slice(&array)
119            .map_err(|_| Error::InvalidCommitment)?;
120
121        compressed
122            .decompress()
123            .map(PointCommitment)
124            .ok_or(Error::InvalidPoint)
125    }
126}
127
128/// A response consisting of a single scalar value.
129#[derive(Clone, Debug)]
130pub struct ScalarResponse(pub Scalar);
131
132impl Response for ScalarResponse {
133    fn to_bytes(&self) -> Vec<u8> {
134        self.0.to_bytes().to_vec()
135    }
136
137    fn from_bytes(bytes: &[u8]) -> Result<Self> {
138        if bytes.len() != 32 {
139            return Err(Error::InvalidResponse);
140        }
141        let mut array = [0u8; 32];
142        array.copy_from_slice(bytes);
143
144        let scalar_option = Scalar::from_canonical_bytes(array);
145
146        if scalar_option.is_some().unwrap_u8() == 1 {
147            Ok(ScalarResponse(scalar_option.unwrap()))
148        } else {
149            Err(Error::InvalidScalar)
150        }
151    }
152}
153
154/// A commitment consisting of multiple elliptic curve points.
155#[derive(Clone, Debug)]
156pub struct MultiPointCommitment(pub Vec<RistrettoPoint>);
157
158impl Commitment for MultiPointCommitment {
159    fn to_bytes(&self) -> Vec<u8> {
160        let mut bytes = Vec::with_capacity(self.0.len() * 32 + 4);
161        bytes.extend_from_slice(&(self.0.len() as u32).to_le_bytes());
162
163        for point in &self.0 {
164            bytes.extend_from_slice(&point.compress().to_bytes());
165        }
166
167        bytes
168    }
169
170    fn from_bytes(bytes: &[u8]) -> Result<Self> {
171        if bytes.len() < 4 {
172            return Err(Error::InvalidCommitment);
173        }
174
175        let len = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
176
177        if bytes.len() != 4 + len * 32 {
178            return Err(Error::InvalidCommitment);
179        }
180
181        let mut points = Vec::with_capacity(len);
182        for i in 0..len {
183            let start = 4 + i * 32;
184            let end = start + 32;
185            let compressed =
186                curve25519_dalek::ristretto::CompressedRistretto::from_slice(&bytes[start..end])
187                    .map_err(|_| Error::InvalidCommitment)?;
188
189            let point = compressed.decompress().ok_or(Error::InvalidPoint)?;
190            points.push(point);
191        }
192
193        Ok(MultiPointCommitment(points))
194    }
195}
196
197/// A response consisting of multiple scalar values.
198#[derive(Clone, Debug)]
199pub struct MultiScalarResponse(pub Vec<Scalar>);
200
201impl Response for MultiScalarResponse {
202    fn to_bytes(&self) -> Vec<u8> {
203        let mut bytes = Vec::with_capacity(self.0.len() * 32 + 4);
204        bytes.extend_from_slice(&(self.0.len() as u32).to_le_bytes());
205
206        for scalar in &self.0 {
207            bytes.extend_from_slice(&scalar.to_bytes());
208        }
209
210        bytes
211    }
212
213    fn from_bytes(bytes: &[u8]) -> Result<Self> {
214        if bytes.len() < 4 {
215            return Err(Error::InvalidResponse);
216        }
217
218        let len = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
219
220        if bytes.len() != 4 + len * 32 {
221            return Err(Error::InvalidResponse);
222        }
223
224        let mut scalars = Vec::with_capacity(len);
225        for i in 0..len {
226            let start = 4 + i * 32;
227            let end = start + 32;
228            let mut array = [0u8; 32];
229            array.copy_from_slice(&bytes[start..end]);
230
231            let scalar_option = Scalar::from_canonical_bytes(array);
232
233            let scalar = if scalar_option.is_some().unwrap_u8() == 1 {
234                scalar_option.unwrap()
235            } else {
236                return Err(Error::InvalidScalar);
237            };
238            scalars.push(scalar);
239        }
240
241        Ok(MultiScalarResponse(scalars))
242    }
243}