1use crate::{
2 absorb::{SymInstance, SymWitness},
3 equations::{SymPoint, SymScalar},
4 errors::{SigmaProofError, SigmaProofResult},
5 transcript::ProofTranscript,
6};
7use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, RistrettoPoint, Scalar};
8
9fn latex_var(name: &str) -> String {
11 let escaped = name.replace('_', "\\_");
12 format!("\\texttt{{{}}}", escaped)
13}
14
15fn sympoint_to_latex_with_context(
17 point: &SymPoint,
18 var_names: &[&str],
19 is_instance: bool,
20) -> String {
21 match point {
22 SymPoint::Const(p) => {
23 if *p == RISTRETTO_BASEPOINT_POINT {
24 "G".to_string()
26 } else {
27 "P".to_string() }
29 }
30 SymPoint::Var(Some(_)) => "P".to_string(), SymPoint::Var(None) => "?".to_string(), SymPoint::Add(p1, p2) => {
33 format!(
34 "({} + {})",
35 sympoint_to_latex_with_context(p1, var_names, is_instance),
36 sympoint_to_latex_with_context(p2, var_names, is_instance)
37 )
38 }
39 SymPoint::Sub(p1, p2) => {
40 format!(
41 "({} - {})",
42 sympoint_to_latex_with_context(p1, var_names, is_instance),
43 sympoint_to_latex_with_context(p2, var_names, is_instance)
44 )
45 }
46 SymPoint::Neg(p) => {
47 format!(
48 "(-{})",
49 sympoint_to_latex_with_context(p, var_names, is_instance)
50 )
51 }
52 SymPoint::Scale(s, p) => {
53 let point_str = match p.as_ref() {
55 SymPoint::Const(pt) if *pt == Scalar::from(2u64) * RISTRETTO_BASEPOINT_POINT => {
56 latex_var("pubkey")
57 }
58 SymPoint::Const(pt) if *pt == Scalar::from(3u64) * RISTRETTO_BASEPOINT_POINT => {
59 latex_var("commitment")
60 }
61 SymPoint::Const(pt) if *pt == Scalar::from(4u64) * RISTRETTO_BASEPOINT_POINT => {
62 latex_var("handle")
63 }
64 _ => sympoint_to_latex_with_context(p, var_names, is_instance),
65 };
66 format!("{} \\cdot {}", symscalar_to_latex(s, var_names), point_str)
67 }
68 }
69}
70
71fn sympoint_to_latex(point: &SymPoint, var_names: &[&str]) -> String {
73 sympoint_to_latex_with_context(point, var_names, false)
74}
75
76fn symscalar_to_latex(scalar: &SymScalar, var_names: &[&str]) -> String {
78 match scalar {
79 SymScalar::Const(s) => {
80 if *s == Scalar::from(1u64) {
82 "1".to_string()
83 } else if *s == Scalar::from(2u64) {
84 "2".to_string()
85 } else if *s == Scalar::from(3u64) {
86 "3".to_string()
87 } else if *s == Scalar::from(4u64) {
88 "4".to_string()
89 } else if *s == Scalar::from(5u64) {
90 "5".to_string()
91 } else {
92 "c".to_string() }
94 }
95 SymScalar::Var(Some(s)) => {
96 if *s == Scalar::from(1u64) && !var_names.is_empty() {
98 latex_var(var_names[0])
99 } else if *s == Scalar::from(2u64) && var_names.len() > 1 {
100 latex_var(var_names[1])
101 } else if *s == Scalar::from(3u64) && var_names.len() > 2 {
102 latex_var(var_names[2])
103 } else if *s == Scalar::from(4u64) && var_names.len() > 3 {
104 latex_var(var_names[3])
105 } else if *s == Scalar::from(5u64) && var_names.len() > 4 {
106 latex_var(var_names[4])
107 } else {
108 "v".to_string() }
110 }
111 SymScalar::Var(None) => "?".to_string(), SymScalar::Add(s1, s2) => {
113 format!(
114 "({} + {})",
115 symscalar_to_latex(s1, var_names),
116 symscalar_to_latex(s2, var_names)
117 )
118 }
119 SymScalar::Sub(s1, s2) => {
120 format!(
121 "({} - {})",
122 symscalar_to_latex(s1, var_names),
123 symscalar_to_latex(s2, var_names)
124 )
125 }
126 SymScalar::Neg(s) => {
127 format!("(-{})", symscalar_to_latex(s, var_names))
128 }
129 SymScalar::Mul(s1, s2) => {
130 format!(
131 "({} \\cdot {})",
132 symscalar_to_latex(s1, var_names),
133 symscalar_to_latex(s2, var_names)
134 )
135 }
136 }
137}
138
139pub trait SigmaProof {
140 const LABEL: &'static [u8];
141
142 type WITNESS: SymWitness;
143 type INSTANCE: SymInstance;
144
145 fn f(instance: &Self::INSTANCE) -> Vec<SymPoint>;
146
147 fn psi(witness: &Self::WITNESS, instance: &Self::INSTANCE) -> Vec<SymPoint>;
148
149 fn prove(witness: &Self::WITNESS, instance: &Self::INSTANCE) -> SigmaProofResult<Vec<u8>> {
150 let mut transcript = ProofTranscript::new_prover(Self::LABEL);
152
153 for point in instance.points() {
155 transcript.common_absorb_point(b"", &point.evaluate()?);
156 }
157 for scalar in instance.scalars() {
158 transcript.common_absorb_scalar(b"", &scalar.evaluate()?);
159 }
160
161 let rng = &mut rand::rngs::OsRng;
163 let alphas = Self::WITNESS::rand(rng);
164 let commited_alphas = Self::psi(&alphas, instance);
165 for point in &commited_alphas {
166 transcript.prover_absorb_point(b"r", &point.evaluate()?);
167 }
168
169 let e = transcript.challenge(b"e");
171
172 for z_i in witness
174 .values()?
175 .into_iter()
176 .zip(alphas.values()?)
177 .map(|(s, a)| s * e + a)
178 {
179 transcript.prover_absorb_scalar(b"z", &z_i);
180 }
181
182 Ok(transcript.finalize())
183 }
184
185 fn verify(instance: &Self::INSTANCE, proof: &[u8]) -> Result<(), SigmaProofError> {
186 if proof.len() % 32 != 0 {
188 return Err(SigmaProofError::TranscriptFinalizationFailed);
189 }
190
191 let mut transcript = ProofTranscript::new_verifier(Self::LABEL, proof);
193
194 let big_x_points: Vec<_> = Self::f(instance)
196 .into_iter()
197 .map(|p| p.evaluate())
198 .collect::<Result<Vec<_>, _>>()?;
199
200 for point in instance.points() {
202 transcript.common_absorb_point(b"", &point.evaluate()?);
203 }
204 for scalar in instance.scalars() {
205 transcript.common_absorb_scalar(b"", &scalar.evaluate()?);
206 }
207
208 let big_a = transcript
210 .verifier_receive_points(b"r", big_x_points.len())
211 .ok_or(SigmaProofError::TranscriptError)?;
212
213 let e = transcript.challenge(b"e");
215
216 let sigmas = transcript
218 .verifier_receives_all_scalars(b"z")
219 .ok_or(SigmaProofError::TranscriptError)?;
220 println!("sigmas received: {}", sigmas.len());
221 let sigmas_as_input = Self::WITNESS::from_values(&sigmas)?;
222
223 let psi_output = Self::psi(&sigmas_as_input, instance);
224
225 if big_x_points.len() != psi_output.len() {
227 return Err(SigmaProofError::PsiOutputLengthMismatch);
228 }
229
230 for ((big_x_i, big_a_i), psi_i) in big_x_points.iter().zip(&big_a).zip(&psi_output) {
231 let rhs = big_a_i + e * big_x_i;
232 if psi_i.evaluate()? != rhs {
233 return Err(SigmaProofError::EquationCheckFailed);
234 }
235 }
236
237 Ok(())
238 }
239
240 fn spec() -> String {
242 let psi_in_len = Self::WITNESS::num_scalars();
243 let f_scalars_in = Self::INSTANCE::num_scalars();
244 let f_points_in = Self::INSTANCE::num_points();
245
246 let protocol_name = String::from_utf8_lossy(Self::LABEL);
247
248 let dummy_scalars: Vec<Scalar> = (1..=psi_in_len).map(|i| Scalar::from(i as u64)).collect();
250 let dummy_witness = match Self::WITNESS::from_values(&dummy_scalars) {
251 Ok(w) => w,
252 Err(_) => {
253 return format!(
255 r#"#### {}
256Error: Could not generate symbolic analysis for this protocol."#,
257 protocol_name
258 );
259 }
260 };
261
262 let dummy_f_scalars_in: Vec<Scalar> =
264 (1..=f_scalars_in).map(|i| Scalar::from(i as u64)).collect();
265 let dummy_instance_points: Vec<RistrettoPoint> = (0..f_points_in)
267 .map(|i| Scalar::from((i + 2) as u64) * RISTRETTO_BASEPOINT_POINT)
268 .collect();
269 let dummy_instance =
270 match Self::INSTANCE::from_values(&dummy_f_scalars_in, &dummy_instance_points) {
271 Ok(i) => i,
272 Err(_) => {
273 return format!(
275 r#"#### {}
276Error: Could not generate symbolic analysis for this protocol."#,
277 protocol_name
278 );
279 }
280 };
281
282 let var_names: Vec<&str> = (0..psi_in_len)
284 .map(|i| Self::WITNESS::get_var_name(i))
285 .collect();
286
287 let instance_field_names = Self::INSTANCE::get_field_names();
289
290 let f_result = Self::f(&dummy_instance);
292
293 let f_equations: Vec<String> = f_result
295 .iter()
296 .map(|point| {
297 match point {
299 SymPoint::Const(p) if *p == Scalar::from(2u64) * RISTRETTO_BASEPOINT_POINT => {
300 if instance_field_names.len() > f_scalars_in {
302 latex_var(&instance_field_names[f_scalars_in])
303 } else {
304 "P_1".to_string()
305 }
306 }
307 SymPoint::Const(p) if *p == Scalar::from(3u64) * RISTRETTO_BASEPOINT_POINT => {
308 if instance_field_names.len() > f_scalars_in + 1 {
310 latex_var(&instance_field_names[f_scalars_in + 1])
311 } else {
312 "P_2".to_string()
313 }
314 }
315 SymPoint::Const(p) if *p == Scalar::from(4u64) * RISTRETTO_BASEPOINT_POINT => {
316 if instance_field_names.len() > f_scalars_in + 2 {
318 latex_var(&instance_field_names[f_scalars_in + 2])
319 } else {
320 "P_3".to_string()
321 }
322 }
323 _ => sympoint_to_latex_with_context(point, &var_names, true),
324 }
325 })
326 .collect();
327
328 let psi_result = Self::psi(&dummy_witness, &dummy_instance);
330
331 let psi_equations: Vec<String> = psi_result
333 .iter()
334 .map(|point| sympoint_to_latex(point, &var_names))
335 .collect();
336
337 let checks = psi_equations
338 .iter()
339 .zip(f_equations.iter())
340 .map(|(psi, f)| format!("* ${} = {}$", psi, f))
341 .collect::<Vec<_>>()
342 .join("\n");
343
344 format!(
345 r#"The Sigma protocol is labeled as `{protocol_name}`.
346
347The **witness** is defined as $\mathbf \omega = \{{ {witness_field_names} \}}$.
348
349The **instance** is defined as $\mathbf X = \{{ {instance_field_names} \}}$.
350
351The sigma protocol allows us to prove knowledge of $\mathbf \omega$ such that $\psi(\mathbf \omega) = f(\mathbf X)$.
352
353The homomorphism $\psi$ is defined as:
354
355$$
356\begin{{aligned}}
357\psi : \mathbb{{F}}^{{{psi_in_len}}} &\to \mathbb{{G}}^{{{psi_out_len}}} \\
358\mathbf \omega &\mapsto ({psi_latex})
359\end{{aligned}}
360$$
361
362The transformation $f$ is defined as:
363
364$$
365\begin{{aligned}}
366f : \mathbb{{F}}^{{{f_scalars_in}}} \times \mathbb{{G}}^{{{f_points_in}}} &\to \mathbb{{G}}^{{{psi_out_len}}} \\
367\mathbf X &\mapsto ({f_latex})
368\end{{aligned}}
369$$
370
371In other words, the following is being proven:
372
373{checks}
374"#,
375 psi_out_len = f_result.len(),
376 psi_latex = psi_equations.join(", "),
377 f_latex = f_equations.join(", "),
378 witness_field_names = var_names
379 .iter()
380 .map(|name| latex_var(name))
381 .collect::<Vec<_>>()
382 .join(", "),
383 instance_field_names = Self::INSTANCE::get_field_names()
384 .iter()
385 .map(|name| latex_var(name))
386 .collect::<Vec<_>>()
387 .join(", "),
388 )
389 }
390}