Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
69027e8
save work, protocol structure now is following the post style
jotabulacios Jul 15, 2025
7284cd7
add prints to debug implementation
jotabulacios Jul 15, 2025
23dd75b
refactor to avoid unwraps
jotabulacios Jul 15, 2025
3bde148
more refactor
jotabulacios Jul 15, 2025
1960af5
more refactor
jotabulacios Jul 16, 2025
39674e7
refactor
nicole-graus Jul 16, 2025
90e67cb
add readme
nicole-graus Jul 16, 2025
9eb8cc3
Fix readme. Add struct Prover
nicole-graus Jul 16, 2025
81495ee
remove extra cargo.toml
nicole-graus Jul 16, 2025
e28777f
remove claimed_sum from sumcheck_proof. Fix test. Add documentation
nicole-graus Jul 17, 2025
2fc6a9a
remove clones and fix clippy
jotabulacios Jul 17, 2025
be70fce
add documentation
nicole-graus Jul 17, 2025
2ab5ae8
add check mark for gkr in readme
nicole-graus Jul 21, 2025
feb370e
add degree check on g_j
jotabulacios Jul 21, 2025
ebdf8cd
add verifier checks: proof structure match circuit structure
nicole-graus Jul 25, 2025
55f0f95
fix clippy for new rust version (#1013)
jotabulacios Aug 1, 2025
6be64de
Merge branch 'main' into gkr_protocol
jotabulacios Aug 1, 2025
6775fe1
fix clippy
jotabulacios Aug 1, 2025
65176e8
fix readme
diegokingston Aug 5, 2025
b19330f
rename modulus
jotabulacios Aug 29, 2025
3d37bb4
avoid repetitive computation
jotabulacios Aug 29, 2025
edd89d2
check terms has len 2
nicole-graus Aug 29, 2025
ad53c07
remove clone term_1 and term_2
nicole-graus Aug 29, 2025
534ec39
add check number of inputs
nicole-graus Aug 29, 2025
d7d7add
Merge branch 'main' into gkr_protocol
diegokingston Sep 4, 2025
3d90a9a
fix type complexity
jotabulacios Sep 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"crates/provers/plonk",
"crates/provers/stark",
"crates/provers/sumcheck",
"crates/provers/gkr",
"crates/provers/winterfell_adapter",
"examples/merkle-tree-cli",
"examples/prove-miden",
Expand Down Expand Up @@ -38,6 +39,7 @@ lambdaworks-math = { path = "./crates/math", version = "0.12.0", default-feature
lambdaworks-groth16 = { path = "./crates/provers/groth16" }
lambdaworks-circom-adapter = { path = "./crates/provers/groth16/circom-adapter" }
lambdaworks-sumcheck = { path = "./crates/provers/sumcheck" }
lambdaworks-sumcheck-gkr = { path = "./crates/provers/gkr" }
lambdaworks-winterfell-adapter = { path = "./crates/provers/winterfell_adapter" }
stark-platinum-prover = { path = "./crates/provers/stark" }
iai-callgrind = "0.3.1"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ List of symbols:
| **SNARKs** | **Lambdaworks** | **Arkworks** | **Halo2** | **gnark** | **Constantine** |
| Groth16 | :heavy_check_mark: | :heavy_check_mark: | :x: | :heavy_check_mark: | :x: |
| Plonk | 🏗️ | :heavy_check_mark: | ✔️ | :heavy_check_mark: | :x: |
| GKR | :x: | :heavy_check_mark: | :x: | :heavy_check_mark: | :x: |
| GKR | :heavy_check_mark: | :heavy_check_mark: | :x: | :heavy_check_mark: | :x: |
| **Polynomial Commitment Schemes** | **Lambdaworks** | **Arkworks** | **Halo2** | **gnark** | **Constantine** |
| KZG10 | :heavy_check_mark: | ✔️ | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| FRI | 🏗️ | :x: | :x: | :heavy_check_mark: | :x: |
Expand Down
20 changes: 9 additions & 11 deletions crates/math/src/polynomial/dense_multilinear_poly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,23 +106,21 @@ where
Ok((0..evals.len()).map(|i| &evals[i] * &chis[i]).sum())
}

/// Fixes the last variable to the given value `r` and returns a new DenseMultilinearPolynomial
/// Fixes the first variable to the given value `r` and returns a new DenseMultilinearPolynomial
/// with one fewer variable.
/// Evaluations are ordered so that the first half corresponds to the last variable = 0,
/// and the second half corresponds to the last variable = 1.
///
/// Combines each pair of evaluations as: new_eval = a + r * (b - a)
/// This reduces the polynomial by one variable, allowing it to later be collapsed
/// into a univariate polynomial by summing over the remaining variables.
///
/// Example (2 variables): evaluations ordered as:
/// Example (2 variables): evaluations are ordered as:
/// [f(0,0), f(0,1), f(1,0), f(1,1)]
/// Fixing the second variable `y = r` produces evaluations of a 1-variable polynomial:
/// [f(0,r), f(1,r)]
/// Fixing the first variable `x = r` produces evaluations of a 1-variable polynomial:
/// [f(r,0), f(r,1)]
/// computed explicitly as:
/// f(0,r) = f(0,0) + r*(f(0,1)-f(0,0)),
/// f(1,r) = f(1,0) + r*(f(1,1)-f(1,0))
pub fn fix_last_variable(&self, r: &FieldElement<F>) -> DenseMultilinearPolynomial<F> {
/// f(r,0) = f(0,0) + r * ( f(1,0) - f(0,0)),
/// f(r,1) = f(0,1) + r * (f(1,1) - f(0,1))
pub fn fix_first_variable(&self, r: &FieldElement<F>) -> DenseMultilinearPolynomial<F> {
let n = self.num_vars();
assert!(n > 0, "Cannot fix variable in a 0-variable polynomial");
let half = 1 << (n - 1);
Expand All @@ -146,8 +144,8 @@ where
/// sums the evaluations, and returns a univariate polynomial (as a Polynomial)
/// of the form: sum0 + (sum1 - sum0) * x.
pub fn to_univariate(&self) -> Polynomial<FieldElement<F>> {
let poly0 = self.fix_last_variable(&FieldElement::zero());
let poly1 = self.fix_last_variable(&FieldElement::one());
let poly0 = self.fix_first_variable(&FieldElement::zero());
let poly1 = self.fix_first_variable(&FieldElement::one());
let sum0: FieldElement<F> = poly0.to_evaluations().into_iter().sum();
let sum1: FieldElement<F> = poly1.to_evaluations().into_iter().sum();
let diff = sum1 - &sum0;
Expand Down
20 changes: 20 additions & 0 deletions crates/provers/gkr/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "lambdaworks-gkr-prover"
version = "0.1.0"
edition = "2021"

# 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 }
lambdaworks-sumcheck = { workspace = true }
thiserror = "1.0"
blake2 = "0.10"
sha3 = "0.10"
digest = "0.10"


[lib]
name = "lambdaworks_gkr_prover"
path = "src/lib.rs"
173 changes: 173 additions & 0 deletions crates/provers/gkr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# GKR Protocol

An implementation of the Goldwasser-Kalai-Rothblum (GKR) Non-Interactive Protocol for proving correct evaluation of arithmetic circuits.

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/).

**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)).

## Overview

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.

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.

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.

### Key Features

- **Layered Circuit Support**: Works with circuits organized in layers where each gate takes inputs from the previous layer.
- **Power-of-Two Constraint**: Each layer must have a power-of-two number of gates for protocol compatibility.
- **Addition and Multiplication Gates**: Supports both addition and multiplication operations.
- **Complete Verification**: Includes input verification to ensure end-to-end correctness.

## Circuit Structure

Circuits are organized in layers, with each layer containing gates that operate on outputs from the previous layer:

```
Output: o o <- circuit.layers[0]
/ \ / \
o o o o <- circuit.layers[1]
...
o o o o <- circuit.layers[layer.len() - 1]
/ \ / \ / \ / \
Input: o o o o o o o o
```

Each layer must have a power-of-two number of gates.

## API

### Main Functions

- `gkr_prove(circuit, input)` - Generate a GKR proof for a circuit evaluation.
- `gkr_verify(proof, circuit, input)` - Verify a GKR proof.

### Circuit Construction

- `Circuit::new(layers, num_inputs)` - Create a new circuit with specified layers, gates, and number of inputs.
- `CircuitLayer::new(gates)` - Create a layer with the given gates.
- `Gate::new(gate_type, inputs_idx)` - Create a gate with type (Add/Mul) and certain input indices.

## Example

Here's a simple example of how to use the GKR Protocol:

```rust
use lambdaworks_math::field::fields::u64_prime_field::U64PrimeField;
use lambdaworks_math::field::element::FieldElement;
use lambdaworks_gkr::{gkr_prove, gkr_verify, circuit_from_lambda};

// Define the field (We use modulus 23 as in the example of our blog post).
const MODULUS23: u64 = 23;
type F23 = U64PrimeField<MODULUS23>;
type F23E = FieldElement<F23>;

// Create the circuit of our blog post.
// This creates a 2-layer circuit plus the input layer.
let circuit = lambda_post_circuit.unwrap();

// Define input values (from the post example).
let input = [F23E::from(3), F23E::from(1)];

// Generate proof.
let proof_result = gkr_prove(&circuit, &input);
assert!(proof_result.is_ok());
let proof = proof_result.unwrap();

// Verify proof.
let verification_result = gkr_verify(&proof, &circuit, &input);
assert!(verification_result.is_ok() && verification_result.unwrap());
println!("GKR verification successful!");

// You can also check the actual output.
let evaluation = circuit.evaluate(&input);
println!("Circuit output: {:?}", evaluation.layers[0]);
```

### Creating Custom Circuits

You can create custom circuits by defining your own layers, gates, and number of inputs.:

```rust
use lambdaworks_gkr::circuit::{Circuit, CircuitLayer, Gate, GateType};

// Create a simple 2-layer circuit
let custom_circuit = Circuit::new(
vec![
// Output layer: 1 gate
CircuitLayer::new(vec![
Gate::new(GateType::Add, [0, 1]), // Add the two results from layer 1
]),
// Layer 1: 2 gates
CircuitLayer::new(vec![
Gate::new(GateType::Mul, [0, 1]), // Multiply first two inputs
Gate::new(GateType::Mul, [2, 3]), // Multiply last two inputs
]),
],
4, // 4 inputs (power of 2)
).unwrap();

// Test with inputs [2, 3, 4, 5]
let input = [F23E::from(2), F23E::from(3), F23E::from(4), F23E::from(5)];

let proof = gkr_prove(&custom_circuit, &input).unwrap();
let is_valid = gkr_verify(&proof, &custom_circuit, &input).unwrap();

assert!(is_valid);
```

## Protocol Details

The GKR protocol works through the following steps:

1. **Circuit Evaluation**: The prover evaluates the circuit on the given input to obtain the values at each layer.
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$.

- **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.

- **Sumcheck Application**: The sumcheck is applied to the polynomial:
$$\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))$$

where $\tilde W_{i+1}$ is the multilinear extension of layer $i+1$ values.

- **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.
3. **Input Verification**: The verifier checks the final claim, evaluating the input multilinear polynomial extension at the final evaluation point.

Each *layer proof* consists of:
- The claimed sum (that the sumcheck proves).
- All the univariate polynomials used in the sumcheck. They are built by fixing the first variable and summing over the remaining variables.
- The polynomial $q = \tilde W_{i+1} \circ \ell$ where $\ell$ is the line function.

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).


## Fiat-Shamir transform

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.

### How Fiat-Shamir is Applied

The transformation works by replacing the verifier's random challenges with outputs from a cryptographic hash function applied on the transcript:

1. **Transcript Initialization**: A transcript is created and seeded with:
- Circuit structure (via `circuit_to_bytes(circuit)`)
- Input values
- Output values

2. **Challenge Generation**: At each step where the interactive protocol would require a random challenge, the implementation:
- Adds the current proof data to the transcript.
- Samples a "random" field element from the transcript using `transcript.sample_field_element()`.

3. **Key Challenge Points**:
- **Initial random values** `r_0` for the output layer.
- **Sumcheck challenges** ($s_j$) for each round of each layer's sumcheck protocol.
- **Line function parameter** `r_last` for connecting layers.


## References

- [Goldwasser, Kalai, and Rothblum. "Delegating computation: interactive proofs for muggles"](https://dl.acm.org/doi/10.1145/1374376.1374396)
- [Proofs, Arguments, and Zero-Knowledge. Chapter 4](https://people.cs.georgetown.edu/jthaler/ProofsArgsAndZK.pdf)
- [Lambdaclass Blog Post: GKR protocol: a step-by-step example](https://blog.lambdaclass.com/gkr-protocol-a-step-by-step-example/)
Loading
Loading