arnaucube
arnaucube/testudoPublic Self-hosted repository
testudo/src/r1csproof.rs
src/r1csproof.rsRaw
  1#![allow(clippy::too_many_arguments)]
  2use crate::constraints::{VerifierCircuit, VerifierConfig};
  3use crate::group::{Fq, Fr};
  4use crate::math::Math;
  5use crate::parameters::poseidon_params;
  6use crate::poseidon_transcript::{AppendToPoseidon, PoseidonTranscript};
  7use crate::sumcheck::SumcheckInstanceProof;
  8use ark_bls12_377::Bls12_377 as I;
  9use ark_bw6_761::BW6_761 as P;
 10use ark_poly::MultilinearExtension;
 11use ark_poly_commit::multilinear_pc::data_structures::{Commitment, Proof};
 12use ark_poly_commit::multilinear_pc::MultilinearPC;
 13
 14use super::commitments::MultiCommitGens;
 15use super::dense_mlpoly::{DensePolynomial, EqPolynomial, PolyCommitmentGens};
 16use super::errors::ProofVerifyError;
 17
 18use super::r1csinstance::R1CSInstance;
 19
 20use super::scalar::Scalar;
 21use super::sparse_mlpoly::{SparsePolyEntry, SparsePolynomial};
 22use super::timer::Timer;
 23use ark_crypto_primitives::{CircuitSpecificSetupSNARK, SNARK};
 24
 25use ark_groth16::Groth16;
 26use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
 27use ark_serialize::*;
 28use ark_std::{One, Zero};
 29
 30use std::time::Instant;
 31
 32#[derive(CanonicalSerialize, CanonicalDeserialize, Debug)]
 33pub struct R1CSProof {
 34  // The PST commitment to the multilinear extension of the witness.
 35  comm: Commitment<I>,
 36  sc_proof_phase1: SumcheckInstanceProof,
 37  claims_phase2: (Scalar, Scalar, Scalar, Scalar),
 38  sc_proof_phase2: SumcheckInstanceProof,
 39  eval_vars_at_ry: Scalar,
 40  proof_eval_vars_at_ry: Proof<I>,
 41  rx: Vec<Scalar>,
 42  ry: Vec<Scalar>,
 43  // The transcript state after the satisfiability proof was computed.
 44  pub transcript_sat_state: Scalar,
 45}
 46#[derive(Clone)]
 47pub struct R1CSSumcheckGens {
 48  gens_1: MultiCommitGens,
 49  gens_3: MultiCommitGens,
 50  gens_4: MultiCommitGens,
 51}
 52
 53// TODO: fix passing gens_1_ref
 54impl R1CSSumcheckGens {
 55  pub fn new(label: &'static [u8], gens_1_ref: &MultiCommitGens) -> Self {
 56    let gens_1 = gens_1_ref.clone();
 57    let gens_3 = MultiCommitGens::new(3, label);
 58    let gens_4 = MultiCommitGens::new(4, label);
 59
 60    R1CSSumcheckGens {
 61      gens_1,
 62      gens_3,
 63      gens_4,
 64    }
 65  }
 66}
 67
 68#[derive(Clone)]
 69pub struct R1CSGens {
 70  gens_sc: R1CSSumcheckGens,
 71  gens_pc: PolyCommitmentGens,
 72}
 73
 74impl R1CSGens {
 75  pub fn new(label: &'static [u8], _num_cons: usize, num_vars: usize) -> Self {
 76    let num_poly_vars = num_vars.log_2();
 77    let gens_pc = PolyCommitmentGens::new(num_poly_vars, label);
 78    let gens_sc = R1CSSumcheckGens::new(label, &gens_pc.gens.gens_1);
 79    R1CSGens { gens_sc, gens_pc }
 80  }
 81}
 82
 83impl R1CSProof {
 84  fn prove_phase_one(
 85    num_rounds: usize,
 86    evals_tau: &mut DensePolynomial,
 87    evals_Az: &mut DensePolynomial,
 88    evals_Bz: &mut DensePolynomial,
 89    evals_Cz: &mut DensePolynomial,
 90    transcript: &mut PoseidonTranscript,
 91  ) -> (SumcheckInstanceProof, Vec<Scalar>, Vec<Scalar>) {
 92    let comb_func =
 93      |poly_tau_comp: &Scalar,
 94       poly_A_comp: &Scalar,
 95       poly_B_comp: &Scalar,
 96       poly_C_comp: &Scalar|
 97       -> Scalar { (*poly_tau_comp) * ((*poly_A_comp) * poly_B_comp - poly_C_comp) };
 98
 99    let (sc_proof_phase_one, r, claims) = SumcheckInstanceProof::prove_cubic_with_additive_term(
100      &Scalar::zero(), // claim is zero
101      num_rounds,
102      evals_tau,
103      evals_Az,
104      evals_Bz,
105      evals_Cz,
106      comb_func,
107      transcript,
108    );
109
110    (sc_proof_phase_one, r, claims)
111  }
112
113  fn prove_phase_two(
114    num_rounds: usize,
115    claim: &Scalar,
116    evals_z: &mut DensePolynomial,
117    evals_ABC: &mut DensePolynomial,
118    transcript: &mut PoseidonTranscript,
119  ) -> (SumcheckInstanceProof, Vec<Scalar>, Vec<Scalar>) {
120    let comb_func =
121      |poly_A_comp: &Scalar, poly_B_comp: &Scalar| -> Scalar { (*poly_A_comp) * poly_B_comp };
122    let (sc_proof_phase_two, r, claims) = SumcheckInstanceProof::prove_quad(
123      claim, num_rounds, evals_z, evals_ABC, comb_func, transcript,
124    );
125
126    (sc_proof_phase_two, r, claims)
127  }
128
129  fn protocol_name() -> &'static [u8] {
130    b"R1CS proof"
131  }
132
133  pub fn prove(
134    inst: &R1CSInstance,
135    vars: Vec<Scalar>,
136    input: &[Scalar],
137    gens: &R1CSGens,
138    transcript: &mut PoseidonTranscript,
139  ) -> (R1CSProof, Vec<Scalar>, Vec<Scalar>) {
140    let timer_prove = Timer::new("R1CSProof::prove");
141    // we currently require the number of |inputs| + 1 to be at most number of vars
142    assert!(input.len() < vars.len());
143
144    // create the multilinear witness polynomial from the satisfying assiment
145    let poly_vars = DensePolynomial::new(vars.clone());
146
147    let timer_commit = Timer::new("polycommit");
148    // commitment to the satisfying witness polynomial
149    let comm = MultilinearPC::<I>::commit(&gens.gens_pc.ck, &poly_vars);
150    comm.append_to_poseidon(transcript);
151    timer_commit.stop();
152
153    let c = transcript.challenge_scalar();
154    transcript.new_from_state(&c);
155
156    transcript.append_scalar_vector(input);
157
158    let timer_sc_proof_phase1 = Timer::new("prove_sc_phase_one");
159
160    // append input to variables to create a single vector z
161    let z = {
162      let num_inputs = input.len();
163      let num_vars = vars.len();
164      let mut z = vars;
165      z.extend(&vec![Scalar::one()]); // add constant term in z
166      z.extend(input);
167      z.extend(&vec![Scalar::zero(); num_vars - num_inputs - 1]); // we will pad with zeros
168      z
169    };
170
171    // derive the verifier's challenge tau
172    let (num_rounds_x, num_rounds_y) = (inst.get_num_cons().log_2(), z.len().log_2());
173    let tau = transcript.challenge_vector(num_rounds_x);
174    // compute the initial evaluation table for R(\tau, x)
175    let mut poly_tau = DensePolynomial::new(EqPolynomial::new(tau).evals());
176    let (mut poly_Az, mut poly_Bz, mut poly_Cz) =
177      inst.multiply_vec(inst.get_num_cons(), z.len(), &z);
178
179    let (sc_proof_phase1, rx, _claims_phase1) = R1CSProof::prove_phase_one(
180      num_rounds_x,
181      &mut poly_tau,
182      &mut poly_Az,
183      &mut poly_Bz,
184      &mut poly_Cz,
185      transcript,
186    );
187    assert_eq!(poly_tau.len(), 1);
188    assert_eq!(poly_Az.len(), 1);
189    assert_eq!(poly_Bz.len(), 1);
190    assert_eq!(poly_Cz.len(), 1);
191    timer_sc_proof_phase1.stop();
192
193    let (tau_claim, Az_claim, Bz_claim, Cz_claim) =
194      (&poly_tau[0], &poly_Az[0], &poly_Bz[0], &poly_Cz[0]);
195    let prod_Az_Bz_claims = (*Az_claim) * Bz_claim;
196
197    // prove the final step of sum-check #1
198    let taus_bound_rx = tau_claim;
199    let _claim_post_phase1 = ((*Az_claim) * Bz_claim - Cz_claim) * taus_bound_rx;
200
201    let timer_sc_proof_phase2 = Timer::new("prove_sc_phase_two");
202    // combine the three claims into a single claim
203    let r_A = transcript.challenge_scalar();
204    let r_B = transcript.challenge_scalar();
205    let r_C = transcript.challenge_scalar();
206    let claim_phase2 = r_A * Az_claim + r_B * Bz_claim + r_C * Cz_claim;
207
208    let evals_ABC = {
209      // compute the initial evaluation table for R(\tau, x)
210      let evals_rx = EqPolynomial::new(rx.clone()).evals();
211      let (evals_A, evals_B, evals_C) =
212        inst.compute_eval_table_sparse(inst.get_num_cons(), z.len(), &evals_rx);
213
214      assert_eq!(evals_A.len(), evals_B.len());
215      assert_eq!(evals_A.len(), evals_C.len());
216      (0..evals_A.len())
217        .map(|i| r_A * evals_A[i] + r_B * evals_B[i] + r_C * evals_C[i])
218        .collect::<Vec<Scalar>>()
219    };
220
221    // another instance of the sum-check protocol
222    let (sc_proof_phase2, ry, _claims_phase2) = R1CSProof::prove_phase_two(
223      num_rounds_y,
224      &claim_phase2,
225      &mut DensePolynomial::new(z),
226      &mut DensePolynomial::new(evals_ABC),
227      transcript,
228    );
229    timer_sc_proof_phase2.stop();
230
231    // TODO: modify the polynomial evaluation in Spartan to be consistent
232    // with the evaluation in ark-poly-commit so that reversing is not needed
233    // anymore
234    let timmer_opening = Timer::new("polyopening");
235    let mut dummy = ry[1..].to_vec().clone();
236    dummy.reverse();
237    let proof_eval_vars_at_ry = MultilinearPC::<I>::open(&gens.gens_pc.ck, &poly_vars, &dummy);
238    println!(
239      "proof size (no of quotients): {:?}",
240      proof_eval_vars_at_ry.proofs.len()
241    );
242    timmer_opening.stop();
243
244    let timer_polyeval = Timer::new("polyeval");
245    let eval_vars_at_ry = poly_vars.evaluate(&ry[1..]);
246    timer_polyeval.stop();
247
248    timer_prove.stop();
249
250    let c = transcript.challenge_scalar();
251
252    (
253      R1CSProof {
254        comm,
255        sc_proof_phase1,
256        claims_phase2: (*Az_claim, *Bz_claim, *Cz_claim, prod_Az_Bz_claims),
257        sc_proof_phase2,
258        eval_vars_at_ry,
259        proof_eval_vars_at_ry,
260        rx: rx.clone(),
261        ry: ry.clone(),
262        transcript_sat_state: c,
263      },
264      rx,
265      ry,
266    )
267  }
268
269  pub fn verify_groth16(
270    &self,
271    num_vars: usize,
272    num_cons: usize,
273    input: &[Scalar],
274    evals: &(Scalar, Scalar, Scalar),
275    transcript: &mut PoseidonTranscript,
276    gens: &R1CSGens,
277  ) -> Result<(u128, u128, u128), ProofVerifyError> {
278    self.comm.append_to_poseidon(transcript);
279
280    let c = transcript.challenge_scalar();
281
282    let mut input_as_sparse_poly_entries = vec![SparsePolyEntry::new(0, Scalar::one())];
283    //remaining inputs
284    input_as_sparse_poly_entries.extend(
285      (0..input.len())
286        .map(|i| SparsePolyEntry::new(i + 1, input[i]))
287        .collect::<Vec<SparsePolyEntry>>(),
288    );
289
290    let n = num_vars;
291    let input_as_sparse_poly =
292      SparsePolynomial::new(n.log_2() as usize, input_as_sparse_poly_entries);
293
294    let config = VerifierConfig {
295      num_vars,
296      num_cons,
297      input: input.to_vec(),
298      evals: *evals,
299      params: poseidon_params(),
300      prev_challenge: c,
301      claims_phase2: self.claims_phase2,
302      polys_sc1: self.sc_proof_phase1.polys.clone(),
303      polys_sc2: self.sc_proof_phase2.polys.clone(),
304      eval_vars_at_ry: self.eval_vars_at_ry,
305      input_as_sparse_poly,
306      // rx: self.rx.clone(),
307      ry: self.ry.clone(),
308      transcript_sat_state: self.transcript_sat_state,
309    };
310
311    let mut rng = ark_std::test_rng();
312
313    let prove_inner = Timer::new("proveinnercircuit");
314    let start = Instant::now();
315    let circuit = VerifierCircuit::new(&config, &mut rng).unwrap();
316    let dp1 = start.elapsed().as_millis();
317    prove_inner.stop();
318
319    let start = Instant::now();
320    let (pk, vk) = Groth16::<P>::setup(circuit.clone(), &mut rng).unwrap();
321    let ds = start.elapsed().as_millis();
322
323    let prove_outer = Timer::new("proveoutercircuit");
324    let start = Instant::now();
325    let proof = Groth16::<P>::prove(&pk, circuit, &mut rng).unwrap();
326    let dp2 = start.elapsed().as_millis();
327    prove_outer.stop();
328
329    let start = Instant::now();
330    let is_verified = Groth16::<P>::verify(&vk, &[], &proof).unwrap();
331    assert!(is_verified);
332
333    let timer_verification = Timer::new("commitverification");
334    let mut dummy = self.ry[1..].to_vec();
335    // TODO: ensure ark-poly-commit and Spartan produce consistent results
336    // when evaluating a polynomial at a given point so this reverse is not
337    // needed.
338    dummy.reverse();
339
340    // Verifies the proof of opening against the result of evaluating the
341    // witness polynomial at point ry.
342    let res = MultilinearPC::<I>::check(
343      &gens.gens_pc.vk,
344      &self.comm,
345      &dummy,
346      self.eval_vars_at_ry,
347      &self.proof_eval_vars_at_ry,
348    );
349
350    timer_verification.stop();
351    assert!(res == true);
352    let dv = start.elapsed().as_millis();
353
354    Ok((ds, dp1 + dp2, dv))
355  }
356
357  // Helper function to find the number of constraint in the circuit which
358  // requires executing it.
359  pub fn circuit_size(
360    &self,
361    num_vars: usize,
362    num_cons: usize,
363    input: &[Scalar],
364    evals: &(Scalar, Scalar, Scalar),
365    transcript: &mut PoseidonTranscript,
366    _gens: &R1CSGens,
367  ) -> Result<usize, ProofVerifyError> {
368    self.comm.append_to_poseidon(transcript);
369
370    let c = transcript.challenge_scalar();
371
372    let mut input_as_sparse_poly_entries = vec![SparsePolyEntry::new(0, Scalar::one())];
373    //remaining inputs
374    input_as_sparse_poly_entries.extend(
375      (0..input.len())
376        .map(|i| SparsePolyEntry::new(i + 1, input[i]))
377        .collect::<Vec<SparsePolyEntry>>(),
378    );
379
380    let n = num_vars;
381    let input_as_sparse_poly =
382      SparsePolynomial::new(n.log_2() as usize, input_as_sparse_poly_entries);
383
384    let config = VerifierConfig {
385      num_vars,
386      num_cons,
387      input: input.to_vec(),
388      evals: *evals,
389      params: poseidon_params(),
390      prev_challenge: c,
391      claims_phase2: self.claims_phase2,
392      polys_sc1: self.sc_proof_phase1.polys.clone(),
393      polys_sc2: self.sc_proof_phase2.polys.clone(),
394      eval_vars_at_ry: self.eval_vars_at_ry,
395      input_as_sparse_poly,
396      // rx: self.rx.clone(),
397      ry: self.ry.clone(),
398      transcript_sat_state: self.transcript_sat_state,
399    };
400
401    let mut rng = ark_std::test_rng();
402    let circuit = VerifierCircuit::new(&config, &mut rng).unwrap();
403
404    let nc_inner = verify_constraints_inner(circuit.clone(), &num_cons);
405
406    let nc_outer = verify_constraints_outer(circuit, &num_cons);
407    Ok(nc_inner + nc_outer)
408  }
409}
410
411fn verify_constraints_outer(circuit: VerifierCircuit, _num_cons: &usize) -> usize {
412  let cs = ConstraintSystem::<Fq>::new_ref();
413  circuit.generate_constraints(cs.clone()).unwrap();
414  assert!(cs.is_satisfied().unwrap());
415  cs.num_constraints()
416}
417
418fn verify_constraints_inner(circuit: VerifierCircuit, _num_cons: &usize) -> usize {
419  let cs = ConstraintSystem::<Fr>::new_ref();
420  circuit
421    .inner_circuit
422    .generate_constraints(cs.clone())
423    .unwrap();
424  assert!(cs.is_satisfied().unwrap());
425  cs.num_constraints()
426}
427
428#[cfg(test)]
429mod tests {
430  use crate::parameters::poseidon_params;
431
432  use super::*;
433
434  use ark_std::UniformRand;
435
436  fn produce_tiny_r1cs() -> (R1CSInstance, Vec<Scalar>, Vec<Scalar>) {
437    // three constraints over five variables Z1, Z2, Z3, Z4, and Z5
438    // rounded to the nearest power of two
439    let num_cons = 128;
440    let num_vars = 256;
441    let num_inputs = 2;
442
443    // encode the above constraints into three matrices
444    let mut A: Vec<(usize, usize, Scalar)> = Vec::new();
445    let mut B: Vec<(usize, usize, Scalar)> = Vec::new();
446    let mut C: Vec<(usize, usize, Scalar)> = Vec::new();
447
448    let one = Scalar::one();
449    // constraint 0 entries
450    // (Z1 + Z2) * I0 - Z3 = 0;
451    A.push((0, 0, one));
452    A.push((0, 1, one));
453    B.push((0, num_vars + 1, one));
454    C.push((0, 2, one));
455
456    // constraint 1 entries
457    // (Z1 + I1) * (Z3) - Z4 = 0
458    A.push((1, 0, one));
459    A.push((1, num_vars + 2, one));
460    B.push((1, 2, one));
461    C.push((1, 3, one));
462    // constraint 3 entries
463    // Z5 * 1 - 0 = 0
464    A.push((2, 4, one));
465    B.push((2, num_vars, one));
466
467    let inst = R1CSInstance::new(num_cons, num_vars, num_inputs, &A, &B, &C);
468
469    // compute a satisfying assignment
470    let mut rng = ark_std::rand::thread_rng();
471    let i0 = Scalar::rand(&mut rng);
472    let i1 = Scalar::rand(&mut rng);
473    let z1 = Scalar::rand(&mut rng);
474    let z2 = Scalar::rand(&mut rng);
475    let z3 = (z1 + z2) * i0; // constraint 1: (Z1 + Z2) * I0 - Z3 = 0;
476    let z4 = (z1 + i1) * z3; // constraint 2: (Z1 + I1) * (Z3) - Z4 = 0
477    let z5 = Scalar::zero(); //constraint 3
478
479    let mut vars = vec![Scalar::zero(); num_vars];
480    vars[0] = z1;
481    vars[1] = z2;
482    vars[2] = z3;
483    vars[3] = z4;
484    vars[4] = z5;
485
486    let mut input = vec![Scalar::zero(); num_inputs];
487    input[0] = i0;
488    input[1] = i1;
489
490    (inst, vars, input)
491  }
492
493  #[test]
494  fn test_tiny_r1cs() {
495    let (inst, vars, input) = tests::produce_tiny_r1cs();
496    let is_sat = inst.is_sat(&vars, &input);
497    assert!(is_sat);
498  }
499
500  #[test]
501  fn test_synthetic_r1cs() {
502    let (inst, vars, input) = R1CSInstance::produce_synthetic_r1cs(1024, 1024, 10);
503    let is_sat = inst.is_sat(&vars, &input);
504    assert!(is_sat);
505  }
506
507  #[test]
508  pub fn check_r1cs_proof() {
509    let num_vars = 1024;
510    let num_cons = num_vars;
511    let num_inputs = 10;
512    let (inst, vars, input) = R1CSInstance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);
513
514    let gens = R1CSGens::new(b"test-m", num_cons, num_vars);
515
516    let params = poseidon_params();
517    // let mut random_tape = RandomTape::new(b"proof");
518
519    let mut prover_transcript = PoseidonTranscript::new(&params);
520    let (proof, rx, ry) = R1CSProof::prove(&inst, vars, &input, &gens, &mut prover_transcript);
521
522    let inst_evals = inst.evaluate(&rx, &ry);
523
524    let mut verifier_transcript = PoseidonTranscript::new(&params);
525
526    // if you want to check the test fails
527    // input[0] = Scalar::zero();
528
529    assert!(proof
530      .verify_groth16(
531        inst.get_num_vars(),
532        inst.get_num_cons(),
533        &input,
534        &inst_evals,
535        &mut verifier_transcript,
536        &gens,
537      )
538      .is_ok());
539  }
540}