diff --git a/Cargo.toml b/Cargo.toml index 2e295c4..c267818 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] -members = [ "algebra", "ff-fft", "r1cs-core", "r1cs-std", "gm17", "crypto-primitives", "dpc", "bench-utils" ] +members = [ "algebra", "ff-fft", "r1cs-core", "r1cs-std", "groth16", "gm17", "crypto-primitives", "dpc", "bench-utils" ] [profile.release] opt-level = 3 diff --git a/crypto-primitives/Cargo.toml b/crypto-primitives/Cargo.toml index 2152a8b..3b937c8 100644 --- a/crypto-primitives/Cargo.toml +++ b/crypto-primitives/Cargo.toml @@ -13,7 +13,7 @@ description = "A library of cryptographic primitives that are used by Zexe" homepage = "https://libzexe.org" repository = "https://github.com/scipr/zexe" documentation = "https://docs.rs/crypto-primitives/" -keywords = ["r1cs", "gm17", "pedersen", "blake2s"] +keywords = ["r1cs", "groth16", "gm17", "pedersen", "blake2s"] categories = ["cryptography"] include = ["Cargo.toml", "src", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] license = "MIT/Apache-2.0" @@ -26,6 +26,7 @@ algebra = { path = "../algebra" } r1cs-core = { path = "../r1cs-core", optional = true } r1cs-std = { path = "../r1cs-std", optional = true } gm17 = { path = "../gm17", optional = true } +groth16 = { path = "../groth16", optional = true } bench-utils = { path = "../bench-utils" } digest = "0.7" diff --git a/crypto-primitives/src/nizk/groth16/constraints.rs b/crypto-primitives/src/nizk/groth16/constraints.rs new file mode 100644 index 0000000..c740fd4 --- /dev/null +++ b/crypto-primitives/src/nizk/groth16/constraints.rs @@ -0,0 +1,500 @@ +use crate::nizk::{groth16::Groth16, NIZKVerifierGadget}; +use algebra::{Field, ToConstraintField, AffineCurve, PairingEngine}; +use r1cs_core::{ConstraintSynthesizer, ConstraintSystem, SynthesisError}; +use r1cs_std::prelude::*; + +use groth16::{Proof, VerifyingKey}; +use std::{borrow::Borrow, marker::PhantomData}; + +#[derive(Derivative)] +#[derivative(Clone(bound = "P::G1Gadget: Clone, P::G2Gadget: Clone"))] +pub struct ProofGadget< + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget, +> { + pub a: P::G1Gadget, + pub b: P::G2Gadget, + pub c: P::G1Gadget, +} + +#[derive(Derivative)] +#[derivative(Clone( + bound = "P::G1Gadget: Clone, P::GTGadget: Clone, P::G1PreparedGadget: Clone, \ + P::G2PreparedGadget: Clone, " +))] +pub struct VerifyingKeyGadget< + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget, +> { + pub alpha_g1: P::G1Gadget, + pub beta_g2: P::G2Gadget, + pub gamma_g2: P::G2Gadget, + pub delta_g2: P::G2Gadget, + pub gamma_abc_g1: Vec, +} + +impl< + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget, + > VerifyingKeyGadget +{ + pub fn prepare>( + &self, + mut cs: CS, + ) -> Result, SynthesisError> { + let mut cs = cs.ns(|| "Preparing verifying key"); + let alpha_g1_pc = P::prepare_g1(&mut cs.ns(|| "Prepare alpha_g1"), &self.alpha_g1)?; + let beta_g2_pc = P::prepare_g2(&mut cs.ns(|| "Prepare beta_g2"), &self.beta_g2)?; + + let alpha_g1_beta_g2 = P::pairing(&mut cs.ns(|| "Precompute e(alpha_g1, beta_g2)"), alpha_g1_pc, beta_g2_pc)?; + + let gamma_g2_neg = self.gamma_g2.negate(&mut cs.ns(|| "Negate gamma_g2"))?; + let gamma_g2_neg_pc = P::prepare_g2(&mut cs.ns(|| "Prepare gamma_g2_neg"), &gamma_g2_neg)?; + + let delta_g2_neg = self.delta_g2.negate(&mut cs.ns(|| "Negate delta_g2"))?; + let delta_g2_neg_pc = P::prepare_g2(&mut cs.ns(|| "Prepare delta_g2_neg"), &delta_g2_neg)?; + + Ok(PreparedVerifyingKeyGadget { + alpha_g1_beta_g2: alpha_g1_beta_g2, + gamma_g2_neg_pc: gamma_g2_neg_pc, + delta_g2_neg_pc: delta_g2_neg_pc, + gamma_abc_g1: self.gamma_abc_g1.clone(), + }) + } +} + +#[derive(Derivative)] +#[derivative(Clone( + bound = "P::G1Gadget: Clone, P::GTGadget: Clone, P::G1PreparedGadget: Clone, \ + P::G2PreparedGadget: Clone, " +))] +pub struct PreparedVerifyingKeyGadget< + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget, +> { + pub alpha_g1_beta_g2: P::GTGadget, + pub gamma_g2_neg_pc: P::G2PreparedGadget, + pub delta_g2_neg_pc: P::G2PreparedGadget, + pub gamma_abc_g1: Vec, +} + +pub struct Groth16VerifierGadget +where + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget, +{ + _pairing_engine: PhantomData, + _engine: PhantomData, + _pairing_gadget: PhantomData

, +} + +impl NIZKVerifierGadget, ConstraintF> + for Groth16VerifierGadget +where + PairingE: PairingEngine, + ConstraintF: Field, + C: ConstraintSynthesizer, + V: ToConstraintField, + P: PairingGadget, +{ + type VerificationKeyGadget = VerifyingKeyGadget; + type ProofGadget = ProofGadget; + + fn check_verify<'a, CS, I, T>( + mut cs: CS, + vk: &Self::VerificationKeyGadget, + mut public_inputs: I, + proof: &Self::ProofGadget, + ) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + I: Iterator, + T: 'a + ToBitsGadget + ?Sized, + { + let pvk = vk.prepare(&mut cs.ns(|| "Prepare vk"))?; + + let g_ic = { + let mut cs = cs.ns(|| "Process input"); + let mut g_ic = pvk.gamma_abc_g1[0].clone(); + let mut input_len = 1; + for (i, (input, b)) in public_inputs + .by_ref() + .zip(pvk.gamma_abc_g1.iter().skip(1)) + .enumerate() + { + let input_bits = input.to_bits(cs.ns(|| format!("Input {}", i)))?; + g_ic = b.mul_bits(cs.ns(|| format!("Mul {}", i)), &g_ic, input_bits.iter())?; + input_len += 1; + } + // Check that the input and the query in the verification are of the + // same length. + assert!(input_len == pvk.gamma_abc_g1.len() && public_inputs.next().is_none()); + g_ic + }; + + let test_exp = { + let proof_a_prep = P::prepare_g1(cs.ns(|| "Prepare proof a"), &proof.a)?; + let proof_b_prep = P::prepare_g2(cs.ns(|| "Prepare proof b"), &proof.b)?; + let proof_c_prep = P::prepare_g1(cs.ns(|| "Prepare proof c"), &proof.c)?; + + let g_ic_prep = P::prepare_g1(cs.ns(|| "Prepare g_ic"), &g_ic)?; + + P::miller_loop( + cs.ns(|| "Miller loop 1"), + &[ + proof_a_prep, + g_ic_prep, + proof_c_prep, + ], + &[ + proof_b_prep, + pvk.gamma_g2_neg_pc.clone(), + pvk.delta_g2_neg_pc.clone(), + ], + )? + }; + + let test = P::final_exponentiation(cs.ns(|| "Final Exp"), &test_exp).unwrap(); + + test.enforce_equal(cs.ns(|| "Test 1"), &pvk.alpha_g1_beta_g2)?; + Ok(()) + } +} + +impl AllocGadget, ConstraintF> + for VerifyingKeyGadget +where + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget, +{ + #[inline] + fn alloc>( + mut cs: CS, + value_gen: FN, + ) -> Result + where + FN: FnOnce() -> Result, + T: Borrow>, + { + value_gen().and_then(|vk| { + let VerifyingKey { + alpha_g1, + beta_g2, + gamma_g2, + delta_g2, + gamma_abc_g1, + } = vk.borrow().clone(); + let alpha_g1 = P::G1Gadget::alloc(cs.ns(|| "alpha_g1"), || Ok(alpha_g1.into_projective()))?; + let beta_g2 = + P::G2Gadget::alloc(cs.ns(|| "beta_g2"), || Ok(beta_g2.into_projective()))?; + let gamma_g2 = + P::G2Gadget::alloc(cs.ns(|| "gamma_g2"), || Ok(gamma_g2.into_projective()))?; + let delta_g2 = + P::G2Gadget::alloc(cs.ns(|| "delta_g2"), || Ok(delta_g2.into_projective()))?; + + let gamma_abc_g1 = gamma_abc_g1 + .into_iter() + .enumerate() + .map(|(i, gamma_abc_i)| { + P::G1Gadget::alloc(cs.ns(|| format!("gamma_abc_{}", i)), || { + Ok(gamma_abc_i.into_projective()) + }) + }) + .collect::>() + .into_iter() + .collect::>()?; + Ok(Self { + alpha_g1, + beta_g2, + gamma_g2, + delta_g2, + gamma_abc_g1, + }) + }) + } + + #[inline] + fn alloc_input>( + mut cs: CS, + value_gen: FN, + ) -> Result + where + FN: FnOnce() -> Result, + T: Borrow>, + { + value_gen().and_then(|vk| { + let VerifyingKey { + alpha_g1, + beta_g2, + gamma_g2, + delta_g2, + gamma_abc_g1, + } = vk.borrow().clone(); + let alpha_g1 = P::G1Gadget::alloc_input(cs.ns(|| "alpha_g1"), || Ok(alpha_g1.into_projective()))?; + let beta_g2 = + P::G2Gadget::alloc_input(cs.ns(|| "beta_g2"), || Ok(beta_g2.into_projective()))?; + let gamma_g2 = + P::G2Gadget::alloc_input(cs.ns(|| "gamma_g2"), || Ok(gamma_g2.into_projective()))?; + let delta_g2 = + P::G2Gadget::alloc_input(cs.ns(|| "delta_g2"), || Ok(delta_g2.into_projective()))?; + + let gamma_abc_g1 = gamma_abc_g1 + .into_iter() + .enumerate() + .map(|(i, gamma_abc_i)| { + P::G1Gadget::alloc_input(cs.ns(|| format!("gamma_abc_{}", i)), || { + Ok(gamma_abc_i.into_projective()) + }) + }) + .collect::>() + .into_iter() + .collect::>()?; + + Ok(Self { + alpha_g1, + beta_g2, + gamma_g2, + delta_g2, + gamma_abc_g1, + }) + }) + } +} + +impl AllocGadget, ConstraintF> + for ProofGadget +where + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget, +{ + #[inline] + fn alloc>( + mut cs: CS, + value_gen: FN, + ) -> Result + where + FN: FnOnce() -> Result, + T: Borrow>, + { + value_gen().and_then(|proof| { + let Proof { a, b, c } = proof.borrow().clone(); + let a = P::G1Gadget::alloc_checked(cs.ns(|| "a"), || Ok(a.into_projective()))?; + let b = P::G2Gadget::alloc_checked(cs.ns(|| "b"), || Ok(b.into_projective()))?; + let c = P::G1Gadget::alloc_checked(cs.ns(|| "c"), || Ok(c.into_projective()))?; + Ok(Self { a, b, c }) + }) + } + + #[inline] + fn alloc_input>( + mut cs: CS, + value_gen: FN, + ) -> Result + where + FN: FnOnce() -> Result, + T: Borrow>, + { + value_gen().and_then(|proof| { + let Proof { a, b, c } = proof.borrow().clone(); + // We don't need to check here because the prime order check can be performed + // in plain. + let a = P::G1Gadget::alloc_input(cs.ns(|| "a"), || Ok(a.into_projective()))?; + let b = P::G2Gadget::alloc_input(cs.ns(|| "b"), || Ok(b.into_projective()))?; + let c = P::G1Gadget::alloc_input(cs.ns(|| "c"), || Ok(c.into_projective()))?; + Ok(Self { a, b, c }) + }) + } +} + +impl ToBytesGadget + for VerifyingKeyGadget +where + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget, +{ + #[inline] + fn to_bytes>( + &self, + mut cs: CS, + ) -> Result, SynthesisError> { + let mut bytes = Vec::new(); + bytes.extend_from_slice(&self.alpha_g1.to_bytes(&mut cs.ns(|| "alpha_g1 to bytes"))?); + bytes.extend_from_slice( + &self + .beta_g2 + .to_bytes(&mut cs.ns(|| "beta_g2 to bytes"))?, + ); + bytes.extend_from_slice( + &self + .gamma_g2 + .to_bytes(&mut cs.ns(|| "gamma_g2 to bytes"))?, + ); + bytes.extend_from_slice( + &self + .delta_g2 + .to_bytes(&mut cs.ns(|| "delta_g2 to bytes"))?, + ); + for (i, g) in self.gamma_abc_g1.iter().enumerate() { + let mut cs = cs.ns(|| format!("Iteration {}", i)); + bytes.extend_from_slice(&g.to_bytes(&mut cs.ns(|| "g"))?); + } + Ok(bytes) + } + + fn to_bytes_strict>( + &self, + cs: CS, + ) -> Result, SynthesisError> { + self.to_bytes(cs) + } +} + +#[cfg(test)] +mod test { + use groth16::*; + use r1cs_core::{ConstraintSynthesizer, ConstraintSystem, SynthesisError}; + + use super::*; + use algebra::{ + curves::bls12_377::Bls12_377, + fields::bls12_377::Fr, + fields::bls12_377::Fq, + BitIterator, PrimeField, + }; + use rand::{thread_rng, Rng}; + use r1cs_std::{ + boolean::Boolean, pairing::bls12_377::PairingGadget as Bls12_377PairingGadget, + test_constraint_system::TestConstraintSystem, + }; + + type TestProofSystem = Groth16, Fr>; + type TestVerifierGadget = Groth16VerifierGadget; + type TestProofGadget = ProofGadget; + type TestVkGadget = VerifyingKeyGadget; + + struct Bench { + inputs: Vec>, + num_constraints: usize, + } + + impl ConstraintSynthesizer for Bench { + fn generate_constraints>(self, cs: &mut CS) -> Result<(), SynthesisError> { + assert!(self.inputs.len() >= 2); + assert!(self.num_constraints >= self.inputs.len()); + + let mut variables: Vec<_> = Vec::with_capacity(self.inputs.len()); + for (i, input) in self.inputs.into_iter().enumerate() { + let input_var = cs.alloc_input( + || format!("Input {}", i), + || input.ok_or(SynthesisError::AssignmentMissing), + )?; + variables.push((input, input_var)); + } + + for i in 0..self.num_constraints { + let new_entry = { + let (input_1_val, input_1_var) = variables[i]; + let (input_2_val, input_2_var) = variables[i + 1]; + let result_val = input_1_val + .and_then(|input_1| input_2_val.map(|input_2| input_1 * &input_2)); + let result_var = cs.alloc( + || format!("Result {}", i), + || result_val.ok_or(SynthesisError::AssignmentMissing), + )?; + cs.enforce( + || format!("Enforce constraint {}", i), + |lc| lc + input_1_var, + |lc| lc + input_2_var, + |lc| lc + result_var, + ); + (result_val, result_var) + }; + variables.push(new_entry); + } + Ok(()) + } + } + + #[test] + fn groth16_verifier_test() { + let num_inputs = 100; + let num_constraints = num_inputs; + let rng = &mut thread_rng(); + let mut inputs: Vec> = Vec::with_capacity(num_inputs); + for _ in 0..num_inputs { + inputs.push(Some(rng.gen())); + } + let params = { + let c = Bench:: { + inputs: vec![None; num_inputs], + num_constraints, + }; + + generate_random_parameters(c, rng).unwrap() + }; + + { + let proof = { + // Create an instance of our circuit (with the + // witness) + let c = Bench { + inputs: inputs.clone(), + num_constraints, + }; + // Create a groth16 proof with our parameters. + create_random_proof(c, ¶ms, rng).unwrap() + }; + + // assert!(!verify_proof(&pvk, &proof, &[a]).unwrap()); + let mut cs = TestConstraintSystem::::new(); + + let inputs: Vec<_> = inputs.into_iter().map(|input| input.unwrap()).collect(); + let mut input_gadgets = Vec::new(); + + { + let mut cs = cs.ns(|| "Allocate Input"); + for (i, input) in inputs.into_iter().enumerate() { + let mut input_bits = BitIterator::new(input.into_repr()).collect::>(); + // Input must be in little-endian, but BitIterator outputs in big-endian. + input_bits.reverse(); + + let input_bits = + Vec::::alloc_input(cs.ns(|| format!("Input {}", i)), || { + Ok(input_bits) + }) + .unwrap(); + input_gadgets.push(input_bits); + } + } + + let vk_gadget = TestVkGadget::alloc_input(cs.ns(|| "Vk"), || Ok(¶ms.vk)).unwrap(); + let proof_gadget = + TestProofGadget::alloc(cs.ns(|| "Proof"), || Ok(proof.clone())).unwrap(); + println!("Time to verify!\n\n\n\n"); + >::check_verify( + cs.ns(|| "Verify"), + &vk_gadget, + input_gadgets.iter(), + &proof_gadget, + ) + .unwrap(); + if !cs.is_satisfied() { + println!("========================================================="); + println!("Unsatisfied constraints:"); + println!("{:?}", cs.which_is_unsatisfied().unwrap()); + println!("========================================================="); + } + + // cs.print_named_objects(); + assert!(cs.is_satisfied()); + } + } +} diff --git a/crypto-primitives/src/nizk/groth16/mod.rs b/crypto-primitives/src/nizk/groth16/mod.rs new file mode 100644 index 0000000..09d5577 --- /dev/null +++ b/crypto-primitives/src/nizk/groth16/mod.rs @@ -0,0 +1,81 @@ +use algebra::PairingEngine; +use crate::Error; +use rand::Rng; +use groth16::{ + create_random_proof, generate_random_parameters, prepare_verifying_key, verify_proof, + Parameters, PreparedVerifyingKey, Proof, VerifyingKey, +}; +use r1cs_core::ConstraintSynthesizer; + +use algebra::ToConstraintField; +use std::marker::PhantomData; + +use super::NIZK; + +#[cfg(feature = "r1cs")] +pub mod constraints; + +/// Note: V should serialize its contents to `Vec` in the same order as +/// during the constraint generation. +pub struct Groth16, V: ToConstraintField + ?Sized> { + #[doc(hidden)] + _engine: PhantomData, + #[doc(hidden)] + _circuit: PhantomData, + #[doc(hidden)] + _verifier_input: PhantomData, +} + +impl, V: ToConstraintField + ?Sized> NIZK for Groth16 { + type Circuit = C; + type AssignedCircuit = C; + type ProvingParameters = Parameters; + type VerificationParameters = VerifyingKey; + type PreparedVerificationParameters = PreparedVerifyingKey; + type VerifierInput = V; + type Proof = Proof; + + fn setup( + circuit: Self::Circuit, + rng: &mut R, + ) -> Result< + ( + Self::ProvingParameters, + Self::PreparedVerificationParameters, + ), + Error, + > { + let nizk_time = start_timer!(|| "{Groth-Maller 2017}::Setup"); + let pp = generate_random_parameters::(circuit, rng)?; + let vk = prepare_verifying_key(&pp.vk); + end_timer!(nizk_time); + Ok((pp, vk)) + } + + fn prove( + pp: &Self::ProvingParameters, + input_and_witness: Self::AssignedCircuit, + rng: &mut R, + ) -> Result { + let proof_time = start_timer!(|| "{Groth-Maller 2017}::Prove"); + let result = create_random_proof::(input_and_witness, pp, rng)?; + end_timer!(proof_time); + Ok(result) + } + + fn verify( + vk: &Self::PreparedVerificationParameters, + input: &Self::VerifierInput, + proof: &Self::Proof, + ) -> Result { + let verify_time = start_timer!(|| "{Groth-Maller 2017}::Verify"); + let conversion_time = start_timer!(|| "Convert input to E::Fr"); + let input = input.to_field_elements()?; + end_timer!(conversion_time); + let verification = start_timer!(|| format!("Verify proof w/ input len: {}", input.len())); + let result = verify_proof(&vk, proof, &input)?; + end_timer!(verification); + end_timer!(verify_time); + Ok(result) + } +} diff --git a/crypto-primitives/src/nizk/mod.rs b/crypto-primitives/src/nizk/mod.rs index c5645e1..048c51c 100644 --- a/crypto-primitives/src/nizk/mod.rs +++ b/crypto-primitives/src/nizk/mod.rs @@ -6,6 +6,11 @@ pub mod gm17; #[cfg(feature = "gm17")] pub use self::gm17::Gm17; +#[cfg(feature = "groth16")] +pub mod groth16; +#[cfg(feature = "groth16")] +pub use self::groth16::Groth16; + #[cfg(feature = "r1cs")] pub mod constraints; #[cfg(feature = "r1cs")]