From 37f426c97f62c31f44d53ae7ba2392b48a0e63a6 Mon Sep 17 00:00:00 2001 From: Gabriel Fior Date: Tue, 7 Nov 2023 10:46:18 -0300 Subject: [PATCH] Skeleton for Groth16 as part of Sparkling water bootcamp efforts (#612) * Skeleton for Groth16 as part of Sparkling water bootcamp efforts * Solving linting * Making clippy happy * lr-o / t = h with no remainder * having a problem with operate_with_self * diego is a genius * a * alpha shift * delta-shift * introduce pairings * minor renamings * getting serious * rearrangement * toxic waste struct * rearrange * broke-down everything * rearrange * one step forward * passes without shifts * K(s) constructed!!!!!!!! * pairings need to be multiplied * a lot of tests. turn back here if you have trouble * 10 times cleaner * 100x cleaner * 500x cleaner * minor * with and without zk * functional zk-snark * Added MSM to verify part * Moved logic to setup | added simple tests * major rearrangement * another major refactor * organize imports * prover + verifier pippenger * code organization * generate_domain * FFT integration * rng -> chacha * fold -> successors * get rid of is_zk * batch inverse * more functional style code in groth16 setup * powers of tau -> successors * small tweak to qap & prover * serde * serde rearrangement * clippy * offset fft for h polynomial * final * clippy * structurify groth16 prover * padding for the prover * missing newline * Implemented review comments * Fixing clippy * minor renaming * clippy * padding corrected + one more test * clippy * perks --------- Co-authored-by: Irfan Bozkurt Co-authored-by: irfan --- .gitignore | 2 + Cargo.toml | 2 +- provers/groth16/Cargo.toml | 16 +++ provers/groth16/README.md | 3 + provers/groth16/src/common.rs | 40 ++++++ provers/groth16/src/lib.rs | 14 +++ provers/groth16/src/prover.rs | 149 +++++++++++++++++++++++ provers/groth16/src/qap.rs | 118 ++++++++++++++++++ provers/groth16/src/setup.rs | 135 ++++++++++++++++++++ provers/groth16/src/test_circuits/mod.rs | 101 +++++++++++++++ provers/groth16/src/verifier.rs | 22 ++++ provers/groth16/tests/groth16.rs | 52 ++++++++ 12 files changed, 653 insertions(+), 1 deletion(-) create mode 100644 provers/groth16/Cargo.toml create mode 100644 provers/groth16/README.md create mode 100644 provers/groth16/src/common.rs create mode 100644 provers/groth16/src/lib.rs create mode 100644 provers/groth16/src/prover.rs create mode 100644 provers/groth16/src/qap.rs create mode 100644 provers/groth16/src/setup.rs create mode 100644 provers/groth16/src/test_circuits/mod.rs create mode 100644 provers/groth16/src/verifier.rs create mode 100644 provers/groth16/tests/groth16.rs diff --git a/.gitignore b/.gitignore index 3f04df90c..e691dd6c4 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ ensure-no_std/target # Files from fuzzers are inside a corpus folder **/corpus/** **/artifacts/** +/.idea/ + diff --git a/Cargo.toml b/Cargo.toml index 629c99791..bd2d0ec5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["math", "crypto", "gpu", "benches", "provers/plonk", "provers/stark", "provers/cairo", "examples/merkle-tree-cli"] +members = ["math", "crypto", "gpu", "benches", "provers/plonk", "provers/stark", "provers/cairo", "provers/groth16", "examples/merkle-tree-cli"] exclude = ["ensure-no_std"] resolver = "2" diff --git a/provers/groth16/Cargo.toml b/provers/groth16/Cargo.toml new file mode 100644 index 000000000..557dc4fe1 --- /dev/null +++ b/provers/groth16/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "lambdaworks-groth16" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lambdaworks-math.workspace = true +lambdaworks-crypto.workspace = true +rand_chacha = "0.3.1" +serde = "1.0" +serde_json = "1.0" +rand = "0.8.5" diff --git a/provers/groth16/README.md b/provers/groth16/README.md new file mode 100644 index 000000000..101de44b3 --- /dev/null +++ b/provers/groth16/README.md @@ -0,0 +1,3 @@ +# Lambdaworks Groth16 Prover + +An incomplete and unoptimized implementation of the [Groth16](https://eprint.iacr.org/2016/260) protocol. diff --git a/provers/groth16/src/common.rs b/provers/groth16/src/common.rs new file mode 100644 index 000000000..c4ede834e --- /dev/null +++ b/provers/groth16/src/common.rs @@ -0,0 +1,40 @@ +use lambdaworks_math::{ + elliptic_curve::{ + short_weierstrass::curves::bls12_381::{ + curve::BLS12381Curve, + default_types::{FrElement as FE, FrField as FrF}, + pairing::BLS12381AtePairing, + twist::BLS12381TwistCurve, + }, + traits::{IsEllipticCurve, IsPairing}, + }, + field::element::FieldElement, + unsigned_integer::element::U256, +}; +use rand::{Rng, SeedableRng}; + +pub type Curve = BLS12381Curve; +pub type TwistedCurve = BLS12381TwistCurve; + +pub type FrElement = FE; +pub type FrField = FrF; + +pub type Pairing = BLS12381AtePairing; + +pub type G1Point = ::PointRepresentation; +pub type G2Point = ::PointRepresentation; +pub type PairingOutput = FieldElement<::OutputField>; + +pub const ORDER_R_MINUS_1_ROOT_UNITY: FrElement = FrElement::from_hex_unchecked("7"); + +pub fn sample_fr_elem() -> FrElement { + let mut rng = rand_chacha::ChaCha20Rng::seed_from_u64(9001); + FrElement::new(U256 { + limbs: [ + rng.gen::(), + rng.gen::(), + rng.gen::(), + rng.gen::(), + ], + }) +} diff --git a/provers/groth16/src/lib.rs b/provers/groth16/src/lib.rs new file mode 100644 index 000000000..5ea5579b7 --- /dev/null +++ b/provers/groth16/src/lib.rs @@ -0,0 +1,14 @@ +pub mod common; +pub mod qap; +pub mod test_circuits; + +mod prover; +mod setup; +mod verifier; + +pub use prover::{Proof, Prover}; +pub use qap::QuadraticArithmeticProgram; +pub use setup::{setup, ProvingKey, VerifyingKey}; +pub use verifier::verify; + +pub use test_circuits::*; diff --git a/provers/groth16/src/prover.rs b/provers/groth16/src/prover.rs new file mode 100644 index 000000000..c9c42f620 --- /dev/null +++ b/provers/groth16/src/prover.rs @@ -0,0 +1,149 @@ +use crate::{common::*, ProvingKey, QuadraticArithmeticProgram}; +use lambdaworks_math::errors::DeserializationError; +use lambdaworks_math::traits::{Deserializable, Serializable}; +use lambdaworks_math::{cyclic_group::IsGroup, msm::pippenger::msm}; +use std::mem::size_of; + +pub struct Proof { + pub pi1: G1Point, + pub pi2: G2Point, + pub pi3: G1Point, +} + +impl Proof { + pub fn serialize(&self) -> Vec { + let mut bytes: Vec = Vec::new(); + [ + Self::serialize_commitment(&self.pi1), + Self::serialize_commitment(&self.pi2), + Self::serialize_commitment(&self.pi3), + ] + .iter() + .for_each(|serialized| { + bytes.extend_from_slice(&(serialized.len() as u32).to_be_bytes()); + bytes.extend_from_slice(serialized); + }); + bytes + } + + pub fn deserialize(bytes: &[u8]) -> Result + where + Self: Sized, + { + let (offset, pi1) = Self::deserialize_commitment::(bytes, 0)?; + let (offset, pi2) = Self::deserialize_commitment::(bytes, offset)?; + let (_, pi3) = Self::deserialize_commitment::(bytes, offset)?; + Ok(Self { pi1, pi2, pi3 }) + } + + fn serialize_commitment(cm: &Commitment) -> Vec { + cm.serialize() + } + + // Repetitive. Same as in plonk/src/prover.rs + fn deserialize_commitment( + bytes: &[u8], + offset: usize, + ) -> Result<(usize, Commitment), DeserializationError> { + let mut offset = offset; + let element_size_bytes: [u8; size_of::()] = bytes + .get(offset..offset + size_of::()) + .ok_or(DeserializationError::InvalidAmountOfBytes)? + .try_into() + .map_err(|_| DeserializationError::InvalidAmountOfBytes)?; + let element_size = u32::from_be_bytes(element_size_bytes) as usize; + offset += size_of::(); + let commitment = Commitment::deserialize( + bytes + .get(offset..offset + element_size) + .ok_or(DeserializationError::InvalidAmountOfBytes)?, + )?; + offset += element_size; + Ok((offset, commitment)) + } +} + +pub struct Prover; +impl Prover { + pub fn prove(w: &[FrElement], qap: &QuadraticArithmeticProgram, pk: &ProvingKey) -> Proof { + let h_coefficients = qap + .calculate_h_coefficients(w) + .iter() + .map(|elem| elem.representative()) + .collect::>(); + + let w = w + .iter() + .map(|elem| elem.representative()) + .collect::>(); + + // Sample randomness for hiding + let r = sample_fr_elem(); + let s = sample_fr_elem(); + + // [π_1]_1 + let pi1 = msm(&w, &pk.l_tau_g1) + .unwrap() + .operate_with(&pk.alpha_g1) + .operate_with(&pk.delta_g1.operate_with_self(r.representative())); + + // [π_2]_2 + let pi2 = msm(&w, &pk.r_tau_g2) + .unwrap() + .operate_with(&pk.beta_g2) + .operate_with(&pk.delta_g2.operate_with_self(s.representative())); + + // [ƍ^{-1} * t(τ)*h(τ)]_1 + let t_tau_h_tau_assigned_g1 = msm( + &h_coefficients, + &pk.z_powers_of_tau_g1[..h_coefficients.len()], + ) + .unwrap(); + + // [ƍ^{-1} * (β*l(τ) + α*r(τ) + o(τ))]_1 + let k_tau_assigned_prover_g1 = msm( + &w[qap.num_of_public_inputs..], + &pk.prover_k_tau_g1[..qap.num_of_private_inputs()], + ) + .unwrap(); + + // [π_2]_1 + let pi2_g1 = msm(&w, &pk.r_tau_g1) + .unwrap() + .operate_with(&pk.beta_g1) + .operate_with(&pk.delta_g1.operate_with_self(s.representative())); + + // [π_3]_1 + let pi3 = k_tau_assigned_prover_g1 + .operate_with(&t_tau_h_tau_assigned_g1) + // s[π_1]_1 + .operate_with(&pi1.operate_with_self(s.representative())) + // r[π_2]_1 + .operate_with(&pi2_g1.operate_with_self(r.representative())) + // -rs[ƍ]_1 + .operate_with(&pk.delta_g1.operate_with_self((-(&r * &s)).representative())); + + Proof { pi1, pi2, pi3 } + } +} + +#[cfg(test)] +mod tests { + use lambdaworks_math::elliptic_curve::traits::IsEllipticCurve; + + use super::*; + + #[test] + fn serde() { + let proof = Proof { + pi1: Curve::generator().operate_with_self(sample_fr_elem().representative()), + pi2: TwistedCurve::generator().operate_with_self(sample_fr_elem().representative()), + pi3: Curve::generator().operate_with_self(sample_fr_elem().representative()), + }; + let deserialized_proof = Proof::deserialize(&proof.serialize()).unwrap(); + + assert_eq!(proof.pi1, deserialized_proof.pi1); + assert_eq!(proof.pi2, deserialized_proof.pi2); + assert_eq!(proof.pi3, deserialized_proof.pi3); + } +} diff --git a/provers/groth16/src/qap.rs b/provers/groth16/src/qap.rs new file mode 100644 index 000000000..35bb8f76d --- /dev/null +++ b/provers/groth16/src/qap.rs @@ -0,0 +1,118 @@ +use lambdaworks_math::{fft::polynomial::FFTPoly, polynomial::Polynomial}; + +use crate::common::*; + +#[derive(Debug)] +pub struct QuadraticArithmeticProgram { + pub num_of_public_inputs: usize, + pub l: Vec>, + pub r: Vec>, + pub o: Vec>, +} + +impl QuadraticArithmeticProgram { + pub fn from_variable_matrices( + num_of_public_inputs: usize, + l: &[Vec], + r: &[Vec], + o: &[Vec], + ) -> Self { + let num_of_total_inputs = l.len(); + assert_eq!(num_of_total_inputs, r.len()); + assert_eq!(num_of_total_inputs, o.len()); + assert!(num_of_total_inputs > 0); + assert!(num_of_public_inputs <= num_of_total_inputs); + + let num_of_gates = l[0].len(); + let pad_zeroes = num_of_gates.next_power_of_two() - num_of_gates; + let l = Self::apply_padding(l, pad_zeroes); + let r = Self::apply_padding(r, pad_zeroes); + let o = Self::apply_padding(o, pad_zeroes); + + Self { + num_of_public_inputs, + l: Self::build_variable_polynomials(&l), + r: Self::build_variable_polynomials(&r), + o: Self::build_variable_polynomials(&o), + } + } + + pub fn num_of_gates(&self) -> usize { + self.l[0].degree() + 1 + } + + pub fn num_of_private_inputs(&self) -> usize { + self.l.len() - self.num_of_public_inputs + } + + pub fn num_of_total_inputs(&self) -> usize { + self.l.len() + } + + pub fn calculate_h_coefficients(&self, w: &[FrElement]) -> Vec { + let offset = &ORDER_R_MINUS_1_ROOT_UNITY; + let degree = self.num_of_gates() * 2; + + let [l, r, o] = self.scale_and_accumulate_variable_polynomials(w, degree, offset); + + // TODO: Change to a vector of offsetted evaluations of x^N-1 + let mut t = (Polynomial::new_monomial(FrElement::one(), self.num_of_gates()) + - FrElement::one()) + .evaluate_offset_fft(1, Some(degree), offset) + .unwrap(); + FrElement::inplace_batch_inverse(&mut t).unwrap(); + + let h_evaluated = l + .iter() + .zip(&r) + .zip(&o) + .zip(&t) + .map(|(((l, r), o), t)| (l * r - o) * t) + .collect::>(); + + Polynomial::interpolate_offset_fft(&h_evaluated, offset) + .unwrap() + .coefficients() + .to_vec() + } + + fn apply_padding(columns: &[Vec], pad_zeroes: usize) -> Vec> { + let from_slice = vec![FrElement::zero(); pad_zeroes]; + columns + .iter() + .map(|column| { + let mut new_column = column.clone(); + new_column.extend_from_slice(&from_slice); + new_column + }) + .collect::>() + } + + fn build_variable_polynomials(from_matrix: &[Vec]) -> Vec> { + from_matrix + .iter() + .map(|row| Polynomial::interpolate_fft(row).unwrap()) + .collect() + } + + // Compute A.s by summing up polynomials A[0].s, A[1].s, ..., A[n].s + // In other words, assign the witness coefficients / execution values + // Similarly for B.s and C.s + fn scale_and_accumulate_variable_polynomials( + &self, + w: &[FrElement], + degree: usize, + offset: &FrElement, + ) -> [Vec; 3] { + [&self.l, &self.r, &self.o].map(|var_polynomials| { + var_polynomials + .iter() + .zip(w) + .map(|(poly, coeff)| poly.mul_with_ref(&Polynomial::new_monomial(coeff.clone(), 0))) + .reduce(|poly1, poly2| poly1 + poly2) + .unwrap() + .evaluate_offset_fft(1, Some(degree), offset) + .unwrap() + }) + } +} diff --git a/provers/groth16/src/setup.rs b/provers/groth16/src/setup.rs new file mode 100644 index 000000000..4847d8922 --- /dev/null +++ b/provers/groth16/src/setup.rs @@ -0,0 +1,135 @@ +use crate::{common::*, QuadraticArithmeticProgram}; +use lambdaworks_math::{ + cyclic_group::IsGroup, + elliptic_curve::{ + short_weierstrass::{point::ShortWeierstrassProjectivePoint, traits::IsShortWeierstrass}, + traits::{IsEllipticCurve, IsPairing}, + }, +}; + +pub struct VerifyingKey { + // e([alpha]_1, [beta]_2) computed during setup as it's a constant + pub alpha_g1_times_beta_g2: PairingOutput, + pub delta_g2: G2Point, + pub gamma_g2: G2Point, + // [K_0(τ)]_1, [K_1(τ)]_1, ..., [K_k(τ)]_1 + // where K_i(τ) = γ^{-1} * (β*l(τ) + α*r(τ) + o(τ)) + // and "k" is the number of public inputs + pub verifier_k_tau_g1: Vec, +} + +pub struct ProvingKey { + pub alpha_g1: G1Point, + pub beta_g1: G1Point, + pub beta_g2: G2Point, + pub delta_g1: G1Point, + pub delta_g2: G2Point, + // [A_0(τ)]_1, [A_1(τ)]_1, ..., [A_n(τ)]_1 + pub l_tau_g1: Vec, + // [B_0(τ)]_1, [B_1(τ)]_1, ..., [B_n(τ)]_1 + pub r_tau_g1: Vec, + // [B_0(τ)]_2, [B_1(τ)]_2, ..., [B_n(τ)]_2 + pub r_tau_g2: Vec, + // [K_{k+1}(τ)]_1, [K_{k+2}(τ)]_1, ..., [K_n(τ)]_1 + // where K_i(τ) = ƍ^{-1} * (β*l(τ) + α*r(τ) + o(τ)) + // and "k" is the number of public inputs + pub prover_k_tau_g1: Vec, + // [delta^{-1} * t(τ) * tau^0]_1, [delta^{-1} * t(τ) * τ^1]_1, ..., [delta^{-1} * t(τ) * τ^m]_1 + pub z_powers_of_tau_g1: Vec, +} + +struct ToxicWaste { + tau: FrElement, + alpha: FrElement, + beta: FrElement, + gamma: FrElement, + delta: FrElement, +} + +impl ToxicWaste { + pub fn new() -> Self { + Self { + tau: sample_fr_elem(), + alpha: sample_fr_elem(), + beta: sample_fr_elem(), + gamma: sample_fr_elem(), + delta: sample_fr_elem(), + } + } +} + +pub fn setup(qap: &QuadraticArithmeticProgram) -> (ProvingKey, VerifyingKey) { + let g1: G1Point = Curve::generator(); + let g2: G2Point = TwistedCurve::generator(); + + let tw = ToxicWaste::new(); + + let l_tau: Vec<_> = qap.l.iter().map(|p| p.evaluate(&tw.tau)).collect(); + let r_tau: Vec<_> = qap.r.iter().map(|p| p.evaluate(&tw.tau)).collect(); + + let mut to_be_inversed = [tw.delta.clone(), tw.gamma.clone()]; + FrElement::inplace_batch_inverse(&mut to_be_inversed).unwrap(); + let [delta_inv, gamma_inv] = to_be_inversed; + + let k_tau: Vec<_> = l_tau + .iter() + .zip(&r_tau) + .enumerate() + .map(|(i, (l, r))| { + let unshifted = &tw.beta * l + &tw.alpha * r + &qap.o[i].evaluate(&tw.tau); + if i < qap.num_of_public_inputs { + &gamma_inv * &unshifted + } else { + &delta_inv * &unshifted + } + }) + .collect(); + + let alpha_g1 = g1.operate_with_self(tw.alpha.representative()); + let beta_g2 = g2.operate_with_self(tw.beta.representative()); + + let alpha_g1_times_beta_g2 = Pairing::compute(&alpha_g1, &beta_g2); + + let delta_g2 = g2.operate_with_self(tw.delta.representative()); + + ( + ProvingKey { + alpha_g1, + beta_g1: g1.operate_with_self(tw.beta.representative()), + beta_g2, + delta_g1: g1.operate_with_self(tw.delta.representative()), + delta_g2: delta_g2.clone(), + l_tau_g1: batch_operate(&l_tau, &g1), + r_tau_g1: batch_operate(&r_tau, &g1), + r_tau_g2: batch_operate(&r_tau, &g2), + prover_k_tau_g1: batch_operate(&k_tau[qap.num_of_public_inputs..], &g1), + z_powers_of_tau_g1: batch_operate( + &core::iter::successors( + // Start from delta^{-1} * t(τ) + // Note that t(τ) = (τ^N - 1) because our domain is roots of unity + Some(&delta_inv * (&tw.tau.pow(qap.num_of_gates()) - FrElement::one())), + |prev| Some(prev * &tw.tau), + ) + .take(qap.num_of_gates() * 2) + .collect::>(), + &g1, + ), + }, + VerifyingKey { + alpha_g1_times_beta_g2, + delta_g2, + gamma_g2: g2.operate_with_self(tw.gamma.representative()), + verifier_k_tau_g1: batch_operate(&k_tau[..qap.num_of_public_inputs], &g1), + }, + ) +} + +fn batch_operate( + elems: &[FrElement], + point: &ShortWeierstrassProjectivePoint, +) -> Vec> { + elems + .iter() + .map(|elem| point.operate_with_self(elem.representative())) + .collect() +} diff --git a/provers/groth16/src/test_circuits/mod.rs b/provers/groth16/src/test_circuits/mod.rs new file mode 100644 index 000000000..41e84e24f --- /dev/null +++ b/provers/groth16/src/test_circuits/mod.rs @@ -0,0 +1,101 @@ +use crate::{common::*, QuadraticArithmeticProgram}; + +/* +Represents x^3 + x + 5 = 35, based on https://vitalik.ca/general/2016/12/10/qap.html + sym_1 = x * x + y = sym_1 * x + sym_2 = y + x + ~out = sym_2 + 5 +*/ +pub fn vitalik_qap() -> QuadraticArithmeticProgram { + let num_of_public_inputs = 1; + let [l, r, o] = [ + [ + ["0", "0", "0", "5"], + ["1", "0", "1", "0"], + ["0", "0", "0", "0"], + ["0", "1", "0", "0"], + ["0", "0", "1", "0"], + ["0", "0", "0", "1"], + ], + [ + ["0", "0", "1", "1"], + ["1", "1", "0", "0"], + ["0", "0", "0", "0"], + ["0", "0", "0", "0"], + ["0", "0", "0", "0"], + ["0", "0", "0", "0"], + ], + [ + ["0", "0", "0", "0"], + ["0", "0", "0", "0"], + ["0", "0", "0", "1"], + ["1", "0", "0", "0"], + ["0", "1", "0", "0"], + ["0", "0", "1", "0"], + ], + ] + .map(|matrix| matrix.map(|row| row.map(FrElement::from_hex_unchecked).to_vec())); + QuadraticArithmeticProgram::from_variable_matrices(num_of_public_inputs, &l, &r, &o) +} + +/* +Represents x^2 = 25 or y^2 = 9 + input signal x, y + + sym_1 = x * x -> 25 + sym_2 = y * y -> 9 + + sym_3 = sym_1 - 25 + sym_4 = sym_2 - 9 + + ~out = sym_3 * sym_4 -> needs to be zero +*/// +pub fn test_qap_2() -> QuadraticArithmeticProgram { + let num_of_public_inputs = 2; + let [l, r, o] = [ + [ + ["0", "0", "-19", "-9", "0"], //1 + ["1", "0", "0", "0", "0"], //x + ["0", "1", "0", "0", "0"], //y + ["0", "0", "0", "0", "1"], //~out + ["0", "0", "1", "0", "0"], //sym_1 + ["0", "0", "0", "1", "0"], //sym_2 + ["0", "0", "0", "0", "1"], //sym_3 + ["0", "0", "0", "0", "0"], //sym_4 + ], + [ + ["0", "0", "1", "1", "0"], //1 + ["1", "0", "0", "0", "0"], //x + ["0", "1", "0", "0", "0"], //y + ["0", "0", "0", "0", "0"], //~out + ["0", "0", "0", "0", "0"], //sym_1 + ["0", "0", "0", "0", "0"], //sym_2 + ["0", "0", "0", "0", "0"], //sym_3 + ["0", "0", "0", "0", "1"], //sym_4 + ], + [ + ["0", "0", "0", "0", "0"], //1 + ["0", "0", "0", "0", "0"], //x + ["0", "0", "0", "0", "0"], //y + ["0", "0", "0", "0", "1"], //~out + ["1", "0", "0", "0", "0"], //sym_1 + ["0", "1", "0", "0", "0"], //sym_2 + ["0", "0", "1", "0", "0"], //sym_3 + ["0", "0", "0", "1", "0"], //sym_4 + ], + ] + .map(|matrix| { + matrix.map(|row| { + row.map(|elem| { + if elem.starts_with('-') { + -FrElement::from_hex_unchecked(&elem.chars().skip(1).collect::()) + } else { + FrElement::from_hex_unchecked(elem) + } + }) + .to_vec() + }) + }); + QuadraticArithmeticProgram::from_variable_matrices(num_of_public_inputs, &l, &r, &o) +} diff --git a/provers/groth16/src/verifier.rs b/provers/groth16/src/verifier.rs new file mode 100644 index 000000000..e5619907f --- /dev/null +++ b/provers/groth16/src/verifier.rs @@ -0,0 +1,22 @@ +use lambdaworks_math::{elliptic_curve::traits::IsPairing, msm::pippenger::msm}; + +use crate::common::{FrElement, Pairing}; +use crate::prover::Proof; +use crate::setup::VerifyingKey; + +pub fn verify(vk: &VerifyingKey, proof: &Proof, pub_inputs: &[FrElement]) -> bool { + // [γ^{-1} * (β*l(τ) + α*r(τ) + o(τ))]_1 + let k_tau_assigned_verifier_g1 = msm( + &pub_inputs + .iter() + .map(|elem| elem.representative()) + .collect::>(), + &vk.verifier_k_tau_g1, + ) + .unwrap(); + + Pairing::compute(&proof.pi3, &vk.delta_g2) + * vk.alpha_g1_times_beta_g2.clone() + * Pairing::compute(&k_tau_assigned_verifier_g1, &vk.gamma_g2) + == Pairing::compute(&proof.pi1, &proof.pi2) +} diff --git a/provers/groth16/tests/groth16.rs b/provers/groth16/tests/groth16.rs new file mode 100644 index 000000000..c1b4cc327 --- /dev/null +++ b/provers/groth16/tests/groth16.rs @@ -0,0 +1,52 @@ +use lambdaworks_groth16::{common::*, setup, test_circuits::*, verify, Proof, Prover}; + +#[test] +fn vitalik_1() { + let qap = vitalik_qap(); // x^3 + x + 5 = 35 + + let (pk, vk) = setup(&qap); + + let w = ["0x1", "0x3", "0x23", "0x9", "0x1b", "0x1e"] // x = 3 + .map(FrElement::from_hex_unchecked) + .to_vec(); + + let serialized_proof = Prover::prove(&w, &qap, &pk).serialize(); + let deserialized_proof = Proof::deserialize(&serialized_proof).unwrap(); + + let accept = verify(&vk, &deserialized_proof, &w[..qap.num_of_public_inputs]); + assert!(accept); +} + +#[test] +fn vitalik_2() { + let qap = vitalik_qap(); // x^3 + x + 5 = 35 + + let (pk, vk) = setup(&qap); + + let w = ["0x1", "0x1", "0x7", "0x1", "0x1", "0x2"] // x = 1 + .map(FrElement::from_hex_unchecked) + .to_vec(); + + let serialized_proof = Prover::prove(&w, &qap, &pk).serialize(); + let deserialized_proof = Proof::deserialize(&serialized_proof).unwrap(); + + let accept = verify(&vk, &deserialized_proof, &w[..qap.num_of_public_inputs]); + assert!(accept); +} + +#[test] +fn qap_2() { + let qap = test_qap_2(); + let (pk, vk) = setup(&qap); + + // 1, x, y, ~out, sym_1, sym_2, sym_3, sym_4 + let w = ["0x1", "0x5", "0x3", "0x0", "0x19", "0x9", "0x0", "0x0"] // x = 3 + .map(FrElement::from_hex_unchecked) + .to_vec(); + + let serialized_proof = Prover::prove(&w, &qap, &pk).serialize(); + let deserialized_proof = Proof::deserialize(&serialized_proof).unwrap(); + + let accept = verify(&vk, &deserialized_proof, &w[..qap.num_of_public_inputs]); + assert!(accept); +}