Skip to content

Commit 575c4a2

Browse files
nicole-grausjotabulaciosdiegokingston
authored
Add GKR Protocol (#1011)
* save work, protocol structure now is following the post style * add prints to debug implementation * refactor to avoid unwraps * more refactor * more refactor * refactor * add readme * Fix readme. Add struct Prover * remove extra cargo.toml * remove claimed_sum from sumcheck_proof. Fix test. Add documentation * remove clones and fix clippy * add documentation * add check mark for gkr in readme * add degree check on g_j * add verifier checks: proof structure match circuit structure * fix clippy for new rust version (#1013) * fix clippy * fix readme * rename modulus * avoid repetitive computation * check terms has len 2 * remove clone term_1 and term_2 * add check number of inputs * fix type complexity --------- Co-authored-by: jotabulacios <jbulacios@fi.uba.ar> Co-authored-by: jotabulacios <45471455+jotabulacios@users.noreply.github.com> Co-authored-by: Diego K <43053772+diegokingston@users.noreply.github.com>
1 parent 4b01854 commit 575c4a2

File tree

10 files changed

+1366
-12
lines changed

10 files changed

+1366
-12
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ members = [
1111
"crates/provers/plonk",
1212
"crates/provers/stark",
1313
"crates/provers/sumcheck",
14+
"crates/provers/gkr",
1415
"crates/provers/winterfell_adapter",
1516
"examples/merkle-tree-cli",
1617
"examples/prove-miden",
@@ -38,6 +39,7 @@ lambdaworks-math = { path = "./crates/math", version = "0.12.0", default-feature
3839
lambdaworks-groth16 = { path = "./crates/provers/groth16" }
3940
lambdaworks-circom-adapter = { path = "./crates/provers/groth16/circom-adapter" }
4041
lambdaworks-sumcheck = { path = "./crates/provers/sumcheck" }
42+
lambdaworks-sumcheck-gkr = { path = "./crates/provers/gkr" }
4143
lambdaworks-winterfell-adapter = { path = "./crates/provers/winterfell_adapter" }
4244
stark-platinum-prover = { path = "./crates/provers/stark" }
4345
iai-callgrind = "0.3.1"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ List of symbols:
113113
| **SNARKs** | **Lambdaworks** | **Arkworks** | **Halo2** | **gnark** | **Constantine** |
114114
| Groth16 | :heavy_check_mark: | :heavy_check_mark: | :x: | :heavy_check_mark: | :x: |
115115
| Plonk | 🏗️ | :heavy_check_mark: | ✔️ | :heavy_check_mark: | :x: |
116-
| GKR | :x: | :heavy_check_mark: | :x: | :heavy_check_mark: | :x: |
116+
| GKR | :heavy_check_mark: | :heavy_check_mark: | :x: | :heavy_check_mark: | :x: |
117117
| **Polynomial Commitment Schemes** | **Lambdaworks** | **Arkworks** | **Halo2** | **gnark** | **Constantine** |
118118
| KZG10 | :heavy_check_mark: | ✔️ | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
119119
| FRI | 🏗️ | :x: | :x: | :heavy_check_mark: | :x: |

crates/math/src/polynomial/dense_multilinear_poly.rs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,23 +106,21 @@ where
106106
Ok((0..evals.len()).map(|i| &evals[i] * &chis[i]).sum())
107107
}
108108

109-
/// Fixes the last variable to the given value `r` and returns a new DenseMultilinearPolynomial
109+
/// Fixes the first variable to the given value `r` and returns a new DenseMultilinearPolynomial
110110
/// with one fewer variable.
111-
/// Evaluations are ordered so that the first half corresponds to the last variable = 0,
112-
/// and the second half corresponds to the last variable = 1.
113111
///
114112
/// Combines each pair of evaluations as: new_eval = a + r * (b - a)
115113
/// This reduces the polynomial by one variable, allowing it to later be collapsed
116114
/// into a univariate polynomial by summing over the remaining variables.
117115
///
118-
/// Example (2 variables): evaluations ordered as:
116+
/// Example (2 variables): evaluations are ordered as:
119117
/// [f(0,0), f(0,1), f(1,0), f(1,1)]
120-
/// Fixing the second variable `y = r` produces evaluations of a 1-variable polynomial:
121-
/// [f(0,r), f(1,r)]
118+
/// Fixing the first variable `x = r` produces evaluations of a 1-variable polynomial:
119+
/// [f(r,0), f(r,1)]
122120
/// computed explicitly as:
123-
/// f(0,r) = f(0,0) + r*(f(0,1)-f(0,0)),
124-
/// f(1,r) = f(1,0) + r*(f(1,1)-f(1,0))
125-
pub fn fix_last_variable(&self, r: &FieldElement<F>) -> DenseMultilinearPolynomial<F> {
121+
/// f(r,0) = f(0,0) + r * ( f(1,0) - f(0,0)),
122+
/// f(r,1) = f(0,1) + r * (f(1,1) - f(0,1))
123+
pub fn fix_first_variable(&self, r: &FieldElement<F>) -> DenseMultilinearPolynomial<F> {
126124
let n = self.num_vars();
127125
assert!(n > 0, "Cannot fix variable in a 0-variable polynomial");
128126
let half = 1 << (n - 1);
@@ -146,8 +144,8 @@ where
146144
/// sums the evaluations, and returns a univariate polynomial (as a Polynomial)
147145
/// of the form: sum0 + (sum1 - sum0) * x.
148146
pub fn to_univariate(&self) -> Polynomial<FieldElement<F>> {
149-
let poly0 = self.fix_last_variable(&FieldElement::zero());
150-
let poly1 = self.fix_last_variable(&FieldElement::one());
147+
let poly0 = self.fix_first_variable(&FieldElement::zero());
148+
let poly1 = self.fix_first_variable(&FieldElement::one());
151149
let sum0: FieldElement<F> = poly0.to_evaluations().into_iter().sum();
152150
let sum1: FieldElement<F> = poly1.to_evaluations().into_iter().sum();
153151
let diff = sum1 - &sum0;

crates/provers/gkr/Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "lambdaworks-gkr-prover"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
lambdaworks-math = { workspace = true }
10+
lambdaworks-crypto = { workspace = true }
11+
lambdaworks-sumcheck = { workspace = true }
12+
thiserror = "1.0"
13+
blake2 = "0.10"
14+
sha3 = "0.10"
15+
digest = "0.10"
16+
17+
18+
[lib]
19+
name = "lambdaworks_gkr_prover"
20+
path = "src/lib.rs"

crates/provers/gkr/README.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# GKR Protocol
2+
3+
An implementation of the Goldwasser-Kalai-Rothblum (GKR) Non-Interactive Protocol for proving correct evaluation of arithmetic circuits.
4+
5+
To help with the understanding of this implementation, we recommend reading our [blog post](https://blog.lambdaclass.com/gkr-protocol-a-step-by-step-example/).
6+
7+
**Warning:** This GKR implementation is for educational purposes and should not be used in production. It uses the Fiat-Shamir transform, which is vulnerable to practical attacks in this context (see ["How to Prove False Statements"](https://eprint.iacr.org/2025/118.pdf)).
8+
9+
## Overview
10+
11+
The GKR Protocol allows a Prover to convince a Verifier that he correctly evaluated an arithmetic circuit on a given input without the Verifier having to perform the entire computation.
12+
13+
It is a fundamental building block for many interactive proof systems and argument systems, providing a way to verify computations in time roughly proportional to the circuit's depth rather than its size.
14+
15+
The protocol works by reducing claims about each layer of the circuit to claims about the next layer, using the Sumcheck Protocol as a subroutine. This process continues until reaching the input layer, where the verifier can directly check the final claim. The key insight is that the wiring of the circuit can be expressed as multilinear polynomials, allowing the use of sumcheck for efficient verification.
16+
17+
### Key Features
18+
19+
- **Layered Circuit Support**: Works with circuits organized in layers where each gate takes inputs from the previous layer.
20+
- **Power-of-Two Constraint**: Each layer must have a power-of-two number of gates for protocol compatibility.
21+
- **Addition and Multiplication Gates**: Supports both addition and multiplication operations.
22+
- **Complete Verification**: Includes input verification to ensure end-to-end correctness.
23+
24+
## Circuit Structure
25+
26+
Circuits are organized in layers, with each layer containing gates that operate on outputs from the previous layer:
27+
28+
```
29+
Output: o o <- circuit.layers[0]
30+
/ \ / \
31+
o o o o <- circuit.layers[1]
32+
...
33+
o o o o <- circuit.layers[layer.len() - 1]
34+
/ \ / \ / \ / \
35+
Input: o o o o o o o o
36+
```
37+
38+
Each layer must have a power-of-two number of gates.
39+
40+
## API
41+
42+
### Main Functions
43+
44+
- `gkr_prove(circuit, input)` - Generate a GKR proof for a circuit evaluation.
45+
- `gkr_verify(proof, circuit, input)` - Verify a GKR proof.
46+
47+
### Circuit Construction
48+
49+
- `Circuit::new(layers, num_inputs)` - Create a new circuit with specified layers, gates, and number of inputs.
50+
- `CircuitLayer::new(gates)` - Create a layer with the given gates.
51+
- `Gate::new(gate_type, inputs_idx)` - Create a gate with type (Add/Mul) and certain input indices.
52+
53+
## Example
54+
55+
Here's a simple example of how to use the GKR Protocol:
56+
57+
```rust
58+
use lambdaworks_math::field::fields::u64_prime_field::U64PrimeField;
59+
use lambdaworks_math::field::element::FieldElement;
60+
use lambdaworks_gkr::{gkr_prove, gkr_verify, circuit_from_lambda};
61+
62+
// Define the field (We use modulus 23 as in the example of our blog post).
63+
const MODULUS23: u64 = 23;
64+
type F23 = U64PrimeField<MODULUS23>;
65+
type F23E = FieldElement<F23>;
66+
67+
// Create the circuit of our blog post.
68+
// This creates a 2-layer circuit plus the input layer.
69+
let circuit = lambda_post_circuit.unwrap();
70+
71+
// Define input values (from the post example).
72+
let input = [F23E::from(3), F23E::from(1)];
73+
74+
// Generate proof.
75+
let proof_result = gkr_prove(&circuit, &input);
76+
assert!(proof_result.is_ok());
77+
let proof = proof_result.unwrap();
78+
79+
// Verify proof.
80+
let verification_result = gkr_verify(&proof, &circuit, &input);
81+
assert!(verification_result.is_ok() && verification_result.unwrap());
82+
println!("GKR verification successful!");
83+
84+
// You can also check the actual output.
85+
let evaluation = circuit.evaluate(&input);
86+
println!("Circuit output: {:?}", evaluation.layers[0]);
87+
```
88+
89+
### Creating Custom Circuits
90+
91+
You can create custom circuits by defining your own layers, gates, and number of inputs.:
92+
93+
```rust
94+
use lambdaworks_gkr::circuit::{Circuit, CircuitLayer, Gate, GateType};
95+
96+
// Create a simple 2-layer circuit
97+
let custom_circuit = Circuit::new(
98+
vec![
99+
// Output layer: 1 gate
100+
CircuitLayer::new(vec![
101+
Gate::new(GateType::Add, [0, 1]), // Add the two results from layer 1
102+
]),
103+
// Layer 1: 2 gates
104+
CircuitLayer::new(vec![
105+
Gate::new(GateType::Mul, [0, 1]), // Multiply first two inputs
106+
Gate::new(GateType::Mul, [2, 3]), // Multiply last two inputs
107+
]),
108+
],
109+
4, // 4 inputs (power of 2)
110+
).unwrap();
111+
112+
// Test with inputs [2, 3, 4, 5]
113+
let input = [F23E::from(2), F23E::from(3), F23E::from(4), F23E::from(5)];
114+
115+
let proof = gkr_prove(&custom_circuit, &input).unwrap();
116+
let is_valid = gkr_verify(&proof, &custom_circuit, &input).unwrap();
117+
118+
assert!(is_valid);
119+
```
120+
121+
## Protocol Details
122+
123+
The GKR protocol works through the following steps:
124+
125+
1. **Circuit Evaluation**: The prover evaluates the circuit on the given input to obtain the values at each layer.
126+
2. **Layer-by-Layer Reduction**: For each layer $i$, the prover uses the Sumcheck Protocol to reduce a claim about the current layer to a claim about the next layer $i+1$.
127+
128+
- **Wiring Polynomial Construction**: The circuit's wiring is encoded as multilinear polynomials $\widetilde{\text{add}_i}$ and $\widetilde{\text{mul}_i}$ that describe which gates are addition/multiplication gates.
129+
130+
- **Sumcheck Application**: The sumcheck is applied to the polynomial:
131+
$$\tilde f_{r_i}(b,c) = \widetilde{\text{add}_i(}r_i, b, c) \cdot (\tilde W_{i+1}(b) + \tilde W_{i+1}(c)) + \widetilde{\text{mul}_i}(r_i, b, c) \cdot (\tilde W_{i+1}(b) \cdot \tilde W_{i+1}(c))$$
132+
133+
where $\tilde W_{i+1}$ is the multilinear extension of layer $i+1$ values.
134+
135+
- **Line Function**: A line function transforms the two claims of $\tilde W_{i+1}(b)$ and $\tilde W_{i+1}(c)$ into a single claim.
136+
3. **Input Verification**: The verifier checks the final claim, evaluating the input multilinear polynomial extension at the final evaluation point.
137+
138+
Each *layer proof* consists of:
139+
- The claimed sum (that the sumcheck proves).
140+
- All the univariate polynomials used in the sumcheck. They are built by fixing the first variable and summing over the remaining variables.
141+
- The polynomial $q = \tilde W_{i+1} \circ \ell$ where $\ell$ is the line function.
142+
143+
The protocol achieves $O(d \log S)$ verifier time and $O(S)$ prover time, where $d$ is the circuit depth and $S$ is the circuit size (i.e., the number of gates).
144+
145+
146+
## Fiat-Shamir transform
147+
148+
This implementation uses the **Fiat-Shamir** to transform the interactive GKR protocol into a non-interactive proof system. Instead of requiring back-and-forth communication between prover and verifier, the prover generates all challenges deterministically by applying a hash function to the transcript.
149+
150+
### How Fiat-Shamir is Applied
151+
152+
The transformation works by replacing the verifier's random challenges with outputs from a cryptographic hash function applied on the transcript:
153+
154+
1. **Transcript Initialization**: A transcript is created and seeded with:
155+
- Circuit structure (via `circuit_to_bytes(circuit)`)
156+
- Input values
157+
- Output values
158+
159+
2. **Challenge Generation**: At each step where the interactive protocol would require a random challenge, the implementation:
160+
- Adds the current proof data to the transcript.
161+
- Samples a "random" field element from the transcript using `transcript.sample_field_element()`.
162+
163+
3. **Key Challenge Points**:
164+
- **Initial random values** `r_0` for the output layer.
165+
- **Sumcheck challenges** ($s_j$) for each round of each layer's sumcheck protocol.
166+
- **Line function parameter** `r_last` for connecting layers.
167+
168+
169+
## References
170+
171+
- [Goldwasser, Kalai, and Rothblum. "Delegating computation: interactive proofs for muggles"](https://dl.acm.org/doi/10.1145/1374376.1374396)
172+
- [Proofs, Arguments, and Zero-Knowledge. Chapter 4](https://people.cs.georgetown.edu/jthaler/ProofsArgsAndZK.pdf)
173+
- [Lambdaclass Blog Post: GKR protocol: a step-by-step example](https://blog.lambdaclass.com/gkr-protocol-a-step-by-step-example/)

0 commit comments

Comments
 (0)