arnaucube
arnaucube/testudoPublic Self-hosted repository
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    let (claim_post_phase2, ry) =
282      self
283        .sc_proof_phase2
284        .verify(claim_phase2, num_rounds_y, 2, transcript)?;
285
286    let poly_input_eval = {
287      // constant term
288      let mut input_as_sparse_poly_entries = vec![SparsePolyEntry::new(0, Scalar::one())];
289      //remaining inputs
290      input_as_sparse_poly_entries.extend(
291        (0..input.len())
292          .map(|i| SparsePolyEntry::new(i + 1, input[i]))
293          .collect::<Vec<SparsePolyEntry>>(),
294      );
295      SparsePolynomial::new(n.log2() as usize, input_as_sparse_poly_entries)
296        .evaluate(&ry[1..].to_vec())
297    };
298
299    let eval_Z_at_ry = (Scalar::one() - ry[0]) * self.eval_vars_at_ry + ry[0] * poly_input_eval;
300
301    // perform the final check in the second sum-check protocol
302    let (eval_A_r, eval_B_r, eval_C_r) = evals;
303    let scalar = r_A * eval_A_r + r_B * eval_B_r + r_C * eval_C_r;
304    let expected_claim_post_phase2 = eval_Z_at_ry * scalar;
305
306    assert_eq!(expected_claim_post_phase2, claim_post_phase2);
307
308    Ok((rx, ry))
309  }
310}
311
312#[cfg(test)]
313mod tests {
314  use super::*;
315  use ark_std::UniformRand;
316  use test::Bencher;
317
318  fn produce_tiny_r1cs() -> (R1CSInstance, Vec<Scalar>, Vec<Scalar>) {
319    // three constraints over five variables Z1, Z2, Z3, Z4, and Z5
320    // rounded to the nearest power of two
321    let num_cons = 128;
322    let num_vars = 256;
323    let num_inputs = 2;
324
325    // encode the above constraints into three matrices
326    let mut A: Vec<(usize, usize, Scalar)> = Vec::new();
327    let mut B: Vec<(usize, usize, Scalar)> = Vec::new();
328    let mut C: Vec<(usize, usize, Scalar)> = Vec::new();
329
330    let one = Scalar::one();
331    // constraint 0 entries
332    // (Z1 + Z2) * I0 - Z3 = 0;
333    A.push((0, 0, one));
334    A.push((0, 1, one));
335    B.push((0, num_vars + 1, one));
336    C.push((0, 2, one));
337
338    // constraint 1 entries
339    // (Z1 + I1) * (Z3) - Z4 = 0
340    A.push((1, 0, one));
341    A.push((1, num_vars + 2, one));
342    B.push((1, 2, one));
343    C.push((1, 3, one));
344    // constraint 3 entries
345    // Z5 * 1 - 0 = 0
346    A.push((2, 4, one));
347    B.push((2, num_vars, one));
348
349    let inst = R1CSInstance::new(num_cons, num_vars, num_inputs, &A, &B, &C);
350
351    // compute a satisfying assignment
352    let mut rng = ark_std::rand::thread_rng();
353    let i0 = Scalar::rand(&mut rng);
354    let i1 = Scalar::rand(&mut rng);
355    let z1 = Scalar::rand(&mut rng);
356    let z2 = Scalar::rand(&mut rng);
357    let z3 = (z1 + z2) * i0; // constraint 1: (Z1 + Z2) * I0 - Z3 = 0;
358    let z4 = (z1 + i1) * z3; // constraint 2: (Z1 + I1) * (Z3) - Z4 = 0
359    let z5 = Scalar::zero(); //constraint 3
360
361    let mut vars = vec![Scalar::zero(); num_vars];
362    vars[0] = z1;
363    vars[1] = z2;
364    vars[2] = z3;
365    vars[3] = z4;
366    vars[4] = z5;
367
368    let mut input = vec![Scalar::zero(); num_inputs];
369    input[0] = i0;
370    input[1] = i1;
371
372    (inst, vars, input)
373  }
374
375  #[test]
376  fn test_tiny_r1cs() {
377    let (inst, vars, input) = tests::produce_tiny_r1cs();
378    let is_sat = inst.is_sat(&vars, &input);
379    assert!(is_sat);
380  }
381
382  #[test]
383  fn test_synthetic_r1cs() {
384    let (inst, vars, input) = R1CSInstance::produce_synthetic_r1cs(1024, 1024, 10);
385    let is_sat = inst.is_sat(&vars, &input);
386    assert!(is_sat);
387  }
388
389  #[test]
390  pub fn check_r1cs_proof() {
391    let num_vars = 1024;
392    let num_cons = num_vars;
393    let num_inputs = 10;
394    let (inst, vars, input) = R1CSInstance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);
395
396    let gens = R1CSGens::new(b"test-m", num_cons, num_vars);
397
398    let mut random_tape = RandomTape::new(b"proof");
399    let mut prover_transcript = Transcript::new(b"example");
400    let (proof, rx, ry) = R1CSProof::prove(
401      &inst,
402      vars,
403      &input,
404      &gens,
405      &mut prover_transcript,
406      &mut random_tape,
407    );
408
409    let inst_evals = inst.evaluate(&rx, &ry);
410
411    let mut verifier_transcript = Transcript::new(b"example");
412    assert!(proof
413      .verify(
414        inst.get_num_vars(),
415        inst.get_num_cons(),
416        &input,
417        &inst_evals,
418        &mut verifier_transcript,
419        &gens,
420      )
421      .is_ok());
422  }
423
424  #[bench]
425  fn bench_r1cs_proof(b: &mut Bencher) {
426    b.iter(|| check_r1cs_proof());
427  }
428}