sigma_rs/schnorr_protocol.rs
1//! Implementation of the generic Schnorr Sigma Protocol over a [`Group`].
2//!
3//! This module defines the [`SchnorrProof`] structure, which implements
4//! a Sigma protocol proving different types of discrete logarithm relations (eg. Schnorr, Pedersen's commitments)
5//! through a group morphism abstraction (see [Maurer09](https://crypto-test.ethz.ch/publications/files/Maurer09.pdf)).
6
7use crate::errors::Error;
8use crate::linear_relation::LinearRelation;
9use crate::{
10 serialization::{
11 deserialize_elements, deserialize_scalars, serialize_elements, serialize_scalars,
12 },
13 traits::{SigmaProtocol, SigmaProtocolSimulator},
14};
15
16use ff::Field;
17use group::{Group, GroupEncoding};
18use rand::{CryptoRng, Rng, RngCore};
19
20/// A Schnorr protocol proving knowledge of a witness for a linear group relation.
21///
22/// This implementation generalizes Schnorr’s discrete logarithm proof by using
23/// a [`LinearRelation`], representing an abstract linear relation over the group.
24///
25/// # Type Parameters
26/// - `G`: A cryptographic group implementing [`Group`] and [`GroupEncoding`].
27#[derive(Clone, Default, Debug)]
28pub struct SchnorrProof<G: Group + GroupEncoding>(pub LinearRelation<G>);
29
30impl<G: Group + GroupEncoding> SchnorrProof<G> {
31 pub fn witness_length(&self) -> usize {
32 self.0.linear_map.num_scalars
33 }
34
35 pub fn commitment_length(&self) -> usize {
36 self.0.linear_map.num_constraints()
37 }
38}
39
40impl<G> From<LinearRelation<G>> for SchnorrProof<G>
41where
42 G: Group + GroupEncoding,
43{
44 fn from(value: LinearRelation<G>) -> Self {
45 Self(value)
46 }
47}
48
49impl<G> SigmaProtocol for SchnorrProof<G>
50where
51 G: Group + GroupEncoding,
52{
53 type Commitment = Vec<G>;
54 type ProverState = (Vec<<G as Group>::Scalar>, Vec<<G as Group>::Scalar>);
55 type Response = Vec<<G as Group>::Scalar>;
56 type Witness = Vec<<G as Group>::Scalar>;
57 type Challenge = <G as Group>::Scalar;
58
59 /// Prover's first message: generates a commitment using random nonces.
60 ///
61 /// # Parameters
62 /// - `witness`: A vector of scalars that satisfy the linear map relation.
63 /// - `rng`: A cryptographically secure random number generator.
64 ///
65 /// # Returns
66 /// - A tuple containing:
67 /// - The commitment (a vector of group elements).
68 /// - The prover state (random nonces and witness) used to compute the response.
69 ///
70 /// # Errors
71 /// -[`Error::InvalidInstanceWitnessPair`] if the witness vector length is incorrect.
72 fn prover_commit(
73 &self,
74 witness: &Self::Witness,
75 mut rng: &mut (impl RngCore + CryptoRng),
76 ) -> Result<(Self::Commitment, Self::ProverState), Error> {
77 if witness.len() != self.witness_length() {
78 return Err(Error::InvalidInstanceWitnessPair);
79 }
80
81 // If the relation being proven is trivial, refuse to prove the statement.
82 if self.0.image()?.iter().all(|&x| x == G::identity()) {
83 return Err(Error::InvalidInstanceWitnessPair);
84 }
85
86 let nonces: Vec<G::Scalar> = (0..self.witness_length())
87 .map(|_| G::Scalar::random(&mut rng))
88 .collect();
89 let commitment = self.0.linear_map.evaluate(&nonces)?;
90 let prover_state = (nonces, witness.clone());
91 Ok((commitment, prover_state))
92 }
93
94 /// Computes the prover's response (second message) using the challenge.
95 ///
96 /// # Parameters
97 /// - `state`: The prover state returned by `prover_commit`, typically containing randomness and witness components.
98 /// - `challenge`: The verifier's challenge scalar.
99 ///
100 /// # Returns
101 /// - A vector of scalars forming the prover's response.
102 ///
103 /// # Errors
104 /// - Returns [`Error::InvalidInstanceWitnessPair`] if the prover state vectors have incorrect lengths.
105 fn prover_response(
106 &self,
107 prover_state: Self::ProverState,
108 challenge: &Self::Challenge,
109 ) -> Result<Self::Response, Error> {
110 let (nonces, witness) = prover_state;
111
112 if nonces.len() != self.witness_length() || witness.len() != self.witness_length() {
113 return Err(Error::InvalidInstanceWitnessPair);
114 }
115
116 let responses = nonces
117 .into_iter()
118 .zip(witness)
119 .map(|(r, w)| r + w * challenge)
120 .collect();
121 Ok(responses)
122 }
123 /// Verifies the correctness of the proof.
124 ///
125 /// # Parameters
126 /// - `commitment`: The prover's commitment vector (group elements).
127 /// - `challenge`: The challenge scalar.
128 /// - `response`: The prover's response vector.
129 ///
130 /// # Returns
131 /// - `Ok(())` if the proof is valid.
132 /// - `Err(Error::VerificationFailure)` if the proof is invalid.
133 /// - `Err(Error::InvalidInstanceWitnessPair)` if the lengths of commitment or response do not match the expected counts.
134 ///
135 /// # Errors
136 /// -[`Error::VerificationFailure`] if the computed relation
137 /// does not hold for the provided challenge and response, indicating proof invalidity.
138 /// -[`Error::InvalidInstanceWitnessPair`] if the commitment or response length is incorrect.
139 fn verifier(
140 &self,
141 commitment: &Self::Commitment,
142 challenge: &Self::Challenge,
143 response: &Self::Response,
144 ) -> Result<(), Error> {
145 if commitment.len() != self.commitment_length() || response.len() != self.witness_length() {
146 return Err(Error::InvalidInstanceWitnessPair);
147 }
148
149 let lhs = self.0.linear_map.evaluate(response)?;
150 let mut rhs = Vec::new();
151 for (i, g) in commitment.iter().enumerate() {
152 rhs.push({
153 let image_var = self.0.image[i];
154 self.0.linear_map.group_elements.get(image_var)? * challenge + g
155 });
156 }
157 if lhs == rhs {
158 Ok(())
159 } else {
160 Err(Error::VerificationFailure)
161 }
162 }
163
164 /// Serializes the prover's commitment into a byte vector.
165 ///
166 /// This function encodes the vector of group elements (the commitment)
167 /// into a binary format suitable for transmission or storage. This is
168 /// typically the first message sent in a Sigma protocol round.
169 ///
170 /// # Parameters
171 /// - `commitment`: A vector of group elements representing the prover's commitment.
172 ///
173 /// # Returns
174 /// A `Vec<u8>` containing the serialized group elements.
175 fn serialize_commitment(&self, commitment: &Self::Commitment) -> Vec<u8> {
176 serialize_elements(commitment)
177 }
178
179 /// Serializes the verifier's challenge scalar into bytes.
180 ///
181 /// Converts the challenge scalar into a fixed-length byte encoding. This can be used
182 /// for Fiat–Shamir hashing, transcript recording, or proof transmission.
183 ///
184 /// # Parameters
185 /// - `challenge`: The scalar challenge value.
186 ///
187 /// # Returns
188 /// A `Vec<u8>` containing the serialized scalar.
189 fn serialize_challenge(&self, challenge: &Self::Challenge) -> Vec<u8> {
190 serialize_scalars::<G>(&[*challenge])
191 }
192
193 /// Serializes the prover's response vector into a byte format.
194 ///
195 /// The response is a vector of scalars computed by the prover after receiving
196 /// the verifier's challenge. This function encodes the vector into a format
197 /// suitable for transmission or inclusion in a batchable proof.
198 ///
199 /// # Parameters
200 /// - `response`: A vector of scalar responses computed by the prover.
201 ///
202 /// # Returns
203 /// A `Vec<u8>` containing the serialized scalars.
204 fn serialize_response(&self, response: &Self::Response) -> Vec<u8> {
205 serialize_scalars::<G>(response)
206 }
207
208 /// Deserializes a byte slice into a vector of group elements (commitment).
209 ///
210 /// This function reconstructs the prover’s commitment from its binary representation.
211 /// The number of elements expected is determined by the number of linear constraints
212 /// in the underlying linear relation.
213 ///
214 /// # Parameters
215 /// - `data`: A byte slice containing the serialized commitment.
216 ///
217 /// # Returns
218 /// A `Vec<G>` containing the deserialized group elements.
219 ///
220 /// # Errors
221 /// - Returns [`Error::VerificationFailure`] if the data is malformed or contains an invalid encoding.
222 fn deserialize_commitment(&self, data: &[u8]) -> Result<Self::Commitment, Error> {
223 deserialize_elements::<G>(data, self.commitment_length()).ok_or(Error::VerificationFailure)
224 }
225
226 /// Deserializes a byte slice into a challenge scalar.
227 ///
228 /// This function expects a single scalar to be encoded and returns it as the verifier's challenge.
229 ///
230 /// # Parameters
231 /// - `data`: A byte slice containing the serialized scalar challenge.
232 ///
233 /// # Returns
234 /// The deserialized scalar challenge value.
235 ///
236 /// # Errors
237 /// - Returns [`Error::VerificationFailure`] if deserialization fails or data is invalid.
238 fn deserialize_challenge(&self, data: &[u8]) -> Result<Self::Challenge, Error> {
239 let scalars = deserialize_scalars::<G>(data, 1).ok_or(Error::VerificationFailure)?;
240 Ok(scalars[0])
241 }
242
243 /// Deserializes a byte slice into the prover's response vector.
244 ///
245 /// The response vector contains scalars used in the second round of the Sigma protocol.
246 /// The expected number of scalars matches the number of witness variables.
247 ///
248 /// # Parameters
249 /// - `data`: A byte slice containing the serialized response.
250 ///
251 /// # Returns
252 /// A vector of deserialized scalars.
253 ///
254 /// # Errors
255 /// - Returns [`Error::VerificationFailure`] if the byte data is malformed or the length is incorrect.
256 fn deserialize_response(&self, data: &[u8]) -> Result<Self::Response, Error> {
257 deserialize_scalars::<G>(data, self.witness_length()).ok_or(Error::VerificationFailure)
258 }
259
260 fn instance_label(&self) -> impl AsRef<[u8]> {
261 self.0.label()
262 }
263
264 fn protocol_identifier(&self) -> impl AsRef<[u8]> {
265 b"SchnorrProof"
266 }
267}
268
269impl<G> SigmaProtocolSimulator for SchnorrProof<G>
270where
271 G: Group + GroupEncoding,
272{
273 /// Simulates a valid transcript for a given challenge without a witness.
274 ///
275 /// # Parameters
276 /// - `challenge`: A scalar value representing the challenge.
277 /// - `rng`: A cryptographically secure RNG.
278 ///
279 /// # Returns
280 /// - A commitment and response forming a valid proof for the given challenge.
281 fn simulate_response<R: Rng + CryptoRng>(&self, mut rng: &mut R) -> Self::Response {
282 let response: Vec<G::Scalar> = (0..self.witness_length())
283 .map(|_| G::Scalar::random(&mut rng))
284 .collect();
285 response
286 }
287
288 /// Simulates a full proof transcript using a randomly generated challenge.
289 ///
290 /// # Parameters
291 /// - `rng`: A cryptographically secure RNG.
292 ///
293 /// # Returns
294 /// - A tuple `(commitment, challenge, response)` forming a valid proof.
295 fn simulate_transcript<R: Rng + CryptoRng>(
296 &self,
297 rng: &mut R,
298 ) -> Result<(Self::Commitment, Self::Challenge, Self::Response), Error> {
299 let challenge = G::Scalar::random(&mut *rng);
300 let response = self.simulate_response(&mut *rng);
301 let commitment = self.simulate_commitment(&challenge, &response)?;
302 Ok((commitment, challenge, response))
303 }
304
305 /// Recomputes the commitment from the challenge and response (used in compact proofs).
306 ///
307 /// # Parameters
308 /// - `challenge`: The challenge scalar issued by the verifier or derived via Fiat–Shamir.
309 /// - `response`: The prover's response vector.
310 ///
311 /// # Returns
312 /// - A vector of group elements representing the simulated commitment (one per linear constraint).
313 ///
314 /// # Errors
315 /// - [`Error::InvalidInstanceWitnessPair`] if the response length does not match the expected number of scalars.
316 fn simulate_commitment(
317 &self,
318 challenge: &Self::Challenge,
319 response: &Self::Response,
320 ) -> Result<Self::Commitment, Error> {
321 if response.len() != self.witness_length() {
322 return Err(Error::InvalidInstanceWitnessPair);
323 }
324
325 let response_image = self.0.linear_map.evaluate(response)?;
326 let image = self.0.image()?;
327
328 let commitment = response_image
329 .iter()
330 .zip(&image)
331 .map(|(res, img)| *res - *img * challenge)
332 .collect::<Vec<_>>();
333 Ok(commitment)
334 }
335}