use std::cmp::max; use crate::errors::ProofVerifyError; use crate::r1csproof::R1CSVerifierProof; use crate::{ poseidon_transcript::PoseidonTranscript, r1csproof::{R1CSGens, R1CSProof}, transcript::Transcript, InputsAssignment, Instance, VarsAssignment, }; use ark_crypto_primitives::sponge::poseidon::PoseidonConfig; use ark_crypto_primitives::sponge::Absorb; use ark_ec::pairing::Pairing; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; #[derive(Debug, CanonicalSerialize, CanonicalDeserialize)] // TestudoNizk is suitable for uniform circuits where the // evaluation of R1CS matrices A, B and C is cheap and can // be done by the verifier. For more complex circuits this // operation has to be offloaded to the prover. pub struct TestudoNizk { pub r1cs_verifier_proof: R1CSVerifierProof, pub r: (Vec, Vec), } pub struct TestudoNizkGens { gens_r1cs_sat: R1CSGens, } impl TestudoNizkGens { /// Performs the setup required by the polynomial commitment PST and Groth16 pub fn setup( num_cons: usize, num_vars: usize, num_inputs: usize, poseidon: PoseidonConfig, ) -> Self { // ensure num_vars is a power of 2 let num_vars_padded = { let mut num_vars_padded = max(num_vars, num_inputs + 1); if num_vars_padded != num_vars_padded.next_power_of_two() { num_vars_padded = num_vars_padded.next_power_of_two(); } num_vars_padded }; let num_cons_padded = { let mut num_cons_padded = num_cons; // ensure that num_cons_padded is at least 2 if num_cons_padded == 0 || num_cons_padded == 1 { num_cons_padded = 2; } // ensure that num_cons_padded is a power of 2 if num_cons.next_power_of_two() != num_cons { num_cons_padded = num_cons.next_power_of_two(); } num_cons_padded }; let gens_r1cs_sat = R1CSGens::setup( b"gens_r1cs_sat", num_cons_padded, num_vars_padded, num_inputs, poseidon, ); TestudoNizkGens { gens_r1cs_sat } } } impl TestudoNizk where E::ScalarField: Absorb, { // Returns a proof that the R1CS instance is satisfiable pub fn prove( inst: &Instance, vars: VarsAssignment, inputs: &InputsAssignment, gens: &TestudoNizkGens, transcript: &mut PoseidonTranscript, poseidon: PoseidonConfig, ) -> Result, ProofVerifyError> { transcript.append_bytes(b"", &inst.digest); let c: E::ScalarField = transcript.challenge_scalar(b""); transcript.new_from_state(&c); // we might need to pad variables let padded_vars = { let num_padded_vars = inst.inst.get_num_vars(); let num_vars = vars.assignment.len(); if num_padded_vars > num_vars { vars.pad(num_padded_vars) } else { vars } }; let (r1cs_sat_proof, rx, ry) = R1CSProof::prove( &inst.inst, padded_vars.assignment, &inputs.assignment, &gens.gens_r1cs_sat, transcript, ); let inst_evals = inst.inst.evaluate(&rx, &ry); transcript.new_from_state(&c); let r1cs_verifier_proof = r1cs_sat_proof .prove_verifier( inst.inst.get_num_vars(), inst.inst.get_num_cons(), &inputs.assignment, &inst_evals, transcript, &gens.gens_r1cs_sat, poseidon, ) .unwrap(); Ok(TestudoNizk { r1cs_verifier_proof, r: (rx, ry), }) } // Verifies the satisfiability proof for the R1CS instance. In NIZK mode, the // verifier evaluates matrices A, B and C themselves, which is a linear // operation and hence this is not a SNARK. // However, for highly structured circuits this operation is fast. pub fn verify( &self, gens: &TestudoNizkGens, inst: &Instance, input: &InputsAssignment, transcript: &mut PoseidonTranscript, _poseidon: PoseidonConfig, ) -> Result { transcript.append_bytes(b"", &inst.digest); let (claimed_rx, claimed_ry) = &self.r; let inst_evals = inst.inst.evaluate(claimed_rx, claimed_ry); let sat_verified = self.r1cs_verifier_proof.verify( (claimed_rx.clone(), claimed_ry.clone()), &input.assignment, &inst_evals, transcript, &gens.gens_r1cs_sat, )?; assert!(sat_verified == true); Ok(sat_verified) } } #[cfg(test)] mod tests { use crate::{ parameters::poseidon_params, poseidon_transcript::PoseidonTranscript, testudo_nizk::{TestudoNizk, TestudoNizkGens}, Instance, }; #[test] pub fn check_testudo_nizk() { let num_vars = 256; let num_cons = num_vars; let num_inputs = 10; type E = ark_bls12_377::Bls12_377; // produce public generators let gens = TestudoNizkGens::::setup(num_cons, num_vars, num_inputs, poseidon_params()); // produce a synthetic R1CSInstance let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs); let params = poseidon_params(); // produce a proof let mut prover_transcript = PoseidonTranscript::new(¶ms); let proof = TestudoNizk::prove(&inst, vars, &inputs, &gens, &mut prover_transcript, params).unwrap(); // verify the proof let mut verifier_transcript = PoseidonTranscript::new(&poseidon_params()); assert!(proof .verify( &gens, &inst, &inputs, &mut verifier_transcript, poseidon_params() ) .is_ok()); } }