From 29c033a916c3caac894c983e0895339db1eeba29 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:49:29 +0200 Subject: [PATCH 01/58] feat: math utilities needed for sum-check protocol --- sumcheck/src/utils/mod.rs | 8 + sumcheck/src/utils/multilinear.rs | 229 +++++++++++++++++++++++++++ sumcheck/src/utils/univariate.rs | 252 ++++++++++++++++++++++++++++++ 3 files changed, 489 insertions(+) create mode 100644 sumcheck/src/utils/mod.rs create mode 100644 sumcheck/src/utils/multilinear.rs create mode 100644 sumcheck/src/utils/univariate.rs diff --git a/sumcheck/src/utils/mod.rs b/sumcheck/src/utils/mod.rs new file mode 100644 index 000000000..41c63e1df --- /dev/null +++ b/sumcheck/src/utils/mod.rs @@ -0,0 +1,8 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +mod univariate; + +mod multilinear; \ No newline at end of file diff --git a/sumcheck/src/utils/multilinear.rs b/sumcheck/src/utils/multilinear.rs new file mode 100644 index 000000000..d6907d14c --- /dev/null +++ b/sumcheck/src/utils/multilinear.rs @@ -0,0 +1,229 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +use alloc::vec::Vec; +use core::ops::Index; +use math::FieldElement; + +#[cfg(feature = "concurrent")] +pub use rayon::prelude::*; + +// MULTI-LINEAR POLYNOMIAL +// ================================================================================================ + +/// Represents a multi-linear polynomial. +/// +/// The representation stores the evaluations of the polynomial over the boolean hyper-cube +/// ${0 , 1}^ν$. +#[derive(Clone, Debug, PartialEq)] +pub struct MultiLinearPoly { + evaluations: Vec, +} + +impl MultiLinearPoly { + /// Constructs a [`MultiLinearPoly`] from its evaluations over the boolean hyper-cube ${0 , 1}^ν$. + pub fn from_evaluations(evaluations: Vec) -> Self { + assert!(evaluations.len().is_power_of_two(), "A multi-linear polynomial should have a power of 2 number of evaluations over the Boolean hyper-cube"); + Self { evaluations } + } + + /// Returns the number of variables of the multi-linear polynomial. + pub fn num_variables(&self) -> usize { + self.evaluations.len().trailing_zeros() as usize + } + + /// Returns the evaluations over the boolean hyper-cube. + pub fn evaluations(&self) -> &[E] { + &self.evaluations + } + + /// Returns the number of evaluations. This is equal to the size of the boolean hyper-cube. + pub fn num_evaluations(&self) -> usize { + self.evaluations.len() + } + + /// Evaluate the multi-linear at some query $(r_0, ..., r_{ν - 1}) ∈ 𝔽^ν$. + /// + /// It first computes the evaluations of the Lagrange basis polynomials over the interpolating + /// set ${0 , 1}^ν$ at $(r_0, ..., r_{ν - 1})$ i.e., the Lagrange kernel at $(r_0, ..., r_{ν - 1})$. + /// The evaluation then is the inner product, indexed by ${0 , 1}^ν$, of the vector of + /// evaluations times the Lagrange kernel. + pub fn evaluate(&self, query: &[E]) -> E { + let tensored_query = compute_lagrange_basis_evals_at(query); + inner_product(&self.evaluations, &tensored_query) + } + + /// Similar to [`Self::evaluate`], except that the query was already turned into the Lagrange + /// kernel (i.e. the [`lagrange_ker::EqFunction`] evaluated at every point in the set + /// `${0 , 1}^ν$`). + /// + /// This is more efficient than [`Self::evaluate`] when multiple different [`MultiLinearPoly`] + /// need to be evaluated at the same query point. + pub fn evaluate_with_lagrange_kernel(&self, lagrange_kernel: &[E]) -> E { + inner_product(&self.evaluations, lagrange_kernel) + } + + /// Computes $f(r_0, y_1, ..., y_{ν - 1})$ using the linear interpolation formula + /// $(1 - r_0) * f(0, y_1, ..., y_{ν - 1}) + r_0 * f(1, y_1, ..., y_{ν - 1})$ and assigns + /// the resulting multi-linear, defined over a domain of half the size, to `self`. + pub fn bind_least_significant_variable(&mut self, round_challenge: E) { + let num_evals = self.evaluations.len() >> 1; + for i in 0..num_evals { + self.evaluations[i] = self.evaluations[i << 1] + + round_challenge * (self.evaluations[(i << 1) + 1] - self.evaluations[i << 1]); + } + self.evaluations.truncate(num_evals) + } + + /// Given the multilinear polynomial $f(y_0, y_1, ..., y_{ν - 1})$, returns two polynomials: + /// $f(0, y_1, ..., y_{ν - 1})$ and $f(1, y_1, ..., y_{ν - 1})$. + pub fn project_least_significant_variable(&self) -> (Self, Self) { + let mut p0 = Vec::with_capacity(self.num_evaluations() / 2); + let mut p1 = Vec::with_capacity(self.num_evaluations() / 2); + for chunk in self.evaluations.chunks_exact(2) { + p0.push(chunk[0]); + p1.push(chunk[1]); + } + + (MultiLinearPoly::from_evaluations(p0), MultiLinearPoly::from_evaluations(p1)) + } +} + +impl Index for MultiLinearPoly { + type Output = E; + + fn index(&self, index: usize) -> &E { + &(self.evaluations[index]) + } +} + +// EQ FUNCTION +// ================================================================================================ + +/// The EQ (equality) function is the binary function defined by +/// +/// $$ +/// EQ: {0 , 1}^ν ⛌ {0 , 1}^ν ⇾ {0 , 1} +/// ((x_0, ..., x_{ν - 1}), (y_0, ..., y_{ν - 1})) ↦ \prod_{i = 0}^{ν - 1} (x_i * y_i + (1 - x_i) +/// * (1 - y_i)) +/// $$ +/// +/// Taking its multi-linear extension $EQ^{~}$, we can define a basis for the set of multi-linear +/// polynomials in ν variables by +/// $${EQ^{~}(., (y_0, ..., y_{ν - 1})): (y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν}$$ +/// where each basis function is a function of its first argument. This is called the Lagrange or +/// evaluation basis for evaluation set ${0 , 1}^ν$. +/// +/// Given a function $(f: {0 , 1}^ν ⇾ 𝔽)$, its multi-linear extension (i.e., the unique +/// mult-linear polynomial extending `f` to $(f^{~}: 𝔽^ν ⇾ 𝔽)$ and agreeing with it on ${0 , 1}^ν$) is +/// defined as the summation of the evaluations of f against the Lagrange basis. +/// More specifically, given $(r_0, ..., r_{ν - 1}) ∈ 𝔽^ν$, then: +/// +/// $$ +/// f^{~}(r_0, ..., r_{ν - 1}) = \sum_{(y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν} +/// f(y_0, ..., y_{ν - 1}) EQ^{~}((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})) +/// $$ +/// +/// We call the Lagrange kernel the evaluation of the EQ^{~} function at +/// $((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))$ for all $(y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν$ for +/// a fixed $(r_0, ..., r_{ν - 1}) ∈ 𝔽^ν$. +/// +/// [`EqFunction`] represents EQ^{~} the multi-linear extension of +/// +/// $((y_0, ..., y_{ν - 1}) ↦ EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})))$ +/// +/// and contains a method to generate the Lagrange kernel for defining evaluations of multi-linear +/// extensions of arbitrary functions $(f: {0 , 1}^ν ⇾ 𝔽)$ at a given point $(r_0, ..., r_{ν - 1})$ +/// as well as a method to evaluate $EQ^{~}((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1})))$ for +/// $(t_0, ..., t_{ν - 1}) ∈ 𝔽^ν$. +pub struct EqFunction { + r: Vec, +} + +impl EqFunction { + /// Creates a new [EqFunction]. + pub fn new(r: Vec) -> Self { + let tmp = r.clone(); + EqFunction { r: tmp } + } + + /// Computes $EQ((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1})))$. + pub fn evaluate(&self, t: &[E]) -> E { + assert_eq!(self.r.len(), t.len()); + + (0..self.r.len()) + .map(|i| self.r[i] * t[i] + (E::ONE - self.r[i]) * (E::ONE - t[i])) + .fold(E::ONE, |acc, term| acc * term) + } + + /// Computes $EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))$ for all + /// $(y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν$ i.e., the Lagrange kernel at $r = (r_0, ..., r_{ν - 1})$. + pub fn evaluations(&self) -> Vec { + compute_lagrange_basis_evals_at(&self.r) + } + + /// Returns the evaluations of + /// $((y_0, ..., y_{ν - 1}) ↦ EQ^{~}((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})))$ + /// over ${0 , 1}^ν$. + pub fn ml_at(evaluation_point: Vec) -> MultiLinearPoly { + let eq_evals = EqFunction::new(evaluation_point.clone()).evaluations(); + MultiLinearPoly::from_evaluations(eq_evals) + } +} + +// HELPER +// ================================================================================================ + +/// Computes the evaluations of the Lagrange basis polynomials over the interpolating +/// set ${0 , 1}^ν$ at $(r_0, ..., r_{ν - 1})$ i.e., the Lagrange kernel at $(r_0, ..., r_{ν - 1})$. +/// +/// TODO: This is a critical function and parallelizing would have a significant impact on +/// performance. +fn compute_lagrange_basis_evals_at(query: &[E]) -> Vec { + let nu = query.len(); + let n = 1 << nu; + + let mut evals: Vec = vec![E::ONE; n]; + let mut size = 1; + for r_i in query.iter().rev() { + size *= 2; + for i in (0..size).rev().step_by(2) { + let scalar = evals[i / 2]; + evals[i] = scalar * *r_i; + evals[i - 1] = scalar - evals[i]; + } + } + evals +} + +/// Computes the inner product in the extension field of two iterators that must yield the same +/// number of items. +/// +/// If `concurrent` feature is enabled, this function can make use of multi-threading. +pub fn inner_product(x: &[E], y: &[E]) -> E { + #[cfg(not(feature = "concurrent"))] + return x.iter().zip(y.iter()).fold(E::ZERO, |acc, (x_i, y_i)| acc + *x_i * *y_i); + + #[cfg(feature = "concurrent")] + return x + .par_iter() + .zip(y.par_iter()) + .map(|(x_i, y_i)| *x_i * *y_i) + .reduce(|| E::ZERO, |a, b| a + b); +} + +// TESTS +// ================================================================================================ + +#[test] +fn test_bind() { + use math::fields::f64::BaseElement; + let mut p = MultiLinearPoly::from_evaluations(vec![BaseElement::ONE; 8]); + let expected = MultiLinearPoly::from_evaluations(vec![BaseElement::ONE; 4]); + + let challenge = rand_utils::rand_value(); + p.bind_least_significant_variable(challenge); + assert_eq!(p, expected) +} diff --git a/sumcheck/src/utils/univariate.rs b/sumcheck/src/utils/univariate.rs new file mode 100644 index 000000000..868206213 --- /dev/null +++ b/sumcheck/src/utils/univariate.rs @@ -0,0 +1,252 @@ + +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +use alloc::vec::Vec; +use math::{batch_inversion, polynom, FieldElement}; + + +// COMPRESSED UNIVARIATE POLYNOMIAL +// ================================================================================================ + +/// The coefficients of a univariate polynomial of degree n with the linear term coefficient +/// omitted. +/// +/// This compressed representation is useful during the sum-check protocol as the full uncompressed +/// representation can be recovered from the compressed one and the current sum-check round claim. +#[derive(Clone, Debug)] +pub struct CompressedUnivariatePoly(Vec); + +impl CompressedUnivariatePoly { + /// Evaluates a polynomial at a challenge point using a round claim. + /// + /// The round claim is used to recover the coefficient of the linear term using the relation + /// 2 * c0 + c1 + ... c_{n - 1} = claim. Using the complete list of coefficients, the polynomial + /// is then evaluated using Horner's method. + pub fn evaluate_using_claim(&self, claim: &E, challenge: &E) -> E { + // recover the coefficient of the linear term + let c1 = *claim - self.0.iter().fold(E::ZERO, |acc, term| acc + *term) - self.0[0]; + + // construct the full coefficient list + let mut complete_coefficients = vec![self.0[0], c1]; + complete_coefficients.extend_from_slice(&self.0[1..]); + + // evaluate + polynom::eval(&complete_coefficients, *challenge) + } +} + +/// The evaluations of a univariate polynomial of degree n at 0, 1, ..., n with the evaluation at 0 +/// omitted. +/// +/// This compressed representation is useful during the sum-check protocol as the full uncompressed +/// representation can be recovered from the compressed one and the current sum-check round claim. +#[derive(Clone, Debug)] +pub struct CompressedUnivariatePolyEvals(Vec); + +impl CompressedUnivariatePolyEvals { + /// Gives the coefficient representation of a polynomial represented in evaluation form. + /// + /// Since the evaluation at 0 is omitted, we need to use the round claim to recover + /// the evaluation at 0 using the identity $p(0) + p(1) = claim$. + /// Now, we have that for any polynomial $p(x) = c0 + c1 * x + ... + c_{n-1} * x^{n - 1}$: + /// + /// 1. $p(0) = c0$. + /// 2. $p(x) = c0 + x * q(x) where q(x) = c1 + ... + c_{n-1} * x^{n - 2}$. + /// + /// This means that we can compute the evaluations of q at 1, ..., n - 1 using the evaluations + /// of p and thus reduce by 1 the size of the interpolation problem. + /// Once the coefficient of q are recovered, the c0 coefficient is appended to these and this + /// is precisely the coefficient representation of the original polynomial q. + /// Note that the coefficient of the linear term is removed as this coefficient can be recovered + /// from the remaining coefficients, again, using the round claim using the relation + /// $2 * c0 + c1 + ... c_{n - 1} = claim$. + pub fn to_poly(&self, round_claim: E) -> CompressedUnivariatePoly { + // construct the vector of interpolation points 1, ..., n + let n_minus_1 = self.0.len(); + let points = (1..=n_minus_1 as u32).map(E::BaseField::from).collect::>(); + + // construct their inverses. These will be needed for computing the evaluations + // of the q polynomial as well as for doing the interpolation on q + let points_inv = batch_inversion(&points); + + // compute the zeroth coefficient + let c0 = round_claim - self.0[0]; + + // compute the evaluations of q + let q_evals: Vec = self + .0 + .iter() + .enumerate() + .map(|(i, evals)| (*evals - c0).mul_base(points_inv[i])) + .collect(); + + // interpolate q + let q_coefs = multiply_by_inverse_vandermonde(&q_evals, &points_inv); + + // append c0 to the coefficients of q to get the coefficients of p. The linear term + // coefficient is removed as this can be recovered from the other coefficients using + // the reduced claim. + let mut coefficients = Vec::with_capacity(self.0.len() + 1); + coefficients.push(c0); + coefficients.extend_from_slice(&q_coefs[1..]); + + CompressedUnivariatePoly(coefficients) + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Given a (row) vector `v`, computes the vector-matrix product `v * V^{-1}` where `V` is +/// the Vandermonde matrix over the points `1, ..., n` where `n` is the length of `v`. +/// The resulting vector will then be the coefficients of the minimal interpolating polynomial +/// through the points `(i+1, v[i])` for `i` in `0, ..., n - 1` +/// +/// The naive way would be to invert the matrix `V` and then compute the vector-matrix product +/// this will cost `O(n^3)` operations and `O(n^2)` memory. We can also try Gaussian elimination +/// but this is also worst case `O(n^3)` operations and `O(n^2)` memory. +/// In the following implementation, we use the fact that the points over which we are interpolating +/// is a set of equidistant points and thus both the Vandermonde matrix and its inverse can be +/// described by sparse linear recurrence equations. +/// More specifically, we use the representation given in [1], where `V^{-1}` is represented as +/// `U * M` where: +/// +/// 1. `M` is a lower triangular matrix where its entries are given by M(i, j) = M(i - 1, j) - M(i - +/// 1, j - 1) / (i - 1) with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. +/// +/// 2. `U` is an upper triangular (involutory) matrix where its entries are given by U(i, j) = U(i, +/// j - 1) - U(i - 1, j - 1) with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. +/// +/// Note that the matrix indexing in the formulas above matches the one in the reference and starts +/// from 1. +/// +/// The above implies that we can do the vector-matrix multiplication in `O(n^2)` and using only +/// `O(n)` space. +/// +/// [1]: https://link.springer.com/article/10.1007/s002110050360 +fn multiply_by_inverse_vandermonde( + vector: &[E], + nodes_inv: &[E::BaseField], +) -> Vec { + let res = multiply_by_u(vector); + multiply_by_m(&res, nodes_inv) +} + +/// Multiplies a (row) vector `v` by an upper triangular matrix `U` to compute `v * U`. +/// +/// `U` is an upper triangular (involutory) matrix with its entries given by +/// U(i, j) = U(i, j - 1) - U(i - 1, j - 1) +/// with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. +fn multiply_by_u(vector: &[E]) -> Vec { + let n = vector.len(); + let mut previous_u_col = vec![E::BaseField::ZERO; n]; + previous_u_col[0] = E::BaseField::ONE; + let mut current_u_col = vec![E::BaseField::ZERO; n]; + current_u_col[0] = E::BaseField::ONE; + + let mut result: Vec = vec![E::ZERO; n]; + for (i, res) in result.iter_mut().enumerate() { + *res = vector[0]; + + for (j, v) in vector.iter().enumerate().take(i + 1).skip(1) { + let u_entry: E::BaseField = + compute_u_entry::(j, &mut previous_u_col, &mut current_u_col); + *res += v.mul_base(u_entry); + } + previous_u_col.clone_from(¤t_u_col); + } + + result +} + +/// Multiplies a (row) vector `v` by a lower triangular matrix `M` to compute `v * M`. +/// +/// `M` is a lower triangular matrix with its entries given by +/// M(i, j) = M(i - 1, j) - M(i - 1, j - 1) / (i - 1) +/// with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. +fn multiply_by_m(vector: &[E], nodes_inv: &[E::BaseField]) -> Vec { + let n = vector.len(); + let mut previous_m_col = vec![E::BaseField::ONE; n]; + let mut current_m_col = vec![E::BaseField::ZERO; n]; + current_m_col[0] = E::BaseField::ONE; + + let mut result: Vec = vec![E::ZERO; n]; + result[0] = vector.iter().fold(E::ZERO, |acc, term| acc + *term); + for (i, res) in result.iter_mut().enumerate().skip(1) { + current_m_col = vec![E::BaseField::ZERO; n]; + + for (j, v) in vector.iter().enumerate().skip(i) { + let m_entry: E::BaseField = + compute_m_entry::(j, &mut previous_m_col, &mut current_m_col, nodes_inv[j - 1]); + *res += v.mul_base(m_entry); + } + previous_m_col.clone_from(¤t_m_col); + } + + result +} + +/// Returns the j-th entry of the i-th column of matrix `U` given the values of the (i - 1)-th +/// column. The i-th column is also updated with the just computed `U(i, j)` entry. +/// +/// `U` is an upper triangular (involutory) matrix with its entries given by +/// U(i, j) = U(i, j - 1) - U(i - 1, j - 1) +/// with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. +fn compute_u_entry( + j: usize, + col_prev: &mut [E::BaseField], + col_cur: &mut [E::BaseField], +) -> E::BaseField { + let value = col_prev[j] - col_prev[j - 1]; + col_cur[j] = value; + value +} + +/// Returns the j-th entry of the i-th column of matrix `M` given the values of the (i - 1)-th +/// and the i-th columns. The i-th column is also updated with the just computed `M(i, j)` entry. +/// +/// `M` is a lower triangular matrix with its entries given by +/// M(i, j) = M(i - 1, j) - M(i - 1, j - 1) / (i - 1) +/// with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. +fn compute_m_entry( + j: usize, + col_previous: &mut [E::BaseField], + col_current: &mut [E::BaseField], + node_inv: E::BaseField, +) -> E::BaseField { + let value = col_current[j - 1] - node_inv * col_previous[j - 1]; + col_current[j] = value; + value +} + +// TESTS +// ================================================================================================ + +#[test] +fn test_poly_partial() { + use math::fields::f64::BaseElement; + + let degree = 1000; + let mut points: Vec = vec![BaseElement::ZERO; degree]; + points + .iter_mut() + .enumerate() + .for_each(|(i, node)| *node = BaseElement::from(i as u32)); + + let p: Vec = rand_utils::rand_vector(degree); + let evals = polynom::eval_many(&p, &points); + + let mut partial_evals = evals.clone(); + partial_evals.remove(0); + + let partial_poly = CompressedUnivariatePolyEvals(partial_evals); + let claim = evals[0] + evals[1]; + let poly_coeff = partial_poly.to_poly(claim); + + let r = rand_utils::rand_vector(1); + + assert_eq!(polynom::eval(&p, r[0]), poly_coeff.evaluate_using_claim(&claim, &r[0])) +} \ No newline at end of file From 0d7fa679961ecc226cd5f1c23b844e3fb9c5c453 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:08:36 +0200 Subject: [PATCH 02/58] feat: add sum-check prover and verifier --- air/src/air/mod.rs | 66 ++++++++++++++++++++++++++++++++ sumcheck/src/utils/mod.rs | 4 +- sumcheck/src/utils/univariate.rs | 20 +++++++++- 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index 5dcee0717..da1e0cf44 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -614,3 +614,69 @@ pub trait Air: Send + Sync { }) } } + + +pub trait LogUpGkrEvaluator: Clone { + /// Defines the base field of the evaluator. + type BaseField: StarkField; + + /// Public inputs need to compute the final claim. + type PublicInputs: ToElements + Send; + + /// Gets a list of all oracles involved in LogUp-GKR; this is intended to be used in construction of + /// MLEs. + fn get_oracles(&self) -> Vec>; + + /// Returns the number of random values needed to evaluate a query. + fn get_num_rand_values(&self) -> usize; + + /// Returns the number of fractions in the LogUp-GKR statement. + fn get_num_fractions(&self) -> usize; + + /// Returns the maximal degree of the multi-variate associated to the input layer. + fn max_degree(&self) -> usize; + + /// Builds a query from the provided main trace frame and periodic values. + /// + /// Note: it should be possible to provide an implementation of this method based on the + /// information returned from `get_oracles()`. However, this implementation is likely to be + /// expensive compared to the hand-written implementation. However, we could provide a test + /// which verifies that `get_oracles()` and `build_query()` methods are consistent. + fn build_query(&self, frame: &EvaluationFrame, periodic_values: &[E]) -> Vec + where + E: FieldElement; + + /// Evaluates the provided query and writes the results into the numerators and denominators. + /// + /// Note: it is also possible to combine `build_query()` and `evaluate_query()` into a single + /// method to avoid the need to first build the query struct and then evaluate it. However: + /// - We assume that the compiler will be able to optimize this away. + /// - Merging the methods will make it more difficult avoid inconsistencies between + /// `evaluate_query()` and `get_oracles()` methods. + fn evaluate_query( + &self, + query: &[F], + rand_values: &[E], + numerator: &mut [E], + denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + ExtensionOf; + + /// Computes the final claim for the LogUp-GKR circuit. + /// + /// The default implementation of this method returns E::ZERO as it is expected that the + /// fractional sums will cancel out. However, in cases when some boundary conditions need to + /// be imposed on the LogUp-GKR relations, this method can be overridden to compute the final + /// expected claim. + fn compute_claim(&self, inputs: &Self::PublicInputs, rand_values: &[E]) -> E + where + E: FieldElement; +} + +#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] +pub enum LogUpGkrOracle { + CurrentRow(usize), + NextRow(usize), + PeriodicValue(Vec), +} \ No newline at end of file diff --git a/sumcheck/src/utils/mod.rs b/sumcheck/src/utils/mod.rs index 41c63e1df..afb0810fd 100644 --- a/sumcheck/src/utils/mod.rs +++ b/sumcheck/src/utils/mod.rs @@ -4,5 +4,7 @@ // LICENSE file in the root directory of this source tree. mod univariate; +pub use univariate::{CompressedUnivariatePoly, CompressedUnivariatePolyEvals}; -mod multilinear; \ No newline at end of file +mod multilinear; +pub use multilinear::{EqFunction, MultiLinearPoly}; \ No newline at end of file diff --git a/sumcheck/src/utils/univariate.rs b/sumcheck/src/utils/univariate.rs index 868206213..361f97273 100644 --- a/sumcheck/src/utils/univariate.rs +++ b/sumcheck/src/utils/univariate.rs @@ -5,6 +5,7 @@ // LICENSE file in the root directory of this source tree. use alloc::vec::Vec; +use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; use math::{batch_inversion, polynom, FieldElement}; @@ -17,7 +18,7 @@ use math::{batch_inversion, polynom, FieldElement}; /// This compressed representation is useful during the sum-check protocol as the full uncompressed /// representation can be recovered from the compressed one and the current sum-check round claim. #[derive(Clone, Debug)] -pub struct CompressedUnivariatePoly(Vec); +pub struct CompressedUnivariatePoly(pub(crate) Vec); impl CompressedUnivariatePoly { /// Evaluates a polynomial at a challenge point using a round claim. @@ -38,13 +39,28 @@ impl CompressedUnivariatePoly { } } +impl Serializable for CompressedUnivariatePoly { + fn write_into(&self, target: &mut W) { + self.0.write_into(target); + } +} + +impl Deserializable for CompressedUnivariatePoly +where + E: FieldElement, +{ + fn read_from(source: &mut R) -> Result { + Ok(Self(Deserializable::read_from(source)?)) + } +} + /// The evaluations of a univariate polynomial of degree n at 0, 1, ..., n with the evaluation at 0 /// omitted. /// /// This compressed representation is useful during the sum-check protocol as the full uncompressed /// representation can be recovered from the compressed one and the current sum-check round claim. #[derive(Clone, Debug)] -pub struct CompressedUnivariatePolyEvals(Vec); +pub struct CompressedUnivariatePolyEvals(pub(crate) Vec); impl CompressedUnivariatePolyEvals { /// Gives the coefficient representation of a polynomial represented in evaluation form. From 6da91af869bd7de9f822a4a769d9d62bed4e1528 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:43:14 +0200 Subject: [PATCH 03/58] tests: add sanity tests for utils --- air/src/air/mod.rs | 3 +- sumcheck/src/utils/mod.rs | 2 +- sumcheck/src/utils/multilinear.rs | 55 ++++++++++++++++++++++++++++--- sumcheck/src/utils/univariate.rs | 11 +++---- 4 files changed, 57 insertions(+), 14 deletions(-) diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index da1e0cf44..0ea0e3528 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -615,7 +615,6 @@ pub trait Air: Send + Sync { } } - pub trait LogUpGkrEvaluator: Clone { /// Defines the base field of the evaluator. type BaseField: StarkField; @@ -679,4 +678,4 @@ pub enum LogUpGkrOracle { CurrentRow(usize), NextRow(usize), PeriodicValue(Vec), -} \ No newline at end of file +} diff --git a/sumcheck/src/utils/mod.rs b/sumcheck/src/utils/mod.rs index afb0810fd..d57e05677 100644 --- a/sumcheck/src/utils/mod.rs +++ b/sumcheck/src/utils/mod.rs @@ -7,4 +7,4 @@ mod univariate; pub use univariate::{CompressedUnivariatePoly, CompressedUnivariatePolyEvals}; mod multilinear; -pub use multilinear::{EqFunction, MultiLinearPoly}; \ No newline at end of file +pub use multilinear::{EqFunction, MultiLinearPoly}; diff --git a/sumcheck/src/utils/multilinear.rs b/sumcheck/src/utils/multilinear.rs index d6907d14c..c45b993e2 100644 --- a/sumcheck/src/utils/multilinear.rs +++ b/sumcheck/src/utils/multilinear.rs @@ -5,8 +5,8 @@ use alloc::vec::Vec; use core::ops::Index; -use math::FieldElement; +use math::FieldElement; #[cfg(feature = "concurrent")] pub use rayon::prelude::*; @@ -178,7 +178,7 @@ impl EqFunction { /// Computes the evaluations of the Lagrange basis polynomials over the interpolating /// set ${0 , 1}^ν$ at $(r_0, ..., r_{ν - 1})$ i.e., the Lagrange kernel at $(r_0, ..., r_{ν - 1})$. -/// +/// /// TODO: This is a critical function and parallelizing would have a significant impact on /// performance. fn compute_lagrange_basis_evals_at(query: &[E]) -> Vec { @@ -198,9 +198,8 @@ fn compute_lagrange_basis_evals_at(query: &[E]) -> Vec { evals } -/// Computes the inner product in the extension field of two iterators that must yield the same -/// number of items. -/// +/// Computes the inner product in the extension field of two slices with the same number of items. +/// /// If `concurrent` feature is enabled, this function can make use of multi-threading. pub fn inner_product(x: &[E], y: &[E]) -> E { #[cfg(not(feature = "concurrent"))] @@ -217,6 +216,26 @@ pub fn inner_product(x: &[E], y: &[E]) -> E { // TESTS // ================================================================================================ +#[test] +fn multi_linear_sanity_checks() { + use math::fields::f64::BaseElement; + let nu = 3; + let n = 1 << nu; + + // the zero multi-linear should evaluate to zero + let p = MultiLinearPoly::from_evaluations(vec![BaseElement::ZERO; n]); + let challenge: Vec = rand_utils::rand_vector(nu); + + assert_eq!(BaseElement::ZERO, p.evaluate(&challenge)); + + // the constant multi-linear should be constant everywhere + let constant = rand_utils::rand_value(); + let p = MultiLinearPoly::from_evaluations(vec![constant; n]); + let challenge: Vec = rand_utils::rand_vector(nu); + + assert_eq!(constant, p.evaluate(&challenge)) +} + #[test] fn test_bind() { use math::fields::f64::BaseElement; @@ -227,3 +246,29 @@ fn test_bind() { p.bind_least_significant_variable(challenge); assert_eq!(p, expected) } + +#[test] +fn test_eq_function() { + use math::fields::f64::BaseElement; + use rand_utils::rand_value; + + let one = BaseElement::ONE; + + // Lagrange kernel is computed correctly + let r0 = rand_value(); + let r1 = rand_value(); + let eq_function = EqFunction::new(vec![r0, r1]); + + let expected = vec![(one - r0) * (one - r1), r0 * (one - r1), (one - r0) * r1, r0 * r1]; + + assert_eq!(expected, eq_function.evaluations()); + + // Lagrange kernel evaluation is correct + let q0 = rand_value(); + let q1 = rand_value(); + let tensored_query = vec![(one - q0) * (one - q1), q0 * (one - q1), (one - q0) * q1, q0 * q1]; + + let expected = inner_product(&tensored_query, &eq_function.evaluations()); + + assert_eq!(expected, eq_function.evaluate(&[q0, q1])) +} diff --git a/sumcheck/src/utils/univariate.rs b/sumcheck/src/utils/univariate.rs index 361f97273..399d19b1b 100644 --- a/sumcheck/src/utils/univariate.rs +++ b/sumcheck/src/utils/univariate.rs @@ -1,13 +1,12 @@ - // Copyright (c) Facebook, Inc. and its affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. use alloc::vec::Vec; -use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; -use math::{batch_inversion, polynom, FieldElement}; +use math::{batch_inversion, polynom, FieldElement}; +use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; // COMPRESSED UNIVARIATE POLYNOMIAL // ================================================================================================ @@ -39,13 +38,13 @@ impl CompressedUnivariatePoly { } } -impl Serializable for CompressedUnivariatePoly { +impl Serializable for CompressedUnivariatePoly { fn write_into(&self, target: &mut W) { self.0.write_into(target); } } -impl Deserializable for CompressedUnivariatePoly +impl Deserializable for CompressedUnivariatePoly where E: FieldElement, { @@ -265,4 +264,4 @@ fn test_poly_partial() { let r = rand_utils::rand_vector(1); assert_eq!(polynom::eval(&p, r[0]), poly_coeff.evaluate_using_claim(&claim, &r[0])) -} \ No newline at end of file +} From 0dfc2299d944c8e326db2623a408a7ba0223dc74 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 7 Aug 2024 08:16:15 +0200 Subject: [PATCH 04/58] feat: use SmallVec --- sumcheck/src/utils/multilinear.rs | 8 +++++-- sumcheck/src/utils/univariate.rs | 37 +++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/sumcheck/src/utils/multilinear.rs b/sumcheck/src/utils/multilinear.rs index c45b993e2..0ad5f6a18 100644 --- a/sumcheck/src/utils/multilinear.rs +++ b/sumcheck/src/utils/multilinear.rs @@ -9,6 +9,7 @@ use core::ops::Index; use math::FieldElement; #[cfg(feature = "concurrent")] pub use rayon::prelude::*; +use smallvec::SmallVec; // MULTI-LINEAR POLYNOMIAL // ================================================================================================ @@ -102,6 +103,9 @@ impl Index for MultiLinearPoly { // EQ FUNCTION // ================================================================================================ +/// Maximal expected size of the point of a given Lagrange kernel. +const MAX_EQ_SIZE: usize = 25; + /// The EQ (equality) function is the binary function defined by /// /// $$ @@ -139,13 +143,13 @@ impl Index for MultiLinearPoly { /// as well as a method to evaluate $EQ^{~}((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1})))$ for /// $(t_0, ..., t_{ν - 1}) ∈ 𝔽^ν$. pub struct EqFunction { - r: Vec, + r: SmallVec<[E; MAX_EQ_SIZE]>, } impl EqFunction { /// Creates a new [EqFunction]. pub fn new(r: Vec) -> Self { - let tmp = r.clone(); + let tmp = r.into(); EqFunction { r: tmp } } diff --git a/sumcheck/src/utils/univariate.rs b/sumcheck/src/utils/univariate.rs index 399d19b1b..8cd56e683 100644 --- a/sumcheck/src/utils/univariate.rs +++ b/sumcheck/src/utils/univariate.rs @@ -6,8 +6,15 @@ use alloc::vec::Vec; use math::{batch_inversion, polynom, FieldElement}; +use smallvec::SmallVec; use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; +// CONSTANTS +// ================================================================================================ + +/// Maximum expected size of the round polynomials. This is needed for `SmallVec`. +const MAX_POLY_SIZE: usize = 10; + // COMPRESSED UNIVARIATE POLYNOMIAL // ================================================================================================ @@ -16,8 +23,8 @@ use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serial /// /// This compressed representation is useful during the sum-check protocol as the full uncompressed /// representation can be recovered from the compressed one and the current sum-check round claim. -#[derive(Clone, Debug)] -pub struct CompressedUnivariatePoly(pub(crate) Vec); +#[derive(Clone, Debug, PartialEq)] +pub struct CompressedUnivariatePoly(pub(crate) SmallVec<[E; MAX_POLY_SIZE]>); impl CompressedUnivariatePoly { /// Evaluates a polynomial at a challenge point using a round claim. @@ -40,7 +47,8 @@ impl CompressedUnivariatePoly { impl Serializable for CompressedUnivariatePoly { fn write_into(&self, target: &mut W) { - self.0.write_into(target); + let vector: Vec = self.0.clone().into_vec(); + vector.write_into(target); } } @@ -49,7 +57,8 @@ where E: FieldElement, { fn read_from(source: &mut R) -> Result { - Ok(Self(Deserializable::read_from(source)?)) + let vector: Vec = Vec::::read_from(source)?; + Ok(Self(vector.into())) } } @@ -59,7 +68,7 @@ where /// This compressed representation is useful during the sum-check protocol as the full uncompressed /// representation can be recovered from the compressed one and the current sum-check round claim. #[derive(Clone, Debug)] -pub struct CompressedUnivariatePolyEvals(pub(crate) Vec); +pub struct CompressedUnivariatePolyEvals(pub(crate) SmallVec<[E; MAX_POLY_SIZE]>); impl CompressedUnivariatePolyEvals { /// Gives the coefficient representation of a polynomial represented in evaluation form. @@ -104,7 +113,7 @@ impl CompressedUnivariatePolyEvals { // append c0 to the coefficients of q to get the coefficients of p. The linear term // coefficient is removed as this can be recovered from the other coefficients using // the reduced claim. - let mut coefficients = Vec::with_capacity(self.0.len() + 1); + let mut coefficients = SmallVec::with_capacity(self.0.len() + 1); coefficients.push(c0); coefficients.extend_from_slice(&q_coefs[1..]); @@ -257,7 +266,7 @@ fn test_poly_partial() { let mut partial_evals = evals.clone(); partial_evals.remove(0); - let partial_poly = CompressedUnivariatePolyEvals(partial_evals); + let partial_poly = CompressedUnivariatePolyEvals(partial_evals.into()); let claim = evals[0] + evals[1]; let poly_coeff = partial_poly.to_poly(claim); @@ -265,3 +274,17 @@ fn test_poly_partial() { assert_eq!(polynom::eval(&p, r[0]), poly_coeff.evaluate_using_claim(&claim, &r[0])) } + +#[test] +fn test_serialization() { + use math::fields::f64::BaseElement; + + let original_poly = + CompressedUnivariatePoly(rand_utils::rand_array::().into()); + let poly_bytes = original_poly.to_bytes(); + + let deserialized_poly = + CompressedUnivariatePoly::::read_from_bytes(&poly_bytes).unwrap(); + + assert_eq!(original_poly, deserialized_poly) +} From a51a464fbc1fdc283667b9abd919437591acfbc3 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 9 Aug 2024 09:34:11 +0200 Subject: [PATCH 05/58] feat: add remaining functions for sum-check verifier --- sumcheck/src/verifier/mod.rs | 141 ++++++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 2 deletions(-) diff --git a/sumcheck/src/verifier/mod.rs b/sumcheck/src/verifier/mod.rs index 887598cc8..3f2a57581 100644 --- a/sumcheck/src/verifier/mod.rs +++ b/sumcheck/src/verifier/mod.rs @@ -126,7 +126,7 @@ where let round_poly_coefs = round_proof.round_poly_coefs.clone(); coin.reseed(H::hash_elements(&round_poly_coefs.0)); - let r = coin.draw().map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; + let r = coin.draw().map_err(|_| Error::FailedToGenerateChallenge)?; round_claim = round_proof.round_poly_coefs.evaluate_using_claim(&round_claim, &r); evaluation_point.push(r); @@ -138,8 +138,145 @@ where }) } +/// Verifies sum-check proofs, as part of the GKR proof, for all GKR layers except for the last one +/// i.e., the circuit input layer. +pub fn verify_sum_check_intermediate_layers< + E: FieldElement, + C: RandomCoin, + H: ElementHasher, +>( + proof: &SumCheckProof, + gkr_eval_point: &[E], + claim: (E, E), + transcript: &mut C, +) -> Result, Error> { + // generate challenge to batch sum-checks + transcript.reseed(H::hash_elements(&[claim.0, claim.1])); + let r_batch: E = transcript.draw().map_err(|_| Error::FailedToGenerateChallenge)?; + + // compute the claim for the batched sum-check + let reduced_claim = claim.0 + claim.1 * r_batch; + + let SumCheckProof { openings_claim, round_proofs } = proof; + + let final_round_claim = verify_rounds(reduced_claim, round_proofs, transcript)?; + assert_eq!(openings_claim.eval_point, final_round_claim.eval_point); + + let p0 = openings_claim.openings[0]; + let p1 = openings_claim.openings[1]; + let q0 = openings_claim.openings[2]; + let q1 = openings_claim.openings[3]; + + let eq = EqFunction::new(gkr_eval_point.to_vec()).evaluate(&openings_claim.eval_point); + + if (p0 * q1 + p1 * q0 + r_batch * q0 * q1) * eq != final_round_claim.claim { + return Err(Error::FinalEvaluationCheckFailed); + } + + Ok(openings_claim.clone()) +} + +/// Verifies the final sum-check proof of a GKR proof. +pub fn verify_sum_check_input_layer< + E: FieldElement, + C: RandomCoin, + H: ElementHasher, +>( + evaluator: &impl LogUpGkrEvaluator, + proof: &FinalLayerProof, + log_up_randomness: Vec, + gkr_eval_point: &[E], + claim: (E, E), + transcript: &mut C, +) -> Result, Error> { + let FinalLayerProof { before_merge_proof, after_merge_proof } = proof; + + // generate challenge to batch sum-checks + transcript.reseed(H::hash_elements(&[claim.0, claim.1])); + let r_batch: E = transcript.draw().map_err(|_| Error::FailedToGenerateChallenge)?; + + // compute the claim for the batched sum-check + let reduced_claim = claim.0 + claim.1 * r_batch; + + // verify the first half of the sum-check proof i.e., `before_merge_proof` + let SumCheckRoundClaim { eval_point: rand_merge, claim } = + verify_rounds(reduced_claim, before_merge_proof, transcript)?; + + // verify the second half of the sum-check proof i.e., `after_merge_proof` + verify_sum_check_final( + claim, + after_merge_proof, + rand_merge, + r_batch, + log_up_randomness, + gkr_eval_point, + evaluator, + transcript, + ) +} + +/// Verifies the second sum-check proof for the input layer, including the final check, and returns +/// a [`FinalOpeningClaim`] to the STARK verifier in order to verify the correctness of +/// the openings. +#[allow(clippy::too_many_arguments)] +fn verify_sum_check_final< + E: FieldElement, + C: RandomCoin, + H: ElementHasher, +>( + claim: E, + after_merge_proof: &SumCheckProof, + rand_merge: Vec, + r_batch: E, + log_up_randomness: Vec, + gkr_eval_point: &[E], + evaluator: &impl LogUpGkrEvaluator, + transcript: &mut C, +) -> Result, Error> { + let SumCheckProof { openings_claim, round_proofs } = after_merge_proof; + + let SumCheckRoundClaim { + eval_point: evaluation_point, + claim: claimed_evaluation, + } = verify_rounds(claim, round_proofs, transcript)?; + + if openings_claim.eval_point != evaluation_point { + return Err(Error::WrongOpeningPoint); + } + + let mut numerators = vec![E::ZERO; evaluator.get_num_fractions()]; + let mut denominators = vec![E::ZERO; evaluator.get_num_fractions()]; + + evaluator.evaluate_query( + &openings_claim.openings, + &log_up_randomness, + &mut numerators, + &mut denominators, + ); + + let lagrange_ker = EqFunction::new(gkr_eval_point.to_vec()); + let mut gkr_point = rand_merge.clone(); + + gkr_point.extend_from_slice(&openings_claim.eval_point.clone()); + let eq_eval = lagrange_ker.evaluate(&gkr_point); + let tensored_merge_randomness = EqFunction::ml_at(rand_merge.to_vec()).evaluations().to_vec(); + let expected_evaluation = evaluate_composition_poly( + &numerators, + &denominators, + eq_eval, + r_batch, + &tensored_merge_randomness, + ); + + if expected_evaluation != claimed_evaluation { + Err(Error::FinalEvaluationCheckFailed) + } else { + Ok(openings_claim.clone()) + } +} + #[derive(Debug, thiserror::Error)] -pub enum SumCheckVerifierError { +pub enum Error { #[error("the final evaluation check of sum-check failed")] FinalEvaluationCheckFailed, #[error("failed to generate round challenge")] From 581343d4245e38942a67db89976499597c95d96b Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:12:18 +0200 Subject: [PATCH 06/58] chore: move prover into sub-mod --- sumcheck/src/utils/univariate.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sumcheck/src/utils/univariate.rs b/sumcheck/src/utils/univariate.rs index 8cd56e683..082a4daf9 100644 --- a/sumcheck/src/utils/univariate.rs +++ b/sumcheck/src/utils/univariate.rs @@ -12,7 +12,12 @@ use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serial // CONSTANTS // ================================================================================================ -/// Maximum expected size of the round polynomials. This is needed for `SmallVec`. +/// Maximum expected size of the round polynomials. This is needed for `SmallVec`. The size of +/// the round polynomials is dictated by the degree of the non-linearity in the sum-check statement +/// which is direcly influenced by the maximal degrees of the numerators and denominators appearing +/// in the LogUp-GKR relation and equal to one plus the maximal degree of the numerators and +/// maximal degree of denominators. +/// The following value assumes that this degree is at most 10. const MAX_POLY_SIZE: usize = 10; // COMPRESSED UNIVARIATE POLYNOMIAL From 34807c9fa661e591488aca1258f94169f29972ff Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:17:44 +0200 Subject: [PATCH 07/58] chore: remove utils mod --- sumcheck/src/utils/mod.rs | 10 - sumcheck/src/utils/multilinear.rs | 278 ---------------------------- sumcheck/src/utils/univariate.rs | 295 ------------------------------ 3 files changed, 583 deletions(-) delete mode 100644 sumcheck/src/utils/mod.rs delete mode 100644 sumcheck/src/utils/multilinear.rs delete mode 100644 sumcheck/src/utils/univariate.rs diff --git a/sumcheck/src/utils/mod.rs b/sumcheck/src/utils/mod.rs deleted file mode 100644 index d57e05677..000000000 --- a/sumcheck/src/utils/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -mod univariate; -pub use univariate::{CompressedUnivariatePoly, CompressedUnivariatePolyEvals}; - -mod multilinear; -pub use multilinear::{EqFunction, MultiLinearPoly}; diff --git a/sumcheck/src/utils/multilinear.rs b/sumcheck/src/utils/multilinear.rs deleted file mode 100644 index 0ad5f6a18..000000000 --- a/sumcheck/src/utils/multilinear.rs +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -use alloc::vec::Vec; -use core::ops::Index; - -use math::FieldElement; -#[cfg(feature = "concurrent")] -pub use rayon::prelude::*; -use smallvec::SmallVec; - -// MULTI-LINEAR POLYNOMIAL -// ================================================================================================ - -/// Represents a multi-linear polynomial. -/// -/// The representation stores the evaluations of the polynomial over the boolean hyper-cube -/// ${0 , 1}^ν$. -#[derive(Clone, Debug, PartialEq)] -pub struct MultiLinearPoly { - evaluations: Vec, -} - -impl MultiLinearPoly { - /// Constructs a [`MultiLinearPoly`] from its evaluations over the boolean hyper-cube ${0 , 1}^ν$. - pub fn from_evaluations(evaluations: Vec) -> Self { - assert!(evaluations.len().is_power_of_two(), "A multi-linear polynomial should have a power of 2 number of evaluations over the Boolean hyper-cube"); - Self { evaluations } - } - - /// Returns the number of variables of the multi-linear polynomial. - pub fn num_variables(&self) -> usize { - self.evaluations.len().trailing_zeros() as usize - } - - /// Returns the evaluations over the boolean hyper-cube. - pub fn evaluations(&self) -> &[E] { - &self.evaluations - } - - /// Returns the number of evaluations. This is equal to the size of the boolean hyper-cube. - pub fn num_evaluations(&self) -> usize { - self.evaluations.len() - } - - /// Evaluate the multi-linear at some query $(r_0, ..., r_{ν - 1}) ∈ 𝔽^ν$. - /// - /// It first computes the evaluations of the Lagrange basis polynomials over the interpolating - /// set ${0 , 1}^ν$ at $(r_0, ..., r_{ν - 1})$ i.e., the Lagrange kernel at $(r_0, ..., r_{ν - 1})$. - /// The evaluation then is the inner product, indexed by ${0 , 1}^ν$, of the vector of - /// evaluations times the Lagrange kernel. - pub fn evaluate(&self, query: &[E]) -> E { - let tensored_query = compute_lagrange_basis_evals_at(query); - inner_product(&self.evaluations, &tensored_query) - } - - /// Similar to [`Self::evaluate`], except that the query was already turned into the Lagrange - /// kernel (i.e. the [`lagrange_ker::EqFunction`] evaluated at every point in the set - /// `${0 , 1}^ν$`). - /// - /// This is more efficient than [`Self::evaluate`] when multiple different [`MultiLinearPoly`] - /// need to be evaluated at the same query point. - pub fn evaluate_with_lagrange_kernel(&self, lagrange_kernel: &[E]) -> E { - inner_product(&self.evaluations, lagrange_kernel) - } - - /// Computes $f(r_0, y_1, ..., y_{ν - 1})$ using the linear interpolation formula - /// $(1 - r_0) * f(0, y_1, ..., y_{ν - 1}) + r_0 * f(1, y_1, ..., y_{ν - 1})$ and assigns - /// the resulting multi-linear, defined over a domain of half the size, to `self`. - pub fn bind_least_significant_variable(&mut self, round_challenge: E) { - let num_evals = self.evaluations.len() >> 1; - for i in 0..num_evals { - self.evaluations[i] = self.evaluations[i << 1] - + round_challenge * (self.evaluations[(i << 1) + 1] - self.evaluations[i << 1]); - } - self.evaluations.truncate(num_evals) - } - - /// Given the multilinear polynomial $f(y_0, y_1, ..., y_{ν - 1})$, returns two polynomials: - /// $f(0, y_1, ..., y_{ν - 1})$ and $f(1, y_1, ..., y_{ν - 1})$. - pub fn project_least_significant_variable(&self) -> (Self, Self) { - let mut p0 = Vec::with_capacity(self.num_evaluations() / 2); - let mut p1 = Vec::with_capacity(self.num_evaluations() / 2); - for chunk in self.evaluations.chunks_exact(2) { - p0.push(chunk[0]); - p1.push(chunk[1]); - } - - (MultiLinearPoly::from_evaluations(p0), MultiLinearPoly::from_evaluations(p1)) - } -} - -impl Index for MultiLinearPoly { - type Output = E; - - fn index(&self, index: usize) -> &E { - &(self.evaluations[index]) - } -} - -// EQ FUNCTION -// ================================================================================================ - -/// Maximal expected size of the point of a given Lagrange kernel. -const MAX_EQ_SIZE: usize = 25; - -/// The EQ (equality) function is the binary function defined by -/// -/// $$ -/// EQ: {0 , 1}^ν ⛌ {0 , 1}^ν ⇾ {0 , 1} -/// ((x_0, ..., x_{ν - 1}), (y_0, ..., y_{ν - 1})) ↦ \prod_{i = 0}^{ν - 1} (x_i * y_i + (1 - x_i) -/// * (1 - y_i)) -/// $$ -/// -/// Taking its multi-linear extension $EQ^{~}$, we can define a basis for the set of multi-linear -/// polynomials in ν variables by -/// $${EQ^{~}(., (y_0, ..., y_{ν - 1})): (y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν}$$ -/// where each basis function is a function of its first argument. This is called the Lagrange or -/// evaluation basis for evaluation set ${0 , 1}^ν$. -/// -/// Given a function $(f: {0 , 1}^ν ⇾ 𝔽)$, its multi-linear extension (i.e., the unique -/// mult-linear polynomial extending `f` to $(f^{~}: 𝔽^ν ⇾ 𝔽)$ and agreeing with it on ${0 , 1}^ν$) is -/// defined as the summation of the evaluations of f against the Lagrange basis. -/// More specifically, given $(r_0, ..., r_{ν - 1}) ∈ 𝔽^ν$, then: -/// -/// $$ -/// f^{~}(r_0, ..., r_{ν - 1}) = \sum_{(y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν} -/// f(y_0, ..., y_{ν - 1}) EQ^{~}((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})) -/// $$ -/// -/// We call the Lagrange kernel the evaluation of the EQ^{~} function at -/// $((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))$ for all $(y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν$ for -/// a fixed $(r_0, ..., r_{ν - 1}) ∈ 𝔽^ν$. -/// -/// [`EqFunction`] represents EQ^{~} the multi-linear extension of -/// -/// $((y_0, ..., y_{ν - 1}) ↦ EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})))$ -/// -/// and contains a method to generate the Lagrange kernel for defining evaluations of multi-linear -/// extensions of arbitrary functions $(f: {0 , 1}^ν ⇾ 𝔽)$ at a given point $(r_0, ..., r_{ν - 1})$ -/// as well as a method to evaluate $EQ^{~}((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1})))$ for -/// $(t_0, ..., t_{ν - 1}) ∈ 𝔽^ν$. -pub struct EqFunction { - r: SmallVec<[E; MAX_EQ_SIZE]>, -} - -impl EqFunction { - /// Creates a new [EqFunction]. - pub fn new(r: Vec) -> Self { - let tmp = r.into(); - EqFunction { r: tmp } - } - - /// Computes $EQ((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1})))$. - pub fn evaluate(&self, t: &[E]) -> E { - assert_eq!(self.r.len(), t.len()); - - (0..self.r.len()) - .map(|i| self.r[i] * t[i] + (E::ONE - self.r[i]) * (E::ONE - t[i])) - .fold(E::ONE, |acc, term| acc * term) - } - - /// Computes $EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))$ for all - /// $(y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν$ i.e., the Lagrange kernel at $r = (r_0, ..., r_{ν - 1})$. - pub fn evaluations(&self) -> Vec { - compute_lagrange_basis_evals_at(&self.r) - } - - /// Returns the evaluations of - /// $((y_0, ..., y_{ν - 1}) ↦ EQ^{~}((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})))$ - /// over ${0 , 1}^ν$. - pub fn ml_at(evaluation_point: Vec) -> MultiLinearPoly { - let eq_evals = EqFunction::new(evaluation_point.clone()).evaluations(); - MultiLinearPoly::from_evaluations(eq_evals) - } -} - -// HELPER -// ================================================================================================ - -/// Computes the evaluations of the Lagrange basis polynomials over the interpolating -/// set ${0 , 1}^ν$ at $(r_0, ..., r_{ν - 1})$ i.e., the Lagrange kernel at $(r_0, ..., r_{ν - 1})$. -/// -/// TODO: This is a critical function and parallelizing would have a significant impact on -/// performance. -fn compute_lagrange_basis_evals_at(query: &[E]) -> Vec { - let nu = query.len(); - let n = 1 << nu; - - let mut evals: Vec = vec![E::ONE; n]; - let mut size = 1; - for r_i in query.iter().rev() { - size *= 2; - for i in (0..size).rev().step_by(2) { - let scalar = evals[i / 2]; - evals[i] = scalar * *r_i; - evals[i - 1] = scalar - evals[i]; - } - } - evals -} - -/// Computes the inner product in the extension field of two slices with the same number of items. -/// -/// If `concurrent` feature is enabled, this function can make use of multi-threading. -pub fn inner_product(x: &[E], y: &[E]) -> E { - #[cfg(not(feature = "concurrent"))] - return x.iter().zip(y.iter()).fold(E::ZERO, |acc, (x_i, y_i)| acc + *x_i * *y_i); - - #[cfg(feature = "concurrent")] - return x - .par_iter() - .zip(y.par_iter()) - .map(|(x_i, y_i)| *x_i * *y_i) - .reduce(|| E::ZERO, |a, b| a + b); -} - -// TESTS -// ================================================================================================ - -#[test] -fn multi_linear_sanity_checks() { - use math::fields::f64::BaseElement; - let nu = 3; - let n = 1 << nu; - - // the zero multi-linear should evaluate to zero - let p = MultiLinearPoly::from_evaluations(vec![BaseElement::ZERO; n]); - let challenge: Vec = rand_utils::rand_vector(nu); - - assert_eq!(BaseElement::ZERO, p.evaluate(&challenge)); - - // the constant multi-linear should be constant everywhere - let constant = rand_utils::rand_value(); - let p = MultiLinearPoly::from_evaluations(vec![constant; n]); - let challenge: Vec = rand_utils::rand_vector(nu); - - assert_eq!(constant, p.evaluate(&challenge)) -} - -#[test] -fn test_bind() { - use math::fields::f64::BaseElement; - let mut p = MultiLinearPoly::from_evaluations(vec![BaseElement::ONE; 8]); - let expected = MultiLinearPoly::from_evaluations(vec![BaseElement::ONE; 4]); - - let challenge = rand_utils::rand_value(); - p.bind_least_significant_variable(challenge); - assert_eq!(p, expected) -} - -#[test] -fn test_eq_function() { - use math::fields::f64::BaseElement; - use rand_utils::rand_value; - - let one = BaseElement::ONE; - - // Lagrange kernel is computed correctly - let r0 = rand_value(); - let r1 = rand_value(); - let eq_function = EqFunction::new(vec![r0, r1]); - - let expected = vec![(one - r0) * (one - r1), r0 * (one - r1), (one - r0) * r1, r0 * r1]; - - assert_eq!(expected, eq_function.evaluations()); - - // Lagrange kernel evaluation is correct - let q0 = rand_value(); - let q1 = rand_value(); - let tensored_query = vec![(one - q0) * (one - q1), q0 * (one - q1), (one - q0) * q1, q0 * q1]; - - let expected = inner_product(&tensored_query, &eq_function.evaluations()); - - assert_eq!(expected, eq_function.evaluate(&[q0, q1])) -} diff --git a/sumcheck/src/utils/univariate.rs b/sumcheck/src/utils/univariate.rs deleted file mode 100644 index 082a4daf9..000000000 --- a/sumcheck/src/utils/univariate.rs +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -use alloc::vec::Vec; - -use math::{batch_inversion, polynom, FieldElement}; -use smallvec::SmallVec; -use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; - -// CONSTANTS -// ================================================================================================ - -/// Maximum expected size of the round polynomials. This is needed for `SmallVec`. The size of -/// the round polynomials is dictated by the degree of the non-linearity in the sum-check statement -/// which is direcly influenced by the maximal degrees of the numerators and denominators appearing -/// in the LogUp-GKR relation and equal to one plus the maximal degree of the numerators and -/// maximal degree of denominators. -/// The following value assumes that this degree is at most 10. -const MAX_POLY_SIZE: usize = 10; - -// COMPRESSED UNIVARIATE POLYNOMIAL -// ================================================================================================ - -/// The coefficients of a univariate polynomial of degree n with the linear term coefficient -/// omitted. -/// -/// This compressed representation is useful during the sum-check protocol as the full uncompressed -/// representation can be recovered from the compressed one and the current sum-check round claim. -#[derive(Clone, Debug, PartialEq)] -pub struct CompressedUnivariatePoly(pub(crate) SmallVec<[E; MAX_POLY_SIZE]>); - -impl CompressedUnivariatePoly { - /// Evaluates a polynomial at a challenge point using a round claim. - /// - /// The round claim is used to recover the coefficient of the linear term using the relation - /// 2 * c0 + c1 + ... c_{n - 1} = claim. Using the complete list of coefficients, the polynomial - /// is then evaluated using Horner's method. - pub fn evaluate_using_claim(&self, claim: &E, challenge: &E) -> E { - // recover the coefficient of the linear term - let c1 = *claim - self.0.iter().fold(E::ZERO, |acc, term| acc + *term) - self.0[0]; - - // construct the full coefficient list - let mut complete_coefficients = vec![self.0[0], c1]; - complete_coefficients.extend_from_slice(&self.0[1..]); - - // evaluate - polynom::eval(&complete_coefficients, *challenge) - } -} - -impl Serializable for CompressedUnivariatePoly { - fn write_into(&self, target: &mut W) { - let vector: Vec = self.0.clone().into_vec(); - vector.write_into(target); - } -} - -impl Deserializable for CompressedUnivariatePoly -where - E: FieldElement, -{ - fn read_from(source: &mut R) -> Result { - let vector: Vec = Vec::::read_from(source)?; - Ok(Self(vector.into())) - } -} - -/// The evaluations of a univariate polynomial of degree n at 0, 1, ..., n with the evaluation at 0 -/// omitted. -/// -/// This compressed representation is useful during the sum-check protocol as the full uncompressed -/// representation can be recovered from the compressed one and the current sum-check round claim. -#[derive(Clone, Debug)] -pub struct CompressedUnivariatePolyEvals(pub(crate) SmallVec<[E; MAX_POLY_SIZE]>); - -impl CompressedUnivariatePolyEvals { - /// Gives the coefficient representation of a polynomial represented in evaluation form. - /// - /// Since the evaluation at 0 is omitted, we need to use the round claim to recover - /// the evaluation at 0 using the identity $p(0) + p(1) = claim$. - /// Now, we have that for any polynomial $p(x) = c0 + c1 * x + ... + c_{n-1} * x^{n - 1}$: - /// - /// 1. $p(0) = c0$. - /// 2. $p(x) = c0 + x * q(x) where q(x) = c1 + ... + c_{n-1} * x^{n - 2}$. - /// - /// This means that we can compute the evaluations of q at 1, ..., n - 1 using the evaluations - /// of p and thus reduce by 1 the size of the interpolation problem. - /// Once the coefficient of q are recovered, the c0 coefficient is appended to these and this - /// is precisely the coefficient representation of the original polynomial q. - /// Note that the coefficient of the linear term is removed as this coefficient can be recovered - /// from the remaining coefficients, again, using the round claim using the relation - /// $2 * c0 + c1 + ... c_{n - 1} = claim$. - pub fn to_poly(&self, round_claim: E) -> CompressedUnivariatePoly { - // construct the vector of interpolation points 1, ..., n - let n_minus_1 = self.0.len(); - let points = (1..=n_minus_1 as u32).map(E::BaseField::from).collect::>(); - - // construct their inverses. These will be needed for computing the evaluations - // of the q polynomial as well as for doing the interpolation on q - let points_inv = batch_inversion(&points); - - // compute the zeroth coefficient - let c0 = round_claim - self.0[0]; - - // compute the evaluations of q - let q_evals: Vec = self - .0 - .iter() - .enumerate() - .map(|(i, evals)| (*evals - c0).mul_base(points_inv[i])) - .collect(); - - // interpolate q - let q_coefs = multiply_by_inverse_vandermonde(&q_evals, &points_inv); - - // append c0 to the coefficients of q to get the coefficients of p. The linear term - // coefficient is removed as this can be recovered from the other coefficients using - // the reduced claim. - let mut coefficients = SmallVec::with_capacity(self.0.len() + 1); - coefficients.push(c0); - coefficients.extend_from_slice(&q_coefs[1..]); - - CompressedUnivariatePoly(coefficients) - } -} - -// HELPER FUNCTIONS -// ================================================================================================ - -/// Given a (row) vector `v`, computes the vector-matrix product `v * V^{-1}` where `V` is -/// the Vandermonde matrix over the points `1, ..., n` where `n` is the length of `v`. -/// The resulting vector will then be the coefficients of the minimal interpolating polynomial -/// through the points `(i+1, v[i])` for `i` in `0, ..., n - 1` -/// -/// The naive way would be to invert the matrix `V` and then compute the vector-matrix product -/// this will cost `O(n^3)` operations and `O(n^2)` memory. We can also try Gaussian elimination -/// but this is also worst case `O(n^3)` operations and `O(n^2)` memory. -/// In the following implementation, we use the fact that the points over which we are interpolating -/// is a set of equidistant points and thus both the Vandermonde matrix and its inverse can be -/// described by sparse linear recurrence equations. -/// More specifically, we use the representation given in [1], where `V^{-1}` is represented as -/// `U * M` where: -/// -/// 1. `M` is a lower triangular matrix where its entries are given by M(i, j) = M(i - 1, j) - M(i - -/// 1, j - 1) / (i - 1) with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. -/// -/// 2. `U` is an upper triangular (involutory) matrix where its entries are given by U(i, j) = U(i, -/// j - 1) - U(i - 1, j - 1) with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. -/// -/// Note that the matrix indexing in the formulas above matches the one in the reference and starts -/// from 1. -/// -/// The above implies that we can do the vector-matrix multiplication in `O(n^2)` and using only -/// `O(n)` space. -/// -/// [1]: https://link.springer.com/article/10.1007/s002110050360 -fn multiply_by_inverse_vandermonde( - vector: &[E], - nodes_inv: &[E::BaseField], -) -> Vec { - let res = multiply_by_u(vector); - multiply_by_m(&res, nodes_inv) -} - -/// Multiplies a (row) vector `v` by an upper triangular matrix `U` to compute `v * U`. -/// -/// `U` is an upper triangular (involutory) matrix with its entries given by -/// U(i, j) = U(i, j - 1) - U(i - 1, j - 1) -/// with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. -fn multiply_by_u(vector: &[E]) -> Vec { - let n = vector.len(); - let mut previous_u_col = vec![E::BaseField::ZERO; n]; - previous_u_col[0] = E::BaseField::ONE; - let mut current_u_col = vec![E::BaseField::ZERO; n]; - current_u_col[0] = E::BaseField::ONE; - - let mut result: Vec = vec![E::ZERO; n]; - for (i, res) in result.iter_mut().enumerate() { - *res = vector[0]; - - for (j, v) in vector.iter().enumerate().take(i + 1).skip(1) { - let u_entry: E::BaseField = - compute_u_entry::(j, &mut previous_u_col, &mut current_u_col); - *res += v.mul_base(u_entry); - } - previous_u_col.clone_from(¤t_u_col); - } - - result -} - -/// Multiplies a (row) vector `v` by a lower triangular matrix `M` to compute `v * M`. -/// -/// `M` is a lower triangular matrix with its entries given by -/// M(i, j) = M(i - 1, j) - M(i - 1, j - 1) / (i - 1) -/// with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. -fn multiply_by_m(vector: &[E], nodes_inv: &[E::BaseField]) -> Vec { - let n = vector.len(); - let mut previous_m_col = vec![E::BaseField::ONE; n]; - let mut current_m_col = vec![E::BaseField::ZERO; n]; - current_m_col[0] = E::BaseField::ONE; - - let mut result: Vec = vec![E::ZERO; n]; - result[0] = vector.iter().fold(E::ZERO, |acc, term| acc + *term); - for (i, res) in result.iter_mut().enumerate().skip(1) { - current_m_col = vec![E::BaseField::ZERO; n]; - - for (j, v) in vector.iter().enumerate().skip(i) { - let m_entry: E::BaseField = - compute_m_entry::(j, &mut previous_m_col, &mut current_m_col, nodes_inv[j - 1]); - *res += v.mul_base(m_entry); - } - previous_m_col.clone_from(¤t_m_col); - } - - result -} - -/// Returns the j-th entry of the i-th column of matrix `U` given the values of the (i - 1)-th -/// column. The i-th column is also updated with the just computed `U(i, j)` entry. -/// -/// `U` is an upper triangular (involutory) matrix with its entries given by -/// U(i, j) = U(i, j - 1) - U(i - 1, j - 1) -/// with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. -fn compute_u_entry( - j: usize, - col_prev: &mut [E::BaseField], - col_cur: &mut [E::BaseField], -) -> E::BaseField { - let value = col_prev[j] - col_prev[j - 1]; - col_cur[j] = value; - value -} - -/// Returns the j-th entry of the i-th column of matrix `M` given the values of the (i - 1)-th -/// and the i-th columns. The i-th column is also updated with the just computed `M(i, j)` entry. -/// -/// `M` is a lower triangular matrix with its entries given by -/// M(i, j) = M(i - 1, j) - M(i - 1, j - 1) / (i - 1) -/// with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. -fn compute_m_entry( - j: usize, - col_previous: &mut [E::BaseField], - col_current: &mut [E::BaseField], - node_inv: E::BaseField, -) -> E::BaseField { - let value = col_current[j - 1] - node_inv * col_previous[j - 1]; - col_current[j] = value; - value -} - -// TESTS -// ================================================================================================ - -#[test] -fn test_poly_partial() { - use math::fields::f64::BaseElement; - - let degree = 1000; - let mut points: Vec = vec![BaseElement::ZERO; degree]; - points - .iter_mut() - .enumerate() - .for_each(|(i, node)| *node = BaseElement::from(i as u32)); - - let p: Vec = rand_utils::rand_vector(degree); - let evals = polynom::eval_many(&p, &points); - - let mut partial_evals = evals.clone(); - partial_evals.remove(0); - - let partial_poly = CompressedUnivariatePolyEvals(partial_evals.into()); - let claim = evals[0] + evals[1]; - let poly_coeff = partial_poly.to_poly(claim); - - let r = rand_utils::rand_vector(1); - - assert_eq!(polynom::eval(&p, r[0]), poly_coeff.evaluate_using_claim(&claim, &r[0])) -} - -#[test] -fn test_serialization() { - use math::fields::f64::BaseElement; - - let original_poly = - CompressedUnivariatePoly(rand_utils::rand_array::().into()); - let poly_bytes = original_poly.to_bytes(); - - let deserialized_poly = - CompressedUnivariatePoly::::read_from_bytes(&poly_bytes).unwrap(); - - assert_eq!(original_poly, deserialized_poly) -} From e61041170462e7e1ad4ff9b4010eae7a80fe1650 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:27:12 +0200 Subject: [PATCH 08/58] chore: move logup evaluator trait to separate file --- air/src/air/mod.rs | 65 ---------------------------------------------- 1 file changed, 65 deletions(-) diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index 0ea0e3528..5dcee0717 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -614,68 +614,3 @@ pub trait Air: Send + Sync { }) } } - -pub trait LogUpGkrEvaluator: Clone { - /// Defines the base field of the evaluator. - type BaseField: StarkField; - - /// Public inputs need to compute the final claim. - type PublicInputs: ToElements + Send; - - /// Gets a list of all oracles involved in LogUp-GKR; this is intended to be used in construction of - /// MLEs. - fn get_oracles(&self) -> Vec>; - - /// Returns the number of random values needed to evaluate a query. - fn get_num_rand_values(&self) -> usize; - - /// Returns the number of fractions in the LogUp-GKR statement. - fn get_num_fractions(&self) -> usize; - - /// Returns the maximal degree of the multi-variate associated to the input layer. - fn max_degree(&self) -> usize; - - /// Builds a query from the provided main trace frame and periodic values. - /// - /// Note: it should be possible to provide an implementation of this method based on the - /// information returned from `get_oracles()`. However, this implementation is likely to be - /// expensive compared to the hand-written implementation. However, we could provide a test - /// which verifies that `get_oracles()` and `build_query()` methods are consistent. - fn build_query(&self, frame: &EvaluationFrame, periodic_values: &[E]) -> Vec - where - E: FieldElement; - - /// Evaluates the provided query and writes the results into the numerators and denominators. - /// - /// Note: it is also possible to combine `build_query()` and `evaluate_query()` into a single - /// method to avoid the need to first build the query struct and then evaluate it. However: - /// - We assume that the compiler will be able to optimize this away. - /// - Merging the methods will make it more difficult avoid inconsistencies between - /// `evaluate_query()` and `get_oracles()` methods. - fn evaluate_query( - &self, - query: &[F], - rand_values: &[E], - numerator: &mut [E], - denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + ExtensionOf; - - /// Computes the final claim for the LogUp-GKR circuit. - /// - /// The default implementation of this method returns E::ZERO as it is expected that the - /// fractional sums will cancel out. However, in cases when some boundary conditions need to - /// be imposed on the LogUp-GKR relations, this method can be overridden to compute the final - /// expected claim. - fn compute_claim(&self, inputs: &Self::PublicInputs, rand_values: &[E]) -> E - where - E: FieldElement; -} - -#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] -pub enum LogUpGkrOracle { - CurrentRow(usize), - NextRow(usize), - PeriodicValue(Vec), -} From d98407ad8a33c096f0e753834de060967e275881 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:36:34 +0200 Subject: [PATCH 09/58] feat: add multi-threading support and simplify input sum-check --- sumcheck/src/verifier/mod.rs | 104 ++++++++++++----------------------- 1 file changed, 34 insertions(+), 70 deletions(-) diff --git a/sumcheck/src/verifier/mod.rs b/sumcheck/src/verifier/mod.rs index 3f2a57581..18b39fed2 100644 --- a/sumcheck/src/verifier/mod.rs +++ b/sumcheck/src/verifier/mod.rs @@ -126,7 +126,7 @@ where let round_poly_coefs = round_proof.round_poly_coefs.clone(); coin.reseed(H::hash_elements(&round_poly_coefs.0)); - let r = coin.draw().map_err(|_| Error::FailedToGenerateChallenge)?; + let r = coin.draw().map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; round_claim = round_proof.round_poly_coefs.evaluate_using_claim(&round_claim, &r); evaluation_point.push(r); @@ -149,10 +149,12 @@ pub fn verify_sum_check_intermediate_layers< gkr_eval_point: &[E], claim: (E, E), transcript: &mut C, -) -> Result, Error> { +) -> Result, SumCheckVerifierError> { // generate challenge to batch sum-checks transcript.reseed(H::hash_elements(&[claim.0, claim.1])); - let r_batch: E = transcript.draw().map_err(|_| Error::FailedToGenerateChallenge)?; + let r_batch: E = transcript + .draw() + .map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; // compute the claim for the batched sum-check let reduced_claim = claim.0 + claim.1 * r_batch; @@ -170,13 +172,15 @@ pub fn verify_sum_check_intermediate_layers< let eq = EqFunction::new(gkr_eval_point.to_vec()).evaluate(&openings_claim.eval_point); if (p0 * q1 + p1 * q0 + r_batch * q0 * q1) * eq != final_round_claim.claim { - return Err(Error::FinalEvaluationCheckFailed); + return Err(SumCheckVerifierError::FinalEvaluationCheckFailed); } Ok(openings_claim.clone()) } -/// Verifies the final sum-check proof of a GKR proof. +/// Verifies the final sum-check proof i.e., the one for the input layer, including the final check, +/// and returns a [`FinalOpeningClaim`] to the STARK verifier in order to verify the correctness of +/// the openings. pub fn verify_sum_check_input_layer< E: FieldElement, C: RandomCoin, @@ -188,95 +192,55 @@ pub fn verify_sum_check_input_layer< gkr_eval_point: &[E], claim: (E, E), transcript: &mut C, -) -> Result, Error> { - let FinalLayerProof { before_merge_proof, after_merge_proof } = proof; +) -> Result, SumCheckVerifierError> { + let FinalLayerProof { proof } = proof; // generate challenge to batch sum-checks transcript.reseed(H::hash_elements(&[claim.0, claim.1])); - let r_batch: E = transcript.draw().map_err(|_| Error::FailedToGenerateChallenge)?; + let r_batch: E = transcript + .draw() + .map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; // compute the claim for the batched sum-check let reduced_claim = claim.0 + claim.1 * r_batch; - // verify the first half of the sum-check proof i.e., `before_merge_proof` - let SumCheckRoundClaim { eval_point: rand_merge, claim } = - verify_rounds(reduced_claim, before_merge_proof, transcript)?; - - // verify the second half of the sum-check proof i.e., `after_merge_proof` - verify_sum_check_final( - claim, - after_merge_proof, - rand_merge, - r_batch, - log_up_randomness, - gkr_eval_point, - evaluator, - transcript, - ) -} - -/// Verifies the second sum-check proof for the input layer, including the final check, and returns -/// a [`FinalOpeningClaim`] to the STARK verifier in order to verify the correctness of -/// the openings. -#[allow(clippy::too_many_arguments)] -fn verify_sum_check_final< - E: FieldElement, - C: RandomCoin, - H: ElementHasher, ->( - claim: E, - after_merge_proof: &SumCheckProof, - rand_merge: Vec, - r_batch: E, - log_up_randomness: Vec, - gkr_eval_point: &[E], - evaluator: &impl LogUpGkrEvaluator, - transcript: &mut C, -) -> Result, Error> { - let SumCheckProof { openings_claim, round_proofs } = after_merge_proof; - - let SumCheckRoundClaim { - eval_point: evaluation_point, - claim: claimed_evaluation, - } = verify_rounds(claim, round_proofs, transcript)?; + // verify the sum-check proof + let SumCheckRoundClaim { eval_point, claim } = + verify_rounds(reduced_claim, &proof.round_proofs, transcript)?; - if openings_claim.eval_point != evaluation_point { - return Err(Error::WrongOpeningPoint); + // execute the final evaluation check + if proof.openings_claim.eval_point != eval_point { + return Err(SumCheckVerifierError::WrongOpeningPoint); } let mut numerators = vec![E::ZERO; evaluator.get_num_fractions()]; let mut denominators = vec![E::ZERO; evaluator.get_num_fractions()]; - evaluator.evaluate_query( - &openings_claim.openings, + &proof.openings_claim.openings, &log_up_randomness, &mut numerators, &mut denominators, ); - let lagrange_ker = EqFunction::new(gkr_eval_point.to_vec()); - let mut gkr_point = rand_merge.clone(); - - gkr_point.extend_from_slice(&openings_claim.eval_point.clone()); - let eq_eval = lagrange_ker.evaluate(&gkr_point); - let tensored_merge_randomness = EqFunction::ml_at(rand_merge.to_vec()).evaluations().to_vec(); - let expected_evaluation = evaluate_composition_poly( - &numerators, - &denominators, - eq_eval, - r_batch, - &tensored_merge_randomness, - ); + let mu = evaluator.get_num_fractions().trailing_zeros() - 1; + let (evaluation_point_mu, evaluation_point_nu) = gkr_eval_point.split_at(mu as usize); + + let eq_mu = EqFunction::new(evaluation_point_mu.to_vec()).evaluations(); + let eq_nu = EqFunction::new(evaluation_point_nu.to_vec()); - if expected_evaluation != claimed_evaluation { - Err(Error::FinalEvaluationCheckFailed) + let eq_nu_eval = eq_nu.evaluate(&proof.openings_claim.eval_point); + let expected_evaluation = + evaluate_composition_poly(&eq_mu, &numerators, &denominators, eq_nu_eval, r_batch); + + if expected_evaluation != claim { + Err(SumCheckVerifierError::FinalEvaluationCheckFailed) } else { - Ok(openings_claim.clone()) + Ok(proof.openings_claim.clone()) } } #[derive(Debug, thiserror::Error)] -pub enum Error { +pub enum SumCheckVerifierError { #[error("the final evaluation check of sum-check failed")] FinalEvaluationCheckFailed, #[error("failed to generate round challenge")] From 3feaff86a808b7babd30f6a8b15390a9801f1c40 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 19 Aug 2024 12:52:44 +0200 Subject: [PATCH 10/58] feat: add benchmarks and address feedback --- sumcheck/src/prover/plain.rs | 1 + sumcheck/src/verifier/mod.rs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sumcheck/src/prover/plain.rs b/sumcheck/src/prover/plain.rs index e0092cf10..e5e0ba773 100644 --- a/sumcheck/src/prover/plain.rs +++ b/sumcheck/src/prover/plain.rs @@ -4,6 +4,7 @@ // LICENSE file in the root directory of this source tree. use crypto::{ElementHasher, RandomCoin}; +use smallvec::smallvec; use math::FieldElement; #[cfg(feature = "concurrent")] pub use rayon::prelude::*; diff --git a/sumcheck/src/verifier/mod.rs b/sumcheck/src/verifier/mod.rs index 18b39fed2..a3717da5a 100644 --- a/sumcheck/src/verifier/mod.rs +++ b/sumcheck/src/verifier/mod.rs @@ -169,7 +169,7 @@ pub fn verify_sum_check_intermediate_layers< let q0 = openings_claim.openings[2]; let q1 = openings_claim.openings[3]; - let eq = EqFunction::new(gkr_eval_point.to_vec()).evaluate(&openings_claim.eval_point); + let eq = EqFunction::new(gkr_eval_point.into()).evaluate(&openings_claim.eval_point); if (p0 * q1 + p1 * q0 + r_batch * q0 * q1) * eq != final_round_claim.claim { return Err(SumCheckVerifierError::FinalEvaluationCheckFailed); @@ -225,8 +225,8 @@ pub fn verify_sum_check_input_layer< let mu = evaluator.get_num_fractions().trailing_zeros() - 1; let (evaluation_point_mu, evaluation_point_nu) = gkr_eval_point.split_at(mu as usize); - let eq_mu = EqFunction::new(evaluation_point_mu.to_vec()).evaluations(); - let eq_nu = EqFunction::new(evaluation_point_nu.to_vec()); + let eq_mu = EqFunction::new(evaluation_point_mu.into()).evaluations(); + let eq_nu = EqFunction::new(evaluation_point_nu.into()); let eq_nu_eval = eq_nu.evaluate(&proof.openings_claim.eval_point); let expected_evaluation = From 0475b4e55159b02cbb6b8444650c3ab54e4c4b81 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 19 Aug 2024 16:14:26 +0200 Subject: [PATCH 11/58] feat: address feedback and add benchmarks --- sumcheck/src/prover/plain.rs | 1 - sumcheck/src/verifier/mod.rs | 41 ++++++++++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/sumcheck/src/prover/plain.rs b/sumcheck/src/prover/plain.rs index e5e0ba773..e0092cf10 100644 --- a/sumcheck/src/prover/plain.rs +++ b/sumcheck/src/prover/plain.rs @@ -4,7 +4,6 @@ // LICENSE file in the root directory of this source tree. use crypto::{ElementHasher, RandomCoin}; -use smallvec::smallvec; use math::FieldElement; #[cfg(feature = "concurrent")] pub use rayon::prelude::*; diff --git a/sumcheck/src/verifier/mod.rs b/sumcheck/src/verifier/mod.rs index a3717da5a..1dd7412d4 100644 --- a/sumcheck/src/verifier/mod.rs +++ b/sumcheck/src/verifier/mod.rs @@ -142,13 +142,12 @@ where /// i.e., the circuit input layer. pub fn verify_sum_check_intermediate_layers< E: FieldElement, - C: RandomCoin, H: ElementHasher, >( proof: &SumCheckProof, gkr_eval_point: &[E], claim: (E, E), - transcript: &mut C, + transcript: &mut impl RandomCoin, ) -> Result, SumCheckVerifierError> { // generate challenge to batch sum-checks transcript.reseed(H::hash_elements(&[claim.0, claim.1])); @@ -171,7 +170,7 @@ pub fn verify_sum_check_intermediate_layers< let eq = EqFunction::new(gkr_eval_point.into()).evaluate(&openings_claim.eval_point); - if (p0 * q1 + p1 * q0 + r_batch * q0 * q1) * eq != final_round_claim.claim { + if comb_func(p0, p1, q0, q1, eq, r_batch) != final_round_claim.claim { return Err(SumCheckVerifierError::FinalEvaluationCheckFailed); } @@ -181,17 +180,13 @@ pub fn verify_sum_check_intermediate_layers< /// Verifies the final sum-check proof i.e., the one for the input layer, including the final check, /// and returns a [`FinalOpeningClaim`] to the STARK verifier in order to verify the correctness of /// the openings. -pub fn verify_sum_check_input_layer< - E: FieldElement, - C: RandomCoin, - H: ElementHasher, ->( +pub fn verify_sum_check_input_layer>( evaluator: &impl LogUpGkrEvaluator, proof: &FinalLayerProof, log_up_randomness: Vec, gkr_eval_point: &[E], claim: (E, E), - transcript: &mut C, + transcript: &mut impl RandomCoin, ) -> Result, SumCheckVerifierError> { let FinalLayerProof { proof } = proof; @@ -239,6 +234,34 @@ pub fn verify_sum_check_input_layer< } } +/// Verifies a round of the sum-check protocol without executing the final check. +fn verify_rounds( + claim: E, + round_proofs: &[RoundProof], + coin: &mut impl RandomCoin, +) -> Result, SumCheckVerifierError> +where + E: FieldElement, + H: ElementHasher, +{ + let mut round_claim = claim; + let mut evaluation_point = vec![]; + for round_proof in round_proofs { + let round_poly_coefs = round_proof.round_poly_coefs.clone(); + coin.reseed(H::hash_elements(&round_poly_coefs.0)); + + let r = coin.draw().map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; + + round_claim = round_proof.round_poly_coefs.evaluate_using_claim(&round_claim, &r); + evaluation_point.push(r); + } + + Ok(SumCheckRoundClaim { + eval_point: evaluation_point, + claim: round_claim, + }) +} + #[derive(Debug, thiserror::Error)] pub enum SumCheckVerifierError { #[error("the final evaluation check of sum-check failed")] From 9bfa4231a6670172e9c1ddacef0f6821dd8fba62 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 9 Aug 2024 19:18:11 +0200 Subject: [PATCH 12/58] feat: add GKR backend for LogUp-GKR --- air/src/air/context.rs | 5 +++ air/src/air/logup_gkr.rs | 49 +++++++++++++++++++++ air/src/air/tests.rs | 1 - examples/Cargo.toml | 1 + examples/src/fibonacci/fib2/air.rs | 54 +++++++++++++++++++++++ examples/src/fibonacci/fib8/air.rs | 54 +++++++++++++++++++++++ examples/src/fibonacci/fib_small/air.rs | 54 +++++++++++++++++++++++ examples/src/fibonacci/mulfib2/air.rs | 54 +++++++++++++++++++++++ examples/src/fibonacci/mulfib8/air.rs | 54 +++++++++++++++++++++++ examples/src/lamport/aggregate/air.rs | 57 +++++++++++++++++++++++++ examples/src/lamport/threshold/air.rs | 57 +++++++++++++++++++++++++ examples/src/merkle/air.rs | 57 +++++++++++++++++++++++++ examples/src/rescue/air.rs | 57 +++++++++++++++++++++++++ examples/src/rescue_raps/air.rs | 57 +++++++++++++++++++++++++ examples/src/vdf/exempt/air.rs | 57 +++++++++++++++++++++++++ examples/src/vdf/regular/air.rs | 57 +++++++++++++++++++++++++ prover/src/lib.rs | 20 ++++++++- verifier/src/lib.rs | 35 +++++++++++++++ winterfell/src/tests.rs | 5 ++- 19 files changed, 780 insertions(+), 5 deletions(-) diff --git a/air/src/air/context.rs b/air/src/air/context.rs index c36173ca3..767ab4e5a 100644 --- a/air/src/air/context.rs +++ b/air/src/air/context.rs @@ -264,6 +264,11 @@ impl AirContext { self.logup_gkr } + /// Returns true if the auxiliary trace segment contains a Lagrange kernel column + pub fn is_with_logup_gkr(&self) -> bool { + self.logup_gkr + } + /// Returns the total number of assertions defined for a computation, excluding the Lagrange /// kernel assertion, which is managed separately. /// diff --git a/air/src/air/logup_gkr.rs b/air/src/air/logup_gkr.rs index 0438064d9..f7bce7aad 100644 --- a/air/src/air/logup_gkr.rs +++ b/air/src/air/logup_gkr.rs @@ -200,3 +200,52 @@ pub enum LogUpGkrOracle { /// must be a power of 2. PeriodicValue(Vec), } + +impl LogUpGkrEvaluator for () { + type BaseField = BaseElement; + + type PublicInputs = (); + + fn get_oracles(&self) -> Vec> { + unimplemented!() + } + + fn get_num_rand_values(&self) -> usize { + unimplemented!() + } + + fn get_num_fractions(&self) -> usize { + unimplemented!() + } + + fn max_degree(&self) -> usize { + unimplemented!() + } + + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + where + E: FieldElement, + { + unimplemented!() + } + + fn evaluate_query( + &self, + _query: &[F], + _rand_values: &[E], + _numerator: &mut [E], + _denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + ExtensionOf, + { + unimplemented!() + } + + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + unimplemented!() + } +} diff --git a/air/src/air/tests.rs b/air/src/air/tests.rs index 5e9871ca5..2400cb883 100644 --- a/air/src/air/tests.rs +++ b/air/src/air/tests.rs @@ -225,7 +225,6 @@ impl MockAir { impl Air for MockAir { type BaseField = BaseElement; type PublicInputs = (); - //type LogUpGkrEvaluator = DummyLogUpGkrEval; fn new(trace_info: TraceInfo, _pub_inputs: (), _options: ProofOptions) -> Self { let num_assertions = trace_info.meta()[0] as usize; diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 2fda1d952..8c75f8ee3 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -26,6 +26,7 @@ default = ["std"] std = ["core-utils/std", "hex/std", "rand-utils", "winterfell/std"] [dependencies] +air = { version = "0.9", path = "../air", package = "winter-air", default-features = false } blake3 = { version = "1.5", default-features = false } core-utils = { version = "0.9", path = "../utils/core", package = "winter-utils", default-features = false } hex = { version = "0.4", optional = true } diff --git a/examples/src/fibonacci/fib2/air.rs b/examples/src/fibonacci/fib2/air.rs index 4019ddcae..92d904e7a 100644 --- a/examples/src/fibonacci/fib2/air.rs +++ b/examples/src/fibonacci/fib2/air.rs @@ -67,3 +67,57 @@ impl Air for FibAir { ] } } + +#[derive(Clone, Default)] +pub struct PlainLogUpGkrEval { + _field: std::marker::PhantomData, +} + +impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { + type BaseField = BaseElement; + + type PublicInputs = BaseElement; + + fn get_oracles(&self) -> Vec> { + unimplemented!() + } + + fn get_num_rand_values(&self) -> usize { + unimplemented!() + } + + fn get_num_fractions(&self) -> usize { + unimplemented!() + } + + fn max_degree(&self) -> usize { + unimplemented!() + } + + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + where + E: FieldElement, + { + unimplemented!() + } + + fn evaluate_query( + &self, + _query: &[F], + _rand_values: &[E], + _numerator: &mut [E], + _denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + winterfell::math::ExtensionOf, + { + unimplemented!() + } + + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + unimplemented!() + } +} diff --git a/examples/src/fibonacci/fib8/air.rs b/examples/src/fibonacci/fib8/air.rs index 17edc7970..34f47c26b 100644 --- a/examples/src/fibonacci/fib8/air.rs +++ b/examples/src/fibonacci/fib8/air.rs @@ -75,3 +75,57 @@ impl Air for Fib8Air { ] } } + +#[derive(Clone, Default)] +pub struct PlainLogUpGkrEval { + _field: std::marker::PhantomData, +} + +impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { + type BaseField = BaseElement; + + type PublicInputs = BaseElement; + + fn get_oracles(&self) -> Vec> { + unimplemented!() + } + + fn get_num_rand_values(&self) -> usize { + unimplemented!() + } + + fn get_num_fractions(&self) -> usize { + unimplemented!() + } + + fn max_degree(&self) -> usize { + unimplemented!() + } + + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + where + E: FieldElement, + { + unimplemented!() + } + + fn evaluate_query( + &self, + _query: &[F], + _rand_values: &[E], + _numerator: &mut [E], + _denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + winterfell::math::ExtensionOf, + { + unimplemented!() + } + + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + unimplemented!() + } +} diff --git a/examples/src/fibonacci/fib_small/air.rs b/examples/src/fibonacci/fib_small/air.rs index b48eb734b..d4e8e17ff 100644 --- a/examples/src/fibonacci/fib_small/air.rs +++ b/examples/src/fibonacci/fib_small/air.rs @@ -67,3 +67,57 @@ impl Air for FibSmall { ] } } + +#[derive(Clone, Default)] +pub struct PlainLogUpGkrEval { + _field: std::marker::PhantomData, +} + +impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { + type BaseField = BaseElement; + + type PublicInputs = BaseElement; + + fn get_oracles(&self) -> Vec> { + unimplemented!() + } + + fn get_num_rand_values(&self) -> usize { + unimplemented!() + } + + fn get_num_fractions(&self) -> usize { + unimplemented!() + } + + fn max_degree(&self) -> usize { + unimplemented!() + } + + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + where + E: FieldElement, + { + unimplemented!() + } + + fn evaluate_query( + &self, + _query: &[F], + _rand_values: &[E], + _numerator: &mut [E], + _denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + winterfell::math::ExtensionOf, + { + unimplemented!() + } + + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + unimplemented!() + } +} diff --git a/examples/src/fibonacci/mulfib2/air.rs b/examples/src/fibonacci/mulfib2/air.rs index 501adf6af..a37f32ac0 100644 --- a/examples/src/fibonacci/mulfib2/air.rs +++ b/examples/src/fibonacci/mulfib2/air.rs @@ -69,3 +69,57 @@ impl Air for MulFib2Air { ] } } + +#[derive(Clone, Default)] +pub struct PlainLogUpGkrEval { + _field: std::marker::PhantomData, +} + +impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { + type BaseField = BaseElement; + + type PublicInputs = BaseElement; + + fn get_oracles(&self) -> Vec> { + unimplemented!() + } + + fn get_num_rand_values(&self) -> usize { + unimplemented!() + } + + fn get_num_fractions(&self) -> usize { + unimplemented!() + } + + fn max_degree(&self) -> usize { + unimplemented!() + } + + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + where + E: FieldElement, + { + unimplemented!() + } + + fn evaluate_query( + &self, + _query: &[F], + _rand_values: &[E], + _numerator: &mut [E], + _denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + winterfell::math::ExtensionOf, + { + unimplemented!() + } + + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + unimplemented!() + } +} diff --git a/examples/src/fibonacci/mulfib8/air.rs b/examples/src/fibonacci/mulfib8/air.rs index c76f4f091..b0612bf49 100644 --- a/examples/src/fibonacci/mulfib8/air.rs +++ b/examples/src/fibonacci/mulfib8/air.rs @@ -90,3 +90,57 @@ impl Air for MulFib8Air { ] } } + +#[derive(Clone, Default)] +pub struct PlainLogUpGkrEval { + _field: std::marker::PhantomData, +} + +impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { + type BaseField = BaseElement; + + type PublicInputs = BaseElement; + + fn get_oracles(&self) -> Vec> { + unimplemented!() + } + + fn get_num_rand_values(&self) -> usize { + unimplemented!() + } + + fn get_num_fractions(&self) -> usize { + unimplemented!() + } + + fn max_degree(&self) -> usize { + unimplemented!() + } + + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + where + E: FieldElement, + { + unimplemented!() + } + + fn evaluate_query( + &self, + _query: &[F], + _rand_values: &[E], + _numerator: &mut [E], + _denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + winterfell::math::ExtensionOf, + { + unimplemented!() + } + + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + unimplemented!() + } +} diff --git a/examples/src/lamport/aggregate/air.rs b/examples/src/lamport/aggregate/air.rs index 57708fd74..699c93e51 100644 --- a/examples/src/lamport/aggregate/air.rs +++ b/examples/src/lamport/aggregate/air.rs @@ -3,6 +3,9 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. +use std::marker::PhantomData; + +use air::LogUpGkrEvaluator; use core_utils::flatten_slice_elements; use winterfell::{ math::{fields::f128::BaseElement, FieldElement, ToElements}, @@ -285,3 +288,57 @@ const HASH_CYCLE_MASK: [BaseElement; HASH_CYCLE_LEN] = [ BaseElement::ONE, BaseElement::ZERO, ]; + +#[derive(Clone, Default)] +pub struct PlainLogUpGkrEval { + _field: PhantomData, +} + +impl LogUpGkrEvaluator for PlainLogUpGkrEval { + type BaseField = BaseElement; + + type PublicInputs = PublicInputs; + + fn get_oracles(&self) -> Vec> { + unimplemented!() + } + + fn get_num_rand_values(&self) -> usize { + unimplemented!() + } + + fn get_num_fractions(&self) -> usize { + unimplemented!() + } + + fn max_degree(&self) -> usize { + unimplemented!() + } + + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + where + E: FieldElement, + { + unimplemented!() + } + + fn evaluate_query( + &self, + _query: &[F], + _rand_values: &[E], + _numerator: &mut [E], + _denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + winterfell::math::ExtensionOf, + { + unimplemented!() + } + + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + unimplemented!() + } +} diff --git a/examples/src/lamport/threshold/air.rs b/examples/src/lamport/threshold/air.rs index b68a2a24d..7f9f4e833 100644 --- a/examples/src/lamport/threshold/air.rs +++ b/examples/src/lamport/threshold/air.rs @@ -3,6 +3,9 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. +use std::marker::PhantomData; + +use air::LogUpGkrEvaluator; use winterfell::{ math::{fields::f128::BaseElement, FieldElement, StarkField, ToElements}, Air, AirContext, Assertion, EvaluationFrame, ProofOptions, TraceInfo, @@ -361,3 +364,57 @@ const HASH_CYCLE_MASK: [BaseElement; HASH_CYCLE_LEN] = [ BaseElement::ONE, BaseElement::ZERO, ]; + +#[derive(Clone, Default)] +pub struct PlainLogUpGkrEval { + _field: PhantomData, +} + +impl LogUpGkrEvaluator for PlainLogUpGkrEval { + type BaseField = BaseElement; + + type PublicInputs = PublicInputs; + + fn get_oracles(&self) -> Vec> { + unimplemented!() + } + + fn get_num_rand_values(&self) -> usize { + unimplemented!() + } + + fn get_num_fractions(&self) -> usize { + unimplemented!() + } + + fn max_degree(&self) -> usize { + unimplemented!() + } + + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + where + E: FieldElement, + { + unimplemented!() + } + + fn evaluate_query( + &self, + _query: &[F], + _rand_values: &[E], + _numerator: &mut [E], + _denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + winterfell::math::ExtensionOf, + { + unimplemented!() + } + + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + unimplemented!() + } +} diff --git a/examples/src/merkle/air.rs b/examples/src/merkle/air.rs index 5d38397ff..9ec9c5346 100644 --- a/examples/src/merkle/air.rs +++ b/examples/src/merkle/air.rs @@ -3,6 +3,9 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. +use std::marker::PhantomData; + +use air::LogUpGkrEvaluator; use winterfell::{ math::ToElements, Air, AirContext, Assertion, EvaluationFrame, ProofOptions, TraceInfo, TransitionConstraintDegree, @@ -132,3 +135,57 @@ const HASH_CYCLE_MASK: [BaseElement; HASH_CYCLE_LEN] = [ BaseElement::ONE, BaseElement::ZERO, ]; + +#[derive(Clone, Default)] +pub struct PlainLogUpGkrEval { + _field: PhantomData, +} + +impl LogUpGkrEvaluator for PlainLogUpGkrEval { + type BaseField = BaseElement; + + type PublicInputs = PublicInputs; + + fn get_oracles(&self) -> Vec> { + unimplemented!() + } + + fn get_num_rand_values(&self) -> usize { + unimplemented!() + } + + fn get_num_fractions(&self) -> usize { + unimplemented!() + } + + fn max_degree(&self) -> usize { + unimplemented!() + } + + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + where + E: FieldElement, + { + unimplemented!() + } + + fn evaluate_query( + &self, + _query: &[F], + _rand_values: &[E], + _numerator: &mut [E], + _denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + winterfell::math::ExtensionOf, + { + unimplemented!() + } + + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + unimplemented!() + } +} diff --git a/examples/src/rescue/air.rs b/examples/src/rescue/air.rs index 09bf9c450..58a3a50a1 100644 --- a/examples/src/rescue/air.rs +++ b/examples/src/rescue/air.rs @@ -3,6 +3,9 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. +use std::marker::PhantomData; + +use air::LogUpGkrEvaluator; use winterfell::{ math::ToElements, Air, AirContext, Assertion, EvaluationFrame, TraceInfo, TransitionConstraintDegree, @@ -137,3 +140,57 @@ fn enforce_hash_copy(result: &mut [E], current: &[E], next: &[E result.agg_constraint(2, flag, is_zero(next[2])); result.agg_constraint(3, flag, is_zero(next[3])); } + +#[derive(Clone, Default)] +pub struct PlainLogUpGkrEval { + _field: PhantomData, +} + +impl LogUpGkrEvaluator for PlainLogUpGkrEval { + type BaseField = BaseElement; + + type PublicInputs = PublicInputs; + + fn get_oracles(&self) -> Vec> { + unimplemented!() + } + + fn get_num_rand_values(&self) -> usize { + unimplemented!() + } + + fn get_num_fractions(&self) -> usize { + unimplemented!() + } + + fn max_degree(&self) -> usize { + unimplemented!() + } + + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + where + E: FieldElement, + { + unimplemented!() + } + + fn evaluate_query( + &self, + _query: &[F], + _rand_values: &[E], + _numerator: &mut [E], + _denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + winterfell::math::ExtensionOf, + { + unimplemented!() + } + + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + unimplemented!() + } +} diff --git a/examples/src/rescue_raps/air.rs b/examples/src/rescue_raps/air.rs index 694e189bc..0d1c26f25 100644 --- a/examples/src/rescue_raps/air.rs +++ b/examples/src/rescue_raps/air.rs @@ -3,6 +3,9 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. +use std::marker::PhantomData; + +use air::LogUpGkrEvaluator; use core_utils::flatten_slice_elements; use winterfell::{ math::ToElements, Air, AirContext, Assertion, AuxRandElements, EvaluationFrame, TraceInfo, @@ -268,3 +271,57 @@ fn enforce_hash_copy(result: &mut [E], current: &[E], next: &[E result.agg_constraint(2, flag, are_equal(current[2], next[2])); result.agg_constraint(3, flag, are_equal(current[3], next[3])); } + +#[derive(Clone, Default)] +pub struct PlainLogUpGkrEval { + _field: PhantomData, +} + +impl LogUpGkrEvaluator for PlainLogUpGkrEval { + type BaseField = BaseElement; + + type PublicInputs = PublicInputs; + + fn get_oracles(&self) -> Vec> { + unimplemented!() + } + + fn get_num_rand_values(&self) -> usize { + unimplemented!() + } + + fn get_num_fractions(&self) -> usize { + unimplemented!() + } + + fn max_degree(&self) -> usize { + unimplemented!() + } + + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + where + E: FieldElement, + { + unimplemented!() + } + + fn evaluate_query( + &self, + _query: &[F], + _rand_values: &[E], + _numerator: &mut [E], + _denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + winterfell::math::ExtensionOf, + { + unimplemented!() + } + + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + unimplemented!() + } +} diff --git a/examples/src/vdf/exempt/air.rs b/examples/src/vdf/exempt/air.rs index 015778459..b938bb99f 100644 --- a/examples/src/vdf/exempt/air.rs +++ b/examples/src/vdf/exempt/air.rs @@ -3,6 +3,9 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. +use std::marker::PhantomData; + +use air::LogUpGkrEvaluator; use winterfell::{ math::ToElements, Air, AirContext, Assertion, EvaluationFrame, TraceInfo, TransitionConstraintDegree, @@ -78,3 +81,57 @@ impl Air for VdfAir { &self.context } } + +#[derive(Clone, Default)] +pub struct PlainLogUpGkrEval { + _field: PhantomData, +} + +impl LogUpGkrEvaluator for PlainLogUpGkrEval { + type BaseField = BaseElement; + + type PublicInputs = VdfInputs; + + fn get_oracles(&self) -> Vec> { + unimplemented!() + } + + fn get_num_rand_values(&self) -> usize { + unimplemented!() + } + + fn get_num_fractions(&self) -> usize { + unimplemented!() + } + + fn max_degree(&self) -> usize { + unimplemented!() + } + + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + where + E: FieldElement, + { + unimplemented!() + } + + fn evaluate_query( + &self, + _query: &[F], + _rand_values: &[E], + _numerator: &mut [E], + _denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + winterfell::math::ExtensionOf, + { + unimplemented!() + } + + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + unimplemented!() + } +} diff --git a/examples/src/vdf/regular/air.rs b/examples/src/vdf/regular/air.rs index bec2ccb3c..ee095b282 100644 --- a/examples/src/vdf/regular/air.rs +++ b/examples/src/vdf/regular/air.rs @@ -3,6 +3,9 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. +use std::marker::PhantomData; + +use air::LogUpGkrEvaluator; use winterfell::{ math::ToElements, Air, AirContext, Assertion, EvaluationFrame, TraceInfo, TransitionConstraintDegree, @@ -69,3 +72,57 @@ impl Air for VdfAir { &self.context } } + +#[derive(Clone, Default)] +pub struct PlainLogUpGkrEval { + _field: PhantomData, +} + +impl LogUpGkrEvaluator for PlainLogUpGkrEval { + type BaseField = BaseElement; + + type PublicInputs = VdfInputs; + + fn get_oracles(&self) -> Vec> { + unimplemented!() + } + + fn get_num_rand_values(&self) -> usize { + unimplemented!() + } + + fn get_num_fractions(&self) -> usize { + unimplemented!() + } + + fn max_degree(&self) -> usize { + unimplemented!() + } + + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + where + E: FieldElement, + { + unimplemented!() + } + + fn evaluate_query( + &self, + _query: &[F], + _rand_values: &[E], + _numerator: &mut [E], + _denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + winterfell::math::ExtensionOf, + { + unimplemented!() + } + + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + unimplemented!() + } +} diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 703f19d8c..0b0b281f2 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -209,7 +209,23 @@ pub trait Prover { where E: FieldElement, { - unimplemented!("`Prover::build_aux_trace` needs to be implemented when the trace has an auxiliary segment.") + let mut aux_trace = self.build_aux_trace(main_trace, aux_rand_elements); + + if let Some(lagrange_randomness) = aux_rand_elements.lagrange() { + let evaluator = air.get_logup_gkr_evaluator::(); + let lagrange_col = build_lagrange_column(lagrange_randomness); + let s_col = build_s_column( + main_trace, + aux_rand_elements.gkr_data().expect("should not be empty"), + &evaluator, + &lagrange_col, + ); + + aux_trace.merge_column(s_col); + aux_trace.merge_column(lagrange_col); + } + + aux_trace } /// Returns a STARK proof attesting to a correct execution of a computation defined by the @@ -357,7 +373,7 @@ pub trait Prover { // This checks validity of both, assertions and state transitions. We do this in debug // mode only because this is a very expensive operation. #[cfg(debug_assertions)] - trace.validate(&air, aux_trace_with_metadata.as_ref()); + //trace.validate(&air, aux_trace_with_metadata.as_ref()); // Destructure `aux_trace_with_metadata`. let (aux_trace, aux_rand_elements, gkr_proof) = match aux_trace_with_metadata { diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs index cabbda6c7..dc404361c 100644 --- a/verifier/src/lib.rs +++ b/verifier/src/lib.rs @@ -350,6 +350,41 @@ where .map_err(VerifierError::FriVerificationFailed) } +fn verify_gkr>( + gkr_proof: GkrCircuitProof, + evaluator: &impl LogUpGkrEvaluator, + public_coin: &mut impl RandomCoin, +) -> Result, GkrVerifierError> { + let claim = E::ZERO; + let num_logup_random_values = evaluator.get_num_rand_values(); + let mut logup_randomness: Vec = Vec::with_capacity(num_logup_random_values); + + for _ in 0..num_logup_random_values { + logup_randomness.push(public_coin.draw().expect("failed to generate randomness")); + } + + let final_eval_claim = + verify_logup_gkr(claim, evaluator, &gkr_proof, logup_randomness, public_coin)?; + + let FinalOpeningClaim { eval_point, openings } = final_eval_claim; + + public_coin.reseed(H::hash_elements(&openings)); + + let mut batching_randomness = Vec::with_capacity(openings.len() - 1); + for _ in 0..openings.len() - 1 { + batching_randomness.push(public_coin.draw().expect("failed to generate randomness")) + } + + let gkr_rand_elements = GkrData::new( + LagrangeKernelRandElements::new(eval_point), + batching_randomness, + openings, + evaluator.get_oracles(), + ); + + Ok(gkr_rand_elements) +} + // ACCEPTABLE OPTIONS // ================================================================================================ // Specifies either the minimal, conjectured or proven, security level or a set of diff --git a/winterfell/src/tests.rs b/winterfell/src/tests.rs index 858f35574..1db0f5f51 100644 --- a/winterfell/src/tests.rs +++ b/winterfell/src/tests.rs @@ -26,14 +26,14 @@ fn test_logup_gkr() { let trace = LogUpGkrSimple::new(2_usize.pow(7), aux_trace_width); let prover = LogUpGkrSimpleProver::new(aux_trace_width); - let proof = prover.prove(trace).unwrap(); + let _proof = prover.prove(trace).unwrap(); verify::< LogUpGkrSimpleAir, Blake3_256, DefaultRandomCoin>, MerkleTree>, - >(proof, (), &AcceptableOptions::MinConjecturedSecurity(0)) + >(_proof, (), &AcceptableOptions::MinConjecturedSecurity(0)) .unwrap() } @@ -115,6 +115,7 @@ struct LogUpGkrSimpleAir { impl Air for LogUpGkrSimpleAir { type BaseField = BaseElement; type PublicInputs = (); + type LogUpGkrEvaluator = PlainLogUpGkrEval; fn new(trace_info: TraceInfo, _pub_inputs: Self::PublicInputs, options: ProofOptions) -> Self { Self { From 3576e3f33ae8f8832f10422f41db5bff604ff40e Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:52:09 +0200 Subject: [PATCH 13/58] chore: remove old way of handling Lagrange kernel --- prover/src/lib.rs | 2 +- winterfell/src/tests.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 0b0b281f2..fe58e85f3 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -373,7 +373,7 @@ pub trait Prover { // This checks validity of both, assertions and state transitions. We do this in debug // mode only because this is a very expensive operation. #[cfg(debug_assertions)] - //trace.validate(&air, aux_trace_with_metadata.as_ref()); + trace.validate(&air, aux_trace_with_metadata.as_ref()); // Destructure `aux_trace_with_metadata`. let (aux_trace, aux_rand_elements, gkr_proof) = match aux_trace_with_metadata { diff --git a/winterfell/src/tests.rs b/winterfell/src/tests.rs index 1db0f5f51..858f35574 100644 --- a/winterfell/src/tests.rs +++ b/winterfell/src/tests.rs @@ -26,14 +26,14 @@ fn test_logup_gkr() { let trace = LogUpGkrSimple::new(2_usize.pow(7), aux_trace_width); let prover = LogUpGkrSimpleProver::new(aux_trace_width); - let _proof = prover.prove(trace).unwrap(); + let proof = prover.prove(trace).unwrap(); verify::< LogUpGkrSimpleAir, Blake3_256, DefaultRandomCoin>, MerkleTree>, - >(_proof, (), &AcceptableOptions::MinConjecturedSecurity(0)) + >(proof, (), &AcceptableOptions::MinConjecturedSecurity(0)) .unwrap() } @@ -115,7 +115,6 @@ struct LogUpGkrSimpleAir { impl Air for LogUpGkrSimpleAir { type BaseField = BaseElement; type PublicInputs = (); - type LogUpGkrEvaluator = PlainLogUpGkrEval; fn new(trace_info: TraceInfo, _pub_inputs: Self::PublicInputs, options: ProofOptions) -> Self { Self { From d6f0bd4cb31b2aec66206922c484ec2c7c3b3110 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 14 Aug 2024 12:30:26 +0200 Subject: [PATCH 14/58] feat: simplify sum-check for input layer --- sumcheck/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/sumcheck/Cargo.toml b/sumcheck/Cargo.toml index 97dc79f3a..3dfae86d8 100644 --- a/sumcheck/Cargo.toml +++ b/sumcheck/Cargo.toml @@ -43,6 +43,7 @@ utils = { version = "0.9", path = "../utils/core", package = "winter-utils", def rayon = { version = "1.8", optional = true } smallvec = { version = "1.13", default-features = false } thiserror = { version = "1.0", git = "https://github.com/bitwalker/thiserror", branch = "no-std", default-features = false } +libc-print = "0.1.23" [dev-dependencies] criterion = "0.5" From 5dfed3d0603e50284bcd9e016d3cbad332545f3d Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:21:59 +0200 Subject: [PATCH 15/58] feat: add mult-threading to sum-checks and multi-linears --- sumcheck/Cargo.toml | 6 +++--- sumcheck/src/multilinear.rs | 4 +++- sumcheck/src/prover/high_degree.rs | 3 +++ sumcheck/src/prover/plain.rs | 3 +++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/sumcheck/Cargo.toml b/sumcheck/Cargo.toml index 3dfae86d8..2fafcb8ac 100644 --- a/sumcheck/Cargo.toml +++ b/sumcheck/Cargo.toml @@ -31,8 +31,8 @@ harness = false required-features = ["concurrent"] [features] -concurrent = ["utils/concurrent", "dep:rayon", "std"] -default = ["std"] +concurrent = ["utils/concurrent", "std"] +default = ["std", ] std = ["utils/std"] [dependencies] @@ -40,7 +40,7 @@ air = { version = "0.9", path = "../air", package = "winter-air", default-featur crypto = { version = "0.9", path = "../crypto", package = "winter-crypto", default-features = false } math = { version = "0.9", path = "../math", package = "winter-math", default-features = false } utils = { version = "0.9", path = "../utils/core", package = "winter-utils", default-features = false } -rayon = { version = "1.8", optional = true } +rayon = { version = "1.8"} smallvec = { version = "1.13", default-features = false } thiserror = { version = "1.0", git = "https://github.com/bitwalker/thiserror", branch = "no-std", default-features = false } libc-print = "0.1.23" diff --git a/sumcheck/src/multilinear.rs b/sumcheck/src/multilinear.rs index 110ef1fa7..73fc173c6 100644 --- a/sumcheck/src/multilinear.rs +++ b/sumcheck/src/multilinear.rs @@ -10,7 +10,9 @@ use math::FieldElement; #[cfg(feature = "concurrent")] pub use rayon::prelude::*; use smallvec::SmallVec; -use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; +use utils::{ + ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, +}; // MULTI-LINEAR POLYNOMIAL // ================================================================================================ diff --git a/sumcheck/src/prover/high_degree.rs b/sumcheck/src/prover/high_degree.rs index 691195925..a7d87d6b9 100644 --- a/sumcheck/src/prover/high_degree.rs +++ b/sumcheck/src/prover/high_degree.rs @@ -17,6 +17,9 @@ use crate::{ MultiLinearPoly, RoundProof, SumCheckProof, SumCheckRoundClaim, }; +#[cfg(feature = "concurrent")] +pub use rayon::prelude::*; + /// A sum-check prover for the input layer which can accommodate non-linear expressions in /// the numerators of the LogUp relation. /// diff --git a/sumcheck/src/prover/plain.rs b/sumcheck/src/prover/plain.rs index e0092cf10..25845a441 100644 --- a/sumcheck/src/prover/plain.rs +++ b/sumcheck/src/prover/plain.rs @@ -15,6 +15,9 @@ use crate::{ SumCheckProof, }; +#[cfg(feature = "concurrent")] +pub use rayon::prelude::*; + /// Sum-check prover for non-linear multivariate polynomial of the simple LogUp-GKR. /// /// More specifically, the following function implements the logic of the sum-check prover as From b62b31d8f2891dddbf18e1419565465a8b336c21 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:19:59 +0200 Subject: [PATCH 16/58] chore: unify serial and concurrent EQ impls --- sumcheck/Cargo.toml | 1 - sumcheck/src/multilinear.rs | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/sumcheck/Cargo.toml b/sumcheck/Cargo.toml index 2fafcb8ac..8ec8e7470 100644 --- a/sumcheck/Cargo.toml +++ b/sumcheck/Cargo.toml @@ -43,7 +43,6 @@ utils = { version = "0.9", path = "../utils/core", package = "winter-utils", def rayon = { version = "1.8"} smallvec = { version = "1.13", default-features = false } thiserror = { version = "1.0", git = "https://github.com/bitwalker/thiserror", branch = "no-std", default-features = false } -libc-print = "0.1.23" [dev-dependencies] criterion = "0.5" diff --git a/sumcheck/src/multilinear.rs b/sumcheck/src/multilinear.rs index 73fc173c6..110ef1fa7 100644 --- a/sumcheck/src/multilinear.rs +++ b/sumcheck/src/multilinear.rs @@ -10,9 +10,7 @@ use math::FieldElement; #[cfg(feature = "concurrent")] pub use rayon::prelude::*; use smallvec::SmallVec; -use utils::{ - ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, -}; +use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; // MULTI-LINEAR POLYNOMIAL // ================================================================================================ From 6a1cc1cafe0265af1b228d0a659d5b62d4db92e1 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:23:10 +0200 Subject: [PATCH 17/58] fix: formatting --- sumcheck/src/prover/high_degree.rs | 3 --- sumcheck/src/prover/plain.rs | 3 --- 2 files changed, 6 deletions(-) diff --git a/sumcheck/src/prover/high_degree.rs b/sumcheck/src/prover/high_degree.rs index a7d87d6b9..691195925 100644 --- a/sumcheck/src/prover/high_degree.rs +++ b/sumcheck/src/prover/high_degree.rs @@ -17,9 +17,6 @@ use crate::{ MultiLinearPoly, RoundProof, SumCheckProof, SumCheckRoundClaim, }; -#[cfg(feature = "concurrent")] -pub use rayon::prelude::*; - /// A sum-check prover for the input layer which can accommodate non-linear expressions in /// the numerators of the LogUp relation. /// diff --git a/sumcheck/src/prover/plain.rs b/sumcheck/src/prover/plain.rs index 25845a441..e0092cf10 100644 --- a/sumcheck/src/prover/plain.rs +++ b/sumcheck/src/prover/plain.rs @@ -15,9 +15,6 @@ use crate::{ SumCheckProof, }; -#[cfg(feature = "concurrent")] -pub use rayon::prelude::*; - /// Sum-check prover for non-linear multivariate polynomial of the simple LogUp-GKR. /// /// More specifically, the following function implements the logic of the sum-check prover as From 70ea9d63c649f455ff5c9ef3a283f66fb920a50f Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 14 Aug 2024 23:17:21 +0200 Subject: [PATCH 18/58] fix: concurrent feature flag --- sumcheck/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sumcheck/Cargo.toml b/sumcheck/Cargo.toml index 8ec8e7470..40c6f8091 100644 --- a/sumcheck/Cargo.toml +++ b/sumcheck/Cargo.toml @@ -31,8 +31,8 @@ harness = false required-features = ["concurrent"] [features] -concurrent = ["utils/concurrent", "std"] -default = ["std", ] +concurrent = ["utils/concurrent", "dep:rayon", "std"] +default = ["std"] std = ["utils/std"] [dependencies] @@ -40,7 +40,7 @@ air = { version = "0.9", path = "../air", package = "winter-air", default-featur crypto = { version = "0.9", path = "../crypto", package = "winter-crypto", default-features = false } math = { version = "0.9", path = "../math", package = "winter-math", default-features = false } utils = { version = "0.9", path = "../utils/core", package = "winter-utils", default-features = false } -rayon = { version = "1.8"} +rayon = { version = "1.8", optional = true} smallvec = { version = "1.13", default-features = false } thiserror = { version = "1.0", git = "https://github.com/bitwalker/thiserror", branch = "no-std", default-features = false } From d49cdbb45f9426323b16c8d8507cb3d8f304652a Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 19 Aug 2024 11:32:06 +0200 Subject: [PATCH 19/58] feat: make query a mut ref in build_query --- air/src/air/logup_gkr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/air/src/air/logup_gkr.rs b/air/src/air/logup_gkr.rs index f7bce7aad..27d316642 100644 --- a/air/src/air/logup_gkr.rs +++ b/air/src/air/logup_gkr.rs @@ -222,7 +222,7 @@ impl LogUpGkrEvaluator for () { unimplemented!() } - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) where E: FieldElement, { From 5f5b18958da849205e6cec5851806b76ca4e0c08 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 20 Aug 2024 13:07:15 +0200 Subject: [PATCH 20/58] chore: update var names and simplify some structs --- examples/src/fibonacci/fib2/air.rs | 2 +- examples/src/fibonacci/fib8/air.rs | 2 +- examples/src/fibonacci/fib_small/air.rs | 2 +- examples/src/fibonacci/mulfib2/air.rs | 2 +- examples/src/fibonacci/mulfib8/air.rs | 2 +- examples/src/lamport/aggregate/air.rs | 2 +- examples/src/lamport/threshold/air.rs | 2 +- examples/src/merkle/air.rs | 2 +- examples/src/rescue/air.rs | 2 +- examples/src/rescue_raps/air.rs | 2 +- examples/src/vdf/exempt/air.rs | 2 +- examples/src/vdf/regular/air.rs | 2 +- prover/src/logup_gkr/prover.rs | 4 ++-- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/src/fibonacci/fib2/air.rs b/examples/src/fibonacci/fib2/air.rs index 92d904e7a..e291060d6 100644 --- a/examples/src/fibonacci/fib2/air.rs +++ b/examples/src/fibonacci/fib2/air.rs @@ -94,7 +94,7 @@ impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { unimplemented!() } - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) where E: FieldElement, { diff --git a/examples/src/fibonacci/fib8/air.rs b/examples/src/fibonacci/fib8/air.rs index 34f47c26b..ebec6f80e 100644 --- a/examples/src/fibonacci/fib8/air.rs +++ b/examples/src/fibonacci/fib8/air.rs @@ -102,7 +102,7 @@ impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { unimplemented!() } - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) where E: FieldElement, { diff --git a/examples/src/fibonacci/fib_small/air.rs b/examples/src/fibonacci/fib_small/air.rs index d4e8e17ff..b5d301b3d 100644 --- a/examples/src/fibonacci/fib_small/air.rs +++ b/examples/src/fibonacci/fib_small/air.rs @@ -94,7 +94,7 @@ impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { unimplemented!() } - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) where E: FieldElement, { diff --git a/examples/src/fibonacci/mulfib2/air.rs b/examples/src/fibonacci/mulfib2/air.rs index a37f32ac0..1fc6849e3 100644 --- a/examples/src/fibonacci/mulfib2/air.rs +++ b/examples/src/fibonacci/mulfib2/air.rs @@ -96,7 +96,7 @@ impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { unimplemented!() } - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) where E: FieldElement, { diff --git a/examples/src/fibonacci/mulfib8/air.rs b/examples/src/fibonacci/mulfib8/air.rs index b0612bf49..fdb1e74e6 100644 --- a/examples/src/fibonacci/mulfib8/air.rs +++ b/examples/src/fibonacci/mulfib8/air.rs @@ -117,7 +117,7 @@ impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { unimplemented!() } - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) where E: FieldElement, { diff --git a/examples/src/lamport/aggregate/air.rs b/examples/src/lamport/aggregate/air.rs index 699c93e51..a468d21ee 100644 --- a/examples/src/lamport/aggregate/air.rs +++ b/examples/src/lamport/aggregate/air.rs @@ -315,7 +315,7 @@ impl LogUpGkrEvaluator for PlainLogUpGkrEval { unimplemented!() } - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) where E: FieldElement, { diff --git a/examples/src/lamport/threshold/air.rs b/examples/src/lamport/threshold/air.rs index 7f9f4e833..54470e246 100644 --- a/examples/src/lamport/threshold/air.rs +++ b/examples/src/lamport/threshold/air.rs @@ -391,7 +391,7 @@ impl LogUpGkrEvaluator for PlainLogUpGkrEval { unimplemented!() } - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) where E: FieldElement, { diff --git a/examples/src/merkle/air.rs b/examples/src/merkle/air.rs index 9ec9c5346..5ee9ceb1b 100644 --- a/examples/src/merkle/air.rs +++ b/examples/src/merkle/air.rs @@ -162,7 +162,7 @@ impl LogUpGkrEvaluator for PlainLogUpGkrEval { unimplemented!() } - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) where E: FieldElement, { diff --git a/examples/src/rescue/air.rs b/examples/src/rescue/air.rs index 58a3a50a1..f10714572 100644 --- a/examples/src/rescue/air.rs +++ b/examples/src/rescue/air.rs @@ -167,7 +167,7 @@ impl LogUpGkrEvaluator for PlainLogUpGkrEval { unimplemented!() } - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) where E: FieldElement, { diff --git a/examples/src/rescue_raps/air.rs b/examples/src/rescue_raps/air.rs index 0d1c26f25..6b197e3f8 100644 --- a/examples/src/rescue_raps/air.rs +++ b/examples/src/rescue_raps/air.rs @@ -298,7 +298,7 @@ impl LogUpGkrEvaluator for PlainLogUpGkrEval { unimplemented!() } - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) where E: FieldElement, { diff --git a/examples/src/vdf/exempt/air.rs b/examples/src/vdf/exempt/air.rs index b938bb99f..13480d54a 100644 --- a/examples/src/vdf/exempt/air.rs +++ b/examples/src/vdf/exempt/air.rs @@ -108,7 +108,7 @@ impl LogUpGkrEvaluator for PlainLogUpGkrEval { unimplemented!() } - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) where E: FieldElement, { diff --git a/examples/src/vdf/regular/air.rs b/examples/src/vdf/regular/air.rs index ee095b282..a65868450 100644 --- a/examples/src/vdf/regular/air.rs +++ b/examples/src/vdf/regular/air.rs @@ -99,7 +99,7 @@ impl LogUpGkrEvaluator for PlainLogUpGkrEval { unimplemented!() } - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E]) -> Vec + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) where E: FieldElement, { diff --git a/prover/src/logup_gkr/prover.rs b/prover/src/logup_gkr/prover.rs index 9fc8fe175..0a6d90f49 100644 --- a/prover/src/logup_gkr/prover.rs +++ b/prover/src/logup_gkr/prover.rs @@ -75,11 +75,11 @@ pub fn prove_gkr( let (before_final_layer_proofs, gkr_claim) = prove_intermediate_layers(circuit, public_coin)?; // build the MLEs of the relevant main trace columns - let main_trace_mls = + let mut main_trace_mls = build_mls_from_main_trace_segment(evaluator.get_oracles(), main_trace.main_segment())?; let final_layer_proof = - prove_input_layer(evaluator, logup_randomness, main_trace_mls, gkr_claim, public_coin)?; + prove_input_layer(evaluator, logup_randomness, &mut main_trace_mls, gkr_claim, public_coin)?; Ok(GkrCircuitProof { circuit_outputs: CircuitOutput { numerators, denominators }, From 49f02a7bd4c22f755404d75cc3851bf23513bff8 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 20 Aug 2024 19:21:48 +0200 Subject: [PATCH 21/58] chore: rebase --- prover/src/logup_gkr/prover.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prover/src/logup_gkr/prover.rs b/prover/src/logup_gkr/prover.rs index 0a6d90f49..eac7c3470 100644 --- a/prover/src/logup_gkr/prover.rs +++ b/prover/src/logup_gkr/prover.rs @@ -75,7 +75,7 @@ pub fn prove_gkr( let (before_final_layer_proofs, gkr_claim) = prove_intermediate_layers(circuit, public_coin)?; // build the MLEs of the relevant main trace columns - let mut main_trace_mls = + let main_trace_mls = build_mls_from_main_trace_segment(evaluator.get_oracles(), main_trace.main_segment())?; let final_layer_proof = From 64c5fde8ea0f43ac05b0e2332db5d9a23e435226 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 21 Aug 2024 11:49:25 +0200 Subject: [PATCH 22/58] chore: fix rebase against facebook:logup-gkr --- sumcheck/src/verifier/mod.rs | 124 ----------------------------------- 1 file changed, 124 deletions(-) diff --git a/sumcheck/src/verifier/mod.rs b/sumcheck/src/verifier/mod.rs index 1dd7412d4..887598cc8 100644 --- a/sumcheck/src/verifier/mod.rs +++ b/sumcheck/src/verifier/mod.rs @@ -138,130 +138,6 @@ where }) } -/// Verifies sum-check proofs, as part of the GKR proof, for all GKR layers except for the last one -/// i.e., the circuit input layer. -pub fn verify_sum_check_intermediate_layers< - E: FieldElement, - H: ElementHasher, ->( - proof: &SumCheckProof, - gkr_eval_point: &[E], - claim: (E, E), - transcript: &mut impl RandomCoin, -) -> Result, SumCheckVerifierError> { - // generate challenge to batch sum-checks - transcript.reseed(H::hash_elements(&[claim.0, claim.1])); - let r_batch: E = transcript - .draw() - .map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; - - // compute the claim for the batched sum-check - let reduced_claim = claim.0 + claim.1 * r_batch; - - let SumCheckProof { openings_claim, round_proofs } = proof; - - let final_round_claim = verify_rounds(reduced_claim, round_proofs, transcript)?; - assert_eq!(openings_claim.eval_point, final_round_claim.eval_point); - - let p0 = openings_claim.openings[0]; - let p1 = openings_claim.openings[1]; - let q0 = openings_claim.openings[2]; - let q1 = openings_claim.openings[3]; - - let eq = EqFunction::new(gkr_eval_point.into()).evaluate(&openings_claim.eval_point); - - if comb_func(p0, p1, q0, q1, eq, r_batch) != final_round_claim.claim { - return Err(SumCheckVerifierError::FinalEvaluationCheckFailed); - } - - Ok(openings_claim.clone()) -} - -/// Verifies the final sum-check proof i.e., the one for the input layer, including the final check, -/// and returns a [`FinalOpeningClaim`] to the STARK verifier in order to verify the correctness of -/// the openings. -pub fn verify_sum_check_input_layer>( - evaluator: &impl LogUpGkrEvaluator, - proof: &FinalLayerProof, - log_up_randomness: Vec, - gkr_eval_point: &[E], - claim: (E, E), - transcript: &mut impl RandomCoin, -) -> Result, SumCheckVerifierError> { - let FinalLayerProof { proof } = proof; - - // generate challenge to batch sum-checks - transcript.reseed(H::hash_elements(&[claim.0, claim.1])); - let r_batch: E = transcript - .draw() - .map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; - - // compute the claim for the batched sum-check - let reduced_claim = claim.0 + claim.1 * r_batch; - - // verify the sum-check proof - let SumCheckRoundClaim { eval_point, claim } = - verify_rounds(reduced_claim, &proof.round_proofs, transcript)?; - - // execute the final evaluation check - if proof.openings_claim.eval_point != eval_point { - return Err(SumCheckVerifierError::WrongOpeningPoint); - } - - let mut numerators = vec![E::ZERO; evaluator.get_num_fractions()]; - let mut denominators = vec![E::ZERO; evaluator.get_num_fractions()]; - evaluator.evaluate_query( - &proof.openings_claim.openings, - &log_up_randomness, - &mut numerators, - &mut denominators, - ); - - let mu = evaluator.get_num_fractions().trailing_zeros() - 1; - let (evaluation_point_mu, evaluation_point_nu) = gkr_eval_point.split_at(mu as usize); - - let eq_mu = EqFunction::new(evaluation_point_mu.into()).evaluations(); - let eq_nu = EqFunction::new(evaluation_point_nu.into()); - - let eq_nu_eval = eq_nu.evaluate(&proof.openings_claim.eval_point); - let expected_evaluation = - evaluate_composition_poly(&eq_mu, &numerators, &denominators, eq_nu_eval, r_batch); - - if expected_evaluation != claim { - Err(SumCheckVerifierError::FinalEvaluationCheckFailed) - } else { - Ok(proof.openings_claim.clone()) - } -} - -/// Verifies a round of the sum-check protocol without executing the final check. -fn verify_rounds( - claim: E, - round_proofs: &[RoundProof], - coin: &mut impl RandomCoin, -) -> Result, SumCheckVerifierError> -where - E: FieldElement, - H: ElementHasher, -{ - let mut round_claim = claim; - let mut evaluation_point = vec![]; - for round_proof in round_proofs { - let round_poly_coefs = round_proof.round_poly_coefs.clone(); - coin.reseed(H::hash_elements(&round_poly_coefs.0)); - - let r = coin.draw().map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; - - round_claim = round_proof.round_poly_coefs.evaluate_using_claim(&round_claim, &r); - evaluation_point.push(r); - } - - Ok(SumCheckRoundClaim { - eval_point: evaluation_point, - claim: round_claim, - }) -} - #[derive(Debug, thiserror::Error)] pub enum SumCheckVerifierError { #[error("the final evaluation check of sum-check failed")] From 843b90dedd92410fd3a19b114519e6dc86670605 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:16:19 +0200 Subject: [PATCH 23/58] chore: address feedback --- air/src/air/logup_gkr.rs | 30 ++++++++++++++----------- examples/src/fibonacci/fib2/air.rs | 2 +- examples/src/fibonacci/fib8/air.rs | 2 +- examples/src/fibonacci/fib_small/air.rs | 2 +- examples/src/fibonacci/mulfib2/air.rs | 2 +- examples/src/fibonacci/mulfib8/air.rs | 2 +- examples/src/lamport/aggregate/air.rs | 2 +- examples/src/lamport/threshold/air.rs | 2 +- examples/src/merkle/air.rs | 2 +- examples/src/rescue/air.rs | 2 +- examples/src/rescue_raps/air.rs | 2 +- examples/src/vdf/exempt/air.rs | 2 +- examples/src/vdf/regular/air.rs | 2 +- verifier/src/lib.rs | 2 +- 14 files changed, 30 insertions(+), 26 deletions(-) diff --git a/air/src/air/logup_gkr.rs b/air/src/air/logup_gkr.rs index 27d316642..3b7e6d1f5 100644 --- a/air/src/air/logup_gkr.rs +++ b/air/src/air/logup_gkr.rs @@ -206,27 +206,27 @@ impl LogUpGkrEvaluator for () { type PublicInputs = (); - fn get_oracles(&self) -> Vec> { - unimplemented!() + fn get_oracles(&self) -> &[LogUpGkrOracle] { + panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") } fn get_num_rand_values(&self) -> usize { - unimplemented!() + panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") } fn get_num_fractions(&self) -> usize { - unimplemented!() + panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") } fn max_degree(&self) -> usize { - unimplemented!() + panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") } fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) where E: FieldElement, { - unimplemented!() + panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") } fn evaluate_query( @@ -239,13 +239,17 @@ impl LogUpGkrEvaluator for () { F: FieldElement, E: FieldElement + ExtensionOf, { - unimplemented!() + panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") } +} - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - unimplemented!() - } +#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] +pub enum LogUpGkrOracle { + // a column with a given index in the main trace segment + CurrentRow(usize), + // a column with a given index in the main trace segment but shifted upwards + NextRow(usize), + // a virtual periodic column defined by its values in a given cycle. Note that the cycle length + // must be a power of 2. + PeriodicValue(Vec), } diff --git a/examples/src/fibonacci/fib2/air.rs b/examples/src/fibonacci/fib2/air.rs index e291060d6..df108f967 100644 --- a/examples/src/fibonacci/fib2/air.rs +++ b/examples/src/fibonacci/fib2/air.rs @@ -78,7 +78,7 @@ impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { type PublicInputs = BaseElement; - fn get_oracles(&self) -> Vec> { + fn get_oracles(&self) -> &[air::LogUpGkrOracle] { unimplemented!() } diff --git a/examples/src/fibonacci/fib8/air.rs b/examples/src/fibonacci/fib8/air.rs index ebec6f80e..85c8c6834 100644 --- a/examples/src/fibonacci/fib8/air.rs +++ b/examples/src/fibonacci/fib8/air.rs @@ -86,7 +86,7 @@ impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { type PublicInputs = BaseElement; - fn get_oracles(&self) -> Vec> { + fn get_oracles(&self) -> &[air::LogUpGkrOracle] { unimplemented!() } diff --git a/examples/src/fibonacci/fib_small/air.rs b/examples/src/fibonacci/fib_small/air.rs index b5d301b3d..a9318c2cc 100644 --- a/examples/src/fibonacci/fib_small/air.rs +++ b/examples/src/fibonacci/fib_small/air.rs @@ -78,7 +78,7 @@ impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { type PublicInputs = BaseElement; - fn get_oracles(&self) -> Vec> { + fn get_oracles(&self) -> &[air::LogUpGkrOracle] { unimplemented!() } diff --git a/examples/src/fibonacci/mulfib2/air.rs b/examples/src/fibonacci/mulfib2/air.rs index 1fc6849e3..d511068e7 100644 --- a/examples/src/fibonacci/mulfib2/air.rs +++ b/examples/src/fibonacci/mulfib2/air.rs @@ -80,7 +80,7 @@ impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { type PublicInputs = BaseElement; - fn get_oracles(&self) -> Vec> { + fn get_oracles(&self) -> &[air::LogUpGkrOracle] { unimplemented!() } diff --git a/examples/src/fibonacci/mulfib8/air.rs b/examples/src/fibonacci/mulfib8/air.rs index fdb1e74e6..80f7040cd 100644 --- a/examples/src/fibonacci/mulfib8/air.rs +++ b/examples/src/fibonacci/mulfib8/air.rs @@ -101,7 +101,7 @@ impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { type PublicInputs = BaseElement; - fn get_oracles(&self) -> Vec> { + fn get_oracles(&self) -> &[air::LogUpGkrOracle] { unimplemented!() } diff --git a/examples/src/lamport/aggregate/air.rs b/examples/src/lamport/aggregate/air.rs index a468d21ee..42ff9ebab 100644 --- a/examples/src/lamport/aggregate/air.rs +++ b/examples/src/lamport/aggregate/air.rs @@ -299,7 +299,7 @@ impl LogUpGkrEvaluator for PlainLogUpGkrEval { type PublicInputs = PublicInputs; - fn get_oracles(&self) -> Vec> { + fn get_oracles(&self) -> &[air::LogUpGkrOracle] { unimplemented!() } diff --git a/examples/src/lamport/threshold/air.rs b/examples/src/lamport/threshold/air.rs index 54470e246..5986d7102 100644 --- a/examples/src/lamport/threshold/air.rs +++ b/examples/src/lamport/threshold/air.rs @@ -375,7 +375,7 @@ impl LogUpGkrEvaluator for PlainLogUpGkrEval { type PublicInputs = PublicInputs; - fn get_oracles(&self) -> Vec> { + fn get_oracles(&self) -> &[air::LogUpGkrOracle] { unimplemented!() } diff --git a/examples/src/merkle/air.rs b/examples/src/merkle/air.rs index 5ee9ceb1b..6f5108efe 100644 --- a/examples/src/merkle/air.rs +++ b/examples/src/merkle/air.rs @@ -146,7 +146,7 @@ impl LogUpGkrEvaluator for PlainLogUpGkrEval { type PublicInputs = PublicInputs; - fn get_oracles(&self) -> Vec> { + fn get_oracles(&self) -> &[air::LogUpGkrOracle] { unimplemented!() } diff --git a/examples/src/rescue/air.rs b/examples/src/rescue/air.rs index f10714572..bc4fcbb5a 100644 --- a/examples/src/rescue/air.rs +++ b/examples/src/rescue/air.rs @@ -151,7 +151,7 @@ impl LogUpGkrEvaluator for PlainLogUpGkrEval { type PublicInputs = PublicInputs; - fn get_oracles(&self) -> Vec> { + fn get_oracles(&self) -> &[air::LogUpGkrOracle] { unimplemented!() } diff --git a/examples/src/rescue_raps/air.rs b/examples/src/rescue_raps/air.rs index 6b197e3f8..236767c85 100644 --- a/examples/src/rescue_raps/air.rs +++ b/examples/src/rescue_raps/air.rs @@ -282,7 +282,7 @@ impl LogUpGkrEvaluator for PlainLogUpGkrEval { type PublicInputs = PublicInputs; - fn get_oracles(&self) -> Vec> { + fn get_oracles(&self) -> &[air::LogUpGkrOracle] { unimplemented!() } diff --git a/examples/src/vdf/exempt/air.rs b/examples/src/vdf/exempt/air.rs index 13480d54a..f75e29dc5 100644 --- a/examples/src/vdf/exempt/air.rs +++ b/examples/src/vdf/exempt/air.rs @@ -92,7 +92,7 @@ impl LogUpGkrEvaluator for PlainLogUpGkrEval { type PublicInputs = VdfInputs; - fn get_oracles(&self) -> Vec> { + fn get_oracles(&self) -> &[air::LogUpGkrOracle] { unimplemented!() } diff --git a/examples/src/vdf/regular/air.rs b/examples/src/vdf/regular/air.rs index a65868450..f3069cbd4 100644 --- a/examples/src/vdf/regular/air.rs +++ b/examples/src/vdf/regular/air.rs @@ -83,7 +83,7 @@ impl LogUpGkrEvaluator for PlainLogUpGkrEval { type PublicInputs = VdfInputs; - fn get_oracles(&self) -> Vec> { + fn get_oracles(&self) -> &[air::LogUpGkrOracle] { unimplemented!() } diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs index dc404361c..f21cfc227 100644 --- a/verifier/src/lib.rs +++ b/verifier/src/lib.rs @@ -379,7 +379,7 @@ fn verify_gkr>( LagrangeKernelRandElements::new(eval_point), batching_randomness, openings, - evaluator.get_oracles(), + evaluator.get_oracles().to_vec(), ); Ok(gkr_rand_elements) From 229d9ed1d58a36cc06f54a6e20689ca3ce784409 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 23 Aug 2024 12:53:30 +0200 Subject: [PATCH 24/58] chore: add dummy GkrLogUp evaluator --- air/src/air/logup_gkr.rs | 32 +++++++++------ air/src/air/mod.rs | 2 +- air/src/lib.rs | 2 +- examples/src/fibonacci/fib2/air.rs | 54 ------------------------- examples/src/fibonacci/fib8/air.rs | 54 ------------------------- examples/src/fibonacci/fib_small/air.rs | 54 ------------------------- examples/src/fibonacci/mulfib2/air.rs | 54 ------------------------- examples/src/fibonacci/mulfib8/air.rs | 54 ------------------------- examples/src/lamport/aggregate/air.rs | 54 ------------------------- examples/src/lamport/threshold/air.rs | 54 ------------------------- examples/src/merkle/air.rs | 54 ------------------------- examples/src/rescue/air.rs | 54 ------------------------- examples/src/rescue_raps/air.rs | 54 ------------------------- examples/src/vdf/exempt/air.rs | 54 ------------------------- examples/src/vdf/regular/air.rs | 54 ------------------------- prover/benches/lagrange_kernel.rs | 3 +- prover/src/tests/mod.rs | 4 +- 17 files changed, 25 insertions(+), 666 deletions(-) diff --git a/air/src/air/logup_gkr.rs b/air/src/air/logup_gkr.rs index 3b7e6d1f5..47f219843 100644 --- a/air/src/air/logup_gkr.rs +++ b/air/src/air/logup_gkr.rs @@ -201,10 +201,20 @@ pub enum LogUpGkrOracle { PeriodicValue(Vec), } -impl LogUpGkrEvaluator for () { - type BaseField = BaseElement; +#[derive(Clone, Default)] +pub struct DummyLogUpGkrEval> { + _field: PhantomData, + _public_inputs: PhantomData

, +} + +impl LogUpGkrEvaluator for DummyLogUpGkrEval +where + B: StarkField, + P: Clone + Send + Sync + ToElements, +{ + type BaseField = B; - type PublicInputs = (); + type PublicInputs = P; fn get_oracles(&self) -> &[LogUpGkrOracle] { panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") @@ -241,15 +251,11 @@ impl LogUpGkrEvaluator for () { { panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") } -} -#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] -pub enum LogUpGkrOracle { - // a column with a given index in the main trace segment - CurrentRow(usize), - // a column with a given index in the main trace segment but shifted upwards - NextRow(usize), - // a virtual periodic column defined by its values in a given cycle. Note that the cycle length - // must be a power of 2. - PeriodicValue(Vec), + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") + } } diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index 5dcee0717..60f9fd87c 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -36,7 +36,7 @@ pub use lagrange::{ }; mod logup_gkr; -pub use logup_gkr::{LogUpGkrEvaluator, LogUpGkrOracle}; +pub use logup_gkr::{DummyLogUpGkrEval, LogUpGkrEvaluator, LogUpGkrOracle}; mod coefficients; pub use coefficients::{ diff --git a/air/src/lib.rs b/air/src/lib.rs index 2993306b9..2cedbe25c 100644 --- a/air/src/lib.rs +++ b/air/src/lib.rs @@ -44,7 +44,7 @@ mod air; pub use air::{ Air, AirContext, Assertion, AuxRandElements, BoundaryConstraint, BoundaryConstraintGroup, BoundaryConstraints, ConstraintCompositionCoefficients, ConstraintDivisor, - DeepCompositionCoefficients, EvaluationFrame, GkrData, + DeepCompositionCoefficients, DummyLogUpGkrEval, EvaluationFrame, GkrData, LagrangeConstraintsCompositionCoefficients, LagrangeKernelBoundaryConstraint, LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, LagrangeKernelRandElements, LagrangeKernelTransitionConstraints, LogUpGkrEvaluator, LogUpGkrOracle, TraceInfo, diff --git a/examples/src/fibonacci/fib2/air.rs b/examples/src/fibonacci/fib2/air.rs index df108f967..4019ddcae 100644 --- a/examples/src/fibonacci/fib2/air.rs +++ b/examples/src/fibonacci/fib2/air.rs @@ -67,57 +67,3 @@ impl Air for FibAir { ] } } - -#[derive(Clone, Default)] -pub struct PlainLogUpGkrEval { - _field: std::marker::PhantomData, -} - -impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { - type BaseField = BaseElement; - - type PublicInputs = BaseElement; - - fn get_oracles(&self) -> &[air::LogUpGkrOracle] { - unimplemented!() - } - - fn get_num_rand_values(&self) -> usize { - unimplemented!() - } - - fn get_num_fractions(&self) -> usize { - unimplemented!() - } - - fn max_degree(&self) -> usize { - unimplemented!() - } - - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) - where - E: FieldElement, - { - unimplemented!() - } - - fn evaluate_query( - &self, - _query: &[F], - _rand_values: &[E], - _numerator: &mut [E], - _denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + winterfell::math::ExtensionOf, - { - unimplemented!() - } - - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - unimplemented!() - } -} diff --git a/examples/src/fibonacci/fib8/air.rs b/examples/src/fibonacci/fib8/air.rs index 85c8c6834..17edc7970 100644 --- a/examples/src/fibonacci/fib8/air.rs +++ b/examples/src/fibonacci/fib8/air.rs @@ -75,57 +75,3 @@ impl Air for Fib8Air { ] } } - -#[derive(Clone, Default)] -pub struct PlainLogUpGkrEval { - _field: std::marker::PhantomData, -} - -impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { - type BaseField = BaseElement; - - type PublicInputs = BaseElement; - - fn get_oracles(&self) -> &[air::LogUpGkrOracle] { - unimplemented!() - } - - fn get_num_rand_values(&self) -> usize { - unimplemented!() - } - - fn get_num_fractions(&self) -> usize { - unimplemented!() - } - - fn max_degree(&self) -> usize { - unimplemented!() - } - - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) - where - E: FieldElement, - { - unimplemented!() - } - - fn evaluate_query( - &self, - _query: &[F], - _rand_values: &[E], - _numerator: &mut [E], - _denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + winterfell::math::ExtensionOf, - { - unimplemented!() - } - - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - unimplemented!() - } -} diff --git a/examples/src/fibonacci/fib_small/air.rs b/examples/src/fibonacci/fib_small/air.rs index a9318c2cc..b48eb734b 100644 --- a/examples/src/fibonacci/fib_small/air.rs +++ b/examples/src/fibonacci/fib_small/air.rs @@ -67,57 +67,3 @@ impl Air for FibSmall { ] } } - -#[derive(Clone, Default)] -pub struct PlainLogUpGkrEval { - _field: std::marker::PhantomData, -} - -impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { - type BaseField = BaseElement; - - type PublicInputs = BaseElement; - - fn get_oracles(&self) -> &[air::LogUpGkrOracle] { - unimplemented!() - } - - fn get_num_rand_values(&self) -> usize { - unimplemented!() - } - - fn get_num_fractions(&self) -> usize { - unimplemented!() - } - - fn max_degree(&self) -> usize { - unimplemented!() - } - - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) - where - E: FieldElement, - { - unimplemented!() - } - - fn evaluate_query( - &self, - _query: &[F], - _rand_values: &[E], - _numerator: &mut [E], - _denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + winterfell::math::ExtensionOf, - { - unimplemented!() - } - - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - unimplemented!() - } -} diff --git a/examples/src/fibonacci/mulfib2/air.rs b/examples/src/fibonacci/mulfib2/air.rs index d511068e7..501adf6af 100644 --- a/examples/src/fibonacci/mulfib2/air.rs +++ b/examples/src/fibonacci/mulfib2/air.rs @@ -69,57 +69,3 @@ impl Air for MulFib2Air { ] } } - -#[derive(Clone, Default)] -pub struct PlainLogUpGkrEval { - _field: std::marker::PhantomData, -} - -impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { - type BaseField = BaseElement; - - type PublicInputs = BaseElement; - - fn get_oracles(&self) -> &[air::LogUpGkrOracle] { - unimplemented!() - } - - fn get_num_rand_values(&self) -> usize { - unimplemented!() - } - - fn get_num_fractions(&self) -> usize { - unimplemented!() - } - - fn max_degree(&self) -> usize { - unimplemented!() - } - - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) - where - E: FieldElement, - { - unimplemented!() - } - - fn evaluate_query( - &self, - _query: &[F], - _rand_values: &[E], - _numerator: &mut [E], - _denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + winterfell::math::ExtensionOf, - { - unimplemented!() - } - - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - unimplemented!() - } -} diff --git a/examples/src/fibonacci/mulfib8/air.rs b/examples/src/fibonacci/mulfib8/air.rs index 80f7040cd..c76f4f091 100644 --- a/examples/src/fibonacci/mulfib8/air.rs +++ b/examples/src/fibonacci/mulfib8/air.rs @@ -90,57 +90,3 @@ impl Air for MulFib8Air { ] } } - -#[derive(Clone, Default)] -pub struct PlainLogUpGkrEval { - _field: std::marker::PhantomData, -} - -impl air::LogUpGkrEvaluator for PlainLogUpGkrEval { - type BaseField = BaseElement; - - type PublicInputs = BaseElement; - - fn get_oracles(&self) -> &[air::LogUpGkrOracle] { - unimplemented!() - } - - fn get_num_rand_values(&self) -> usize { - unimplemented!() - } - - fn get_num_fractions(&self) -> usize { - unimplemented!() - } - - fn max_degree(&self) -> usize { - unimplemented!() - } - - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) - where - E: FieldElement, - { - unimplemented!() - } - - fn evaluate_query( - &self, - _query: &[F], - _rand_values: &[E], - _numerator: &mut [E], - _denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + winterfell::math::ExtensionOf, - { - unimplemented!() - } - - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - unimplemented!() - } -} diff --git a/examples/src/lamport/aggregate/air.rs b/examples/src/lamport/aggregate/air.rs index 42ff9ebab..e434b8847 100644 --- a/examples/src/lamport/aggregate/air.rs +++ b/examples/src/lamport/aggregate/air.rs @@ -288,57 +288,3 @@ const HASH_CYCLE_MASK: [BaseElement; HASH_CYCLE_LEN] = [ BaseElement::ONE, BaseElement::ZERO, ]; - -#[derive(Clone, Default)] -pub struct PlainLogUpGkrEval { - _field: PhantomData, -} - -impl LogUpGkrEvaluator for PlainLogUpGkrEval { - type BaseField = BaseElement; - - type PublicInputs = PublicInputs; - - fn get_oracles(&self) -> &[air::LogUpGkrOracle] { - unimplemented!() - } - - fn get_num_rand_values(&self) -> usize { - unimplemented!() - } - - fn get_num_fractions(&self) -> usize { - unimplemented!() - } - - fn max_degree(&self) -> usize { - unimplemented!() - } - - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) - where - E: FieldElement, - { - unimplemented!() - } - - fn evaluate_query( - &self, - _query: &[F], - _rand_values: &[E], - _numerator: &mut [E], - _denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + winterfell::math::ExtensionOf, - { - unimplemented!() - } - - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - unimplemented!() - } -} diff --git a/examples/src/lamport/threshold/air.rs b/examples/src/lamport/threshold/air.rs index 5986d7102..1b803126a 100644 --- a/examples/src/lamport/threshold/air.rs +++ b/examples/src/lamport/threshold/air.rs @@ -364,57 +364,3 @@ const HASH_CYCLE_MASK: [BaseElement; HASH_CYCLE_LEN] = [ BaseElement::ONE, BaseElement::ZERO, ]; - -#[derive(Clone, Default)] -pub struct PlainLogUpGkrEval { - _field: PhantomData, -} - -impl LogUpGkrEvaluator for PlainLogUpGkrEval { - type BaseField = BaseElement; - - type PublicInputs = PublicInputs; - - fn get_oracles(&self) -> &[air::LogUpGkrOracle] { - unimplemented!() - } - - fn get_num_rand_values(&self) -> usize { - unimplemented!() - } - - fn get_num_fractions(&self) -> usize { - unimplemented!() - } - - fn max_degree(&self) -> usize { - unimplemented!() - } - - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) - where - E: FieldElement, - { - unimplemented!() - } - - fn evaluate_query( - &self, - _query: &[F], - _rand_values: &[E], - _numerator: &mut [E], - _denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + winterfell::math::ExtensionOf, - { - unimplemented!() - } - - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - unimplemented!() - } -} diff --git a/examples/src/merkle/air.rs b/examples/src/merkle/air.rs index 6f5108efe..f6331824f 100644 --- a/examples/src/merkle/air.rs +++ b/examples/src/merkle/air.rs @@ -135,57 +135,3 @@ const HASH_CYCLE_MASK: [BaseElement; HASH_CYCLE_LEN] = [ BaseElement::ONE, BaseElement::ZERO, ]; - -#[derive(Clone, Default)] -pub struct PlainLogUpGkrEval { - _field: PhantomData, -} - -impl LogUpGkrEvaluator for PlainLogUpGkrEval { - type BaseField = BaseElement; - - type PublicInputs = PublicInputs; - - fn get_oracles(&self) -> &[air::LogUpGkrOracle] { - unimplemented!() - } - - fn get_num_rand_values(&self) -> usize { - unimplemented!() - } - - fn get_num_fractions(&self) -> usize { - unimplemented!() - } - - fn max_degree(&self) -> usize { - unimplemented!() - } - - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) - where - E: FieldElement, - { - unimplemented!() - } - - fn evaluate_query( - &self, - _query: &[F], - _rand_values: &[E], - _numerator: &mut [E], - _denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + winterfell::math::ExtensionOf, - { - unimplemented!() - } - - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - unimplemented!() - } -} diff --git a/examples/src/rescue/air.rs b/examples/src/rescue/air.rs index bc4fcbb5a..0bf92ccac 100644 --- a/examples/src/rescue/air.rs +++ b/examples/src/rescue/air.rs @@ -140,57 +140,3 @@ fn enforce_hash_copy(result: &mut [E], current: &[E], next: &[E result.agg_constraint(2, flag, is_zero(next[2])); result.agg_constraint(3, flag, is_zero(next[3])); } - -#[derive(Clone, Default)] -pub struct PlainLogUpGkrEval { - _field: PhantomData, -} - -impl LogUpGkrEvaluator for PlainLogUpGkrEval { - type BaseField = BaseElement; - - type PublicInputs = PublicInputs; - - fn get_oracles(&self) -> &[air::LogUpGkrOracle] { - unimplemented!() - } - - fn get_num_rand_values(&self) -> usize { - unimplemented!() - } - - fn get_num_fractions(&self) -> usize { - unimplemented!() - } - - fn max_degree(&self) -> usize { - unimplemented!() - } - - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) - where - E: FieldElement, - { - unimplemented!() - } - - fn evaluate_query( - &self, - _query: &[F], - _rand_values: &[E], - _numerator: &mut [E], - _denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + winterfell::math::ExtensionOf, - { - unimplemented!() - } - - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - unimplemented!() - } -} diff --git a/examples/src/rescue_raps/air.rs b/examples/src/rescue_raps/air.rs index 236767c85..c991afe0a 100644 --- a/examples/src/rescue_raps/air.rs +++ b/examples/src/rescue_raps/air.rs @@ -271,57 +271,3 @@ fn enforce_hash_copy(result: &mut [E], current: &[E], next: &[E result.agg_constraint(2, flag, are_equal(current[2], next[2])); result.agg_constraint(3, flag, are_equal(current[3], next[3])); } - -#[derive(Clone, Default)] -pub struct PlainLogUpGkrEval { - _field: PhantomData, -} - -impl LogUpGkrEvaluator for PlainLogUpGkrEval { - type BaseField = BaseElement; - - type PublicInputs = PublicInputs; - - fn get_oracles(&self) -> &[air::LogUpGkrOracle] { - unimplemented!() - } - - fn get_num_rand_values(&self) -> usize { - unimplemented!() - } - - fn get_num_fractions(&self) -> usize { - unimplemented!() - } - - fn max_degree(&self) -> usize { - unimplemented!() - } - - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) - where - E: FieldElement, - { - unimplemented!() - } - - fn evaluate_query( - &self, - _query: &[F], - _rand_values: &[E], - _numerator: &mut [E], - _denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + winterfell::math::ExtensionOf, - { - unimplemented!() - } - - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - unimplemented!() - } -} diff --git a/examples/src/vdf/exempt/air.rs b/examples/src/vdf/exempt/air.rs index f75e29dc5..d92612ba6 100644 --- a/examples/src/vdf/exempt/air.rs +++ b/examples/src/vdf/exempt/air.rs @@ -81,57 +81,3 @@ impl Air for VdfAir { &self.context } } - -#[derive(Clone, Default)] -pub struct PlainLogUpGkrEval { - _field: PhantomData, -} - -impl LogUpGkrEvaluator for PlainLogUpGkrEval { - type BaseField = BaseElement; - - type PublicInputs = VdfInputs; - - fn get_oracles(&self) -> &[air::LogUpGkrOracle] { - unimplemented!() - } - - fn get_num_rand_values(&self) -> usize { - unimplemented!() - } - - fn get_num_fractions(&self) -> usize { - unimplemented!() - } - - fn max_degree(&self) -> usize { - unimplemented!() - } - - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) - where - E: FieldElement, - { - unimplemented!() - } - - fn evaluate_query( - &self, - _query: &[F], - _rand_values: &[E], - _numerator: &mut [E], - _denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + winterfell::math::ExtensionOf, - { - unimplemented!() - } - - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - unimplemented!() - } -} diff --git a/examples/src/vdf/regular/air.rs b/examples/src/vdf/regular/air.rs index f3069cbd4..cba77a3ff 100644 --- a/examples/src/vdf/regular/air.rs +++ b/examples/src/vdf/regular/air.rs @@ -72,57 +72,3 @@ impl Air for VdfAir { &self.context } } - -#[derive(Clone, Default)] -pub struct PlainLogUpGkrEval { - _field: PhantomData, -} - -impl LogUpGkrEvaluator for PlainLogUpGkrEval { - type BaseField = BaseElement; - - type PublicInputs = VdfInputs; - - fn get_oracles(&self) -> &[air::LogUpGkrOracle] { - unimplemented!() - } - - fn get_num_rand_values(&self) -> usize { - unimplemented!() - } - - fn get_num_fractions(&self) -> usize { - unimplemented!() - } - - fn max_degree(&self) -> usize { - unimplemented!() - } - - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) - where - E: FieldElement, - { - unimplemented!() - } - - fn evaluate_query( - &self, - _query: &[F], - _rand_values: &[E], - _numerator: &mut [E], - _denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + winterfell::math::ExtensionOf, - { - unimplemented!() - } - - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - unimplemented!() - } -} diff --git a/prover/benches/lagrange_kernel.rs b/prover/benches/lagrange_kernel.rs index 348554806..a4862d20e 100644 --- a/prover/benches/lagrange_kernel.rs +++ b/prover/benches/lagrange_kernel.rs @@ -7,7 +7,8 @@ use std::time::Duration; use air::{ Air, AirContext, Assertion, AuxRandElements, ConstraintCompositionCoefficients, - EvaluationFrame, FieldExtension, ProofOptions, TraceInfo, TransitionConstraintDegree, + DummyLogUpGkrEval, EvaluationFrame, FieldExtension, ProofOptions, TraceInfo, + TransitionConstraintDegree, }; use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; use crypto::{hashers::Blake3_256, DefaultRandomCoin, MerkleTree}; diff --git a/prover/src/tests/mod.rs b/prover/src/tests/mod.rs index 5132e2025..7c1b66b9f 100644 --- a/prover/src/tests/mod.rs +++ b/prover/src/tests/mod.rs @@ -6,8 +6,8 @@ use alloc::vec::Vec; use air::{ - Air, AirContext, Assertion, EvaluationFrame, FieldExtension, ProofOptions, TraceInfo, - TransitionConstraintDegree, + Air, AirContext, Assertion, DummyLogUpGkrEval, EvaluationFrame, FieldExtension, ProofOptions, + TraceInfo, TransitionConstraintDegree, }; use math::{fields::f64::BaseElement, FieldElement, StarkField}; From b36eaa3f7d3360bdfaffe6a642431bf0c42c42da Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 23 Aug 2024 12:55:57 +0200 Subject: [PATCH 25/58] chore: fix clippy warnings --- examples/src/lamport/aggregate/air.rs | 3 --- examples/src/lamport/threshold/air.rs | 3 --- examples/src/merkle/air.rs | 3 --- examples/src/rescue/air.rs | 3 --- examples/src/rescue_raps/air.rs | 3 --- examples/src/vdf/exempt/air.rs | 3 --- examples/src/vdf/regular/air.rs | 3 --- 7 files changed, 21 deletions(-) diff --git a/examples/src/lamport/aggregate/air.rs b/examples/src/lamport/aggregate/air.rs index e434b8847..57708fd74 100644 --- a/examples/src/lamport/aggregate/air.rs +++ b/examples/src/lamport/aggregate/air.rs @@ -3,9 +3,6 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -use std::marker::PhantomData; - -use air::LogUpGkrEvaluator; use core_utils::flatten_slice_elements; use winterfell::{ math::{fields::f128::BaseElement, FieldElement, ToElements}, diff --git a/examples/src/lamport/threshold/air.rs b/examples/src/lamport/threshold/air.rs index 1b803126a..b68a2a24d 100644 --- a/examples/src/lamport/threshold/air.rs +++ b/examples/src/lamport/threshold/air.rs @@ -3,9 +3,6 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -use std::marker::PhantomData; - -use air::LogUpGkrEvaluator; use winterfell::{ math::{fields::f128::BaseElement, FieldElement, StarkField, ToElements}, Air, AirContext, Assertion, EvaluationFrame, ProofOptions, TraceInfo, diff --git a/examples/src/merkle/air.rs b/examples/src/merkle/air.rs index f6331824f..5d38397ff 100644 --- a/examples/src/merkle/air.rs +++ b/examples/src/merkle/air.rs @@ -3,9 +3,6 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -use std::marker::PhantomData; - -use air::LogUpGkrEvaluator; use winterfell::{ math::ToElements, Air, AirContext, Assertion, EvaluationFrame, ProofOptions, TraceInfo, TransitionConstraintDegree, diff --git a/examples/src/rescue/air.rs b/examples/src/rescue/air.rs index 0bf92ccac..09bf9c450 100644 --- a/examples/src/rescue/air.rs +++ b/examples/src/rescue/air.rs @@ -3,9 +3,6 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -use std::marker::PhantomData; - -use air::LogUpGkrEvaluator; use winterfell::{ math::ToElements, Air, AirContext, Assertion, EvaluationFrame, TraceInfo, TransitionConstraintDegree, diff --git a/examples/src/rescue_raps/air.rs b/examples/src/rescue_raps/air.rs index c991afe0a..694e189bc 100644 --- a/examples/src/rescue_raps/air.rs +++ b/examples/src/rescue_raps/air.rs @@ -3,9 +3,6 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -use std::marker::PhantomData; - -use air::LogUpGkrEvaluator; use core_utils::flatten_slice_elements; use winterfell::{ math::ToElements, Air, AirContext, Assertion, AuxRandElements, EvaluationFrame, TraceInfo, diff --git a/examples/src/vdf/exempt/air.rs b/examples/src/vdf/exempt/air.rs index d92612ba6..015778459 100644 --- a/examples/src/vdf/exempt/air.rs +++ b/examples/src/vdf/exempt/air.rs @@ -3,9 +3,6 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -use std::marker::PhantomData; - -use air::LogUpGkrEvaluator; use winterfell::{ math::ToElements, Air, AirContext, Assertion, EvaluationFrame, TraceInfo, TransitionConstraintDegree, diff --git a/examples/src/vdf/regular/air.rs b/examples/src/vdf/regular/air.rs index cba77a3ff..bec2ccb3c 100644 --- a/examples/src/vdf/regular/air.rs +++ b/examples/src/vdf/regular/air.rs @@ -3,9 +3,6 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -use std::marker::PhantomData; - -use air::LogUpGkrEvaluator; use winterfell::{ math::ToElements, Air, AirContext, Assertion, EvaluationFrame, TraceInfo, TransitionConstraintDegree, From b8dcb352b0cb6d3d2b3a694b277562c6fe6c29c4 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:15:47 +0200 Subject: [PATCH 26/58] chore: address feedback --- air/src/air/mod.rs | 2 +- air/src/lib.rs | 6 ++--- prover/benches/lagrange_kernel.rs | 3 +-- prover/src/tests/mod.rs | 9 ++++++- sumcheck/Cargo.toml | 2 +- verifier/src/lib.rs | 41 ++++--------------------------- verifier/src/logup_gkr/mod.rs | 35 ++++++++++++++++++++++++++ 7 files changed, 54 insertions(+), 44 deletions(-) diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index 60f9fd87c..5dcee0717 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -36,7 +36,7 @@ pub use lagrange::{ }; mod logup_gkr; -pub use logup_gkr::{DummyLogUpGkrEval, LogUpGkrEvaluator, LogUpGkrOracle}; +pub use logup_gkr::{LogUpGkrEvaluator, LogUpGkrOracle}; mod coefficients; pub use coefficients::{ diff --git a/air/src/lib.rs b/air/src/lib.rs index 2cedbe25c..4b6c5b914 100644 --- a/air/src/lib.rs +++ b/air/src/lib.rs @@ -44,9 +44,9 @@ mod air; pub use air::{ Air, AirContext, Assertion, AuxRandElements, BoundaryConstraint, BoundaryConstraintGroup, BoundaryConstraints, ConstraintCompositionCoefficients, ConstraintDivisor, - DeepCompositionCoefficients, DummyLogUpGkrEval, EvaluationFrame, GkrData, + DeepCompositionCoefficients, EvaluationFrame, GkrData, LagrangeConstraintsCompositionCoefficients, LagrangeKernelBoundaryConstraint, LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, LagrangeKernelRandElements, - LagrangeKernelTransitionConstraints, LogUpGkrEvaluator, LogUpGkrOracle, TraceInfo, - TransitionConstraintDegree, TransitionConstraints, + LagrangeKernelTransitionConstraints, LogUpGkrEvaluator, LogUpGkrOracle, PhantomLogUpGkrEval, + TraceInfo, TransitionConstraintDegree, TransitionConstraints, }; diff --git a/prover/benches/lagrange_kernel.rs b/prover/benches/lagrange_kernel.rs index a4862d20e..348554806 100644 --- a/prover/benches/lagrange_kernel.rs +++ b/prover/benches/lagrange_kernel.rs @@ -7,8 +7,7 @@ use std::time::Duration; use air::{ Air, AirContext, Assertion, AuxRandElements, ConstraintCompositionCoefficients, - DummyLogUpGkrEval, EvaluationFrame, FieldExtension, ProofOptions, TraceInfo, - TransitionConstraintDegree, + EvaluationFrame, FieldExtension, ProofOptions, TraceInfo, TransitionConstraintDegree, }; use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; use crypto::{hashers::Blake3_256, DefaultRandomCoin, MerkleTree}; diff --git a/prover/src/tests/mod.rs b/prover/src/tests/mod.rs index 7c1b66b9f..c7aeb934d 100644 --- a/prover/src/tests/mod.rs +++ b/prover/src/tests/mod.rs @@ -6,7 +6,7 @@ use alloc::vec::Vec; use air::{ - Air, AirContext, Assertion, DummyLogUpGkrEval, EvaluationFrame, FieldExtension, ProofOptions, + Air, AirContext, Assertion, EvaluationFrame, FieldExtension, PhantomLogUpGkrEval, ProofOptions, TraceInfo, TransitionConstraintDegree, }; use math::{fields::f64::BaseElement, FieldElement, StarkField}; @@ -104,6 +104,13 @@ impl Air for MockAir { fn get_periodic_column_values(&self) -> Vec> { self.periodic_columns.clone() } + + fn get_logup_gkr_evaluator>( + &self, + ) -> impl air::LogUpGkrEvaluator + { + PhantomLogUpGkrEval::default() + } } // HELPER FUNCTIONS diff --git a/sumcheck/Cargo.toml b/sumcheck/Cargo.toml index 40c6f8091..97dc79f3a 100644 --- a/sumcheck/Cargo.toml +++ b/sumcheck/Cargo.toml @@ -40,7 +40,7 @@ air = { version = "0.9", path = "../air", package = "winter-air", default-featur crypto = { version = "0.9", path = "../crypto", package = "winter-crypto", default-features = false } math = { version = "0.9", path = "../math", package = "winter-math", default-features = false } utils = { version = "0.9", path = "../utils/core", package = "winter-utils", default-features = false } -rayon = { version = "1.8", optional = true} +rayon = { version = "1.8", optional = true } smallvec = { version = "1.13", default-features = false } thiserror = { version = "1.0", git = "https://github.com/bitwalker/thiserror", branch = "no-std", default-features = false } diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs index f21cfc227..9e6d9e158 100644 --- a/verifier/src/lib.rs +++ b/verifier/src/lib.rs @@ -104,7 +104,7 @@ where public_coin_seed.append(&mut pub_inputs.to_elements()); // create AIR instance for the computation specified in the proof - let air = AIR::new(proof.trace_info().clone(), pub_inputs, proof.options().clone()); + let air = AIR::new(proof.trace_info().clone(), pub_inputs.clone(), proof.options().clone()); // figure out which version of the generic proof verification procedure to run. this is a sort // of static dispatch for selecting two generic parameter: extension field and hash function. @@ -116,6 +116,7 @@ where air, channel, public_coin, + pub_inputs, ) }, FieldExtension::Quadratic => { @@ -128,6 +129,7 @@ where air, channel, public_coin, + pub_inputs, ) }, FieldExtension::Cubic => { @@ -140,6 +142,7 @@ where air, channel, public_coin, + pub_inputs, ) }, } @@ -153,6 +156,7 @@ fn perform_verification( air: A, mut channel: VerifierChannel, mut public_coin: R, + pub_inputs: A::PublicInputs, ) -> Result<(), VerifierError> where E: FieldElement, @@ -350,41 +354,6 @@ where .map_err(VerifierError::FriVerificationFailed) } -fn verify_gkr>( - gkr_proof: GkrCircuitProof, - evaluator: &impl LogUpGkrEvaluator, - public_coin: &mut impl RandomCoin, -) -> Result, GkrVerifierError> { - let claim = E::ZERO; - let num_logup_random_values = evaluator.get_num_rand_values(); - let mut logup_randomness: Vec = Vec::with_capacity(num_logup_random_values); - - for _ in 0..num_logup_random_values { - logup_randomness.push(public_coin.draw().expect("failed to generate randomness")); - } - - let final_eval_claim = - verify_logup_gkr(claim, evaluator, &gkr_proof, logup_randomness, public_coin)?; - - let FinalOpeningClaim { eval_point, openings } = final_eval_claim; - - public_coin.reseed(H::hash_elements(&openings)); - - let mut batching_randomness = Vec::with_capacity(openings.len() - 1); - for _ in 0..openings.len() - 1 { - batching_randomness.push(public_coin.draw().expect("failed to generate randomness")) - } - - let gkr_rand_elements = GkrData::new( - LagrangeKernelRandElements::new(eval_point), - batching_randomness, - openings, - evaluator.get_oracles().to_vec(), - ); - - Ok(gkr_rand_elements) -} - // ACCEPTABLE OPTIONS // ================================================================================================ // Specifies either the minimal, conjectured or proven, security level or a set of diff --git a/verifier/src/logup_gkr/mod.rs b/verifier/src/logup_gkr/mod.rs index e317e0ab1..1a4324bf3 100644 --- a/verifier/src/logup_gkr/mod.rs +++ b/verifier/src/logup_gkr/mod.rs @@ -113,3 +113,38 @@ pub enum VerifierError { #[error("failed to verify the sum-check proof")] FailedToVerifySumCheck(#[from] SumCheckVerifierError), } + +// UNIVARIATE IOP FOR MULTI-LINEAR EVALUATION +// =============================================================================================== + +/// Generates the batching randomness used to batch a number of multi-linear evaluation claims. +/// +/// This is the $\lambda$ randomness in section 5.2 in [1] but using different random values for +/// each term instead of powers of a single random element. +/// +/// [1]: https://eprint.iacr.org/2023/1284 +pub fn generate_gkr_randomness< + E: FieldElement, + C: RandomCoin, + H: ElementHasher, +>( + final_opening_claim: FinalOpeningClaim, + oracles: &[LogUpGkrOracle], + public_coin: &mut C, +) -> GkrData { + let FinalOpeningClaim { eval_point, openings } = final_opening_claim; + + public_coin.reseed(H::hash_elements(&openings)); + + let mut batching_randomness = Vec::with_capacity(openings.len() - 1); + for _ in 0..openings.len() - 1 { + batching_randomness.push(public_coin.draw().expect("failed to generate randomness")) + } + + GkrData::new( + LagrangeKernelRandElements::new(eval_point), + batching_randomness, + openings, + oracles.to_vec(), + ) +} From 7f9e8706840190cf94365be663d3f2a5dc8a42cc Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:44:24 +0200 Subject: [PATCH 27/58] chore: loosen the trait bound --- prover/src/lib.rs | 2 +- prover/src/tests/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prover/src/lib.rs b/prover/src/lib.rs index fe58e85f3..bafb9d933 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -212,7 +212,7 @@ pub trait Prover { let mut aux_trace = self.build_aux_trace(main_trace, aux_rand_elements); if let Some(lagrange_randomness) = aux_rand_elements.lagrange() { - let evaluator = air.get_logup_gkr_evaluator::(); + let evaluator = air.get_logup_gkr_evaluator::(); let lagrange_col = build_lagrange_column(lagrange_randomness); let s_col = build_s_column( main_trace, diff --git a/prover/src/tests/mod.rs b/prover/src/tests/mod.rs index c7aeb934d..cc3c39ae1 100644 --- a/prover/src/tests/mod.rs +++ b/prover/src/tests/mod.rs @@ -105,7 +105,7 @@ impl Air for MockAir { self.periodic_columns.clone() } - fn get_logup_gkr_evaluator>( + fn get_logup_gkr_evaluator( &self, ) -> impl air::LogUpGkrEvaluator { From b5464bd00fc321e91709078a3eb1f84835c8c172 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:56:58 +0200 Subject: [PATCH 28/58] chore: address feedback 1 --- examples/Cargo.toml | 1 - verifier/src/channel.rs | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 8c75f8ee3..2fda1d952 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -26,7 +26,6 @@ default = ["std"] std = ["core-utils/std", "hex/std", "rand-utils", "winterfell/std"] [dependencies] -air = { version = "0.9", path = "../air", package = "winter-air", default-features = false } blake3 = { version = "1.5", default-features = false } core-utils = { version = "0.9", path = "../utils/core", package = "winter-utils", default-features = false } hex = { version = "0.4", optional = true } diff --git a/verifier/src/channel.rs b/verifier/src/channel.rs index 5fefe991d..fcec70bd1 100644 --- a/verifier/src/channel.rs +++ b/verifier/src/channel.rs @@ -5,6 +5,8 @@ use alloc::{string::ToString, vec::Vec}; use core::marker::PhantomData; +use sumcheck::GkrCircuitProof; +use utils::Deserializable; use air::{ proof::{Proof, Queries, Table, TraceOodFrame}, From 9322da9c1e836087d455a1e5b60613407c84a367 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:43:53 +0200 Subject: [PATCH 29/58] chore: address feedback 2 --- prover/src/lib.rs | 24 ++++++------------------ verifier/src/channel.rs | 2 -- verifier/src/lib.rs | 6 +----- 3 files changed, 7 insertions(+), 25 deletions(-) diff --git a/prover/src/lib.rs b/prover/src/lib.rs index bafb9d933..e0a320605 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -205,27 +205,15 @@ pub trait Prover { /// Builds and returns the auxiliary trace. #[allow(unused_variables)] #[maybe_async] - fn build_aux_trace(&self, main_trace: &Self::Trace, aux_rand_elements: &[E]) -> ColMatrix + fn build_aux_trace( + &self, + main_trace: &Self::Trace, + aux_rand_elements: &AuxRandElements, + ) -> ColMatrix where E: FieldElement, { - let mut aux_trace = self.build_aux_trace(main_trace, aux_rand_elements); - - if let Some(lagrange_randomness) = aux_rand_elements.lagrange() { - let evaluator = air.get_logup_gkr_evaluator::(); - let lagrange_col = build_lagrange_column(lagrange_randomness); - let s_col = build_s_column( - main_trace, - aux_rand_elements.gkr_data().expect("should not be empty"), - &evaluator, - &lagrange_col, - ); - - aux_trace.merge_column(s_col); - aux_trace.merge_column(lagrange_col); - } - - aux_trace + unimplemented!("`Prover::build_aux_trace` needs to be implemented when the trace has an auxiliary segment.") } /// Returns a STARK proof attesting to a correct execution of a computation defined by the diff --git a/verifier/src/channel.rs b/verifier/src/channel.rs index fcec70bd1..5fefe991d 100644 --- a/verifier/src/channel.rs +++ b/verifier/src/channel.rs @@ -5,8 +5,6 @@ use alloc::{string::ToString, vec::Vec}; use core::marker::PhantomData; -use sumcheck::GkrCircuitProof; -use utils::Deserializable; use air::{ proof::{Proof, Queries, Table, TraceOodFrame}, diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs index 9e6d9e158..cabbda6c7 100644 --- a/verifier/src/lib.rs +++ b/verifier/src/lib.rs @@ -104,7 +104,7 @@ where public_coin_seed.append(&mut pub_inputs.to_elements()); // create AIR instance for the computation specified in the proof - let air = AIR::new(proof.trace_info().clone(), pub_inputs.clone(), proof.options().clone()); + let air = AIR::new(proof.trace_info().clone(), pub_inputs, proof.options().clone()); // figure out which version of the generic proof verification procedure to run. this is a sort // of static dispatch for selecting two generic parameter: extension field and hash function. @@ -116,7 +116,6 @@ where air, channel, public_coin, - pub_inputs, ) }, FieldExtension::Quadratic => { @@ -129,7 +128,6 @@ where air, channel, public_coin, - pub_inputs, ) }, FieldExtension::Cubic => { @@ -142,7 +140,6 @@ where air, channel, public_coin, - pub_inputs, ) }, } @@ -156,7 +153,6 @@ fn perform_verification( air: A, mut channel: VerifierChannel, mut public_coin: R, - pub_inputs: A::PublicInputs, ) -> Result<(), VerifierError> where E: FieldElement, From 18636297ae8a154f739c4562bdcad49dd24f85f5 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:49:29 +0200 Subject: [PATCH 30/58] feat: math utilities needed for sum-check protocol --- sumcheck/src/utils/mod.rs | 8 + sumcheck/src/utils/multilinear.rs | 229 +++++++++++++++++++++++++++ sumcheck/src/utils/univariate.rs | 252 ++++++++++++++++++++++++++++++ 3 files changed, 489 insertions(+) create mode 100644 sumcheck/src/utils/mod.rs create mode 100644 sumcheck/src/utils/multilinear.rs create mode 100644 sumcheck/src/utils/univariate.rs diff --git a/sumcheck/src/utils/mod.rs b/sumcheck/src/utils/mod.rs new file mode 100644 index 000000000..41c63e1df --- /dev/null +++ b/sumcheck/src/utils/mod.rs @@ -0,0 +1,8 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +mod univariate; + +mod multilinear; \ No newline at end of file diff --git a/sumcheck/src/utils/multilinear.rs b/sumcheck/src/utils/multilinear.rs new file mode 100644 index 000000000..d6907d14c --- /dev/null +++ b/sumcheck/src/utils/multilinear.rs @@ -0,0 +1,229 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +use alloc::vec::Vec; +use core::ops::Index; +use math::FieldElement; + +#[cfg(feature = "concurrent")] +pub use rayon::prelude::*; + +// MULTI-LINEAR POLYNOMIAL +// ================================================================================================ + +/// Represents a multi-linear polynomial. +/// +/// The representation stores the evaluations of the polynomial over the boolean hyper-cube +/// ${0 , 1}^ν$. +#[derive(Clone, Debug, PartialEq)] +pub struct MultiLinearPoly { + evaluations: Vec, +} + +impl MultiLinearPoly { + /// Constructs a [`MultiLinearPoly`] from its evaluations over the boolean hyper-cube ${0 , 1}^ν$. + pub fn from_evaluations(evaluations: Vec) -> Self { + assert!(evaluations.len().is_power_of_two(), "A multi-linear polynomial should have a power of 2 number of evaluations over the Boolean hyper-cube"); + Self { evaluations } + } + + /// Returns the number of variables of the multi-linear polynomial. + pub fn num_variables(&self) -> usize { + self.evaluations.len().trailing_zeros() as usize + } + + /// Returns the evaluations over the boolean hyper-cube. + pub fn evaluations(&self) -> &[E] { + &self.evaluations + } + + /// Returns the number of evaluations. This is equal to the size of the boolean hyper-cube. + pub fn num_evaluations(&self) -> usize { + self.evaluations.len() + } + + /// Evaluate the multi-linear at some query $(r_0, ..., r_{ν - 1}) ∈ 𝔽^ν$. + /// + /// It first computes the evaluations of the Lagrange basis polynomials over the interpolating + /// set ${0 , 1}^ν$ at $(r_0, ..., r_{ν - 1})$ i.e., the Lagrange kernel at $(r_0, ..., r_{ν - 1})$. + /// The evaluation then is the inner product, indexed by ${0 , 1}^ν$, of the vector of + /// evaluations times the Lagrange kernel. + pub fn evaluate(&self, query: &[E]) -> E { + let tensored_query = compute_lagrange_basis_evals_at(query); + inner_product(&self.evaluations, &tensored_query) + } + + /// Similar to [`Self::evaluate`], except that the query was already turned into the Lagrange + /// kernel (i.e. the [`lagrange_ker::EqFunction`] evaluated at every point in the set + /// `${0 , 1}^ν$`). + /// + /// This is more efficient than [`Self::evaluate`] when multiple different [`MultiLinearPoly`] + /// need to be evaluated at the same query point. + pub fn evaluate_with_lagrange_kernel(&self, lagrange_kernel: &[E]) -> E { + inner_product(&self.evaluations, lagrange_kernel) + } + + /// Computes $f(r_0, y_1, ..., y_{ν - 1})$ using the linear interpolation formula + /// $(1 - r_0) * f(0, y_1, ..., y_{ν - 1}) + r_0 * f(1, y_1, ..., y_{ν - 1})$ and assigns + /// the resulting multi-linear, defined over a domain of half the size, to `self`. + pub fn bind_least_significant_variable(&mut self, round_challenge: E) { + let num_evals = self.evaluations.len() >> 1; + for i in 0..num_evals { + self.evaluations[i] = self.evaluations[i << 1] + + round_challenge * (self.evaluations[(i << 1) + 1] - self.evaluations[i << 1]); + } + self.evaluations.truncate(num_evals) + } + + /// Given the multilinear polynomial $f(y_0, y_1, ..., y_{ν - 1})$, returns two polynomials: + /// $f(0, y_1, ..., y_{ν - 1})$ and $f(1, y_1, ..., y_{ν - 1})$. + pub fn project_least_significant_variable(&self) -> (Self, Self) { + let mut p0 = Vec::with_capacity(self.num_evaluations() / 2); + let mut p1 = Vec::with_capacity(self.num_evaluations() / 2); + for chunk in self.evaluations.chunks_exact(2) { + p0.push(chunk[0]); + p1.push(chunk[1]); + } + + (MultiLinearPoly::from_evaluations(p0), MultiLinearPoly::from_evaluations(p1)) + } +} + +impl Index for MultiLinearPoly { + type Output = E; + + fn index(&self, index: usize) -> &E { + &(self.evaluations[index]) + } +} + +// EQ FUNCTION +// ================================================================================================ + +/// The EQ (equality) function is the binary function defined by +/// +/// $$ +/// EQ: {0 , 1}^ν ⛌ {0 , 1}^ν ⇾ {0 , 1} +/// ((x_0, ..., x_{ν - 1}), (y_0, ..., y_{ν - 1})) ↦ \prod_{i = 0}^{ν - 1} (x_i * y_i + (1 - x_i) +/// * (1 - y_i)) +/// $$ +/// +/// Taking its multi-linear extension $EQ^{~}$, we can define a basis for the set of multi-linear +/// polynomials in ν variables by +/// $${EQ^{~}(., (y_0, ..., y_{ν - 1})): (y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν}$$ +/// where each basis function is a function of its first argument. This is called the Lagrange or +/// evaluation basis for evaluation set ${0 , 1}^ν$. +/// +/// Given a function $(f: {0 , 1}^ν ⇾ 𝔽)$, its multi-linear extension (i.e., the unique +/// mult-linear polynomial extending `f` to $(f^{~}: 𝔽^ν ⇾ 𝔽)$ and agreeing with it on ${0 , 1}^ν$) is +/// defined as the summation of the evaluations of f against the Lagrange basis. +/// More specifically, given $(r_0, ..., r_{ν - 1}) ∈ 𝔽^ν$, then: +/// +/// $$ +/// f^{~}(r_0, ..., r_{ν - 1}) = \sum_{(y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν} +/// f(y_0, ..., y_{ν - 1}) EQ^{~}((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})) +/// $$ +/// +/// We call the Lagrange kernel the evaluation of the EQ^{~} function at +/// $((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))$ for all $(y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν$ for +/// a fixed $(r_0, ..., r_{ν - 1}) ∈ 𝔽^ν$. +/// +/// [`EqFunction`] represents EQ^{~} the multi-linear extension of +/// +/// $((y_0, ..., y_{ν - 1}) ↦ EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})))$ +/// +/// and contains a method to generate the Lagrange kernel for defining evaluations of multi-linear +/// extensions of arbitrary functions $(f: {0 , 1}^ν ⇾ 𝔽)$ at a given point $(r_0, ..., r_{ν - 1})$ +/// as well as a method to evaluate $EQ^{~}((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1})))$ for +/// $(t_0, ..., t_{ν - 1}) ∈ 𝔽^ν$. +pub struct EqFunction { + r: Vec, +} + +impl EqFunction { + /// Creates a new [EqFunction]. + pub fn new(r: Vec) -> Self { + let tmp = r.clone(); + EqFunction { r: tmp } + } + + /// Computes $EQ((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1})))$. + pub fn evaluate(&self, t: &[E]) -> E { + assert_eq!(self.r.len(), t.len()); + + (0..self.r.len()) + .map(|i| self.r[i] * t[i] + (E::ONE - self.r[i]) * (E::ONE - t[i])) + .fold(E::ONE, |acc, term| acc * term) + } + + /// Computes $EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))$ for all + /// $(y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν$ i.e., the Lagrange kernel at $r = (r_0, ..., r_{ν - 1})$. + pub fn evaluations(&self) -> Vec { + compute_lagrange_basis_evals_at(&self.r) + } + + /// Returns the evaluations of + /// $((y_0, ..., y_{ν - 1}) ↦ EQ^{~}((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})))$ + /// over ${0 , 1}^ν$. + pub fn ml_at(evaluation_point: Vec) -> MultiLinearPoly { + let eq_evals = EqFunction::new(evaluation_point.clone()).evaluations(); + MultiLinearPoly::from_evaluations(eq_evals) + } +} + +// HELPER +// ================================================================================================ + +/// Computes the evaluations of the Lagrange basis polynomials over the interpolating +/// set ${0 , 1}^ν$ at $(r_0, ..., r_{ν - 1})$ i.e., the Lagrange kernel at $(r_0, ..., r_{ν - 1})$. +/// +/// TODO: This is a critical function and parallelizing would have a significant impact on +/// performance. +fn compute_lagrange_basis_evals_at(query: &[E]) -> Vec { + let nu = query.len(); + let n = 1 << nu; + + let mut evals: Vec = vec![E::ONE; n]; + let mut size = 1; + for r_i in query.iter().rev() { + size *= 2; + for i in (0..size).rev().step_by(2) { + let scalar = evals[i / 2]; + evals[i] = scalar * *r_i; + evals[i - 1] = scalar - evals[i]; + } + } + evals +} + +/// Computes the inner product in the extension field of two iterators that must yield the same +/// number of items. +/// +/// If `concurrent` feature is enabled, this function can make use of multi-threading. +pub fn inner_product(x: &[E], y: &[E]) -> E { + #[cfg(not(feature = "concurrent"))] + return x.iter().zip(y.iter()).fold(E::ZERO, |acc, (x_i, y_i)| acc + *x_i * *y_i); + + #[cfg(feature = "concurrent")] + return x + .par_iter() + .zip(y.par_iter()) + .map(|(x_i, y_i)| *x_i * *y_i) + .reduce(|| E::ZERO, |a, b| a + b); +} + +// TESTS +// ================================================================================================ + +#[test] +fn test_bind() { + use math::fields::f64::BaseElement; + let mut p = MultiLinearPoly::from_evaluations(vec![BaseElement::ONE; 8]); + let expected = MultiLinearPoly::from_evaluations(vec![BaseElement::ONE; 4]); + + let challenge = rand_utils::rand_value(); + p.bind_least_significant_variable(challenge); + assert_eq!(p, expected) +} diff --git a/sumcheck/src/utils/univariate.rs b/sumcheck/src/utils/univariate.rs new file mode 100644 index 000000000..868206213 --- /dev/null +++ b/sumcheck/src/utils/univariate.rs @@ -0,0 +1,252 @@ + +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +use alloc::vec::Vec; +use math::{batch_inversion, polynom, FieldElement}; + + +// COMPRESSED UNIVARIATE POLYNOMIAL +// ================================================================================================ + +/// The coefficients of a univariate polynomial of degree n with the linear term coefficient +/// omitted. +/// +/// This compressed representation is useful during the sum-check protocol as the full uncompressed +/// representation can be recovered from the compressed one and the current sum-check round claim. +#[derive(Clone, Debug)] +pub struct CompressedUnivariatePoly(Vec); + +impl CompressedUnivariatePoly { + /// Evaluates a polynomial at a challenge point using a round claim. + /// + /// The round claim is used to recover the coefficient of the linear term using the relation + /// 2 * c0 + c1 + ... c_{n - 1} = claim. Using the complete list of coefficients, the polynomial + /// is then evaluated using Horner's method. + pub fn evaluate_using_claim(&self, claim: &E, challenge: &E) -> E { + // recover the coefficient of the linear term + let c1 = *claim - self.0.iter().fold(E::ZERO, |acc, term| acc + *term) - self.0[0]; + + // construct the full coefficient list + let mut complete_coefficients = vec![self.0[0], c1]; + complete_coefficients.extend_from_slice(&self.0[1..]); + + // evaluate + polynom::eval(&complete_coefficients, *challenge) + } +} + +/// The evaluations of a univariate polynomial of degree n at 0, 1, ..., n with the evaluation at 0 +/// omitted. +/// +/// This compressed representation is useful during the sum-check protocol as the full uncompressed +/// representation can be recovered from the compressed one and the current sum-check round claim. +#[derive(Clone, Debug)] +pub struct CompressedUnivariatePolyEvals(Vec); + +impl CompressedUnivariatePolyEvals { + /// Gives the coefficient representation of a polynomial represented in evaluation form. + /// + /// Since the evaluation at 0 is omitted, we need to use the round claim to recover + /// the evaluation at 0 using the identity $p(0) + p(1) = claim$. + /// Now, we have that for any polynomial $p(x) = c0 + c1 * x + ... + c_{n-1} * x^{n - 1}$: + /// + /// 1. $p(0) = c0$. + /// 2. $p(x) = c0 + x * q(x) where q(x) = c1 + ... + c_{n-1} * x^{n - 2}$. + /// + /// This means that we can compute the evaluations of q at 1, ..., n - 1 using the evaluations + /// of p and thus reduce by 1 the size of the interpolation problem. + /// Once the coefficient of q are recovered, the c0 coefficient is appended to these and this + /// is precisely the coefficient representation of the original polynomial q. + /// Note that the coefficient of the linear term is removed as this coefficient can be recovered + /// from the remaining coefficients, again, using the round claim using the relation + /// $2 * c0 + c1 + ... c_{n - 1} = claim$. + pub fn to_poly(&self, round_claim: E) -> CompressedUnivariatePoly { + // construct the vector of interpolation points 1, ..., n + let n_minus_1 = self.0.len(); + let points = (1..=n_minus_1 as u32).map(E::BaseField::from).collect::>(); + + // construct their inverses. These will be needed for computing the evaluations + // of the q polynomial as well as for doing the interpolation on q + let points_inv = batch_inversion(&points); + + // compute the zeroth coefficient + let c0 = round_claim - self.0[0]; + + // compute the evaluations of q + let q_evals: Vec = self + .0 + .iter() + .enumerate() + .map(|(i, evals)| (*evals - c0).mul_base(points_inv[i])) + .collect(); + + // interpolate q + let q_coefs = multiply_by_inverse_vandermonde(&q_evals, &points_inv); + + // append c0 to the coefficients of q to get the coefficients of p. The linear term + // coefficient is removed as this can be recovered from the other coefficients using + // the reduced claim. + let mut coefficients = Vec::with_capacity(self.0.len() + 1); + coefficients.push(c0); + coefficients.extend_from_slice(&q_coefs[1..]); + + CompressedUnivariatePoly(coefficients) + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Given a (row) vector `v`, computes the vector-matrix product `v * V^{-1}` where `V` is +/// the Vandermonde matrix over the points `1, ..., n` where `n` is the length of `v`. +/// The resulting vector will then be the coefficients of the minimal interpolating polynomial +/// through the points `(i+1, v[i])` for `i` in `0, ..., n - 1` +/// +/// The naive way would be to invert the matrix `V` and then compute the vector-matrix product +/// this will cost `O(n^3)` operations and `O(n^2)` memory. We can also try Gaussian elimination +/// but this is also worst case `O(n^3)` operations and `O(n^2)` memory. +/// In the following implementation, we use the fact that the points over which we are interpolating +/// is a set of equidistant points and thus both the Vandermonde matrix and its inverse can be +/// described by sparse linear recurrence equations. +/// More specifically, we use the representation given in [1], where `V^{-1}` is represented as +/// `U * M` where: +/// +/// 1. `M` is a lower triangular matrix where its entries are given by M(i, j) = M(i - 1, j) - M(i - +/// 1, j - 1) / (i - 1) with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. +/// +/// 2. `U` is an upper triangular (involutory) matrix where its entries are given by U(i, j) = U(i, +/// j - 1) - U(i - 1, j - 1) with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. +/// +/// Note that the matrix indexing in the formulas above matches the one in the reference and starts +/// from 1. +/// +/// The above implies that we can do the vector-matrix multiplication in `O(n^2)` and using only +/// `O(n)` space. +/// +/// [1]: https://link.springer.com/article/10.1007/s002110050360 +fn multiply_by_inverse_vandermonde( + vector: &[E], + nodes_inv: &[E::BaseField], +) -> Vec { + let res = multiply_by_u(vector); + multiply_by_m(&res, nodes_inv) +} + +/// Multiplies a (row) vector `v` by an upper triangular matrix `U` to compute `v * U`. +/// +/// `U` is an upper triangular (involutory) matrix with its entries given by +/// U(i, j) = U(i, j - 1) - U(i - 1, j - 1) +/// with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. +fn multiply_by_u(vector: &[E]) -> Vec { + let n = vector.len(); + let mut previous_u_col = vec![E::BaseField::ZERO; n]; + previous_u_col[0] = E::BaseField::ONE; + let mut current_u_col = vec![E::BaseField::ZERO; n]; + current_u_col[0] = E::BaseField::ONE; + + let mut result: Vec = vec![E::ZERO; n]; + for (i, res) in result.iter_mut().enumerate() { + *res = vector[0]; + + for (j, v) in vector.iter().enumerate().take(i + 1).skip(1) { + let u_entry: E::BaseField = + compute_u_entry::(j, &mut previous_u_col, &mut current_u_col); + *res += v.mul_base(u_entry); + } + previous_u_col.clone_from(¤t_u_col); + } + + result +} + +/// Multiplies a (row) vector `v` by a lower triangular matrix `M` to compute `v * M`. +/// +/// `M` is a lower triangular matrix with its entries given by +/// M(i, j) = M(i - 1, j) - M(i - 1, j - 1) / (i - 1) +/// with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. +fn multiply_by_m(vector: &[E], nodes_inv: &[E::BaseField]) -> Vec { + let n = vector.len(); + let mut previous_m_col = vec![E::BaseField::ONE; n]; + let mut current_m_col = vec![E::BaseField::ZERO; n]; + current_m_col[0] = E::BaseField::ONE; + + let mut result: Vec = vec![E::ZERO; n]; + result[0] = vector.iter().fold(E::ZERO, |acc, term| acc + *term); + for (i, res) in result.iter_mut().enumerate().skip(1) { + current_m_col = vec![E::BaseField::ZERO; n]; + + for (j, v) in vector.iter().enumerate().skip(i) { + let m_entry: E::BaseField = + compute_m_entry::(j, &mut previous_m_col, &mut current_m_col, nodes_inv[j - 1]); + *res += v.mul_base(m_entry); + } + previous_m_col.clone_from(¤t_m_col); + } + + result +} + +/// Returns the j-th entry of the i-th column of matrix `U` given the values of the (i - 1)-th +/// column. The i-th column is also updated with the just computed `U(i, j)` entry. +/// +/// `U` is an upper triangular (involutory) matrix with its entries given by +/// U(i, j) = U(i, j - 1) - U(i - 1, j - 1) +/// with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. +fn compute_u_entry( + j: usize, + col_prev: &mut [E::BaseField], + col_cur: &mut [E::BaseField], +) -> E::BaseField { + let value = col_prev[j] - col_prev[j - 1]; + col_cur[j] = value; + value +} + +/// Returns the j-th entry of the i-th column of matrix `M` given the values of the (i - 1)-th +/// and the i-th columns. The i-th column is also updated with the just computed `M(i, j)` entry. +/// +/// `M` is a lower triangular matrix with its entries given by +/// M(i, j) = M(i - 1, j) - M(i - 1, j - 1) / (i - 1) +/// with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. +fn compute_m_entry( + j: usize, + col_previous: &mut [E::BaseField], + col_current: &mut [E::BaseField], + node_inv: E::BaseField, +) -> E::BaseField { + let value = col_current[j - 1] - node_inv * col_previous[j - 1]; + col_current[j] = value; + value +} + +// TESTS +// ================================================================================================ + +#[test] +fn test_poly_partial() { + use math::fields::f64::BaseElement; + + let degree = 1000; + let mut points: Vec = vec![BaseElement::ZERO; degree]; + points + .iter_mut() + .enumerate() + .for_each(|(i, node)| *node = BaseElement::from(i as u32)); + + let p: Vec = rand_utils::rand_vector(degree); + let evals = polynom::eval_many(&p, &points); + + let mut partial_evals = evals.clone(); + partial_evals.remove(0); + + let partial_poly = CompressedUnivariatePolyEvals(partial_evals); + let claim = evals[0] + evals[1]; + let poly_coeff = partial_poly.to_poly(claim); + + let r = rand_utils::rand_vector(1); + + assert_eq!(polynom::eval(&p, r[0]), poly_coeff.evaluate_using_claim(&claim, &r[0])) +} \ No newline at end of file From ea3ed0219f548f13d4b717b50b3cc14d03a43094 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:08:36 +0200 Subject: [PATCH 31/58] feat: add sum-check prover and verifier --- air/src/air/mod.rs | 66 ++++++++++++++++++++++++++++++++ sumcheck/src/utils/mod.rs | 4 +- sumcheck/src/utils/univariate.rs | 20 +++++++++- 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index 5dcee0717..da1e0cf44 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -614,3 +614,69 @@ pub trait Air: Send + Sync { }) } } + + +pub trait LogUpGkrEvaluator: Clone { + /// Defines the base field of the evaluator. + type BaseField: StarkField; + + /// Public inputs need to compute the final claim. + type PublicInputs: ToElements + Send; + + /// Gets a list of all oracles involved in LogUp-GKR; this is intended to be used in construction of + /// MLEs. + fn get_oracles(&self) -> Vec>; + + /// Returns the number of random values needed to evaluate a query. + fn get_num_rand_values(&self) -> usize; + + /// Returns the number of fractions in the LogUp-GKR statement. + fn get_num_fractions(&self) -> usize; + + /// Returns the maximal degree of the multi-variate associated to the input layer. + fn max_degree(&self) -> usize; + + /// Builds a query from the provided main trace frame and periodic values. + /// + /// Note: it should be possible to provide an implementation of this method based on the + /// information returned from `get_oracles()`. However, this implementation is likely to be + /// expensive compared to the hand-written implementation. However, we could provide a test + /// which verifies that `get_oracles()` and `build_query()` methods are consistent. + fn build_query(&self, frame: &EvaluationFrame, periodic_values: &[E]) -> Vec + where + E: FieldElement; + + /// Evaluates the provided query and writes the results into the numerators and denominators. + /// + /// Note: it is also possible to combine `build_query()` and `evaluate_query()` into a single + /// method to avoid the need to first build the query struct and then evaluate it. However: + /// - We assume that the compiler will be able to optimize this away. + /// - Merging the methods will make it more difficult avoid inconsistencies between + /// `evaluate_query()` and `get_oracles()` methods. + fn evaluate_query( + &self, + query: &[F], + rand_values: &[E], + numerator: &mut [E], + denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + ExtensionOf; + + /// Computes the final claim for the LogUp-GKR circuit. + /// + /// The default implementation of this method returns E::ZERO as it is expected that the + /// fractional sums will cancel out. However, in cases when some boundary conditions need to + /// be imposed on the LogUp-GKR relations, this method can be overridden to compute the final + /// expected claim. + fn compute_claim(&self, inputs: &Self::PublicInputs, rand_values: &[E]) -> E + where + E: FieldElement; +} + +#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] +pub enum LogUpGkrOracle { + CurrentRow(usize), + NextRow(usize), + PeriodicValue(Vec), +} \ No newline at end of file diff --git a/sumcheck/src/utils/mod.rs b/sumcheck/src/utils/mod.rs index 41c63e1df..afb0810fd 100644 --- a/sumcheck/src/utils/mod.rs +++ b/sumcheck/src/utils/mod.rs @@ -4,5 +4,7 @@ // LICENSE file in the root directory of this source tree. mod univariate; +pub use univariate::{CompressedUnivariatePoly, CompressedUnivariatePolyEvals}; -mod multilinear; \ No newline at end of file +mod multilinear; +pub use multilinear::{EqFunction, MultiLinearPoly}; \ No newline at end of file diff --git a/sumcheck/src/utils/univariate.rs b/sumcheck/src/utils/univariate.rs index 868206213..361f97273 100644 --- a/sumcheck/src/utils/univariate.rs +++ b/sumcheck/src/utils/univariate.rs @@ -5,6 +5,7 @@ // LICENSE file in the root directory of this source tree. use alloc::vec::Vec; +use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; use math::{batch_inversion, polynom, FieldElement}; @@ -17,7 +18,7 @@ use math::{batch_inversion, polynom, FieldElement}; /// This compressed representation is useful during the sum-check protocol as the full uncompressed /// representation can be recovered from the compressed one and the current sum-check round claim. #[derive(Clone, Debug)] -pub struct CompressedUnivariatePoly(Vec); +pub struct CompressedUnivariatePoly(pub(crate) Vec); impl CompressedUnivariatePoly { /// Evaluates a polynomial at a challenge point using a round claim. @@ -38,13 +39,28 @@ impl CompressedUnivariatePoly { } } +impl Serializable for CompressedUnivariatePoly { + fn write_into(&self, target: &mut W) { + self.0.write_into(target); + } +} + +impl Deserializable for CompressedUnivariatePoly +where + E: FieldElement, +{ + fn read_from(source: &mut R) -> Result { + Ok(Self(Deserializable::read_from(source)?)) + } +} + /// The evaluations of a univariate polynomial of degree n at 0, 1, ..., n with the evaluation at 0 /// omitted. /// /// This compressed representation is useful during the sum-check protocol as the full uncompressed /// representation can be recovered from the compressed one and the current sum-check round claim. #[derive(Clone, Debug)] -pub struct CompressedUnivariatePolyEvals(Vec); +pub struct CompressedUnivariatePolyEvals(pub(crate) Vec); impl CompressedUnivariatePolyEvals { /// Gives the coefficient representation of a polynomial represented in evaluation form. From 3ae1ec09d2a5016e7849869293e13eb06b3ca616 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:43:14 +0200 Subject: [PATCH 32/58] tests: add sanity tests for utils --- air/src/air/mod.rs | 3 +- sumcheck/src/utils/mod.rs | 2 +- sumcheck/src/utils/multilinear.rs | 55 ++++++++++++++++++++++++++++--- sumcheck/src/utils/univariate.rs | 11 +++---- 4 files changed, 57 insertions(+), 14 deletions(-) diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index da1e0cf44..0ea0e3528 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -615,7 +615,6 @@ pub trait Air: Send + Sync { } } - pub trait LogUpGkrEvaluator: Clone { /// Defines the base field of the evaluator. type BaseField: StarkField; @@ -679,4 +678,4 @@ pub enum LogUpGkrOracle { CurrentRow(usize), NextRow(usize), PeriodicValue(Vec), -} \ No newline at end of file +} diff --git a/sumcheck/src/utils/mod.rs b/sumcheck/src/utils/mod.rs index afb0810fd..d57e05677 100644 --- a/sumcheck/src/utils/mod.rs +++ b/sumcheck/src/utils/mod.rs @@ -7,4 +7,4 @@ mod univariate; pub use univariate::{CompressedUnivariatePoly, CompressedUnivariatePolyEvals}; mod multilinear; -pub use multilinear::{EqFunction, MultiLinearPoly}; \ No newline at end of file +pub use multilinear::{EqFunction, MultiLinearPoly}; diff --git a/sumcheck/src/utils/multilinear.rs b/sumcheck/src/utils/multilinear.rs index d6907d14c..c45b993e2 100644 --- a/sumcheck/src/utils/multilinear.rs +++ b/sumcheck/src/utils/multilinear.rs @@ -5,8 +5,8 @@ use alloc::vec::Vec; use core::ops::Index; -use math::FieldElement; +use math::FieldElement; #[cfg(feature = "concurrent")] pub use rayon::prelude::*; @@ -178,7 +178,7 @@ impl EqFunction { /// Computes the evaluations of the Lagrange basis polynomials over the interpolating /// set ${0 , 1}^ν$ at $(r_0, ..., r_{ν - 1})$ i.e., the Lagrange kernel at $(r_0, ..., r_{ν - 1})$. -/// +/// /// TODO: This is a critical function and parallelizing would have a significant impact on /// performance. fn compute_lagrange_basis_evals_at(query: &[E]) -> Vec { @@ -198,9 +198,8 @@ fn compute_lagrange_basis_evals_at(query: &[E]) -> Vec { evals } -/// Computes the inner product in the extension field of two iterators that must yield the same -/// number of items. -/// +/// Computes the inner product in the extension field of two slices with the same number of items. +/// /// If `concurrent` feature is enabled, this function can make use of multi-threading. pub fn inner_product(x: &[E], y: &[E]) -> E { #[cfg(not(feature = "concurrent"))] @@ -217,6 +216,26 @@ pub fn inner_product(x: &[E], y: &[E]) -> E { // TESTS // ================================================================================================ +#[test] +fn multi_linear_sanity_checks() { + use math::fields::f64::BaseElement; + let nu = 3; + let n = 1 << nu; + + // the zero multi-linear should evaluate to zero + let p = MultiLinearPoly::from_evaluations(vec![BaseElement::ZERO; n]); + let challenge: Vec = rand_utils::rand_vector(nu); + + assert_eq!(BaseElement::ZERO, p.evaluate(&challenge)); + + // the constant multi-linear should be constant everywhere + let constant = rand_utils::rand_value(); + let p = MultiLinearPoly::from_evaluations(vec![constant; n]); + let challenge: Vec = rand_utils::rand_vector(nu); + + assert_eq!(constant, p.evaluate(&challenge)) +} + #[test] fn test_bind() { use math::fields::f64::BaseElement; @@ -227,3 +246,29 @@ fn test_bind() { p.bind_least_significant_variable(challenge); assert_eq!(p, expected) } + +#[test] +fn test_eq_function() { + use math::fields::f64::BaseElement; + use rand_utils::rand_value; + + let one = BaseElement::ONE; + + // Lagrange kernel is computed correctly + let r0 = rand_value(); + let r1 = rand_value(); + let eq_function = EqFunction::new(vec![r0, r1]); + + let expected = vec![(one - r0) * (one - r1), r0 * (one - r1), (one - r0) * r1, r0 * r1]; + + assert_eq!(expected, eq_function.evaluations()); + + // Lagrange kernel evaluation is correct + let q0 = rand_value(); + let q1 = rand_value(); + let tensored_query = vec![(one - q0) * (one - q1), q0 * (one - q1), (one - q0) * q1, q0 * q1]; + + let expected = inner_product(&tensored_query, &eq_function.evaluations()); + + assert_eq!(expected, eq_function.evaluate(&[q0, q1])) +} diff --git a/sumcheck/src/utils/univariate.rs b/sumcheck/src/utils/univariate.rs index 361f97273..399d19b1b 100644 --- a/sumcheck/src/utils/univariate.rs +++ b/sumcheck/src/utils/univariate.rs @@ -1,13 +1,12 @@ - // Copyright (c) Facebook, Inc. and its affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. use alloc::vec::Vec; -use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; -use math::{batch_inversion, polynom, FieldElement}; +use math::{batch_inversion, polynom, FieldElement}; +use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; // COMPRESSED UNIVARIATE POLYNOMIAL // ================================================================================================ @@ -39,13 +38,13 @@ impl CompressedUnivariatePoly { } } -impl Serializable for CompressedUnivariatePoly { +impl Serializable for CompressedUnivariatePoly { fn write_into(&self, target: &mut W) { self.0.write_into(target); } } -impl Deserializable for CompressedUnivariatePoly +impl Deserializable for CompressedUnivariatePoly where E: FieldElement, { @@ -265,4 +264,4 @@ fn test_poly_partial() { let r = rand_utils::rand_vector(1); assert_eq!(polynom::eval(&p, r[0]), poly_coeff.evaluate_using_claim(&claim, &r[0])) -} \ No newline at end of file +} From e44894afaacf243a56f2de6601a13eeb2b2c69f7 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 7 Aug 2024 08:16:15 +0200 Subject: [PATCH 33/58] feat: use SmallVec --- sumcheck/src/utils/multilinear.rs | 8 +++++-- sumcheck/src/utils/univariate.rs | 37 +++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/sumcheck/src/utils/multilinear.rs b/sumcheck/src/utils/multilinear.rs index c45b993e2..0ad5f6a18 100644 --- a/sumcheck/src/utils/multilinear.rs +++ b/sumcheck/src/utils/multilinear.rs @@ -9,6 +9,7 @@ use core::ops::Index; use math::FieldElement; #[cfg(feature = "concurrent")] pub use rayon::prelude::*; +use smallvec::SmallVec; // MULTI-LINEAR POLYNOMIAL // ================================================================================================ @@ -102,6 +103,9 @@ impl Index for MultiLinearPoly { // EQ FUNCTION // ================================================================================================ +/// Maximal expected size of the point of a given Lagrange kernel. +const MAX_EQ_SIZE: usize = 25; + /// The EQ (equality) function is the binary function defined by /// /// $$ @@ -139,13 +143,13 @@ impl Index for MultiLinearPoly { /// as well as a method to evaluate $EQ^{~}((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1})))$ for /// $(t_0, ..., t_{ν - 1}) ∈ 𝔽^ν$. pub struct EqFunction { - r: Vec, + r: SmallVec<[E; MAX_EQ_SIZE]>, } impl EqFunction { /// Creates a new [EqFunction]. pub fn new(r: Vec) -> Self { - let tmp = r.clone(); + let tmp = r.into(); EqFunction { r: tmp } } diff --git a/sumcheck/src/utils/univariate.rs b/sumcheck/src/utils/univariate.rs index 399d19b1b..8cd56e683 100644 --- a/sumcheck/src/utils/univariate.rs +++ b/sumcheck/src/utils/univariate.rs @@ -6,8 +6,15 @@ use alloc::vec::Vec; use math::{batch_inversion, polynom, FieldElement}; +use smallvec::SmallVec; use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; +// CONSTANTS +// ================================================================================================ + +/// Maximum expected size of the round polynomials. This is needed for `SmallVec`. +const MAX_POLY_SIZE: usize = 10; + // COMPRESSED UNIVARIATE POLYNOMIAL // ================================================================================================ @@ -16,8 +23,8 @@ use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serial /// /// This compressed representation is useful during the sum-check protocol as the full uncompressed /// representation can be recovered from the compressed one and the current sum-check round claim. -#[derive(Clone, Debug)] -pub struct CompressedUnivariatePoly(pub(crate) Vec); +#[derive(Clone, Debug, PartialEq)] +pub struct CompressedUnivariatePoly(pub(crate) SmallVec<[E; MAX_POLY_SIZE]>); impl CompressedUnivariatePoly { /// Evaluates a polynomial at a challenge point using a round claim. @@ -40,7 +47,8 @@ impl CompressedUnivariatePoly { impl Serializable for CompressedUnivariatePoly { fn write_into(&self, target: &mut W) { - self.0.write_into(target); + let vector: Vec = self.0.clone().into_vec(); + vector.write_into(target); } } @@ -49,7 +57,8 @@ where E: FieldElement, { fn read_from(source: &mut R) -> Result { - Ok(Self(Deserializable::read_from(source)?)) + let vector: Vec = Vec::::read_from(source)?; + Ok(Self(vector.into())) } } @@ -59,7 +68,7 @@ where /// This compressed representation is useful during the sum-check protocol as the full uncompressed /// representation can be recovered from the compressed one and the current sum-check round claim. #[derive(Clone, Debug)] -pub struct CompressedUnivariatePolyEvals(pub(crate) Vec); +pub struct CompressedUnivariatePolyEvals(pub(crate) SmallVec<[E; MAX_POLY_SIZE]>); impl CompressedUnivariatePolyEvals { /// Gives the coefficient representation of a polynomial represented in evaluation form. @@ -104,7 +113,7 @@ impl CompressedUnivariatePolyEvals { // append c0 to the coefficients of q to get the coefficients of p. The linear term // coefficient is removed as this can be recovered from the other coefficients using // the reduced claim. - let mut coefficients = Vec::with_capacity(self.0.len() + 1); + let mut coefficients = SmallVec::with_capacity(self.0.len() + 1); coefficients.push(c0); coefficients.extend_from_slice(&q_coefs[1..]); @@ -257,7 +266,7 @@ fn test_poly_partial() { let mut partial_evals = evals.clone(); partial_evals.remove(0); - let partial_poly = CompressedUnivariatePolyEvals(partial_evals); + let partial_poly = CompressedUnivariatePolyEvals(partial_evals.into()); let claim = evals[0] + evals[1]; let poly_coeff = partial_poly.to_poly(claim); @@ -265,3 +274,17 @@ fn test_poly_partial() { assert_eq!(polynom::eval(&p, r[0]), poly_coeff.evaluate_using_claim(&claim, &r[0])) } + +#[test] +fn test_serialization() { + use math::fields::f64::BaseElement; + + let original_poly = + CompressedUnivariatePoly(rand_utils::rand_array::().into()); + let poly_bytes = original_poly.to_bytes(); + + let deserialized_poly = + CompressedUnivariatePoly::::read_from_bytes(&poly_bytes).unwrap(); + + assert_eq!(original_poly, deserialized_poly) +} From 0613574fc6a76f98fe795d424f6351eca0684471 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 9 Aug 2024 09:34:11 +0200 Subject: [PATCH 34/58] feat: add remaining functions for sum-check verifier --- sumcheck/src/verifier/mod.rs | 141 ++++++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 2 deletions(-) diff --git a/sumcheck/src/verifier/mod.rs b/sumcheck/src/verifier/mod.rs index 887598cc8..3f2a57581 100644 --- a/sumcheck/src/verifier/mod.rs +++ b/sumcheck/src/verifier/mod.rs @@ -126,7 +126,7 @@ where let round_poly_coefs = round_proof.round_poly_coefs.clone(); coin.reseed(H::hash_elements(&round_poly_coefs.0)); - let r = coin.draw().map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; + let r = coin.draw().map_err(|_| Error::FailedToGenerateChallenge)?; round_claim = round_proof.round_poly_coefs.evaluate_using_claim(&round_claim, &r); evaluation_point.push(r); @@ -138,8 +138,145 @@ where }) } +/// Verifies sum-check proofs, as part of the GKR proof, for all GKR layers except for the last one +/// i.e., the circuit input layer. +pub fn verify_sum_check_intermediate_layers< + E: FieldElement, + C: RandomCoin, + H: ElementHasher, +>( + proof: &SumCheckProof, + gkr_eval_point: &[E], + claim: (E, E), + transcript: &mut C, +) -> Result, Error> { + // generate challenge to batch sum-checks + transcript.reseed(H::hash_elements(&[claim.0, claim.1])); + let r_batch: E = transcript.draw().map_err(|_| Error::FailedToGenerateChallenge)?; + + // compute the claim for the batched sum-check + let reduced_claim = claim.0 + claim.1 * r_batch; + + let SumCheckProof { openings_claim, round_proofs } = proof; + + let final_round_claim = verify_rounds(reduced_claim, round_proofs, transcript)?; + assert_eq!(openings_claim.eval_point, final_round_claim.eval_point); + + let p0 = openings_claim.openings[0]; + let p1 = openings_claim.openings[1]; + let q0 = openings_claim.openings[2]; + let q1 = openings_claim.openings[3]; + + let eq = EqFunction::new(gkr_eval_point.to_vec()).evaluate(&openings_claim.eval_point); + + if (p0 * q1 + p1 * q0 + r_batch * q0 * q1) * eq != final_round_claim.claim { + return Err(Error::FinalEvaluationCheckFailed); + } + + Ok(openings_claim.clone()) +} + +/// Verifies the final sum-check proof of a GKR proof. +pub fn verify_sum_check_input_layer< + E: FieldElement, + C: RandomCoin, + H: ElementHasher, +>( + evaluator: &impl LogUpGkrEvaluator, + proof: &FinalLayerProof, + log_up_randomness: Vec, + gkr_eval_point: &[E], + claim: (E, E), + transcript: &mut C, +) -> Result, Error> { + let FinalLayerProof { before_merge_proof, after_merge_proof } = proof; + + // generate challenge to batch sum-checks + transcript.reseed(H::hash_elements(&[claim.0, claim.1])); + let r_batch: E = transcript.draw().map_err(|_| Error::FailedToGenerateChallenge)?; + + // compute the claim for the batched sum-check + let reduced_claim = claim.0 + claim.1 * r_batch; + + // verify the first half of the sum-check proof i.e., `before_merge_proof` + let SumCheckRoundClaim { eval_point: rand_merge, claim } = + verify_rounds(reduced_claim, before_merge_proof, transcript)?; + + // verify the second half of the sum-check proof i.e., `after_merge_proof` + verify_sum_check_final( + claim, + after_merge_proof, + rand_merge, + r_batch, + log_up_randomness, + gkr_eval_point, + evaluator, + transcript, + ) +} + +/// Verifies the second sum-check proof for the input layer, including the final check, and returns +/// a [`FinalOpeningClaim`] to the STARK verifier in order to verify the correctness of +/// the openings. +#[allow(clippy::too_many_arguments)] +fn verify_sum_check_final< + E: FieldElement, + C: RandomCoin, + H: ElementHasher, +>( + claim: E, + after_merge_proof: &SumCheckProof, + rand_merge: Vec, + r_batch: E, + log_up_randomness: Vec, + gkr_eval_point: &[E], + evaluator: &impl LogUpGkrEvaluator, + transcript: &mut C, +) -> Result, Error> { + let SumCheckProof { openings_claim, round_proofs } = after_merge_proof; + + let SumCheckRoundClaim { + eval_point: evaluation_point, + claim: claimed_evaluation, + } = verify_rounds(claim, round_proofs, transcript)?; + + if openings_claim.eval_point != evaluation_point { + return Err(Error::WrongOpeningPoint); + } + + let mut numerators = vec![E::ZERO; evaluator.get_num_fractions()]; + let mut denominators = vec![E::ZERO; evaluator.get_num_fractions()]; + + evaluator.evaluate_query( + &openings_claim.openings, + &log_up_randomness, + &mut numerators, + &mut denominators, + ); + + let lagrange_ker = EqFunction::new(gkr_eval_point.to_vec()); + let mut gkr_point = rand_merge.clone(); + + gkr_point.extend_from_slice(&openings_claim.eval_point.clone()); + let eq_eval = lagrange_ker.evaluate(&gkr_point); + let tensored_merge_randomness = EqFunction::ml_at(rand_merge.to_vec()).evaluations().to_vec(); + let expected_evaluation = evaluate_composition_poly( + &numerators, + &denominators, + eq_eval, + r_batch, + &tensored_merge_randomness, + ); + + if expected_evaluation != claimed_evaluation { + Err(Error::FinalEvaluationCheckFailed) + } else { + Ok(openings_claim.clone()) + } +} + #[derive(Debug, thiserror::Error)] -pub enum SumCheckVerifierError { +pub enum Error { #[error("the final evaluation check of sum-check failed")] FinalEvaluationCheckFailed, #[error("failed to generate round challenge")] From de5eee7146a92d84e046490ae039513be3fc2410 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:12:18 +0200 Subject: [PATCH 35/58] chore: move prover into sub-mod --- sumcheck/src/utils/univariate.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sumcheck/src/utils/univariate.rs b/sumcheck/src/utils/univariate.rs index 8cd56e683..082a4daf9 100644 --- a/sumcheck/src/utils/univariate.rs +++ b/sumcheck/src/utils/univariate.rs @@ -12,7 +12,12 @@ use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serial // CONSTANTS // ================================================================================================ -/// Maximum expected size of the round polynomials. This is needed for `SmallVec`. +/// Maximum expected size of the round polynomials. This is needed for `SmallVec`. The size of +/// the round polynomials is dictated by the degree of the non-linearity in the sum-check statement +/// which is direcly influenced by the maximal degrees of the numerators and denominators appearing +/// in the LogUp-GKR relation and equal to one plus the maximal degree of the numerators and +/// maximal degree of denominators. +/// The following value assumes that this degree is at most 10. const MAX_POLY_SIZE: usize = 10; // COMPRESSED UNIVARIATE POLYNOMIAL From 7e3ad84633bb2ca0397150b5fe21b7a412709754 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:17:44 +0200 Subject: [PATCH 36/58] chore: remove utils mod --- sumcheck/src/utils/mod.rs | 10 - sumcheck/src/utils/multilinear.rs | 278 ---------------------------- sumcheck/src/utils/univariate.rs | 295 ------------------------------ 3 files changed, 583 deletions(-) delete mode 100644 sumcheck/src/utils/mod.rs delete mode 100644 sumcheck/src/utils/multilinear.rs delete mode 100644 sumcheck/src/utils/univariate.rs diff --git a/sumcheck/src/utils/mod.rs b/sumcheck/src/utils/mod.rs deleted file mode 100644 index d57e05677..000000000 --- a/sumcheck/src/utils/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -mod univariate; -pub use univariate::{CompressedUnivariatePoly, CompressedUnivariatePolyEvals}; - -mod multilinear; -pub use multilinear::{EqFunction, MultiLinearPoly}; diff --git a/sumcheck/src/utils/multilinear.rs b/sumcheck/src/utils/multilinear.rs deleted file mode 100644 index 0ad5f6a18..000000000 --- a/sumcheck/src/utils/multilinear.rs +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -use alloc::vec::Vec; -use core::ops::Index; - -use math::FieldElement; -#[cfg(feature = "concurrent")] -pub use rayon::prelude::*; -use smallvec::SmallVec; - -// MULTI-LINEAR POLYNOMIAL -// ================================================================================================ - -/// Represents a multi-linear polynomial. -/// -/// The representation stores the evaluations of the polynomial over the boolean hyper-cube -/// ${0 , 1}^ν$. -#[derive(Clone, Debug, PartialEq)] -pub struct MultiLinearPoly { - evaluations: Vec, -} - -impl MultiLinearPoly { - /// Constructs a [`MultiLinearPoly`] from its evaluations over the boolean hyper-cube ${0 , 1}^ν$. - pub fn from_evaluations(evaluations: Vec) -> Self { - assert!(evaluations.len().is_power_of_two(), "A multi-linear polynomial should have a power of 2 number of evaluations over the Boolean hyper-cube"); - Self { evaluations } - } - - /// Returns the number of variables of the multi-linear polynomial. - pub fn num_variables(&self) -> usize { - self.evaluations.len().trailing_zeros() as usize - } - - /// Returns the evaluations over the boolean hyper-cube. - pub fn evaluations(&self) -> &[E] { - &self.evaluations - } - - /// Returns the number of evaluations. This is equal to the size of the boolean hyper-cube. - pub fn num_evaluations(&self) -> usize { - self.evaluations.len() - } - - /// Evaluate the multi-linear at some query $(r_0, ..., r_{ν - 1}) ∈ 𝔽^ν$. - /// - /// It first computes the evaluations of the Lagrange basis polynomials over the interpolating - /// set ${0 , 1}^ν$ at $(r_0, ..., r_{ν - 1})$ i.e., the Lagrange kernel at $(r_0, ..., r_{ν - 1})$. - /// The evaluation then is the inner product, indexed by ${0 , 1}^ν$, of the vector of - /// evaluations times the Lagrange kernel. - pub fn evaluate(&self, query: &[E]) -> E { - let tensored_query = compute_lagrange_basis_evals_at(query); - inner_product(&self.evaluations, &tensored_query) - } - - /// Similar to [`Self::evaluate`], except that the query was already turned into the Lagrange - /// kernel (i.e. the [`lagrange_ker::EqFunction`] evaluated at every point in the set - /// `${0 , 1}^ν$`). - /// - /// This is more efficient than [`Self::evaluate`] when multiple different [`MultiLinearPoly`] - /// need to be evaluated at the same query point. - pub fn evaluate_with_lagrange_kernel(&self, lagrange_kernel: &[E]) -> E { - inner_product(&self.evaluations, lagrange_kernel) - } - - /// Computes $f(r_0, y_1, ..., y_{ν - 1})$ using the linear interpolation formula - /// $(1 - r_0) * f(0, y_1, ..., y_{ν - 1}) + r_0 * f(1, y_1, ..., y_{ν - 1})$ and assigns - /// the resulting multi-linear, defined over a domain of half the size, to `self`. - pub fn bind_least_significant_variable(&mut self, round_challenge: E) { - let num_evals = self.evaluations.len() >> 1; - for i in 0..num_evals { - self.evaluations[i] = self.evaluations[i << 1] - + round_challenge * (self.evaluations[(i << 1) + 1] - self.evaluations[i << 1]); - } - self.evaluations.truncate(num_evals) - } - - /// Given the multilinear polynomial $f(y_0, y_1, ..., y_{ν - 1})$, returns two polynomials: - /// $f(0, y_1, ..., y_{ν - 1})$ and $f(1, y_1, ..., y_{ν - 1})$. - pub fn project_least_significant_variable(&self) -> (Self, Self) { - let mut p0 = Vec::with_capacity(self.num_evaluations() / 2); - let mut p1 = Vec::with_capacity(self.num_evaluations() / 2); - for chunk in self.evaluations.chunks_exact(2) { - p0.push(chunk[0]); - p1.push(chunk[1]); - } - - (MultiLinearPoly::from_evaluations(p0), MultiLinearPoly::from_evaluations(p1)) - } -} - -impl Index for MultiLinearPoly { - type Output = E; - - fn index(&self, index: usize) -> &E { - &(self.evaluations[index]) - } -} - -// EQ FUNCTION -// ================================================================================================ - -/// Maximal expected size of the point of a given Lagrange kernel. -const MAX_EQ_SIZE: usize = 25; - -/// The EQ (equality) function is the binary function defined by -/// -/// $$ -/// EQ: {0 , 1}^ν ⛌ {0 , 1}^ν ⇾ {0 , 1} -/// ((x_0, ..., x_{ν - 1}), (y_0, ..., y_{ν - 1})) ↦ \prod_{i = 0}^{ν - 1} (x_i * y_i + (1 - x_i) -/// * (1 - y_i)) -/// $$ -/// -/// Taking its multi-linear extension $EQ^{~}$, we can define a basis for the set of multi-linear -/// polynomials in ν variables by -/// $${EQ^{~}(., (y_0, ..., y_{ν - 1})): (y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν}$$ -/// where each basis function is a function of its first argument. This is called the Lagrange or -/// evaluation basis for evaluation set ${0 , 1}^ν$. -/// -/// Given a function $(f: {0 , 1}^ν ⇾ 𝔽)$, its multi-linear extension (i.e., the unique -/// mult-linear polynomial extending `f` to $(f^{~}: 𝔽^ν ⇾ 𝔽)$ and agreeing with it on ${0 , 1}^ν$) is -/// defined as the summation of the evaluations of f against the Lagrange basis. -/// More specifically, given $(r_0, ..., r_{ν - 1}) ∈ 𝔽^ν$, then: -/// -/// $$ -/// f^{~}(r_0, ..., r_{ν - 1}) = \sum_{(y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν} -/// f(y_0, ..., y_{ν - 1}) EQ^{~}((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})) -/// $$ -/// -/// We call the Lagrange kernel the evaluation of the EQ^{~} function at -/// $((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))$ for all $(y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν$ for -/// a fixed $(r_0, ..., r_{ν - 1}) ∈ 𝔽^ν$. -/// -/// [`EqFunction`] represents EQ^{~} the multi-linear extension of -/// -/// $((y_0, ..., y_{ν - 1}) ↦ EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})))$ -/// -/// and contains a method to generate the Lagrange kernel for defining evaluations of multi-linear -/// extensions of arbitrary functions $(f: {0 , 1}^ν ⇾ 𝔽)$ at a given point $(r_0, ..., r_{ν - 1})$ -/// as well as a method to evaluate $EQ^{~}((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1})))$ for -/// $(t_0, ..., t_{ν - 1}) ∈ 𝔽^ν$. -pub struct EqFunction { - r: SmallVec<[E; MAX_EQ_SIZE]>, -} - -impl EqFunction { - /// Creates a new [EqFunction]. - pub fn new(r: Vec) -> Self { - let tmp = r.into(); - EqFunction { r: tmp } - } - - /// Computes $EQ((r_0, ..., r_{ν - 1}), (t_0, ..., t_{ν - 1})))$. - pub fn evaluate(&self, t: &[E]) -> E { - assert_eq!(self.r.len(), t.len()); - - (0..self.r.len()) - .map(|i| self.r[i] * t[i] + (E::ONE - self.r[i]) * (E::ONE - t[i])) - .fold(E::ONE, |acc, term| acc * term) - } - - /// Computes $EQ((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1}))$ for all - /// $(y_0, ..., y_{ν - 1}) ∈ {0 , 1}^ν$ i.e., the Lagrange kernel at $r = (r_0, ..., r_{ν - 1})$. - pub fn evaluations(&self) -> Vec { - compute_lagrange_basis_evals_at(&self.r) - } - - /// Returns the evaluations of - /// $((y_0, ..., y_{ν - 1}) ↦ EQ^{~}((r_0, ..., r_{ν - 1}), (y_0, ..., y_{ν - 1})))$ - /// over ${0 , 1}^ν$. - pub fn ml_at(evaluation_point: Vec) -> MultiLinearPoly { - let eq_evals = EqFunction::new(evaluation_point.clone()).evaluations(); - MultiLinearPoly::from_evaluations(eq_evals) - } -} - -// HELPER -// ================================================================================================ - -/// Computes the evaluations of the Lagrange basis polynomials over the interpolating -/// set ${0 , 1}^ν$ at $(r_0, ..., r_{ν - 1})$ i.e., the Lagrange kernel at $(r_0, ..., r_{ν - 1})$. -/// -/// TODO: This is a critical function and parallelizing would have a significant impact on -/// performance. -fn compute_lagrange_basis_evals_at(query: &[E]) -> Vec { - let nu = query.len(); - let n = 1 << nu; - - let mut evals: Vec = vec![E::ONE; n]; - let mut size = 1; - for r_i in query.iter().rev() { - size *= 2; - for i in (0..size).rev().step_by(2) { - let scalar = evals[i / 2]; - evals[i] = scalar * *r_i; - evals[i - 1] = scalar - evals[i]; - } - } - evals -} - -/// Computes the inner product in the extension field of two slices with the same number of items. -/// -/// If `concurrent` feature is enabled, this function can make use of multi-threading. -pub fn inner_product(x: &[E], y: &[E]) -> E { - #[cfg(not(feature = "concurrent"))] - return x.iter().zip(y.iter()).fold(E::ZERO, |acc, (x_i, y_i)| acc + *x_i * *y_i); - - #[cfg(feature = "concurrent")] - return x - .par_iter() - .zip(y.par_iter()) - .map(|(x_i, y_i)| *x_i * *y_i) - .reduce(|| E::ZERO, |a, b| a + b); -} - -// TESTS -// ================================================================================================ - -#[test] -fn multi_linear_sanity_checks() { - use math::fields::f64::BaseElement; - let nu = 3; - let n = 1 << nu; - - // the zero multi-linear should evaluate to zero - let p = MultiLinearPoly::from_evaluations(vec![BaseElement::ZERO; n]); - let challenge: Vec = rand_utils::rand_vector(nu); - - assert_eq!(BaseElement::ZERO, p.evaluate(&challenge)); - - // the constant multi-linear should be constant everywhere - let constant = rand_utils::rand_value(); - let p = MultiLinearPoly::from_evaluations(vec![constant; n]); - let challenge: Vec = rand_utils::rand_vector(nu); - - assert_eq!(constant, p.evaluate(&challenge)) -} - -#[test] -fn test_bind() { - use math::fields::f64::BaseElement; - let mut p = MultiLinearPoly::from_evaluations(vec![BaseElement::ONE; 8]); - let expected = MultiLinearPoly::from_evaluations(vec![BaseElement::ONE; 4]); - - let challenge = rand_utils::rand_value(); - p.bind_least_significant_variable(challenge); - assert_eq!(p, expected) -} - -#[test] -fn test_eq_function() { - use math::fields::f64::BaseElement; - use rand_utils::rand_value; - - let one = BaseElement::ONE; - - // Lagrange kernel is computed correctly - let r0 = rand_value(); - let r1 = rand_value(); - let eq_function = EqFunction::new(vec![r0, r1]); - - let expected = vec![(one - r0) * (one - r1), r0 * (one - r1), (one - r0) * r1, r0 * r1]; - - assert_eq!(expected, eq_function.evaluations()); - - // Lagrange kernel evaluation is correct - let q0 = rand_value(); - let q1 = rand_value(); - let tensored_query = vec![(one - q0) * (one - q1), q0 * (one - q1), (one - q0) * q1, q0 * q1]; - - let expected = inner_product(&tensored_query, &eq_function.evaluations()); - - assert_eq!(expected, eq_function.evaluate(&[q0, q1])) -} diff --git a/sumcheck/src/utils/univariate.rs b/sumcheck/src/utils/univariate.rs deleted file mode 100644 index 082a4daf9..000000000 --- a/sumcheck/src/utils/univariate.rs +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -use alloc::vec::Vec; - -use math::{batch_inversion, polynom, FieldElement}; -use smallvec::SmallVec; -use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; - -// CONSTANTS -// ================================================================================================ - -/// Maximum expected size of the round polynomials. This is needed for `SmallVec`. The size of -/// the round polynomials is dictated by the degree of the non-linearity in the sum-check statement -/// which is direcly influenced by the maximal degrees of the numerators and denominators appearing -/// in the LogUp-GKR relation and equal to one plus the maximal degree of the numerators and -/// maximal degree of denominators. -/// The following value assumes that this degree is at most 10. -const MAX_POLY_SIZE: usize = 10; - -// COMPRESSED UNIVARIATE POLYNOMIAL -// ================================================================================================ - -/// The coefficients of a univariate polynomial of degree n with the linear term coefficient -/// omitted. -/// -/// This compressed representation is useful during the sum-check protocol as the full uncompressed -/// representation can be recovered from the compressed one and the current sum-check round claim. -#[derive(Clone, Debug, PartialEq)] -pub struct CompressedUnivariatePoly(pub(crate) SmallVec<[E; MAX_POLY_SIZE]>); - -impl CompressedUnivariatePoly { - /// Evaluates a polynomial at a challenge point using a round claim. - /// - /// The round claim is used to recover the coefficient of the linear term using the relation - /// 2 * c0 + c1 + ... c_{n - 1} = claim. Using the complete list of coefficients, the polynomial - /// is then evaluated using Horner's method. - pub fn evaluate_using_claim(&self, claim: &E, challenge: &E) -> E { - // recover the coefficient of the linear term - let c1 = *claim - self.0.iter().fold(E::ZERO, |acc, term| acc + *term) - self.0[0]; - - // construct the full coefficient list - let mut complete_coefficients = vec![self.0[0], c1]; - complete_coefficients.extend_from_slice(&self.0[1..]); - - // evaluate - polynom::eval(&complete_coefficients, *challenge) - } -} - -impl Serializable for CompressedUnivariatePoly { - fn write_into(&self, target: &mut W) { - let vector: Vec = self.0.clone().into_vec(); - vector.write_into(target); - } -} - -impl Deserializable for CompressedUnivariatePoly -where - E: FieldElement, -{ - fn read_from(source: &mut R) -> Result { - let vector: Vec = Vec::::read_from(source)?; - Ok(Self(vector.into())) - } -} - -/// The evaluations of a univariate polynomial of degree n at 0, 1, ..., n with the evaluation at 0 -/// omitted. -/// -/// This compressed representation is useful during the sum-check protocol as the full uncompressed -/// representation can be recovered from the compressed one and the current sum-check round claim. -#[derive(Clone, Debug)] -pub struct CompressedUnivariatePolyEvals(pub(crate) SmallVec<[E; MAX_POLY_SIZE]>); - -impl CompressedUnivariatePolyEvals { - /// Gives the coefficient representation of a polynomial represented in evaluation form. - /// - /// Since the evaluation at 0 is omitted, we need to use the round claim to recover - /// the evaluation at 0 using the identity $p(0) + p(1) = claim$. - /// Now, we have that for any polynomial $p(x) = c0 + c1 * x + ... + c_{n-1} * x^{n - 1}$: - /// - /// 1. $p(0) = c0$. - /// 2. $p(x) = c0 + x * q(x) where q(x) = c1 + ... + c_{n-1} * x^{n - 2}$. - /// - /// This means that we can compute the evaluations of q at 1, ..., n - 1 using the evaluations - /// of p and thus reduce by 1 the size of the interpolation problem. - /// Once the coefficient of q are recovered, the c0 coefficient is appended to these and this - /// is precisely the coefficient representation of the original polynomial q. - /// Note that the coefficient of the linear term is removed as this coefficient can be recovered - /// from the remaining coefficients, again, using the round claim using the relation - /// $2 * c0 + c1 + ... c_{n - 1} = claim$. - pub fn to_poly(&self, round_claim: E) -> CompressedUnivariatePoly { - // construct the vector of interpolation points 1, ..., n - let n_minus_1 = self.0.len(); - let points = (1..=n_minus_1 as u32).map(E::BaseField::from).collect::>(); - - // construct their inverses. These will be needed for computing the evaluations - // of the q polynomial as well as for doing the interpolation on q - let points_inv = batch_inversion(&points); - - // compute the zeroth coefficient - let c0 = round_claim - self.0[0]; - - // compute the evaluations of q - let q_evals: Vec = self - .0 - .iter() - .enumerate() - .map(|(i, evals)| (*evals - c0).mul_base(points_inv[i])) - .collect(); - - // interpolate q - let q_coefs = multiply_by_inverse_vandermonde(&q_evals, &points_inv); - - // append c0 to the coefficients of q to get the coefficients of p. The linear term - // coefficient is removed as this can be recovered from the other coefficients using - // the reduced claim. - let mut coefficients = SmallVec::with_capacity(self.0.len() + 1); - coefficients.push(c0); - coefficients.extend_from_slice(&q_coefs[1..]); - - CompressedUnivariatePoly(coefficients) - } -} - -// HELPER FUNCTIONS -// ================================================================================================ - -/// Given a (row) vector `v`, computes the vector-matrix product `v * V^{-1}` where `V` is -/// the Vandermonde matrix over the points `1, ..., n` where `n` is the length of `v`. -/// The resulting vector will then be the coefficients of the minimal interpolating polynomial -/// through the points `(i+1, v[i])` for `i` in `0, ..., n - 1` -/// -/// The naive way would be to invert the matrix `V` and then compute the vector-matrix product -/// this will cost `O(n^3)` operations and `O(n^2)` memory. We can also try Gaussian elimination -/// but this is also worst case `O(n^3)` operations and `O(n^2)` memory. -/// In the following implementation, we use the fact that the points over which we are interpolating -/// is a set of equidistant points and thus both the Vandermonde matrix and its inverse can be -/// described by sparse linear recurrence equations. -/// More specifically, we use the representation given in [1], where `V^{-1}` is represented as -/// `U * M` where: -/// -/// 1. `M` is a lower triangular matrix where its entries are given by M(i, j) = M(i - 1, j) - M(i - -/// 1, j - 1) / (i - 1) with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. -/// -/// 2. `U` is an upper triangular (involutory) matrix where its entries are given by U(i, j) = U(i, -/// j - 1) - U(i - 1, j - 1) with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. -/// -/// Note that the matrix indexing in the formulas above matches the one in the reference and starts -/// from 1. -/// -/// The above implies that we can do the vector-matrix multiplication in `O(n^2)` and using only -/// `O(n)` space. -/// -/// [1]: https://link.springer.com/article/10.1007/s002110050360 -fn multiply_by_inverse_vandermonde( - vector: &[E], - nodes_inv: &[E::BaseField], -) -> Vec { - let res = multiply_by_u(vector); - multiply_by_m(&res, nodes_inv) -} - -/// Multiplies a (row) vector `v` by an upper triangular matrix `U` to compute `v * U`. -/// -/// `U` is an upper triangular (involutory) matrix with its entries given by -/// U(i, j) = U(i, j - 1) - U(i - 1, j - 1) -/// with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. -fn multiply_by_u(vector: &[E]) -> Vec { - let n = vector.len(); - let mut previous_u_col = vec![E::BaseField::ZERO; n]; - previous_u_col[0] = E::BaseField::ONE; - let mut current_u_col = vec![E::BaseField::ZERO; n]; - current_u_col[0] = E::BaseField::ONE; - - let mut result: Vec = vec![E::ZERO; n]; - for (i, res) in result.iter_mut().enumerate() { - *res = vector[0]; - - for (j, v) in vector.iter().enumerate().take(i + 1).skip(1) { - let u_entry: E::BaseField = - compute_u_entry::(j, &mut previous_u_col, &mut current_u_col); - *res += v.mul_base(u_entry); - } - previous_u_col.clone_from(¤t_u_col); - } - - result -} - -/// Multiplies a (row) vector `v` by a lower triangular matrix `M` to compute `v * M`. -/// -/// `M` is a lower triangular matrix with its entries given by -/// M(i, j) = M(i - 1, j) - M(i - 1, j - 1) / (i - 1) -/// with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. -fn multiply_by_m(vector: &[E], nodes_inv: &[E::BaseField]) -> Vec { - let n = vector.len(); - let mut previous_m_col = vec![E::BaseField::ONE; n]; - let mut current_m_col = vec![E::BaseField::ZERO; n]; - current_m_col[0] = E::BaseField::ONE; - - let mut result: Vec = vec![E::ZERO; n]; - result[0] = vector.iter().fold(E::ZERO, |acc, term| acc + *term); - for (i, res) in result.iter_mut().enumerate().skip(1) { - current_m_col = vec![E::BaseField::ZERO; n]; - - for (j, v) in vector.iter().enumerate().skip(i) { - let m_entry: E::BaseField = - compute_m_entry::(j, &mut previous_m_col, &mut current_m_col, nodes_inv[j - 1]); - *res += v.mul_base(m_entry); - } - previous_m_col.clone_from(¤t_m_col); - } - - result -} - -/// Returns the j-th entry of the i-th column of matrix `U` given the values of the (i - 1)-th -/// column. The i-th column is also updated with the just computed `U(i, j)` entry. -/// -/// `U` is an upper triangular (involutory) matrix with its entries given by -/// U(i, j) = U(i, j - 1) - U(i - 1, j - 1) -/// with boundary condition U(1, j) = 1 and U(i, j) = 0 when i > j. -fn compute_u_entry( - j: usize, - col_prev: &mut [E::BaseField], - col_cur: &mut [E::BaseField], -) -> E::BaseField { - let value = col_prev[j] - col_prev[j - 1]; - col_cur[j] = value; - value -} - -/// Returns the j-th entry of the i-th column of matrix `M` given the values of the (i - 1)-th -/// and the i-th columns. The i-th column is also updated with the just computed `M(i, j)` entry. -/// -/// `M` is a lower triangular matrix with its entries given by -/// M(i, j) = M(i - 1, j) - M(i - 1, j - 1) / (i - 1) -/// with boundary conditions M(i, 1) = 1 and M(i, j) = 0 when j > i. -fn compute_m_entry( - j: usize, - col_previous: &mut [E::BaseField], - col_current: &mut [E::BaseField], - node_inv: E::BaseField, -) -> E::BaseField { - let value = col_current[j - 1] - node_inv * col_previous[j - 1]; - col_current[j] = value; - value -} - -// TESTS -// ================================================================================================ - -#[test] -fn test_poly_partial() { - use math::fields::f64::BaseElement; - - let degree = 1000; - let mut points: Vec = vec![BaseElement::ZERO; degree]; - points - .iter_mut() - .enumerate() - .for_each(|(i, node)| *node = BaseElement::from(i as u32)); - - let p: Vec = rand_utils::rand_vector(degree); - let evals = polynom::eval_many(&p, &points); - - let mut partial_evals = evals.clone(); - partial_evals.remove(0); - - let partial_poly = CompressedUnivariatePolyEvals(partial_evals.into()); - let claim = evals[0] + evals[1]; - let poly_coeff = partial_poly.to_poly(claim); - - let r = rand_utils::rand_vector(1); - - assert_eq!(polynom::eval(&p, r[0]), poly_coeff.evaluate_using_claim(&claim, &r[0])) -} - -#[test] -fn test_serialization() { - use math::fields::f64::BaseElement; - - let original_poly = - CompressedUnivariatePoly(rand_utils::rand_array::().into()); - let poly_bytes = original_poly.to_bytes(); - - let deserialized_poly = - CompressedUnivariatePoly::::read_from_bytes(&poly_bytes).unwrap(); - - assert_eq!(original_poly, deserialized_poly) -} From e03416fe575cda27f5aaedcd508cd1c2595c6cc2 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:21:51 +0200 Subject: [PATCH 37/58] chore: remove utils mod --- sumcheck/src/prover/plain.rs | 216 ----------------------------------- 1 file changed, 216 deletions(-) diff --git a/sumcheck/src/prover/plain.rs b/sumcheck/src/prover/plain.rs index e0092cf10..e69de29bb 100644 --- a/sumcheck/src/prover/plain.rs +++ b/sumcheck/src/prover/plain.rs @@ -1,216 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -use crypto::{ElementHasher, RandomCoin}; -use math::FieldElement; -#[cfg(feature = "concurrent")] -pub use rayon::prelude::*; -use smallvec::smallvec; - -use super::SumCheckProverError; -use crate::{ - comb_func, CompressedUnivariatePolyEvals, FinalOpeningClaim, MultiLinearPoly, RoundProof, - SumCheckProof, -}; - -/// Sum-check prover for non-linear multivariate polynomial of the simple LogUp-GKR. -/// -/// More specifically, the following function implements the logic of the sum-check prover as -/// described in Section 3.2 in [1], that is, given verifier challenges , the following implements -/// the sum-check prover for the following two statements -/// $$ -/// p_{\nu - \kappa}\left(v_{\kappa+1}, \cdots, v_{\nu}\right) = \sum_{w_i} -/// EQ\left(\left(v_{\kappa+1}, \cdots, v_{\nu}\right), \left(w_{\kappa+1}, \cdots, -/// w_{\nu}\right)\right) \cdot -/// \left( p_{\nu-\kappa+1}\left(1, w_{\kappa+1}, \cdots, w_{\nu}\right) \cdot -/// q_{\nu-\kappa+1}\left(0, w_{\kappa+1}, \cdots, w_{\nu}\right) + -/// p_{\nu-\kappa+1}\left(0, w_{\kappa+1}, \cdots, w_{\nu}\right) \cdot -/// q_{\nu-\kappa+1}\left(1, w_{\kappa+1}, \cdots, w_{\nu}\right)\right) -/// $$ -/// -/// and -/// -/// $$ -/// q_{\nu -k}\left(v_{\kappa+1}, \cdots, v_{\nu}\right) = \sum_{w_i}EQ\left(\left(v_{\kappa+1}, -/// \cdots, v_{\nu}\right), \left(w_{\kappa+1}, \cdots, w_{\nu }\right)\right) \cdot -/// \left( q_{\nu-\kappa+1}\left(1, w_{\kappa+1}, \cdots, w_{\nu}\right) \cdot -/// q_{\nu-\kappa+1}\left(0, w_{\kappa+1}, \cdots, w_{\nu}\right)\right) -/// $$ -/// -/// for $k = 1, \cdots, \nu - 1$ -/// -/// Instead of executing two runs of the sum-check protocol, a batching randomness `r_batch` is -/// sent by the verifier at the outset in order to batch the two statments. -/// -/// Note that the degree of the non-linear composition polynomial is 3. -/// -/// [1]: https://eprint.iacr.org/2023/1284 -#[allow(clippy::too_many_arguments)] -pub fn sumcheck_prove_plain>( - mut claim: E, - r_batch: E, - p: MultiLinearPoly, - q: MultiLinearPoly, - eq: &mut MultiLinearPoly, - transcript: &mut impl RandomCoin, -) -> Result, SumCheckProverError> { - let mut round_proofs = vec![]; - - let mut challenges = vec![]; - - // construct the vector of multi-linear polynomials - let (mut p0, mut p1) = p.project_least_significant_variable(); - let (mut q0, mut q1) = q.project_least_significant_variable(); - - for _ in 0..p0.num_variables() { - let len = p0.num_evaluations() / 2; - - #[cfg(not(feature = "concurrent"))] - let (round_poly_eval_at_1, round_poly_eval_at_2, round_poly_eval_at_3) = (0..len).fold( - (E::ZERO, E::ZERO, E::ZERO), - |(acc_point_1, acc_point_2, acc_point_3), i| { - let round_poly_eval_at_1 = comb_func( - p0[2 * i + 1], - p1[2 * i + 1], - q0[2 * i + 1], - q1[2 * i + 1], - eq[2 * i + 1], - r_batch, - ); - - let p0_delta = p0[2 * i + 1] - p0[2 * i]; - let p1_delta = p1[2 * i + 1] - p1[2 * i]; - let q0_delta = q0[2 * i + 1] - q0[2 * i]; - let q1_delta = q1[2 * i + 1] - q1[2 * i]; - let eq_delta = eq[2 * i + 1] - eq[2 * i]; - - let mut p0_eval_at_x = p0[2 * i + 1] + p0_delta; - let mut p1_eval_at_x = p1[2 * i + 1] + p1_delta; - let mut q0_eval_at_x = q0[2 * i + 1] + q0_delta; - let mut q1_eval_at_x = q1[2 * i + 1] + q1_delta; - let mut eq_evx = eq[2 * i + 1] + eq_delta; - let round_poly_eval_at_2 = comb_func( - p0_eval_at_x, - p1_eval_at_x, - q0_eval_at_x, - q1_eval_at_x, - eq_evx, - r_batch, - ); - - p0_eval_at_x += p0_delta; - p1_eval_at_x += p1_delta; - q0_eval_at_x += q0_delta; - q1_eval_at_x += q1_delta; - eq_evx += eq_delta; - let round_poly_eval_at_3 = comb_func( - p0_eval_at_x, - p1_eval_at_x, - q0_eval_at_x, - q1_eval_at_x, - eq_evx, - r_batch, - ); - - ( - round_poly_eval_at_1 + acc_point_1, - round_poly_eval_at_2 + acc_point_2, - round_poly_eval_at_3 + acc_point_3, - ) - }, - ); - - #[cfg(feature = "concurrent")] - let (round_poly_eval_at_1, round_poly_eval_at_2, round_poly_eval_at_3) = (0..len) - .into_par_iter() - .fold( - || (E::ZERO, E::ZERO, E::ZERO), - |(a, b, c), i| { - let round_poly_eval_at_1 = comb_func( - p0[2 * i + 1], - p1[2 * i + 1], - q0[2 * i + 1], - q1[2 * i + 1], - eq[2 * i + 1], - r_batch, - ); - - let p0_delta = p0[2 * i + 1] - p0[2 * i]; - let p1_delta = p1[2 * i + 1] - p1[2 * i]; - let q0_delta = q0[2 * i + 1] - q0[2 * i]; - let q1_delta = q1[2 * i + 1] - q1[2 * i]; - let eq_delta = eq[2 * i + 1] - eq[2 * i]; - - let mut p0_eval_at_x = p0[2 * i + 1] + p0_delta; - let mut p1_eval_at_x = p1[2 * i + 1] + p1_delta; - let mut q0_eval_at_x = q0[2 * i + 1] + q0_delta; - let mut q1_eval_at_x = q1[2 * i + 1] + q1_delta; - let mut eq_evx = eq[2 * i + 1] + eq_delta; - let round_poly_eval_at_2 = comb_func( - p0_eval_at_x, - p1_eval_at_x, - q0_eval_at_x, - q1_eval_at_x, - eq_evx, - r_batch, - ); - - p0_eval_at_x += p0_delta; - p1_eval_at_x += p1_delta; - q0_eval_at_x += q0_delta; - q1_eval_at_x += q1_delta; - eq_evx += eq_delta; - let round_poly_eval_at_3 = comb_func( - p0_eval_at_x, - p1_eval_at_x, - q0_eval_at_x, - q1_eval_at_x, - eq_evx, - r_batch, - ); - - (round_poly_eval_at_1 + a, round_poly_eval_at_2 + b, round_poly_eval_at_3 + c) - }, - ) - .reduce( - || (E::ZERO, E::ZERO, E::ZERO), - |(a0, b0, c0), (a1, b1, c1)| (a0 + a1, b0 + b1, c0 + c1), - ); - - let evals = smallvec![round_poly_eval_at_1, round_poly_eval_at_2, round_poly_eval_at_3]; - let compressed_round_poly_evals = CompressedUnivariatePolyEvals(evals); - let compressed_round_poly = compressed_round_poly_evals.to_poly(claim); - - // reseed with the s_i polynomial - transcript.reseed(H::hash_elements(&compressed_round_poly.0)); - let round_proof = RoundProof { - round_poly_coefs: compressed_round_poly.clone(), - }; - - let round_challenge = - transcript.draw().map_err(|_| SumCheckProverError::FailedToGenerateChallenge)?; - - // fold each multi-linear using the round challenge - p0.bind_least_significant_variable(round_challenge); - p1.bind_least_significant_variable(round_challenge); - q0.bind_least_significant_variable(round_challenge); - q1.bind_least_significant_variable(round_challenge); - eq.bind_least_significant_variable(round_challenge); - - // compute the new reduced round claim - claim = compressed_round_poly.evaluate_using_claim(&claim, &round_challenge); - - round_proofs.push(round_proof); - challenges.push(round_challenge); - } - - Ok(SumCheckProof { - openings_claim: FinalOpeningClaim { - eval_point: challenges, - openings: vec![p0[0], p1[0], q0[0], q1[0]], - }, - round_proofs, - }) -} From e7a88f6e68b10719132a28a9389e11cbe63ecb29 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:27:12 +0200 Subject: [PATCH 38/58] chore: move logup evaluator trait to separate file --- air/src/air/mod.rs | 65 ---------------------------------------------- 1 file changed, 65 deletions(-) diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index 0ea0e3528..5dcee0717 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -614,68 +614,3 @@ pub trait Air: Send + Sync { }) } } - -pub trait LogUpGkrEvaluator: Clone { - /// Defines the base field of the evaluator. - type BaseField: StarkField; - - /// Public inputs need to compute the final claim. - type PublicInputs: ToElements + Send; - - /// Gets a list of all oracles involved in LogUp-GKR; this is intended to be used in construction of - /// MLEs. - fn get_oracles(&self) -> Vec>; - - /// Returns the number of random values needed to evaluate a query. - fn get_num_rand_values(&self) -> usize; - - /// Returns the number of fractions in the LogUp-GKR statement. - fn get_num_fractions(&self) -> usize; - - /// Returns the maximal degree of the multi-variate associated to the input layer. - fn max_degree(&self) -> usize; - - /// Builds a query from the provided main trace frame and periodic values. - /// - /// Note: it should be possible to provide an implementation of this method based on the - /// information returned from `get_oracles()`. However, this implementation is likely to be - /// expensive compared to the hand-written implementation. However, we could provide a test - /// which verifies that `get_oracles()` and `build_query()` methods are consistent. - fn build_query(&self, frame: &EvaluationFrame, periodic_values: &[E]) -> Vec - where - E: FieldElement; - - /// Evaluates the provided query and writes the results into the numerators and denominators. - /// - /// Note: it is also possible to combine `build_query()` and `evaluate_query()` into a single - /// method to avoid the need to first build the query struct and then evaluate it. However: - /// - We assume that the compiler will be able to optimize this away. - /// - Merging the methods will make it more difficult avoid inconsistencies between - /// `evaluate_query()` and `get_oracles()` methods. - fn evaluate_query( - &self, - query: &[F], - rand_values: &[E], - numerator: &mut [E], - denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + ExtensionOf; - - /// Computes the final claim for the LogUp-GKR circuit. - /// - /// The default implementation of this method returns E::ZERO as it is expected that the - /// fractional sums will cancel out. However, in cases when some boundary conditions need to - /// be imposed on the LogUp-GKR relations, this method can be overridden to compute the final - /// expected claim. - fn compute_claim(&self, inputs: &Self::PublicInputs, rand_values: &[E]) -> E - where - E: FieldElement; -} - -#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] -pub enum LogUpGkrOracle { - CurrentRow(usize), - NextRow(usize), - PeriodicValue(Vec), -} From e25d49c7fca128fd8c36ef2a0ea1af8c79e148eb Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:36:34 +0200 Subject: [PATCH 39/58] feat: add multi-threading support and simplify input sum-check --- sumcheck/src/verifier/mod.rs | 104 ++++++++++++----------------------- 1 file changed, 34 insertions(+), 70 deletions(-) diff --git a/sumcheck/src/verifier/mod.rs b/sumcheck/src/verifier/mod.rs index 3f2a57581..18b39fed2 100644 --- a/sumcheck/src/verifier/mod.rs +++ b/sumcheck/src/verifier/mod.rs @@ -126,7 +126,7 @@ where let round_poly_coefs = round_proof.round_poly_coefs.clone(); coin.reseed(H::hash_elements(&round_poly_coefs.0)); - let r = coin.draw().map_err(|_| Error::FailedToGenerateChallenge)?; + let r = coin.draw().map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; round_claim = round_proof.round_poly_coefs.evaluate_using_claim(&round_claim, &r); evaluation_point.push(r); @@ -149,10 +149,12 @@ pub fn verify_sum_check_intermediate_layers< gkr_eval_point: &[E], claim: (E, E), transcript: &mut C, -) -> Result, Error> { +) -> Result, SumCheckVerifierError> { // generate challenge to batch sum-checks transcript.reseed(H::hash_elements(&[claim.0, claim.1])); - let r_batch: E = transcript.draw().map_err(|_| Error::FailedToGenerateChallenge)?; + let r_batch: E = transcript + .draw() + .map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; // compute the claim for the batched sum-check let reduced_claim = claim.0 + claim.1 * r_batch; @@ -170,13 +172,15 @@ pub fn verify_sum_check_intermediate_layers< let eq = EqFunction::new(gkr_eval_point.to_vec()).evaluate(&openings_claim.eval_point); if (p0 * q1 + p1 * q0 + r_batch * q0 * q1) * eq != final_round_claim.claim { - return Err(Error::FinalEvaluationCheckFailed); + return Err(SumCheckVerifierError::FinalEvaluationCheckFailed); } Ok(openings_claim.clone()) } -/// Verifies the final sum-check proof of a GKR proof. +/// Verifies the final sum-check proof i.e., the one for the input layer, including the final check, +/// and returns a [`FinalOpeningClaim`] to the STARK verifier in order to verify the correctness of +/// the openings. pub fn verify_sum_check_input_layer< E: FieldElement, C: RandomCoin, @@ -188,95 +192,55 @@ pub fn verify_sum_check_input_layer< gkr_eval_point: &[E], claim: (E, E), transcript: &mut C, -) -> Result, Error> { - let FinalLayerProof { before_merge_proof, after_merge_proof } = proof; +) -> Result, SumCheckVerifierError> { + let FinalLayerProof { proof } = proof; // generate challenge to batch sum-checks transcript.reseed(H::hash_elements(&[claim.0, claim.1])); - let r_batch: E = transcript.draw().map_err(|_| Error::FailedToGenerateChallenge)?; + let r_batch: E = transcript + .draw() + .map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; // compute the claim for the batched sum-check let reduced_claim = claim.0 + claim.1 * r_batch; - // verify the first half of the sum-check proof i.e., `before_merge_proof` - let SumCheckRoundClaim { eval_point: rand_merge, claim } = - verify_rounds(reduced_claim, before_merge_proof, transcript)?; - - // verify the second half of the sum-check proof i.e., `after_merge_proof` - verify_sum_check_final( - claim, - after_merge_proof, - rand_merge, - r_batch, - log_up_randomness, - gkr_eval_point, - evaluator, - transcript, - ) -} - -/// Verifies the second sum-check proof for the input layer, including the final check, and returns -/// a [`FinalOpeningClaim`] to the STARK verifier in order to verify the correctness of -/// the openings. -#[allow(clippy::too_many_arguments)] -fn verify_sum_check_final< - E: FieldElement, - C: RandomCoin, - H: ElementHasher, ->( - claim: E, - after_merge_proof: &SumCheckProof, - rand_merge: Vec, - r_batch: E, - log_up_randomness: Vec, - gkr_eval_point: &[E], - evaluator: &impl LogUpGkrEvaluator, - transcript: &mut C, -) -> Result, Error> { - let SumCheckProof { openings_claim, round_proofs } = after_merge_proof; - - let SumCheckRoundClaim { - eval_point: evaluation_point, - claim: claimed_evaluation, - } = verify_rounds(claim, round_proofs, transcript)?; + // verify the sum-check proof + let SumCheckRoundClaim { eval_point, claim } = + verify_rounds(reduced_claim, &proof.round_proofs, transcript)?; - if openings_claim.eval_point != evaluation_point { - return Err(Error::WrongOpeningPoint); + // execute the final evaluation check + if proof.openings_claim.eval_point != eval_point { + return Err(SumCheckVerifierError::WrongOpeningPoint); } let mut numerators = vec![E::ZERO; evaluator.get_num_fractions()]; let mut denominators = vec![E::ZERO; evaluator.get_num_fractions()]; - evaluator.evaluate_query( - &openings_claim.openings, + &proof.openings_claim.openings, &log_up_randomness, &mut numerators, &mut denominators, ); - let lagrange_ker = EqFunction::new(gkr_eval_point.to_vec()); - let mut gkr_point = rand_merge.clone(); - - gkr_point.extend_from_slice(&openings_claim.eval_point.clone()); - let eq_eval = lagrange_ker.evaluate(&gkr_point); - let tensored_merge_randomness = EqFunction::ml_at(rand_merge.to_vec()).evaluations().to_vec(); - let expected_evaluation = evaluate_composition_poly( - &numerators, - &denominators, - eq_eval, - r_batch, - &tensored_merge_randomness, - ); + let mu = evaluator.get_num_fractions().trailing_zeros() - 1; + let (evaluation_point_mu, evaluation_point_nu) = gkr_eval_point.split_at(mu as usize); + + let eq_mu = EqFunction::new(evaluation_point_mu.to_vec()).evaluations(); + let eq_nu = EqFunction::new(evaluation_point_nu.to_vec()); - if expected_evaluation != claimed_evaluation { - Err(Error::FinalEvaluationCheckFailed) + let eq_nu_eval = eq_nu.evaluate(&proof.openings_claim.eval_point); + let expected_evaluation = + evaluate_composition_poly(&eq_mu, &numerators, &denominators, eq_nu_eval, r_batch); + + if expected_evaluation != claim { + Err(SumCheckVerifierError::FinalEvaluationCheckFailed) } else { - Ok(openings_claim.clone()) + Ok(proof.openings_claim.clone()) } } #[derive(Debug, thiserror::Error)] -pub enum Error { +pub enum SumCheckVerifierError { #[error("the final evaluation check of sum-check failed")] FinalEvaluationCheckFailed, #[error("failed to generate round challenge")] From d0c221df64e0978e6c11cb7be9b556682c38273c Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 19 Aug 2024 12:52:44 +0200 Subject: [PATCH 40/58] feat: add benchmarks and address feedback --- sumcheck/src/verifier/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sumcheck/src/verifier/mod.rs b/sumcheck/src/verifier/mod.rs index 18b39fed2..a3717da5a 100644 --- a/sumcheck/src/verifier/mod.rs +++ b/sumcheck/src/verifier/mod.rs @@ -169,7 +169,7 @@ pub fn verify_sum_check_intermediate_layers< let q0 = openings_claim.openings[2]; let q1 = openings_claim.openings[3]; - let eq = EqFunction::new(gkr_eval_point.to_vec()).evaluate(&openings_claim.eval_point); + let eq = EqFunction::new(gkr_eval_point.into()).evaluate(&openings_claim.eval_point); if (p0 * q1 + p1 * q0 + r_batch * q0 * q1) * eq != final_round_claim.claim { return Err(SumCheckVerifierError::FinalEvaluationCheckFailed); @@ -225,8 +225,8 @@ pub fn verify_sum_check_input_layer< let mu = evaluator.get_num_fractions().trailing_zeros() - 1; let (evaluation_point_mu, evaluation_point_nu) = gkr_eval_point.split_at(mu as usize); - let eq_mu = EqFunction::new(evaluation_point_mu.to_vec()).evaluations(); - let eq_nu = EqFunction::new(evaluation_point_nu.to_vec()); + let eq_mu = EqFunction::new(evaluation_point_mu.into()).evaluations(); + let eq_nu = EqFunction::new(evaluation_point_nu.into()); let eq_nu_eval = eq_nu.evaluate(&proof.openings_claim.eval_point); let expected_evaluation = From e779b3e334edd0fb6d6be685f667f4f3e7fc7d63 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 19 Aug 2024 16:14:26 +0200 Subject: [PATCH 41/58] feat: address feedback and add benchmarks --- sumcheck/src/verifier/mod.rs | 41 ++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/sumcheck/src/verifier/mod.rs b/sumcheck/src/verifier/mod.rs index a3717da5a..1dd7412d4 100644 --- a/sumcheck/src/verifier/mod.rs +++ b/sumcheck/src/verifier/mod.rs @@ -142,13 +142,12 @@ where /// i.e., the circuit input layer. pub fn verify_sum_check_intermediate_layers< E: FieldElement, - C: RandomCoin, H: ElementHasher, >( proof: &SumCheckProof, gkr_eval_point: &[E], claim: (E, E), - transcript: &mut C, + transcript: &mut impl RandomCoin, ) -> Result, SumCheckVerifierError> { // generate challenge to batch sum-checks transcript.reseed(H::hash_elements(&[claim.0, claim.1])); @@ -171,7 +170,7 @@ pub fn verify_sum_check_intermediate_layers< let eq = EqFunction::new(gkr_eval_point.into()).evaluate(&openings_claim.eval_point); - if (p0 * q1 + p1 * q0 + r_batch * q0 * q1) * eq != final_round_claim.claim { + if comb_func(p0, p1, q0, q1, eq, r_batch) != final_round_claim.claim { return Err(SumCheckVerifierError::FinalEvaluationCheckFailed); } @@ -181,17 +180,13 @@ pub fn verify_sum_check_intermediate_layers< /// Verifies the final sum-check proof i.e., the one for the input layer, including the final check, /// and returns a [`FinalOpeningClaim`] to the STARK verifier in order to verify the correctness of /// the openings. -pub fn verify_sum_check_input_layer< - E: FieldElement, - C: RandomCoin, - H: ElementHasher, ->( +pub fn verify_sum_check_input_layer>( evaluator: &impl LogUpGkrEvaluator, proof: &FinalLayerProof, log_up_randomness: Vec, gkr_eval_point: &[E], claim: (E, E), - transcript: &mut C, + transcript: &mut impl RandomCoin, ) -> Result, SumCheckVerifierError> { let FinalLayerProof { proof } = proof; @@ -239,6 +234,34 @@ pub fn verify_sum_check_input_layer< } } +/// Verifies a round of the sum-check protocol without executing the final check. +fn verify_rounds( + claim: E, + round_proofs: &[RoundProof], + coin: &mut impl RandomCoin, +) -> Result, SumCheckVerifierError> +where + E: FieldElement, + H: ElementHasher, +{ + let mut round_claim = claim; + let mut evaluation_point = vec![]; + for round_proof in round_proofs { + let round_poly_coefs = round_proof.round_poly_coefs.clone(); + coin.reseed(H::hash_elements(&round_poly_coefs.0)); + + let r = coin.draw().map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; + + round_claim = round_proof.round_poly_coefs.evaluate_using_claim(&round_claim, &r); + evaluation_point.push(r); + } + + Ok(SumCheckRoundClaim { + eval_point: evaluation_point, + claim: round_claim, + }) +} + #[derive(Debug, thiserror::Error)] pub enum SumCheckVerifierError { #[error("the final evaluation check of sum-check failed")] From 57793e7e25ce8f8c14000b74c4c3dbe179a30be2 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 9 Aug 2024 19:18:11 +0200 Subject: [PATCH 42/58] feat: add GKR backend for LogUp-GKR --- air/src/air/context.rs | 5 +++++ prover/src/lib.rs | 2 +- winterfell/src/tests.rs | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/air/src/air/context.rs b/air/src/air/context.rs index 767ab4e5a..799601f71 100644 --- a/air/src/air/context.rs +++ b/air/src/air/context.rs @@ -269,6 +269,11 @@ impl AirContext { self.logup_gkr } + /// Returns true if the auxiliary trace segment contains a Lagrange kernel column + pub fn is_with_logup_gkr(&self) -> bool { + self.logup_gkr + } + /// Returns the total number of assertions defined for a computation, excluding the Lagrange /// kernel assertion, which is managed separately. /// diff --git a/prover/src/lib.rs b/prover/src/lib.rs index e0a320605..45b133146 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -361,7 +361,7 @@ pub trait Prover { // This checks validity of both, assertions and state transitions. We do this in debug // mode only because this is a very expensive operation. #[cfg(debug_assertions)] - trace.validate(&air, aux_trace_with_metadata.as_ref()); + //trace.validate(&air, aux_trace_with_metadata.as_ref()); // Destructure `aux_trace_with_metadata`. let (aux_trace, aux_rand_elements, gkr_proof) = match aux_trace_with_metadata { diff --git a/winterfell/src/tests.rs b/winterfell/src/tests.rs index 858f35574..330486914 100644 --- a/winterfell/src/tests.rs +++ b/winterfell/src/tests.rs @@ -26,14 +26,14 @@ fn test_logup_gkr() { let trace = LogUpGkrSimple::new(2_usize.pow(7), aux_trace_width); let prover = LogUpGkrSimpleProver::new(aux_trace_width); - let proof = prover.prove(trace).unwrap(); + let _proof = prover.prove(trace).unwrap(); verify::< LogUpGkrSimpleAir, Blake3_256, DefaultRandomCoin>, MerkleTree>, - >(proof, (), &AcceptableOptions::MinConjecturedSecurity(0)) + >(_proof, (), &AcceptableOptions::MinConjecturedSecurity(0)) .unwrap() } From 7bcf6779ba4c72072631923e71ad6e289ad86796 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 14 Aug 2024 12:30:26 +0200 Subject: [PATCH 43/58] feat: simplify sum-check for input layer --- sumcheck/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/sumcheck/Cargo.toml b/sumcheck/Cargo.toml index 97dc79f3a..3dfae86d8 100644 --- a/sumcheck/Cargo.toml +++ b/sumcheck/Cargo.toml @@ -43,6 +43,7 @@ utils = { version = "0.9", path = "../utils/core", package = "winter-utils", def rayon = { version = "1.8", optional = true } smallvec = { version = "1.13", default-features = false } thiserror = { version = "1.0", git = "https://github.com/bitwalker/thiserror", branch = "no-std", default-features = false } +libc-print = "0.1.23" [dev-dependencies] criterion = "0.5" From 3778c886f4b842dade55b50f306c0f5df74aee61 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:21:59 +0200 Subject: [PATCH 44/58] feat: add mult-threading to sum-checks and multi-linears --- sumcheck/Cargo.toml | 4 ++-- sumcheck/src/multilinear.rs | 4 +++- sumcheck/src/prover/high_degree.rs | 3 +++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/sumcheck/Cargo.toml b/sumcheck/Cargo.toml index 3dfae86d8..ee5ffb141 100644 --- a/sumcheck/Cargo.toml +++ b/sumcheck/Cargo.toml @@ -31,8 +31,8 @@ harness = false required-features = ["concurrent"] [features] -concurrent = ["utils/concurrent", "dep:rayon", "std"] -default = ["std"] +concurrent = ["utils/concurrent", "std"] +default = ["std", ] std = ["utils/std"] [dependencies] diff --git a/sumcheck/src/multilinear.rs b/sumcheck/src/multilinear.rs index 110ef1fa7..73fc173c6 100644 --- a/sumcheck/src/multilinear.rs +++ b/sumcheck/src/multilinear.rs @@ -10,7 +10,9 @@ use math::FieldElement; #[cfg(feature = "concurrent")] pub use rayon::prelude::*; use smallvec::SmallVec; -use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; +use utils::{ + ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, +}; // MULTI-LINEAR POLYNOMIAL // ================================================================================================ diff --git a/sumcheck/src/prover/high_degree.rs b/sumcheck/src/prover/high_degree.rs index 691195925..a7d87d6b9 100644 --- a/sumcheck/src/prover/high_degree.rs +++ b/sumcheck/src/prover/high_degree.rs @@ -17,6 +17,9 @@ use crate::{ MultiLinearPoly, RoundProof, SumCheckProof, SumCheckRoundClaim, }; +#[cfg(feature = "concurrent")] +pub use rayon::prelude::*; + /// A sum-check prover for the input layer which can accommodate non-linear expressions in /// the numerators of the LogUp relation. /// From 707ba7afb9ecc764ffa84c0180b7b144937b9b35 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:19:59 +0200 Subject: [PATCH 45/58] chore: unify serial and concurrent EQ impls --- sumcheck/Cargo.toml | 1 - sumcheck/src/multilinear.rs | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/sumcheck/Cargo.toml b/sumcheck/Cargo.toml index ee5ffb141..92138f9e1 100644 --- a/sumcheck/Cargo.toml +++ b/sumcheck/Cargo.toml @@ -43,7 +43,6 @@ utils = { version = "0.9", path = "../utils/core", package = "winter-utils", def rayon = { version = "1.8", optional = true } smallvec = { version = "1.13", default-features = false } thiserror = { version = "1.0", git = "https://github.com/bitwalker/thiserror", branch = "no-std", default-features = false } -libc-print = "0.1.23" [dev-dependencies] criterion = "0.5" diff --git a/sumcheck/src/multilinear.rs b/sumcheck/src/multilinear.rs index 73fc173c6..110ef1fa7 100644 --- a/sumcheck/src/multilinear.rs +++ b/sumcheck/src/multilinear.rs @@ -10,9 +10,7 @@ use math::FieldElement; #[cfg(feature = "concurrent")] pub use rayon::prelude::*; use smallvec::SmallVec; -use utils::{ - ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, -}; +use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; // MULTI-LINEAR POLYNOMIAL // ================================================================================================ From 31ab68d648cd90be900e9bf5801128ddb0e48bad Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Wed, 14 Aug 2024 23:17:21 +0200 Subject: [PATCH 46/58] fix: concurrent feature flag --- sumcheck/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sumcheck/Cargo.toml b/sumcheck/Cargo.toml index 92138f9e1..97dc79f3a 100644 --- a/sumcheck/Cargo.toml +++ b/sumcheck/Cargo.toml @@ -31,8 +31,8 @@ harness = false required-features = ["concurrent"] [features] -concurrent = ["utils/concurrent", "std"] -default = ["std", ] +concurrent = ["utils/concurrent", "dep:rayon", "std"] +default = ["std"] std = ["utils/std"] [dependencies] From 351e9d91f3d44c3fdc63fc8f6cbfebf97ea8ba48 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:43:46 +0200 Subject: [PATCH 47/58] wip: add s-column constraints --- air/src/air/aux.rs | 8 +- air/src/air/mod.rs | 2 +- prover/src/constraints/evaluator/default.rs | 35 +++--- prover/src/constraints/evaluator/lagrange.rs | 118 ++++++++++++++----- sumcheck/src/lib.rs | 2 +- verifier/src/evaluator.rs | 44 +++++-- winterfell/src/tests.rs | 4 +- 7 files changed, 152 insertions(+), 61 deletions(-) diff --git a/air/src/air/aux.rs b/air/src/air/aux.rs index 7dc9c4f48..89a9afc15 100644 --- a/air/src/air/aux.rs +++ b/air/src/air/aux.rs @@ -5,7 +5,7 @@ use alloc::vec::Vec; -use math::FieldElement; +use math::{ExtensionOf, FieldElement}; use super::{lagrange::LagrangeKernelRandElements, LogUpGkrOracle}; @@ -130,7 +130,11 @@ impl GkrData { .fold(E::ZERO, |acc, (a, b)| acc + *a * *b) } - pub fn compute_batched_query(&self, query: &[E::BaseField]) -> E { + pub fn compute_batched_query_(&self, query: &[F]) -> E + where + F: FieldElement, + E: ExtensionOf, + { E::from(query[0]) + query .iter() diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index 5dcee0717..0b810425f 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -610,7 +610,7 @@ pub trait Air: Send + Sync { trace: t_coefficients, constraints: c_coefficients, lagrange: lagrange_cc, - s_col, + s_col: s_col_cc, }) } } diff --git a/prover/src/constraints/evaluator/default.rs b/prover/src/constraints/evaluator/default.rs index ea02b41d4..a9118a514 100644 --- a/prover/src/constraints/evaluator/default.rs +++ b/prover/src/constraints/evaluator/default.rs @@ -13,9 +13,9 @@ use utils::iter_mut; use utils::{iterators::*, rayon}; use super::{ - super::EvaluationTableFragment, lagrange::LagrangeKernelConstraintsBatchEvaluator, - BoundaryConstraints, CompositionPolyTrace, ConstraintEvaluationTable, ConstraintEvaluator, - PeriodicValueTable, StarkDomain, TraceLde, + super::EvaluationTableFragment, lagrange::LogUpGkrConstraintsEvaluator, BoundaryConstraints, + CompositionPolyTrace, ConstraintEvaluationTable, ConstraintEvaluator, PeriodicValueTable, + StarkDomain, TraceLde, }; // CONSTANTS @@ -40,7 +40,7 @@ pub struct DefaultConstraintEvaluator<'a, A: Air, E: FieldElement, transition_constraints: TransitionConstraints, - lagrange_constraints_evaluator: Option>, + logup_gkr_constraints_evaluator: Option>, aux_rand_elements: Option>, periodic_values: PeriodicValueTable, } @@ -117,10 +117,10 @@ where evaluation_table.validate_transition_degrees(); // combine all constraint evaluations into a single column, including the evaluations of the - // Lagrange kernel constraints (if present) + // LogUp-GKR constraints (if present) let combined_evaluations = { let mut constraints_evaluations = evaluation_table.combine(); - self.evaluate_lagrange_kernel_constraints(trace, domain, &mut constraints_evaluations); + self.evaluate_logup_gkr_constraints(trace, domain, &mut constraints_evaluations); constraints_evaluations }; @@ -161,15 +161,18 @@ where let lagrange_constraints_evaluator = if air.context().logup_gkr_enabled() { let aux_rand_elements = aux_rand_elements.as_ref().expect("expected aux rand elements to be present"); - let lagrange_rand_elements = aux_rand_elements - .lagrange() - .expect("expected lagrange rand elements to be present"); - Some(LagrangeKernelConstraintsBatchEvaluator::new( + + Some(LogUpGkrConstraintsEvaluator::new( air, - lagrange_rand_elements.clone(), + aux_rand_elements + .gkr_data() + .expect("expected LogUp-GKR randomness to be present"), composition_coefficients .lagrange .expect("expected Lagrange kernel composition coefficients to be present"), + composition_coefficients + .s_col + .expect("expected s-column composition coefficient to be present"), )) } else { None @@ -179,7 +182,7 @@ where air, boundary_constraints, transition_constraints, - lagrange_constraints_evaluator, + logup_gkr_constraints_evaluator, aux_rand_elements, periodic_values, } @@ -295,7 +298,7 @@ where } } - /// If present, evaluates the Lagrange kernel constraints over the constraint evaluation domain. + /// If present, evaluates the LogUp-GKR constraints over the constraint evaluation domain. /// The evaluation of each constraint (both boundary and transition) is divided by its divisor, /// multiplied by its composition coefficient, the result of which is added to /// `combined_evaluations_accumulator`. @@ -303,14 +306,14 @@ where /// Specifically, `combined_evaluations_accumulator` is a buffer whose length is the size of the /// constraint evaluation domain, where each index contains combined evaluations of other /// constraints in the system. - fn evaluate_lagrange_kernel_constraints>( + fn evaluate_logup_gkr_constraints>( &self, trace: &T, domain: &StarkDomain, combined_evaluations_accumulator: &mut [E], ) { - if let Some(ref lagrange_constraints_evaluator) = self.lagrange_constraints_evaluator { - lagrange_constraints_evaluator.evaluate_constraints( + if let Some(ref logup_gkr_constraints_evaluator) = self.logup_gkr_constraints_evaluator { + logup_gkr_constraints_evaluator.evaluate_constraints( trace, domain, combined_evaluations_accumulator, diff --git a/prover/src/constraints/evaluator/lagrange.rs b/prover/src/constraints/evaluator/lagrange.rs index 89d07f62c..541e440ce 100644 --- a/prover/src/constraints/evaluator/lagrange.rs +++ b/prover/src/constraints/evaluator/lagrange.rs @@ -6,40 +6,44 @@ use alloc::vec::Vec; use air::{ - Air, LagrangeConstraintsCompositionCoefficients, LagrangeKernelConstraints, - LagrangeKernelEvaluationFrame, LagrangeKernelRandElements, + Air, EvaluationFrame, GkrData, LagrangeConstraintsCompositionCoefficients, + LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, LogUpGkrEvaluator, }; use math::{batch_inversion, FieldElement}; use crate::{StarkDomain, TraceLde}; -/// Contains a specific strategy for evaluating the Lagrange kernel boundary and transition -/// constraints where the divisors' evaluation is batched. -/// -/// Specifically, [`batch_inversion`] is used to reduce the number of divisions performed. -pub struct LagrangeKernelConstraintsBatchEvaluator { +/// Contains a specific strategy for evaluating the Lagrange kernel and s-column boundary and +/// transition constraints. +pub struct LogUpGkrConstraintsEvaluator<'a, E: FieldElement, A: Air> { + air: &'a A, lagrange_kernel_constraints: LagrangeKernelConstraints, - rand_elements: LagrangeKernelRandElements, + gkr_data: GkrData, + s_col_composition_coefficient: E, } -impl LagrangeKernelConstraintsBatchEvaluator { - /// Constructs a new [`LagrangeConstraintsBatchEvaluator`]. - pub fn new( - air: &A, - lagrange_kernel_rand_elements: LagrangeKernelRandElements, +impl<'a, E, A> LogUpGkrConstraintsEvaluator<'a, E, A> +where + E: FieldElement, + A: Air, +{ + /// Constructs a new [`LogUpGkrConstraintsEvaluator`]. + pub fn new( + air: &'a A, + gkr_data: GkrData, lagrange_composition_coefficients: LagrangeConstraintsCompositionCoefficients, - ) -> Self - where - E: FieldElement, - { + s_col_composition_coefficient: E, + ) -> Self { Self { lagrange_kernel_constraints: air .get_lagrange_kernel_constraints( lagrange_composition_coefficients, - &lagrange_kernel_rand_elements, + gkr_data.lagrange_kernel_rand_elements(), ) .expect("expected Lagrange kernel constraints to be present"), - rand_elements: lagrange_kernel_rand_elements, + air, + gkr_data, + s_col_composition_coefficient, } } @@ -64,28 +68,41 @@ impl LagrangeKernelConstraintsBatchEvaluator { ); let boundary_divisors_inv = self.compute_boundary_divisors_inv(domain); - let mut frame = LagrangeKernelEvaluationFrame::new_empty(); + let mut lagrange_frame = LagrangeKernelEvaluationFrame::new_empty(); + + let evaluator = self.air.get_logup_gkr_evaluator::(); + let s_col_constraint_divisor = + compute_s_col_divisor::(domain.ce_domain_size(), domain, self.air.trace_length()); + let s_col_idx = trace.trace_info().aux_segment_width() - 2; + let l_col_idx = trace.trace_info().aux_segment_width() - 1; + let mut main_frame = EvaluationFrame::new(trace.trace_info().main_trace_width()); + let mut aux_frame = EvaluationFrame::new(trace.trace_info().aux_segment_width()); + + let c = self.gkr_data.compute_batched_claim(); + let mean = c / E::from(E::BaseField::from(trace.trace_info().length() as u32)); for step in 0..domain.ce_domain_size() { // compute Lagrange kernel frame trace.read_lagrange_kernel_frame_into( step << lde_shift, self.lagrange_kernel_constraints.lagrange_kernel_col_idx, - &mut frame, + &mut lagrange_frame, ); // Compute the combined transition and boundary constraints evaluations for this row - let combined_evaluations = { + let lagrange_combined_evaluations = { let mut combined_evaluations = E::ZERO; // combine transition constraints for trans_constraint_idx in 0..self.lagrange_kernel_constraints.transition.num_constraints() { - let numerator = self - .lagrange_kernel_constraints - .transition - .evaluate_ith_numerator(&frame, &self.rand_elements, trans_constraint_idx); + let numerator = + self.lagrange_kernel_constraints.transition.evaluate_ith_numerator( + &lagrange_frame, + &self.gkr_data.lagrange_kernel_eval_point, + trans_constraint_idx, + ); let inv_divisor = trans_constraints_divisors .get_inverse_divisor_eval(trans_constraint_idx, step); @@ -94,8 +111,10 @@ impl LagrangeKernelConstraintsBatchEvaluator { // combine boundary constraints { - let boundary_numerator = - self.lagrange_kernel_constraints.boundary.evaluate_numerator_at(&frame); + let boundary_numerator = self + .lagrange_kernel_constraints + .boundary + .evaluate_numerator_at(&lagrange_frame); combined_evaluations += boundary_numerator * boundary_divisors_inv[step]; } @@ -103,7 +122,28 @@ impl LagrangeKernelConstraintsBatchEvaluator { combined_evaluations }; - combined_evaluations_acc[step] += combined_evaluations; + let s_col_combined_evaluation = { + trace.read_main_trace_frame_into(step << lde_shift, &mut main_frame); + trace.read_aux_trace_frame_into(step << lde_shift, &mut aux_frame); + + let l_cur = aux_frame.current()[l_col_idx]; + let s_cur = aux_frame.current()[s_col_idx]; + let s_nxt = aux_frame.next()[s_col_idx]; + + let query = evaluator.build_query(&main_frame, &[]); + let batched_query = self.gkr_data.compute_batched_query_(&query); + + let rhs = s_cur - mean + batched_query * l_cur; + let lhs = s_nxt; + + (rhs - lhs) + * self + .s_col_composition_coefficient + .mul_base(s_col_constraint_divisor[step % (domain.trace_to_ce_blowup())]) + }; + + combined_evaluations_acc[step] += + lagrange_combined_evaluations + s_col_combined_evaluation; } } @@ -293,3 +333,23 @@ impl TransitionDivisorEvaluator { - E::BaseField::ONE } } + +/// Computes the evaluations of the s-column divisor. +/// +/// The divisor for the s-column is $X^n - 1$ where $n$ is the trace length. This means that +/// we need only compute `ce_blowup` many values and thus only that many exponentiations. +fn compute_s_col_divisor( + ce_domain_size: usize, + domain: &StarkDomain, + trace_length: usize, +) -> Vec { + let degree = trace_length as u32; + let mut result = Vec::with_capacity(ce_domain_size); + + for row in 0..domain.trace_to_ce_blowup() { + let x = domain.get_ce_x_at(row).exp(degree.into()) - E::BaseField::ONE; + + result.push(x); + } + batch_inversion(&result) +} diff --git a/sumcheck/src/lib.rs b/sumcheck/src/lib.rs index 5beac9bb9..b7f670a9d 100644 --- a/sumcheck/src/lib.rs +++ b/sumcheck/src/lib.rs @@ -26,7 +26,7 @@ mod univariate; pub use univariate::{CompressedUnivariatePoly, CompressedUnivariatePolyEvals}; mod multilinear; -pub use multilinear::{EqFunction, MultiLinearPoly}; +pub use multilinear::{inner_product, EqFunction, MultiLinearPoly}; /// Represents an opening claim at an evaluation point against a batch of oracles. /// diff --git a/verifier/src/evaluator.rs b/verifier/src/evaluator.rs index b26f7b926..4e673d39a 100644 --- a/verifier/src/evaluator.rs +++ b/verifier/src/evaluator.rs @@ -7,7 +7,7 @@ use alloc::vec::Vec; use air::{ Air, AuxRandElements, ConstraintCompositionCoefficients, EvaluationFrame, - LagrangeKernelEvaluationFrame, + LagrangeKernelEvaluationFrame, LogUpGkrEvaluator, }; use math::{polynom, FieldElement}; @@ -92,12 +92,8 @@ pub fn evaluate_constraints>( let lagrange_coefficients = composition_coefficients .lagrange .expect("expected Lagrange kernel composition coefficients to be present"); - let air::GkrData { - lagrange_kernel_eval_point: lagrange_kernel_evaluation_point, - openings_combining_randomness: _, - openings: _, - oracles: _, - } = aux_rand_elements + + let gkr_data = aux_rand_elements .expect("expected aux rand elements to be present") .gkr_data() .expect("expected LogUp-GKR rand elements to be present"); @@ -107,17 +103,45 @@ pub fn evaluate_constraints>( let lagrange_constraints = air .get_lagrange_kernel_constraints( lagrange_coefficients, - &lagrange_kernel_evaluation_point, + &gkr_data.lagrange_kernel_eval_point, ) .expect("expected Lagrange kernel constraints to be present"); result += lagrange_constraints.transition.evaluate_and_combine::( lagrange_kernel_column_frame, - &lagrange_kernel_evaluation_point, + &gkr_data.lagrange_kernel_eval_point, x, ); - result += lagrange_constraints.boundary.evaluate_at(x, lagrange_kernel_column_frame); + + // s-column constraints + + let s_col_idx = air.trace_info().aux_segment_width() - 2; + let s_cur = aux_trace_frame + .as_ref() + .expect("expected aux rand elements to be present") + .current()[s_col_idx]; + let s_nxt = aux_trace_frame + .as_ref() + .expect("expected aux rand elements to be present") + .next()[s_col_idx]; + let l_cur = lagrange_kernel_column_frame.inner()[0]; + + let batched_claim = gkr_data.compute_batched_claim(); + let mean = batched_claim + .mul_base(E::BaseField::ONE / E::BaseField::from(air.trace_length() as u32)); + + let query = air.get_logup_gkr_evaluator::().build_query(main_trace_frame, &[]); + let batched_claim_at_query = gkr_data.compute_batched_query_::(&query); + let rhs = s_cur - mean + batched_claim_at_query * l_cur; + let lhs = s_nxt; + + let divisor = x.exp((air.trace_length() as u32).into()) - E::ONE; + result += composition_coefficients + .s_col + .expect("expected constraint composition coefficient for s-column to be present") + * (rhs - lhs) + / divisor; } result diff --git a/winterfell/src/tests.rs b/winterfell/src/tests.rs index 330486914..63efe383e 100644 --- a/winterfell/src/tests.rs +++ b/winterfell/src/tests.rs @@ -22,7 +22,7 @@ use crate::{ #[test] fn test_logup_gkr() { - let aux_trace_width = 2; + let aux_trace_width = 1; let trace = LogUpGkrSimple::new(2_usize.pow(7), aux_trace_width); let prover = LogUpGkrSimpleProver::new(aux_trace_width); @@ -269,7 +269,7 @@ impl LogUpGkrSimpleProver { fn new(aux_trace_width: usize) -> Self { Self { aux_trace_width, - options: ProofOptions::new(1, 8, 0, FieldExtension::None, 2, 1), + options: ProofOptions::new(1, 8, 0, FieldExtension::Quadratic, 2, 1), } } } From a6570ca6964bf73122a9c868cec00c6412ec6140 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Thu, 15 Aug 2024 10:57:15 +0200 Subject: [PATCH 48/58] feat: add support for s-col and merge it with the that of Lagrange kernel under LogUp-GKR --- air/src/air/context.rs | 2 ++ air/src/air/mod.rs | 2 +- prover/src/constraints/evaluator/default.rs | 2 +- .../constraints/evaluator/{lagrange.rs => logup_gkr.rs} | 9 ++++----- prover/src/constraints/evaluator/mod.rs | 2 +- verifier/src/evaluator.rs | 4 ++-- 6 files changed, 11 insertions(+), 10 deletions(-) rename prover/src/constraints/evaluator/{lagrange.rs => logup_gkr.rs} (98%) diff --git a/air/src/air/context.rs b/air/src/air/context.rs index 799601f71..688a752e3 100644 --- a/air/src/air/context.rs +++ b/air/src/air/context.rs @@ -10,6 +10,8 @@ use math::StarkField; use crate::{air::TransitionConstraintDegree, ProofOptions, TraceInfo}; +use super::LAGRANGE_KERNEL_OFFSET; + // AIR CONTEXT // ================================================================================================ /// STARK parameters and trace properties for a specific execution of a computation. diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index 0b810425f..7d25ca7f2 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -36,7 +36,7 @@ pub use lagrange::{ }; mod logup_gkr; -pub use logup_gkr::{LogUpGkrEvaluator, LogUpGkrOracle}; +pub use logup_gkr::{LogUpGkrEvaluator, LogUpGkrOracle, PhantomLogUpGkrEval, LAGRANGE_KERNEL_OFFSET, S_COLUMN_OFFSET}; mod coefficients; pub use coefficients::{ diff --git a/prover/src/constraints/evaluator/default.rs b/prover/src/constraints/evaluator/default.rs index a9118a514..fd4bb1c42 100644 --- a/prover/src/constraints/evaluator/default.rs +++ b/prover/src/constraints/evaluator/default.rs @@ -13,7 +13,7 @@ use utils::iter_mut; use utils::{iterators::*, rayon}; use super::{ - super::EvaluationTableFragment, lagrange::LogUpGkrConstraintsEvaluator, BoundaryConstraints, + super::EvaluationTableFragment, logup_gkr::LogUpGkrConstraintsEvaluator, BoundaryConstraints, CompositionPolyTrace, ConstraintEvaluationTable, ConstraintEvaluator, PeriodicValueTable, StarkDomain, TraceLde, }; diff --git a/prover/src/constraints/evaluator/lagrange.rs b/prover/src/constraints/evaluator/logup_gkr.rs similarity index 98% rename from prover/src/constraints/evaluator/lagrange.rs rename to prover/src/constraints/evaluator/logup_gkr.rs index 541e440ce..3d0a3e3fc 100644 --- a/prover/src/constraints/evaluator/lagrange.rs +++ b/prover/src/constraints/evaluator/logup_gkr.rs @@ -6,8 +6,7 @@ use alloc::vec::Vec; use air::{ - Air, EvaluationFrame, GkrData, LagrangeConstraintsCompositionCoefficients, - LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, LogUpGkrEvaluator, + Air, EvaluationFrame, GkrData, LagrangeConstraintsCompositionCoefficients, LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, LogUpGkrEvaluator, LAGRANGE_KERNEL_OFFSET, S_COLUMN_OFFSET }; use math::{batch_inversion, FieldElement}; @@ -49,7 +48,7 @@ where /// Evaluates the transition and boundary constraints. Specifically, the constraint evaluations /// are divided by their corresponding divisors, and the resulting terms are linearly combined - /// using the composition coefficients. + /// using the constraint composition coefficients. /// /// Writes the evaluations in `combined_evaluations_acc` at the corresponding (constraint /// evaluation) domain index. @@ -73,8 +72,8 @@ where let evaluator = self.air.get_logup_gkr_evaluator::(); let s_col_constraint_divisor = compute_s_col_divisor::(domain.ce_domain_size(), domain, self.air.trace_length()); - let s_col_idx = trace.trace_info().aux_segment_width() - 2; - let l_col_idx = trace.trace_info().aux_segment_width() - 1; + let s_col_idx = trace.trace_info().aux_segment_width() - S_COLUMN_OFFSET; + let l_col_idx = trace.trace_info().aux_segment_width() - LAGRANGE_KERNEL_OFFSET; let mut main_frame = EvaluationFrame::new(trace.trace_info().main_trace_width()); let mut aux_frame = EvaluationFrame::new(trace.trace_info().aux_segment_width()); diff --git a/prover/src/constraints/evaluator/mod.rs b/prover/src/constraints/evaluator/mod.rs index da8a166c2..0ff6916f8 100644 --- a/prover/src/constraints/evaluator/mod.rs +++ b/prover/src/constraints/evaluator/mod.rs @@ -14,7 +14,7 @@ pub use default::DefaultConstraintEvaluator; mod boundary; use boundary::BoundaryConstraints; -mod lagrange; +mod logup_gkr; mod periodic_table; use periodic_table::PeriodicValueTable; diff --git a/verifier/src/evaluator.rs b/verifier/src/evaluator.rs index 4e673d39a..8c5e4c564 100644 --- a/verifier/src/evaluator.rs +++ b/verifier/src/evaluator.rs @@ -7,7 +7,7 @@ use alloc::vec::Vec; use air::{ Air, AuxRandElements, ConstraintCompositionCoefficients, EvaluationFrame, - LagrangeKernelEvaluationFrame, LogUpGkrEvaluator, + LagrangeKernelEvaluationFrame, LogUpGkrEvaluator, S_COLUMN_OFFSET, }; use math::{polynom, FieldElement}; @@ -116,7 +116,7 @@ pub fn evaluate_constraints>( // s-column constraints - let s_col_idx = air.trace_info().aux_segment_width() - 2; + let s_col_idx = air.trace_info().aux_segment_width() - S_COLUMN_OFFSET; let s_cur = aux_trace_frame .as_ref() .expect("expected aux rand elements to be present") From cc5aab7cc7316021db9aa6b4e87f611bcfaa2132 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 20 Aug 2024 13:34:03 +0200 Subject: [PATCH 49/58] chore: rebase and remove conflicts --- air/src/air/aux.rs | 2 +- air/src/air/context.rs | 8 +------- prover/src/constraints/evaluator/logup_gkr.rs | 9 ++++++--- verifier/src/evaluator.rs | 6 ++++-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/air/src/air/aux.rs b/air/src/air/aux.rs index 89a9afc15..0c677d109 100644 --- a/air/src/air/aux.rs +++ b/air/src/air/aux.rs @@ -130,7 +130,7 @@ impl GkrData { .fold(E::ZERO, |acc, (a, b)| acc + *a * *b) } - pub fn compute_batched_query_(&self, query: &[F]) -> E + pub fn compute_batched_query(&self, query: &[F]) -> E where F: FieldElement, E: ExtensionOf, diff --git a/air/src/air/context.rs b/air/src/air/context.rs index 688a752e3..354537828 100644 --- a/air/src/air/context.rs +++ b/air/src/air/context.rs @@ -8,9 +8,8 @@ use core::cmp; use math::StarkField; -use crate::{air::TransitionConstraintDegree, ProofOptions, TraceInfo}; - use super::LAGRANGE_KERNEL_OFFSET; +use crate::{air::TransitionConstraintDegree, ProofOptions, TraceInfo}; // AIR CONTEXT // ================================================================================================ @@ -271,11 +270,6 @@ impl AirContext { self.logup_gkr } - /// Returns true if the auxiliary trace segment contains a Lagrange kernel column - pub fn is_with_logup_gkr(&self) -> bool { - self.logup_gkr - } - /// Returns the total number of assertions defined for a computation, excluding the Lagrange /// kernel assertion, which is managed separately. /// diff --git a/prover/src/constraints/evaluator/logup_gkr.rs b/prover/src/constraints/evaluator/logup_gkr.rs index 3d0a3e3fc..ef45a6816 100644 --- a/prover/src/constraints/evaluator/logup_gkr.rs +++ b/prover/src/constraints/evaluator/logup_gkr.rs @@ -6,7 +6,9 @@ use alloc::vec::Vec; use air::{ - Air, EvaluationFrame, GkrData, LagrangeConstraintsCompositionCoefficients, LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, LogUpGkrEvaluator, LAGRANGE_KERNEL_OFFSET, S_COLUMN_OFFSET + Air, EvaluationFrame, GkrData, LagrangeConstraintsCompositionCoefficients, + LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, LogUpGkrEvaluator, + LAGRANGE_KERNEL_OFFSET, S_COLUMN_OFFSET, }; use math::{batch_inversion, FieldElement}; @@ -79,6 +81,7 @@ where let c = self.gkr_data.compute_batched_claim(); let mean = c / E::from(E::BaseField::from(trace.trace_info().length() as u32)); + let mut query = vec![E::BaseField::ZERO; evaluator.get_oracles().len()]; for step in 0..domain.ce_domain_size() { // compute Lagrange kernel frame @@ -129,8 +132,8 @@ where let s_cur = aux_frame.current()[s_col_idx]; let s_nxt = aux_frame.next()[s_col_idx]; - let query = evaluator.build_query(&main_frame, &[]); - let batched_query = self.gkr_data.compute_batched_query_(&query); + evaluator.build_query(&main_frame, &[], &mut query); + let batched_query = self.gkr_data.compute_batched_query(&query); let rhs = s_cur - mean + batched_query * l_cur; let lhs = s_nxt; diff --git a/verifier/src/evaluator.rs b/verifier/src/evaluator.rs index 8c5e4c564..636124bd6 100644 --- a/verifier/src/evaluator.rs +++ b/verifier/src/evaluator.rs @@ -131,8 +131,10 @@ pub fn evaluate_constraints>( let mean = batched_claim .mul_base(E::BaseField::ONE / E::BaseField::from(air.trace_length() as u32)); - let query = air.get_logup_gkr_evaluator::().build_query(main_trace_frame, &[]); - let batched_claim_at_query = gkr_data.compute_batched_query_::(&query); + let mut query = vec![E::ZERO; air.get_logup_gkr_evaluator::().get_oracles().len()]; + air.get_logup_gkr_evaluator::() + .build_query(main_trace_frame, &[], &mut query); + let batched_claim_at_query = gkr_data.compute_batched_query::(&query); let rhs = s_cur - mean + batched_claim_at_query * l_cur; let lhs = s_nxt; From 344c9950319115843f71fb98fe3d16fa4f6d88d9 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 27 Aug 2024 20:31:53 +0200 Subject: [PATCH 50/58] chore: rebase --- air/src/air/context.rs | 2 +- air/src/air/logup_gkr.rs | 5 + air/src/air/mod.rs | 6 +- air/src/lib.rs | 3 +- prover/src/constraints/evaluator/default.rs | 2 +- prover/src/constraints/evaluator/logup_gkr.rs | 4 +- sumcheck/src/prover/high_degree.rs | 3 - sumcheck/src/prover/plain.rs | 216 ++++++++++++++++++ sumcheck/src/verifier/mod.rs | 124 ---------- verifier/src/evaluator.rs | 10 +- 10 files changed, 238 insertions(+), 137 deletions(-) diff --git a/air/src/air/context.rs b/air/src/air/context.rs index 354537828..f8d9c505c 100644 --- a/air/src/air/context.rs +++ b/air/src/air/context.rs @@ -254,7 +254,7 @@ impl AirContext { /// Returns the index of the auxiliary column which implements the Lagrange kernel, if any pub fn lagrange_kernel_aux_column_idx(&self) -> Option { if self.logup_gkr_enabled() { - Some(self.trace_info().aux_segment_width() - 1) + Some(self.trace_info().aux_segment_width() - LAGRANGE_KERNEL_OFFSET) } else { None } diff --git a/air/src/air/logup_gkr.rs b/air/src/air/logup_gkr.rs index 47f219843..c0a33f8ea 100644 --- a/air/src/air/logup_gkr.rs +++ b/air/src/air/logup_gkr.rs @@ -11,6 +11,11 @@ use math::{ExtensionOf, FieldElement, StarkField, ToElements}; use super::{EvaluationFrame, GkrData, LagrangeKernelRandElements}; +// CONSTANTS +// =============================================================================================== +pub const LAGRANGE_KERNEL_OFFSET: usize = 1; +pub const S_COLUMN_OFFSET: usize = 2; + /// A trait containing the necessary information in order to run the LogUp-GKR protocol of [1]. /// /// The trait contains useful information for running the GKR protocol as well as for implementing diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index 7d25ca7f2..156f59f58 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -36,7 +36,9 @@ pub use lagrange::{ }; mod logup_gkr; -pub use logup_gkr::{LogUpGkrEvaluator, LogUpGkrOracle, PhantomLogUpGkrEval, LAGRANGE_KERNEL_OFFSET, S_COLUMN_OFFSET}; +pub use logup_gkr::{ + LogUpGkrEvaluator, LogUpGkrOracle, PhantomLogUpGkrEval, LAGRANGE_KERNEL_OFFSET, S_COLUMN_OFFSET, +}; mod coefficients; pub use coefficients::{ @@ -600,7 +602,7 @@ pub trait Air: Send + Sync { None }; - let s_col = if self.context().logup_gkr_enabled() { + let s_col_cc = if self.context().logup_gkr_enabled() { Some(public_coin.draw()?) } else { None diff --git a/air/src/lib.rs b/air/src/lib.rs index 4b6c5b914..6c82362a9 100644 --- a/air/src/lib.rs +++ b/air/src/lib.rs @@ -48,5 +48,6 @@ pub use air::{ LagrangeConstraintsCompositionCoefficients, LagrangeKernelBoundaryConstraint, LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, LagrangeKernelRandElements, LagrangeKernelTransitionConstraints, LogUpGkrEvaluator, LogUpGkrOracle, PhantomLogUpGkrEval, - TraceInfo, TransitionConstraintDegree, TransitionConstraints, + TraceInfo, TransitionConstraintDegree, TransitionConstraints, LAGRANGE_KERNEL_OFFSET, + S_COLUMN_OFFSET, }; diff --git a/prover/src/constraints/evaluator/default.rs b/prover/src/constraints/evaluator/default.rs index fd4bb1c42..4373494f9 100644 --- a/prover/src/constraints/evaluator/default.rs +++ b/prover/src/constraints/evaluator/default.rs @@ -158,7 +158,7 @@ where &composition_coefficients.boundary, ); - let lagrange_constraints_evaluator = if air.context().logup_gkr_enabled() { + let logup_gkr_constraints_evaluator = if air.context().logup_gkr_enabled() { let aux_rand_elements = aux_rand_elements.as_ref().expect("expected aux rand elements to be present"); diff --git a/prover/src/constraints/evaluator/logup_gkr.rs b/prover/src/constraints/evaluator/logup_gkr.rs index ef45a6816..0ed5b2c75 100644 --- a/prover/src/constraints/evaluator/logup_gkr.rs +++ b/prover/src/constraints/evaluator/logup_gkr.rs @@ -71,12 +71,12 @@ where let mut lagrange_frame = LagrangeKernelEvaluationFrame::new_empty(); - let evaluator = self.air.get_logup_gkr_evaluator::(); + let evaluator = self.air.get_logup_gkr_evaluator::(); let s_col_constraint_divisor = compute_s_col_divisor::(domain.ce_domain_size(), domain, self.air.trace_length()); let s_col_idx = trace.trace_info().aux_segment_width() - S_COLUMN_OFFSET; let l_col_idx = trace.trace_info().aux_segment_width() - LAGRANGE_KERNEL_OFFSET; - let mut main_frame = EvaluationFrame::new(trace.trace_info().main_trace_width()); + let mut main_frame = EvaluationFrame::new(trace.trace_info().main_segment_width()); let mut aux_frame = EvaluationFrame::new(trace.trace_info().aux_segment_width()); let c = self.gkr_data.compute_batched_claim(); diff --git a/sumcheck/src/prover/high_degree.rs b/sumcheck/src/prover/high_degree.rs index a7d87d6b9..691195925 100644 --- a/sumcheck/src/prover/high_degree.rs +++ b/sumcheck/src/prover/high_degree.rs @@ -17,9 +17,6 @@ use crate::{ MultiLinearPoly, RoundProof, SumCheckProof, SumCheckRoundClaim, }; -#[cfg(feature = "concurrent")] -pub use rayon::prelude::*; - /// A sum-check prover for the input layer which can accommodate non-linear expressions in /// the numerators of the LogUp relation. /// diff --git a/sumcheck/src/prover/plain.rs b/sumcheck/src/prover/plain.rs index e69de29bb..e0092cf10 100644 --- a/sumcheck/src/prover/plain.rs +++ b/sumcheck/src/prover/plain.rs @@ -0,0 +1,216 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +use crypto::{ElementHasher, RandomCoin}; +use math::FieldElement; +#[cfg(feature = "concurrent")] +pub use rayon::prelude::*; +use smallvec::smallvec; + +use super::SumCheckProverError; +use crate::{ + comb_func, CompressedUnivariatePolyEvals, FinalOpeningClaim, MultiLinearPoly, RoundProof, + SumCheckProof, +}; + +/// Sum-check prover for non-linear multivariate polynomial of the simple LogUp-GKR. +/// +/// More specifically, the following function implements the logic of the sum-check prover as +/// described in Section 3.2 in [1], that is, given verifier challenges , the following implements +/// the sum-check prover for the following two statements +/// $$ +/// p_{\nu - \kappa}\left(v_{\kappa+1}, \cdots, v_{\nu}\right) = \sum_{w_i} +/// EQ\left(\left(v_{\kappa+1}, \cdots, v_{\nu}\right), \left(w_{\kappa+1}, \cdots, +/// w_{\nu}\right)\right) \cdot +/// \left( p_{\nu-\kappa+1}\left(1, w_{\kappa+1}, \cdots, w_{\nu}\right) \cdot +/// q_{\nu-\kappa+1}\left(0, w_{\kappa+1}, \cdots, w_{\nu}\right) + +/// p_{\nu-\kappa+1}\left(0, w_{\kappa+1}, \cdots, w_{\nu}\right) \cdot +/// q_{\nu-\kappa+1}\left(1, w_{\kappa+1}, \cdots, w_{\nu}\right)\right) +/// $$ +/// +/// and +/// +/// $$ +/// q_{\nu -k}\left(v_{\kappa+1}, \cdots, v_{\nu}\right) = \sum_{w_i}EQ\left(\left(v_{\kappa+1}, +/// \cdots, v_{\nu}\right), \left(w_{\kappa+1}, \cdots, w_{\nu }\right)\right) \cdot +/// \left( q_{\nu-\kappa+1}\left(1, w_{\kappa+1}, \cdots, w_{\nu}\right) \cdot +/// q_{\nu-\kappa+1}\left(0, w_{\kappa+1}, \cdots, w_{\nu}\right)\right) +/// $$ +/// +/// for $k = 1, \cdots, \nu - 1$ +/// +/// Instead of executing two runs of the sum-check protocol, a batching randomness `r_batch` is +/// sent by the verifier at the outset in order to batch the two statments. +/// +/// Note that the degree of the non-linear composition polynomial is 3. +/// +/// [1]: https://eprint.iacr.org/2023/1284 +#[allow(clippy::too_many_arguments)] +pub fn sumcheck_prove_plain>( + mut claim: E, + r_batch: E, + p: MultiLinearPoly, + q: MultiLinearPoly, + eq: &mut MultiLinearPoly, + transcript: &mut impl RandomCoin, +) -> Result, SumCheckProverError> { + let mut round_proofs = vec![]; + + let mut challenges = vec![]; + + // construct the vector of multi-linear polynomials + let (mut p0, mut p1) = p.project_least_significant_variable(); + let (mut q0, mut q1) = q.project_least_significant_variable(); + + for _ in 0..p0.num_variables() { + let len = p0.num_evaluations() / 2; + + #[cfg(not(feature = "concurrent"))] + let (round_poly_eval_at_1, round_poly_eval_at_2, round_poly_eval_at_3) = (0..len).fold( + (E::ZERO, E::ZERO, E::ZERO), + |(acc_point_1, acc_point_2, acc_point_3), i| { + let round_poly_eval_at_1 = comb_func( + p0[2 * i + 1], + p1[2 * i + 1], + q0[2 * i + 1], + q1[2 * i + 1], + eq[2 * i + 1], + r_batch, + ); + + let p0_delta = p0[2 * i + 1] - p0[2 * i]; + let p1_delta = p1[2 * i + 1] - p1[2 * i]; + let q0_delta = q0[2 * i + 1] - q0[2 * i]; + let q1_delta = q1[2 * i + 1] - q1[2 * i]; + let eq_delta = eq[2 * i + 1] - eq[2 * i]; + + let mut p0_eval_at_x = p0[2 * i + 1] + p0_delta; + let mut p1_eval_at_x = p1[2 * i + 1] + p1_delta; + let mut q0_eval_at_x = q0[2 * i + 1] + q0_delta; + let mut q1_eval_at_x = q1[2 * i + 1] + q1_delta; + let mut eq_evx = eq[2 * i + 1] + eq_delta; + let round_poly_eval_at_2 = comb_func( + p0_eval_at_x, + p1_eval_at_x, + q0_eval_at_x, + q1_eval_at_x, + eq_evx, + r_batch, + ); + + p0_eval_at_x += p0_delta; + p1_eval_at_x += p1_delta; + q0_eval_at_x += q0_delta; + q1_eval_at_x += q1_delta; + eq_evx += eq_delta; + let round_poly_eval_at_3 = comb_func( + p0_eval_at_x, + p1_eval_at_x, + q0_eval_at_x, + q1_eval_at_x, + eq_evx, + r_batch, + ); + + ( + round_poly_eval_at_1 + acc_point_1, + round_poly_eval_at_2 + acc_point_2, + round_poly_eval_at_3 + acc_point_3, + ) + }, + ); + + #[cfg(feature = "concurrent")] + let (round_poly_eval_at_1, round_poly_eval_at_2, round_poly_eval_at_3) = (0..len) + .into_par_iter() + .fold( + || (E::ZERO, E::ZERO, E::ZERO), + |(a, b, c), i| { + let round_poly_eval_at_1 = comb_func( + p0[2 * i + 1], + p1[2 * i + 1], + q0[2 * i + 1], + q1[2 * i + 1], + eq[2 * i + 1], + r_batch, + ); + + let p0_delta = p0[2 * i + 1] - p0[2 * i]; + let p1_delta = p1[2 * i + 1] - p1[2 * i]; + let q0_delta = q0[2 * i + 1] - q0[2 * i]; + let q1_delta = q1[2 * i + 1] - q1[2 * i]; + let eq_delta = eq[2 * i + 1] - eq[2 * i]; + + let mut p0_eval_at_x = p0[2 * i + 1] + p0_delta; + let mut p1_eval_at_x = p1[2 * i + 1] + p1_delta; + let mut q0_eval_at_x = q0[2 * i + 1] + q0_delta; + let mut q1_eval_at_x = q1[2 * i + 1] + q1_delta; + let mut eq_evx = eq[2 * i + 1] + eq_delta; + let round_poly_eval_at_2 = comb_func( + p0_eval_at_x, + p1_eval_at_x, + q0_eval_at_x, + q1_eval_at_x, + eq_evx, + r_batch, + ); + + p0_eval_at_x += p0_delta; + p1_eval_at_x += p1_delta; + q0_eval_at_x += q0_delta; + q1_eval_at_x += q1_delta; + eq_evx += eq_delta; + let round_poly_eval_at_3 = comb_func( + p0_eval_at_x, + p1_eval_at_x, + q0_eval_at_x, + q1_eval_at_x, + eq_evx, + r_batch, + ); + + (round_poly_eval_at_1 + a, round_poly_eval_at_2 + b, round_poly_eval_at_3 + c) + }, + ) + .reduce( + || (E::ZERO, E::ZERO, E::ZERO), + |(a0, b0, c0), (a1, b1, c1)| (a0 + a1, b0 + b1, c0 + c1), + ); + + let evals = smallvec![round_poly_eval_at_1, round_poly_eval_at_2, round_poly_eval_at_3]; + let compressed_round_poly_evals = CompressedUnivariatePolyEvals(evals); + let compressed_round_poly = compressed_round_poly_evals.to_poly(claim); + + // reseed with the s_i polynomial + transcript.reseed(H::hash_elements(&compressed_round_poly.0)); + let round_proof = RoundProof { + round_poly_coefs: compressed_round_poly.clone(), + }; + + let round_challenge = + transcript.draw().map_err(|_| SumCheckProverError::FailedToGenerateChallenge)?; + + // fold each multi-linear using the round challenge + p0.bind_least_significant_variable(round_challenge); + p1.bind_least_significant_variable(round_challenge); + q0.bind_least_significant_variable(round_challenge); + q1.bind_least_significant_variable(round_challenge); + eq.bind_least_significant_variable(round_challenge); + + // compute the new reduced round claim + claim = compressed_round_poly.evaluate_using_claim(&claim, &round_challenge); + + round_proofs.push(round_proof); + challenges.push(round_challenge); + } + + Ok(SumCheckProof { + openings_claim: FinalOpeningClaim { + eval_point: challenges, + openings: vec![p0[0], p1[0], q0[0], q1[0]], + }, + round_proofs, + }) +} diff --git a/sumcheck/src/verifier/mod.rs b/sumcheck/src/verifier/mod.rs index 1dd7412d4..887598cc8 100644 --- a/sumcheck/src/verifier/mod.rs +++ b/sumcheck/src/verifier/mod.rs @@ -138,130 +138,6 @@ where }) } -/// Verifies sum-check proofs, as part of the GKR proof, for all GKR layers except for the last one -/// i.e., the circuit input layer. -pub fn verify_sum_check_intermediate_layers< - E: FieldElement, - H: ElementHasher, ->( - proof: &SumCheckProof, - gkr_eval_point: &[E], - claim: (E, E), - transcript: &mut impl RandomCoin, -) -> Result, SumCheckVerifierError> { - // generate challenge to batch sum-checks - transcript.reseed(H::hash_elements(&[claim.0, claim.1])); - let r_batch: E = transcript - .draw() - .map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; - - // compute the claim for the batched sum-check - let reduced_claim = claim.0 + claim.1 * r_batch; - - let SumCheckProof { openings_claim, round_proofs } = proof; - - let final_round_claim = verify_rounds(reduced_claim, round_proofs, transcript)?; - assert_eq!(openings_claim.eval_point, final_round_claim.eval_point); - - let p0 = openings_claim.openings[0]; - let p1 = openings_claim.openings[1]; - let q0 = openings_claim.openings[2]; - let q1 = openings_claim.openings[3]; - - let eq = EqFunction::new(gkr_eval_point.into()).evaluate(&openings_claim.eval_point); - - if comb_func(p0, p1, q0, q1, eq, r_batch) != final_round_claim.claim { - return Err(SumCheckVerifierError::FinalEvaluationCheckFailed); - } - - Ok(openings_claim.clone()) -} - -/// Verifies the final sum-check proof i.e., the one for the input layer, including the final check, -/// and returns a [`FinalOpeningClaim`] to the STARK verifier in order to verify the correctness of -/// the openings. -pub fn verify_sum_check_input_layer>( - evaluator: &impl LogUpGkrEvaluator, - proof: &FinalLayerProof, - log_up_randomness: Vec, - gkr_eval_point: &[E], - claim: (E, E), - transcript: &mut impl RandomCoin, -) -> Result, SumCheckVerifierError> { - let FinalLayerProof { proof } = proof; - - // generate challenge to batch sum-checks - transcript.reseed(H::hash_elements(&[claim.0, claim.1])); - let r_batch: E = transcript - .draw() - .map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; - - // compute the claim for the batched sum-check - let reduced_claim = claim.0 + claim.1 * r_batch; - - // verify the sum-check proof - let SumCheckRoundClaim { eval_point, claim } = - verify_rounds(reduced_claim, &proof.round_proofs, transcript)?; - - // execute the final evaluation check - if proof.openings_claim.eval_point != eval_point { - return Err(SumCheckVerifierError::WrongOpeningPoint); - } - - let mut numerators = vec![E::ZERO; evaluator.get_num_fractions()]; - let mut denominators = vec![E::ZERO; evaluator.get_num_fractions()]; - evaluator.evaluate_query( - &proof.openings_claim.openings, - &log_up_randomness, - &mut numerators, - &mut denominators, - ); - - let mu = evaluator.get_num_fractions().trailing_zeros() - 1; - let (evaluation_point_mu, evaluation_point_nu) = gkr_eval_point.split_at(mu as usize); - - let eq_mu = EqFunction::new(evaluation_point_mu.into()).evaluations(); - let eq_nu = EqFunction::new(evaluation_point_nu.into()); - - let eq_nu_eval = eq_nu.evaluate(&proof.openings_claim.eval_point); - let expected_evaluation = - evaluate_composition_poly(&eq_mu, &numerators, &denominators, eq_nu_eval, r_batch); - - if expected_evaluation != claim { - Err(SumCheckVerifierError::FinalEvaluationCheckFailed) - } else { - Ok(proof.openings_claim.clone()) - } -} - -/// Verifies a round of the sum-check protocol without executing the final check. -fn verify_rounds( - claim: E, - round_proofs: &[RoundProof], - coin: &mut impl RandomCoin, -) -> Result, SumCheckVerifierError> -where - E: FieldElement, - H: ElementHasher, -{ - let mut round_claim = claim; - let mut evaluation_point = vec![]; - for round_proof in round_proofs { - let round_poly_coefs = round_proof.round_poly_coefs.clone(); - coin.reseed(H::hash_elements(&round_poly_coefs.0)); - - let r = coin.draw().map_err(|_| SumCheckVerifierError::FailedToGenerateChallenge)?; - - round_claim = round_proof.round_poly_coefs.evaluate_using_claim(&round_claim, &r); - evaluation_point.push(r); - } - - Ok(SumCheckRoundClaim { - eval_point: evaluation_point, - claim: round_claim, - }) -} - #[derive(Debug, thiserror::Error)] pub enum SumCheckVerifierError { #[error("the final evaluation check of sum-check failed")] diff --git a/verifier/src/evaluator.rs b/verifier/src/evaluator.rs index 636124bd6..b66eb931f 100644 --- a/verifier/src/evaluator.rs +++ b/verifier/src/evaluator.rs @@ -131,9 +131,13 @@ pub fn evaluate_constraints>( let mean = batched_claim .mul_base(E::BaseField::ONE / E::BaseField::from(air.trace_length() as u32)); - let mut query = vec![E::ZERO; air.get_logup_gkr_evaluator::().get_oracles().len()]; - air.get_logup_gkr_evaluator::() - .build_query(main_trace_frame, &[], &mut query); + let mut query = + vec![E::ZERO; air.get_logup_gkr_evaluator::().get_oracles().len()]; + air.get_logup_gkr_evaluator::().build_query( + main_trace_frame, + &[], + &mut query, + ); let batched_claim_at_query = gkr_data.compute_batched_query::(&query); let rhs = s_cur - mean + batched_claim_at_query * l_cur; let lhs = s_nxt; From 4d6514c2feaccadcdebd7611c1ed969e11011367 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 30 Aug 2024 20:02:03 +0200 Subject: [PATCH 51/58] chore: fix post rebase issues --- air/src/air/context.rs | 5 --- air/src/air/mod.rs | 2 +- air/src/lib.rs | 2 +- prover/src/constraints/evaluator/logup_gkr.rs | 2 +- prover/src/lib.rs | 2 +- prover/src/logup_gkr/prover.rs | 2 +- prover/src/tests/mod.rs | 9 +---- verifier/src/evaluator.rs | 4 +-- verifier/src/logup_gkr/mod.rs | 35 ------------------- 9 files changed, 8 insertions(+), 55 deletions(-) diff --git a/air/src/air/context.rs b/air/src/air/context.rs index f8d9c505c..a90c6637e 100644 --- a/air/src/air/context.rs +++ b/air/src/air/context.rs @@ -265,11 +265,6 @@ impl AirContext { self.logup_gkr } - /// Returns true if the auxiliary trace segment contains a Lagrange kernel column - pub fn is_with_logup_gkr(&self) -> bool { - self.logup_gkr - } - /// Returns the total number of assertions defined for a computation, excluding the Lagrange /// kernel assertion, which is managed separately. /// diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index 156f59f58..f104eb6cc 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -37,7 +37,7 @@ pub use lagrange::{ mod logup_gkr; pub use logup_gkr::{ - LogUpGkrEvaluator, LogUpGkrOracle, PhantomLogUpGkrEval, LAGRANGE_KERNEL_OFFSET, S_COLUMN_OFFSET, + LogUpGkrEvaluator, LogUpGkrOracle, LAGRANGE_KERNEL_OFFSET, S_COLUMN_OFFSET, }; mod coefficients; diff --git a/air/src/lib.rs b/air/src/lib.rs index 6c82362a9..050801a4e 100644 --- a/air/src/lib.rs +++ b/air/src/lib.rs @@ -47,7 +47,7 @@ pub use air::{ DeepCompositionCoefficients, EvaluationFrame, GkrData, LagrangeConstraintsCompositionCoefficients, LagrangeKernelBoundaryConstraint, LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, LagrangeKernelRandElements, - LagrangeKernelTransitionConstraints, LogUpGkrEvaluator, LogUpGkrOracle, PhantomLogUpGkrEval, + LagrangeKernelTransitionConstraints, LogUpGkrEvaluator, LogUpGkrOracle, TraceInfo, TransitionConstraintDegree, TransitionConstraints, LAGRANGE_KERNEL_OFFSET, S_COLUMN_OFFSET, }; diff --git a/prover/src/constraints/evaluator/logup_gkr.rs b/prover/src/constraints/evaluator/logup_gkr.rs index 0ed5b2c75..46d27846b 100644 --- a/prover/src/constraints/evaluator/logup_gkr.rs +++ b/prover/src/constraints/evaluator/logup_gkr.rs @@ -71,7 +71,7 @@ where let mut lagrange_frame = LagrangeKernelEvaluationFrame::new_empty(); - let evaluator = self.air.get_logup_gkr_evaluator::(); + let evaluator = self.air.get_logup_gkr_evaluator(); let s_col_constraint_divisor = compute_s_col_divisor::(domain.ce_domain_size(), domain, self.air.trace_length()); let s_col_idx = trace.trace_info().aux_segment_width() - S_COLUMN_OFFSET; diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 45b133146..a94306fab 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -208,7 +208,7 @@ pub trait Prover { fn build_aux_trace( &self, main_trace: &Self::Trace, - aux_rand_elements: &AuxRandElements, + aux_rand_elements: &[E], ) -> ColMatrix where E: FieldElement, diff --git a/prover/src/logup_gkr/prover.rs b/prover/src/logup_gkr/prover.rs index eac7c3470..9fc8fe175 100644 --- a/prover/src/logup_gkr/prover.rs +++ b/prover/src/logup_gkr/prover.rs @@ -79,7 +79,7 @@ pub fn prove_gkr( build_mls_from_main_trace_segment(evaluator.get_oracles(), main_trace.main_segment())?; let final_layer_proof = - prove_input_layer(evaluator, logup_randomness, &mut main_trace_mls, gkr_claim, public_coin)?; + prove_input_layer(evaluator, logup_randomness, main_trace_mls, gkr_claim, public_coin)?; Ok(GkrCircuitProof { circuit_outputs: CircuitOutput { numerators, denominators }, diff --git a/prover/src/tests/mod.rs b/prover/src/tests/mod.rs index cc3c39ae1..d42ebdcfe 100644 --- a/prover/src/tests/mod.rs +++ b/prover/src/tests/mod.rs @@ -6,7 +6,7 @@ use alloc::vec::Vec; use air::{ - Air, AirContext, Assertion, EvaluationFrame, FieldExtension, PhantomLogUpGkrEval, ProofOptions, + Air, AirContext, Assertion, EvaluationFrame, FieldExtension, ProofOptions, TraceInfo, TransitionConstraintDegree, }; use math::{fields::f64::BaseElement, FieldElement, StarkField}; @@ -104,13 +104,6 @@ impl Air for MockAir { fn get_periodic_column_values(&self) -> Vec> { self.periodic_columns.clone() } - - fn get_logup_gkr_evaluator( - &self, - ) -> impl air::LogUpGkrEvaluator - { - PhantomLogUpGkrEval::default() - } } // HELPER FUNCTIONS diff --git a/verifier/src/evaluator.rs b/verifier/src/evaluator.rs index b66eb931f..2ca0c8181 100644 --- a/verifier/src/evaluator.rs +++ b/verifier/src/evaluator.rs @@ -132,8 +132,8 @@ pub fn evaluate_constraints>( .mul_base(E::BaseField::ONE / E::BaseField::from(air.trace_length() as u32)); let mut query = - vec![E::ZERO; air.get_logup_gkr_evaluator::().get_oracles().len()]; - air.get_logup_gkr_evaluator::().build_query( + vec![E::ZERO; air.get_logup_gkr_evaluator().get_oracles().len()]; + air.get_logup_gkr_evaluator().build_query( main_trace_frame, &[], &mut query, diff --git a/verifier/src/logup_gkr/mod.rs b/verifier/src/logup_gkr/mod.rs index 1a4324bf3..e317e0ab1 100644 --- a/verifier/src/logup_gkr/mod.rs +++ b/verifier/src/logup_gkr/mod.rs @@ -113,38 +113,3 @@ pub enum VerifierError { #[error("failed to verify the sum-check proof")] FailedToVerifySumCheck(#[from] SumCheckVerifierError), } - -// UNIVARIATE IOP FOR MULTI-LINEAR EVALUATION -// =============================================================================================== - -/// Generates the batching randomness used to batch a number of multi-linear evaluation claims. -/// -/// This is the $\lambda$ randomness in section 5.2 in [1] but using different random values for -/// each term instead of powers of a single random element. -/// -/// [1]: https://eprint.iacr.org/2023/1284 -pub fn generate_gkr_randomness< - E: FieldElement, - C: RandomCoin, - H: ElementHasher, ->( - final_opening_claim: FinalOpeningClaim, - oracles: &[LogUpGkrOracle], - public_coin: &mut C, -) -> GkrData { - let FinalOpeningClaim { eval_point, openings } = final_opening_claim; - - public_coin.reseed(H::hash_elements(&openings)); - - let mut batching_randomness = Vec::with_capacity(openings.len() - 1); - for _ in 0..openings.len() - 1 { - batching_randomness.push(public_coin.draw().expect("failed to generate randomness")) - } - - GkrData::new( - LagrangeKernelRandElements::new(eval_point), - batching_randomness, - openings, - oracles.to_vec(), - ) -} From 11ccbff63615866014a0b9af8f351cc5344188ff Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 2 Sep 2024 12:10:57 +0200 Subject: [PATCH 52/58] chore: address feedback 1 --- air/src/air/context.rs | 9 ++------ air/src/air/lagrange/mod.rs | 3 --- air/src/air/logup_gkr.rs | 20 +++++++++++----- air/src/air/mod.rs | 21 +---------------- air/src/air/trace_info.rs | 22 ++++++++++++++++++ air/src/lib.rs | 5 ++-- prover/src/constraints/evaluator/logup_gkr.rs | 14 ++++++----- prover/src/lib.rs | 8 ++----- verifier/src/evaluator.rs | 23 +++++++------------ 9 files changed, 59 insertions(+), 66 deletions(-) diff --git a/air/src/air/context.rs b/air/src/air/context.rs index a90c6637e..f3167c005 100644 --- a/air/src/air/context.rs +++ b/air/src/air/context.rs @@ -8,7 +8,6 @@ use core::cmp; use math::StarkField; -use super::LAGRANGE_KERNEL_OFFSET; use crate::{air::TransitionConstraintDegree, ProofOptions, TraceInfo}; // AIR CONTEXT @@ -251,13 +250,9 @@ impl AirContext { self.aux_transition_constraint_degrees.len() } - /// Returns the index of the auxiliary column which implements the Lagrange kernel, if any + /// Returns the index of the auxiliary column which implements the Lagrange kernel, if any. pub fn lagrange_kernel_aux_column_idx(&self) -> Option { - if self.logup_gkr_enabled() { - Some(self.trace_info().aux_segment_width() - LAGRANGE_KERNEL_OFFSET) - } else { - None - } + self.trace_info.lagrange_kernel_column_idx() } /// Returns true if LogUp-GKR is enabled. diff --git a/air/src/air/lagrange/mod.rs b/air/src/air/lagrange/mod.rs index fed5897f3..8f352ad45 100644 --- a/air/src/air/lagrange/mod.rs +++ b/air/src/air/lagrange/mod.rs @@ -22,7 +22,6 @@ use crate::LagrangeConstraintsCompositionCoefficients; pub struct LagrangeKernelConstraints { pub transition: LagrangeKernelTransitionConstraints, pub boundary: LagrangeKernelBoundaryConstraint, - pub lagrange_kernel_col_idx: usize, } impl LagrangeKernelConstraints { @@ -30,7 +29,6 @@ impl LagrangeKernelConstraints { pub fn new( lagrange_composition_coefficients: LagrangeConstraintsCompositionCoefficients, lagrange_kernel_rand_elements: &LagrangeKernelRandElements, - lagrange_kernel_col_idx: usize, ) -> Self { Self { transition: LagrangeKernelTransitionConstraints::new( @@ -40,7 +38,6 @@ impl LagrangeKernelConstraints { lagrange_composition_coefficients.boundary, lagrange_kernel_rand_elements, ), - lagrange_kernel_col_idx, } } } diff --git a/air/src/air/logup_gkr.rs b/air/src/air/logup_gkr.rs index c0a33f8ea..fa0010e06 100644 --- a/air/src/air/logup_gkr.rs +++ b/air/src/air/logup_gkr.rs @@ -9,12 +9,7 @@ use core::marker::PhantomData; use crypto::{ElementHasher, RandomCoin}; use math::{ExtensionOf, FieldElement, StarkField, ToElements}; -use super::{EvaluationFrame, GkrData, LagrangeKernelRandElements}; - -// CONSTANTS -// =============================================================================================== -pub const LAGRANGE_KERNEL_OFFSET: usize = 1; -pub const S_COLUMN_OFFSET: usize = 2; +use super::{EvaluationFrame, GkrData, LagrangeConstraintsCompositionCoefficients, LagrangeKernelConstraints, LagrangeKernelRandElements}; /// A trait containing the necessary information in order to run the LogUp-GKR protocol of [1]. /// @@ -121,6 +116,19 @@ pub trait LogUpGkrEvaluator: Clone + Sync { self.get_oracles().to_vec(), ) } + + /// Returns a new [`LagrangeKernelConstraints`]. + fn get_lagrange_kernel_constraints>( + &self, + lagrange_composition_coefficients: LagrangeConstraintsCompositionCoefficients, + lagrange_kernel_rand_elements: &LagrangeKernelRandElements, + ) -> LagrangeKernelConstraints { + LagrangeKernelConstraints::new( + lagrange_composition_coefficients, + lagrange_kernel_rand_elements, + ) + + } } #[derive(Clone, Default)] diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index f104eb6cc..b4c0c32af 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -37,7 +37,7 @@ pub use lagrange::{ mod logup_gkr; pub use logup_gkr::{ - LogUpGkrEvaluator, LogUpGkrOracle, LAGRANGE_KERNEL_OFFSET, S_COLUMN_OFFSET, + LogUpGkrEvaluator, LogUpGkrOracle, }; mod coefficients; @@ -333,25 +333,6 @@ pub trait Air: Send + Sync { Ok(rand_elements) } - /// Returns a new [`LagrangeKernelConstraints`] if a Lagrange kernel auxiliary column is present - /// in the trace, or `None` otherwise. - fn get_lagrange_kernel_constraints>( - &self, - lagrange_composition_coefficients: LagrangeConstraintsCompositionCoefficients, - lagrange_kernel_rand_elements: &LagrangeKernelRandElements, - ) -> Option> { - if self.context().logup_gkr_enabled() { - let col_idx = self.context().trace_info().aux_segment_width() - 1; - Some(LagrangeKernelConstraints::new( - lagrange_composition_coefficients, - lagrange_kernel_rand_elements, - col_idx, - )) - } else { - None - } - } - /// Returns values for all periodic columns used in the computation. /// /// These values will be used to compute column values at specific states of the computation diff --git a/air/src/air/trace_info.rs b/air/src/air/trace_info.rs index 44aa0a7ea..d55514bce 100644 --- a/air/src/air/trace_info.rs +++ b/air/src/air/trace_info.rs @@ -39,6 +39,10 @@ impl TraceInfo { pub const MAX_META_LENGTH: usize = 65535; /// Maximum number of random elements in the auxiliary trace segment; currently set to 255. pub const MAX_RAND_SEGMENT_ELEMENTS: usize = 255; + /// The Lagrange kernel, if present, is the last column of the auxiliary trace. + pub const LAGRANGE_KERNEL_OFFSET: usize = 1; + /// The s-column, if present, is the second to last column of the auxiliary trace. + pub const S_COLUMN_OFFSET: usize = 2; // CONSTRUCTORS // -------------------------------------------------------------------------------------------- @@ -210,6 +214,24 @@ impl TraceInfo { self.logup_gkr } + /// Returns the index of the auxiliary column which implements the Lagrange kernel, if any. + pub fn lagrange_kernel_column_idx(&self) -> Option { + if self.logup_gkr_enabled() { + Some(self.aux_segment_width() - TraceInfo::LAGRANGE_KERNEL_OFFSET) + } else { + None + } + } + + /// Returns the index of the auxiliary column which implements the s-column, if any. + pub fn s_column_idx(&self) -> Option { + if self.logup_gkr_enabled() { + Some(self.aux_segment_width() - TraceInfo::S_COLUMN_OFFSET) + } else { + None + } + } + /// Returns the number of random elements needed to build all auxiliary columns, except for the /// Lagrange kernel column. pub fn get_num_aux_segment_rand_elements(&self) -> usize { diff --git a/air/src/lib.rs b/air/src/lib.rs index 050801a4e..2993306b9 100644 --- a/air/src/lib.rs +++ b/air/src/lib.rs @@ -47,7 +47,6 @@ pub use air::{ DeepCompositionCoefficients, EvaluationFrame, GkrData, LagrangeConstraintsCompositionCoefficients, LagrangeKernelBoundaryConstraint, LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, LagrangeKernelRandElements, - LagrangeKernelTransitionConstraints, LogUpGkrEvaluator, LogUpGkrOracle, - TraceInfo, TransitionConstraintDegree, TransitionConstraints, LAGRANGE_KERNEL_OFFSET, - S_COLUMN_OFFSET, + LagrangeKernelTransitionConstraints, LogUpGkrEvaluator, LogUpGkrOracle, TraceInfo, + TransitionConstraintDegree, TransitionConstraints, }; diff --git a/prover/src/constraints/evaluator/logup_gkr.rs b/prover/src/constraints/evaluator/logup_gkr.rs index 46d27846b..2d6ec7e37 100644 --- a/prover/src/constraints/evaluator/logup_gkr.rs +++ b/prover/src/constraints/evaluator/logup_gkr.rs @@ -8,7 +8,6 @@ use alloc::vec::Vec; use air::{ Air, EvaluationFrame, GkrData, LagrangeConstraintsCompositionCoefficients, LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, LogUpGkrEvaluator, - LAGRANGE_KERNEL_OFFSET, S_COLUMN_OFFSET, }; use math::{batch_inversion, FieldElement}; @@ -37,11 +36,11 @@ where ) -> Self { Self { lagrange_kernel_constraints: air + .get_logup_gkr_evaluator() .get_lagrange_kernel_constraints( lagrange_composition_coefficients, gkr_data.lagrange_kernel_rand_elements(), - ) - .expect("expected Lagrange kernel constraints to be present"), + ), air, gkr_data, s_col_composition_coefficient, @@ -74,8 +73,11 @@ where let evaluator = self.air.get_logup_gkr_evaluator(); let s_col_constraint_divisor = compute_s_col_divisor::(domain.ce_domain_size(), domain, self.air.trace_length()); - let s_col_idx = trace.trace_info().aux_segment_width() - S_COLUMN_OFFSET; - let l_col_idx = trace.trace_info().aux_segment_width() - LAGRANGE_KERNEL_OFFSET; + let s_col_idx = trace.trace_info().s_column_idx().expect("S-column should be present"); + let l_col_idx = trace + .trace_info() + .lagrange_kernel_column_idx() + .expect("Lagrange kernel should be present"); let mut main_frame = EvaluationFrame::new(trace.trace_info().main_segment_width()); let mut aux_frame = EvaluationFrame::new(trace.trace_info().aux_segment_width()); @@ -87,7 +89,7 @@ where // compute Lagrange kernel frame trace.read_lagrange_kernel_frame_into( step << lde_shift, - self.lagrange_kernel_constraints.lagrange_kernel_col_idx, + l_col_idx, &mut lagrange_frame, ); diff --git a/prover/src/lib.rs b/prover/src/lib.rs index a94306fab..703f19d8c 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -205,11 +205,7 @@ pub trait Prover { /// Builds and returns the auxiliary trace. #[allow(unused_variables)] #[maybe_async] - fn build_aux_trace( - &self, - main_trace: &Self::Trace, - aux_rand_elements: &[E], - ) -> ColMatrix + fn build_aux_trace(&self, main_trace: &Self::Trace, aux_rand_elements: &[E]) -> ColMatrix where E: FieldElement, { @@ -361,7 +357,7 @@ pub trait Prover { // This checks validity of both, assertions and state transitions. We do this in debug // mode only because this is a very expensive operation. #[cfg(debug_assertions)] - //trace.validate(&air, aux_trace_with_metadata.as_ref()); + trace.validate(&air, aux_trace_with_metadata.as_ref()); // Destructure `aux_trace_with_metadata`. let (aux_trace, aux_rand_elements, gkr_proof) = match aux_trace_with_metadata { diff --git a/verifier/src/evaluator.rs b/verifier/src/evaluator.rs index 2ca0c8181..74a4e98ea 100644 --- a/verifier/src/evaluator.rs +++ b/verifier/src/evaluator.rs @@ -7,7 +7,7 @@ use alloc::vec::Vec; use air::{ Air, AuxRandElements, ConstraintCompositionCoefficients, EvaluationFrame, - LagrangeKernelEvaluationFrame, LogUpGkrEvaluator, S_COLUMN_OFFSET, + LagrangeKernelEvaluationFrame, LogUpGkrEvaluator, }; use math::{polynom, FieldElement}; @@ -100,12 +100,10 @@ pub fn evaluate_constraints>( // Lagrange kernel constraints - let lagrange_constraints = air - .get_lagrange_kernel_constraints( - lagrange_coefficients, - &gkr_data.lagrange_kernel_eval_point, - ) - .expect("expected Lagrange kernel constraints to be present"); + let lagrange_constraints = air.get_logup_gkr_evaluator().get_lagrange_kernel_constraints( + lagrange_coefficients, + &gkr_data.lagrange_kernel_eval_point, + ); result += lagrange_constraints.transition.evaluate_and_combine::( lagrange_kernel_column_frame, @@ -116,7 +114,7 @@ pub fn evaluate_constraints>( // s-column constraints - let s_col_idx = air.trace_info().aux_segment_width() - S_COLUMN_OFFSET; + let s_col_idx = air.trace_info().s_column_idx().expect("s-column should be present"); let s_cur = aux_trace_frame .as_ref() .expect("expected aux rand elements to be present") @@ -131,13 +129,8 @@ pub fn evaluate_constraints>( let mean = batched_claim .mul_base(E::BaseField::ONE / E::BaseField::from(air.trace_length() as u32)); - let mut query = - vec![E::ZERO; air.get_logup_gkr_evaluator().get_oracles().len()]; - air.get_logup_gkr_evaluator().build_query( - main_trace_frame, - &[], - &mut query, - ); + let mut query = vec![E::ZERO; air.get_logup_gkr_evaluator().get_oracles().len()]; + air.get_logup_gkr_evaluator().build_query(main_trace_frame, &[], &mut query); let batched_claim_at_query = gkr_data.compute_batched_query::(&query); let rhs = s_cur - mean + batched_claim_at_query * l_cur; let lhs = s_nxt; From 150398377d49a87088f3ff733ccabbe8d0eacdae Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:40:47 +0200 Subject: [PATCH 53/58] chore: address feedback 2 --- air/src/air/logup_gkr.rs | 83 ++++--------------- air/src/air/mod.rs | 2 + air/src/air/s_column.rs | 57 +++++++++++++ prover/src/constraints/evaluator/logup_gkr.rs | 5 ++ verifier/src/evaluator.rs | 22 ++--- 5 files changed, 89 insertions(+), 80 deletions(-) create mode 100644 air/src/air/s_column.rs diff --git a/air/src/air/logup_gkr.rs b/air/src/air/logup_gkr.rs index fa0010e06..1f224e772 100644 --- a/air/src/air/logup_gkr.rs +++ b/air/src/air/logup_gkr.rs @@ -9,7 +9,11 @@ use core::marker::PhantomData; use crypto::{ElementHasher, RandomCoin}; use math::{ExtensionOf, FieldElement, StarkField, ToElements}; -use super::{EvaluationFrame, GkrData, LagrangeConstraintsCompositionCoefficients, LagrangeKernelConstraints, LagrangeKernelRandElements}; +use super::{ + s_column::SColumnConstraint, EvaluationFrame, GkrData, + LagrangeConstraintsCompositionCoefficients, LagrangeKernelConstraints, + LagrangeKernelRandElements, +}; /// A trait containing the necessary information in order to run the LogUp-GKR protocol of [1]. /// @@ -123,11 +127,19 @@ pub trait LogUpGkrEvaluator: Clone + Sync { lagrange_composition_coefficients: LagrangeConstraintsCompositionCoefficients, lagrange_kernel_rand_elements: &LagrangeKernelRandElements, ) -> LagrangeKernelConstraints { - LagrangeKernelConstraints::new( - lagrange_composition_coefficients, - lagrange_kernel_rand_elements, - ) - + LagrangeKernelConstraints::new( + lagrange_composition_coefficients, + lagrange_kernel_rand_elements, + ) + } + + /// Returns a new [`SColumnConstraints`]. + fn get_s_column_constraints>( + &self, + gkr_data: GkrData, + composition_coefficient: E, + ) -> SColumnConstraint { + SColumnConstraint::new(gkr_data, composition_coefficient) } } @@ -213,62 +225,3 @@ pub enum LogUpGkrOracle { /// must be a power of 2. PeriodicValue(Vec), } - -#[derive(Clone, Default)] -pub struct DummyLogUpGkrEval> { - _field: PhantomData, - _public_inputs: PhantomData

, -} - -impl LogUpGkrEvaluator for DummyLogUpGkrEval -where - B: StarkField, - P: Clone + Send + Sync + ToElements, -{ - type BaseField = B; - - type PublicInputs = P; - - fn get_oracles(&self) -> &[LogUpGkrOracle] { - panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") - } - - fn get_num_rand_values(&self) -> usize { - panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") - } - - fn get_num_fractions(&self) -> usize { - panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") - } - - fn max_degree(&self) -> usize { - panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") - } - - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) - where - E: FieldElement, - { - panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") - } - - fn evaluate_query( - &self, - _query: &[F], - _rand_values: &[E], - _numerator: &mut [E], - _denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + ExtensionOf, - { - panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") - } - - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") - } -} diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index b4c0c32af..37df67c9e 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -35,6 +35,8 @@ pub use lagrange::{ LagrangeKernelRandElements, LagrangeKernelTransitionConstraints, }; +mod s_column; + mod logup_gkr; pub use logup_gkr::{ LogUpGkrEvaluator, LogUpGkrOracle, diff --git a/air/src/air/s_column.rs b/air/src/air/s_column.rs new file mode 100644 index 000000000..b2fed82fe --- /dev/null +++ b/air/src/air/s_column.rs @@ -0,0 +1,57 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +use math::FieldElement; + +use crate::LogUpGkrEvaluator; + +use super::{Air, EvaluationFrame, GkrData}; + +/// Represents the transition constraint for the s-column, as well as the random coefficient used +/// to linearly combine the constraint into the constraint composition polynomial. +/// +/// The s-column implements the cohomological sum-check argument of [1] and the constraint in +/// [`SColumnConstraint`] is exactly Eq (4) in Lemma 1 in [1]. +/// +/// +/// [1]: https://eprint.iacr.org/2021/930 +pub struct SColumnConstraint { + gkr_data: GkrData, + composition_coefficient: E, +} + +impl SColumnConstraint { + pub fn new(gkr_data: GkrData, composition_coefficient: E) -> Self { + Self { gkr_data, composition_coefficient } + } + + /// Evaluates the transition constraint over the specificed main trace segment, s-column, + /// and Lagrange kernel evaluation frames. + pub fn evaluate( + &self, + air: &A, + main_trace_frame: &EvaluationFrame, + s_cur: E, + s_nxt: E, + l_cur: E, + x: E, + ) -> E + where + A: Air, + { + let batched_claim = self.gkr_data.compute_batched_claim(); + let mean = batched_claim + .mul_base(E::BaseField::ONE / E::BaseField::from(air.trace_length() as u32)); + + let mut query = vec![E::ZERO; air.get_logup_gkr_evaluator().get_oracles().len()]; + air.get_logup_gkr_evaluator().build_query(main_trace_frame, &[], &mut query); + let batched_claim_at_query = self.gkr_data.compute_batched_query::(&query); + let rhs = s_cur - mean + batched_claim_at_query * l_cur; + let lhs = s_nxt; + + let divisor = x.exp((air.trace_length() as u32).into()) - E::ONE; + self.composition_coefficient * (rhs - lhs) / divisor + } +} diff --git a/prover/src/constraints/evaluator/logup_gkr.rs b/prover/src/constraints/evaluator/logup_gkr.rs index 2d6ec7e37..44164ebba 100644 --- a/prover/src/constraints/evaluator/logup_gkr.rs +++ b/prover/src/constraints/evaluator/logup_gkr.rs @@ -126,6 +126,11 @@ where combined_evaluations }; + // compute and combine the transition constraints for the s-column. + // The s-column implements the cohomological sum-check argument of [1] and + // the constraint we enfore is exactly Eq (4) in Lemma 1 in [1]. + // + // [1]: https://eprint.iacr.org/2021/930 let s_col_combined_evaluation = { trace.read_main_trace_frame_into(step << lde_shift, &mut main_frame); trace.read_aux_trace_frame_into(step << lde_shift, &mut aux_frame); diff --git a/verifier/src/evaluator.rs b/verifier/src/evaluator.rs index 74a4e98ea..fc7fdf24c 100644 --- a/verifier/src/evaluator.rs +++ b/verifier/src/evaluator.rs @@ -125,22 +125,14 @@ pub fn evaluate_constraints>( .next()[s_col_idx]; let l_cur = lagrange_kernel_column_frame.inner()[0]; - let batched_claim = gkr_data.compute_batched_claim(); - let mean = batched_claim - .mul_base(E::BaseField::ONE / E::BaseField::from(air.trace_length() as u32)); - - let mut query = vec![E::ZERO; air.get_logup_gkr_evaluator().get_oracles().len()]; - air.get_logup_gkr_evaluator().build_query(main_trace_frame, &[], &mut query); - let batched_claim_at_query = gkr_data.compute_batched_query::(&query); - let rhs = s_cur - mean + batched_claim_at_query * l_cur; - let lhs = s_nxt; - - let divisor = x.exp((air.trace_length() as u32).into()) - E::ONE; - result += composition_coefficients + let s_column_cc = composition_coefficients .s_col - .expect("expected constraint composition coefficient for s-column to be present") - * (rhs - lhs) - / divisor; + .expect("expected constraint composition coefficient for s-column to be present"); + + let s_column_constraint = + air.get_logup_gkr_evaluator().get_s_column_constraints(gkr_data, s_column_cc); + + result += s_column_constraint.evaluate(air, main_trace_frame, s_cur, s_nxt, l_cur, x); } result From 1bdb8491ce50f924c7a866282cf17e49acae8c2a Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:56:49 +0200 Subject: [PATCH 54/58] chore: re-organise logup_gkr module --- air/src/air/aux.rs | 2 +- air/src/air/lagrange/boundary.rs | 71 --------- air/src/air/lagrange/frame.rs | 86 ----------- air/src/air/lagrange/mod.rs | 80 ---------- air/src/air/lagrange/transition.rs | 141 ------------------ air/src/air/logup_gkr.rs | 227 ----------------------------- air/src/air/mod.rs | 14 +- prover/src/tests/mod.rs | 4 +- 8 files changed, 7 insertions(+), 618 deletions(-) delete mode 100644 air/src/air/lagrange/boundary.rs delete mode 100644 air/src/air/lagrange/frame.rs delete mode 100644 air/src/air/lagrange/mod.rs delete mode 100644 air/src/air/lagrange/transition.rs delete mode 100644 air/src/air/logup_gkr.rs diff --git a/air/src/air/aux.rs b/air/src/air/aux.rs index 0c677d109..33d7d8539 100644 --- a/air/src/air/aux.rs +++ b/air/src/air/aux.rs @@ -7,7 +7,7 @@ use alloc::vec::Vec; use math::{ExtensionOf, FieldElement}; -use super::{lagrange::LagrangeKernelRandElements, LogUpGkrOracle}; +use super::{LagrangeKernelRandElements, LogUpGkrOracle}; /// Holds the randomly generated elements used in defining the auxiliary segment of the trace. /// diff --git a/air/src/air/lagrange/boundary.rs b/air/src/air/lagrange/boundary.rs deleted file mode 100644 index 5d1954615..000000000 --- a/air/src/air/lagrange/boundary.rs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -use math::FieldElement; - -use crate::{LagrangeKernelEvaluationFrame, LagrangeKernelRandElements}; - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct LagrangeKernelBoundaryConstraint -where - E: FieldElement, -{ - assertion_value: E, - composition_coefficient: E, -} - -impl LagrangeKernelBoundaryConstraint -where - E: FieldElement, -{ - /// Creates a new Lagrange kernel boundary constraint. - pub fn new( - composition_coefficient: E, - lagrange_kernel_rand_elements: &LagrangeKernelRandElements, - ) -> Self { - Self { - assertion_value: Self::assertion_value(lagrange_kernel_rand_elements), - composition_coefficient, - } - } - - /// Returns the evaluation of the boundary constraint at `x`, multiplied by the composition - /// coefficient. - /// - /// `frame` is the evaluation frame of the Lagrange kernel column `c`, starting at `c(x)` - pub fn evaluate_at(&self, x: E, frame: &LagrangeKernelEvaluationFrame) -> E { - let numerator = self.evaluate_numerator_at(frame); - let denominator = self.evaluate_denominator_at(x); - - numerator / denominator - } - - /// Returns the evaluation of the boundary constraint numerator, multiplied by the composition - /// coefficient. - /// - /// `frame` is the evaluation frame of the Lagrange kernel column `c`, starting at `c(x)` for - /// some `x` - pub fn evaluate_numerator_at(&self, frame: &LagrangeKernelEvaluationFrame) -> E { - let trace_value = frame.inner()[0]; - let constraint_evaluation = trace_value - self.assertion_value; - - constraint_evaluation * self.composition_coefficient - } - - /// Returns the evaluation of the boundary constraint denominator at point `x`. - pub fn evaluate_denominator_at(&self, x: E) -> E { - x - E::ONE - } - - /// Computes the assertion value given the provided random elements. - pub fn assertion_value(lagrange_kernel_rand_elements: &LagrangeKernelRandElements) -> E { - let mut assertion_value = E::ONE; - for &rand_ele in lagrange_kernel_rand_elements.as_ref() { - assertion_value *= E::ONE - rand_ele; - } - - assertion_value - } -} diff --git a/air/src/air/lagrange/frame.rs b/air/src/air/lagrange/frame.rs deleted file mode 100644 index d0ffc4fa4..000000000 --- a/air/src/air/lagrange/frame.rs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -use alloc::vec::Vec; - -use math::{polynom, FieldElement, StarkField}; - -/// The evaluation frame for the Lagrange kernel. -/// -/// The Lagrange kernel's evaluation frame is different from [`crate::EvaluationFrame`]. -/// Specifically, -/// - it only contains evaluations from the Lagrange kernel column compared to all columns in the -/// case of [`crate::EvaluationFrame`]) -/// - The column is evaluated at points `x`, `gx`, `g^2 x`, ..., `g^(2^(v-1)) x`, where `x` is an -/// arbitrary point, and `g` is the trace domain generator -#[derive(Debug, Clone)] -pub struct LagrangeKernelEvaluationFrame { - frame: Vec, -} - -impl LagrangeKernelEvaluationFrame { - // CONSTRUCTORS - // -------------------------------------------------------------------------------------------- - - /// Constructs a Lagrange kernel evaluation frame from the raw column polynomial evaluations. - pub fn new(frame: Vec) -> Self { - Self { frame } - } - - /// Constructs an empty Lagrange kernel evaluation frame from the raw column polynomial - /// evaluations. The frame can subsequently be filled using [`Self::frame_mut`]. - pub fn new_empty() -> Self { - Self { frame: Vec::new() } - } - - /// Constructs the frame from the Lagrange kernel column trace polynomial coefficients for an - /// evaluation point. - pub fn from_lagrange_kernel_column_poly(lagrange_kernel_col_poly: &[E], z: E) -> Self { - let log_trace_len = lagrange_kernel_col_poly.len().ilog2(); - let g = E::from(E::BaseField::get_root_of_unity(log_trace_len)); - - let mut frame = Vec::with_capacity(log_trace_len as usize + 1); - - // push c(x) - frame.push(polynom::eval(lagrange_kernel_col_poly, z)); - - // push c(z * g), c(z * g^2), c(z * g^4), ..., c(z * g^(2^(v-1))) - let mut g_exp = g; - for _ in 0..log_trace_len { - let x = g_exp * z; - let lagrange_poly_at_x = polynom::eval(lagrange_kernel_col_poly, x); - - frame.push(lagrange_poly_at_x); - - // takes on the values `g`, `g^2`, `g^4`, `g^8`, ... - g_exp *= g_exp; - } - - Self { frame } - } - - // MUTATORS - // -------------------------------------------------------------------------------------------- - - /// Returns a mutable reference to the inner frame. - pub fn frame_mut(&mut self) -> &mut Vec { - &mut self.frame - } - - // ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns a reference to the inner frame. - pub fn inner(&self) -> &[E] { - &self.frame - } - - /// Returns the number of rows in the frame. - /// - /// This is equal to `log(trace_length) + 1`. - pub fn num_rows(&self) -> usize { - self.frame.len() - } -} diff --git a/air/src/air/lagrange/mod.rs b/air/src/air/lagrange/mod.rs deleted file mode 100644 index 8f352ad45..000000000 --- a/air/src/air/lagrange/mod.rs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -mod boundary; -use alloc::vec::Vec; -use core::ops::Deref; - -pub use boundary::LagrangeKernelBoundaryConstraint; - -mod frame; -pub use frame::LagrangeKernelEvaluationFrame; - -mod transition; -use math::FieldElement; -pub use transition::LagrangeKernelTransitionConstraints; - -use crate::LagrangeConstraintsCompositionCoefficients; - -/// Represents the Lagrange kernel transition and boundary constraints. -pub struct LagrangeKernelConstraints { - pub transition: LagrangeKernelTransitionConstraints, - pub boundary: LagrangeKernelBoundaryConstraint, -} - -impl LagrangeKernelConstraints { - /// Constructs a new [`LagrangeKernelConstraints`]. - pub fn new( - lagrange_composition_coefficients: LagrangeConstraintsCompositionCoefficients, - lagrange_kernel_rand_elements: &LagrangeKernelRandElements, - ) -> Self { - Self { - transition: LagrangeKernelTransitionConstraints::new( - lagrange_composition_coefficients.transition, - ), - boundary: LagrangeKernelBoundaryConstraint::new( - lagrange_composition_coefficients.boundary, - lagrange_kernel_rand_elements, - ), - } - } -} - -/// Holds the randomly generated elements needed to build the Lagrange kernel auxiliary column. -/// -/// The Lagrange kernel consists of evaluating the function $eq(x, r)$, where $x$ is the binary -/// decomposition of the row index, and $r$ is some random point. The "Lagrange kernel random -/// elements" refer to this (multidimensional) point $r$. -#[derive(Debug, Clone, Default)] -pub struct LagrangeKernelRandElements { - elements: Vec, -} - -impl LagrangeKernelRandElements { - /// Creates a new [`LagrangeKernelRandElements`]. - pub fn new(elements: Vec) -> Self { - Self { elements } - } -} - -impl Deref for LagrangeKernelRandElements { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.elements - } -} - -impl AsRef<[E]> for LagrangeKernelRandElements { - fn as_ref(&self) -> &[E] { - &self.elements - } -} - -impl From> for Vec { - fn from(lagrange_rand_elements: LagrangeKernelRandElements) -> Self { - lagrange_rand_elements.elements - } -} diff --git a/air/src/air/lagrange/transition.rs b/air/src/air/lagrange/transition.rs deleted file mode 100644 index 18bdfa9be..000000000 --- a/air/src/air/lagrange/transition.rs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -use alloc::vec::Vec; - -use math::{ExtensionOf, FieldElement}; - -use crate::{ConstraintDivisor, LagrangeKernelEvaluationFrame}; - -/// Represents the transition constraints for the Lagrange kernel column, as well as the random -/// coefficients used to linearly combine all the constraints. -/// -/// There are `log(trace_len)` constraints, each with its own divisor, as described in -/// [this issue](https://github.com/facebook/winterfell/issues/240). -pub struct LagrangeKernelTransitionConstraints { - lagrange_constraint_coefficients: Vec, - divisors: Vec>, -} - -impl LagrangeKernelTransitionConstraints { - /// Creates a new [`LagrangeKernelTransitionConstraints`], which represents the Lagrange kernel - /// transition constraints as well as the random coefficients necessary to combine the - /// constraints together. - pub fn new(lagrange_constraint_coefficients: Vec) -> Self { - let num_lagrange_kernel_transition_constraints = lagrange_constraint_coefficients.len(); - - let divisors = { - let mut divisors = Vec::with_capacity(num_lagrange_kernel_transition_constraints); - for i in 0..num_lagrange_kernel_transition_constraints { - let constraint_domain_size = 2_usize.pow(i as u32); - let divisor = ConstraintDivisor::from_transition(constraint_domain_size, 0); - - divisors.push(divisor); - } - divisors - }; - - Self { - lagrange_constraint_coefficients, - divisors, - } - } - - /// Evaluates the numerator of the `constraint_idx`th transition constraint. - pub fn evaluate_ith_numerator( - &self, - lagrange_kernel_column_frame: &LagrangeKernelEvaluationFrame, - lagrange_kernel_rand_elements: &[E], - constraint_idx: usize, - ) -> E - where - F: FieldElement, - E: ExtensionOf, - { - let c = lagrange_kernel_column_frame.inner(); - let v = c.len() - 1; - let r = lagrange_kernel_rand_elements; - let k = constraint_idx + 1; - - let eval = (r[v - k] * c[0]) - ((E::ONE - r[v - k]) * c[v - k + 1]); - - self.lagrange_constraint_coefficients[constraint_idx].mul_base(eval) - } - - /// Evaluates the divisor of the `constraint_idx`th transition constraint. - pub fn evaluate_ith_divisor(&self, constraint_idx: usize, x: F) -> E - where - F: FieldElement, - E: ExtensionOf, - { - self.divisors[constraint_idx].evaluate_at(x.into()) - } - - /// Evaluates the transition constraints over the specificed Lagrange kernel evaluation frame, - /// and combines them. - /// - /// By "combining transition constraints evaluations", we mean computing a linear combination of - /// each transition constraint evaluation, where each transition evaluation is divided by its - /// corresponding divisor. - pub fn evaluate_and_combine( - &self, - lagrange_kernel_column_frame: &LagrangeKernelEvaluationFrame, - lagrange_kernel_rand_elements: &[E], - x: F, - ) -> E - where - F: FieldElement, - E: ExtensionOf, - { - let numerators = self - .evaluate_numerators::(lagrange_kernel_column_frame, lagrange_kernel_rand_elements); - - numerators - .iter() - .zip(self.divisors.iter()) - .fold(E::ZERO, |acc, (&numerator, divisor)| { - let z = divisor.evaluate_at(x); - - acc + (numerator / z.into()) - }) - } - - /// Returns the number of constraints. - pub fn num_constraints(&self) -> usize { - self.lagrange_constraint_coefficients.len() - } - - // HELPERS - // --------------------------------------------------------------------------------------------- - - /// Evaluates the transition constraints' numerators over the specified Lagrange kernel - /// evaluation frame. - fn evaluate_numerators( - &self, - lagrange_kernel_column_frame: &LagrangeKernelEvaluationFrame, - lagrange_kernel_rand_elements: &[E], - ) -> Vec - where - F: FieldElement, - E: ExtensionOf, - { - let log2_trace_len = lagrange_kernel_column_frame.num_rows() - 1; - let mut transition_evals = vec![E::ZERO; log2_trace_len]; - - let c = lagrange_kernel_column_frame.inner(); - let v = c.len() - 1; - let r = lagrange_kernel_rand_elements; - - for k in 1..v + 1 { - transition_evals[k - 1] = (r[v - k] * c[0]) - ((E::ONE - r[v - k]) * c[v - k + 1]); - } - - transition_evals - .into_iter() - .zip(self.lagrange_constraint_coefficients.iter()) - .map(|(transition_eval, &coeff)| coeff.mul_base(transition_eval)) - .collect() - } -} diff --git a/air/src/air/logup_gkr.rs b/air/src/air/logup_gkr.rs deleted file mode 100644 index 1f224e772..000000000 --- a/air/src/air/logup_gkr.rs +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -use alloc::vec::Vec; -use core::marker::PhantomData; - -use crypto::{ElementHasher, RandomCoin}; -use math::{ExtensionOf, FieldElement, StarkField, ToElements}; - -use super::{ - s_column::SColumnConstraint, EvaluationFrame, GkrData, - LagrangeConstraintsCompositionCoefficients, LagrangeKernelConstraints, - LagrangeKernelRandElements, -}; - -/// A trait containing the necessary information in order to run the LogUp-GKR protocol of [1]. -/// -/// The trait contains useful information for running the GKR protocol as well as for implementing -/// the univariate IOP for multi-linear evaluation of Section 5 in [1] for the final evaluation -/// check resulting from GKR. -/// -/// [1]: https://eprint.iacr.org/2023/1284 -pub trait LogUpGkrEvaluator: Clone + Sync { - /// Defines the base field of the evaluator. - type BaseField: StarkField; - - /// Public inputs need to compute the final claim. - type PublicInputs: ToElements + Send; - - /// Gets a list of all oracles involved in LogUp-GKR; this is intended to be used in construction of - /// MLEs. - fn get_oracles(&self) -> &[LogUpGkrOracle]; - - /// Returns the number of random values needed to evaluate a query. - fn get_num_rand_values(&self) -> usize; - - /// Returns the number of fractions in the LogUp-GKR statement. - fn get_num_fractions(&self) -> usize; - - /// Returns the maximal degree of the multi-variate associated to the input layer. - /// - /// This is equal to the max of $1 + deg_k(\text{numerator}_i) * deg_k(\text{denominator}_j)$ where - /// $i$ and $j$ range over the number of numerators and denominators, respectively, and $deg_k$ - /// is the degree of a multi-variate polynomial in its $k$-th variable. - fn max_degree(&self) -> usize; - - /// Builds a query from the provided main trace frame and periodic values. - /// - /// Note: it should be possible to provide an implementation of this method based on the - /// information returned from `get_oracles()`. However, this implementation is likely to be - /// expensive compared to the hand-written implementation. However, we could provide a test - /// which verifies that `get_oracles()` and `build_query()` methods are consistent. - fn build_query(&self, frame: &EvaluationFrame, periodic_values: &[E], query: &mut [E]) - where - E: FieldElement; - - /// Evaluates the provided query and writes the results into the numerators and denominators. - /// - /// Note: it is also possible to combine `build_query()` and `evaluate_query()` into a single - /// method to avoid the need to first build the query struct and then evaluate it. However: - /// - We assume that the compiler will be able to optimize this away. - /// - Merging the methods will make it more difficult avoid inconsistencies between - /// `evaluate_query()` and `get_oracles()` methods. - fn evaluate_query( - &self, - query: &[F], - logup_randomness: &[E], - numerators: &mut [E], - denominators: &mut [E], - ) where - F: FieldElement, - E: FieldElement + ExtensionOf; - - /// Computes the final claim for the LogUp-GKR circuit. - /// - /// The default implementation of this method returns E::ZERO as it is expected that the - /// fractional sums will cancel out. However, in cases when some boundary conditions need to - /// be imposed on the LogUp-GKR relations, this method can be overridden to compute the final - /// expected claim. - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - E::ZERO - } - - /// Generates the data needed for running the univariate IOP for multi-linear evaluation of [1]. - /// - /// This mainly generates the batching randomness used to batch a number of multi-linear - /// evaluation claims and includes some additional data that is needed for building/verifying - /// the univariate IOP for multi-linear evaluation of [1]. - /// - /// This is the $\lambda$ randomness in section 5.2 in [1] but using different random values for - /// each term instead of powers of a single random element. - /// - /// [1]: https://eprint.iacr.org/2023/1284 - fn generate_univariate_iop_for_multi_linear_opening_data( - &self, - openings: Vec, - eval_point: Vec, - public_coin: &mut impl RandomCoin, - ) -> GkrData - where - E: FieldElement, - H: ElementHasher, - { - public_coin.reseed(H::hash_elements(&openings)); - - let mut batching_randomness = Vec::with_capacity(openings.len() - 1); - for _ in 0..openings.len() - 1 { - batching_randomness.push(public_coin.draw().expect("failed to generate randomness")) - } - - GkrData::new( - LagrangeKernelRandElements::new(eval_point), - batching_randomness, - openings, - self.get_oracles().to_vec(), - ) - } - - /// Returns a new [`LagrangeKernelConstraints`]. - fn get_lagrange_kernel_constraints>( - &self, - lagrange_composition_coefficients: LagrangeConstraintsCompositionCoefficients, - lagrange_kernel_rand_elements: &LagrangeKernelRandElements, - ) -> LagrangeKernelConstraints { - LagrangeKernelConstraints::new( - lagrange_composition_coefficients, - lagrange_kernel_rand_elements, - ) - } - - /// Returns a new [`SColumnConstraints`]. - fn get_s_column_constraints>( - &self, - gkr_data: GkrData, - composition_coefficient: E, - ) -> SColumnConstraint { - SColumnConstraint::new(gkr_data, composition_coefficient) - } -} - -#[derive(Clone, Default)] -pub(crate) struct PhantomLogUpGkrEval> { - _field: PhantomData, - _public_inputs: PhantomData

, -} - -impl PhantomLogUpGkrEval -where - B: StarkField, - P: Clone + Send + Sync + ToElements, -{ - pub fn new() -> Self { - Self { - _field: PhantomData, - _public_inputs: PhantomData, - } - } -} - -impl LogUpGkrEvaluator for PhantomLogUpGkrEval -where - B: StarkField, - P: Clone + Send + Sync + ToElements, -{ - type BaseField = B; - - type PublicInputs = P; - - fn get_oracles(&self) -> &[LogUpGkrOracle] { - panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") - } - - fn get_num_rand_values(&self) -> usize { - panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") - } - - fn get_num_fractions(&self) -> usize { - panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") - } - - fn max_degree(&self) -> usize { - panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") - } - - fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) - where - E: FieldElement, - { - panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") - } - - fn evaluate_query( - &self, - _query: &[F], - _rand_values: &[E], - _numerator: &mut [E], - _denominator: &mut [E], - ) where - F: FieldElement, - E: FieldElement + ExtensionOf, - { - panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") - } - - fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E - where - E: FieldElement, - { - panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") - } -} - -#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] -pub enum LogUpGkrOracle { - /// A column with a given index in the main trace segment. - CurrentRow(usize), - /// A column with a given index in the main trace segment but shifted upwards. - NextRow(usize), - /// A virtual periodic column defined by its values in a given cycle. Note that the cycle length - /// must be a power of 2. - PeriodicValue(Vec), -} diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index 37df67c9e..bedfa5e35 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -6,7 +6,6 @@ use alloc::{collections::BTreeMap, vec::Vec}; use crypto::{RandomCoin, RandomCoinError}; -use logup_gkr::PhantomLogUpGkrEval; use math::{fft, ExtensibleField, ExtensionOf, FieldElement, StarkField, ToElements}; use crate::ProofOptions; @@ -29,17 +28,12 @@ pub use boundary::{BoundaryConstraint, BoundaryConstraintGroup, BoundaryConstrai mod transition; pub use transition::{EvaluationFrame, TransitionConstraintDegree, TransitionConstraints}; -mod lagrange; -pub use lagrange::{ - LagrangeKernelBoundaryConstraint, LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, - LagrangeKernelRandElements, LagrangeKernelTransitionConstraints, -}; - -mod s_column; - mod logup_gkr; +use logup_gkr::PhantomLogUpGkrEval; pub use logup_gkr::{ - LogUpGkrEvaluator, LogUpGkrOracle, + LagrangeKernelBoundaryConstraint, LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, + LagrangeKernelRandElements, LagrangeKernelTransitionConstraints, LogUpGkrEvaluator, + LogUpGkrOracle, }; mod coefficients; diff --git a/prover/src/tests/mod.rs b/prover/src/tests/mod.rs index d42ebdcfe..5132e2025 100644 --- a/prover/src/tests/mod.rs +++ b/prover/src/tests/mod.rs @@ -6,8 +6,8 @@ use alloc::vec::Vec; use air::{ - Air, AirContext, Assertion, EvaluationFrame, FieldExtension, ProofOptions, - TraceInfo, TransitionConstraintDegree, + Air, AirContext, Assertion, EvaluationFrame, FieldExtension, ProofOptions, TraceInfo, + TransitionConstraintDegree, }; use math::{fields::f64::BaseElement, FieldElement, StarkField}; From 431215ab854f755aab2166d830402d565a8994e9 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:57:12 +0200 Subject: [PATCH 55/58] chore: re-organise logup_gkr module --- air/src/air/logup_gkr/lagrange/boundary.rs | 71 ++++++ air/src/air/logup_gkr/lagrange/frame.rs | 86 +++++++ air/src/air/logup_gkr/lagrange/mod.rs | 81 +++++++ air/src/air/logup_gkr/lagrange/transition.rs | 141 +++++++++++ air/src/air/logup_gkr/mod.rs | 231 +++++++++++++++++++ air/src/air/logup_gkr/s_column.rs | 56 +++++ 6 files changed, 666 insertions(+) create mode 100644 air/src/air/logup_gkr/lagrange/boundary.rs create mode 100644 air/src/air/logup_gkr/lagrange/frame.rs create mode 100644 air/src/air/logup_gkr/lagrange/mod.rs create mode 100644 air/src/air/logup_gkr/lagrange/transition.rs create mode 100644 air/src/air/logup_gkr/mod.rs create mode 100644 air/src/air/logup_gkr/s_column.rs diff --git a/air/src/air/logup_gkr/lagrange/boundary.rs b/air/src/air/logup_gkr/lagrange/boundary.rs new file mode 100644 index 000000000..f3cc886aa --- /dev/null +++ b/air/src/air/logup_gkr/lagrange/boundary.rs @@ -0,0 +1,71 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +use math::FieldElement; + +use super::{LagrangeKernelEvaluationFrame, LagrangeKernelRandElements}; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct LagrangeKernelBoundaryConstraint +where + E: FieldElement, +{ + assertion_value: E, + composition_coefficient: E, +} + +impl LagrangeKernelBoundaryConstraint +where + E: FieldElement, +{ + /// Creates a new Lagrange kernel boundary constraint. + pub fn new( + composition_coefficient: E, + lagrange_kernel_rand_elements: &LagrangeKernelRandElements, + ) -> Self { + Self { + assertion_value: Self::assertion_value(lagrange_kernel_rand_elements), + composition_coefficient, + } + } + + /// Returns the evaluation of the boundary constraint at `x`, multiplied by the composition + /// coefficient. + /// + /// `frame` is the evaluation frame of the Lagrange kernel column `c`, starting at `c(x)` + pub fn evaluate_at(&self, x: E, frame: &LagrangeKernelEvaluationFrame) -> E { + let numerator = self.evaluate_numerator_at(frame); + let denominator = self.evaluate_denominator_at(x); + + numerator / denominator + } + + /// Returns the evaluation of the boundary constraint numerator, multiplied by the composition + /// coefficient. + /// + /// `frame` is the evaluation frame of the Lagrange kernel column `c`, starting at `c(x)` for + /// some `x` + pub fn evaluate_numerator_at(&self, frame: &LagrangeKernelEvaluationFrame) -> E { + let trace_value = frame.inner()[0]; + let constraint_evaluation = trace_value - self.assertion_value; + + constraint_evaluation * self.composition_coefficient + } + + /// Returns the evaluation of the boundary constraint denominator at point `x`. + pub fn evaluate_denominator_at(&self, x: E) -> E { + x - E::ONE + } + + /// Computes the assertion value given the provided random elements. + pub fn assertion_value(lagrange_kernel_rand_elements: &LagrangeKernelRandElements) -> E { + let mut assertion_value = E::ONE; + for &rand_ele in lagrange_kernel_rand_elements.as_ref() { + assertion_value *= E::ONE - rand_ele; + } + + assertion_value + } +} diff --git a/air/src/air/logup_gkr/lagrange/frame.rs b/air/src/air/logup_gkr/lagrange/frame.rs new file mode 100644 index 000000000..d0ffc4fa4 --- /dev/null +++ b/air/src/air/logup_gkr/lagrange/frame.rs @@ -0,0 +1,86 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +use alloc::vec::Vec; + +use math::{polynom, FieldElement, StarkField}; + +/// The evaluation frame for the Lagrange kernel. +/// +/// The Lagrange kernel's evaluation frame is different from [`crate::EvaluationFrame`]. +/// Specifically, +/// - it only contains evaluations from the Lagrange kernel column compared to all columns in the +/// case of [`crate::EvaluationFrame`]) +/// - The column is evaluated at points `x`, `gx`, `g^2 x`, ..., `g^(2^(v-1)) x`, where `x` is an +/// arbitrary point, and `g` is the trace domain generator +#[derive(Debug, Clone)] +pub struct LagrangeKernelEvaluationFrame { + frame: Vec, +} + +impl LagrangeKernelEvaluationFrame { + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Constructs a Lagrange kernel evaluation frame from the raw column polynomial evaluations. + pub fn new(frame: Vec) -> Self { + Self { frame } + } + + /// Constructs an empty Lagrange kernel evaluation frame from the raw column polynomial + /// evaluations. The frame can subsequently be filled using [`Self::frame_mut`]. + pub fn new_empty() -> Self { + Self { frame: Vec::new() } + } + + /// Constructs the frame from the Lagrange kernel column trace polynomial coefficients for an + /// evaluation point. + pub fn from_lagrange_kernel_column_poly(lagrange_kernel_col_poly: &[E], z: E) -> Self { + let log_trace_len = lagrange_kernel_col_poly.len().ilog2(); + let g = E::from(E::BaseField::get_root_of_unity(log_trace_len)); + + let mut frame = Vec::with_capacity(log_trace_len as usize + 1); + + // push c(x) + frame.push(polynom::eval(lagrange_kernel_col_poly, z)); + + // push c(z * g), c(z * g^2), c(z * g^4), ..., c(z * g^(2^(v-1))) + let mut g_exp = g; + for _ in 0..log_trace_len { + let x = g_exp * z; + let lagrange_poly_at_x = polynom::eval(lagrange_kernel_col_poly, x); + + frame.push(lagrange_poly_at_x); + + // takes on the values `g`, `g^2`, `g^4`, `g^8`, ... + g_exp *= g_exp; + } + + Self { frame } + } + + // MUTATORS + // -------------------------------------------------------------------------------------------- + + /// Returns a mutable reference to the inner frame. + pub fn frame_mut(&mut self) -> &mut Vec { + &mut self.frame + } + + // ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns a reference to the inner frame. + pub fn inner(&self) -> &[E] { + &self.frame + } + + /// Returns the number of rows in the frame. + /// + /// This is equal to `log(trace_length) + 1`. + pub fn num_rows(&self) -> usize { + self.frame.len() + } +} diff --git a/air/src/air/logup_gkr/lagrange/mod.rs b/air/src/air/logup_gkr/lagrange/mod.rs new file mode 100644 index 000000000..9d80b4437 --- /dev/null +++ b/air/src/air/logup_gkr/lagrange/mod.rs @@ -0,0 +1,81 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +use alloc::vec::Vec; +use core::ops::Deref; + +use math::FieldElement; + +mod boundary; +pub use boundary::LagrangeKernelBoundaryConstraint; + +mod frame; +pub use frame::LagrangeKernelEvaluationFrame; + +mod transition; +pub use transition::LagrangeKernelTransitionConstraints; + +use crate::LagrangeConstraintsCompositionCoefficients; + +/// Represents the Lagrange kernel transition and boundary constraints. +pub struct LagrangeKernelConstraints { + pub transition: LagrangeKernelTransitionConstraints, + pub boundary: LagrangeKernelBoundaryConstraint, +} + +impl LagrangeKernelConstraints { + /// Constructs a new [`LagrangeKernelConstraints`]. + pub fn new( + lagrange_composition_coefficients: LagrangeConstraintsCompositionCoefficients, + lagrange_kernel_rand_elements: &LagrangeKernelRandElements, + ) -> Self { + Self { + transition: LagrangeKernelTransitionConstraints::new( + lagrange_composition_coefficients.transition, + ), + boundary: LagrangeKernelBoundaryConstraint::new( + lagrange_composition_coefficients.boundary, + lagrange_kernel_rand_elements, + ), + } + } +} + +/// Holds the randomly generated elements needed to build the Lagrange kernel auxiliary column. +/// +/// The Lagrange kernel consists of evaluating the function $eq(x, r)$, where $x$ is the binary +/// decomposition of the row index, and $r$ is some random point. The "Lagrange kernel random +/// elements" refer to this (multidimensional) point $r$. +#[derive(Debug, Clone, Default)] +pub struct LagrangeKernelRandElements { + elements: Vec, +} + +impl LagrangeKernelRandElements { + /// Creates a new [`LagrangeKernelRandElements`]. + pub fn new(elements: Vec) -> Self { + Self { elements } + } +} + +impl Deref for LagrangeKernelRandElements { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.elements + } +} + +impl AsRef<[E]> for LagrangeKernelRandElements { + fn as_ref(&self) -> &[E] { + &self.elements + } +} + +impl From> for Vec { + fn from(lagrange_rand_elements: LagrangeKernelRandElements) -> Self { + lagrange_rand_elements.elements + } +} diff --git a/air/src/air/logup_gkr/lagrange/transition.rs b/air/src/air/logup_gkr/lagrange/transition.rs new file mode 100644 index 000000000..18bdfa9be --- /dev/null +++ b/air/src/air/logup_gkr/lagrange/transition.rs @@ -0,0 +1,141 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +use alloc::vec::Vec; + +use math::{ExtensionOf, FieldElement}; + +use crate::{ConstraintDivisor, LagrangeKernelEvaluationFrame}; + +/// Represents the transition constraints for the Lagrange kernel column, as well as the random +/// coefficients used to linearly combine all the constraints. +/// +/// There are `log(trace_len)` constraints, each with its own divisor, as described in +/// [this issue](https://github.com/facebook/winterfell/issues/240). +pub struct LagrangeKernelTransitionConstraints { + lagrange_constraint_coefficients: Vec, + divisors: Vec>, +} + +impl LagrangeKernelTransitionConstraints { + /// Creates a new [`LagrangeKernelTransitionConstraints`], which represents the Lagrange kernel + /// transition constraints as well as the random coefficients necessary to combine the + /// constraints together. + pub fn new(lagrange_constraint_coefficients: Vec) -> Self { + let num_lagrange_kernel_transition_constraints = lagrange_constraint_coefficients.len(); + + let divisors = { + let mut divisors = Vec::with_capacity(num_lagrange_kernel_transition_constraints); + for i in 0..num_lagrange_kernel_transition_constraints { + let constraint_domain_size = 2_usize.pow(i as u32); + let divisor = ConstraintDivisor::from_transition(constraint_domain_size, 0); + + divisors.push(divisor); + } + divisors + }; + + Self { + lagrange_constraint_coefficients, + divisors, + } + } + + /// Evaluates the numerator of the `constraint_idx`th transition constraint. + pub fn evaluate_ith_numerator( + &self, + lagrange_kernel_column_frame: &LagrangeKernelEvaluationFrame, + lagrange_kernel_rand_elements: &[E], + constraint_idx: usize, + ) -> E + where + F: FieldElement, + E: ExtensionOf, + { + let c = lagrange_kernel_column_frame.inner(); + let v = c.len() - 1; + let r = lagrange_kernel_rand_elements; + let k = constraint_idx + 1; + + let eval = (r[v - k] * c[0]) - ((E::ONE - r[v - k]) * c[v - k + 1]); + + self.lagrange_constraint_coefficients[constraint_idx].mul_base(eval) + } + + /// Evaluates the divisor of the `constraint_idx`th transition constraint. + pub fn evaluate_ith_divisor(&self, constraint_idx: usize, x: F) -> E + where + F: FieldElement, + E: ExtensionOf, + { + self.divisors[constraint_idx].evaluate_at(x.into()) + } + + /// Evaluates the transition constraints over the specificed Lagrange kernel evaluation frame, + /// and combines them. + /// + /// By "combining transition constraints evaluations", we mean computing a linear combination of + /// each transition constraint evaluation, where each transition evaluation is divided by its + /// corresponding divisor. + pub fn evaluate_and_combine( + &self, + lagrange_kernel_column_frame: &LagrangeKernelEvaluationFrame, + lagrange_kernel_rand_elements: &[E], + x: F, + ) -> E + where + F: FieldElement, + E: ExtensionOf, + { + let numerators = self + .evaluate_numerators::(lagrange_kernel_column_frame, lagrange_kernel_rand_elements); + + numerators + .iter() + .zip(self.divisors.iter()) + .fold(E::ZERO, |acc, (&numerator, divisor)| { + let z = divisor.evaluate_at(x); + + acc + (numerator / z.into()) + }) + } + + /// Returns the number of constraints. + pub fn num_constraints(&self) -> usize { + self.lagrange_constraint_coefficients.len() + } + + // HELPERS + // --------------------------------------------------------------------------------------------- + + /// Evaluates the transition constraints' numerators over the specified Lagrange kernel + /// evaluation frame. + fn evaluate_numerators( + &self, + lagrange_kernel_column_frame: &LagrangeKernelEvaluationFrame, + lagrange_kernel_rand_elements: &[E], + ) -> Vec + where + F: FieldElement, + E: ExtensionOf, + { + let log2_trace_len = lagrange_kernel_column_frame.num_rows() - 1; + let mut transition_evals = vec![E::ZERO; log2_trace_len]; + + let c = lagrange_kernel_column_frame.inner(); + let v = c.len() - 1; + let r = lagrange_kernel_rand_elements; + + for k in 1..v + 1 { + transition_evals[k - 1] = (r[v - k] * c[0]) - ((E::ONE - r[v - k]) * c[v - k + 1]); + } + + transition_evals + .into_iter() + .zip(self.lagrange_constraint_coefficients.iter()) + .map(|(transition_eval, &coeff)| coeff.mul_base(transition_eval)) + .collect() + } +} diff --git a/air/src/air/logup_gkr/mod.rs b/air/src/air/logup_gkr/mod.rs new file mode 100644 index 000000000..a907fad40 --- /dev/null +++ b/air/src/air/logup_gkr/mod.rs @@ -0,0 +1,231 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +use alloc::vec::Vec; +use core::marker::PhantomData; + +use crypto::{ElementHasher, RandomCoin}; +use math::{ExtensionOf, FieldElement, StarkField, ToElements}; + +use super::{EvaluationFrame, GkrData, LagrangeConstraintsCompositionCoefficients}; +mod s_column; +use s_column::SColumnConstraint; + +mod lagrange; +pub use lagrange::{ + LagrangeKernelBoundaryConstraint, LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, + LagrangeKernelRandElements, LagrangeKernelTransitionConstraints, +}; + +/// A trait containing the necessary information in order to run the LogUp-GKR protocol of [1]. +/// +/// The trait contains useful information for running the GKR protocol as well as for implementing +/// the univariate IOP for multi-linear evaluation of Section 5 in [1] for the final evaluation +/// check resulting from GKR. +/// +/// [1]: https://eprint.iacr.org/2023/1284 +pub trait LogUpGkrEvaluator: Clone + Sync { + /// Defines the base field of the evaluator. + type BaseField: StarkField; + + /// Public inputs need to compute the final claim. + type PublicInputs: ToElements + Send; + + /// Gets a list of all oracles involved in LogUp-GKR; this is intended to be used in construction of + /// MLEs. + fn get_oracles(&self) -> &[LogUpGkrOracle]; + + /// Returns the number of random values needed to evaluate a query. + fn get_num_rand_values(&self) -> usize; + + /// Returns the number of fractions in the LogUp-GKR statement. + fn get_num_fractions(&self) -> usize; + + /// Returns the maximal degree of the multi-variate associated to the input layer. + /// + /// This is equal to the max of $1 + deg_k(\text{numerator}_i) * deg_k(\text{denominator}_j)$ where + /// $i$ and $j$ range over the number of numerators and denominators, respectively, and $deg_k$ + /// is the degree of a multi-variate polynomial in its $k$-th variable. + fn max_degree(&self) -> usize; + + /// Builds a query from the provided main trace frame and periodic values. + /// + /// Note: it should be possible to provide an implementation of this method based on the + /// information returned from `get_oracles()`. However, this implementation is likely to be + /// expensive compared to the hand-written implementation. However, we could provide a test + /// which verifies that `get_oracles()` and `build_query()` methods are consistent. + fn build_query(&self, frame: &EvaluationFrame, periodic_values: &[E], query: &mut [E]) + where + E: FieldElement; + + /// Evaluates the provided query and writes the results into the numerators and denominators. + /// + /// Note: it is also possible to combine `build_query()` and `evaluate_query()` into a single + /// method to avoid the need to first build the query struct and then evaluate it. However: + /// - We assume that the compiler will be able to optimize this away. + /// - Merging the methods will make it more difficult avoid inconsistencies between + /// `evaluate_query()` and `get_oracles()` methods. + fn evaluate_query( + &self, + query: &[F], + logup_randomness: &[E], + numerators: &mut [E], + denominators: &mut [E], + ) where + F: FieldElement, + E: FieldElement + ExtensionOf; + + /// Computes the final claim for the LogUp-GKR circuit. + /// + /// The default implementation of this method returns E::ZERO as it is expected that the + /// fractional sums will cancel out. However, in cases when some boundary conditions need to + /// be imposed on the LogUp-GKR relations, this method can be overridden to compute the final + /// expected claim. + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + E::ZERO + } + + /// Generates the data needed for running the univariate IOP for multi-linear evaluation of [1]. + /// + /// This mainly generates the batching randomness used to batch a number of multi-linear + /// evaluation claims and includes some additional data that is needed for building/verifying + /// the univariate IOP for multi-linear evaluation of [1]. + /// + /// This is the $\lambda$ randomness in section 5.2 in [1] but using different random values for + /// each term instead of powers of a single random element. + /// + /// [1]: https://eprint.iacr.org/2023/1284 + fn generate_univariate_iop_for_multi_linear_opening_data( + &self, + openings: Vec, + eval_point: Vec, + public_coin: &mut impl RandomCoin, + ) -> GkrData + where + E: FieldElement, + H: ElementHasher, + { + public_coin.reseed(H::hash_elements(&openings)); + + let mut batching_randomness = Vec::with_capacity(openings.len() - 1); + for _ in 0..openings.len() - 1 { + batching_randomness.push(public_coin.draw().expect("failed to generate randomness")) + } + + GkrData::new( + LagrangeKernelRandElements::new(eval_point), + batching_randomness, + openings, + self.get_oracles().to_vec(), + ) + } + + /// Returns a new [`LagrangeKernelConstraints`]. + fn get_lagrange_kernel_constraints>( + &self, + lagrange_composition_coefficients: LagrangeConstraintsCompositionCoefficients, + lagrange_kernel_rand_elements: &LagrangeKernelRandElements, + ) -> LagrangeKernelConstraints { + LagrangeKernelConstraints::new( + lagrange_composition_coefficients, + lagrange_kernel_rand_elements, + ) + } + + /// Returns a new [`SColumnConstraints`]. + fn get_s_column_constraints>( + &self, + gkr_data: GkrData, + composition_coefficient: E, + ) -> SColumnConstraint { + SColumnConstraint::new(gkr_data, composition_coefficient) + } +} + +#[derive(Clone, Default)] +pub(crate) struct PhantomLogUpGkrEval> { + _field: PhantomData, + _public_inputs: PhantomData

, +} + +impl PhantomLogUpGkrEval +where + B: StarkField, + P: Clone + Send + Sync + ToElements, +{ + pub fn new() -> Self { + Self { + _field: PhantomData, + _public_inputs: PhantomData, + } + } +} + +impl LogUpGkrEvaluator for PhantomLogUpGkrEval +where + B: StarkField, + P: Clone + Send + Sync + ToElements, +{ + type BaseField = B; + + type PublicInputs = P; + + fn get_oracles(&self) -> &[LogUpGkrOracle] { + panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") + } + + fn get_num_rand_values(&self) -> usize { + panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") + } + + fn get_num_fractions(&self) -> usize { + panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") + } + + fn max_degree(&self) -> usize { + panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") + } + + fn build_query(&self, _frame: &EvaluationFrame, _periodic_values: &[E], _query: &mut [E]) + where + E: FieldElement, + { + panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") + } + + fn evaluate_query( + &self, + _query: &[F], + _rand_values: &[E], + _numerator: &mut [E], + _denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + ExtensionOf, + { + panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") + } + + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + panic!("LogUpGkrEvaluator method called but LogUp-GKR is not implemented") + } +} + +#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] +pub enum LogUpGkrOracle { + /// A column with a given index in the main trace segment. + CurrentRow(usize), + /// A column with a given index in the main trace segment but shifted upwards. + NextRow(usize), + /// A virtual periodic column defined by its values in a given cycle. Note that the cycle length + /// must be a power of 2. + PeriodicValue(Vec), +} diff --git a/air/src/air/logup_gkr/s_column.rs b/air/src/air/logup_gkr/s_column.rs new file mode 100644 index 000000000..29848ceeb --- /dev/null +++ b/air/src/air/logup_gkr/s_column.rs @@ -0,0 +1,56 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +use math::FieldElement; + +use super::{super::Air, EvaluationFrame, GkrData}; +use crate::LogUpGkrEvaluator; + +/// Represents the transition constraint for the s-column, as well as the random coefficient used +/// to linearly combine the constraint into the constraint composition polynomial. +/// +/// The s-column implements the cohomological sum-check argument of [1] and the constraint in +/// [`SColumnConstraint`] is exactly Eq (4) in Lemma 1 in [1]. +/// +/// +/// [1]: https://eprint.iacr.org/2021/930 +pub struct SColumnConstraint { + gkr_data: GkrData, + composition_coefficient: E, +} + +impl SColumnConstraint { + pub fn new(gkr_data: GkrData, composition_coefficient: E) -> Self { + Self { gkr_data, composition_coefficient } + } + + /// Evaluates the transition constraint over the specificed main trace segment, s-column, + /// and Lagrange kernel evaluation frames. + pub fn evaluate( + &self, + air: &A, + main_trace_frame: &EvaluationFrame, + s_cur: E, + s_nxt: E, + l_cur: E, + x: E, + ) -> E + where + A: Air, + { + let batched_claim = self.gkr_data.compute_batched_claim(); + let mean = batched_claim + .mul_base(E::BaseField::ONE / E::BaseField::from(air.trace_length() as u32)); + + let mut query = vec![E::ZERO; air.get_logup_gkr_evaluator().get_oracles().len()]; + air.get_logup_gkr_evaluator().build_query(main_trace_frame, &[], &mut query); + let batched_claim_at_query = self.gkr_data.compute_batched_query::(&query); + let rhs = s_cur - mean + batched_claim_at_query * l_cur; + let lhs = s_nxt; + + let divisor = x.exp((air.trace_length() as u32).into()) - E::ONE; + self.composition_coefficient * (rhs - lhs) / divisor + } +} From e3b2dd0d309ab3dbe9e39199ea593c3b12caae14 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:40:08 +0200 Subject: [PATCH 56/58] feat: add s-column constraint validation --- prover/src/trace/mod.rs | 56 +++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/prover/src/trace/mod.rs b/prover/src/trace/mod.rs index 8d7c999bf..60d66365e 100644 --- a/prover/src/trace/mod.rs +++ b/prover/src/trace/mod.rs @@ -3,7 +3,10 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -use air::{Air, AuxRandElements, EvaluationFrame, LagrangeKernelBoundaryConstraint, TraceInfo}; +use air::{ + Air, AuxRandElements, EvaluationFrame, LagrangeKernelBoundaryConstraint, LogUpGkrEvaluator, + TraceInfo, +}; use math::{polynom, FieldElement, StarkField}; use sumcheck::GkrCircuitProof; @@ -222,17 +225,25 @@ pub trait Trace: Sized { x *= g; } - // evaluate transition constraints for Lagrange kernel column (if any) and make sure - // they all evaluate to zeros - if let Some(col_idx) = air.context().lagrange_kernel_aux_column_idx() { + // evaluate transition constraints for Lagrange kernel column and s-column, when LogUp-GKR + // is enabled, and make sure they all evaluate to zeros + if air.context().logup_gkr_enabled() { let aux_trace_with_metadata = aux_trace_with_metadata.expect("expected aux trace to be present"); let aux_trace = &aux_trace_with_metadata.aux_trace; let aux_rand_elements = &aux_trace_with_metadata.aux_rand_elements; - - let c = aux_trace.get_column(col_idx); - let v = self.length().ilog2() as usize; - let r = aux_rand_elements.lagrange().expect("expected Lagrange column to be present"); + let l_col_idx = air + .context() + .trace_info() + .lagrange_kernel_column_idx() + .expect("should not be None"); + let s_col_idx = air.context().trace_info().s_column_idx().expect("should not be None"); + + let c = aux_trace.get_column(l_col_idx); + let trace_length = self.length(); + let v = trace_length.ilog2() as usize; + let gkr_data = aux_rand_elements.gkr_data().expect("should not be None"); + let r = gkr_data.lagrange_kernel_rand_elements(); // Loop over every constraint for constraint_idx in 1..v + 1 { @@ -254,6 +265,35 @@ pub trait Trace: Sized { ); } } + + let evaluator = air.get_logup_gkr_evaluator(); + let mut aux_frame = EvaluationFrame::new(self.aux_trace_width()); + + let c = gkr_data.compute_batched_claim(); + let mean = c / E::from(E::BaseField::from(trace_length as u32)); + + let mut query = vec![E::BaseField::ZERO; evaluator.get_oracles().len()]; + for step in 0..self.length() { + self.read_main_frame(step, &mut main_frame); + read_aux_frame(aux_trace, step, &mut aux_frame); + + let l_cur = aux_frame.current()[l_col_idx]; + let s_cur = aux_frame.current()[s_col_idx]; + let s_nxt = aux_frame.next()[s_col_idx]; + + evaluator.build_query(&main_frame, &[], &mut query); + let batched_query = gkr_data.compute_batched_query(&query); + + let rhs = s_cur - mean + batched_query * l_cur; + let lhs = s_nxt; + + let evaluation = rhs - lhs; + + assert!( + evaluation == E::ZERO, + "s-column transition constraint did not evaluate to ZERO at step {step}" + ); + } } } } From b0ca76c1eb6983208c32f464b77cf2776218542d Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 2 Sep 2024 15:08:58 +0200 Subject: [PATCH 57/58] chore: improve doc and remove obsolete file --- air/src/air/s_column.rs | 57 ----------------------------------------- prover/src/trace/mod.rs | 3 ++- 2 files changed, 2 insertions(+), 58 deletions(-) delete mode 100644 air/src/air/s_column.rs diff --git a/air/src/air/s_column.rs b/air/src/air/s_column.rs deleted file mode 100644 index b2fed82fe..000000000 --- a/air/src/air/s_column.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -use math::FieldElement; - -use crate::LogUpGkrEvaluator; - -use super::{Air, EvaluationFrame, GkrData}; - -/// Represents the transition constraint for the s-column, as well as the random coefficient used -/// to linearly combine the constraint into the constraint composition polynomial. -/// -/// The s-column implements the cohomological sum-check argument of [1] and the constraint in -/// [`SColumnConstraint`] is exactly Eq (4) in Lemma 1 in [1]. -/// -/// -/// [1]: https://eprint.iacr.org/2021/930 -pub struct SColumnConstraint { - gkr_data: GkrData, - composition_coefficient: E, -} - -impl SColumnConstraint { - pub fn new(gkr_data: GkrData, composition_coefficient: E) -> Self { - Self { gkr_data, composition_coefficient } - } - - /// Evaluates the transition constraint over the specificed main trace segment, s-column, - /// and Lagrange kernel evaluation frames. - pub fn evaluate( - &self, - air: &A, - main_trace_frame: &EvaluationFrame, - s_cur: E, - s_nxt: E, - l_cur: E, - x: E, - ) -> E - where - A: Air, - { - let batched_claim = self.gkr_data.compute_batched_claim(); - let mean = batched_claim - .mul_base(E::BaseField::ONE / E::BaseField::from(air.trace_length() as u32)); - - let mut query = vec![E::ZERO; air.get_logup_gkr_evaluator().get_oracles().len()]; - air.get_logup_gkr_evaluator().build_query(main_trace_frame, &[], &mut query); - let batched_claim_at_query = self.gkr_data.compute_batched_query::(&query); - let rhs = s_cur - mean + batched_claim_at_query * l_cur; - let lhs = s_nxt; - - let divisor = x.exp((air.trace_length() as u32).into()) - E::ONE; - self.composition_coefficient * (rhs - lhs) / divisor - } -} diff --git a/prover/src/trace/mod.rs b/prover/src/trace/mod.rs index 60d66365e..b8c728cb1 100644 --- a/prover/src/trace/mod.rs +++ b/prover/src/trace/mod.rs @@ -245,7 +245,7 @@ pub trait Trace: Sized { let gkr_data = aux_rand_elements.gkr_data().expect("should not be None"); let r = gkr_data.lagrange_kernel_rand_elements(); - // Loop over every constraint + // Loop over every Lagrange kernel constraint for constraint_idx in 1..v + 1 { let domain_step = 2_usize.pow((v - constraint_idx + 1) as u32); let domain_half_step = 2_usize.pow((v - constraint_idx) as u32); @@ -266,6 +266,7 @@ pub trait Trace: Sized { } } + // Validate the s-column constraint let evaluator = air.get_logup_gkr_evaluator(); let mut aux_frame = EvaluationFrame::new(self.aux_trace_width()); From b54745970c98d359baaf8a8cf02495d4e0d57619 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 2 Sep 2024 23:30:44 +0200 Subject: [PATCH 58/58] chore: address feeback 3 --- air/src/air/context.rs | 2 +- air/src/air/trace_info.rs | 6 +++--- prover/src/constraints/evaluator/logup_gkr.rs | 14 ++++++------- prover/src/lib.rs | 2 +- prover/src/trace/mod.rs | 2 +- verifier/src/composer.rs | 2 +- verifier/src/evaluator.rs | 20 +++++++++---------- winterfell/src/tests.rs | 4 ++-- 8 files changed, 25 insertions(+), 27 deletions(-) diff --git a/air/src/air/context.rs b/air/src/air/context.rs index f3167c005..8998e64e0 100644 --- a/air/src/air/context.rs +++ b/air/src/air/context.rs @@ -251,7 +251,7 @@ impl AirContext { } /// Returns the index of the auxiliary column which implements the Lagrange kernel, if any. - pub fn lagrange_kernel_aux_column_idx(&self) -> Option { + pub fn lagrange_kernel_column_idx(&self) -> Option { self.trace_info.lagrange_kernel_column_idx() } diff --git a/air/src/air/trace_info.rs b/air/src/air/trace_info.rs index d55514bce..8c3545539 100644 --- a/air/src/air/trace_info.rs +++ b/air/src/air/trace_info.rs @@ -116,7 +116,7 @@ impl TraceInfo { // validate trace segment widths assert!(main_segment_width > 0, "main trace segment must consist of at least one column"); - let full_width = main_segment_width + aux_segment_width; + let full_width = main_segment_width + aux_segment_width + 2 * logup_gkr as usize; assert!( full_width <= TraceInfo::MAX_TRACE_WIDTH, "total number of columns in the trace cannot be greater than {}, but was {}", @@ -174,9 +174,9 @@ impl TraceInfo { &self.trace_meta } - /// Returns true if an execution trace contains the auxiliary trace segment. + /// Returns true if an execution trace contains an auxiliary trace segment. pub fn is_multi_segment(&self) -> bool { - self.aux_segment_width > 0 + self.aux_segment_width > 0 || self.logup_gkr } /// Returns the number of columns in the main segment of an execution trace. diff --git a/prover/src/constraints/evaluator/logup_gkr.rs b/prover/src/constraints/evaluator/logup_gkr.rs index 44164ebba..f8fa3ae36 100644 --- a/prover/src/constraints/evaluator/logup_gkr.rs +++ b/prover/src/constraints/evaluator/logup_gkr.rs @@ -71,8 +71,7 @@ where let mut lagrange_frame = LagrangeKernelEvaluationFrame::new_empty(); let evaluator = self.air.get_logup_gkr_evaluator(); - let s_col_constraint_divisor = - compute_s_col_divisor::(domain.ce_domain_size(), domain, self.air.trace_length()); + let s_col_constraint_divisor = compute_s_col_divisor::(domain, self.air.trace_length()); let s_col_idx = trace.trace_info().s_column_idx().expect("S-column should be present"); let l_col_idx = trace .trace_info() @@ -145,10 +144,10 @@ where let rhs = s_cur - mean + batched_query * l_cur; let lhs = s_nxt; - (rhs - lhs) - * self - .s_col_composition_coefficient - .mul_base(s_col_constraint_divisor[step % (domain.trace_to_ce_blowup())]) + let divisor_at_step = + s_col_constraint_divisor[step % (domain.trace_to_ce_blowup())]; + + (rhs - lhs) * self.s_col_composition_coefficient.mul_base(divisor_at_step) }; combined_evaluations_acc[step] += @@ -348,12 +347,11 @@ impl TransitionDivisorEvaluator { /// The divisor for the s-column is $X^n - 1$ where $n$ is the trace length. This means that /// we need only compute `ce_blowup` many values and thus only that many exponentiations. fn compute_s_col_divisor( - ce_domain_size: usize, domain: &StarkDomain, trace_length: usize, ) -> Vec { let degree = trace_length as u32; - let mut result = Vec::with_capacity(ce_domain_size); + let mut result = Vec::with_capacity(domain.trace_to_ce_blowup()); for row in 0..domain.trace_to_ce_blowup() { let x = domain.get_ce_x_at(row).exp(degree.into()) - E::BaseField::ONE; diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 703f19d8c..b62df14d8 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -346,7 +346,7 @@ pub trait Prover { }; trace_polys - .add_aux_segment(aux_segment_polys, air.context().lagrange_kernel_aux_column_idx()); + .add_aux_segment(aux_segment_polys, air.context().lagrange_kernel_column_idx()); Some(AuxTraceWithMetadata { aux_trace, aux_rand_elements, gkr_proof }) } else { diff --git a/prover/src/trace/mod.rs b/prover/src/trace/mod.rs index b8c728cb1..5fef475e6 100644 --- a/prover/src/trace/mod.rs +++ b/prover/src/trace/mod.rs @@ -142,7 +142,7 @@ pub trait Trace: Sized { } // then, check the Lagrange kernel assertion, if any - if let Some(lagrange_kernel_col_idx) = air.context().lagrange_kernel_aux_column_idx() { + if let Some(lagrange_kernel_col_idx) = air.context().lagrange_kernel_column_idx() { let boundary_constraint_assertion_value = LagrangeKernelBoundaryConstraint::assertion_value( aux_rand_elements diff --git a/verifier/src/composer.rs b/verifier/src/composer.rs index 5f10ef79f..ae20f4586 100644 --- a/verifier/src/composer.rs +++ b/verifier/src/composer.rs @@ -43,7 +43,7 @@ impl DeepComposer { x_coordinates, z: [z, z * E::from(g_trace)], g_trace, - lagrange_kernel_column_idx: air.context().lagrange_kernel_aux_column_idx(), + lagrange_kernel_column_idx: air.context().lagrange_kernel_column_idx(), } } diff --git a/verifier/src/evaluator.rs b/verifier/src/evaluator.rs index fc7fdf24c..a226ec9c8 100644 --- a/verifier/src/evaluator.rs +++ b/verifier/src/evaluator.rs @@ -89,6 +89,8 @@ pub fn evaluate_constraints>( // 3 ----- evaluate Lagrange kernel constraints ------------------------------------ if let Some(lagrange_kernel_column_frame) = lagrange_kernel_frame { + let logup_gkr_evaluator = air.get_logup_gkr_evaluator(); + let lagrange_coefficients = composition_coefficients .lagrange .expect("expected Lagrange kernel composition coefficients to be present"); @@ -100,7 +102,7 @@ pub fn evaluate_constraints>( // Lagrange kernel constraints - let lagrange_constraints = air.get_logup_gkr_evaluator().get_lagrange_kernel_constraints( + let lagrange_constraints = logup_gkr_evaluator.get_lagrange_kernel_constraints( lagrange_coefficients, &gkr_data.lagrange_kernel_eval_point, ); @@ -115,14 +117,12 @@ pub fn evaluate_constraints>( // s-column constraints let s_col_idx = air.trace_info().s_column_idx().expect("s-column should be present"); - let s_cur = aux_trace_frame - .as_ref() - .expect("expected aux rand elements to be present") - .current()[s_col_idx]; - let s_nxt = aux_trace_frame - .as_ref() - .expect("expected aux rand elements to be present") - .next()[s_col_idx]; + + let aux_trace_frame = + aux_trace_frame.as_ref().expect("expected aux rand elements to be present"); + + let s_cur = aux_trace_frame.current()[s_col_idx]; + let s_nxt = aux_trace_frame.next()[s_col_idx]; let l_cur = lagrange_kernel_column_frame.inner()[0]; let s_column_cc = composition_coefficients @@ -130,7 +130,7 @@ pub fn evaluate_constraints>( .expect("expected constraint composition coefficient for s-column to be present"); let s_column_constraint = - air.get_logup_gkr_evaluator().get_s_column_constraints(gkr_data, s_column_cc); + logup_gkr_evaluator.get_s_column_constraints(gkr_data, s_column_cc); result += s_column_constraint.evaluate(air, main_trace_frame, s_cur, s_nxt, l_cur, x); } diff --git a/winterfell/src/tests.rs b/winterfell/src/tests.rs index 63efe383e..99e52971f 100644 --- a/winterfell/src/tests.rs +++ b/winterfell/src/tests.rs @@ -26,14 +26,14 @@ fn test_logup_gkr() { let trace = LogUpGkrSimple::new(2_usize.pow(7), aux_trace_width); let prover = LogUpGkrSimpleProver::new(aux_trace_width); - let _proof = prover.prove(trace).unwrap(); + let proof = prover.prove(trace).unwrap(); verify::< LogUpGkrSimpleAir, Blake3_256, DefaultRandomCoin>, MerkleTree>, - >(_proof, (), &AcceptableOptions::MinConjecturedSecurity(0)) + >(proof, (), &AcceptableOptions::MinConjecturedSecurity(0)) .unwrap() }