Skip to content

Commit 37f426c

Browse files
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 <[email protected]> Co-authored-by: irfan <[email protected]>
1 parent 2d2f4e3 commit 37f426c

File tree

12 files changed

+653
-1
lines changed

12 files changed

+653
-1
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ ensure-no_std/target
2424
# Files from fuzzers are inside a corpus folder
2525
**/corpus/**
2626
**/artifacts/**
27+
/.idea/
28+

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = ["math", "crypto", "gpu", "benches", "provers/plonk", "provers/stark", "provers/cairo", "examples/merkle-tree-cli"]
2+
members = ["math", "crypto", "gpu", "benches", "provers/plonk", "provers/stark", "provers/cairo", "provers/groth16", "examples/merkle-tree-cli"]
33
exclude = ["ensure-no_std"]
44
resolver = "2"
55

provers/groth16/Cargo.toml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "lambdaworks-groth16"
3+
version.workspace = true
4+
edition.workspace = true
5+
license.workspace = true
6+
repository.workspace = true
7+
8+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9+
10+
[dependencies]
11+
lambdaworks-math.workspace = true
12+
lambdaworks-crypto.workspace = true
13+
rand_chacha = "0.3.1"
14+
serde = "1.0"
15+
serde_json = "1.0"
16+
rand = "0.8.5"

provers/groth16/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Lambdaworks Groth16 Prover
2+
3+
An incomplete and unoptimized implementation of the [Groth16](https://eprint.iacr.org/2016/260) protocol.

provers/groth16/src/common.rs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use lambdaworks_math::{
2+
elliptic_curve::{
3+
short_weierstrass::curves::bls12_381::{
4+
curve::BLS12381Curve,
5+
default_types::{FrElement as FE, FrField as FrF},
6+
pairing::BLS12381AtePairing,
7+
twist::BLS12381TwistCurve,
8+
},
9+
traits::{IsEllipticCurve, IsPairing},
10+
},
11+
field::element::FieldElement,
12+
unsigned_integer::element::U256,
13+
};
14+
use rand::{Rng, SeedableRng};
15+
16+
pub type Curve = BLS12381Curve;
17+
pub type TwistedCurve = BLS12381TwistCurve;
18+
19+
pub type FrElement = FE;
20+
pub type FrField = FrF;
21+
22+
pub type Pairing = BLS12381AtePairing;
23+
24+
pub type G1Point = <BLS12381Curve as IsEllipticCurve>::PointRepresentation;
25+
pub type G2Point = <BLS12381TwistCurve as IsEllipticCurve>::PointRepresentation;
26+
pub type PairingOutput = FieldElement<<Pairing as IsPairing>::OutputField>;
27+
28+
pub const ORDER_R_MINUS_1_ROOT_UNITY: FrElement = FrElement::from_hex_unchecked("7");
29+
30+
pub fn sample_fr_elem() -> FrElement {
31+
let mut rng = rand_chacha::ChaCha20Rng::seed_from_u64(9001);
32+
FrElement::new(U256 {
33+
limbs: [
34+
rng.gen::<u64>(),
35+
rng.gen::<u64>(),
36+
rng.gen::<u64>(),
37+
rng.gen::<u64>(),
38+
],
39+
})
40+
}

provers/groth16/src/lib.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
pub mod common;
2+
pub mod qap;
3+
pub mod test_circuits;
4+
5+
mod prover;
6+
mod setup;
7+
mod verifier;
8+
9+
pub use prover::{Proof, Prover};
10+
pub use qap::QuadraticArithmeticProgram;
11+
pub use setup::{setup, ProvingKey, VerifyingKey};
12+
pub use verifier::verify;
13+
14+
pub use test_circuits::*;

provers/groth16/src/prover.rs

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use crate::{common::*, ProvingKey, QuadraticArithmeticProgram};
2+
use lambdaworks_math::errors::DeserializationError;
3+
use lambdaworks_math::traits::{Deserializable, Serializable};
4+
use lambdaworks_math::{cyclic_group::IsGroup, msm::pippenger::msm};
5+
use std::mem::size_of;
6+
7+
pub struct Proof {
8+
pub pi1: G1Point,
9+
pub pi2: G2Point,
10+
pub pi3: G1Point,
11+
}
12+
13+
impl Proof {
14+
pub fn serialize(&self) -> Vec<u8> {
15+
let mut bytes: Vec<u8> = Vec::new();
16+
[
17+
Self::serialize_commitment(&self.pi1),
18+
Self::serialize_commitment(&self.pi2),
19+
Self::serialize_commitment(&self.pi3),
20+
]
21+
.iter()
22+
.for_each(|serialized| {
23+
bytes.extend_from_slice(&(serialized.len() as u32).to_be_bytes());
24+
bytes.extend_from_slice(serialized);
25+
});
26+
bytes
27+
}
28+
29+
pub fn deserialize(bytes: &[u8]) -> Result<Self, DeserializationError>
30+
where
31+
Self: Sized,
32+
{
33+
let (offset, pi1) = Self::deserialize_commitment::<G1Point>(bytes, 0)?;
34+
let (offset, pi2) = Self::deserialize_commitment::<G2Point>(bytes, offset)?;
35+
let (_, pi3) = Self::deserialize_commitment::<G1Point>(bytes, offset)?;
36+
Ok(Self { pi1, pi2, pi3 })
37+
}
38+
39+
fn serialize_commitment<Commitment: Serializable>(cm: &Commitment) -> Vec<u8> {
40+
cm.serialize()
41+
}
42+
43+
// Repetitive. Same as in plonk/src/prover.rs
44+
fn deserialize_commitment<Commitment: Deserializable>(
45+
bytes: &[u8],
46+
offset: usize,
47+
) -> Result<(usize, Commitment), DeserializationError> {
48+
let mut offset = offset;
49+
let element_size_bytes: [u8; size_of::<u32>()] = bytes
50+
.get(offset..offset + size_of::<u32>())
51+
.ok_or(DeserializationError::InvalidAmountOfBytes)?
52+
.try_into()
53+
.map_err(|_| DeserializationError::InvalidAmountOfBytes)?;
54+
let element_size = u32::from_be_bytes(element_size_bytes) as usize;
55+
offset += size_of::<u32>();
56+
let commitment = Commitment::deserialize(
57+
bytes
58+
.get(offset..offset + element_size)
59+
.ok_or(DeserializationError::InvalidAmountOfBytes)?,
60+
)?;
61+
offset += element_size;
62+
Ok((offset, commitment))
63+
}
64+
}
65+
66+
pub struct Prover;
67+
impl Prover {
68+
pub fn prove(w: &[FrElement], qap: &QuadraticArithmeticProgram, pk: &ProvingKey) -> Proof {
69+
let h_coefficients = qap
70+
.calculate_h_coefficients(w)
71+
.iter()
72+
.map(|elem| elem.representative())
73+
.collect::<Vec<_>>();
74+
75+
let w = w
76+
.iter()
77+
.map(|elem| elem.representative())
78+
.collect::<Vec<_>>();
79+
80+
// Sample randomness for hiding
81+
let r = sample_fr_elem();
82+
let s = sample_fr_elem();
83+
84+
// [π_1]_1
85+
let pi1 = msm(&w, &pk.l_tau_g1)
86+
.unwrap()
87+
.operate_with(&pk.alpha_g1)
88+
.operate_with(&pk.delta_g1.operate_with_self(r.representative()));
89+
90+
// [π_2]_2
91+
let pi2 = msm(&w, &pk.r_tau_g2)
92+
.unwrap()
93+
.operate_with(&pk.beta_g2)
94+
.operate_with(&pk.delta_g2.operate_with_self(s.representative()));
95+
96+
// [ƍ^{-1} * t(τ)*h(τ)]_1
97+
let t_tau_h_tau_assigned_g1 = msm(
98+
&h_coefficients,
99+
&pk.z_powers_of_tau_g1[..h_coefficients.len()],
100+
)
101+
.unwrap();
102+
103+
// [ƍ^{-1} * (β*l(τ) + α*r(τ) + o(τ))]_1
104+
let k_tau_assigned_prover_g1 = msm(
105+
&w[qap.num_of_public_inputs..],
106+
&pk.prover_k_tau_g1[..qap.num_of_private_inputs()],
107+
)
108+
.unwrap();
109+
110+
// [π_2]_1
111+
let pi2_g1 = msm(&w, &pk.r_tau_g1)
112+
.unwrap()
113+
.operate_with(&pk.beta_g1)
114+
.operate_with(&pk.delta_g1.operate_with_self(s.representative()));
115+
116+
// [π_3]_1
117+
let pi3 = k_tau_assigned_prover_g1
118+
.operate_with(&t_tau_h_tau_assigned_g1)
119+
// s[π_1]_1
120+
.operate_with(&pi1.operate_with_self(s.representative()))
121+
// r[π_2]_1
122+
.operate_with(&pi2_g1.operate_with_self(r.representative()))
123+
// -rs[ƍ]_1
124+
.operate_with(&pk.delta_g1.operate_with_self((-(&r * &s)).representative()));
125+
126+
Proof { pi1, pi2, pi3 }
127+
}
128+
}
129+
130+
#[cfg(test)]
131+
mod tests {
132+
use lambdaworks_math::elliptic_curve::traits::IsEllipticCurve;
133+
134+
use super::*;
135+
136+
#[test]
137+
fn serde() {
138+
let proof = Proof {
139+
pi1: Curve::generator().operate_with_self(sample_fr_elem().representative()),
140+
pi2: TwistedCurve::generator().operate_with_self(sample_fr_elem().representative()),
141+
pi3: Curve::generator().operate_with_self(sample_fr_elem().representative()),
142+
};
143+
let deserialized_proof = Proof::deserialize(&proof.serialize()).unwrap();
144+
145+
assert_eq!(proof.pi1, deserialized_proof.pi1);
146+
assert_eq!(proof.pi2, deserialized_proof.pi2);
147+
assert_eq!(proof.pi3, deserialized_proof.pi3);
148+
}
149+
}

provers/groth16/src/qap.rs

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use lambdaworks_math::{fft::polynomial::FFTPoly, polynomial::Polynomial};
2+
3+
use crate::common::*;
4+
5+
#[derive(Debug)]
6+
pub struct QuadraticArithmeticProgram {
7+
pub num_of_public_inputs: usize,
8+
pub l: Vec<Polynomial<FrElement>>,
9+
pub r: Vec<Polynomial<FrElement>>,
10+
pub o: Vec<Polynomial<FrElement>>,
11+
}
12+
13+
impl QuadraticArithmeticProgram {
14+
pub fn from_variable_matrices(
15+
num_of_public_inputs: usize,
16+
l: &[Vec<FrElement>],
17+
r: &[Vec<FrElement>],
18+
o: &[Vec<FrElement>],
19+
) -> Self {
20+
let num_of_total_inputs = l.len();
21+
assert_eq!(num_of_total_inputs, r.len());
22+
assert_eq!(num_of_total_inputs, o.len());
23+
assert!(num_of_total_inputs > 0);
24+
assert!(num_of_public_inputs <= num_of_total_inputs);
25+
26+
let num_of_gates = l[0].len();
27+
let pad_zeroes = num_of_gates.next_power_of_two() - num_of_gates;
28+
let l = Self::apply_padding(l, pad_zeroes);
29+
let r = Self::apply_padding(r, pad_zeroes);
30+
let o = Self::apply_padding(o, pad_zeroes);
31+
32+
Self {
33+
num_of_public_inputs,
34+
l: Self::build_variable_polynomials(&l),
35+
r: Self::build_variable_polynomials(&r),
36+
o: Self::build_variable_polynomials(&o),
37+
}
38+
}
39+
40+
pub fn num_of_gates(&self) -> usize {
41+
self.l[0].degree() + 1
42+
}
43+
44+
pub fn num_of_private_inputs(&self) -> usize {
45+
self.l.len() - self.num_of_public_inputs
46+
}
47+
48+
pub fn num_of_total_inputs(&self) -> usize {
49+
self.l.len()
50+
}
51+
52+
pub fn calculate_h_coefficients(&self, w: &[FrElement]) -> Vec<FrElement> {
53+
let offset = &ORDER_R_MINUS_1_ROOT_UNITY;
54+
let degree = self.num_of_gates() * 2;
55+
56+
let [l, r, o] = self.scale_and_accumulate_variable_polynomials(w, degree, offset);
57+
58+
// TODO: Change to a vector of offsetted evaluations of x^N-1
59+
let mut t = (Polynomial::new_monomial(FrElement::one(), self.num_of_gates())
60+
- FrElement::one())
61+
.evaluate_offset_fft(1, Some(degree), offset)
62+
.unwrap();
63+
FrElement::inplace_batch_inverse(&mut t).unwrap();
64+
65+
let h_evaluated = l
66+
.iter()
67+
.zip(&r)
68+
.zip(&o)
69+
.zip(&t)
70+
.map(|(((l, r), o), t)| (l * r - o) * t)
71+
.collect::<Vec<_>>();
72+
73+
Polynomial::interpolate_offset_fft(&h_evaluated, offset)
74+
.unwrap()
75+
.coefficients()
76+
.to_vec()
77+
}
78+
79+
fn apply_padding(columns: &[Vec<FrElement>], pad_zeroes: usize) -> Vec<Vec<FrElement>> {
80+
let from_slice = vec![FrElement::zero(); pad_zeroes];
81+
columns
82+
.iter()
83+
.map(|column| {
84+
let mut new_column = column.clone();
85+
new_column.extend_from_slice(&from_slice);
86+
new_column
87+
})
88+
.collect::<Vec<_>>()
89+
}
90+
91+
fn build_variable_polynomials(from_matrix: &[Vec<FrElement>]) -> Vec<Polynomial<FrElement>> {
92+
from_matrix
93+
.iter()
94+
.map(|row| Polynomial::interpolate_fft(row).unwrap())
95+
.collect()
96+
}
97+
98+
// Compute A.s by summing up polynomials A[0].s, A[1].s, ..., A[n].s
99+
// In other words, assign the witness coefficients / execution values
100+
// Similarly for B.s and C.s
101+
fn scale_and_accumulate_variable_polynomials(
102+
&self,
103+
w: &[FrElement],
104+
degree: usize,
105+
offset: &FrElement,
106+
) -> [Vec<FrElement>; 3] {
107+
[&self.l, &self.r, &self.o].map(|var_polynomials| {
108+
var_polynomials
109+
.iter()
110+
.zip(w)
111+
.map(|(poly, coeff)| poly.mul_with_ref(&Polynomial::new_monomial(coeff.clone(), 0)))
112+
.reduce(|poly1, poly2| poly1 + poly2)
113+
.unwrap()
114+
.evaluate_offset_fft(1, Some(degree), offset)
115+
.unwrap()
116+
})
117+
}
118+
}

0 commit comments

Comments
 (0)