arnaucube
arnaucube/miden-cryptoPublic Self-hosted repository
miden-crypto/src/gkr/circuit/mod.rs
src/gkr/circuit/mod.rsRaw
  1use alloc::sync::Arc;
  2use winter_crypto::{ElementHasher, RandomCoin};
  3use winter_math::fields::f64::BaseElement;
  4use winter_math::FieldElement;
  5
  6use crate::gkr::multivariate::{
  7    ComposedMultiLinearsOracle, EqPolynomial, GkrCompositionVanilla, MultiLinearOracle,
  8};
  9use crate::gkr::sumcheck::{sum_check_verify, Claim};
 10
 11use super::multivariate::{
 12    gen_plain_gkr_oracle, gkr_composition_from_composition_polys, ComposedMultiLinears,
 13    CompositionPolynomial, MultiLinear,
 14};
 15use super::sumcheck::{
 16    sum_check_prove, sum_check_verify_and_reduce, FinalEvaluationClaim,
 17    PartialProof as SumcheckInstanceProof, RoundProof as SumCheckRoundProof, Witness,
 18};
 19
 20/// Layered circuit for computing a sum of fractions.
 21///
 22/// The circuit computes a sum of fractions based on the formula a / c + b / d = (a * d + b * c) / (c * d)
 23/// which defines a "gate" ((a, b), (c, d)) --> (a * d + b * c, c * d) upon which the `FractionalSumCircuit`
 24/// is built.
 25/// TODO: Swap 1 and 0
 26#[derive(Debug)]
 27pub struct FractionalSumCircuit<E: FieldElement> {
 28    p_1_vec: Vec<MultiLinear<E>>,
 29    p_0_vec: Vec<MultiLinear<E>>,
 30    q_1_vec: Vec<MultiLinear<E>>,
 31    q_0_vec: Vec<MultiLinear<E>>,
 32}
 33
 34impl<E: FieldElement> FractionalSumCircuit<E> {
 35    /// Computes The values of the gates outputs for each of the layers of the fractional sum circuit.
 36    pub fn new_(num_den: &Vec<MultiLinear<E>>) -> Self {
 37        let mut p_1_vec: Vec<MultiLinear<E>> = Vec::new();
 38        let mut p_0_vec: Vec<MultiLinear<E>> = Vec::new();
 39        let mut q_1_vec: Vec<MultiLinear<E>> = Vec::new();
 40        let mut q_0_vec: Vec<MultiLinear<E>> = Vec::new();
 41
 42        let num_layers = num_den[0].len().ilog2() as usize;
 43
 44        p_1_vec.push(num_den[0].to_owned());
 45        p_0_vec.push(num_den[1].to_owned());
 46        q_1_vec.push(num_den[2].to_owned());
 47        q_0_vec.push(num_den[3].to_owned());
 48
 49        for i in 0..num_layers {
 50            let (output_p_1, output_p_0, output_q_1, output_q_0) =
 51                FractionalSumCircuit::compute_layer(
 52                    &p_1_vec[i],
 53                    &p_0_vec[i],
 54                    &q_1_vec[i],
 55                    &q_0_vec[i],
 56                );
 57            p_1_vec.push(output_p_1);
 58            p_0_vec.push(output_p_0);
 59            q_1_vec.push(output_q_1);
 60            q_0_vec.push(output_q_0);
 61        }
 62
 63        FractionalSumCircuit { p_1_vec, p_0_vec, q_1_vec, q_0_vec }
 64    }
 65
 66    /// Compute the output values of the layer given a set of input values
 67    fn compute_layer(
 68        inp_p_1: &MultiLinear<E>,
 69        inp_p_0: &MultiLinear<E>,
 70        inp_q_1: &MultiLinear<E>,
 71        inp_q_0: &MultiLinear<E>,
 72    ) -> (MultiLinear<E>, MultiLinear<E>, MultiLinear<E>, MultiLinear<E>) {
 73        let len = inp_q_1.len();
 74        let outp_p_1 = (0..len / 2)
 75            .map(|i| inp_p_1[i] * inp_q_0[i] + inp_p_0[i] * inp_q_1[i])
 76            .collect::<Vec<E>>();
 77        let outp_p_0 = (len / 2..len)
 78            .map(|i| inp_p_1[i] * inp_q_0[i] + inp_p_0[i] * inp_q_1[i])
 79            .collect::<Vec<E>>();
 80        let outp_q_1 = (0..len / 2).map(|i| inp_q_1[i] * inp_q_0[i]).collect::<Vec<E>>();
 81        let outp_q_0 = (len / 2..len).map(|i| inp_q_1[i] * inp_q_0[i]).collect::<Vec<E>>();
 82
 83        (
 84            MultiLinear::new(outp_p_1),
 85            MultiLinear::new(outp_p_0),
 86            MultiLinear::new(outp_q_1),
 87            MultiLinear::new(outp_q_0),
 88        )
 89    }
 90
 91    /// Computes The values of the gates outputs for each of the layers of the fractional sum circuit.
 92    pub fn new(poly: &MultiLinear<E>) -> Self {
 93        let mut p_1_vec: Vec<MultiLinear<E>> = Vec::new();
 94        let mut p_0_vec: Vec<MultiLinear<E>> = Vec::new();
 95        let mut q_1_vec: Vec<MultiLinear<E>> = Vec::new();
 96        let mut q_0_vec: Vec<MultiLinear<E>> = Vec::new();
 97
 98        let num_layers = poly.len().ilog2() as usize - 1;
 99        let (output_p, output_q) = poly.split(poly.len() / 2);
100        let (output_p_1, output_p_0) = output_p.split(output_p.len() / 2);
101        let (output_q_1, output_q_0) = output_q.split(output_q.len() / 2);
102
103        p_1_vec.push(output_p_1);
104        p_0_vec.push(output_p_0);
105        q_1_vec.push(output_q_1);
106        q_0_vec.push(output_q_0);
107
108        for i in 0..num_layers - 1 {
109            let (output_p_1, output_p_0, output_q_1, output_q_0) =
110                FractionalSumCircuit::compute_layer(
111                    &p_1_vec[i],
112                    &p_0_vec[i],
113                    &q_1_vec[i],
114                    &q_0_vec[i],
115                );
116            p_1_vec.push(output_p_1);
117            p_0_vec.push(output_p_0);
118            q_1_vec.push(output_q_1);
119            q_0_vec.push(output_q_0);
120        }
121
122        FractionalSumCircuit { p_1_vec, p_0_vec, q_1_vec, q_0_vec }
123    }
124
125    /// Given a value r, computes the evaluation of the last layer at r when interpreted as (two)
126    /// multilinear polynomials.
127    pub fn evaluate(&self, r: E) -> (E, E) {
128        let len = self.p_1_vec.len();
129        assert_eq!(self.p_1_vec[len - 1].num_variables(), 0);
130        assert_eq!(self.p_0_vec[len - 1].num_variables(), 0);
131        assert_eq!(self.q_1_vec[len - 1].num_variables(), 0);
132        assert_eq!(self.q_0_vec[len - 1].num_variables(), 0);
133
134        let mut p_1 = self.p_1_vec[len - 1].clone();
135        p_1.extend(&self.p_0_vec[len - 1]);
136        let mut q_1 = self.q_1_vec[len - 1].clone();
137        q_1.extend(&self.q_0_vec[len - 1]);
138
139        (p_1.evaluate(&[r]), q_1.evaluate(&[r]))
140    }
141}
142
143/// A proof for reducing a claim on the correctness of the output of a layer to that of:
144///
145/// 1. Correctness of a sumcheck proof on the claimed output.
146/// 2. Correctness of the evaluation of the input (to the said layer) at a random point when
147/// interpreted as multilinear polynomial.
148///
149/// The verifier will then have to work backward and:
150///
151/// 1. Verify that the sumcheck proof is valid.
152/// 2. Recurse on the (claimed evaluations) using the same approach as above.
153///
154/// Note that the following struct batches proofs for many circuits of the same type that
155/// are independent i.e., parallel.
156#[derive(Debug)]
157pub struct LayerProof<E: FieldElement> {
158    pub proof: SumcheckInstanceProof<E>,
159    pub claims_sum_p1: E,
160    pub claims_sum_p0: E,
161    pub claims_sum_q1: E,
162    pub claims_sum_q0: E,
163}
164
165#[allow(dead_code)]
166impl<E: FieldElement<BaseField = BaseElement> + 'static> LayerProof<E> {
167    /// Checks the validity of a `LayerProof`.
168    ///
169    /// It first reduces the 2 claims to 1 claim using randomness and then checks that the sumcheck
170    /// protocol was correctly executed.
171    ///
172    /// The method outputs:
173    ///
174    /// 1. A vector containing the randomness sent by the verifier throughout the course of the
175    /// sum-check protocol.
176    /// 2. The (claimed) evaluation of the inner polynomial (i.e., the one being summed) at the this random vector.
177    /// 3. The random value used in the 2-to-1 reduction of the 2 sumchecks.  
178    pub fn verify_sum_check_before_last<
179        C: RandomCoin<Hasher = H, BaseField = BaseElement>,
180        H: ElementHasher<BaseField = BaseElement>,
181    >(
182        &self,
183        claim: (E, E),
184        num_rounds: usize,
185        transcript: &mut C,
186    ) -> ((E, Vec<E>), E) {
187        // Absorb the claims
188        let data = vec![claim.0, claim.1];
189        transcript.reseed(H::hash_elements(&data));
190
191        // Squeeze challenge to reduce two sumchecks to one
192        let r_sum_check: E = transcript.draw().unwrap();
193
194        // Run the sumcheck protocol
195
196        // Given r_sum_check and claim, we create a Claim with the GKR composer and then call the generic sum-check verifier
197        let reduced_claim = claim.0 + claim.1 * r_sum_check;
198
199        // Create vanilla oracle
200        let oracle = gen_plain_gkr_oracle(num_rounds, r_sum_check);
201
202        // Create sum-check claim
203        let transformed_claim = Claim {
204            sum_value: reduced_claim,
205            polynomial: oracle,
206        };
207
208        let reduced_gkr_claim =
209            sum_check_verify_and_reduce(&transformed_claim, self.proof.clone(), transcript);
210
211        (reduced_gkr_claim, r_sum_check)
212    }
213}
214
215#[derive(Debug)]
216pub struct GkrClaim<E: FieldElement + 'static> {
217    evaluation_point: Vec<E>,
218    claimed_evaluation: (E, E),
219}
220
221#[derive(Debug)]
222pub struct CircuitProof<E: FieldElement + 'static> {
223    pub proof: Vec<LayerProof<E>>,
224}
225
226impl<E: FieldElement<BaseField = BaseElement> + 'static> CircuitProof<E> {
227    pub fn prove<
228        C: RandomCoin<Hasher = H, BaseField = BaseElement>,
229        H: ElementHasher<BaseField = BaseElement>,
230    >(
231        circuit: &mut FractionalSumCircuit<E>,
232        transcript: &mut C,
233    ) -> (Self, Vec<E>, Vec<Vec<E>>) {
234        let mut proof_layers: Vec<LayerProof<E>> = Vec::new();
235        let num_layers = circuit.p_0_vec.len();
236
237        let data = vec![
238            circuit.p_1_vec[num_layers - 1][0],
239            circuit.p_0_vec[num_layers - 1][0],
240            circuit.q_1_vec[num_layers - 1][0],
241            circuit.q_0_vec[num_layers - 1][0],
242        ];
243        transcript.reseed(H::hash_elements(&data));
244
245        // Challenge to reduce p1, p0, q1, q0 to pr, qr
246        let r_cord = transcript.draw().unwrap();
247
248        // Compute the (2-to-1 folded) claim
249        let mut claim = circuit.evaluate(r_cord);
250        let mut all_rand = Vec::new();
251
252        let mut rand = Vec::new();
253        rand.push(r_cord);
254        for layer_id in (0..num_layers - 1).rev() {
255            let len = circuit.p_0_vec[layer_id].len();
256
257            // Construct the Lagrange kernel evaluated at previous GKR round randomness.
258            // TODO: Treat the direction of doing sum-check more robustly.
259            let mut rand_reversed = rand.clone();
260            rand_reversed.reverse();
261            let eq_evals = EqPolynomial::new(rand_reversed.clone()).evaluations();
262            let mut poly_x = MultiLinear::from_values(&eq_evals);
263            assert_eq!(poly_x.len(), len);
264
265            let num_rounds = poly_x.len().ilog2() as usize;
266
267            // 1. A is a polynomial containing the evaluations `p_1`.
268            // 2. B is a polynomial containing the evaluations `p_0`.
269            // 3. C is a polynomial containing the evaluations `q_1`.
270            // 4. D is a polynomial containing the evaluations `q_0`.
271            let poly_a: &mut MultiLinear<E>;
272            let poly_b: &mut MultiLinear<E>;
273            let poly_c: &mut MultiLinear<E>;
274            let poly_d: &mut MultiLinear<E>;
275            poly_a = &mut circuit.p_1_vec[layer_id];
276            poly_b = &mut circuit.p_0_vec[layer_id];
277            poly_c = &mut circuit.q_1_vec[layer_id];
278            poly_d = &mut circuit.q_0_vec[layer_id];
279
280            let poly_vec_par = (poly_a, poly_b, poly_c, poly_d, &mut poly_x);
281
282            // The (non-linear) polynomial combining the multilinear polynomials
283            let comb_func = |a: &E, b: &E, c: &E, d: &E, x: &E, rho: &E| -> E {
284                (*a * *d + *b * *c + *rho * *c * *d) * *x
285            };
286
287            // Run the sumcheck protocol
288            let (proof, rand_sumcheck, claims_sum) = sum_check_prover_gkr_before_last::<E, _, _>(
289                claim,
290                num_rounds,
291                poly_vec_par,
292                comb_func,
293                transcript,
294            );
295
296            let (claims_sum_p1, claims_sum_p0, claims_sum_q1, claims_sum_q0, _claims_eq) =
297                claims_sum;
298
299            let data = vec![claims_sum_p1, claims_sum_p0, claims_sum_q1, claims_sum_q0];
300            transcript.reseed(H::hash_elements(&data));
301
302            // Produce a random challenge to condense claims into a single claim
303            let r_layer = transcript.draw().unwrap();
304
305            claim = (
306                claims_sum_p1 + r_layer * (claims_sum_p0 - claims_sum_p1),
307                claims_sum_q1 + r_layer * (claims_sum_q0 - claims_sum_q1),
308            );
309
310            // Collect the randomness used for the current layer in order to construct the random
311            // point where the input multilinear polynomials were evaluated.
312            let mut ext = rand_sumcheck;
313            ext.push(r_layer);
314            all_rand.push(rand);
315            rand = ext;
316
317            proof_layers.push(LayerProof {
318                proof,
319                claims_sum_p1,
320                claims_sum_p0,
321                claims_sum_q1,
322                claims_sum_q0,
323            });
324        }
325
326        (CircuitProof { proof: proof_layers }, rand, all_rand)
327    }
328
329    pub fn prove_virtual_bus<
330        C: RandomCoin<Hasher = H, BaseField = BaseElement>,
331        H: ElementHasher<BaseField = BaseElement>,
332    >(
333        composition_polys: Vec<Vec<Arc<dyn CompositionPolynomial<E>>>>,
334        mls: &mut Vec<MultiLinear<E>>,
335        transcript: &mut C,
336    ) -> (Vec<E>, Self, super::sumcheck::FullProof<E>) {
337        let num_evaluations = 1 << mls[0].num_variables();
338
339        // I) Evaluate the numerators and denominators over the boolean hyper-cube
340        let mut num_den: Vec<Vec<E>> = vec![vec![]; 4];
341        for i in 0..num_evaluations {
342            for j in 0..4 {
343                let query: Vec<E> = mls.iter().map(|ml| ml[i]).collect();
344
345                composition_polys[j].iter().for_each(|c| {
346                    let evaluation = c.as_ref().evaluate(&query);
347                    num_den[j].push(evaluation);
348                });
349            }
350        }
351
352        // II) Evaluate the GKR fractional sum circuit
353        let input: Vec<MultiLinear<E>> =
354            (0..4).map(|i| MultiLinear::from_values(&num_den[i])).collect();
355        let mut circuit = FractionalSumCircuit::new_(&input);
356
357        // III) Run the GKR prover for all layers except the last one
358        let (gkr_proofs, GkrClaim { evaluation_point, claimed_evaluation }) =
359            CircuitProof::prove_before_final(&mut circuit, transcript);
360
361        // IV) Run the sum-check prover for the last GKR layer counting backwards i.e., first layer
362        // in the circuit.
363
364        // 1) Build the EQ polynomial (Lagrange kernel) at the randomness sampled during the previous
365        // sum-check protocol run
366        let mut rand_reversed = evaluation_point.clone();
367        rand_reversed.reverse();
368        let eq_evals = EqPolynomial::new(rand_reversed.clone()).evaluations();
369        let poly_x = MultiLinear::from_values(&eq_evals);
370
371        // 2) Add the Lagrange kernel to the list of MLs
372        mls.push(poly_x);
373
374        // 3) Absorb the final sum-check claims and generate randomness for 2-to-1 sum-check reduction
375        let data = vec![claimed_evaluation.0, claimed_evaluation.1];
376        transcript.reseed(H::hash_elements(&data));
377        // Squeeze challenge to reduce two sumchecks to one
378        let r_sum_check = transcript.draw().unwrap();
379        let reduced_claim = claimed_evaluation.0 + claimed_evaluation.1 * r_sum_check;
380
381        // 4) Create the composed ML representing the numerators and denominators of the topmost GKR layer
382        let gkr_final_composed_ml = gkr_composition_from_composition_polys(
383            &composition_polys,
384            r_sum_check,
385            1 << mls[0].num_variables,
386        );
387        let composed_ml =
388            ComposedMultiLinears::new(Arc::new(gkr_final_composed_ml.clone()), mls.to_vec());
389
390        // 5) Create the composed ML oracle. This will be used for verifying the FinalEvaluationClaim downstream
391        // TODO: This should be an input to the current function.
392        // TODO: Make MultiLinearOracle a variant in an enum so that it is possible to capture other types of oracles.
393        // For example, shifts of polynomials, Lagrange kernels at a random point or periodic (transparent) polynomials.
394        let left_num_oracle = MultiLinearOracle { id: 0 };
395        let right_num_oracle = MultiLinearOracle { id: 1 };
396        let left_denom_oracle = MultiLinearOracle { id: 2 };
397        let right_denom_oracle = MultiLinearOracle { id: 3 };
398        let eq_oracle = MultiLinearOracle { id: 4 };
399        let composed_ml_oracle = ComposedMultiLinearsOracle {
400            composer: (Arc::new(gkr_final_composed_ml.clone())),
401            multi_linears: vec![
402                eq_oracle,
403                left_num_oracle,
404                right_num_oracle,
405                left_denom_oracle,
406                right_denom_oracle,
407            ],
408        };
409
410        // 6) Create the claim for the final sum-check protocol.
411        let claim = Claim {
412            sum_value: reduced_claim,
413            polynomial: composed_ml_oracle.clone(),
414        };
415
416        // 7) Create the witness for the sum-check claim.
417        let witness = Witness { polynomial: composed_ml };
418        let output = sum_check_prove(&claim, composed_ml_oracle, witness, transcript);
419
420        // 8) Create the claimed output of the circuit.
421        let circuit_outputs = vec![
422            circuit.p_1_vec.last().unwrap()[0],
423            circuit.p_0_vec.last().unwrap()[0],
424            circuit.q_1_vec.last().unwrap()[0],
425            circuit.q_0_vec.last().unwrap()[0],
426        ];
427
428        // 9) Return:
429        //  1. The claimed circuit outputs.
430        //  2. GKR proofs of all circuit layers except the initial layer.
431        //  3. Output of the final sum-check protocol.
432        (circuit_outputs, gkr_proofs, output)
433    }
434
435    pub fn prove_before_final<
436        C: RandomCoin<Hasher = H, BaseField = BaseElement>,
437        H: ElementHasher<BaseField = BaseElement>,
438    >(
439        sum_circuits: &mut FractionalSumCircuit<E>,
440        transcript: &mut C,
441    ) -> (Self, GkrClaim<E>) {
442        let mut proof_layers: Vec<LayerProof<E>> = Vec::new();
443        let num_layers = sum_circuits.p_0_vec.len();
444
445        let data = vec![
446            sum_circuits.p_1_vec[num_layers - 1][0],
447            sum_circuits.p_0_vec[num_layers - 1][0],
448            sum_circuits.q_1_vec[num_layers - 1][0],
449            sum_circuits.q_0_vec[num_layers - 1][0],
450        ];
451        transcript.reseed(H::hash_elements(&data));
452
453        // Challenge to reduce p1, p0, q1, q0 to pr, qr
454        let r_cord = transcript.draw().unwrap();
455
456        // Compute the (2-to-1 folded) claim
457        let mut claims_to_verify = sum_circuits.evaluate(r_cord);
458        let mut all_rand = Vec::new();
459
460        let mut rand = Vec::new();
461        rand.push(r_cord);
462        for layer_id in (1..num_layers - 1).rev() {
463            let len = sum_circuits.p_0_vec[layer_id].len();
464
465            // Construct the Lagrange kernel evaluated at previous GKR round randomness.
466            // TODO: Treat the direction of doing sum-check more robustly.
467            let mut rand_reversed = rand.clone();
468            rand_reversed.reverse();
469            let eq_evals = EqPolynomial::new(rand_reversed.clone()).evaluations();
470            let mut poly_x = MultiLinear::from_values(&eq_evals);
471            assert_eq!(poly_x.len(), len);
472
473            let num_rounds = poly_x.len().ilog2() as usize;
474
475            // 1. A is a polynomial containing the evaluations `p_1`.
476            // 2. B is a polynomial containing the evaluations `p_0`.
477            // 3. C is a polynomial containing the evaluations `q_1`.
478            // 4. D is a polynomial containing the evaluations `q_0`.
479            let poly_a: &mut MultiLinear<E>;
480            let poly_b: &mut MultiLinear<E>;
481            let poly_c: &mut MultiLinear<E>;
482            let poly_d: &mut MultiLinear<E>;
483            poly_a = &mut sum_circuits.p_1_vec[layer_id];
484            poly_b = &mut sum_circuits.p_0_vec[layer_id];
485            poly_c = &mut sum_circuits.q_1_vec[layer_id];
486            poly_d = &mut sum_circuits.q_0_vec[layer_id];
487
488            let poly_vec = (poly_a, poly_b, poly_c, poly_d, &mut poly_x);
489
490            let claim = claims_to_verify;
491
492            // The (non-linear) polynomial combining the multilinear polynomials
493            let comb_func = |a: &E, b: &E, c: &E, d: &E, x: &E, rho: &E| -> E {
494                (*a * *d + *b * *c + *rho * *c * *d) * *x
495            };
496
497            // Run the sumcheck protocol
498            let (proof, rand_sumcheck, claims_sum) = sum_check_prover_gkr_before_last::<E, _, _>(
499                claim, num_rounds, poly_vec, comb_func, transcript,
500            );
501
502            let (claims_sum_p1, claims_sum_p0, claims_sum_q1, claims_sum_q0, _claims_eq) =
503                claims_sum;
504
505            let data = vec![claims_sum_p1, claims_sum_p0, claims_sum_q1, claims_sum_q0];
506            transcript.reseed(H::hash_elements(&data));
507
508            // Produce a random challenge to condense claims into a single claim
509            let r_layer = transcript.draw().unwrap();
510
511            claims_to_verify = (
512                claims_sum_p1 + r_layer * (claims_sum_p0 - claims_sum_p1),
513                claims_sum_q1 + r_layer * (claims_sum_q0 - claims_sum_q1),
514            );
515
516            // Collect the randomness used for the current layer in order to construct the random
517            // point where the input multilinear polynomials were evaluated.
518            let mut ext = rand_sumcheck;
519            ext.push(r_layer);
520            all_rand.push(rand);
521            rand = ext;
522
523            proof_layers.push(LayerProof {
524                proof,
525                claims_sum_p1,
526                claims_sum_p0,
527                claims_sum_q1,
528                claims_sum_q0,
529            });
530        }
531        let gkr_claim = GkrClaim {
532            evaluation_point: rand.clone(),
533            claimed_evaluation: claims_to_verify,
534        };
535
536        (CircuitProof { proof: proof_layers }, gkr_claim)
537    }
538
539    pub fn verify<
540        C: RandomCoin<Hasher = H, BaseField = BaseElement>,
541        H: ElementHasher<BaseField = BaseElement>,
542    >(
543        &self,
544        claims_sum_vec: &[E],
545        transcript: &mut C,
546    ) -> ((E, E), Vec<E>) {
547        let num_layers = self.proof.len() as usize - 1;
548        let mut rand: Vec<E> = Vec::new();
549
550        let data = claims_sum_vec;
551        transcript.reseed(H::hash_elements(&data));
552
553        let r_cord = transcript.draw().unwrap();
554
555        let p_poly_coef = vec![claims_sum_vec[0], claims_sum_vec[1]];
556        let q_poly_coef = vec![claims_sum_vec[2], claims_sum_vec[3]];
557
558        let p_poly = MultiLinear::new(p_poly_coef);
559        let q_poly = MultiLinear::new(q_poly_coef);
560        let p_eval = p_poly.evaluate(&[r_cord]);
561        let q_eval = q_poly.evaluate(&[r_cord]);
562
563        let mut reduced_claim = (p_eval, q_eval);
564
565        rand.push(r_cord);
566        for (num_rounds, i) in (0..num_layers).enumerate() {
567            let ((claim_last, rand_sumcheck), r_two_sumchecks) = self.proof[i]
568                .verify_sum_check_before_last::<_, _>(reduced_claim, num_rounds + 1, transcript);
569
570            let claims_sum_p1 = &self.proof[i].claims_sum_p1;
571            let claims_sum_p0 = &self.proof[i].claims_sum_p0;
572            let claims_sum_q1 = &self.proof[i].claims_sum_q1;
573            let claims_sum_q0 = &self.proof[i].claims_sum_q0;
574
575            let data = vec![
576                claims_sum_p1.clone(),
577                claims_sum_p0.clone(),
578                claims_sum_q1.clone(),
579                claims_sum_q0.clone(),
580            ];
581            transcript.reseed(H::hash_elements(&data));
582
583            assert_eq!(rand.len(), rand_sumcheck.len());
584
585            let eq: E = (0..rand.len())
586                .map(|i| {
587                    rand[i] * rand_sumcheck[i] + (E::ONE - rand[i]) * (E::ONE - rand_sumcheck[i])
588                })
589                .fold(E::ONE, |acc, term| acc * term);
590
591            let claim_expected: E = (*claims_sum_p1 * *claims_sum_q0
592                + *claims_sum_p0 * *claims_sum_q1
593                + r_two_sumchecks * *claims_sum_q1 * *claims_sum_q0)
594                * eq;
595
596            assert_eq!(claim_expected, claim_last);
597
598            // Produce a random challenge to condense claims into a single claim
599            let r_layer = transcript.draw().unwrap();
600
601            reduced_claim = (
602                *claims_sum_p1 + r_layer * (*claims_sum_p0 - *claims_sum_p1),
603                *claims_sum_q1 + r_layer * (*claims_sum_q0 - *claims_sum_q1),
604            );
605
606            // Collect the randomness' used for the current layer in order to construct the random
607            // point where the input multilinear polynomials were evaluated.
608            let mut ext = rand_sumcheck;
609            ext.push(r_layer);
610            rand = ext;
611        }
612        (reduced_claim, rand)
613    }
614
615    pub fn verify_virtual_bus<
616        C: RandomCoin<Hasher = H, BaseField = BaseElement>,
617        H: ElementHasher<BaseField = BaseElement>,
618    >(
619        &self,
620        composition_polys: Vec<Vec<Arc<dyn CompositionPolynomial<E>>>>,
621        final_layer_proof: super::sumcheck::FullProof<E>,
622        claims_sum_vec: &[E],
623        transcript: &mut C,
624    ) -> (FinalEvaluationClaim<E>, Vec<E>) {
625        let num_layers = self.proof.len() as usize;
626        let mut rand: Vec<E> = Vec::new();
627
628        // Check that a/b + d/e is equal to 0
629        assert_ne!(claims_sum_vec[2], E::ZERO);
630        assert_ne!(claims_sum_vec[3], E::ZERO);
631        assert_eq!(
632            claims_sum_vec[0] * claims_sum_vec[3] + claims_sum_vec[1] * claims_sum_vec[2],
633            E::ZERO
634        );
635
636        let data = claims_sum_vec;
637        transcript.reseed(H::hash_elements(&data));
638
639        let r_cord = transcript.draw().unwrap();
640
641        let p_poly_coef = vec![claims_sum_vec[0], claims_sum_vec[1]];
642        let q_poly_coef = vec![claims_sum_vec[2], claims_sum_vec[3]];
643
644        let p_poly = MultiLinear::new(p_poly_coef);
645        let q_poly = MultiLinear::new(q_poly_coef);
646        let p_eval = p_poly.evaluate(&[r_cord]);
647        let q_eval = q_poly.evaluate(&[r_cord]);
648
649        let mut reduced_claim = (p_eval, q_eval);
650
651        // I) Verify all GKR layers but for the last one counting backwards.
652        rand.push(r_cord);
653        for (num_rounds, i) in (0..num_layers).enumerate() {
654            let ((claim_last, rand_sumcheck), r_two_sumchecks) = self.proof[i]
655                .verify_sum_check_before_last::<_, _>(reduced_claim, num_rounds + 1, transcript);
656
657            let claims_sum_p1 = &self.proof[i].claims_sum_p1;
658            let claims_sum_p0 = &self.proof[i].claims_sum_p0;
659            let claims_sum_q1 = &self.proof[i].claims_sum_q1;
660            let claims_sum_q0 = &self.proof[i].claims_sum_q0;
661
662            let data = vec![
663                claims_sum_p1.clone(),
664                claims_sum_p0.clone(),
665                claims_sum_q1.clone(),
666                claims_sum_q0.clone(),
667            ];
668            transcript.reseed(H::hash_elements(&data));
669
670            assert_eq!(rand.len(), rand_sumcheck.len());
671
672            let eq: E = (0..rand.len())
673                .map(|i| {
674                    rand[i] * rand_sumcheck[i] + (E::ONE - rand[i]) * (E::ONE - rand_sumcheck[i])
675                })
676                .fold(E::ONE, |acc, term| acc * term);
677
678            let claim_expected: E = (*claims_sum_p1 * *claims_sum_q0
679                + *claims_sum_p0 * *claims_sum_q1
680                + r_two_sumchecks * *claims_sum_q1 * *claims_sum_q0)
681                * eq;
682
683            assert_eq!(claim_expected, claim_last);
684
685            // Produce a random challenge to condense claims into a single claim
686            let r_layer = transcript.draw().unwrap();
687
688            reduced_claim = (
689                *claims_sum_p1 + r_layer * (*claims_sum_p0 - *claims_sum_p1),
690                *claims_sum_q1 + r_layer * (*claims_sum_q0 - *claims_sum_q1),
691            );
692
693            let mut ext = rand_sumcheck;
694            ext.push(r_layer);
695            rand = ext;
696        }
697
698        // II) Verify the final GKR layer counting backwards.
699
700        // Absorb the claims
701        let data = vec![reduced_claim.0, reduced_claim.1];
702        transcript.reseed(H::hash_elements(&data));
703
704        // Squeeze challenge to reduce two sumchecks to one
705        let r_sum_check = transcript.draw().unwrap();
706        let reduced_claim = reduced_claim.0 + reduced_claim.1 * r_sum_check;
707
708        let gkr_final_composed_ml = gkr_composition_from_composition_polys(
709            &composition_polys,
710            r_sum_check,
711            1 << (num_layers + 1),
712        );
713
714        // TODO: refactor
715        let composed_ml_oracle = {
716            let left_num_oracle = MultiLinearOracle { id: 0 };
717            let right_num_oracle = MultiLinearOracle { id: 1 };
718            let left_denom_oracle = MultiLinearOracle { id: 2 };
719            let right_denom_oracle = MultiLinearOracle { id: 3 };
720            let eq_oracle = MultiLinearOracle { id: 4 };
721            ComposedMultiLinearsOracle {
722                composer: (Arc::new(gkr_final_composed_ml.clone())),
723                multi_linears: vec![
724                    eq_oracle,
725                    left_num_oracle,
726                    right_num_oracle,
727                    left_denom_oracle,
728                    right_denom_oracle,
729                ],
730            }
731        };
732
733        let claim = Claim {
734            sum_value: reduced_claim,
735            polynomial: composed_ml_oracle.clone(),
736        };
737
738        let final_eval_claim = sum_check_verify(&claim, final_layer_proof, transcript);
739
740        (final_eval_claim, rand)
741    }
742}
743
744fn sum_check_prover_gkr_before_last<
745    E: FieldElement<BaseField = BaseElement>,
746    C: RandomCoin<Hasher = H, BaseField = BaseElement>,
747    H: ElementHasher<BaseField = BaseElement>,
748>(
749    claim: (E, E),
750    num_rounds: usize,
751    ml_polys: (
752        &mut MultiLinear<E>,
753        &mut MultiLinear<E>,
754        &mut MultiLinear<E>,
755        &mut MultiLinear<E>,
756        &mut MultiLinear<E>,
757    ),
758    comb_func: impl Fn(&E, &E, &E, &E, &E, &E) -> E,
759    transcript: &mut C,
760) -> (SumcheckInstanceProof<E>, Vec<E>, (E, E, E, E, E)) {
761    // Absorb the claims
762    let data = vec![claim.0, claim.1];
763    transcript.reseed(H::hash_elements(&data));
764
765    // Squeeze challenge to reduce two sumchecks to one
766    let r_sum_check = transcript.draw().unwrap();
767
768    let (poly_a, poly_b, poly_c, poly_d, poly_x) = ml_polys;
769
770    let mut e = claim.0 + claim.1 * r_sum_check;
771
772    let mut r: Vec<E> = Vec::new();
773    let mut round_proofs: Vec<SumCheckRoundProof<E>> = Vec::new();
774
775    for _j in 0..num_rounds {
776        let evals: (E, E, E) = {
777            let mut eval_point_0 = E::ZERO;
778            let mut eval_point_2 = E::ZERO;
779            let mut eval_point_3 = E::ZERO;
780
781            let len = poly_a.len() / 2;
782            for i in 0..len {
783                // The interpolation formula for a linear function is:
784                // z * A(x) + (1 - z) * A (y)
785                // z * A(1) + (1 - z) * A(0)
786
787                // eval at z = 0: A(1)
788                eval_point_0 += comb_func(
789                    &poly_a[i << 1],
790                    &poly_b[i << 1],
791                    &poly_c[i << 1],
792                    &poly_d[i << 1],
793                    &poly_x[i << 1],
794                    &r_sum_check,
795                );
796
797                let poly_a_u = poly_a[(i << 1) + 1];
798                let poly_a_v = poly_a[i << 1];
799                let poly_b_u = poly_b[(i << 1) + 1];
800                let poly_b_v = poly_b[i << 1];
801                let poly_c_u = poly_c[(i << 1) + 1];
802                let poly_c_v = poly_c[i << 1];
803                let poly_d_u = poly_d[(i << 1) + 1];
804                let poly_d_v = poly_d[i << 1];
805                let poly_x_u = poly_x[(i << 1) + 1];
806                let poly_x_v = poly_x[i << 1];
807
808                // eval at z = 2: 2 * A(1) - A(0)
809                let poly_a_extrapolated_point = poly_a_u + poly_a_u - poly_a_v;
810                let poly_b_extrapolated_point = poly_b_u + poly_b_u - poly_b_v;
811                let poly_c_extrapolated_point = poly_c_u + poly_c_u - poly_c_v;
812                let poly_d_extrapolated_point = poly_d_u + poly_d_u - poly_d_v;
813                let poly_x_extrapolated_point = poly_x_u + poly_x_u - poly_x_v;
814                eval_point_2 += comb_func(
815                    &poly_a_extrapolated_point,
816                    &poly_b_extrapolated_point,
817                    &poly_c_extrapolated_point,
818                    &poly_d_extrapolated_point,
819                    &poly_x_extrapolated_point,
820                    &r_sum_check,
821                );
822
823                // eval at z = 3: 3 * A(1) - 2 * A(0) = 2 * A(1) - A(0) + A(1) - A(0)
824                // hence we can compute the evaluation at z + 1 from that of z for z > 1
825                let poly_a_extrapolated_point = poly_a_extrapolated_point + poly_a_u - poly_a_v;
826                let poly_b_extrapolated_point = poly_b_extrapolated_point + poly_b_u - poly_b_v;
827                let poly_c_extrapolated_point = poly_c_extrapolated_point + poly_c_u - poly_c_v;
828                let poly_d_extrapolated_point = poly_d_extrapolated_point + poly_d_u - poly_d_v;
829                let poly_x_extrapolated_point = poly_x_extrapolated_point + poly_x_u - poly_x_v;
830
831                eval_point_3 += comb_func(
832                    &poly_a_extrapolated_point,
833                    &poly_b_extrapolated_point,
834                    &poly_c_extrapolated_point,
835                    &poly_d_extrapolated_point,
836                    &poly_x_extrapolated_point,
837                    &r_sum_check,
838                );
839            }
840
841            (eval_point_0, eval_point_2, eval_point_3)
842        };
843
844        let eval_0 = evals.0;
845        let eval_2 = evals.1;
846        let eval_3 = evals.2;
847
848        let evals = vec![e - eval_0, eval_2, eval_3];
849        let compressed_poly = SumCheckRoundProof { poly_evals: evals };
850
851        // append the prover's message to the transcript
852        transcript.reseed(H::hash_elements(&compressed_poly.poly_evals));
853
854        // derive the verifier's challenge for the next round
855        let r_j = transcript.draw().unwrap();
856        r.push(r_j);
857
858        poly_a.bind_assign(r_j);
859        poly_b.bind_assign(r_j);
860        poly_c.bind_assign(r_j);
861        poly_d.bind_assign(r_j);
862
863        poly_x.bind_assign(r_j);
864
865        e = compressed_poly.evaluate(e, r_j);
866
867        round_proofs.push(compressed_poly);
868    }
869    let claims_sum = (poly_a[0], poly_b[0], poly_c[0], poly_d[0], poly_x[0]);
870
871    (SumcheckInstanceProof { round_proofs }, r, claims_sum)
872}
873
874#[cfg(test)]
875mod sum_circuit_tests {
876    use crate::rand::RpoRandomCoin;
877
878    use super::*;
879    use rand::Rng;
880    use rand_utils::rand_value;
881    use BaseElement as Felt;
882
883    /// The following tests the fractional sum circuit to check that \sum_{i = 0}^{log(m)-1} m / 2^{i} = 2 * (m - 1)
884    #[test]
885    fn sum_circuit_example() {
886        let n = 4; // n := log(m)
887        let mut inp: Vec<Felt> = (0..n).map(|_| Felt::from(1_u64 << n)).collect();
888        let inp_: Vec<Felt> = (0..n).map(|i| Felt::from(1_u64 << i)).collect();
889        inp.extend(inp_.iter());
890
891        let summation = MultiLinear::new(inp);
892
893        let expected_output = Felt::from(2 * ((1_u64 << n) - 1));
894
895        let mut circuit = FractionalSumCircuit::new(&summation);
896
897        let seed = [BaseElement::ZERO; 4];
898        let mut transcript = RpoRandomCoin::new(seed.into());
899
900        let (proof, _evals, _) = CircuitProof::prove(&mut circuit, &mut transcript);
901
902        let (p1, q1) = circuit.evaluate(Felt::from(1_u8));
903        let (p0, q0) = circuit.evaluate(Felt::from(0_u8));
904        assert_eq!(expected_output, (p1 * q0 + q1 * p0) / (q1 * q0));
905
906        let seed = [BaseElement::ZERO; 4];
907        let mut transcript = RpoRandomCoin::new(seed.into());
908        let claims = vec![p0, p1, q0, q1];
909        proof.verify(&claims, &mut transcript);
910    }
911
912    // Test the fractional sum GKR in the context of LogUp.
913    #[test]
914    fn log_up() {
915        use rand::distributions::Slice;
916
917        let n: usize = 16;
918        let num_w: usize = 31; // This should be of the form 2^k - 1
919        let rng = rand::thread_rng();
920
921        let t_table: Vec<u32> = (0..(1 << n)).collect();
922        let mut m_table: Vec<u32> = (0..(1 << n)).map(|_| 0).collect();
923
924        let t_table_slice = Slice::new(&t_table).unwrap();
925
926        // Construct the witness columns. Uses sampling with replacement in order to have multiplicities
927        // different from 1.
928        let mut w_tables = Vec::new();
929        for _ in 0..num_w {
930            let wi_table: Vec<u32> =
931                rng.clone().sample_iter(&t_table_slice).cloned().take(1 << n).collect();
932
933            // Construct the multiplicities
934            wi_table.iter().for_each(|w| {
935                m_table[*w as usize] += 1;
936            });
937            w_tables.push(wi_table)
938        }
939
940        // The numerators
941        let mut p: Vec<Felt> = m_table.iter().map(|m| Felt::from(*m as u32)).collect();
942        p.extend((0..(num_w * (1 << n))).map(|_| Felt::from(1_u32)).collect::<Vec<Felt>>());
943
944        // Sample the challenge alpha to construct the denominators.
945        let alpha = rand_value();
946
947        // Construct the denominators
948        let mut q: Vec<Felt> = t_table.iter().map(|t| Felt::from(*t) - alpha).collect();
949        for w_table in w_tables {
950            q.extend(w_table.iter().map(|w| alpha - Felt::from(*w)).collect::<Vec<Felt>>());
951        }
952
953        // Build the input to the fractional sum GKR circuit
954        p.extend(q);
955        let input = p;
956
957        let summation = MultiLinear::new(input);
958
959        let expected_output = Felt::from(0_u8);
960
961        let mut circuit = FractionalSumCircuit::new(&summation);
962
963        let seed = [BaseElement::ZERO; 4];
964        let mut transcript = RpoRandomCoin::new(seed.into());
965
966        let (proof, _evals, _) = CircuitProof::prove(&mut circuit, &mut transcript);
967
968        let (p1, q1) = circuit.evaluate(Felt::from(1_u8));
969        let (p0, q0) = circuit.evaluate(Felt::from(0_u8));
970        assert_eq!(expected_output, (p1 * q0 + q1 * p0) / (q1 * q0)); // This check should be part of verification
971
972        let seed = [BaseElement::ZERO; 4];
973        let mut transcript = RpoRandomCoin::new(seed.into());
974        let claims = vec![p0, p1, q0, q1];
975        proof.verify(&claims, &mut transcript);
976    }
977}