testudo/src/r1csproof.rs
src/r1csproof.rsRaw
1#![allow(clippy::too_many_arguments)]
2use crate::group::CompressedGroup;
3use crate::sumcheck::SumcheckInstanceProof;
4
5use super::commitments::{Commitments, MultiCommitGens};
6use super::dense_mlpoly::{
7 DensePolynomial, EqPolynomial, PolyCommitment, PolyCommitmentGens, PolyEvalProof,
8};
9use super::errors::ProofVerifyError;
10use super::group::{
11 CompressGroupElement, DecompressGroupElement, GroupElement, VartimeMultiscalarMul,
12};
13use super::nizk::{EqualityProof, KnowledgeProof, ProductProof};
14use super::r1csinstance::R1CSInstance;
15use super::random::RandomTape;
16use super::scalar::Scalar;
17use super::sparse_mlpoly::{SparsePolyEntry, SparsePolynomial};
18use super::sumcheck::ZKSumcheckInstanceProof;
19use super::timer::Timer;
20use super::transcript::{AppendToTranscript, ProofTranscript};
21use ark_ec::ProjectiveCurve;
22use ark_ff::PrimeField;
23use ark_serialize::*;
24use ark_std::{One, Zero};
25use core::iter;
26use merlin::Transcript;
27
28#[derive(CanonicalSerialize, CanonicalDeserialize, Debug)]
29pub struct R1CSProof {
30 sc_proof_phase1: SumcheckInstanceProof,
31 claims_phase2: (Scalar, Scalar, Scalar, Scalar),
32 // pok_claims_phase2: (KnowledgeProof, ProductProof),
33 // proof_eq_sc_phase1: EqualityProof,
34 sc_proof_phase2: SumcheckInstanceProof,
35 eval_vars_at_ry: Scalar,
36 // proof_eval_vars_at_ry: PolyEvalProof,
37 // proof_eq_sc_phase2: EqualityProof,
38}
39
40pub struct R1CSSumcheckGens {
41 gens_1: MultiCommitGens,
42 gens_3: MultiCommitGens,
43 gens_4: MultiCommitGens,
44}
45
46// TODO: fix passing gens_1_ref
47impl R1CSSumcheckGens {
48 pub fn new(label: &'static [u8], gens_1_ref: &MultiCommitGens) -> Self {
49 let gens_1 = gens_1_ref.clone();
50 let gens_3 = MultiCommitGens::new(3, label);
51 let gens_4 = MultiCommitGens::new(4, label);
52
53 R1CSSumcheckGens {
54 gens_1,
55 gens_3,
56 gens_4,
57 }
58 }
59}
60
61pub struct R1CSGens {
62 gens_sc: R1CSSumcheckGens,
63 gens_pc: PolyCommitmentGens,
64}
65
66impl R1CSGens {
67 pub fn new(label: &'static [u8], _num_cons: usize, num_vars: usize) -> Self {
68 let num_poly_vars = num_vars.log2() as usize;
69 let gens_pc = PolyCommitmentGens::new(num_poly_vars, label);
70 let gens_sc = R1CSSumcheckGens::new(label, &gens_pc.gens.gens_1);
71 R1CSGens { gens_sc, gens_pc }
72 }
73}
74
75impl R1CSProof {
76 fn prove_phase_one(
77 num_rounds: usize,
78 evals_tau: &mut DensePolynomial,
79 evals_Az: &mut DensePolynomial,
80 evals_Bz: &mut DensePolynomial,
81 evals_Cz: &mut DensePolynomial,
82 transcript: &mut Transcript,
83 ) -> (SumcheckInstanceProof, Vec<Scalar>, Vec<Scalar>) {
84 let comb_func =
85 |poly_tau_comp: &Scalar,
86 poly_A_comp: &Scalar,
87 poly_B_comp: &Scalar,
88 poly_C_comp: &Scalar|
89 -> Scalar { (*poly_tau_comp) * ((*poly_A_comp) * poly_B_comp - poly_C_comp) };
90
91 let (sc_proof_phase_one, r, claims) = SumcheckInstanceProof::prove_cubic_with_additive_term(
92 &Scalar::zero(), // claim is zero
93 num_rounds,
94 evals_tau,
95 evals_Az,
96 evals_Bz,
97 evals_Cz,
98 comb_func,
99 transcript,
100 );
101
102 (sc_proof_phase_one, r, claims)
103 }
104
105 fn prove_phase_two(
106 num_rounds: usize,
107 claim: &Scalar,
108 evals_z: &mut DensePolynomial,
109 evals_ABC: &mut DensePolynomial,
110 transcript: &mut Transcript,
111 ) -> (SumcheckInstanceProof, Vec<Scalar>, Vec<Scalar>) {
112 let comb_func =
113 |poly_A_comp: &Scalar, poly_B_comp: &Scalar| -> Scalar { (*poly_A_comp) * poly_B_comp };
114 let (sc_proof_phase_two, r, claims) = SumcheckInstanceProof::prove_quad(
115 claim, num_rounds, evals_z, evals_ABC, comb_func, transcript,
116 );
117
118 (sc_proof_phase_two, r, claims)
119 }
120
121 fn protocol_name() -> &'static [u8] {
122 b"R1CS proof"
123 }
124
125 pub fn prove(
126 inst: &R1CSInstance,
127 vars: Vec<Scalar>,
128 input: &[Scalar],
129 gens: &R1CSGens,
130 transcript: &mut Transcript,
131 random_tape: &mut RandomTape,
132 ) -> (R1CSProof, Vec<Scalar>, Vec<Scalar>) {
133 let timer_prove = Timer::new("R1CSProof::prove");
134 transcript.append_protocol_name(R1CSProof::protocol_name());
135
136 // we currently require the number of |inputs| + 1 to be at most number of vars
137 assert!(input.len() < vars.len());
138
139 input.append_to_transcript(b"input", transcript);
140
141 let poly_vars = DensePolynomial::new(vars.clone());
142
143 let timer_sc_proof_phase1 = Timer::new("prove_sc_phase_one");
144
145 // append input to variables to create a single vector z
146 let z = {
147 let num_inputs = input.len();
148 let num_vars = vars.len();
149 let mut z = vars;
150 z.extend(&vec![Scalar::one()]); // add constant term in z
151 z.extend(input);
152 z.extend(&vec![Scalar::zero(); num_vars - num_inputs - 1]); // we will pad with zeros
153 z
154 };
155
156 // derive the verifier's challenge tau
157 let (num_rounds_x, num_rounds_y) =
158 (inst.get_num_cons().log2() as usize, z.len().log2() as usize);
159 let tau = transcript.challenge_vector(b"challenge_tau", num_rounds_x);
160 // compute the initial evaluation table for R(\tau, x)
161 let mut poly_tau = DensePolynomial::new(EqPolynomial::new(tau).evals());
162 let (mut poly_Az, mut poly_Bz, mut poly_Cz) =
163 inst.multiply_vec(inst.get_num_cons(), z.len(), &z);
164
165 let (sc_proof_phase1, rx, _claims_phase1) = R1CSProof::prove_phase_one(
166 num_rounds_x,
167 &mut poly_tau,
168 &mut poly_Az,
169 &mut poly_Bz,
170 &mut poly_Cz,
171 transcript,
172 );
173 assert_eq!(poly_tau.len(), 1);
174 assert_eq!(poly_Az.len(), 1);
175 assert_eq!(poly_Bz.len(), 1);
176 assert_eq!(poly_Cz.len(), 1);
177 timer_sc_proof_phase1.stop();
178
179 let (tau_claim, Az_claim, Bz_claim, Cz_claim) =
180 (&poly_tau[0], &poly_Az[0], &poly_Bz[0], &poly_Cz[0]);
181 let prod_Az_Bz_claims = (*Az_claim) * Bz_claim;
182
183 // prove the final step of sum-check #1
184 let taus_bound_rx = tau_claim;
185 let claim_post_phase1 = ((*Az_claim) * Bz_claim - Cz_claim) * taus_bound_rx;
186
187 let timer_sc_proof_phase2 = Timer::new("prove_sc_phase_two");
188 // combine the three claims into a single claim
189 let r_A = transcript.challenge_scalar(b"challenege_Az");
190 let r_B = transcript.challenge_scalar(b"challenege_Bz");
191 let r_C = transcript.challenge_scalar(b"challenege_Cz");
192 let claim_phase2 = r_A * Az_claim + r_B * Bz_claim + r_C * Cz_claim;
193
194 let evals_ABC = {
195 // compute the initial evaluation table for R(\tau, x)
196 let evals_rx = EqPolynomial::new(rx.clone()).evals();
197 let (evals_A, evals_B, evals_C) =
198 inst.compute_eval_table_sparse(inst.get_num_cons(), z.len(), &evals_rx);
199
200 assert_eq!(evals_A.len(), evals_B.len());
201 assert_eq!(evals_A.len(), evals_C.len());
202 (0..evals_A.len())
203 .map(|i| r_A * evals_A[i] + r_B * evals_B[i] + r_C * evals_C[i])
204 .collect::<Vec<Scalar>>()
205 };
206
207 // another instance of the sum-check protocol
208 let (sc_proof_phase2, ry, claims_phase2) = R1CSProof::prove_phase_two(
209 num_rounds_y,
210 &claim_phase2,
211 &mut DensePolynomial::new(z),
212 &mut DensePolynomial::new(evals_ABC),
213 transcript,
214 );
215 timer_sc_proof_phase2.stop();
216
217 let timer_polyeval = Timer::new("polyeval");
218 let eval_vars_at_ry = poly_vars.evaluate(&ry[1..].to_vec());
219 timer_polyeval.stop();
220
221 timer_prove.stop();
222
223 (
224 R1CSProof {
225 sc_proof_phase1,
226 claims_phase2: (*Az_claim, *Bz_claim, *Cz_claim, prod_Az_Bz_claims),
227 sc_proof_phase2,
228 eval_vars_at_ry,
229 },
230 rx,
231 ry,
232 )
233 }
234
235 pub fn verify(
236 &self,
237 num_vars: usize,
238 num_cons: usize,
239 input: &[Scalar],
240 evals: &(Scalar, Scalar, Scalar),
241 transcript: &mut Transcript,
242 gens: &R1CSGens,
243 ) -> Result<(Vec<Scalar>, Vec<Scalar>), ProofVerifyError> {
244 transcript.append_protocol_name(R1CSProof::protocol_name());
245
246 input.append_to_transcript(b"input", transcript);
247
248 let n = num_vars;
249
250 let (num_rounds_x, num_rounds_y) = (num_cons.log2() as usize, (2 * num_vars).log2() as usize);
251
252 // derive the verifier's challenge tau
253 let tau = transcript.challenge_vector(b"challenge_tau", num_rounds_x);
254
255 // verify the first sum-check instance
256 let claim_phase1 = Scalar::zero();
257 let (claim_post_phase1, rx) =
258 self
259 .sc_proof_phase1
260 .verify(claim_phase1, num_rounds_x, 3, transcript)?;
261
262 // perform the intermediate sum-check test with claimed Az, Bz, and Cz
263 let (Az_claim, Bz_claim, Cz_claim, prod_Az_Bz_claims) = &self.claims_phase2;
264
265 let taus_bound_rx: Scalar = (0..rx.len())
266 .map(|i| rx[i] * tau[i] + (Scalar::one() - rx[i]) * (Scalar::one() - tau[i]))
267 .product();
268
269 let expected_claim_post_phase1 = (*prod_Az_Bz_claims - *Cz_claim) * (taus_bound_rx);
270
271 assert_eq!(claim_post_phase1, expected_claim_post_phase1);
272
273 // derive three public challenges and then derive a joint claim
274 let r_A = transcript.challenge_scalar(b"challenege_Az");
275 let r_B = transcript.challenge_scalar(b"challenege_Bz");
276 let r_C = transcript.challenge_scalar(b"challenege_Cz");
277
278 let claim_phase2 = r_A * Az_claim + r_B * Bz_claim + r_C * Cz_claim;
279
280 // verify the joint claim with a sum-check protocol
281<<<<<<< HEAD
282 let (claim_post_phase2, ry) =
283 self
284 .sc_proof_phase2
285 .verify(claim_phase2, num_rounds_y, 2, transcript)?;
286=======
287 let (comm_claim_post_phase2, ry) = self.sc_proof_phase2.verify(
288 &comm_claim_phase2,
289 num_rounds_y,
290 2,
291 &gens.gens_sc.gens_1,
292 &gens.gens_sc.gens_3,
293 transcript,
294 )?;
295
296 // verify Z(ry) proof against the initial commitment
297 self.proof_eval_vars_at_ry.verify(
298 &gens.gens_pc,
299 transcript,
300 &ry[1..],
301 &self.comm_vars_at_ry,
302 &self.comm_vars,
303 )?;
304>>>>>>> clippy fixes (#50)
305
306 let poly_input_eval = {
307 // constant term
308 let mut input_as_sparse_poly_entries = vec![SparsePolyEntry::new(0, Scalar::one())];
309 //remaining inputs
310 input_as_sparse_poly_entries.extend(
311 (0..input.len())
312 .map(|i| SparsePolyEntry::new(i + 1, input[i]))
313 .collect::<Vec<SparsePolyEntry>>(),
314 );
315 SparsePolynomial::new(n.log2() as usize, input_as_sparse_poly_entries).evaluate(&ry[1..])
316 };
317
318 let eval_Z_at_ry = (Scalar::one() - ry[0]) * self.eval_vars_at_ry + ry[0] * poly_input_eval;
319
320 // perform the final check in the second sum-check protocol
321 let (eval_A_r, eval_B_r, eval_C_r) = evals;
322 let scalar = r_A * eval_A_r + r_B * eval_B_r + r_C * eval_C_r;
323 let expected_claim_post_phase2 = eval_Z_at_ry * scalar;
324
325 assert_eq!(expected_claim_post_phase2, claim_post_phase2);
326
327 Ok((rx, ry))
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334 use ark_std::UniformRand;
335 use test::Bencher;
336
337 fn produce_tiny_r1cs() -> (R1CSInstance, Vec<Scalar>, Vec<Scalar>) {
338 // three constraints over five variables Z1, Z2, Z3, Z4, and Z5
339 // rounded to the nearest power of two
340 let num_cons = 128;
341 let num_vars = 256;
342 let num_inputs = 2;
343
344 // encode the above constraints into three matrices
345 let mut A: Vec<(usize, usize, Scalar)> = Vec::new();
346 let mut B: Vec<(usize, usize, Scalar)> = Vec::new();
347 let mut C: Vec<(usize, usize, Scalar)> = Vec::new();
348
349 let one = Scalar::one();
350 // constraint 0 entries
351 // (Z1 + Z2) * I0 - Z3 = 0;
352 A.push((0, 0, one));
353 A.push((0, 1, one));
354 B.push((0, num_vars + 1, one));
355 C.push((0, 2, one));
356
357 // constraint 1 entries
358 // (Z1 + I1) * (Z3) - Z4 = 0
359 A.push((1, 0, one));
360 A.push((1, num_vars + 2, one));
361 B.push((1, 2, one));
362 C.push((1, 3, one));
363 // constraint 3 entries
364 // Z5 * 1 - 0 = 0
365 A.push((2, 4, one));
366 B.push((2, num_vars, one));
367
368 let inst = R1CSInstance::new(num_cons, num_vars, num_inputs, &A, &B, &C);
369
370 // compute a satisfying assignment
371 let mut rng = ark_std::rand::thread_rng();
372 let i0 = Scalar::rand(&mut rng);
373 let i1 = Scalar::rand(&mut rng);
374 let z1 = Scalar::rand(&mut rng);
375 let z2 = Scalar::rand(&mut rng);
376 let z3 = (z1 + z2) * i0; // constraint 1: (Z1 + Z2) * I0 - Z3 = 0;
377 let z4 = (z1 + i1) * z3; // constraint 2: (Z1 + I1) * (Z3) - Z4 = 0
378 let z5 = Scalar::zero(); //constraint 3
379
380 let mut vars = vec![Scalar::zero(); num_vars];
381 vars[0] = z1;
382 vars[1] = z2;
383 vars[2] = z3;
384 vars[3] = z4;
385 vars[4] = z5;
386
387 let mut input = vec![Scalar::zero(); num_inputs];
388 input[0] = i0;
389 input[1] = i1;
390
391 (inst, vars, input)
392 }
393
394 #[test]
395 fn test_tiny_r1cs() {
396 let (inst, vars, input) = tests::produce_tiny_r1cs();
397 let is_sat = inst.is_sat(&vars, &input);
398 assert!(is_sat);
399 }
400
401 #[test]
402 fn test_synthetic_r1cs() {
403 let (inst, vars, input) = R1CSInstance::produce_synthetic_r1cs(1024, 1024, 10);
404 let is_sat = inst.is_sat(&vars, &input);
405 assert!(is_sat);
406 }
407
408 #[test]
409 pub fn check_r1cs_proof() {
410 let num_vars = 1024;
411 let num_cons = num_vars;
412 let num_inputs = 10;
413 let (inst, vars, input) = R1CSInstance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);
414
415 let gens = R1CSGens::new(b"test-m", num_cons, num_vars);
416
417 let mut random_tape = RandomTape::new(b"proof");
418 let mut prover_transcript = Transcript::new(b"example");
419 let (proof, rx, ry) = R1CSProof::prove(
420 &inst,
421 vars,
422 &input,
423 &gens,
424 &mut prover_transcript,
425 &mut random_tape,
426 );
427
428 let inst_evals = inst.evaluate(&rx, &ry);
429
430 let mut verifier_transcript = Transcript::new(b"example");
431 assert!(proof
432 .verify(
433 inst.get_num_vars(),
434 inst.get_num_cons(),
435 &input,
436 &inst_evals,
437 &mut verifier_transcript,
438 &gens,
439 )
440 .is_ok());
441 }
442
443 #[bench]
444 fn bench_r1cs_proof(b: &mut Bencher) {
445 b.iter(|| check_r1cs_proof());
446 }
447}