#![allow(clippy::too_many_arguments)] use super::commitments::{Commitments, MultiCommitGens}; use super::dense_mlpoly::{ DensePolynomial, EqPolynomial, PolyCommitment, PolyCommitmentGens, PolyEvalProof, }; use super::errors::ProofVerifyError; use super::group::{CompressedGroup, GroupElement, VartimeMultiscalarMul}; use super::math::Math; use super::nizk::{EqualityProof, KnowledgeProof, ProductProof}; use super::r1csinstance::{R1CSInstance, R1CSInstanceEvals}; use super::random::RandomTape; use super::scalar::Scalar; use super::sparse_mlpoly::{SparsePolyEntry, SparsePolynomial}; use super::sumcheck::ZKSumcheckInstanceProof; use super::timer::Timer; use super::transcript::{AppendToTranscript, ProofTranscript}; use core::iter; use merlin::Transcript; use serde::{Deserialize, Serialize}; #[cfg(test)] use super::sparse_mlpoly::{SparseMatEntry, SparseMatPolynomial}; #[derive(Serialize, Deserialize, Debug)] pub struct R1CSProof { comm_vars: PolyCommitment, sc_proof_phase1: ZKSumcheckInstanceProof, claims_phase2: ( CompressedGroup, CompressedGroup, CompressedGroup, CompressedGroup, ), pok_claims_phase2: (KnowledgeProof, ProductProof), proof_eq_sc_phase1: EqualityProof, sc_proof_phase2: ZKSumcheckInstanceProof, comm_vars_at_ry: CompressedGroup, proof_eval_vars_at_ry: PolyEvalProof, proof_eq_sc_phase2: EqualityProof, } pub struct R1CSSumcheckGens { gens_1: MultiCommitGens, gens_3: MultiCommitGens, gens_4: MultiCommitGens, } // TODO: fix passing gens_1_ref impl R1CSSumcheckGens { pub fn new(label: &'static [u8], gens_1_ref: &MultiCommitGens) -> Self { let gens_1 = gens_1_ref.clone(); let gens_3 = MultiCommitGens::new(3, label); let gens_4 = MultiCommitGens::new(4, label); R1CSSumcheckGens { gens_1, gens_3, gens_4, } } } pub struct R1CSGens { gens_sc: R1CSSumcheckGens, gens_pc: PolyCommitmentGens, } impl R1CSGens { pub fn new(_num_cons: usize, num_vars: usize, label: &'static [u8]) -> Self { let num_poly_vars = num_vars.log2(); let gens_pc = PolyCommitmentGens::new(num_poly_vars, label); let gens_sc = R1CSSumcheckGens::new(label, &gens_pc.gens.gens_1); R1CSGens { gens_sc, gens_pc } } } impl R1CSProof { fn prove_phase_one( num_rounds: usize, evals_tau: &mut DensePolynomial, evals_Az: &mut DensePolynomial, evals_Bz: &mut DensePolynomial, evals_Cz: &mut DensePolynomial, gens: &R1CSSumcheckGens, transcript: &mut Transcript, random_tape: &mut RandomTape, ) -> (ZKSumcheckInstanceProof, Vec, Vec, Scalar) { let comb_func = |poly_A_comp: &Scalar, poly_B_comp: &Scalar, poly_C_comp: &Scalar, poly_D_comp: &Scalar| -> Scalar { poly_A_comp * (poly_B_comp * poly_C_comp - poly_D_comp) }; let (sc_proof_phase_one, r, claims, blind_claim_postsc) = ZKSumcheckInstanceProof::prove_cubic_with_additive_term( &Scalar::zero(), // claim is zero &Scalar::zero(), // blind for claim is also zero num_rounds, evals_tau, evals_Az, evals_Bz, evals_Cz, comb_func, &gens.gens_1, &gens.gens_4, transcript, random_tape, ); (sc_proof_phase_one, r, claims, blind_claim_postsc) } fn prove_phase_two( num_rounds: usize, claim: &Scalar, blind_claim: &Scalar, evals_z: &mut DensePolynomial, evals_ABC: &mut DensePolynomial, gens: &R1CSSumcheckGens, transcript: &mut Transcript, random_tape: &mut RandomTape, ) -> (ZKSumcheckInstanceProof, Vec, Vec, Scalar) { let comb_func = |poly_A_comp: &Scalar, poly_B_comp: &Scalar| -> Scalar { poly_A_comp * poly_B_comp }; let (sc_proof_phase_two, r, claims, blind_claim_postsc) = ZKSumcheckInstanceProof::prove_quad( claim, blind_claim, num_rounds, evals_z, evals_ABC, comb_func, &gens.gens_1, &gens.gens_3, transcript, random_tape, ); (sc_proof_phase_two, r, claims, blind_claim_postsc) } fn protocol_name() -> &'static [u8] { b"R1CS proof" } pub fn prove( inst: &R1CSInstance, vars: Vec, input: &[Scalar], gens: &R1CSGens, transcript: &mut Transcript, random_tape: &mut RandomTape, ) -> (R1CSProof, Vec, Vec) { let timer_prove = Timer::new("R1CSProof::prove"); transcript.append_protocol_name(R1CSProof::protocol_name()); // we currently require the number of |inputs| + 1 to be at most number of vars assert!(input.len() < vars.len()); let timer_commit = Timer::new("polycommit"); let (poly_vars, comm_vars, blinds_vars) = { // create a multilinear polynomial using the supplied assignment for variables let poly_vars = DensePolynomial::new(vars.clone()); // produce a commitment to the satisfying assignment let (comm_vars, blinds_vars) = poly_vars.commit(true, &gens.gens_pc, Some(random_tape)); // add the commitment to the prover's transcript comm_vars.append_to_transcript(b"poly_commitment", transcript); (poly_vars, comm_vars, blinds_vars) }; timer_commit.stop(); let timer_sc_proof_phase1 = Timer::new("prove_sc_phase_one"); // append input to variables to create a single vector z let z = { let num_inputs = input.len(); let num_vars = vars.len(); let mut z = vars; z.extend(&vec![Scalar::one()]); // add constant term in z z.extend(input); z.extend(&vec![Scalar::zero(); num_vars - num_inputs - 1]); // we will pad with zeros z }; // derive the verifier's challenge tau let (num_rounds_x, num_rounds_y) = (inst.get_num_cons().log2(), z.len().log2()); let tau = transcript.challenge_vector(b"challenge_tau", num_rounds_x); // compute the initial evaluation table for R(\tau, x) let mut poly_tau = DensePolynomial::new(EqPolynomial::new(tau).evals()); let (mut poly_Az, mut poly_Bz, mut poly_Cz) = inst.multiply_vec(inst.get_num_cons(), z.len(), &z); let (sc_proof_phase1, rx, _claims_phase1, blind_claim_postsc1) = R1CSProof::prove_phase_one( num_rounds_x, &mut poly_tau, &mut poly_Az, &mut poly_Bz, &mut poly_Cz, &gens.gens_sc, transcript, random_tape, ); assert_eq!(poly_tau.len(), 1); assert_eq!(poly_Az.len(), 1); assert_eq!(poly_Bz.len(), 1); assert_eq!(poly_Cz.len(), 1); timer_sc_proof_phase1.stop(); let (tau_claim, Az_claim, Bz_claim, Cz_claim) = (&poly_tau[0], &poly_Az[0], &poly_Bz[0], &poly_Cz[0]); let (Az_blind, Bz_blind, Cz_blind, prod_Az_Bz_blind) = ( random_tape.random_scalar(b"Az_blind"), random_tape.random_scalar(b"Bz_blind"), random_tape.random_scalar(b"Cz_blind"), random_tape.random_scalar(b"prod_Az_Bz_blind"), ); let (pok_Cz_claim, comm_Cz_claim) = { KnowledgeProof::prove( &gens.gens_sc.gens_1, transcript, random_tape, &Cz_claim, &Cz_blind, ) }; let (proof_prod, comm_Az_claim, comm_Bz_claim, comm_prod_Az_Bz_claims) = { let prod = Az_claim * Bz_claim; ProductProof::prove( &gens.gens_sc.gens_1, transcript, random_tape, &Az_claim, &Az_blind, &Bz_claim, &Bz_blind, &prod, &prod_Az_Bz_blind, ) }; comm_Az_claim.append_to_transcript(b"comm_Az_claim", transcript); comm_Bz_claim.append_to_transcript(b"comm_Bz_claim", transcript); comm_Cz_claim.append_to_transcript(b"comm_Cz_claim", transcript); comm_prod_Az_Bz_claims.append_to_transcript(b"comm_prod_Az_Bz_claims", transcript); // prove the final step of sum-check #1 let taus_bound_rx = tau_claim; let blind_expected_claim_postsc1 = taus_bound_rx * (prod_Az_Bz_blind - Cz_blind); let claim_post_phase1 = (Az_claim * Bz_claim - Cz_claim) * taus_bound_rx; let (proof_eq_sc_phase1, _C1, _C2) = EqualityProof::prove( &gens.gens_sc.gens_1, transcript, random_tape, &claim_post_phase1, &blind_expected_claim_postsc1, &claim_post_phase1, &blind_claim_postsc1, ); let timer_sc_proof_phase2 = Timer::new("prove_sc_phase_two"); // combine the three claims into a single claim let r_A = transcript.challenge_scalar(b"challenege_Az"); let r_B = transcript.challenge_scalar(b"challenege_Bz"); let r_C = transcript.challenge_scalar(b"challenege_Cz"); let claim_phase2 = r_A * Az_claim + r_B * Bz_claim + r_C * Cz_claim; let blind_claim_phase2 = r_A * Az_blind + r_B * Bz_blind + r_C * Cz_blind; let evals_ABC = { // compute the initial evaluation table for R(\tau, x) let evals_rx = EqPolynomial::new(rx.clone()).evals(); let (evals_A, evals_B, evals_C) = inst.compute_eval_table_sparse(inst.get_num_cons(), z.len(), &evals_rx); assert_eq!(evals_A.len(), evals_B.len()); assert_eq!(evals_A.len(), evals_C.len()); (0..evals_A.len()) .map(|i| r_A * evals_A[i] + r_B * evals_B[i] + r_C * evals_C[i]) .collect::>() }; // another instance of the sum-check protocol let (sc_proof_phase2, ry, claims_phase2, blind_claim_postsc2) = R1CSProof::prove_phase_two( num_rounds_y, &claim_phase2, &blind_claim_phase2, &mut DensePolynomial::new(z), &mut DensePolynomial::new(evals_ABC), &gens.gens_sc, transcript, random_tape, ); timer_sc_proof_phase2.stop(); let timer_polyeval = Timer::new("polyeval"); let eval_vars_at_ry = poly_vars.evaluate(&ry[1..].to_vec()); let blind_eval = random_tape.random_scalar(b"blind_eval"); let (proof_eval_vars_at_ry, comm_vars_at_ry) = PolyEvalProof::prove( &poly_vars, Some(&blinds_vars), &ry[1..].to_vec(), &eval_vars_at_ry, Some(&blind_eval), &gens.gens_pc, transcript, random_tape, ); timer_polyeval.stop(); // prove the final step of sum-check #2 let blind_eval_Z_at_ry = (Scalar::one() - ry[0]) * blind_eval; let blind_expected_claim_postsc2 = claims_phase2[1] * blind_eval_Z_at_ry; let claim_post_phase2 = claims_phase2[0] * claims_phase2[1]; let (proof_eq_sc_phase2, _C1, _C2) = EqualityProof::prove( &gens.gens_pc.gens.gens_1, transcript, random_tape, &claim_post_phase2, &blind_expected_claim_postsc2, &claim_post_phase2, &blind_claim_postsc2, ); timer_prove.stop(); ( R1CSProof { comm_vars, sc_proof_phase1, claims_phase2: ( comm_Az_claim, comm_Bz_claim, comm_Cz_claim, comm_prod_Az_Bz_claims, ), pok_claims_phase2: (pok_Cz_claim, proof_prod), proof_eq_sc_phase1, sc_proof_phase2, comm_vars_at_ry, proof_eval_vars_at_ry, proof_eq_sc_phase2, }, rx, ry, ) } pub fn verify( &self, num_vars: usize, num_cons: usize, input: &[Scalar], evals: &R1CSInstanceEvals, transcript: &mut Transcript, gens: &R1CSGens, ) -> Result<(Vec, Vec), ProofVerifyError> { transcript.append_protocol_name(R1CSProof::protocol_name()); let n = num_vars; // add the commitment to the verifier's transcript self .comm_vars .append_to_transcript(b"poly_commitment", transcript); let (num_rounds_x, num_rounds_y) = (num_cons.log2(), (2 * num_vars).log2()); // derive the verifier's challenge tau let tau = transcript.challenge_vector(b"challenge_tau", num_rounds_x); // verify the first sum-check instance let claim_phase1 = Scalar::zero() .commit(&Scalar::zero(), &gens.gens_sc.gens_1) .compress(); let (comm_claim_post_phase1, rx) = self .sc_proof_phase1 .verify( &claim_phase1, num_rounds_x, 3, &gens.gens_sc.gens_1, &gens.gens_sc.gens_4, transcript, ) .unwrap(); // perform the intermediate sum-check test with claimed Az, Bz, and Cz let (comm_Az_claim, comm_Bz_claim, comm_Cz_claim, comm_prod_Az_Bz_claims) = &self.claims_phase2; let (pok_Cz_claim, proof_prod) = &self.pok_claims_phase2; assert!(pok_Cz_claim .verify(&gens.gens_sc.gens_1, transcript, &comm_Cz_claim) .is_ok()); assert!(proof_prod .verify( &gens.gens_sc.gens_1, transcript, &comm_Az_claim, &comm_Bz_claim, &comm_prod_Az_Bz_claims ) .is_ok()); comm_Az_claim.append_to_transcript(b"comm_Az_claim", transcript); comm_Bz_claim.append_to_transcript(b"comm_Bz_claim", transcript); comm_Cz_claim.append_to_transcript(b"comm_Cz_claim", transcript); comm_prod_Az_Bz_claims.append_to_transcript(b"comm_prod_Az_Bz_claims", transcript); let taus_bound_rx: Scalar = (0..rx.len()) .map(|i| rx[i] * tau[i] + (Scalar::one() - rx[i]) * (Scalar::one() - tau[i])) .product(); let expected_claim_post_phase1 = (taus_bound_rx * (comm_prod_Az_Bz_claims.decompress().unwrap() - comm_Cz_claim.decompress().unwrap())) .compress(); // verify proof that expected_claim_post_phase1 == claim_post_phase1 assert!(self .proof_eq_sc_phase1 .verify( &gens.gens_sc.gens_1, transcript, &expected_claim_post_phase1, &comm_claim_post_phase1, ) .is_ok()); // derive three public challenges and then derive a joint claim let r_A = transcript.challenge_scalar(b"challenege_Az"); let r_B = transcript.challenge_scalar(b"challenege_Bz"); let r_C = transcript.challenge_scalar(b"challenege_Cz"); // r_A * comm_Az_claim + r_B * comm_Bz_claim + r_C * comm_Cz_claim; let comm_claim_phase2 = GroupElement::vartime_multiscalar_mul( iter::once(&r_A) .chain(iter::once(&r_B)) .chain(iter::once(&r_C)), iter::once(&comm_Az_claim) .chain(iter::once(&comm_Bz_claim)) .chain(iter::once(&comm_Cz_claim)) .map(|pt| pt.decompress().unwrap()) .collect::>(), ) .compress(); // verify the joint claim with a sum-check protocol let (comm_claim_post_phase2, ry) = self .sc_proof_phase2 .verify( &comm_claim_phase2, num_rounds_y, 2, &gens.gens_sc.gens_1, &gens.gens_sc.gens_3, transcript, ) .unwrap(); // verify Z(ry) proof against the initial commitment assert!(self .proof_eval_vars_at_ry .verify( &gens.gens_pc, transcript, &ry[1..].to_vec(), &self.comm_vars_at_ry, &self.comm_vars ) .is_ok()); let poly_input_eval = { // constant term let mut input_as_sparse_poly_entries = vec![SparsePolyEntry::new(0, Scalar::one())]; //remaining inputs input_as_sparse_poly_entries.extend( (0..input.len()) .map(|i| SparsePolyEntry::new(i + 1, input[i])) .collect::>(), ); SparsePolynomial::new(n.log2(), input_as_sparse_poly_entries).evaluate(&ry[1..].to_vec()) }; // compute commitment to eval_Z_at_ry = (Scalar::one() - ry[0]) * self.eval_vars_at_ry + ry[0] * poly_input_eval let comm_eval_Z_at_ry = GroupElement::vartime_multiscalar_mul( iter::once(Scalar::one() - ry[0]).chain(iter::once(ry[0])), iter::once(&self.comm_vars_at_ry.decompress().unwrap()).chain(iter::once( &poly_input_eval.commit(&Scalar::zero(), &gens.gens_pc.gens.gens_1), )), ); // perform the final check in the second sum-check protocol let (eval_A_r, eval_B_r, eval_C_r) = evals.get_evaluations(); let expected_claim_post_phase2 = ((r_A * eval_A_r + r_B * eval_B_r + r_C * eval_C_r) * comm_eval_Z_at_ry).compress(); // verify proof that expected_claim_post_phase1 == claim_post_phase1 assert!(self .proof_eq_sc_phase2 .verify( &gens.gens_sc.gens_1, transcript, &expected_claim_post_phase2, &comm_claim_post_phase2, ) .is_ok()); Ok((rx, ry)) } } #[cfg(test)] mod tests { use super::*; use rand::rngs::OsRng; fn produce_tiny_r1cs() -> (R1CSInstance, Vec, Vec) { // three constraints over five variables Z1, Z2, Z3, Z4, and Z5 // rounded to the nearest power of two let num_cons = 128; let num_vars = 256; let num_inputs = 2; // encode the above constraints into three matrices let mut A: Vec = Vec::new(); let mut B: Vec = Vec::new(); let mut C: Vec = Vec::new(); let one = Scalar::one(); // constraint 0 entries // (Z1 + Z2) * I0 - Z3 = 0; A.push(SparseMatEntry::new(0, 0, one)); A.push(SparseMatEntry::new(0, 1, one)); B.push(SparseMatEntry::new(0, num_vars + 1, one)); C.push(SparseMatEntry::new(0, 2, one)); // constraint 1 entries // (Z1 + I1) * (Z3) - Z4 = 0 A.push(SparseMatEntry::new(1, 0, one)); A.push(SparseMatEntry::new(1, num_vars + 2, one)); B.push(SparseMatEntry::new(1, 2, one)); C.push(SparseMatEntry::new(1, 3, one)); // constraint 3 entries // Z5 * 1 - 0 = 0 A.push(SparseMatEntry::new(2, 4, one)); B.push(SparseMatEntry::new(2, num_vars, one)); let num_vars_x = num_cons.log2(); let num_vars_y = (2 * num_vars).log2(); let poly_A = SparseMatPolynomial::new(num_vars_x, num_vars_y, A); let poly_B = SparseMatPolynomial::new(num_vars_x, num_vars_y, B); let poly_C = SparseMatPolynomial::new(num_vars_x, num_vars_y, C); let inst = R1CSInstance::new(num_cons, num_vars, num_inputs, poly_A, poly_B, poly_C); // compute a satisfying assignment let mut csprng: OsRng = OsRng; let i0 = Scalar::random(&mut csprng); let i1 = Scalar::random(&mut csprng); let z1 = Scalar::random(&mut csprng); let z2 = Scalar::random(&mut csprng); let z3 = (z1 + z2) * i0; // constraint 1: (Z1 + Z2) * I0 - Z3 = 0; let z4 = (z1 + i1) * z3; // constraint 2: (Z1 + I1) * (Z3) - Z4 = 0 let z5 = Scalar::zero(); //constraint 3 let mut vars = vec![Scalar::zero(); num_vars]; vars[0] = z1; vars[1] = z2; vars[2] = z3; vars[3] = z4; vars[4] = z5; let mut input = vec![Scalar::zero(); num_inputs]; input[0] = i0; input[1] = i1; (inst, vars, input) } #[test] fn test_tiny_r1cs() { let (inst, vars, input) = tests::produce_tiny_r1cs(); let is_sat = inst.is_sat(&vars, &input); assert_eq!(is_sat, true); } #[test] fn test_synthetic_r1cs() { let (inst, vars, input) = R1CSInstance::produce_synthetic_r1cs(1024, 1024, 10); let is_sat = inst.is_sat(&vars, &input); assert_eq!(is_sat, true); } #[test] pub fn check_r1cs_proof() { let num_vars = 1024; let num_cons = num_vars; let num_inputs = 10; let (inst, vars, input) = R1CSInstance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs); let gens = R1CSGens::new(num_cons, num_vars, b"test-m"); let mut random_tape = RandomTape::new(b"proof"); let mut prover_transcript = Transcript::new(b"example"); let (proof, rx, ry) = R1CSProof::prove( &inst, vars, &input, &gens, &mut prover_transcript, &mut random_tape, ); let eval_table_rx = EqPolynomial::new(rx).evals(); let eval_table_ry = EqPolynomial::new(ry).evals(); let inst_evals = inst.evaluate_with_tables(&eval_table_rx, &eval_table_ry); let mut verifier_transcript = Transcript::new(b"example"); assert!(proof .verify( inst.get_num_vars(), inst.get_num_cons(), &input, &inst_evals, &mut verifier_transcript, &gens, ) .is_ok()); } }