From 51c7359dac92e20632a447746657864b5bb66377 Mon Sep 17 00:00:00 2001 From: Adrian Hamelink Date: Fri, 7 Jul 2023 15:17:16 +0200 Subject: [PATCH] - make `self` in `query_instance` mutable allows the keygen to compute size of advice cols - remove evaluation.rs - Add comments - Compute all column sizes during keygen --- halo2_proofs/src/dev.rs | 2 +- halo2_proofs/src/dev/cost.rs | 2 +- halo2_proofs/src/dev/graph.rs | 2 +- halo2_proofs/src/dev/graph/layout.rs | 2 +- halo2_proofs/src/plonk/circuit.rs | 2 +- halo2_proofs/src/plonk/keygen.rs | 2 +- halo2_proofs/src/plonk/prover.rs | 6 +- halo2_proofs/src/protostar.rs | 1 - halo2_proofs/src/protostar/error_check.rs | 336 ++++++--- halo2_proofs/src/protostar/evaluation.rs | 812 ---------------------- halo2_proofs/src/protostar/gate.rs | 146 ++-- halo2_proofs/src/protostar/keygen.rs | 111 ++- halo2_proofs/src/protostar/prover.rs | 112 +-- halo2_proofs/src/protostar/shuffle.rs | 34 +- halo2_proofs/src/protostar/witness.rs | 20 +- 15 files changed, 502 insertions(+), 1088 deletions(-) delete mode 100644 halo2_proofs/src/protostar/evaluation.rs diff --git a/halo2_proofs/src/dev.rs b/halo2_proofs/src/dev.rs index 8fe602f1..099bcf55 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -404,7 +404,7 @@ impl Assignment for MockProver { } fn query_instance( - &self, + &mut self, column: Column, row: usize, ) -> Result, Error> { diff --git a/halo2_proofs/src/dev/cost.rs b/halo2_proofs/src/dev/cost.rs index f2c4e9f2..deca030c 100644 --- a/halo2_proofs/src/dev/cost.rs +++ b/halo2_proofs/src/dev/cost.rs @@ -71,7 +71,7 @@ impl Assignment for Assembly { Ok(()) } - fn query_instance(&self, _: Column, _: usize) -> Result, Error> { + fn query_instance(&mut self, _: Column, _: usize) -> Result, Error> { Ok(Value::unknown()) } diff --git a/halo2_proofs/src/dev/graph.rs b/halo2_proofs/src/dev/graph.rs index 008d5840..ef8a96c3 100644 --- a/halo2_proofs/src/dev/graph.rs +++ b/halo2_proofs/src/dev/graph.rs @@ -112,7 +112,7 @@ impl Assignment for Graph { // Do nothing } - fn query_instance(&self, _: Column, _: usize) -> Result, Error> { + fn query_instance(&mut self, _: Column, _: usize) -> Result, Error> { Ok(Value::unknown()) } diff --git a/halo2_proofs/src/dev/graph/layout.rs b/halo2_proofs/src/dev/graph/layout.rs index 4782fd8a..f50d2694 100644 --- a/halo2_proofs/src/dev/graph/layout.rs +++ b/halo2_proofs/src/dev/graph/layout.rs @@ -437,7 +437,7 @@ impl Assignment for Layout { Ok(()) } - fn query_instance(&self, _: Column, _: usize) -> Result, Error> { + fn query_instance(&mut self, _: Column, _: usize) -> Result, Error> { Ok(Value::unknown()) } diff --git a/halo2_proofs/src/plonk/circuit.rs b/halo2_proofs/src/plonk/circuit.rs index eb8bdb31..0f83fa34 100644 --- a/halo2_proofs/src/plonk/circuit.rs +++ b/halo2_proofs/src/plonk/circuit.rs @@ -652,7 +652,7 @@ pub trait Assignment { /// Queries the cell of an instance column at a particular absolute row. /// /// Returns the cell's value, if known. - fn query_instance(&self, column: Column, row: usize) -> Result, Error>; + fn query_instance(&mut self, column: Column, row: usize) -> Result, Error>; /// Assign an advice column value (witness) fn assign_advice( diff --git a/halo2_proofs/src/plonk/keygen.rs b/halo2_proofs/src/plonk/keygen.rs index 91922b69..502c4072 100644 --- a/halo2_proofs/src/plonk/keygen.rs +++ b/halo2_proofs/src/plonk/keygen.rs @@ -90,7 +90,7 @@ impl Assignment for Assembly { Ok(()) } - fn query_instance(&self, _: Column, row: usize) -> Result, Error> { + fn query_instance(&mut self, _: Column, row: usize) -> Result, Error> { if !self.usable_rows.contains(&row) { return Err(Error::not_enough_rows_available(self.k)); } diff --git a/halo2_proofs/src/plonk/prover.rs b/halo2_proofs/src/plonk/prover.rs index 61392e95..89e56aa3 100644 --- a/halo2_proofs/src/plonk/prover.rs +++ b/halo2_proofs/src/plonk/prover.rs @@ -187,7 +187,11 @@ where // Do nothing } - fn query_instance(&self, column: Column, row: usize) -> Result, Error> { + fn query_instance( + &mut self, + column: Column, + row: usize, + ) -> Result, Error> { if !self.usable_rows.contains(&row) { return Err(Error::not_enough_rows_available(self.k)); } diff --git a/halo2_proofs/src/protostar.rs b/halo2_proofs/src/protostar.rs index e995727e..bd18e140 100644 --- a/halo2_proofs/src/protostar.rs +++ b/halo2_proofs/src/protostar.rs @@ -1,7 +1,6 @@ //! Contains logic for handling halo2 circuits with Protostar mod error_check; -mod evaluation; mod gate; mod keygen; mod prover; diff --git a/halo2_proofs/src/protostar/error_check.rs b/halo2_proofs/src/protostar/error_check.rs index c633e078..3c05dfa0 100644 --- a/halo2_proofs/src/protostar/error_check.rs +++ b/halo2_proofs/src/protostar/error_check.rs @@ -1,5 +1,5 @@ use core::num; -use std::collections::BTreeMap; +use std::{collections::BTreeMap, iter::zip}; use crate::poly::{commitment::Blind, empty_lagrange, LagrangeCoeff, Polynomial, Rotation}; use ff::Field; @@ -10,32 +10,83 @@ use super::{ keygen::{CircuitData, ProvingKey}, }; -/// +/// The "instance" portion of a Protostar accumulator +/// TODO(@adr1anh): Implement `Add` so we can combine two accumulators. #[derive(Clone)] pub struct AccumulatorInstance { + // Variable μ for homogenizing polynomials slack: F, + // Variable cⱼᵈ, where `j` is the challenge index, + // and `d` is its power in the polynomial constraint. + // cⱼᵈ = `challenges[j][d]` + // The number of "powers" `d` of each challenge is determined by + // looking at the maximum `d` over all `polys` in each `gate`. + // For each `j`, we have `challenges[j].len() = d+1`, + // since we include `challenge[j][0] = 1` challenges: Vec>, + // Public inputs and outputs of the circuit + // The shape of `instance` is determined by `CircuitData::num_instance_rows` instance: Vec>, } impl AccumulatorInstance { + /// Generate an empty accumulator with all values set to zero. pub fn new_empty(pk: &ProvingKey) -> Self { - let challenges = vec![F::ZERO; pk.circuit_data.cs.num_challenges]; - let instance = vec![ - empty_lagrange(pk.circuit_data.n as usize); - pk.circuit_data.cs.num_instance_columns - ]; - AccumulatorInstance::new(pk, challenges, instance) + // For each challenge `j`, with max degee `power`, + // we need `power + 1` elements + let challenges: Vec<_> = pk + .max_challenge_power + .iter() + .map(|power| vec![F::ZERO; *power + 1]) + .collect(); + + let instance = pk + .circuit_data + .num_instance_rows + .iter() + .map(|num_rows| empty_lagrange(*num_rows)) + .collect(); + + Self { + slack: F::ZERO, + challenges, + instance, + } } + // Create accumulator instance given data obtained by generating the witness. pub fn new( pk: &ProvingKey, challenges: Vec, instance: Vec>, ) -> Self { - let challenge_powers: Vec<_> = challenges - .iter() - .map(|c| powers_of(c, pk.max_degree)) + #[cfg(feature = "sanity-checks")] + { + assert_eq!( + challenges.len(), + pk.circuit_data.cs.num_challenges(), + "invalid number of challenges supplied" + ); + assert_eq!( + instance.len(), + pk.circuit_data.cs.num_instance_columns(), + "invalid number of instance columns supplied" + ); + for (i, (instance_len, expected_len)) in zip( + instance.iter().map(|instance| instance.len()), + pk.circuit_data.num_instance_rows.iter(), + ) + .enumerate() + { + assert_eq!( + instance_len, *expected_len, + "invalid size for instance column {i}" + ); + } + } + + let challenge_powers: Vec<_> = zip(challenges.iter(), pk.max_challenge_power.iter()) + .map(|(c, power)| powers_of(c, *power)) .collect(); Self { slack: F::ONE, @@ -45,19 +96,40 @@ impl AccumulatorInstance { } } #[derive(Clone)] +/// The "witness" portion of the accumulator containing the pub struct AccumulatorWitness { advice: Vec>, advice_blinds: Vec>, } impl AccumulatorWitness { - pub fn new_empty(n: usize, num_advice: usize) -> Self { + // Initialized a new empty `AccumulatorWitness` for a given circuit, + // with all values set to zero. + pub fn new_empty(pk: &ProvingKey) -> Self { + let advice = vec![empty_lagrange(pk.circuit_data.n); pk.circuit_data.cs.num_advice_columns]; + // let advice = pk + // .circuit_data + // .num_advice_rows + // .iter() + // .map(|advice_len| empty_lagrange(*advice_len)) + // .collect(); Self { - advice: vec![empty_lagrange(n); num_advice], - advice_blinds: vec![Blind::default(); num_advice], + advice, + advice_blinds: vec![Blind::default(); pk.circuit_data.cs.num_advice_columns], } } + + // Initializes a new `AccumulatorWitness` given a list of advice columns generated by the prover. pub fn new(advice: Vec>, advice_blinds: Vec>) -> Self { + #[cfg(feature = "sanity-checks")] + { + assert_eq!( + advice.len(), + advice_blinds.len(), + "number of advice columns and blinds must match" + ); + // TODO(@adr1anh): check the lengths of `advice` + } Self { advice, advice_blinds, @@ -66,83 +138,113 @@ impl AccumulatorWitness { } #[derive(Clone)] +/// When evaluating a `Gate` over all rows, we fetch the input data from the previous accumulator +/// and the new witness and store it in this struct. +/// This struct can be cloned when paralellizing the evaluation over chunks of rows, +/// so that each chunk operates over independent data. pub struct GateEvaluationCache { - gate: Gate, - // required number of evaluations for each gate + pub gate: Gate, + // Number of evaluations requried to recover each `poly` in `gate`. + // Equal to `poly.degree() + 1 + num_extra_evaluations`, + // which is the number of coefficients of the polynomial, + // plus any additional evaluations required if we want to multiply by another variable + // later on. num_evals: Vec, + // Maximum of `num_evals` max_num_evals: usize, - // precomputed powers of slack and challenges + // `slack_powers_evals[X][d]` = (μ' + X⋅μ)ᵈ, for + // - X = 0, 1, ..., max_num_evals - 1` + // - d = 0, 1, ..., gate.degrees.max() - 1` slack_powers_evals: Vec>, - challenge_powers_evals: Vec>, - // Cached data for a row + // `challenge_powers_evals[X][j][d]` = c'ⱼᵈ + X⋅cⱼᵈ + // - X = 0, 1, ..., max_num_evals - 1` + // - j = 0, 1, ..., num_challenges - 1` + // - d = 0, 1, ..., max_challenge_power[j] - 1` + challenge_powers_evals: Vec>>, + + // Cache for storing the fixed, accumulated, and witness values for evaluating this + // `gate` at a single row. + // + // If all `polys` are multiplied by the same `Selector`, store its value here + // and allow the evaluation of the gate to be skipped if it is `false` simple_selector: Option, + // Contains the remaining selectors inside `polys` defined by `gate.queried_selectors` selectors: Vec, + // Values for the row from `circuit_data` at the indices defined by `gate.queried_fixed` fixed: Vec, + // Values for the row from `acc` at the indices defined by `gate.queried_instance` instance_acc: Vec, + // Values for the row from `new` at the indices defined by `gate.queried_instance` instance_new: Vec, + // Values for the row from `acc` at the indices defined by `gate.queried_advice` advice_acc: Vec, + // Values for the row from `new` at the indices defined by `gate.queried_advice` advice_new: Vec, - // Local evaluation - // gate_eval[gate_idx][gate_deg] - gate_eval: Vec>, + // For each `poly` at index `j` in `gate`, + // store the evaluations of the error polynomial `e_j(X)` + // at X = 0, 1, ..., num_evals[j] - 1 in `gate_eval[j][X]` + pub gate_eval: Vec>, } impl GateEvaluationCache { + /// Initialize a chache for a given `Gate` for combining an existing accumulator with + /// the data from a new transcript. + /// Evaluations of the linear combination of the slack and challenge variables + /// are recomputed and stored locally to ensure no data races between different caches. + /// The `evaluate` method can compute additional evaluations defined by `num_extra_evaluations`, + /// so that the resulting evaluations can be multiplied afterwards, for example by the challenge `y`. pub fn new( gate: &Gate, gate_accumulator: &AccumulatorInstance, gate_transcript: &AccumulatorInstance, num_extra_evaluations: usize, ) -> Self { + // Each `poly` Gⱼ(X) has degree dⱼ and dⱼ+1 coefficients, + // therefore we need at least dⱼ+1 evaluations of Gⱼ(X) to recover + // the coefficients. let num_evals: Vec<_> = gate .degrees .iter() - .map(|d| d + num_extra_evaluations) + .map(|d| d + 1 + num_extra_evaluations) .collect(); - + // d = maxⱼ{dⱼ} let max_poly_degree = *gate.degrees.iter().max().unwrap(); - let max_num_evals = max_poly_degree + num_extra_evaluations; + // maximum number of evaluations over all Gⱼ(X) + let max_num_evals = max_poly_degree + 1 + num_extra_evaluations; - // The accumual - // Challenge evaluations: challenge_powers_evals[j][X][power] = - // challenges_acc[j][power] + X * challenges_new[j]^power + // Challenge evaluations: challenge_powers_evals[X][j][power] = + // challenges_acc[j][power] + X⋅challenges_new[j]^power // for + // X = 0, 1, ..., max_num_evals - 1 // j = 0, 1, ..., num_challenges - 1 - // X = 0, 1, ..., num_evals - 1 - // power = 0, 1, ..., max_poly_degree - 1 - let challenge_powers_evals: Vec<_> = { - let challenges_acc = &gate_accumulator.challenges; - let challenges_new = &gate_transcript.challenges; - debug_assert_eq!(challenges_acc.len(), challenges_new.len()); - - challenges_acc - .iter() - .zip(challenges_new.iter()) - .map(|(c_acc, c_new)| { - EvaluatedChallenge::new(c_acc, c_new, max_num_evals, max_poly_degree) - }) - .collect() - }; + // power = 0, 1, ..., max_poly_degree + let challenge_powers_evals = evaluated_challenge_powers( + &gate_accumulator.challenges, + &gate_transcript.challenges, + max_num_evals, + ); - // slack_powers_evals[X][power] = (slack + X)^power + // slack_powers_evals[X][power] = (acc.slack + X⋅new.slack)^power let slack_powers_evals = { - let slack = gate_accumulator.slack; - let mut slack_powers_evals = Vec::with_capacity(max_num_evals); - // X = 0 => slack_powers_evals[0] = [1, slack, slack^2, ...] - let mut acc = slack; + // X = 0 => slack_powers_evals[0] = [1, acc.slack, acc.slack^2, ...] + let mut acc = gate_accumulator.slack; slack_powers_evals.push(powers_of(&acc, max_poly_degree)); for _i in 1..max_num_evals { - acc += F::ONE; + // slack_powers_evals[X][power] = (acc.slack + X⋅new.slack)^power + acc += gate_transcript.slack; slack_powers_evals.push(powers_of(&acc, max_poly_degree)); } slack_powers_evals }; + // for each polynomial, allocate a buffer for storing all the evaluations + let gate_eval = num_evals.iter().map(|d| vec![F::ZERO; *d]).collect(); + Self { gate: gate.clone(), num_evals, @@ -156,11 +258,7 @@ impl GateEvaluationCache { instance_new: vec![F::ZERO; gate.queried_instance.len()], advice_acc: vec![F::ZERO; gate.queried_advice.len()], advice_new: vec![F::ZERO; gate.queried_advice.len()], - gate_eval: gate - .degrees - .iter() - .map(|d| vec![F::ZERO; *d + num_extra_evaluations]) - .collect(), + gate_eval, } } @@ -212,7 +310,7 @@ impl GateEvaluationCache { } // Fill advice - for (i, advice) in self.gate.queried_instance.iter().enumerate() { + for (i, advice) in self.gate.queried_advice.iter().enumerate() { let advice_column = advice.column_index(); let advice_row = get_rotation_idx(row, advice.rotation(), isize); self.advice_acc[i] = acc.advice[advice_column][advice_row]; @@ -220,8 +318,10 @@ impl GateEvaluationCache { } } - /// Evaluates the error polynomial for the populated row - /// WARN: `populate` must have been called before. + /// Evaluates the error polynomial for the populated row. + /// Returns `None` if the common selector for the gate is false, + /// otherwise returns a list of vectors containing the evaluations for + /// each `poly` Gⱼ(X) in `gate`. pub fn evaluate( &mut self, row: usize, @@ -230,6 +330,8 @@ impl GateEvaluationCache { acc: &AccumulatorWitness, new: &AccumulatorWitness, ) -> Option<&[Vec]> { + // Fill the buffers with data from the fixed circuit_data, the previous accumulator + // and the new witness. self.populate(row, isize, circuit_data, acc, new); // exit early if there is a simple selector and it is off @@ -239,32 +341,33 @@ impl GateEvaluationCache { } } let max_num_evals = self.max_num_evals; - let mut instance_tmp = self.instance_acc.to_vec(); - let mut advice_tmp = self.advice_acc.to_vec(); + // Use the `*_acc` buffers to store the linear combination evaluations. + // In the first iteration with X=0, it is already equal to `*_acc` + let instance_tmp = &mut self.instance_acc; + let advice_tmp = &mut self.advice_acc; - // TODO(@adr1anh): Check whether we are not off-by-one - // Iterate over all evaluations points X = 0, ..., d-1 + // Iterate over all evaluations points X = 0, ..., max_num_evals-1 for eval_idx in 0..max_num_evals { // After the first iteration, add the contents of the new instance and advice to the tmp buffer if eval_idx > 0 { - for (i_tmp, i_new) in instance_tmp.iter_mut().zip(self.instance_new.iter()) { + for (i_tmp, i_new) in zip(instance_tmp.iter_mut(), self.instance_new.iter()) { *i_tmp += i_new; } - for (a_tmp, a_new) in advice_tmp.iter_mut().zip(self.advice_new.iter()) { + for (a_tmp, a_new) in zip(advice_tmp.iter_mut(), self.advice_new.iter()) { *a_tmp += a_new; } } - // Iterate over each polynomial constraint + // Iterate over each polynomial constraint Gⱼ, along with its required number of evaluations for (poly_idx, (poly, num_evals)) in - self.gate.polys.iter().zip(&self.num_evals).enumerate() + zip(self.gate.polys.iter(), self.num_evals.iter()).enumerate() { - // if the degree of this constraint is less than the max degree, - // we don't need to evaluate it - if *num_evals > eval_idx { + // If the eval_idx X is larger than the required number of evaluations for the current poly, + // we don't evaluate it and continue to the next poly. + if eval_idx > *num_evals { continue; } // evaluate the j-th constraint G_j at X=d - self.gate_eval[poly_idx][eval_idx] = poly.evaluate( + let e = poly.evaluate( &|slack_power| self.slack_powers_evals[eval_idx][slack_power], &|constant| constant, &|selector_idx| self.selectors[selector_idx], @@ -272,69 +375,76 @@ impl GateEvaluationCache { &|advice_idx| advice_tmp[advice_idx], &|instance_idx| instance_tmp[instance_idx], &|challenge_idx, challenge_power| { - self.challenge_powers_evals[challenge_idx].powers_evals[eval_idx] - [challenge_power] + self.challenge_powers_evals[eval_idx][challenge_idx][challenge_power] }, &|negated| -negated, &|sum_a, sum_b| sum_a + sum_b, &|prod_a, prod_b| prod_a * prod_b, &|scaled, v| scaled * v, ); + self.gate_eval[poly_idx][eval_idx] = e; } } Some(&self.gate_eval) } } -/// -#[derive(Clone)] -struct EvaluatedChallenge { - // powers_evals[X][power] = acc[power] + X * new^power - pub powers_evals: Vec>, +/// Given two lists of challenge powers from an existing accumulator and a new circuit execution, +/// compute the evaluations at X = 0, 1, ..., num_evals-1 of "challenges_acc + X⋅challenges_new". +/// The returned vector satisfies: +/// challenge_powers_evals[X][j][power] = challenges_acc[j][power] + X⋅challenges_new[j]^power +/// for +/// X = 0, 1, ..., num_evals - 1 +/// j = 0, 1, ..., num_challenges - 1 +/// power = 0, 1, ..., +pub fn evaluated_challenge_powers( + challenges_acc: &[Vec], + challenges_new: &[Vec], + num_evals: usize, +) -> Vec>> { + debug_assert_eq!( + challenges_acc.len(), + challenges_new.len(), + "number of challenges in both accumulators must be the same" + ); + + let mut challenge_powers_evals = Vec::with_capacity(num_evals); + + // Add the evaluation of the challenge variables at X=0, + // corresponding to challenges_acc + challenge_powers_evals.push(challenges_acc.to_vec()); + + // Iterate over eval = X = 1, ..., num_evals - 1 + for eval in 1..num_evals { + // Get previous evalutions at X-1 + let prev_evals = &challenge_powers_evals[eval - 1]; + // compute next row by adding `new` to the previous row + let curr_eval = zip(prev_evals.iter(), challenges_new.iter()) + .map(|(prev_powers_j, new_powers_j)| { + // `prev_powers_j` and `new_powers_j` are vectors of the powers of the challenge j + // this loop adds them up to get the evaluation + zip(prev_powers_j.iter(), new_powers_j.iter()) + .map(|(prev, new)| *prev + new) + .collect() + }) + .collect(); + + challenge_powers_evals.push(curr_eval); + } + + challenge_powers_evals } -fn powers_of(v: &F, num_powers: usize) -> Vec { - let mut powers = Vec::with_capacity(num_powers); +/// Computes [1, v, v^2, ..., v^max_degee] +fn powers_of(v: &F, max_degee: usize) -> Vec { + let mut powers = Vec::with_capacity(max_degee + 1); let mut acc = F::ONE; powers.push(acc); - for _i in 1..num_powers { + // we need max_degee + 1 since we include v^0 = 1 + for _i in 1..max_degee + 1 { acc *= v; powers.push(acc); } powers } - -impl EvaluatedChallenge { - fn new(acc: &[F], new: &[F], num_evals: usize, max_degree: usize) -> Self { - debug_assert_eq!(acc.len(), new.len()); - // debug_assert!( - // acc.len() <= max_degree, - // "number of accumulated challenge powers must be at least `max_degree`" - // ); - // d = max_degree, D = num_evals - // [ - // [1 , acc_1 , ..., acc_{d-1} ], - // [1 + 1, acc_1 + new, ..., acc_{d-1} + new^{d-1}], - // ..., - // [1 + D * 1, acc_1 + D * new, ..., acc_{d-1} + D * new^{d-1}], - // ] - - let mut powers_evals = Vec::with_capacity(num_evals); - // set first row for D=0 - debug_assert_eq!(acc[0], F::ONE); - powers_evals.push(acc.to_vec()); - - for eval in 1..max_degree { - // compute next row by adding `new` to the previous row - powers_evals.push( - powers_evals[eval - 1] - .iter() - .zip(new.iter()) - .map(|(prev, new)| *prev + new) - .collect(), - ); - } - EvaluatedChallenge { powers_evals } - } -} diff --git a/halo2_proofs/src/protostar/evaluation.rs b/halo2_proofs/src/protostar/evaluation.rs deleted file mode 100644 index c362433e..00000000 --- a/halo2_proofs/src/protostar/evaluation.rs +++ /dev/null @@ -1,812 +0,0 @@ -// use crate::plonk::permutation::Argument; -// use crate::plonk::{permutation, AdviceQuery, Any, Challenge, FixedQuery, InstanceQuery}; -// use crate::poly::Basis; -// use crate::protostar::expression::Expr; -// use crate::{ -// arithmetic::{eval_polynomial, parallelize, CurveAffine}, -// poly::{ -// commitment::Params, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, -// Polynomial, ProverQuery, Rotation, -// }, -// transcript::{EncodedChallenge, TranscriptWrite}, -// }; -// use crate::{multicore, poly::empty_lagrange}; -// use group::prime::PrimeCurve; -// use group::{ -// ff::{BatchInvert, Field, PrimeField, WithSmallOrderMulGroup}, -// Curve, -// }; -// use std::any::TypeId; -// use std::convert::TryInto; -// use std::num::ParseIntError; -// use std::slice; -// use std::{ -// collections::BTreeMap, -// iter, -// ops::{Index, Mul, MulAssign}, -// }; - -// use crate::plonk::circuit::{ConstraintSystem, Expression}; - -// use super::keygen::CircuitData; - -// /// Return the index in the polynomial of size `isize` after rotation `rot`. -// // TODO(@adr1anh) always inline -// fn get_rotation_idx(idx: usize, rot: i32, isize: i32) -> usize { -// (((idx as i32) + rot).rem_euclid(isize)) as usize -// } - -// /// Value used in a calculation -// #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd)] -// pub enum ValueSource { -// /// This is a slack variable whose purpose is to make the equation homogeneous -// Slack(usize), -// /// This is a constant value -// Constant(usize), -// /// This is an intermediate value -// Intermediate(usize), -// /// This is a selector column -// /// TODO(@adr1anh): Find out how to remove rotation which is always curr -// Selector(usize), -// /// This is a fixed column -// Fixed(usize, usize), -// /// This is an advice (witness) column -// Advice(usize, usize), -// /// This is an instance (external) column -// Instance(usize, usize), -// /// This is a challenge, where the second parameter represents the power of a Challenge. -// /// Two challenges with different powers are treated as separate variables. -// Challenge(usize, usize), -// /// beta -// Beta(), -// /// gamma -// Gamma(), -// /// theta -// Theta(), -// /// y -// Y(), -// /// Previous value -// PreviousValue(), -// } - -// impl Default for ValueSource { -// fn default() -> Self { -// ValueSource::Constant(0) -// } -// } - -// impl ValueSource { -// /// Get the value for this source -// pub fn get( -// &self, -// rotations: &[usize], -// slacks: &[F], -// constants: &[F], -// intermediates: &[F], -// selectors: &[Vec], -// fixed_values: &[Polynomial], -// advice_values: &[Polynomial], -// instance_values: &[Polynomial], -// challenges: &[&[F]], -// beta: &F, -// gamma: &F, -// theta: &F, -// y: &F, -// previous_value: &F, -// ) -> F { -// match self { -// ValueSource::Slack(idx) => slacks[*idx], -// ValueSource::Constant(idx) => constants[*idx], -// ValueSource::Intermediate(idx) => intermediates[*idx], -// ValueSource::Selector(column_index, rotation) => { -// if selectors[*column_index][rotations[*rotation]] { -// F::ONE -// } else { -// F::ZERO -// } -// } -// ValueSource::Fixed(column_index, rotation) => { -// fixed_values[*column_index][rotations[*rotation]] -// } -// ValueSource::Advice(column_index, rotation) => { -// advice_values[*column_index][rotations[*rotation]] -// } -// ValueSource::Instance(column_index, rotation) => { -// instance_values[*column_index][rotations[*rotation]] -// } -// ValueSource::Challenge(index, power) => challenges[*index][*power - 1], -// ValueSource::Beta() => *beta, -// ValueSource::Gamma() => *gamma, -// ValueSource::Theta() => *theta, -// ValueSource::Y() => *y, -// ValueSource::PreviousValue() => *previous_value, -// } -// } -// } - -// /// Calculation -// #[derive(Clone, Debug, PartialEq, Eq)] -// pub enum Calculation { -// /// This is an addition -// Add(ValueSource, ValueSource), -// /// This is a subtraction -// Sub(ValueSource, ValueSource), -// /// This is a product -// Mul(ValueSource, ValueSource), -// /// This is a square -// Square(ValueSource), -// /// This is a double -// Double(ValueSource), -// /// This is a negation -// Negate(ValueSource), -// /// This is Horner's rule: `val = a; val = val * c + b[]` -// Horner(ValueSource, Vec, ValueSource), -// /// This is a simple assignment -// Store(ValueSource), -// } - -// impl Calculation { -// /// Get the resulting value of this calculation -// pub fn evaluate( -// &self, -// rotations: &[usize], -// slacks: &[F], -// constants: &[F], -// intermediates: &[F], -// selectors: &[Vec], -// fixed_values: &[Polynomial], -// advice_values: &[Polynomial], -// instance_values: &[Polynomial], -// challenges: &[&[F]], -// // challenges: &[&[F]], -// beta: &F, -// gamma: &F, -// theta: &F, -// y: &F, -// previous_value: &F, -// ) -> F { -// let get_value = |value: &ValueSource| { -// value.get( -// rotations, -// slacks, -// constants, -// intermediates, -// selectors, -// fixed_values, -// advice_values, -// instance_values, -// challenges, -// beta, -// gamma, -// theta, -// y, -// previous_value, -// ) -// }; -// match self { -// Calculation::Add(a, b) => get_value(a) + get_value(b), -// Calculation::Sub(a, b) => get_value(a) - get_value(b), -// Calculation::Mul(a, b) => get_value(a) * get_value(b), -// Calculation::Square(v) => get_value(v).square(), -// Calculation::Double(v) => get_value(v).double(), -// Calculation::Negate(v) => -get_value(v), -// Calculation::Horner(start_value, parts, factor) => { -// // This should probably be a panic ? -// let factor = get_value(factor); -// let mut value = get_value(start_value); -// for part in parts.iter() { -// value = value * factor + get_value(part); -// } -// value -// } -// Calculation::Store(v) => get_value(v), -// } -// } -// } - -// /// Evaluator -// #[derive(Clone, Default, Debug)] -// pub struct Evaluator { -// /// Custom gates evalution -// pub custom_gates: GraphEvaluator, -// /// Lookups evalution -// pub lookups: Vec>, -// } - -// /// GraphEvaluator -// #[derive(Clone, Debug)] -// pub struct GraphEvaluator { -// /// Slacks -// pub slacks: Vec, -// /// Constants -// pub constants: Vec, -// /// Rotations -// pub rotations: Vec, -// /// Calculations -// pub calculations: Vec, -// /// Number of intermediates -// pub num_intermediates: usize, -// } - -// /// EvaluationData -// #[derive(Default, Debug)] -// pub struct EvaluationData { -// /// Intermediates -// pub intermediates: Vec, -// /// Rotations -// pub rotations: Vec, -// } - -// /// CaluclationInfo -// #[derive(Clone, Debug)] -// pub struct CalculationInfo { -// /// Calculation -// pub calculation: Calculation, -// /// Target -// pub target: usize, -// } - -// impl Evaluator { -// /// Creates a new evaluation structure -// pub fn new(cs: &ConstraintSystem) -> Self { -// let mut ev = Evaluator::default(); - -// // Custom gates -// let mut parts = Vec::new(); -// for gate in cs.gates.iter() { -// parts.extend(gate.polynomials().iter().map(|poly| { -// let e = Expr::::from(poly.clone()); -// ev.custom_gates.add_expression(&e) -// })); -// } -// ev.custom_gates.add_calculation(Calculation::Horner( -// ValueSource::PreviousValue(), -// parts, -// ValueSource::Y(), -// )); - -// // TODO(@adr1anh) -// // // Lookups -// // for lookup in cs.lookups.iter() { -// // let mut graph = GraphEvaluator::default(); - -// // let mut evaluate_lc = |expressions: &Vec>| { -// // let parts = expressions -// // .iter() -// // .map(|expr| graph.add_expression(expr)) -// // .collect(); -// // graph.add_calculation(Calculation::Horner( -// // ValueSource::Constant(0), -// // parts, -// // ValueSource::Theta(), -// // )) -// // }; - -// // // Input coset -// // // Map halo2 Expression to Protostar Expression -// // let compressed_input_coset = evaluate_lc( -// // &lookup -// // .input_expressions -// // .iter() -// // .map(|expr| Expr::from(*expr)) -// // .collect(), -// // ); -// // // table coset -// // // Map halo2 Expression to Protostar Expression -// // let compressed_table_coset = evaluate_lc( -// // &lookup -// // .table_expressions -// // .iter() -// // .map(|expr| Expr::from(*expr)) -// // .collect(), -// // ); -// // // z(\omega X) (a'(X) + \beta) (s'(X) + \gamma) -// // let right_gamma = graph.add_calculation(Calculation::Add( -// // compressed_table_coset, -// // ValueSource::Gamma(), -// // )); -// // let lc = graph.add_calculation(Calculation::Add( -// // compressed_input_coset, -// // ValueSource::Beta(), -// // )); -// // graph.add_calculation(Calculation::Mul(lc, right_gamma)); - -// // ev.lookups.push(graph); -// // } - -// ev -// } - -// /// Evaluate h poly -// fn evaluate_h( -// &self, -// pk: &CircuitData, -// advice: &[&[Polynomial]], -// instance: &[&[Polynomial]], -// challenges: &[&[C::ScalarExt]], -// y: C::ScalarExt, -// beta: C::ScalarExt, -// gamma: C::ScalarExt, -// theta: C::ScalarExt, -// // lookups: &[Vec>], -// permutations: &[permutation::prover::Committed], -// ) -> Polynomial { -// // TODO: extended domain can be removed, no ffts are needed -// let size = pk.n as usize; -// // let fixed = &pk.fixed_cosets[..]; -// // let extended_omega = domain.get_extended_omega(); -// let isize = size as i32; -// let one = C::ScalarExt::ONE; - -// let mut values = empty_lagrange(size); - -// // Core expression evaluations -// let num_threads = multicore::current_num_threads(); -// for (advice, instance) in advice.iter().zip(instance.iter()) -// // for ((advice, instance), lookups) in advice.iter().zip(instance.iter()).zip(lookups.iter()) -// { -// multicore::scope(|scope| { -// let chunk_size = (size + num_threads - 1) / num_threads; -// for (thread_idx, values) in values.chunks_mut(chunk_size).enumerate() { -// let start = thread_idx * chunk_size; -// scope.spawn(move |_| { -// let mut eval_data = self.custom_gates.instance(); -// for (i, value) in values.iter_mut().enumerate() { -// let idx = start + i; -// *value = self.custom_gates.evaluate( -// &mut eval_data, -// &pk.selectors, -// &pk.fixed, -// advice, -// instance, -// challenges, -// &beta, -// &gamma, -// &theta, -// &y, -// value, -// idx, -// isize, -// ); -// } -// }); -// } -// }); - -// // // Permutations - -// // // Lookups -// // for (n, lookup) in lookups.iter().enumerate() { -// // // Polynomials required for this lookup. -// // // Calculated here so these only have to be kept in memory for the short time -// // // they are actually needed. -// // let product_coset = pk.vk.domain.coeff_to_extended(lookup.product_poly.clone()); -// // let permuted_input_coset = pk -// // .vk -// // .domain -// // .coeff_to_extended(lookup.permuted_input_poly.clone()); -// // let permuted_table_coset = pk -// // .vk -// // .domain -// // .coeff_to_extended(lookup.permuted_table_poly.clone()); - -// // // Lookup constraints -// // parallelize(&mut values, |values, start| { -// // let lookup_evaluator = &self.lookups[n]; -// // let mut eval_data = lookup_evaluator.instance(); -// // for (i, value) in values.iter_mut().enumerate() { -// // let idx = start + i; - -// // let table_value = lookup_evaluator.evaluate( -// // &mut eval_data, -// // fixed, -// // advice, -// // instance, -// // challenges, -// // &beta, -// // &gamma, -// // &theta, -// // &y, -// // &C::ScalarExt::ZERO, -// // idx, -// // rot_scale, -// // isize, -// // ); - -// // let r_next = get_rotation_idx(idx, 1, rot_scale, isize); -// // let r_prev = get_rotation_idx(idx, -1, rot_scale, isize); - -// // let a_minus_s = permuted_input_coset[idx] - permuted_table_coset[idx]; -// // // l_0(X) * (1 - z(X)) = 0 -// // *value = *value * y + ((one - product_coset[idx]) * l0[idx]); -// // // l_last(X) * (z(X)^2 - z(X)) = 0 -// // *value = *value * y -// // + ((product_coset[idx] * product_coset[idx] - product_coset[idx]) -// // * l_last[idx]); -// // // (1 - (l_last(X) + l_blind(X))) * ( -// // // z(\omega X) (a'(X) + \beta) (s'(X) + \gamma) -// // // - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta) -// // // (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma) -// // // ) = 0 -// // *value = *value * y -// // + ((product_coset[r_next] -// // * (permuted_input_coset[idx] + beta) -// // * (permuted_table_coset[idx] + gamma) -// // - product_coset[idx] * table_value) -// // * l_active_row[idx]); -// // // Check that the first values in the permuted input expression and permuted -// // // fixed expression are the same. -// // // l_0(X) * (a'(X) - s'(X)) = 0 -// // *value = *value * y + (a_minus_s * l0[idx]); -// // // Check that each value in the permuted lookup input expression is either -// // // equal to the value above it, or the value at the same index in the -// // // permuted table expression. -// // // (1 - (l_last + l_blind)) * (a′(X) − s′(X))⋅(a′(X) − a′(\omega^{-1} X)) = 0 -// // *value = *value * y -// // + (a_minus_s -// // * (permuted_input_coset[idx] - permuted_input_coset[r_prev]) -// // * l_active_row[idx]); -// // } -// // }); -// // } -// } -// values -// } -// } - -// impl Default for GraphEvaluator { -// fn default() -> Self { -// Self { -// slacks: Vec::new(), -// // Fixed positions to allow easy access -// constants: vec![ -// C::ScalarExt::ZERO, -// C::ScalarExt::ONE, -// C::ScalarExt::from(2u64), -// ], -// rotations: Vec::new(), -// calculations: Vec::new(), -// num_intermediates: 0, -// } -// } -// } - -// impl GraphEvaluator { -// /// Adds a rotation -// fn add_rotation(&mut self, rotation: &Rotation) -> usize { -// let position = self.rotations.iter().position(|&c| c == rotation.0); -// match position { -// Some(pos) => pos, -// None => { -// self.rotations.push(rotation.0); -// self.rotations.len() - 1 -// } -// } -// } - -// /// Adds a constant -// fn add_constant(&mut self, constant: &C::ScalarExt) -> ValueSource { -// let position = self.constants.iter().position(|&c| c == *constant); -// ValueSource::Constant(match position { -// Some(pos) => pos, -// None => { -// self.constants.push(*constant); -// self.constants.len() - 1 -// } -// }) -// } - -// /// Adds a calculation. -// /// Currently does the simplest thing possible: just stores the -// /// resulting value so the result can be reused when that calculation -// /// is done multiple times. -// fn add_calculation(&mut self, calculation: Calculation) -> ValueSource { -// let existing_calculation = self -// .calculations -// .iter() -// .find(|c| c.calculation == calculation); -// match existing_calculation { -// Some(existing_calculation) => ValueSource::Intermediate(existing_calculation.target), -// None => { -// let target = self.num_intermediates; -// self.calculations.push(CalculationInfo { -// calculation, -// target, -// }); -// self.num_intermediates += 1; -// ValueSource::Intermediate(target) -// } -// } -// } - -// /// Generates an optimized evaluation for the expression -// fn add_expression(&mut self, expr: &Expr) -> ValueSource { -// match expr { -// Expr::Slack(d) => self.add_calculation(Calculation::Store(ValueSource::Slack(*d))), -// Expr::Constant(scalar) => self.add_constant(scalar), -// Expr::Selector(selector) => { -// self.add_calculation(Calculation::Store(ValueSource::Selector(selector.index()))) -// } -// Expr::Fixed(query) => { -// let rot_idx = self.add_rotation(&query.rotation); -// self.add_calculation(Calculation::Store(ValueSource::Fixed( -// query.column_index, -// rot_idx, -// ))) -// } -// Expr::Advice(query) => { -// let rot_idx = self.add_rotation(&query.rotation); -// self.add_calculation(Calculation::Store(ValueSource::Advice( -// query.column_index, -// rot_idx, -// ))) -// } -// Expr::Instance(query) => { -// let rot_idx = self.add_rotation(&query.rotation); -// self.add_calculation(Calculation::Store(ValueSource::Instance( -// query.column_index, -// rot_idx, -// ))) -// } -// Expr::Challenge(value, power) => self.add_calculation(Calculation::Store( -// ValueSource::Challenge(value.index(), *power), -// )), -// Expr::Negated(a) => match **a { -// Expr::Constant(scalar) => self.add_constant(&-scalar), -// _ => { -// let result_a = self.add_expression(a); -// match result_a { -// ValueSource::Constant(0) => result_a, -// _ => self.add_calculation(Calculation::Negate(result_a)), -// } -// } -// }, -// Expr::Sum(a, b) => { -// // Undo subtraction stored as a + (-b) in expressions -// match &**b { -// Expr::Negated(b_int) => { -// let result_a = self.add_expression(a); -// let result_b = self.add_expression(b_int); -// if result_a == ValueSource::Constant(0) { -// self.add_calculation(Calculation::Negate(result_b)) -// } else if result_b == ValueSource::Constant(0) { -// result_a -// } else { -// self.add_calculation(Calculation::Sub(result_a, result_b)) -// } -// } -// _ => { -// let result_a = self.add_expression(a); -// let result_b = self.add_expression(b); -// if result_a == ValueSource::Constant(0) { -// result_b -// } else if result_b == ValueSource::Constant(0) { -// result_a -// } else if result_a <= result_b { -// self.add_calculation(Calculation::Add(result_a, result_b)) -// } else { -// self.add_calculation(Calculation::Add(result_b, result_a)) -// } -// } -// } -// } -// Expr::Product(a, b) => { -// let result_a = self.add_expression(a); -// let result_b = self.add_expression(b); -// if result_a == ValueSource::Constant(0) || result_b == ValueSource::Constant(0) { -// ValueSource::Constant(0) -// } else if result_a == ValueSource::Constant(1) { -// result_b -// } else if result_b == ValueSource::Constant(1) { -// result_a -// } else if result_a == ValueSource::Constant(2) { -// self.add_calculation(Calculation::Double(result_b)) -// } else if result_b == ValueSource::Constant(2) { -// self.add_calculation(Calculation::Double(result_a)) -// } else if result_a == result_b { -// self.add_calculation(Calculation::Square(result_a)) -// } else if result_a <= result_b { -// self.add_calculation(Calculation::Mul(result_a, result_b)) -// } else { -// self.add_calculation(Calculation::Mul(result_b, result_a)) -// } -// } -// Expr::Scaled(a, f) => { -// if *f == C::ScalarExt::ZERO { -// ValueSource::Constant(0) -// } else if *f == C::ScalarExt::ONE { -// self.add_expression(a) -// } else { -// let cst = self.add_constant(f); -// let result_a = self.add_expression(a); -// self.add_calculation(Calculation::Mul(result_a, cst)) -// } -// } -// } -// } - -// /// Creates a new evaluation structure -// pub fn instance(&self) -> EvaluationData { -// EvaluationData { -// intermediates: vec![C::ScalarExt::ZERO; self.num_intermediates], -// rotations: vec![0usize; self.rotations.len()], -// } -// } - -// pub fn evaluate( -// &self, -// data: &mut EvaluationData, -// selectors: &[Vec], -// fixed: &[Polynomial], -// advice: &[Polynomial], -// instance: &[Polynomial], -// challenges: &[&[C::ScalarExt]], -// beta: &C::ScalarExt, -// gamma: &C::ScalarExt, -// theta: &C::ScalarExt, -// y: &C::ScalarExt, -// previous_value: &C::ScalarExt, -// idx: usize, - -// isize: i32, -// ) -> C::ScalarExt { -// // All rotation index values -// for (rot_idx, rot) in self.rotations.iter().enumerate() { -// data.rotations[rot_idx] = get_rotation_idx(idx, *rot, isize); -// } -// let slack = C::ScalarExt::ONE; - -// // All calculations, with cached intermediate results -// for calc in self.calculations.iter() { -// data.intermediates[calc.target] = calc.calculation.evaluate( -// &data.rotations, -// &[slack], -// &self.constants, -// &data.intermediates, -// selectors, -// fixed, -// advice, -// instance, -// challenges, -// beta, -// gamma, -// theta, -// y, -// previous_value, -// ); -// } - -// // Return the result of the last calculation (if any) -// if let Some(calc) = self.calculations.last() { -// data.intermediates[calc.target] -// } else { -// C::ScalarExt::ZERO -// } -// } -// } - -// // this function is called once in plonk::lookup::prover::commit_permuted -// /// Simple evaluation of an expression -// pub fn evaluate( -// expression: &Expr, -// size: usize, -// selectors: &[Vec], -// fixed: &[Polynomial], -// advice: &[Polynomial], -// instance: &[Polynomial], -// slacks: &[F], -// challenges: &[&[&F]], -// ) -> Vec { -// let mut values = vec![F::ZERO; size]; -// let isize = size as i32; -// parallelize(&mut values, |values, start| { -// for (i, value) in values.iter_mut().enumerate() { -// let idx = start + i; -// *value = expression.evaluate( -// &|slack| slacks[slack], -// &|scalar| scalar, -// &|selector| { -// if selectors[selector.index()][idx] { -// F::ONE -// } else { -// F::ZERO -// } -// }, //TODO -// &|query| fixed[query.column_index][get_rotation_idx(idx, query.rotation.0, isize)], -// &|query| advice[query.column_index][get_rotation_idx(idx, query.rotation.0, isize)], -// &|query| { -// instance[query.column_index][get_rotation_idx(idx, query.rotation.0, isize)] -// }, -// &|challenge, degree| *challenges[challenge.index()][degree - 1], -// &|a| -a, -// &|a, b| a + &b, -// &|a, b| a * b, -// &|a, scalar| a * scalar, -// ); -// } -// }); -// values -// } - -// mod test { -// use core::num; - -// use crate::plonk::{sealed::Phase, ConstraintSystem, FirstPhase}; -// use crate::{halo2curves::pasta::Fp, plonk::sealed::SealedPhase}; - -// use super::*; -// use crate::plonk::sealed; -// use crate::plonk::Expression; -// use rand_core::{OsRng, RngCore}; - -// #[test] -// fn test_evaluate_h() { -// let rng = OsRng; - -// let num_challenges = 4; -// let degree = 5; - -// let challenges: Vec<_> = { -// let mut cs = ConstraintSystem::::default(); -// let _ = cs.advice_column(); -// let _ = cs.advice_column_in(FirstPhase); -// (0..num_challenges) -// .map(|_| cs.challenge_usable_after(FirstPhase)) -// .collect() -// }; -// let x = challenges[0]; -// let x_var: Expression = Expression::Challenge(x); - -// let coefficients: Vec<_> = (0..degree - 1) -// .map(|_| Expression::Constant(Fp::random(rng))) -// .collect(); - -// // Expression for the polynomial computed via Horner's method -// // F(X) = a0 + X(a1 + X(a2 + X(...(a{n-2} + Xa{n-1}))))) -// let horner_poly = coefficients -// .iter() -// .rev() -// .skip(1) -// .fold(coefficients.last().unwrap().clone(), |acc, a_i| { -// x_var.clone() * acc + a_i.clone() -// }); - -// // Distribute the challenge with power 0 -// let flattened_poly = Expr::from(horner_poly).distribute_challenge(&x, 0); -// let (homogenous_poly, _) = flattened_poly.homogenize(); - -// let eval_poly = homogenous_poly.evaluate( -// slack, -// constant, -// selector_column, -// fixed_column, -// advice_column, -// instance_column, -// challenge, -// negated, -// sum, -// product, -// scaled, -// ); -// // TODO(gnosed): -// // Evaluate the h(X) polynomial -// // let h_poly = pk.ev.evaluate_h( -// // pk, -// // &advice -// // .iter() -// // .map(|a| a.advice_polys.as_slice()) -// // .collect::>(), -// // &instance -// // .iter() -// // .map(|i| i.instance_polys.as_slice()) -// // .collect::>(), -// // &challenges, -// // *y, -// // *beta, -// // *gamma, -// // *theta, -// // &lookups, -// // &permutations, -// // ); -// } -// } diff --git a/halo2_proofs/src/protostar/gate.rs b/halo2_proofs/src/protostar/gate.rs index e567f508..0352e02b 100644 --- a/halo2_proofs/src/protostar/gate.rs +++ b/halo2_proofs/src/protostar/gate.rs @@ -9,10 +9,10 @@ use crate::{ use super::keygen::CircuitData; -/// A Protostar gate is augments the structure of a plonk::Gate to allow for more efficient evaluation +/// A Protostar gate augments the structure of a plonk::Gate to allow for more efficient evaluation. #[derive(Clone)] pub struct Gate { - // polynomial expressions where nodes point to indices of elements of `queried_*` vectors + // polynomial expressions Gⱼ where nodes point to indices of elements of `queried_*` vectors // top-level selector has been extracted into `simple_selectors` if the gate was obtained via `WithSelector` pub(super) polys: Vec>, // simple selector for toggling all polys @@ -28,14 +28,18 @@ pub struct Gate { pub(super) queried_advice: Vec, } +/// Undo `Constraints::WithSelector` and return the common top-level `Selector` along with the expressions it selects. +/// If no simple `Selector` is found, returns the original list of polynomials. fn extract_simple_selector( original_polys: &[Expression], ) -> (Vec>, Option) { let (mut polys, simple_selectors): (Vec<_>, Vec<_>) = original_polys .iter() .map(|poly| { + // Check whether the top node is a multiplication by a selector let (simple_selector, poly) = match poly { - // If the whole polynomial is multiplied by a simple selector, return it along with the expression it selects + // If the whole polynomial is multiplied by a simple selector, + // return it along with the expression it selects Expression::Product(e1, e2) => match (&**e1, &**e2) { (Expression::Selector(s), e) | (e, Expression::Selector(s)) => (Some(*s), e), _ => (None, poly), @@ -68,6 +72,11 @@ fn extract_simple_selector( } impl From<&crate::plonk::Gate> for Gate { + /// Create an augmented Protostar gate from a `plonk::Gate`. + /// - Extract the common top-level `Selector` if it exists + /// - Extract all queries, and replace leaves with indices to the queries stored in the gate + /// - Flatten challenges so that a product of the same challenge is replaced by a power of that challenge + /// - Homogenize the expression by introcuding a slack variable μ fn from(cs_gate: &crate::plonk::Gate) -> Gate { let mut selectors = BTreeSet::::new(); let mut fixed = BTreeSet::::new(); @@ -123,7 +132,6 @@ impl From<&crate::plonk::Gate> for Gate { // homogenize the expression by introducing slack variable, returning the degree too .map(|e| e.homogenize()) .unzip(); - // let max_degree = *degrees.iter().max().unwrap(); Gate { polys, @@ -139,7 +147,7 @@ impl From<&crate::plonk::Gate> for Gate { } /// Low-degree expression representing an identity that must hold over the committed columns. -#[derive(Debug, Clone)] +#[derive(Clone)] pub enum Expr { /// This is a slack variable whose purpose is to make the equation homogeneous. Slack(usize), @@ -168,6 +176,8 @@ pub enum Expr { } impl Expression { + /// Given lists of all leaves of the original `Expression`, create an `Expr` where the leaves + /// correspond to indices of the variable in the lists. pub fn to_expr( &self, selectors: &Vec, @@ -200,9 +210,11 @@ impl Expression { } impl Expr { + /// Given the indices of the challenges contained in `self`, + /// apply the challenge flattening to all challenges. fn flatten_challenges(self, challenges: &[usize]) -> Self { // for each challenge, flatten the tree and turn products of the challenge - // with powers of the challenge + // into powers of the challenge challenges .iter() .fold(self, |acc, c| acc.distribute_challenge(*c, 0)) @@ -264,7 +276,9 @@ impl Expr { } } - // Compute the degree where powers of challenges count as 1 + /// Compute the degree of `Expr`, considering + /// `Fixed`, `Advice`, `Instance`, `Challenge` and `Slack` as the variables. + /// Powers of challenges are counted as a degree-1 variable. fn degree(&self) -> usize { match self { Expr::Slack(d) => *d, @@ -281,20 +295,22 @@ impl Expr { } } - // Homogenizes self using powers of Expr::Slack, also returning the new degree. - // Assumes that the expression has not been homogenized yet + /// Homogenizes `self` using powers of `Expr::Slack`, also returning the new degree. pub fn homogenize(self) -> (Expr, usize) { + // TODO(@adr1anh): Test that this function idempotent match self { - Expr::Slack(_) => panic!("Should not contain existing slack variable"), Expr::Negated(e) => { let (e, d) = e.homogenize(); (Expr::Negated(e.into()), d) } Expr::Sum(e1, e2) => { + // Homogenize both branches let (mut e1, d1) = e1.homogenize(); let (mut e2, d2) = e2.homogenize(); let d = std::cmp::max(d1, d2); + // if either branch has a lesser homogeneous degree, multiply it by `Expr::Slack` + // so that both branches have the same degree `d`. e1 = if d1 < d { Expr::Product(Expr::Slack(d - d1).into(), e1.into()) } else { @@ -310,7 +326,6 @@ impl Expr { Expr::Product(e1, e2) => { let (e1, d1) = e1.homogenize(); let (e2, d2) = e2.homogenize(); - // otherwise increase the degree of e_prod to degree (Expr::Product(e1.into(), e2.into()), d1 + d2) } Expr::Scaled(e, v) => { @@ -324,7 +339,7 @@ impl Expr { } } - /// If self is homogeneous, return the degree, else None + /// If `self` is homogeneous, return the degree, else `None` fn homogeneous_degree(&self) -> Option { match self { Expr::Negated(e) => e.homogeneous_degree(), @@ -619,7 +634,8 @@ impl Expr { } } - fn traverse(&self, f: &mut impl FnMut(&Expr)) { + /// Applies `f` to all leaf nodes. + pub fn traverse(&self, f: &mut impl FnMut(&Expr)) { match self { Expr::Negated(e) => e.traverse(f), Expr::Sum(e1, e2) => { @@ -634,62 +650,58 @@ impl Expr { v => f(v), } } + + /// For a given `Challenge` node with index `challenge_idx`, + /// returns the maximum power for this challenge appearing in the expression. + pub fn max_challenge_power(&self, challenge_idx: usize) -> usize { + match self { + Expr::Slack(_) => 0, + Expr::Constant(_) => 0, + Expr::Selector(_) => 0, + Expr::Fixed(_) => 0, + Expr::Advice(_) => 0, + Expr::Instance(_) => 0, + Expr::Challenge(c, power) => { + if *c == challenge_idx { + *power + } else { + 0 + } + } + Expr::Negated(poly) => poly.max_challenge_power(challenge_idx), + Expr::Sum(a, b) => std::cmp::max( + a.max_challenge_power(challenge_idx), + b.max_challenge_power(challenge_idx), + ), + Expr::Product(a, b) => std::cmp::max( + a.max_challenge_power(challenge_idx), + b.max_challenge_power(challenge_idx), + ), + Expr::Scaled(poly, _) => poly.max_challenge_power(challenge_idx), + } + } } -// impl std::fmt::Debug for Expr { -// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -// match self { -// Expr::Slack(d) => f.debug_tuple("Slack").field(d).finish(), -// Expr::Constant(scalar) => f.debug_tuple("Constant").field(scalar).finish(), -// Expr::Selector(selector) => f.debug_tuple("Selector").field(selector).finish(), -// // Skip enum variant and print query struct directly to maintain backwards compatibility. -// Expr::Fixed(query) => { -// let mut debug_struct = f.debug_struct("Fixed"); -// match query.index { -// None => debug_struct.field("query_index", &query.index), -// Some(idx) => debug_struct.field("query_index", &idx), -// }; -// debug_struct -// .field("column_index", &query.column_index) -// .field("rotation", &query.rotation) -// .finish() -// } -// Expr::Advice(query) => { -// let mut debug_struct = f.debug_struct("Advice"); -// match query.index { -// None => debug_struct.field("query_index", &query.index), -// Some(idx) => debug_struct.field("query_index", &idx), -// }; -// debug_struct -// .field("column_index", &query.column_index) -// .field("rotation", &query.rotation); -// // Only show advice's phase if it's not in first phase. -// // if query.phase != FirstPhase.to_sealed() { -// // debug_struct.field("phase", &query.phase); -// // } -// debug_struct.finish() -// } -// Expr::Instance(query) => { -// let mut debug_struct = f.debug_struct("Instance"); -// match query.index { -// None => debug_struct.field("query_index", &query.index), -// Some(idx) => debug_struct.field("query_index", &idx), -// }; -// debug_struct -// .field("column_index", &query.column_index) -// .field("rotation", &query.rotation) -// .finish() -// } -// Expr::Challenge(_, power) => f.debug_tuple("Challenge").field(power).finish(), -// Expr::Negated(poly) => f.debug_tuple("Negated").field(poly).finish(), -// Expr::Sum(a, b) => f.debug_tuple("Sum").field(a).field(b).finish(), -// Expr::Product(a, b) => f.debug_tuple("Product").field(a).field(b).finish(), -// Expr::Scaled(poly, scalar) => { -// f.debug_tuple("Scaled").field(poly).field(scalar).finish() -// } -// } -// } -// } +impl std::fmt::Debug for Expr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Expr::Slack(d) => f.debug_tuple("Slack").field(d).finish(), + Expr::Constant(scalar) => f.debug_tuple("Constant").field(scalar).finish(), + Expr::Selector(selector) => f.debug_tuple("Selector").field(selector).finish(), + // Skip enum variant and print query struct directly to maintain backwards compatibility. + Expr::Fixed(query) => f.debug_tuple("Fixed").field(query).finish(), + Expr::Advice(query) => f.debug_tuple("Advice").field(query).finish(), + Expr::Instance(query) => f.debug_tuple("Instance").field(query).finish(), + Expr::Challenge(c, power) => f.debug_tuple("Challenge").field(c).field(power).finish(), + Expr::Negated(poly) => f.debug_tuple("Negated").field(poly).finish(), + Expr::Sum(a, b) => f.debug_tuple("Sum").field(a).field(b).finish(), + Expr::Product(a, b) => f.debug_tuple("Product").field(a).field(b).finish(), + Expr::Scaled(poly, scalar) => { + f.debug_tuple("Scaled").field(poly).field(scalar).finish() + } + } + } +} // #[cfg(test)] diff --git a/halo2_proofs/src/protostar/keygen.rs b/halo2_proofs/src/protostar/keygen.rs index 7c9a55ca..e89de96c 100644 --- a/halo2_proofs/src/protostar/keygen.rs +++ b/halo2_proofs/src/protostar/keygen.rs @@ -19,29 +19,37 @@ use crate::{ /// CircuitData is the minimal algebraic representation of a PlonK-ish circuit. /// From this, we can derive the Proving/Verification keys for a chosen proof system. -/// TODO(@adr1anh): this could be generic over F instead pub struct CircuitData { - // number of rows - pub n: u64, + // maximum number of rows in the trace + pub n: usize, // ceil(log(n)) pub k: u32, // max active rows, without blinding pub usable_rows: Range, - // gates, lookups, advice, fixed columns + // original constraint system pub cs: ConstraintSystem, + + // actual number of elements in each column for each column type. + pub num_selector_rows: Vec, + pub num_fixed_rows: Vec, + pub num_advice_rows: Vec, + pub num_instance_rows: Vec, + // fixed[fixed_col][row] pub fixed: Vec>, // selectors[gate][row] + // TODO(@adr1anh): Replace with a `BTreeMap` to save memory pub selectors: Vec>, + // permutations[advice_col][row] = sigma(advice_col*n+row) // advice_col are from cs.permutation - // maybe store as Vec) + // TODO(@adr1anh): maybe store as Vec) pub permutations: Vec>, // lookups? } impl CircuitData { - /// Generate from the circuit, parametrized on the commitment scheme. + /// Generate algebraic representation of the circuit. pub fn new( n: usize, circuit: &ConcreteCircuit, @@ -49,20 +57,18 @@ impl CircuitData { where ConcreteCircuit: Circuit, { + // Get `config` from the `ConstraintSystem` let mut cs = ConstraintSystem::default(); #[cfg(feature = "circuit-params")] let config = ConcreteCircuit::configure_with_params(&mut cs, circuit.params()); #[cfg(not(feature = "circuit-params"))] let config = ConcreteCircuit::configure(&mut cs); - // We probably want a different degree, but we may not even need this - // let degree = cs.degree(); - let cs = cs; let k = n.next_power_of_two() as u32; + // TODO(@adr1anh): Blinding will be different for Protostar if n < cs.minimum_rows() { - // TODO(@adr1anh): Why is this k instead of n? return Err(Error::not_enough_rows_available(k)); } @@ -73,7 +79,13 @@ impl CircuitData { selectors: vec![vec![false; n]; cs.num_selectors], // We don't need blinding factors for Protostar, but later for the Decider, // leave for now - usable_rows: 0..n - (cs.blinding_factors() + 1), + usable_rows: 0..n, + + num_selector_rows: vec![0; cs.num_selectors], + num_fixed_rows: vec![0; cs.num_fixed_columns], + num_advice_rows: vec![0; cs.num_advice_columns], + num_instance_rows: vec![0; cs.num_instance_columns], + _marker: std::marker::PhantomData, }; @@ -99,14 +111,17 @@ impl CircuitData { let permutations = assembly.permutation.build_permutations(n, &cs.permutation); Ok(CircuitData { - n: n as u64, + n, k, usable_rows: assembly.usable_rows, cs, fixed, selectors: assembly.selectors, permutations, - // ev, + num_selector_rows: assembly.num_selector_rows, + num_fixed_rows: assembly.num_fixed_rows, + num_advice_rows: assembly.num_advice_rows, + num_instance_rows: assembly.num_instance_rows, }) } } @@ -138,6 +153,13 @@ struct Assembly { selectors: Vec>, // A range of available rows for assignment and copies. usable_rows: Range, + + // keep track of actual number of elements in each column + num_selector_rows: Vec, + num_fixed_rows: Vec, + num_advice_rows: Vec, + num_instance_rows: Vec, + _marker: std::marker::PhantomData, } @@ -161,6 +183,8 @@ impl Assignment for Assembly { A: FnOnce() -> AR, AR: Into, { + self.num_selector_rows[selector.0] = std::cmp::max(self.num_selector_rows[selector.0], row); + if !self.usable_rows.contains(&row) { return Err(Error::not_enough_rows_available(self.k)); } @@ -170,7 +194,10 @@ impl Assignment for Assembly { Ok(()) } - fn query_instance(&self, _: Column, row: usize) -> Result, Error> { + fn query_instance(&mut self, column: Column, row: usize) -> Result, Error> { + let column_index = column.index(); + self.num_instance_rows[column_index] = + std::cmp::max(self.num_instance_rows[column_index], row); if !self.usable_rows.contains(&row) { return Err(Error::not_enough_rows_available(self.k)); } @@ -182,8 +209,8 @@ impl Assignment for Assembly { fn assign_advice( &mut self, _: A, - _: Column, - _: usize, + column: Column, + row: usize, _: V, ) -> Result<(), Error> where @@ -192,6 +219,8 @@ impl Assignment for Assembly { A: FnOnce() -> AR, AR: Into, { + let column_index = column.index(); + self.num_advice_rows[column_index] = std::cmp::max(self.num_advice_rows[column_index], row); // We only care about fixed columns here Ok(()) } @@ -209,6 +238,9 @@ impl Assignment for Assembly { A: FnOnce() -> AR, AR: Into, { + let column_index = column.index(); + self.num_fixed_rows[column_index] = std::cmp::max(self.num_fixed_rows[column_index], row); + if !self.usable_rows.contains(&row) { return Err(Error::not_enough_rows_available(self.k)); } @@ -229,6 +261,38 @@ impl Assignment for Assembly { right_column: Column, right_row: usize, ) -> Result<(), Error> { + let left_column_index = left_column.index(); + let right_column_index = right_column.index(); + + match left_column.column_type() { + Any::Advice(_) => { + self.num_advice_rows[left_column_index] = + std::cmp::max(self.num_advice_rows[left_column_index], left_row); + } + Any::Fixed => { + self.num_fixed_rows[left_column_index] = + std::cmp::max(self.num_fixed_rows[left_column_index], left_row); + } + Any::Instance => { + self.num_instance_rows[left_column_index] = + std::cmp::max(self.num_instance_rows[left_column_index], left_row); + } + } + match right_column.column_type() { + Any::Advice(_) => { + self.num_advice_rows[right_column_index] = + std::cmp::max(self.num_advice_rows[right_column_index], right_row); + } + Any::Fixed => { + self.num_fixed_rows[right_column_index] = + std::cmp::max(self.num_fixed_rows[right_column_index], right_row); + } + Any::Instance => { + self.num_instance_rows[right_column_index] = + std::cmp::max(self.num_instance_rows[right_column_index], right_row); + } + } + if !self.usable_rows.contains(&left_row) || !self.usable_rows.contains(&right_row) { return Err(Error::not_enough_rows_available(self.k)); } @@ -289,10 +353,12 @@ impl Assignment for Assembly { // _phantom: PhantomData, // } +/// A Protostar proving key that augments `CircuitData` pub struct ProvingKey<'cd, F: Field> { pub circuit_data: &'cd CircuitData, pub gates: Vec>, pub max_degree: usize, + pub max_challenge_power: Vec, // vk: VerifyingKey, // ev etc? } @@ -301,6 +367,18 @@ impl<'cd, F: Field> ProvingKey<'cd, F> { pub fn new(circuit_data: &'cd CircuitData) -> Result, Error> { let gates: Vec> = circuit_data.cs.gates().iter().map(Gate::from).collect(); + // for each challenge, find the maximum power of it appearing across all expressions + let mut max_challenge_power = vec![0; circuit_data.cs.num_challenges]; + for gate in gates.iter() { + for poly in gate.polys.iter() { + poly.traverse(&mut |v| { + if let Expr::Challenge(c, d) = v { + max_challenge_power[*c] = std::cmp::max(max_challenge_power[*c], *d); + } + }); + } + } + let max_degree = *gates .iter() .map(|g| g.degrees.iter().max().unwrap()) @@ -313,6 +391,7 @@ impl<'cd, F: Field> ProvingKey<'cd, F> { circuit_data, gates, max_degree, + max_challenge_power, }) } } diff --git a/halo2_proofs/src/protostar/prover.rs b/halo2_proofs/src/protostar/prover.rs index ecb92cf0..2c6eade9 100644 --- a/halo2_proofs/src/protostar/prover.rs +++ b/halo2_proofs/src/protostar/prover.rs @@ -1,10 +1,11 @@ -use std::marker::PhantomData; +use std::{iter::zip, marker::PhantomData}; use ff::{Field, FromUniformBytes}; use halo2curves::CurveAffine; use rand_core::RngCore; use crate::{ + arithmetic::lagrange_interpolate, plonk::{Circuit, Error, FixedQuery}, poly::{ commitment::{CommitmentScheme, Prover}, @@ -15,7 +16,7 @@ use crate::{ use super::{ error_check::{AccumulatorInstance, AccumulatorWitness, GateEvaluationCache}, - gate::Expr, + gate::{self, Expr}, keygen::{CircuitData, ProvingKey}, witness::{create_advice_transcript, create_instance_polys}, }; @@ -48,9 +49,10 @@ where // Hash verification key into transcript // pk.vk.hash_into(transcript)?; - // Create polynomials from instance, and + // Create polynomials from instance and add to transcript let instance_polys = create_instance_polys::(¶ms, &pk.circuit_data.cs, instances, transcript)?; + // Create advice columns and add to transcript let advice_transcript = create_advice_transcript::( params, &pk.circuit_data.cs, @@ -60,76 +62,84 @@ where transcript, )?; - let fixed_polys = &pk.circuit_data.fixed; - let selectors = &pk.circuit_data.selectors; - let n = pk.circuit_data.usable_rows.end as i32; let new_instance = AccumulatorInstance::new(pk, advice_transcript.challenges, instance_polys); - let acc_instance = new_instance.clone(); - // AccumulatorInstance::new_empty(pk); + let acc_instance = AccumulatorInstance::new_empty(pk); + // let acc_instance = new_instance.clone(); let new_witness = AccumulatorWitness::new( advice_transcript.advice_polys, advice_transcript.advice_blinds, ); - let acc_witness = new_witness.clone(); - // AccumulatorWitness::::new_empty(n as usize, pk.circuit_data.n as usize); + let acc_witness = AccumulatorWitness::new_empty(pk); + // let acc_witness = new_witness.clone(); + let num_extra_evaluations = 3; let mut gate_caches: Vec<_> = pk .gates .iter() - .map(|gate| GateEvaluationCache::new(gate, &acc_instance, &new_instance, 1)) + .map(|gate| { + GateEvaluationCache::new(gate, &acc_instance, &new_instance, num_extra_evaluations) + }) + .collect(); + + // for poly in gate_caches + // .iter() + // .flat_map(|gate_chache| gate_chache.gate.polys.iter()) + // { + // println!("{:?}", poly); + // } + + let num_evals = pk.max_degree + 1 + num_extra_evaluations; + + // DEBUG: Evaluation points used to do lagrange interpolation + let points = { + let mut points = Vec::with_capacity(num_evals); + points.push(Scheme::Scalar::ZERO); + for i in 1..num_evals { + points.push(points[i - 1] + Scheme::Scalar::ONE); + } + points + }; + + // eval_sums[gate_idx][constraint_idx][eval_idx] + let mut eval_sums: Vec<_> = gate_caches + .iter() + .map(|gate_cache| gate_cache.gate_eval.clone()) .collect(); for row_idx in pk.circuit_data.usable_rows.clone().into_iter() { - for gate_cache in gate_caches.iter_mut() { + for (gate_idx, gate_cache) in gate_caches.iter_mut().enumerate() { let evals = gate_cache.evaluate(row_idx, n, &pk.circuit_data, &acc_witness, &new_witness); if let Some(evals) = evals { - for eval in evals.iter() { - debug_assert!(eval[0].is_zero_vartime()); - // debug_assert!(eval[1].is_zero_vartime()); + for (poly_idx, poly_evals) in evals.iter().enumerate() { + // DEBUG: Check that the last `num_extra_evaluations` coeffs are zero + let poly_points = &points[..poly_evals.len()]; + let eval_coeffs = lagrange_interpolate(poly_points, poly_evals); + debug_assert_ne!(eval_coeffs.len(), 0); + + for (existing, new) in + zip(eval_sums[gate_idx][poly_idx].iter_mut(), poly_evals.iter()) + { + *existing += new; + } } } } } - // for i in pk.circuit_data.usable_rows.clone().into_iter() { - // for gate in pk.circuit_data.cs.gates() { - // for poly in gate.polynomials() { - // let e: Expr = poly.clone().into(); - // let result = e.evaluate_lazy( - // &|_slack| 1.into(), - // &|constant| constant, - // &|selector| { - // if selectors[selector.0][i] { - // Scheme::Scalar::ONE - // } else { - // Scheme::Scalar::ZERO - // } - // }, - // &|fixed| { - // fixed_polys[fixed.column_index][get_rotation_idx(i, fixed.rotation(), n)] - // }, - // &|advice| { - // advice_polys[advice.column_index][get_rotation_idx(i, advice.rotation(), n)] - // }, - // &|instance| { - // instance_polys[instance.column_index] - // [get_rotation_idx(i, instance.rotation(), n)] - // }, - // &|challenge, power| challenges[challenge.index()].pow_vartime([power as u64]), - // &|a| -a, - // &|a, b| a + b, - // &|a, b| a * b, - // &|a, b| a * b, - // &Scheme::Scalar::ZERO, - // ); - // assert_eq!(result, Scheme::Scalar::ZERO); - // } - // } - // } + let eval_coeffs: Vec<_> = eval_sums + .iter() + .flat_map(|gate_evals| { + gate_evals.iter().map(|poly_evals| -> Vec { + let poly_points = &points[..poly_evals.len()]; + lagrange_interpolate(poly_points, poly_evals) + }) + }) + .collect(); + assert_ne!(eval_coeffs.len(), 0); Ok(()) } @@ -163,7 +173,7 @@ mod tests { const W: usize = 4; const H: usize = 32; const K: u32 = 8; - const N: usize = 1 << K; + const N: usize = 64; // could be 33? let circuit = MyCircuit::<_, W, H>::rand(&mut rng); let params = poly::ipa::commitment::ParamsIPA::<_>::new(K); diff --git a/halo2_proofs/src/protostar/shuffle.rs b/halo2_proofs/src/protostar/shuffle.rs index ee79adfc..133cb3dd 100644 --- a/halo2_proofs/src/protostar/shuffle.rs +++ b/halo2_proofs/src/protostar/shuffle.rs @@ -19,7 +19,7 @@ use crate::{ }; use ff::{BatchInvert, FromUniformBytes}; use rand_core::{OsRng, RngCore}; -use std::iter; +use std::iter::{self, zip}; fn rand_2d_array(rng: &mut R) -> [[F; H]; W] { [(); W].map(|_| [(); H].map(|_| F::random(&mut *rng))) @@ -50,6 +50,7 @@ pub struct MyConfig { shuffled: [Column; W], theta: Challenge, gamma: Challenge, + gamma2: Challenge, z: Column, } @@ -59,7 +60,7 @@ impl MyConfig { // First phase let original = [(); W].map(|_| meta.advice_column_in(FirstPhase)); let shuffled = [(); W].map(|_| meta.advice_column_in(FirstPhase)); - let [theta, gamma] = [(); 2].map(|_| meta.challenge_usable_after(FirstPhase)); + let [theta, gamma, gamma2] = [(); 3].map(|_| meta.challenge_usable_after(FirstPhase)); // Second phase let z = meta.advice_column_in(SecondPhase); @@ -79,7 +80,7 @@ impl MyConfig { let q_shuffle = q_shuffle.expr(); let original = original.map(|advice| advice.cur()); let shuffled = shuffled.map(|advice| advice.cur()); - let [theta, gamma] = [theta, gamma].map(|challenge| challenge.expr()); + let [theta, gamma, gamma2] = [theta, gamma, gamma2].map(|challenge| challenge.expr()); // Compress let original = original @@ -93,7 +94,11 @@ impl MyConfig { .reduce(|acc, a| acc * theta.clone() + a) .unwrap(); - vec![q_shuffle * (z.cur() * (original + gamma.clone()) - z.next() * (shuffled + gamma))] + vec![ + q_shuffle + * (z.cur() * (original + gamma.clone() * gamma2.clone()) + - z.next() * (shuffled + gamma * gamma2)), + ] }); Self { @@ -104,6 +109,7 @@ impl MyConfig { shuffled, theta, gamma, + gamma2, z, } } @@ -160,11 +166,11 @@ impl Circuit for MyCircuit } // First phase - for (idx, (&column, values)) in config - .original - .iter() - .zip(self.original.transpose_array().iter()) - .enumerate() + for (idx, (&column, values)) in zip( + config.original.iter(), + self.original.transpose_array().iter(), + ) + .enumerate() { for (offset, &value) in values.transpose_array().iter().enumerate() { region.assign_advice( @@ -175,11 +181,11 @@ impl Circuit for MyCircuit )?; } } - for (idx, (&column, values)) in config - .shuffled - .iter() - .zip(self.shuffled.transpose_array().iter()) - .enumerate() + for (idx, (&column, values)) in zip( + config.shuffled.iter(), + self.shuffled.transpose_array().iter(), + ) + .enumerate() { for (offset, &value) in values.transpose_array().iter().enumerate() { region.assign_advice( diff --git a/halo2_proofs/src/protostar/witness.rs b/halo2_proofs/src/protostar/witness.rs index 081e2d36..97f1091e 100644 --- a/halo2_proofs/src/protostar/witness.rs +++ b/halo2_proofs/src/protostar/witness.rs @@ -20,10 +20,11 @@ use group::{prime::PrimeCurveAffine, Curve}; use rand_core::RngCore; use std::{ collections::{BTreeSet, HashMap}, + iter::zip, ops::RangeTo, }; -/// +/// Cache for storing the evaluated witness data during all phases of the advice generation. struct WitnessCollection<'a, F: Field> { k: u32, current_phase: sealed::Phase, @@ -67,7 +68,8 @@ impl<'a, F: Field> Assignment for WitnessCollection<'a, F> { // Do nothing } - fn query_instance(&self, column: Column, row: usize) -> Result, Error> { + fn query_instance(&mut self, column: Column, row: usize) -> Result, Error> { + // TODO(@adr1anh): Compare with actual length of the instance column if !self.usable_rows.contains(&row) { return Err(Error::not_enough_rows_available(self.k)); } @@ -98,6 +100,7 @@ impl<'a, F: Field> Assignment for WitnessCollection<'a, F> { return Ok(()); } + // TODO(@adr1anh): Compare with actual length of the advice column if !self.usable_rows.contains(&row) { return Err(Error::not_enough_rows_available(self.k)); } @@ -165,6 +168,7 @@ impl<'a, F: Field> Assignment for WitnessCollection<'a, F> { } } +/// Generates `Polynomial`s from given `instance` values, and adds them to the transcript. pub fn create_instance_polys< Scheme: CommitmentScheme, E: EncodedChallenge, @@ -175,24 +179,25 @@ pub fn create_instance_polys< instances: &[&[Scheme::Scalar]], transcript: &mut T, ) -> Result>, Error> { + // TODO(@adr1anh): Check that the lengths of each instance column is correct as well if instances.len() != cs.num_instance_columns { return Err(Error::InvalidInstances); } let n = params.n() as usize; - // TODO(@adr1anh): refactor into own function // generate polys for instance columns // NOTE(@adr1anh): In the case where the verifier does not query the instance, // we do not need to create a Lagrange polynomial of size n. let instance_polys = instances .iter() .map(|values| { + // TODO(@adr1anh): Allocate only the required size for each column let mut poly = empty_lagrange(n); if values.len() > (poly.len() - (cs.blinding_factors() + 1)) { return Err(Error::InstanceTooLarge); } - for (poly, value) in poly.iter_mut().zip(values.iter()) { + for (poly, value) in zip(poly.iter_mut(), values.iter()) { // The instance is part of the transcript // if !P::QUERY_INSTANCE { // transcript.common_scalar(*value)?; @@ -204,6 +209,7 @@ pub fn create_instance_polys< }) .collect::, _>>()?; + // TODO(@adr1anh): Split into two functions for handling both committed and raw instance columns // // For large instances, we send a commitment to it and open it with PCS // if P::QUERY_INSTANCE { // let instance_commitments_projective: Vec<_> = instance_polys @@ -247,13 +253,14 @@ pub fn create_advice_transcript< params: &Scheme::ParamsProver, cs: &ConstraintSystem, circuit: &ConcreteCircuit, - // raw instance columns instances: &[&[Scheme::Scalar]], mut rng: R, transcript: &mut T, ) -> Result, Error> { let n = params.n() as usize; + // TODO(@adr1anh): Can we cache the config in the `circuit_data`? + // We don't apply selector optimization so it should remain the same as during the keygen. let config = { let mut meta = ConstraintSystem::default(); @@ -264,12 +271,11 @@ pub fn create_advice_transcript< config }; - // Selector optimizations cannot be applied here; use the ConstraintSystem - // from the verification key. let meta = &cs; // Synthesize the circuit over multiple iterations let (advice_polys, advice_blinds, challenges) = { + // TODO(@adr1anh): Use `circuit_data.num_advice_rows` to only allocate required number of rows let mut advice_assigned = vec![empty_lagrange_assigned(n); meta.num_advice_columns]; let mut advice_polys = vec![empty_lagrange(n); meta.num_advice_columns]; let mut advice_blinds = vec![Blind::default(); meta.num_advice_columns];