Skip to content

Commit d6fd18e

Browse files
committed
add documentation
1 parent 2fc6a9a commit d6fd18e

File tree

4 files changed

+49
-51
lines changed

4 files changed

+49
-51
lines changed

crates/provers/gkr/README.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ An implementation of the Goldwasser-Kalai-Rothblum (GKR) Non-Interactive Protoco
44

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

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 Statments"](https://eprint.iacr.org/2025/118.pdf)).
8+
79
## Overview
810

911
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.
@@ -40,13 +42,13 @@ Each layer must have a power-of-two number of gates.
4042
### Main Functions
4143

4244
- `gkr_prove(circuit, input)` - Generate a GKR proof for a circuit evaluation.
43-
- `gkr_verify(proof, circuit, input)` - Verify a GKR proof with complete input checking.
45+
- `gkr_verify(proof, circuit, input)` - Verify a GKR proof.
4446

4547
### Circuit Construction
4648

47-
- `Circuit::new(layers, num_inputs)` - Create a new circuit with specified layers and input count
48-
- `CircuitLayer::new(gates)` - Create a layer with the given gates
49-
- `Gate::new(gate_type, inputs)` - Create a gate with type (Add/Mul) and input connections
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 indeces.
5052

5153
## Example
5254

@@ -57,29 +59,29 @@ use lambdaworks_math::field::fields::u64_prime_field::U64PrimeField;
5759
use lambdaworks_math::field::element::FieldElement;
5860
use lambdaworks_gkr::{gkr_prove, gkr_verify, circuit_from_lambda};
5961

60-
// Define the field (We use modulus 23 as in the example of our blog posr)
62+
// Define the field (We use modulus 23 as in the example of our blog post).
6163
const MODULUS23: u64 = 23;
6264
type F23 = U64PrimeField<MODULUS23>;
6365
type F23E = FieldElement<F23>;
6466

65-
// Create the circuit from our blog post.
66-
// This creates a 2-layer circuit.
67+
// Create the circuit of our blog post.
68+
// This creates a 2-layer circuit plus the input layer.
6769
let circuit = lambda_post_circuit.unwrap();
6870

69-
// Define input values (from the post example)
71+
// Define input values (from the post example).
7072
let input = [F23E::from(3), F23E::from(1)];
7173

72-
// Generate proof
74+
// Generate proof.
7375
let proof_result = gkr_prove(&circuit, &input);
7476
assert!(proof_result.is_ok());
7577
let proof = proof_result.unwrap();
7678

77-
// Verify proof
79+
// Verify proof.
7880
let verification_result = gkr_verify(&proof, &circuit, &input);
7981
assert!(verification_result.is_ok() && verification_result.unwrap());
8082
println!("GKR verification successful!");
8183

82-
// You can also check the actual output
84+
// You can also check the actual output.
8385
let evaluation = circuit.evaluate(&input);
8486
println!("Circuit output: {:?}", evaluation.layers[0]);
8587
```
@@ -109,10 +111,10 @@ let custom_circuit = Circuit::new(
109111

110112
// Test with inputs [2, 3, 4, 5]
111113
let input = [F23E::from(2), F23E::from(3), F23E::from(4), F23E::from(5)];
112-
// This computes: Layer 1: [2*3, 4*5] = [6, 20], Layer 0: [6+20] = [26 mod 23] = [3]
113114

114115
let proof = gkr_prove(&custom_circuit, &input).unwrap();
115116
let is_valid = gkr_verify(&proof, &custom_circuit, &input).unwrap();
117+
116118
assert!(is_valid);
117119
```
118120

crates/provers/gkr/src/circuit.rs

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,6 @@ impl CircuitLayer {
5656
}
5757
}
5858

59-
/// An evaluation of a `Circuit` on some input.
60-
pub struct CircuitEvaluation<F> {
61-
/// Evaluations on per-layer basis.
62-
pub layers: Vec<Vec<F>>,
63-
}
64-
65-
impl<F: Copy> CircuitEvaluation<F> {
66-
/// Takes a gate label and outputs the corresponding gate's value at layer `layer`.
67-
pub fn w(&self, layer: usize, label: usize) -> F {
68-
self.layers[layer][label]
69-
}
70-
}
71-
7259
#[derive(Debug, Clone)]
7360
pub enum CircuitError {
7461
InputsNotPowerOfTwo,
@@ -99,15 +86,20 @@ pub enum CircuitError {
9986
/// - The circuit is evaluated from inputs upward, but layers are stored from output to input.
10087
#[derive(Clone)]
10188
pub struct Circuit {
102-
/// First layer being the output layer, last layer being
103-
/// the input layer.
89+
/// First layer is the output layer. It doesn't include the input layer.
10490
layers: Vec<CircuitLayer>,
10591

10692
/// Number of inputs
10793
num_inputs: usize,
10894
input_num_vars: usize, // log2 of number of inputs
10995
}
11096

97+
/// An evaluation of a `Circuit` on some input.
98+
pub struct CircuitEvaluation<F> {
99+
/// Evaluations on per-layer basis. First layer is the output and last layer is the input.
100+
pub layers: Vec<Vec<F>>,
101+
}
102+
111103
impl Circuit {
112104
pub fn new(layers: Vec<CircuitLayer>, num_inputs: usize) -> Result<Self, CircuitError> {
113105
if layers.is_empty() {
@@ -196,22 +188,11 @@ impl Circuit {
196188
current_input = temp_layer;
197189
}
198190

191+
// Reverse the order so that the first layer is the output layer.
199192
layers.reverse();
200193
CircuitEvaluation { layers }
201194
}
202195

203-
/// The $\text{add}_i(a, b, c)$ polynomial value at layer $i$.
204-
pub fn add_i(&self, i: usize, a: usize, b: usize, c: usize) -> bool {
205-
let gate = &self.layers[i].gates[a];
206-
gate.gate_type == GateType::Add && gate.inputs_idx[0] == b && gate.inputs_idx[1] == c
207-
}
208-
209-
/// The $\text{mul}_i(a, b, c)$ polynomial value at layer $i$.
210-
pub fn mul_i(&self, i: usize, a: usize, b: usize, c: usize) -> bool {
211-
let gate = &self.layers[i].gates[a];
212-
gate.gate_type == GateType::Mul && gate.inputs_idx[0] == b && gate.inputs_idx[1] == c
213-
}
214-
215196
pub fn layers(&self) -> &[CircuitLayer] {
216197
&self.layers
217198
}
@@ -224,6 +205,7 @@ impl Circuit {
224205
self.num_inputs
225206
}
226207

208+
/// The multilinear polynomial extension of the function `add_i(a, b, c)`, where `a` is fixed at `r_i`.
227209
pub fn add_i_ext<F: IsField>(
228210
&self,
229211
r_i: &[FieldElement<F>],
@@ -240,6 +222,8 @@ impl Circuit {
240222
};
241223
let total_vars = num_vars_current + 2 * num_vars_next;
242224
let mut add_i_evals = vec![FieldElement::zero(); 1 << total_vars];
225+
226+
// For each Add gate, we set the corresponding index `a || b || c` in the evaluation vector to one.
243227
for (a, gate) in self.layers[i].gates.iter().enumerate() {
244228
if gate.gate_type == GateType::Add {
245229
let b = gate.inputs_idx[0];
@@ -248,13 +232,15 @@ impl Circuit {
248232
add_i_evals[idx] = FieldElement::one();
249233
}
250234
}
251-
let mut p = DenseMultilinearPolynomial::new(add_i_evals);
235+
236+
let mut add_i_poly = DenseMultilinearPolynomial::new(add_i_evals);
252237
for val in r_i.iter() {
253-
p = p.fix_first_variable(val);
238+
add_i_poly = add_i_poly.fix_first_variable(val);
254239
}
255-
p
240+
add_i_poly
256241
}
257242

243+
/// The multilinear polynomial extension of the function `mul_i(a, b, c)`, where `a` is fixed at `r_i`.
258244
pub fn mul_i_ext<F: IsField>(
259245
&self,
260246
r_i: &[FieldElement<F>],
@@ -271,6 +257,8 @@ impl Circuit {
271257
};
272258
let total_vars = num_vars_current + 2 * num_vars_next;
273259
let mut mul_i_evals = vec![FieldElement::zero(); 1 << total_vars];
260+
261+
// For each Mul gate, we set the corresponding index `a || b || c` in the evaluation vector to one.
274262
for (a, gate) in self.layers[i].gates.iter().enumerate() {
275263
if gate.gate_type == GateType::Mul {
276264
let b = gate.inputs_idx[0];
@@ -279,10 +267,11 @@ impl Circuit {
279267
mul_i_evals[idx] = FieldElement::one();
280268
}
281269
}
282-
let mut p = DenseMultilinearPolynomial::new(mul_i_evals);
270+
271+
let mut mul_i_poly = DenseMultilinearPolynomial::new(mul_i_evals);
283272
for val in r_i.iter() {
284-
p = p.fix_first_variable(val);
273+
mul_i_poly = mul_i_poly.fix_first_variable(val);
285274
}
286-
p
275+
mul_i_poly
287276
}
288277
}

crates/provers/gkr/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ mod tests {
125125
2,
126126
)
127127
}
128-
/// Create a circuit with four layers (without counting the inputs).
128+
/// Create a circuit with four layers (without counting the input layer).
129129
/// To picture this circuit, imagine a tree structure where each layer has twice the number of gates as the layer above.
130130
pub fn four_layer_circuit() -> Result<Circuit, CircuitError> {
131131
use crate::circuit::{CircuitLayer, Gate, GateType};

crates/provers/gkr/src/sumcheck.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@ use lambdaworks_sumcheck::{Channel, Prover};
1111

1212
use crate::prover::ProverError;
1313

14+
/// GKR-specific sumcheck proof, which contains each round polynomial `g_j` and the challenges used.
1415
#[derive(Debug, Clone)]
1516
pub struct GKRSumcheckProof<F: IsField> {
1617
pub round_polynomials: Vec<Polynomial<FieldElement<F>>>,
1718
pub challenges: Vec<FieldElement<F>>,
1819
}
1920

20-
/// GKR-specific sumcheck prover
21+
/// GKR-specific sumcheck prover.
22+
/// This function will recieve a vector of two terms. Each term contains two multilinear polynomials.
23+
/// This separation of terms is necessary because the classic/original sumcheck only accepts a product of multilinear polynomials.
24+
/// In this way, we apply the sumcheck to two products, each consisting of two factors.
2125
pub fn gkr_sumcheck_prove<F, T>(
2226
terms: Vec<Vec<DenseMultilinearPolynomial<F>>>,
2327
transcript: &mut T,
@@ -35,9 +39,11 @@ where
3539
let factors_term_1 = terms[0].clone();
3640
let factors_term_2 = terms[1].clone();
3741

42+
// Create two separate sumcheck provers for each term.
3843
let mut prover_term_1 = Prover::new(factors_term_1).map_err(|_| ProverError::SumcheckError)?;
3944
let mut prover_term_2 = Prover::new(factors_term_2).map_err(|_| ProverError::SumcheckError)?;
4045

46+
// Both terms have the same number of variables.
4147
let num_vars = prover_term_1.num_vars();
4248

4349
let claimed_sum_term_1 = prover_term_1
@@ -58,7 +64,7 @@ where
5864
let mut challenges = Vec::new();
5965
let mut current_challenge: Option<FieldElement<F>> = None;
6066

61-
// Execute rounds
67+
// Execute sumcheck rounds
6268
for j in 0..num_vars {
6369
let g_j_term_1 = prover_term_1
6470
.round(current_challenge.as_ref())
@@ -101,6 +107,7 @@ where
101107
Ok(sumcheck_proof)
102108
}
103109

110+
/// GKR-specific sumcheck Verifier.
104111
pub fn gkr_sumcheck_verify<F, T>(
105112
claimed_sum: FieldElement<F>,
106113
sumcheck_proof: &GKRSumcheckProof<F>,
@@ -126,12 +133,11 @@ where
126133

127134
let mut challenges = Vec::new();
128135

129-
// Process each round polynomial
136+
// Verify each round polynomial.
130137
for (j, g_j) in proof_polys.iter().enumerate() {
131138
// Add polynomial info to transcript
132139
let round_label = format!("round_{}_poly", j);
133140
transcript.append_bytes(round_label.as_bytes());
134-
135141
let coeffs = g_j.coefficients();
136142
transcript.append_bytes(&(coeffs.len() as u64).to_be_bytes());
137143
if coeffs.is_empty() {
@@ -142,11 +148,12 @@ where
142148
}
143149
}
144150

151+
// Verify `g_j(0) + g_j(1) = m_{j-1}`, where:
152+
// `m_{j-1} = g_{j-1} (s_{j-1})`, the previous claimed sum.
145153
let g_j_0 = g_j.evaluate::<F>(&FieldElement::zero());
146154
let g_j_1 = g_j.evaluate::<F>(&FieldElement::one());
147155
let sum_evals = &g_j_0 + &g_j_1;
148156

149-
// We need to manually track the expected sum
150157
let expected_sum = if j == 0 {
151158
claimed_sum.clone()
152159
} else {

0 commit comments

Comments
 (0)