From aef404d4bcd11dce2cbe26e71450b2eead032411 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 30 Aug 2024 16:06:38 +0200 Subject: [PATCH] Implement GKR backend for LogUp-GKR (#296) --- .cargo/katex-header.html | 1 + air/src/air/aux.rs | 161 +++---- air/src/air/boundary/mod.rs | 10 +- air/src/air/coefficients.rs | 24 +- air/src/air/context.rs | 70 ++- air/src/air/logup_gkr.rs | 117 ++++- air/src/air/mod.rs | 56 ++- air/src/air/tests.rs | 22 +- air/src/air/trace_info.rs | 29 +- air/src/air/transition/mod.rs | 2 +- air/src/lib.rs | 2 +- air/src/proof/context.rs | 11 +- air/src/proof/ood_frame.rs | 2 + crypto/src/merkle/concurrent.rs | 7 +- examples/src/fibonacci/fib2/air.rs | 8 +- examples/src/fibonacci/fib8/air.rs | 8 +- examples/src/fibonacci/fib_small/air.rs | 8 +- examples/src/fibonacci/mulfib2/air.rs | 8 +- examples/src/fibonacci/mulfib8/air.rs | 8 +- examples/src/lamport/aggregate/air.rs | 8 +- examples/src/lamport/threshold/air.rs | 10 +- examples/src/merkle/air.rs | 9 +- examples/src/rescue/air.rs | 9 +- examples/src/rescue_raps/air.rs | 9 +- .../src/rescue_raps/custom_trace_table.rs | 6 +- examples/src/rescue_raps/prover.rs | 29 +- examples/src/utils/rescue.rs | 2 + examples/src/vdf/exempt/air.rs | 10 +- examples/src/vdf/regular/air.rs | 8 +- math/src/field/f64/mod.rs | 7 +- prover/Cargo.toml | 2 + prover/benches/lagrange_kernel.rs | 52 +-- prover/src/constraints/evaluator/default.rs | 6 +- .../constraints/evaluator/periodic_table.rs | 6 +- prover/src/errors.rs | 5 + prover/src/lib.rs | 105 +++-- prover/src/logup_gkr/mod.rs | 403 ++++++++++++++++++ prover/src/logup_gkr/prover.rs | 256 +++++++++++ prover/src/tests/mod.rs | 12 +- prover/src/trace/mod.rs | 20 +- prover/src/trace/tests.rs | 2 +- prover/src/trace/trace_lde/default/tests.rs | 2 +- prover/src/trace/trace_table.rs | 4 +- sumcheck/benches/sum_check_high_degree.rs | 26 +- sumcheck/src/lib.rs | 19 +- sumcheck/src/prover/high_degree.rs | 6 +- sumcheck/src/verifier/mod.rs | 14 +- verifier/Cargo.toml | 2 + verifier/src/channel.rs | 17 +- verifier/src/evaluator.rs | 23 +- verifier/src/lib.rs | 57 +-- verifier/src/logup_gkr/mod.rs | 115 +++++ winterfell/src/lib.rs | 37 +- winterfell/src/tests.rs | 281 ++++++------ 54 files changed, 1556 insertions(+), 577 deletions(-) create mode 100644 prover/src/logup_gkr/mod.rs create mode 100644 prover/src/logup_gkr/prover.rs create mode 100644 verifier/src/logup_gkr/mod.rs diff --git a/.cargo/katex-header.html b/.cargo/katex-header.html index 5db5bc0b1..ca338654e 100644 --- a/.cargo/katex-header.html +++ b/.cargo/katex-header.html @@ -11,6 +11,7 @@ renderMathInElement(document.body, { fleqn: false, macros: { + "\\B": "\\mathbb{B}", "\\F": "\\mathbb{F}", "\\G": "\\mathbb{G}", "\\O": "\\mathcal{O}", diff --git a/air/src/air/aux.rs b/air/src/air/aux.rs index 01f59035a..7dc9c4f48 100644 --- a/air/src/air/aux.rs +++ b/air/src/air/aux.rs @@ -3,39 +3,32 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -use alloc::{string::ToString, vec::Vec}; +use alloc::vec::Vec; -use crypto::{ElementHasher, RandomCoin, RandomCoinError}; use math::FieldElement; -use utils::Deserializable; -use super::lagrange::LagrangeKernelRandElements; +use super::{lagrange::LagrangeKernelRandElements, LogUpGkrOracle}; -/// Holds the randomly generated elements necessary to build the auxiliary trace. +/// Holds the randomly generated elements used in defining the auxiliary segment of the trace. /// -/// Specifically, [`AuxRandElements`] currently supports 3 types of random elements: -/// - the ones needed to build the Lagrange kernel column (when using GKR to accelerate LogUp), -/// - the ones needed to build the "s" auxiliary column (when using GKR to accelerate LogUp), -/// - the ones needed to build all the other auxiliary columns +/// Specifically, [`AuxRandElements`] currently supports 2 types of random elements: +/// - the ones needed to build all the auxiliary columns except for the ones associated +/// to LogUp-GKR. +/// - the ones needed to build the "s" and Lagrange kernel auxiliary columns (when using GKR to +/// accelerate LogUp). These also include additional information needed to evaluate constraints +/// one these two columns. #[derive(Debug, Clone)] -pub struct AuxRandElements { +pub struct AuxRandElements { rand_elements: Vec, - gkr: Option>, + gkr: Option>, } -impl AuxRandElements { - /// Creates a new [`AuxRandElements`], where the auxiliary trace doesn't contain a Lagrange - /// kernel column. - pub fn new(rand_elements: Vec) -> Self { - Self { rand_elements, gkr: None } - } - - /// Creates a new [`AuxRandElements`], where the auxiliary trace contains columns needed when +impl AuxRandElements { + /// Creates a new [`AuxRandElements`], where the auxiliary segment may contain columns needed when /// using GKR to accelerate LogUp (i.e. a Lagrange kernel column and the "s" column). - pub fn new_with_gkr(rand_elements: Vec, gkr: GkrRandElements) -> Self { - Self { rand_elements, gkr: Some(gkr) } + pub fn new(rand_elements: Vec, gkr: Option>) -> Self { + Self { rand_elements, gkr } } - /// Returns the random elements needed to build all columns other than the two GKR-related ones. pub fn rand_elements(&self) -> &[E] { &self.rand_elements @@ -43,7 +36,7 @@ impl AuxRandElements { /// Returns the random elements needed to build the Lagrange kernel column. pub fn lagrange(&self) -> Option<&LagrangeKernelRandElements> { - self.gkr.as_ref().map(|gkr| &gkr.lagrange) + self.gkr.as_ref().map(|gkr| &gkr.lagrange_kernel_eval_point) } /// Returns the random values used to linearly combine the openings returned from the GKR proof. @@ -52,83 +45,97 @@ impl AuxRandElements { pub fn gkr_openings_combining_randomness(&self) -> Option<&[E]> { self.gkr.as_ref().map(|gkr| gkr.openings_combining_randomness.as_ref()) } + + /// Returns a collection of data necessary for implementing the univariate IOP for multi-linear + /// evaluations of [1] when LogUp-GKR is enabled, else returns a `None`. + /// + /// [1]: https://eprint.iacr.org/2023/1284 + pub fn gkr_data(&self) -> Option> { + self.gkr.clone() + } } -/// Holds all the random elements needed when using GKR to accelerate LogUp. +/// Holds all the data needed when using LogUp-GKR in order to build and verify the correctness of +/// two extra auxiliary columns required for running the univariate IOP for multi-linear +/// evaluations of [1]. /// -/// This consists of two sets of random values: -/// 1. The Lagrange kernel random elements (expanded on in [`LagrangeKernelRandElements`]), and +/// This consists of: +/// 1. The Lagrange kernel random elements (expanded on in [`LagrangeKernelRandElements`]). These +/// make up the evaluation point of the multi-linear extension polynomials underlying the oracles +/// in point 4 below. /// 2. The "openings combining randomness". +/// 3. The openings of the multi-linear extension polynomials of the main trace columns involved +/// in LogUp. +/// 4. A description of the each of the oracles involved in LogUp. /// -/// After the verifying the LogUp-GKR circuit, the verifier is left with unproven claims provided -/// nondeterministically by the prover about the evaluations of the MLE of the main trace columns at -/// the Lagrange kernel random elements. Those claims are (linearly) combined into one using the -/// openings combining randomness. +/// After verifying the LogUp-GKR circuit, the verifier is left with unproven claims provided +/// by the prover about the evaluations of the MLEs of the main trace columns at the evaluation +/// point defining the Lagrange kernel. Those claims are (linearly) batched into one using the +/// openings combining randomness and checked against the batched oracles using univariate IOP +/// for multi-linear evaluations of [1]. +/// +/// [1]: https://eprint.iacr.org/2023/1284 #[derive(Clone, Debug)] -pub struct GkrRandElements { - lagrange: LagrangeKernelRandElements, - openings_combining_randomness: Vec, +pub struct GkrData { + pub lagrange_kernel_eval_point: LagrangeKernelRandElements, + pub openings_combining_randomness: Vec, + pub openings: Vec, + pub oracles: Vec>, } -impl GkrRandElements { - /// Constructs a new [`GkrRandElements`] from [`LagrangeKernelRandElements`], and the openings - /// combining randomness. +impl GkrData { + /// Constructs a new [`GkrData`] from [`LagrangeKernelRandElements`], the openings combining + /// randomness and the LogUp-GKR oracles. /// - /// See [`GkrRandElements`] for a more detailed description. + /// See [`GkrData`] for a more detailed description. pub fn new( - lagrange: LagrangeKernelRandElements, + lagrange_kernel_eval_point: LagrangeKernelRandElements, openings_combining_randomness: Vec, + openings: Vec, + oracles: Vec>, ) -> Self { - Self { lagrange, openings_combining_randomness } + Self { + lagrange_kernel_eval_point, + openings_combining_randomness, + openings, + oracles, + } } /// Returns the random elements needed to build the Lagrange kernel column. pub fn lagrange_kernel_rand_elements(&self) -> &LagrangeKernelRandElements { - &self.lagrange + &self.lagrange_kernel_eval_point } /// Returns the random values used to linearly combine the openings returned from the GKR proof. pub fn openings_combining_randomness(&self) -> &[E] { &self.openings_combining_randomness } -} -/// A trait for verifying a GKR proof. -/// -/// Specifically, the use case in mind is proving the constraints of a LogUp bus using GKR, as -/// described in [Improving logarithmic derivative lookups using -/// GKR](https://eprint.iacr.org/2023/1284.pdf). -pub trait GkrVerifier { - /// The GKR proof. - type GkrProof: Deserializable; - /// The error that can occur during GKR proof verification. - type Error: ToString; - - /// Verifies the GKR proof, and returns the random elements that were used in building - /// the Lagrange kernel auxiliary column. - fn verify( - &self, - gkr_proof: Self::GkrProof, - public_coin: &mut impl RandomCoin, - ) -> Result, Self::Error> - where - E: FieldElement, - Hasher: ElementHasher; -} + pub fn openings(&self) -> &[E] { + &self.openings + } + + pub fn oracles(&self) -> &[LogUpGkrOracle] { + &self.oracles + } + + pub fn compute_batched_claim(&self) -> E { + self.openings[0] + + self + .openings + .iter() + .skip(1) + .zip(self.openings_combining_randomness.iter()) + .fold(E::ZERO, |acc, (a, b)| acc + *a * *b) + } -impl GkrVerifier for () { - type GkrProof = (); - type Error = RandomCoinError; - - fn verify( - &self, - _gkr_proof: Self::GkrProof, - _public_coin: &mut impl RandomCoin, - ) -> Result, Self::Error> - where - E: FieldElement, - Hasher: ElementHasher, - { - Ok(GkrRandElements::new(LagrangeKernelRandElements::default(), Vec::new())) + pub fn compute_batched_query(&self, query: &[E::BaseField]) -> E { + E::from(query[0]) + + query + .iter() + .skip(1) + .zip(self.openings_combining_randomness.iter()) + .fold(E::ZERO, |acc, (a, b)| acc + b.mul_base(*a)) } } diff --git a/air/src/air/boundary/mod.rs b/air/src/air/boundary/mod.rs index 7f92c80ab..2c15ac5a3 100644 --- a/air/src/air/boundary/mod.rs +++ b/air/src/air/boundary/mod.rs @@ -58,8 +58,8 @@ impl BoundaryConstraints { /// coefficients. /// * The specified assertions are not valid in the context of the computation (e.g., assertion /// column index is out of bounds). - pub fn new( - context: &AirContext, + pub fn new

( + context: &AirContext, main_assertions: Vec>, aux_assertions: Vec>, composition_coefficients: &[E], @@ -88,7 +88,7 @@ impl BoundaryConstraints { ); let trace_length = context.trace_info.length(); - let main_trace_width = context.trace_info.main_trace_width(); + let main_trace_width = context.trace_info.main_segment_width(); let aux_trace_width = context.trace_info.aux_segment_width(); // make sure the assertions are valid in the context of their respective trace segments; @@ -151,9 +151,9 @@ impl BoundaryConstraints { /// Translates the provided assertions into boundary constraints, groups the constraints by their /// divisor, and sorts the resulting groups by the degree adjustment factor. -fn group_constraints( +fn group_constraints( assertions: Vec>, - context: &AirContext, + context: &AirContext, composition_coefficients: &[E], inv_g: F::BaseField, twiddle_map: &mut BTreeMap>, diff --git a/air/src/air/coefficients.rs b/air/src/air/coefficients.rs index b82b2ac6b..ed6c3fa99 100644 --- a/air/src/air/coefficients.rs +++ b/air/src/air/coefficients.rs @@ -27,11 +27,19 @@ use math::FieldElement; /// /// The coefficients are separated into two lists: one for transition constraints and another one /// for boundary constraints. This separation is done for convenience only. +/// +/// In addition to the above, and when LogUp-GKR is enabled, there are two extra sets of +/// constraint composition coefficients that are used, namely for: +/// +/// 1. Lagrange kernel constraints, which include both transition and boundary constraints. +/// 2. S-column constraint, which is used in implementing the cohomological sum-check argument +/// of https://eprint.iacr.org/2021/930 #[derive(Debug, Clone)] pub struct ConstraintCompositionCoefficients { pub transition: Vec, pub boundary: Vec, pub lagrange: Option>, + pub s_col: Option, } /// Stores the constraint composition coefficients for the Lagrange kernel transition and boundary @@ -83,8 +91,9 @@ pub struct LagrangeConstraintsCompositionCoefficients { /// negligible increase in soundness error. The formula for the updated error can be found in /// Theorem 8 of https://eprint.iacr.org/2022/1216. /// -/// In the case when the trace polynomials contain a trace polynomial corresponding to a Lagrange -/// kernel column, the above expression of $Y(x)$ includes the additional term given by +/// In the case when LogUp-GKR is enabled, the trace polynomials contain an additional trace +/// polynomial corresponding to a Lagrange kernel column and the above expression of $Y(x)$ +/// includes the additional term given by /// /// $$ /// \gamma \cdot \frac{T_l(x) - p_S(x)}{Z_S(x)} @@ -99,8 +108,13 @@ pub struct LagrangeConstraintsCompositionCoefficients { /// 4. $p_S(X)$ is the polynomial of minimal degree interpolating the set ${(a, T_l(a)): a \in S}$. /// 5. $Z_S(X)$ is the polynomial of minimal degree vanishing over the set $S$. /// -/// Note that, if a Lagrange kernel trace polynomial is present, then $\rho^{+}$ from above should -/// be updated to be $\rho^{+} := \frac{\kappa + log_2(\nu) + 1}{\nu}$. +/// Note that when LogUp-GKR is enabled, we also have to take into account an additional column, +/// called s-column throughout, used in implementing the univariate IOP for multi-linear evaluation. +/// This means that we need and additional random value, in addition to $\gamma$ above, when +/// LogUp-GKR is enabled. +/// +/// Note that, when LogUp-GKR is enabled, $\rho^{+}$ from above should be updated to be +/// $\rho^{+} := \frac{\kappa + log_2(\nu) + 1}{\nu}$. #[derive(Debug, Clone)] pub struct DeepCompositionCoefficients { /// Trace polynomial composition coefficients $\alpha_i$. @@ -109,4 +123,6 @@ pub struct DeepCompositionCoefficients { pub constraints: Vec, /// Lagrange kernel trace polynomial composition coefficient $\gamma$. pub lagrange: Option, + /// S-column trace polynomial composition coefficient. + pub s_col: Option, } diff --git a/air/src/air/context.rs b/air/src/air/context.rs index 183f575fc..c36173ca3 100644 --- a/air/src/air/context.rs +++ b/air/src/air/context.rs @@ -14,21 +14,22 @@ use crate::{air::TransitionConstraintDegree, ProofOptions, TraceInfo}; // ================================================================================================ /// STARK parameters and trace properties for a specific execution of a computation. #[derive(Clone, PartialEq, Eq)] -pub struct AirContext { +pub struct AirContext { pub(super) options: ProofOptions, pub(super) trace_info: TraceInfo, + pub(super) pub_inputs: P, pub(super) main_transition_constraint_degrees: Vec, pub(super) aux_transition_constraint_degrees: Vec, pub(super) num_main_assertions: usize, pub(super) num_aux_assertions: usize, - pub(super) lagrange_kernel_aux_column_idx: Option, pub(super) ce_blowup_factor: usize, pub(super) trace_domain_generator: B, pub(super) lde_domain_generator: B, pub(super) num_transition_exemptions: usize, + pub(super) logup_gkr: bool, } -impl AirContext { +impl AirContext { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- /// Returns a new instance of [AirContext] instantiated for computations which require a single @@ -48,6 +49,7 @@ impl AirContext { /// * `trace_info` describes a multi-segment execution trace. pub fn new( trace_info: TraceInfo, + pub_inputs: P, transition_constraint_degrees: Vec, num_assertions: usize, options: ProofOptions, @@ -58,11 +60,11 @@ impl AirContext { ); Self::new_multi_segment( trace_info, + pub_inputs, transition_constraint_degrees, Vec::new(), num_assertions, 0, - None, options, ) } @@ -91,11 +93,11 @@ impl AirContext { /// of the specified transition constraints. pub fn new_multi_segment( trace_info: TraceInfo, + pub_inputs: P, main_transition_constraint_degrees: Vec, aux_transition_constraint_degrees: Vec, num_main_assertions: usize, num_aux_assertions: usize, - lagrange_kernel_aux_column_idx: Option, options: ProofOptions, ) -> Self { assert!( @@ -104,11 +106,11 @@ impl AirContext { ); assert!(num_main_assertions > 0, "at least one assertion must be specified"); - if trace_info.is_multi_segment() { + if trace_info.is_multi_segment() && !trace_info.logup_gkr_enabled() { assert!( - !aux_transition_constraint_degrees.is_empty(), - "at least one transition constraint degree must be specified for the auxiliary trace segment" - ); + !aux_transition_constraint_degrees.is_empty(), + "at least one transition constraint degree must be specified for the auxiliary trace segment" + ); assert!( num_aux_assertions > 0, "at least one assertion must be specified against the auxiliary trace segment" @@ -124,15 +126,6 @@ impl AirContext { ); } - // validate Lagrange kernel aux column, if any - if let Some(lagrange_kernel_aux_column_idx) = lagrange_kernel_aux_column_idx { - assert!( - lagrange_kernel_aux_column_idx == trace_info.get_aux_segment_width() - 1, - "Lagrange kernel column should be the last column of the auxiliary trace: index={}, but aux trace width is {}", - lagrange_kernel_aux_column_idx, trace_info.get_aux_segment_width() - ); - } - // determine minimum blowup factor needed to evaluate transition constraints by taking // the blowup factor of the highest degree constraint let mut ce_blowup_factor = 0; @@ -161,18 +154,41 @@ impl AirContext { AirContext { options, trace_info, + pub_inputs, main_transition_constraint_degrees, aux_transition_constraint_degrees, num_main_assertions, num_aux_assertions, - lagrange_kernel_aux_column_idx, ce_blowup_factor, trace_domain_generator: B::get_root_of_unity(trace_length.ilog2()), lde_domain_generator: B::get_root_of_unity(lde_domain_size.ilog2()), num_transition_exemptions: 1, + logup_gkr: false, } } + pub fn with_logup_gkr( + trace_info: TraceInfo, + pub_inputs: P, + main_transition_constraint_degrees: Vec, + aux_transition_constraint_degrees: Vec, + num_main_assertions: usize, + num_aux_assertions: usize, + options: ProofOptions, + ) -> Self { + let mut air_context = Self::new_multi_segment( + trace_info, + pub_inputs, + main_transition_constraint_degrees, + aux_transition_constraint_degrees, + num_main_assertions, + num_aux_assertions, + options, + ); + air_context.logup_gkr = true; + air_context + } + // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- @@ -209,6 +225,10 @@ impl AirContext { self.trace_info.length() * self.options.blowup_factor() } + pub fn public_inputs(&self) -> &P { + &self.pub_inputs + } + /// Returns the number of transition constraints for a computation, excluding the Lagrange /// kernel transition constraints, which are managed separately. /// @@ -232,12 +252,16 @@ 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 { - self.lagrange_kernel_aux_column_idx + if self.logup_gkr_enabled() { + Some(self.trace_info().aux_segment_width() - 1) + } else { + None + } } - /// Returns true if the auxiliary trace segment contains a Lagrange kernel column - pub fn has_lagrange_kernel_aux_column(&self) -> bool { - self.lagrange_kernel_aux_column_idx().is_some() + /// Returns true if LogUp-GKR is enabled. + pub fn logup_gkr_enabled(&self) -> bool { + self.logup_gkr } /// Returns the total number of assertions defined for a computation, excluding the Lagrange diff --git a/air/src/air/logup_gkr.rs b/air/src/air/logup_gkr.rs index 98054c938..0438064d9 100644 --- a/air/src/air/logup_gkr.rs +++ b/air/src/air/logup_gkr.rs @@ -4,10 +4,12 @@ // 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; +use super::{EvaluationFrame, GkrData, LagrangeKernelRandElements}; /// A trait containing the necessary information in order to run the LogUp-GKR protocol of [1]. /// @@ -25,7 +27,7 @@ pub trait LogUpGkrEvaluator: Clone + Sync { /// 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>; + fn get_oracles(&self) -> &[LogUpGkrOracle]; /// Returns the number of random values needed to evaluate a query. fn get_num_rand_values(&self) -> usize; @@ -79,11 +81,122 @@ pub trait LogUpGkrEvaluator: Clone + Sync { { 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(), + ) + } +} + +#[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 07f38cce1..5dcee0717 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -6,12 +6,13 @@ 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; mod aux; -pub use aux::{AuxRandElements, GkrRandElements, GkrVerifier}; +pub use aux::{AuxRandElements, GkrData}; mod trace_info; pub use trace_info::TraceInfo; @@ -45,7 +46,6 @@ pub use coefficients::{ mod divisor; pub use divisor::ConstraintDivisor; -use utils::{Deserializable, Serializable}; #[cfg(test)] mod tests; @@ -195,13 +195,7 @@ pub trait Air: Send + Sync { /// A type defining shape of public inputs for the computation described by this protocol. /// This could be any type as long as it can be serialized into a sequence of field elements. - type PublicInputs: ToElements + Send; - - /// An GKR proof object. If not needed, set to `()`. - type GkrProof: Serializable + Deserializable + Send; - - /// A verifier for verifying GKR proofs. If not needed, set to `()`. - type GkrVerifier: GkrVerifier; + type PublicInputs: ToElements + Clone + Send + Sync; // REQUIRED METHODS // -------------------------------------------------------------------------------------------- @@ -217,7 +211,7 @@ pub trait Air: Send + Sync { fn new(trace_info: TraceInfo, pub_inputs: Self::PublicInputs, options: ProofOptions) -> Self; /// Returns context for this instance of the computation. - fn context(&self) -> &AirContext; + fn context(&self) -> &AirContext; /// Evaluates transition constraints over the specified evaluation frame. /// @@ -306,16 +300,15 @@ pub trait Air: Send + Sync { Vec::new() } - // AUXILIARY PROOF VERIFIER + // LOGUP-GKR EVALUATOR // -------------------------------------------------------------------------------------------- - /// Returns the [`GkrVerifier`] to be used to verify the GKR proof. - /// - /// Leave unimplemented if the `Air` doesn't use a GKR proof. - fn get_gkr_proof_verifier>( + /// Returns the object needed for the LogUp-GKR argument. + fn get_logup_gkr_evaluator( &self, - ) -> Self::GkrVerifier { - unimplemented!("`get_auxiliary_proof_verifier()` must be implemented when the proof contains a GKR proof"); + ) -> impl LogUpGkrEvaluator + { + PhantomLogUpGkrEval::new() } // PROVIDED METHODS @@ -345,13 +338,16 @@ pub trait Air: Send + Sync { lagrange_composition_coefficients: LagrangeConstraintsCompositionCoefficients, lagrange_kernel_rand_elements: &LagrangeKernelRandElements, ) -> Option> { - self.context().lagrange_kernel_aux_column_idx().map(|col_idx| { - LagrangeKernelConstraints::new( + 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. @@ -548,7 +544,7 @@ pub trait Air: Send + Sync { b_coefficients.push(public_coin.draw()?); } - let lagrange = if self.context().has_lagrange_kernel_aux_column() { + let lagrange = if self.context().logup_gkr_enabled() { let mut lagrange_kernel_t_coefficients = Vec::new(); for _ in 0..self.context().trace_len().ilog2() { lagrange_kernel_t_coefficients.push(public_coin.draw()?); @@ -564,10 +560,17 @@ pub trait Air: Send + Sync { None }; + let s_col = if self.context().logup_gkr_enabled() { + Some(public_coin.draw()?) + } else { + None + }; + Ok(ConstraintCompositionCoefficients { transition: t_coefficients, boundary: b_coefficients, lagrange, + s_col, }) } @@ -591,7 +594,13 @@ pub trait Air: Send + Sync { c_coefficients.push(public_coin.draw()?); } - let lagrange_cc = if self.context().has_lagrange_kernel_aux_column() { + let lagrange_cc = if self.context().logup_gkr_enabled() { + Some(public_coin.draw()?) + } else { + None + }; + + let s_col = if self.context().logup_gkr_enabled() { Some(public_coin.draw()?) } else { None @@ -601,6 +610,7 @@ pub trait Air: Send + Sync { trace: t_coefficients, constraints: c_coefficients, lagrange: lagrange_cc, + s_col, }) } } diff --git a/air/src/air/tests.rs b/air/src/air/tests.rs index e0063ed3b..5e9871ca5 100644 --- a/air/src/air/tests.rs +++ b/air/src/air/tests.rs @@ -9,8 +9,8 @@ use crypto::{hashers::Blake3_256, DefaultRandomCoin, RandomCoin}; use math::{fields::f64::BaseElement, get_power_series, polynom, FieldElement, StarkField}; use super::{ - Air, AirContext, Assertion, EvaluationFrame, ProofOptions, TraceInfo, - TransitionConstraintDegree, + logup_gkr::PhantomLogUpGkrEval, Air, AirContext, Assertion, EvaluationFrame, ProofOptions, + TraceInfo, TransitionConstraintDegree, }; use crate::FieldExtension; @@ -192,7 +192,7 @@ fn get_boundary_constraints() { // ================================================================================================ struct MockAir { - context: AirContext, + context: AirContext, assertions: Vec>, periodic_columns: Vec>, } @@ -225,8 +225,7 @@ impl MockAir { impl Air for MockAir { type BaseField = BaseElement; type PublicInputs = (); - type GkrProof = (); - type GkrVerifier = (); + //type LogUpGkrEvaluator = DummyLogUpGkrEval; fn new(trace_info: TraceInfo, _pub_inputs: (), _options: ProofOptions) -> Self { let num_assertions = trace_info.meta()[0] as usize; @@ -238,7 +237,7 @@ impl Air for MockAir { } } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } @@ -257,6 +256,13 @@ impl Air for MockAir { _result: &mut [E], ) { } + + fn get_logup_gkr_evaluator( + &self, + ) -> impl super::LogUpGkrEvaluator + { + PhantomLogUpGkrEval::default() + } } // UTILITY FUNCTIONS @@ -266,11 +272,11 @@ pub fn build_context( trace_length: usize, trace_width: usize, num_assertions: usize, -) -> AirContext { +) -> AirContext { let options = ProofOptions::new(32, 8, 0, FieldExtension::None, 4, 31); let t_degrees = vec![TransitionConstraintDegree::new(2)]; let trace_info = TraceInfo::new(trace_width, trace_length); - AirContext::new(trace_info, t_degrees, num_assertions, options) + AirContext::new(trace_info, (), t_degrees, num_assertions, options) } pub fn build_prng() -> DefaultRandomCoin> { diff --git a/air/src/air/trace_info.rs b/air/src/air/trace_info.rs index 99ff4aa6d..44aa0a7ea 100644 --- a/air/src/air/trace_info.rs +++ b/air/src/air/trace_info.rs @@ -27,6 +27,7 @@ pub struct TraceInfo { num_aux_segment_rands: usize, trace_length: usize, trace_meta: Vec, + logup_gkr: bool, } impl TraceInfo { @@ -65,7 +66,7 @@ impl TraceInfo { /// * Length of `meta` is greater than 65535; pub fn with_meta(width: usize, length: usize, meta: Vec) -> Self { assert!(width > 0, "trace width must be greater than 0"); - Self::new_multi_segment(width, 0, 0, length, meta) + Self::new_multi_segment(width, 0, 0, length, meta, false) } /// Creates a new [TraceInfo] with main and auxiliary segments. @@ -90,6 +91,7 @@ impl TraceInfo { num_aux_segment_rands: usize, trace_length: usize, trace_meta: Vec, + logup_gkr: bool, ) -> Self { assert!( trace_length >= Self::MIN_TRACE_LENGTH, @@ -138,6 +140,7 @@ impl TraceInfo { num_aux_segment_rands, trace_length, trace_meta, + logup_gkr, } } @@ -146,9 +149,13 @@ impl TraceInfo { /// Returns the total number of columns in an execution trace. /// + /// When LogUp-GKR is enabled, we also account for two extra columns, in the auxiliary segment, + /// which are needed for implementing the univariate IOP for multi-linear evaluation in + /// https://eprint.iacr.org/2023/1284. + /// /// This is guaranteed to be between 1 and 255. pub fn width(&self) -> usize { - self.main_segment_width + self.aux_segment_width + self.main_segment_width + self.aux_segment_width + 2 * self.logup_gkr as usize } /// Returns execution trace length. @@ -171,13 +178,13 @@ impl TraceInfo { /// Returns the number of columns in the main segment of an execution trace. /// /// This is guaranteed to be between 1 and 255. - pub fn main_trace_width(&self) -> usize { + pub fn main_segment_width(&self) -> usize { self.main_segment_width } /// Returns the number of columns in the auxiliary segment of an execution trace. pub fn aux_segment_width(&self) -> usize { - self.aux_segment_width + self.aux_segment_width + 2 * self.logup_gkr as usize } /// Returns the total number of segments in an execution trace. @@ -198,9 +205,9 @@ impl TraceInfo { } } - /// Returns the number of columns in the auxiliary trace segment. - pub fn get_aux_segment_width(&self) -> usize { - self.aux_segment_width + /// Returns a boolean indicating whether LogUp-GKR is enabled. + pub fn logup_gkr_enabled(&self) -> bool { + self.logup_gkr } /// Returns the number of random elements needed to build all auxiliary columns, except for the @@ -264,6 +271,9 @@ impl Serializable for TraceInfo { // store trace meta target.write_u16(self.trace_meta.len() as u16); target.write_bytes(&self.trace_meta); + + // write bool indicating if LogUp-GKR is used + target.write_bool(self.logup_gkr); } } @@ -326,12 +336,16 @@ impl Deserializable for TraceInfo { vec![] }; + // read `logup_gkr` + let logup_gkr = source.read_bool()?; + Ok(Self::new_multi_segment( main_segment_width, aux_segment_width, num_aux_segment_rands, trace_length, trace_meta, + logup_gkr, )) } } @@ -387,6 +401,7 @@ mod tests { aux_rands, trace_length as usize, trace_meta, + false, ); assert_eq!(expected, info.to_elements()); diff --git a/air/src/air/transition/mod.rs b/air/src/air/transition/mod.rs index 60e641817..d29cbbb8b 100644 --- a/air/src/air/transition/mod.rs +++ b/air/src/air/transition/mod.rs @@ -46,7 +46,7 @@ impl TransitionConstraints { /// # Panics /// Panics if the number of transition constraints in the context does not match the number of /// provided composition coefficients. - pub fn new(context: &AirContext, composition_coefficients: &[E]) -> Self { + pub fn new

(context: &AirContext, composition_coefficients: &[E]) -> Self { assert_eq!( context.num_transition_constraints(), composition_coefficients.len(), diff --git a/air/src/lib.rs b/air/src/lib.rs index aaede0bda..2993306b9 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, GkrRandElements, GkrVerifier, + DeepCompositionCoefficients, EvaluationFrame, GkrData, LagrangeConstraintsCompositionCoefficients, LagrangeKernelBoundaryConstraint, LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, LagrangeKernelRandElements, LagrangeKernelTransitionConstraints, LogUpGkrEvaluator, LogUpGkrOracle, TraceInfo, diff --git a/air/src/proof/context.rs b/air/src/proof/context.rs index 83c2beece..73152709a 100644 --- a/air/src/proof/context.rs +++ b/air/src/proof/context.rs @@ -190,6 +190,7 @@ mod tests { aux_rands, trace_length, vec![], + false, ); let mut expected = trace_info.to_elements(); @@ -213,8 +214,14 @@ mod tests { fri_folding_factor as usize, fri_remainder_max_degree as usize, ); - let trace_info = - TraceInfo::new_multi_segment(main_width, aux_width, aux_rands, trace_length, vec![]); + let trace_info = TraceInfo::new_multi_segment( + main_width, + aux_width, + aux_rands, + trace_length, + vec![], + false, + ); let context = Context::new::(trace_info, options); assert_eq!(expected, context.to_elements()); } diff --git a/air/src/proof/ood_frame.rs b/air/src/proof/ood_frame.rs index d4b3f14ec..feab1b260 100644 --- a/air/src/proof/ood_frame.rs +++ b/air/src/proof/ood_frame.rs @@ -229,6 +229,8 @@ impl Deserializable for OodFrame { // OOD FRAME TRACE STATES // ================================================================================================ +/// Stores trace evaluations at an OOD point. +/// /// Stores the trace evaluations at `z` and `gz`, where `z` is a random Field element in /// `current_row` and `next_row`, respectively. If the Air contains a Lagrange kernel auxiliary /// column, then that column interpolated polynomial will be evaluated at `z`, `gz`, `g^2 z`, ... diff --git a/crypto/src/merkle/concurrent.rs b/crypto/src/merkle/concurrent.rs index 637bd51b5..7a3ba077f 100644 --- a/crypto/src/merkle/concurrent.rs +++ b/crypto/src/merkle/concurrent.rs @@ -18,9 +18,10 @@ pub const MIN_CONCURRENT_LEAVES: usize = 1024; // PUBLIC FUNCTIONS // ================================================================================================ -/// Builds all internal nodes of the Merkle using all available threads and stores the -/// results in a single vector such that root of the tree is at position 1, nodes immediately -/// under the root is at positions 2 and 3 etc. +/// Builds all internal nodes of the Merkle tree. +/// +/// This uses all available threads and stores the results in a single vector such that root of +/// the tree is at position 1, nodes immediately under the root is at positions 2 and 3 etc. pub fn build_merkle_nodes(leaves: &[H::Digest]) -> Vec { let n = leaves.len() / 2; diff --git a/examples/src/fibonacci/fib2/air.rs b/examples/src/fibonacci/fib2/air.rs index 9e5d75a48..4019ddcae 100644 --- a/examples/src/fibonacci/fib2/air.rs +++ b/examples/src/fibonacci/fib2/air.rs @@ -14,15 +14,13 @@ use crate::utils::are_equal; // ================================================================================================ pub struct FibAir { - context: AirContext, + context: AirContext, result: BaseElement, } impl Air for FibAir { type BaseField = BaseElement; type PublicInputs = BaseElement; - type GkrProof = (); - type GkrVerifier = (); // CONSTRUCTOR // -------------------------------------------------------------------------------------------- @@ -30,12 +28,12 @@ impl Air for FibAir { let degrees = vec![TransitionConstraintDegree::new(1), TransitionConstraintDegree::new(1)]; assert_eq!(TRACE_WIDTH, trace_info.width()); FibAir { - context: AirContext::new(trace_info, degrees, 3, options), + context: AirContext::new(trace_info, pub_inputs, degrees, 3, options), result: pub_inputs, } } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } diff --git a/examples/src/fibonacci/fib8/air.rs b/examples/src/fibonacci/fib8/air.rs index 4d7aef9ba..17edc7970 100644 --- a/examples/src/fibonacci/fib8/air.rs +++ b/examples/src/fibonacci/fib8/air.rs @@ -15,15 +15,13 @@ use crate::utils::are_equal; // ================================================================================================ pub struct Fib8Air { - context: AirContext, + context: AirContext, result: BaseElement, } impl Air for Fib8Air { type BaseField = BaseElement; type PublicInputs = BaseElement; - type GkrProof = (); - type GkrVerifier = (); // CONSTRUCTOR // -------------------------------------------------------------------------------------------- @@ -31,12 +29,12 @@ impl Air for Fib8Air { let degrees = vec![TransitionConstraintDegree::new(1), TransitionConstraintDegree::new(1)]; assert_eq!(TRACE_WIDTH, trace_info.width()); Fib8Air { - context: AirContext::new(trace_info, degrees, 3, options), + context: AirContext::new(trace_info, pub_inputs, degrees, 3, options), result: pub_inputs, } } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } diff --git a/examples/src/fibonacci/fib_small/air.rs b/examples/src/fibonacci/fib_small/air.rs index 66580c872..b48eb734b 100644 --- a/examples/src/fibonacci/fib_small/air.rs +++ b/examples/src/fibonacci/fib_small/air.rs @@ -14,15 +14,13 @@ use crate::utils::are_equal; // ================================================================================================ pub struct FibSmall { - context: AirContext, + context: AirContext, result: BaseElement, } impl Air for FibSmall { type BaseField = BaseElement; type PublicInputs = BaseElement; - type GkrProof = (); - type GkrVerifier = (); // CONSTRUCTOR // -------------------------------------------------------------------------------------------- @@ -30,12 +28,12 @@ impl Air for FibSmall { let degrees = vec![TransitionConstraintDegree::new(1), TransitionConstraintDegree::new(1)]; assert_eq!(TRACE_WIDTH, trace_info.width()); FibSmall { - context: AirContext::new(trace_info, degrees, 3, options), + context: AirContext::new(trace_info, pub_inputs, degrees, 3, options), result: pub_inputs, } } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } diff --git a/examples/src/fibonacci/mulfib2/air.rs b/examples/src/fibonacci/mulfib2/air.rs index 3190d2e41..501adf6af 100644 --- a/examples/src/fibonacci/mulfib2/air.rs +++ b/examples/src/fibonacci/mulfib2/air.rs @@ -16,15 +16,13 @@ use crate::utils::are_equal; // ================================================================================================ pub struct MulFib2Air { - context: AirContext, + context: AirContext, result: BaseElement, } impl Air for MulFib2Air { type BaseField = BaseElement; type PublicInputs = BaseElement; - type GkrProof = (); - type GkrVerifier = (); // CONSTRUCTOR // -------------------------------------------------------------------------------------------- @@ -32,12 +30,12 @@ impl Air for MulFib2Air { let degrees = vec![TransitionConstraintDegree::new(2), TransitionConstraintDegree::new(2)]; assert_eq!(TRACE_WIDTH, trace_info.width()); MulFib2Air { - context: AirContext::new(trace_info, degrees, 3, options), + context: AirContext::new(trace_info, pub_inputs, degrees, 3, options), result: pub_inputs, } } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } diff --git a/examples/src/fibonacci/mulfib8/air.rs b/examples/src/fibonacci/mulfib8/air.rs index bbbe1dea0..c76f4f091 100644 --- a/examples/src/fibonacci/mulfib8/air.rs +++ b/examples/src/fibonacci/mulfib8/air.rs @@ -16,15 +16,13 @@ use crate::utils::are_equal; // ================================================================================================ pub struct MulFib8Air { - context: AirContext, + context: AirContext, result: BaseElement, } impl Air for MulFib8Air { type BaseField = BaseElement; type PublicInputs = BaseElement; - type GkrProof = (); - type GkrVerifier = (); // CONSTRUCTOR // -------------------------------------------------------------------------------------------- @@ -41,12 +39,12 @@ impl Air for MulFib8Air { ]; assert_eq!(TRACE_WIDTH, trace_info.width()); MulFib8Air { - context: AirContext::new(trace_info, degrees, 3, options), + context: AirContext::new(trace_info, pub_inputs, degrees, 3, options), result: pub_inputs, } } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } diff --git a/examples/src/lamport/aggregate/air.rs b/examples/src/lamport/aggregate/air.rs index 29b6e2372..57708fd74 100644 --- a/examples/src/lamport/aggregate/air.rs +++ b/examples/src/lamport/aggregate/air.rs @@ -38,7 +38,7 @@ impl ToElements for PublicInputs { } pub struct LamportAggregateAir { - context: AirContext, + context: AirContext, pub_keys: Vec<[BaseElement; 2]>, messages: Vec<[BaseElement; 2]>, } @@ -46,8 +46,6 @@ pub struct LamportAggregateAir { impl Air for LamportAggregateAir { type BaseField = BaseElement; type PublicInputs = PublicInputs; - type GkrProof = (); - type GkrVerifier = (); // CONSTRUCTOR // -------------------------------------------------------------------------------------------- @@ -88,13 +86,13 @@ impl Air for LamportAggregateAir { ]; assert_eq!(TRACE_WIDTH, trace_info.width()); LamportAggregateAir { - context: AirContext::new(trace_info, degrees, 22, options), + context: AirContext::new(trace_info, pub_inputs.clone(), degrees, 22, options), pub_keys: pub_inputs.pub_keys, messages: pub_inputs.messages, } } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } diff --git a/examples/src/lamport/threshold/air.rs b/examples/src/lamport/threshold/air.rs index 41983c743..b68a2a24d 100644 --- a/examples/src/lamport/threshold/air.rs +++ b/examples/src/lamport/threshold/air.rs @@ -22,7 +22,7 @@ const TWO: BaseElement = BaseElement::new(2); // THRESHOLD LAMPORT PLUS SIGNATURE AIR // ================================================================================================ -#[derive(Clone)] +#[derive(Clone, Default)] pub struct PublicInputs { pub pub_key_root: [BaseElement; 2], pub num_pub_keys: usize, @@ -41,7 +41,7 @@ impl ToElements for PublicInputs { } pub struct LamportThresholdAir { - context: AirContext, + context: AirContext, pub_key_root: [BaseElement; 2], num_pub_keys: usize, num_signatures: usize, @@ -51,8 +51,6 @@ pub struct LamportThresholdAir { impl Air for LamportThresholdAir { type BaseField = BaseElement; type PublicInputs = PublicInputs; - type GkrProof = (); - type GkrVerifier = (); // CONSTRUCTOR // -------------------------------------------------------------------------------------------- @@ -99,7 +97,7 @@ impl Air for LamportThresholdAir { ]; assert_eq!(TRACE_WIDTH, trace_info.width()); LamportThresholdAir { - context: AirContext::new(trace_info, degrees, 26, options), + context: AirContext::new(trace_info, pub_inputs.clone(), degrees, 26, options), pub_key_root: pub_inputs.pub_key_root, num_pub_keys: pub_inputs.num_pub_keys, num_signatures: pub_inputs.num_signatures, @@ -244,7 +242,7 @@ impl Air for LamportThresholdAir { result } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } } diff --git a/examples/src/merkle/air.rs b/examples/src/merkle/air.rs index e0c8b177c..5d38397ff 100644 --- a/examples/src/merkle/air.rs +++ b/examples/src/merkle/air.rs @@ -14,6 +14,7 @@ use crate::utils::{are_equal, is_binary, is_zero, not, EvaluationResult}; // MERKLE PATH VERIFICATION AIR // ================================================================================================ +#[derive(Clone)] pub struct PublicInputs { pub tree_root: [BaseElement; 2], } @@ -25,15 +26,13 @@ impl ToElements for PublicInputs { } pub struct MerkleAir { - context: AirContext, + context: AirContext, tree_root: [BaseElement; 2], } impl Air for MerkleAir { type BaseField = BaseElement; type PublicInputs = PublicInputs; - type GkrProof = (); - type GkrVerifier = (); // CONSTRUCTOR // -------------------------------------------------------------------------------------------- @@ -49,12 +48,12 @@ impl Air for MerkleAir { ]; assert_eq!(TRACE_WIDTH, trace_info.width()); MerkleAir { - context: AirContext::new(trace_info, degrees, 4, options), + context: AirContext::new(trace_info, pub_inputs.clone(), degrees, 4, options), tree_root: pub_inputs.tree_root, } } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } diff --git a/examples/src/rescue/air.rs b/examples/src/rescue/air.rs index a9d3d5ebb..09bf9c450 100644 --- a/examples/src/rescue/air.rs +++ b/examples/src/rescue/air.rs @@ -37,6 +37,7 @@ const CYCLE_MASK: [BaseElement; CYCLE_LENGTH] = [ // RESCUE AIR // ================================================================================================ +#[derive(Clone)] pub struct PublicInputs { pub seed: [BaseElement; 2], pub result: [BaseElement; 2], @@ -51,7 +52,7 @@ impl ToElements for PublicInputs { } pub struct RescueAir { - context: AirContext, + context: AirContext, seed: [BaseElement; 2], result: [BaseElement; 2], } @@ -59,8 +60,6 @@ pub struct RescueAir { impl Air for RescueAir { type BaseField = BaseElement; type PublicInputs = PublicInputs; - type GkrProof = (); - type GkrVerifier = (); // CONSTRUCTOR // -------------------------------------------------------------------------------------------- @@ -73,13 +72,13 @@ impl Air for RescueAir { ]; assert_eq!(TRACE_WIDTH, trace_info.width()); RescueAir { - context: AirContext::new(trace_info, degrees, 4, options), + context: AirContext::new(trace_info, pub_inputs.clone(), degrees, 4, options), seed: pub_inputs.seed, result: pub_inputs.result, } } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } diff --git a/examples/src/rescue_raps/air.rs b/examples/src/rescue_raps/air.rs index 6fb5321b1..694e189bc 100644 --- a/examples/src/rescue_raps/air.rs +++ b/examples/src/rescue_raps/air.rs @@ -41,6 +41,7 @@ const CYCLE_MASK: [BaseElement; CYCLE_LENGTH] = [ // RESCUE AIR // ================================================================================================ +#[derive(Clone)] pub struct PublicInputs { pub result: [[BaseElement; 2]; 2], } @@ -52,15 +53,13 @@ impl ToElements for PublicInputs { } pub struct RescueRapsAir { - context: AirContext, + context: AirContext, result: [[BaseElement; 2]; 2], } impl Air for RescueRapsAir { type BaseField = BaseElement; type PublicInputs = PublicInputs; - type GkrProof = (); - type GkrVerifier = (); // CONSTRUCTOR // -------------------------------------------------------------------------------------------- @@ -76,18 +75,18 @@ impl Air for RescueRapsAir { RescueRapsAir { context: AirContext::new_multi_segment( trace_info, + pub_inputs.clone(), main_degrees, aux_degrees, 8, 2, - None, options, ), result: pub_inputs.result, } } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } diff --git a/examples/src/rescue_raps/custom_trace_table.rs b/examples/src/rescue_raps/custom_trace_table.rs index 063d509a4..f6f9d075b 100644 --- a/examples/src/rescue_raps/custom_trace_table.rs +++ b/examples/src/rescue_raps/custom_trace_table.rs @@ -89,7 +89,7 @@ impl RapTraceTable { let columns = unsafe { (0..width).map(|_| uninit_vector(length)).collect() }; Self { - info: TraceInfo::new_multi_segment(width, 3, 3, length, meta), + info: TraceInfo::new_multi_segment(width, 3, 3, length, meta, false), trace: ColMatrix::new(columns), } } @@ -113,7 +113,7 @@ impl RapTraceTable { I: Fn(&mut [B]), U: Fn(usize, &mut [B]), { - let mut state = vec![B::ZERO; self.info.main_trace_width()]; + let mut state = vec![B::ZERO; self.info.main_segment_width()]; init(&mut state); self.update_row(0, &state); @@ -133,7 +133,7 @@ impl RapTraceTable { /// Returns the number of columns in this execution trace. pub fn width(&self) -> usize { - self.info.main_trace_width() + self.info.main_segment_width() } /// Returns value of the cell in the specified column at the specified row of this trace. diff --git a/examples/src/rescue_raps/prover.rs b/examples/src/rescue_raps/prover.rs index 7adee9bbb..6e50f1572 100644 --- a/examples/src/rescue_raps/prover.rs +++ b/examples/src/rescue_raps/prover.rs @@ -139,16 +139,11 @@ where DefaultConstraintEvaluator::new(air, aux_rand_elements, composition_coefficients) } - fn build_aux_trace( - &self, - trace: &Self::Trace, - aux_rand_elements: &AuxRandElements, - ) -> ColMatrix + fn build_aux_trace(&self, trace: &Self::Trace, aux_rand_elements: &[E]) -> ColMatrix where E: FieldElement, { let main_trace = trace.main_segment(); - let rand_elements = aux_rand_elements.rand_elements(); let mut current_row = unsafe { uninit_vector(main_trace.num_cols()) }; let mut next_row = unsafe { uninit_vector(main_trace.num_cols()) }; @@ -157,10 +152,10 @@ where // Columns storing the copied values for the permutation argument are not necessary, but // help understanding the construction of RAPs and are kept for illustrative purposes. - aux_columns[0][0] = - rand_elements[0] * current_row[0].into() + rand_elements[1] * current_row[1].into(); - aux_columns[1][0] = - rand_elements[0] * current_row[4].into() + rand_elements[1] * current_row[5].into(); + aux_columns[0][0] = aux_rand_elements[0] * current_row[0].into() + + aux_rand_elements[1] * current_row[1].into(); + aux_columns[1][0] = aux_rand_elements[0] * current_row[4].into() + + aux_rand_elements[1] * current_row[5].into(); // Permutation argument column aux_columns[2][0] = E::ONE; @@ -172,14 +167,16 @@ where main_trace.read_row_into(index, &mut current_row); main_trace.read_row_into(index + 1, &mut next_row); - aux_columns[0][index] = rand_elements[0] * (next_row[0] - current_row[0]).into() - + rand_elements[1] * (next_row[1] - current_row[1]).into(); - aux_columns[1][index] = rand_elements[0] * (next_row[4] - current_row[4]).into() - + rand_elements[1] * (next_row[5] - current_row[5]).into(); + aux_columns[0][index] = aux_rand_elements[0] + * (next_row[0] - current_row[0]).into() + + aux_rand_elements[1] * (next_row[1] - current_row[1]).into(); + aux_columns[1][index] = aux_rand_elements[0] + * (next_row[4] - current_row[4]).into() + + aux_rand_elements[1] * (next_row[5] - current_row[5]).into(); } - let num = aux_columns[0][index - 1] + rand_elements[2]; - let denom = aux_columns[1][index - 1] + rand_elements[2]; + let num = aux_columns[0][index - 1] + aux_rand_elements[2]; + let denom = aux_columns[1][index - 1] + aux_rand_elements[2]; aux_columns[2][index] = aux_columns[2][index - 1] * num * denom.inv(); } diff --git a/examples/src/utils/rescue.rs b/examples/src/utils/rescue.rs index e09cb094e..be297fcf3 100644 --- a/examples/src/utils/rescue.rs +++ b/examples/src/utils/rescue.rs @@ -21,6 +21,8 @@ pub const RATE_WIDTH: usize = 4; /// Two elements (32-bytes) are returned as digest. const DIGEST_SIZE: usize = 2; +/// Number of rounds used in Rescue. +/// /// The number of rounds is set to 7 to provide 128-bit security level with 40% security margin; /// computed using algorithm 7 from /// security margin here differs from Rescue Prime specification which suggests 50% security diff --git a/examples/src/vdf/exempt/air.rs b/examples/src/vdf/exempt/air.rs index 9254e4e0a..015778459 100644 --- a/examples/src/vdf/exempt/air.rs +++ b/examples/src/vdf/exempt/air.rs @@ -29,7 +29,7 @@ impl ToElements for VdfInputs { // ================================================================================================ pub struct VdfAir { - context: AirContext, + context: AirContext, seed: BaseElement, result: BaseElement, } @@ -37,16 +37,14 @@ pub struct VdfAir { impl Air for VdfAir { type BaseField = BaseElement; type PublicInputs = VdfInputs; - type GkrProof = (); - type GkrVerifier = (); fn new(trace_info: TraceInfo, pub_inputs: VdfInputs, options: ProofOptions) -> Self { let degrees = vec![TransitionConstraintDegree::new(3)]; assert_eq!(TRACE_WIDTH, trace_info.width()); // make sure the last two rows are excluded from transition constraints as we populate // values in the last row with garbage - let context = - AirContext::new(trace_info, degrees, 2, options).set_num_transition_exemptions(2); + let context = AirContext::new(trace_info, pub_inputs.clone(), degrees, 2, options) + .set_num_transition_exemptions(2); Self { context, seed: pub_inputs.seed, @@ -76,7 +74,7 @@ impl Air for VdfAir { ] } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } } diff --git a/examples/src/vdf/regular/air.rs b/examples/src/vdf/regular/air.rs index b434c1478..bec2ccb3c 100644 --- a/examples/src/vdf/regular/air.rs +++ b/examples/src/vdf/regular/air.rs @@ -29,7 +29,7 @@ impl ToElements for VdfInputs { // ================================================================================================ pub struct VdfAir { - context: AirContext, + context: AirContext, seed: BaseElement, result: BaseElement, } @@ -37,14 +37,12 @@ pub struct VdfAir { impl Air for VdfAir { type BaseField = BaseElement; type PublicInputs = VdfInputs; - type GkrProof = (); - type GkrVerifier = (); fn new(trace_info: TraceInfo, pub_inputs: VdfInputs, options: ProofOptions) -> Self { let degrees = vec![TransitionConstraintDegree::new(3)]; assert_eq!(TRACE_WIDTH, trace_info.width()); Self { - context: AirContext::new(trace_info, degrees, 2, options), + context: AirContext::new(trace_info, pub_inputs.clone(), degrees, 2, options), seed: pub_inputs.seed, result: pub_inputs.result, } @@ -67,7 +65,7 @@ impl Air for VdfAir { vec![Assertion::single(0, 0, self.seed), Assertion::single(0, last_step, self.result)] } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } } diff --git a/math/src/field/f64/mod.rs b/math/src/field/f64/mod.rs index 119676076..64c637c0a 100644 --- a/math/src/field/f64/mod.rs +++ b/math/src/field/f64/mod.rs @@ -3,9 +3,10 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -//! An implementation of a 64-bit STARK-friendly prime field with modulus $2^{64} - 2^{32} + 1$ -//! using Montgomery representation. -//! Our implementation follows and is constant-time. +//! An implementation of a 64-bit STARK-friendly prime field with modulus $2^{64} - 2^{32} + 1$. +//! +//! Our implementation uses Montgomery representation and follows +//! and is constant-time. //! //! This field supports very fast modular arithmetic and has a number of other attractive //! properties, including: diff --git a/prover/Cargo.toml b/prover/Cargo.toml index 36272766f..6fef7f90f 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -35,6 +35,8 @@ crypto = { version = "0.9", path = "../crypto", package = "winter-crypto", defau fri = { version = "0.9", path = '../fri', package = "winter-fri", default-features = false } math = { version = "0.9", path = "../math", package = "winter-math", default-features = false } maybe_async = { path = "../utils/maybe_async" , package = "winter-maybe-async" } +sumcheck = { version = "0.1", path = "../sumcheck", package = "winter-sumcheck", default-features = false } +thiserror = { version = "1.0", git = "https://github.com/bitwalker/thiserror", branch = "no-std", default-features = false } tracing = { version = "0.1", default-features = false, features = ["attributes"]} utils = { version = "0.9", path = "../utils/core", package = "winter-utils", default-features = false } diff --git a/prover/benches/lagrange_kernel.rs b/prover/benches/lagrange_kernel.rs index 7ee8ab3c3..348554806 100644 --- a/prover/benches/lagrange_kernel.rs +++ b/prover/benches/lagrange_kernel.rs @@ -7,15 +7,14 @@ use std::time::Duration; use air::{ Air, AirContext, Assertion, AuxRandElements, ConstraintCompositionCoefficients, - EvaluationFrame, FieldExtension, GkrRandElements, LagrangeKernelRandElements, ProofOptions, - TraceInfo, TransitionConstraintDegree, + EvaluationFrame, FieldExtension, ProofOptions, TraceInfo, TransitionConstraintDegree, }; use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; -use crypto::{hashers::Blake3_256, DefaultRandomCoin, MerkleTree, RandomCoin}; +use crypto::{hashers::Blake3_256, DefaultRandomCoin, MerkleTree}; use math::{fields::f64::BaseElement, ExtensionOf, FieldElement}; use winter_prover::{ - matrix::ColMatrix, DefaultConstraintEvaluator, DefaultTraceLde, Prover, ProverGkrProof, - StarkDomain, Trace, TracePolyTable, + matrix::ColMatrix, DefaultConstraintEvaluator, DefaultTraceLde, Prover, StarkDomain, Trace, + TracePolyTable, }; const TRACE_LENS: [usize; 2] = [2_usize.pow(16), 2_usize.pow(20)]; @@ -61,7 +60,7 @@ impl LagrangeTrace { Self { main_trace: ColMatrix::new(vec![main_trace_col]), - info: TraceInfo::new_multi_segment(1, aux_segment_width, 0, trace_len, vec![]), + info: TraceInfo::new_multi_segment(1, aux_segment_width, 0, trace_len, vec![], false), } } @@ -94,31 +93,28 @@ impl Trace for LagrangeTrace { // ================================================================================================= struct LagrangeKernelAir { - context: AirContext, + context: AirContext, } impl Air for LagrangeKernelAir { type BaseField = BaseElement; - type GkrProof = (); - type GkrVerifier = (); - type PublicInputs = (); fn new(trace_info: TraceInfo, _pub_inputs: Self::PublicInputs, options: ProofOptions) -> Self { Self { context: AirContext::new_multi_segment( trace_info, + _pub_inputs, vec![TransitionConstraintDegree::new(1)], vec![TransitionConstraintDegree::new(1)], 1, 1, - Some(0), options, ), } } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } @@ -221,42 +217,14 @@ impl Prover for LagrangeProver { DefaultConstraintEvaluator::new(air, aux_rand_elements, composition_coefficients) } - fn generate_gkr_proof( - &self, - main_trace: &Self::Trace, - public_coin: &mut Self::RandomCoin, - ) -> (ProverGkrProof, GkrRandElements) - where - E: FieldElement, - { - let main_trace = main_trace.main_segment(); - let lagrange_kernel_rand_elements = { - let log_trace_len = main_trace.num_rows().ilog2() as usize; - let mut rand_elements = Vec::with_capacity(log_trace_len); - for _ in 0..log_trace_len { - rand_elements.push(public_coin.draw().unwrap()); - } - - LagrangeKernelRandElements::new(rand_elements) - }; - - ((), GkrRandElements::new(lagrange_kernel_rand_elements, Vec::new())) - } - - fn build_aux_trace( - &self, - main_trace: &Self::Trace, - aux_rand_elements: &AuxRandElements, - ) -> ColMatrix + fn build_aux_trace(&self, main_trace: &Self::Trace, aux_rand_elements: &[E]) -> ColMatrix where E: FieldElement, { let main_trace = main_trace.main_segment(); let mut columns = Vec::new(); - let lagrange_kernel_rand_elements = aux_rand_elements - .lagrange() - .expect("expected lagrange kernel random elements to be present."); + let lagrange_kernel_rand_elements = aux_rand_elements; // first build the Lagrange kernel column { diff --git a/prover/src/constraints/evaluator/default.rs b/prover/src/constraints/evaluator/default.rs index 8f96c7dcd..ea02b41d4 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().has_lagrange_kernel_aux_column() { + 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 @@ -198,7 +198,7 @@ where fragment: &mut EvaluationTableFragment, ) { // initialize buffers to hold trace values and evaluation results at each step; - 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 evaluations = vec![E::ZERO; fragment.num_columns()]; let mut t_evaluations = vec![E::BaseField::ZERO; self.num_main_transition_constraints()]; @@ -249,7 +249,7 @@ where fragment: &mut EvaluationTableFragment, ) { // initialize buffers to hold trace values and evaluation results at each step - 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 mut tm_evaluations = vec![E::BaseField::ZERO; self.num_main_transition_constraints()]; let mut ta_evaluations = vec![E::ZERO; self.num_aux_transition_constraints()]; diff --git a/prover/src/constraints/evaluator/periodic_table.rs b/prover/src/constraints/evaluator/periodic_table.rs index ec72aa766..f1fc751e0 100644 --- a/prover/src/constraints/evaluator/periodic_table.rs +++ b/prover/src/constraints/evaluator/periodic_table.rs @@ -94,7 +94,7 @@ mod tests { use air::Air; use math::{ - fields::f128::BaseElement, get_power_series_with_offset, polynom, FieldElement, StarkField, + fields::f64::BaseElement, get_power_series_with_offset, polynom, FieldElement, StarkField, }; use crate::tests::MockAir; @@ -104,8 +104,8 @@ mod tests { let trace_length = 32; // instantiate AIR with 2 periodic columns - let col1 = vec![1u128, 2].into_iter().map(BaseElement::new).collect::>(); - let col2 = vec![3u128, 4, 5, 6].into_iter().map(BaseElement::new).collect::>(); + let col1 = vec![1u64, 2].into_iter().map(BaseElement::new).collect::>(); + let col2 = vec![3u64, 4, 5, 6].into_iter().map(BaseElement::new).collect::>(); let air = MockAir::with_periodic_columns(vec![col1, col2], trace_length); // build a table of periodic values diff --git a/prover/src/errors.rs b/prover/src/errors.rs index a0d01a233..3a14de46e 100644 --- a/prover/src/errors.rs +++ b/prover/src/errors.rs @@ -21,6 +21,8 @@ pub enum ProverError { /// This error occurs when the base field specified by the AIR does not support field extension /// of degree specified by proof options. UnsupportedFieldExtension(usize), + /// This error occurs when generation of the GKR proof for the LogUp relation fails. + FailedToGenerateGkrProof, } impl fmt::Display for ProverError { @@ -36,6 +38,9 @@ impl fmt::Display for ProverError { Self::UnsupportedFieldExtension(degree) => { write!(f, "field extension of degree {degree} is not supported for the specified base field") } + ProverError::FailedToGenerateGkrProof => { + write!(f, "Failed to generate the GKR proof for the LogUp relation") + } } } } diff --git a/prover/src/lib.rs b/prover/src/lib.rs index ac0e82be2..703f19d8c 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -48,7 +48,7 @@ pub use air::{ EvaluationFrame, FieldExtension, LagrangeKernelRandElements, ProofOptions, TraceInfo, TransitionConstraintDegree, }; -use air::{AuxRandElements, GkrRandElements}; +use air::{AuxRandElements, GkrData, LogUpGkrEvaluator}; pub use crypto; use crypto::{ElementHasher, RandomCoin, VectorCommitment}; use fri::FriProver; @@ -58,6 +58,7 @@ use math::{ fields::{CubeExtension, QuadExtension}, ExtensibleField, FieldElement, StarkField, ToElements, }; +use sumcheck::FinalOpeningClaim; use tracing::{event, info_span, instrument, Level}; pub use utils::{ iterators, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, @@ -86,6 +87,9 @@ pub use trace::{ TraceTableFragment, }; +mod logup_gkr; +use logup_gkr::{build_lagrange_column, build_s_column, prove_gkr}; + mod channel; use channel::ProverChannel; @@ -101,9 +105,6 @@ pub mod tests; // this segment width seems to give the best performance for small fields (i.e., 64 bits) const DEFAULT_SEGMENT_WIDTH: usize = 8; -/// Accesses the `GkrProof` type in a [`Prover`]. -pub type ProverGkrProof

= <

::Air as Air>::GkrProof; - /// Defines a STARK prover for a computation. /// /// A STARK prover can be used to generate STARK proofs. The prover contains definitions of a @@ -201,28 +202,10 @@ pub trait Prover { // PROVIDED METHODS // -------------------------------------------------------------------------------------------- - /// Builds the GKR proof. If the [`Air`] doesn't use a GKR proof, leave unimplemented. - #[allow(unused_variables)] - #[maybe_async] - fn generate_gkr_proof( - &self, - main_trace: &Self::Trace, - public_coin: &mut Self::RandomCoin, - ) -> (ProverGkrProof, GkrRandElements) - where - E: FieldElement, - { - unimplemented!("`Prover::generate_gkr_proof` needs to be implemented when the auxiliary trace has a Lagrange kernel column.") - } - /// Builds and returns the auxiliary trace. #[allow(unused_variables)] #[maybe_async] - fn build_aux_trace( - &self, - main_trace: &Self::Trace, - aux_rand_elements: &AuxRandElements, - ) -> ColMatrix + fn build_aux_trace(&self, main_trace: &Self::Trace, aux_rand_elements: &[E]) -> ColMatrix where E: FieldElement, { @@ -241,7 +224,6 @@ pub trait Prover { fn prove(&self, trace: Self::Trace) -> Result where ::PublicInputs: Send, - ::GkrProof: Send, { // figure out which version of the generic proof generation procedure to run. this is a sort // of static dispatch for selecting two generic parameter: extension field and hash @@ -275,7 +257,6 @@ pub trait Prover { where E: FieldElement, ::PublicInputs: Send, - ::GkrProof: Send, { // 0 ----- instantiate AIR and prover channel --------------------------------------------- @@ -314,27 +295,40 @@ pub trait Prover { // build the auxiliary trace segment, and append the resulting segments to trace commitment // and trace polynomial table structs let aux_trace_with_metadata = if air.trace_info().is_multi_segment() { - let (gkr_proof, aux_rand_elements) = if air.context().has_lagrange_kernel_aux_column() { - let (gkr_proof, gkr_rand_elements) = - maybe_await!(self.generate_gkr_proof(&trace, channel.public_coin())); - - let rand_elements = air - .get_aux_rand_elements(channel.public_coin()) - .expect("failed to draw random elements for the auxiliary trace segment"); - - let aux_rand_elements = - AuxRandElements::new_with_gkr(rand_elements, gkr_rand_elements); - - (Some(gkr_proof), aux_rand_elements) + // build the auxiliary segment without the LogUp-GKR related part + let aux_rand_elements = air + .get_aux_rand_elements(channel.public_coin()) + .expect("failed to draw random elements for the auxiliary trace segment"); + let mut aux_trace = maybe_await!(self.build_aux_trace(&trace, &aux_rand_elements)); + + // build the LogUp-GKR related section of the auxiliary segment, if any. This will also + // build an object containing randomness and data related to the LogUp-GKR section of + // the auxiliary trace segment. + let (gkr_proof, gkr_rand_elements) = if air.context().logup_gkr_enabled() { + let gkr_proof = + prove_gkr(&trace, &air.get_logup_gkr_evaluator(), channel.public_coin()) + .map_err(|_| ProverError::FailedToGenerateGkrProof)?; + + let FinalOpeningClaim { eval_point, openings } = + gkr_proof.get_final_opening_claim(); + + let gkr_data = air + .get_logup_gkr_evaluator() + .generate_univariate_iop_for_multi_linear_opening_data( + openings, + eval_point, + channel.public_coin(), + ); + + // add the extra columns required for LogUp-GKR + maybe_await!(build_logup_gkr_columns(&air, &trace, &mut aux_trace, &gkr_data)); + + (Some(gkr_proof), Some(gkr_data)) } else { - let rand_elements = air - .get_aux_rand_elements(channel.public_coin()) - .expect("failed to draw random elements for the auxiliary trace segment"); - - (None, AuxRandElements::new(rand_elements)) + (None, None) }; - - let aux_trace = maybe_await!(self.build_aux_trace(&trace, &aux_rand_elements)); + // build the set of all random values associated to the auxiliary segment + let aux_rand_elements = AuxRandElements::new(aux_rand_elements, gkr_rand_elements); // commit to the auxiliary trace segment let aux_segment_polys = { @@ -616,3 +610,26 @@ pub trait Prover { (constraint_commitment, composition_poly) } } + +/// Builds and appends to the auxiliary segment two additional columns needed for implementing +/// the univariate IOP for multi-linear evaluation of Section 5 in [1]. +/// +/// [1]: https://eprint.iacr.org/2023/1284 +#[maybe_async] +fn build_logup_gkr_columns( + air: &A, + main_trace: &T, + aux_trace: &mut ColMatrix, + gkr_data: &GkrData, +) where + E: FieldElement, + A: Air, + T: Trace, +{ + let evaluator = air.get_logup_gkr_evaluator(); + let lagrange_col = build_lagrange_column(&gkr_data.lagrange_kernel_eval_point); + let s_col = build_s_column(main_trace, gkr_data, &evaluator, &lagrange_col); + + aux_trace.merge_column(s_col); + aux_trace.merge_column(lagrange_col); +} diff --git a/prover/src/logup_gkr/mod.rs b/prover/src/logup_gkr/mod.rs new file mode 100644 index 000000000..643258ee2 --- /dev/null +++ b/prover/src/logup_gkr/mod.rs @@ -0,0 +1,403 @@ +use alloc::vec::Vec; +use core::ops::Add; + +use air::{EvaluationFrame, GkrData, LogUpGkrEvaluator}; +use math::FieldElement; +use sumcheck::{EqFunction, MultiLinearPoly, SumCheckProverError}; +use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; + +use crate::Trace; + +mod prover; +pub use prover::prove_gkr; + +// EVALUATED CIRCUIT +// ================================================================================================ + +/// Evaluation of a layered circuit for computing a sum of fractions. +/// +/// The circuit computes a sum of fractions based on the formula a / c + b / d = (a * d + b * c) / +/// (c * d) which defines a "gate" ((a, b), (c, d)) --> (a * d + b * c, c * d) upon which the +/// [`EvaluatedCircuit`] is built. Due to the uniformity of the circuit, each of the circuit +/// layers collect all the: +/// +/// 1. `a`'s into a [`MultiLinearPoly`] called `left_numerators`. +/// 2. `b`'s into a [`MultiLinearPoly`] called `right_numerators`. +/// 3. `c`'s into a [`MultiLinearPoly`] called `left_denominators`. +/// 4. `d`'s into a [`MultiLinearPoly`] called `right_denominators`. +/// +/// The relation between two subsequent layers is given by the formula +/// +/// p_0[layer + 1](x_0, x_1, ..., x_{ν - 2}) = p_0[layer](x_0, x_1, ..., x_{ν - 2}, 0) * +/// q_1[layer](x_0, x_1, ..., x_{ν - 2}, 0) +/// + p_1[layer](x_0, x_1, ..., x_{ν - 2}, 0) * q_0[layer](x_0, +/// x_1, ..., x_{ν - 2}, 0) +/// +/// p_1[layer + 1](x_0, x_1, ..., x_{ν - 2}) = p_0[layer](x_0, x_1, ..., x_{ν - 2}, 1) * +/// q_1[layer](x_0, x_1, ..., x_{ν - 2}, 1) +/// + p_1[layer](x_0, x_1, ..., x_{ν - 2}, 1) * q_0[layer](x_0, +/// x_1, ..., x_{ν - 2}, 1) +/// +/// and +/// +/// q_0[layer + 1](x_0, x_1, ..., x_{ν - 2}) = q_0[layer](x_0, x_1, ..., x_{ν - 2}, 0) * +/// q_1[layer](x_0, x_1, ..., x_{ν - 1}, 0) +/// q_1[layer + 1](x_0, x_1, ..., x_{ν - 2}) = q_0[layer](x_0, x_1, ..., x_{ν - 2}, 1) * +/// q_1[layer](x_0, x_1, ..., x_{ν - 1}, 1) +/// +/// This logic is encoded in [`CircuitWire`]. +/// +/// This means that layer ν will be the output layer and will consist of four values +/// (p_0[ν - 1], p_1[ν - 1], p_0[ν - 1], p_1[ν - 1]) ∈ 𝔽^ν. +pub struct EvaluatedCircuit { + layer_polys: Vec>, +} + +impl EvaluatedCircuit { + /// Creates a new [`EvaluatedCircuit`] by evaluating the circuit where the input layer is + /// defined from the main trace columns. + pub fn new( + main_trace_columns: &impl Trace, + evaluator: &impl LogUpGkrEvaluator, + log_up_randomness: &[E], + ) -> Result { + let mut layer_polys = Vec::new(); + + let mut current_layer = + Self::generate_input_layer(main_trace_columns, evaluator, log_up_randomness); + while current_layer.num_wires() > 1 { + let next_layer = Self::compute_next_layer(¤t_layer); + + layer_polys.push(CircuitLayerPolys::from_circuit_layer(current_layer)); + + current_layer = next_layer; + } + + Ok(Self { layer_polys }) + } + + /// Returns all layers of the evaluated circuit, starting from the input layer. + /// + /// Note that the return type is a slice of [`CircuitLayerPolys`] as opposed to + /// [`CircuitLayer`], since the evaluated layers are stored in a representation which can be + /// proved using GKR. + pub fn layers(self) -> Vec> { + self.layer_polys + } + + /// Returns the numerator/denominator polynomials representing the output layer of the circuit. + pub fn output_layer(&self) -> &CircuitLayerPolys { + self.layer_polys.last().expect("circuit has at least one layer") + } + + /// Evaluates the output layer at `query`, where the numerators of the output layer are treated + /// as evaluations of a multilinear polynomial, and similarly for the denominators. + pub fn evaluate_output_layer(&self, query: E) -> (E, E) { + let CircuitLayerPolys { numerators, denominators } = self.output_layer(); + + (numerators.evaluate(&[query]), denominators.evaluate(&[query])) + } + + // HELPERS + // ------------------------------------------------------------------------------------------- + + /// Generates the input layer of the circuit from the main trace columns and some randomness + /// provided by the verifier. + fn generate_input_layer( + main_trace: &impl Trace, + evaluator: &impl LogUpGkrEvaluator, + log_up_randomness: &[E], + ) -> CircuitLayer { + let num_fractions = evaluator.get_num_fractions(); + let mut input_layer_wires = + Vec::with_capacity(main_trace.main_segment().num_rows() * num_fractions); + let mut main_frame = EvaluationFrame::new(main_trace.main_segment().num_cols()); + + let mut query = vec![E::BaseField::ZERO; evaluator.get_oracles().len()]; + let mut numerators = vec![E::ZERO; num_fractions]; + let mut denominators = vec![E::ZERO; num_fractions]; + for i in 0..main_trace.main_segment().num_rows() { + let wires_from_trace_row = { + main_trace.read_main_frame(i, &mut main_frame); + + evaluator.build_query(&main_frame, &[], &mut query); + + evaluator.evaluate_query( + &query, + log_up_randomness, + &mut numerators, + &mut denominators, + ); + let input_gates_values: Vec> = numerators + .iter() + .zip(denominators.iter()) + .map(|(numerator, denominator)| CircuitWire::new(*numerator, *denominator)) + .collect(); + input_gates_values + }; + + input_layer_wires.extend(wires_from_trace_row); + } + + CircuitLayer::new(input_layer_wires) + } + + /// Computes the subsequent layer of the circuit from a given layer. + fn compute_next_layer(prev_layer: &CircuitLayer) -> CircuitLayer { + let next_layer_wires = prev_layer + .wires() + .chunks_exact(2) + .map(|input_wires| { + let left_input_wire = input_wires[0]; + let right_input_wire = input_wires[1]; + + // output wire + left_input_wire + right_input_wire + }) + .collect(); + + CircuitLayer::new(next_layer_wires) + } +} + +// CIRCUIT LAYER POLYS +// =============================================================================================== + +/// Holds a layer of an [`EvaluatedCircuit`] in a representation amenable to proving circuit +/// evaluation using GKR. +#[derive(Clone, Debug)] +pub struct CircuitLayerPolys { + pub numerators: MultiLinearPoly, + pub denominators: MultiLinearPoly, +} + +impl CircuitLayerPolys +where + E: FieldElement, +{ + pub fn from_circuit_layer(layer: CircuitLayer) -> Self { + Self::from_wires(layer.wires) + } + + pub fn from_wires(wires: Vec>) -> Self { + let mut numerators = Vec::new(); + let mut denominators = Vec::new(); + + for wire in wires { + numerators.push(wire.numerator); + denominators.push(wire.denominator); + } + + Self { + numerators: MultiLinearPoly::from_evaluations(numerators), + denominators: MultiLinearPoly::from_evaluations(denominators), + } + } + + fn into_numerators_denominators(self) -> (MultiLinearPoly, MultiLinearPoly) { + (self.numerators, self.denominators) + } +} + +impl Serializable for CircuitLayerPolys +where + E: FieldElement, +{ + fn write_into(&self, target: &mut W) { + let Self { numerators, denominators } = self; + numerators.write_into(target); + denominators.write_into(target); + } +} + +impl Deserializable for CircuitLayerPolys +where + E: FieldElement, +{ + fn read_from(source: &mut R) -> Result { + Ok(Self { + numerators: MultiLinearPoly::read_from(source)?, + denominators: MultiLinearPoly::read_from(source)?, + }) + } +} + +// CIRCUIT LAYER +// =============================================================================================== + +/// Represents a layer in a [`EvaluatedCircuit`]. +/// +/// A layer is made up of a set of `n` wires, where `n` is a power of two. This is the natural +/// circuit representation of a layer, where each consecutive pair of wires are summed to yield a +/// wire in the subsequent layer of an [`EvaluatedCircuit`]. +/// +/// Note that a [`Layer`] needs to be first converted to a [`LayerPolys`] before the evaluation of +/// the layer can be proved using GKR. +pub struct CircuitLayer { + wires: Vec>, +} + +impl CircuitLayer { + /// Creates a new [`Layer`] from a set of projective coordinates. + /// + /// Panics if the number of projective coordinates is not a power of two. + pub fn new(wires: Vec>) -> Self { + assert!(wires.len().is_power_of_two()); + + Self { wires } + } + + /// Returns the wires that make up this circuit layer. + pub fn wires(&self) -> &[CircuitWire] { + &self.wires + } + + /// Returns the number of wires in the layer. + pub fn num_wires(&self) -> usize { + self.wires.len() + } +} + +// CIRCUIT WIRE +// =============================================================================================== + +/// Represents a fraction `numerator / denominator` as a pair `(numerator, denominator)`. This is +/// the type for the gates' inputs in [`prover::EvaluatedCircuit`]. +/// +/// Hence, addition is defined in the natural way fractions are added together: `a/b + c/d = (ad + +/// bc) / bd`. +#[derive(Debug, Clone, Copy)] +pub struct CircuitWire { + numerator: E, + denominator: E, +} + +impl CircuitWire +where + E: FieldElement, +{ + /// Creates new projective coordinates from a numerator and a denominator. + pub fn new(numerator: E, denominator: E) -> Self { + assert_ne!(denominator, E::ZERO); + + Self { numerator, denominator } + } +} + +impl Add for CircuitWire +where + E: FieldElement, +{ + type Output = Self; + + fn add(self, other: Self) -> Self { + let numerator = self.numerator * other.denominator + other.numerator * self.denominator; + let denominator = self.denominator * other.denominator; + + Self::new(numerator, denominator) + } +} + +/// Represents a claim to be proven by a subsequent call to the sum-check protocol. +#[derive(Debug)] +pub struct GkrClaim { + pub evaluation_point: Vec, + pub claimed_evaluation: (E, E), +} + +/// We receive our 4 multilinear polynomials which were evaluated at a random point: +/// `left_numerators` (or `p0`), `right_numerators` (or `p1`), `left_denominators` (or `q0`), and +/// `right_denominators` (or `q1`). We'll call the 4 evaluations at a random point `p0(r)`, `p1(r)`, +/// `q0(r)`, and `q1(r)`, respectively, where `r` is the random point. Note that `r` is a shorthand +/// for a tuple of random values `(r_0, ... r_{l-1})`, where `2^{l + 1}` is the number of wires in +/// the layer. +/// +/// It is important to recall how `p0` and `p1` were constructed (and analogously for `q0` and +/// `q1`). They are the `numerators` layer polynomial (or `p`) evaluations `p(0, r)` and `p(1, r)`, +/// obtained from [`MultiLinearPoly::project_least_significant_variable`]. Hence, `[p0, p1]` form +/// the evaluations of polynomial `p'(x_0) = p(x_0, r)`. Then, the round claim for `numerators`, +/// defined as `p(r_layer, r)`, is simply `p'(r_layer)`. +fn reduce_layer_claim( + left_numerators_opening: E, + right_numerators_opening: E, + left_denominators_opening: E, + right_denominators_opening: E, + r_layer: E, +) -> (E, E) +where + E: FieldElement, +{ + // This is the `numerators` layer polynomial `f(x_0) = numerators(x_0, rx_0, ..., rx_{l-1})`, + // where `rx_0, ..., rx_{l-1}` are the random variables that were sampled during the sumcheck + // round for this layer. + let numerators_univariate = + MultiLinearPoly::from_evaluations(vec![left_numerators_opening, right_numerators_opening]); + + // This is analogous to `numerators_univariate`, but for the `denominators` layer polynomial + let denominators_univariate = MultiLinearPoly::from_evaluations(vec![ + left_denominators_opening, + right_denominators_opening, + ]); + + ( + numerators_univariate.evaluate(&[r_layer]), + denominators_univariate.evaluate(&[r_layer]), + ) +} + +/// Builds the auxiliary trace column for the univariate sum-check argument. +/// +/// Following Section 5.2 in [1] and using the inner product representation of multi-linear queries, +/// we need two univariate oracles, or equivalently two columns in the auxiliary trace, namely: +/// +/// 1. The Lagrange oracle, denoted by $c(X)$ in [1], and refered to throughout the codebase by +/// the Lagrange kernel column. +/// 2. The oracle witnessing the univariate sum-check relation defined by the aforementioned inner +/// product i.e., equation (12) in [1]. This oracle is refered to throughout the codebase as +/// the s-column. +/// +/// The following function's purpose is two build the column in point 2 given the one in point 1. +/// +/// [1]: https://eprint.iacr.org/2023/1284 +pub fn build_s_column( + main_trace: &impl Trace, + gkr_data: &GkrData, + evaluator: &impl LogUpGkrEvaluator, + lagrange_kernel_col: &[E], +) -> Vec { + let c = gkr_data.compute_batched_claim(); + let main_segment = main_trace.main_segment(); + let mean = c / E::from(E::BaseField::from(main_segment.num_rows() as u32)); + + let mut result = Vec::with_capacity(main_segment.num_rows()); + let mut last_value = E::ZERO; + result.push(last_value); + + let mut query = vec![E::BaseField::ZERO; evaluator.get_oracles().len()]; + let mut main_frame = EvaluationFrame::new(main_trace.main_segment().num_cols()); + + for (i, item) in lagrange_kernel_col.iter().enumerate().take(main_segment.num_rows() - 1) { + main_trace.read_main_frame(i, &mut main_frame); + + evaluator.build_query(&main_frame, &[], &mut query); + let cur_value = last_value - mean + gkr_data.compute_batched_query(&query) * *item; + + result.push(cur_value); + last_value = cur_value; + } + + result +} + +/// Builds the Lagrange kernel column at a given point. +pub fn build_lagrange_column(lagrange_randomness: &[E]) -> Vec { + EqFunction::new(lagrange_randomness.into()).evaluations() +} + +#[derive(Debug, thiserror::Error)] +pub enum GkrProverError { + #[error("failed to generate the sum-check proof")] + FailedToProveSumCheck(#[from] SumCheckProverError), + #[error("failed to generate the random challenge")] + FailedToGenerateChallenge, +} diff --git a/prover/src/logup_gkr/prover.rs b/prover/src/logup_gkr/prover.rs new file mode 100644 index 000000000..9fc8fe175 --- /dev/null +++ b/prover/src/logup_gkr/prover.rs @@ -0,0 +1,256 @@ +use alloc::vec::Vec; + +use air::{LogUpGkrEvaluator, LogUpGkrOracle}; +use crypto::{ElementHasher, RandomCoin}; +use math::FieldElement; +use sumcheck::{ + sum_check_prove_higher_degree, sumcheck_prove_plain, BeforeFinalLayerProof, CircuitOutput, + EqFunction, FinalLayerProof, GkrCircuitProof, MultiLinearPoly, SumCheckProof, +}; + +use super::{reduce_layer_claim, CircuitLayerPolys, EvaluatedCircuit, GkrClaim, GkrProverError}; +use crate::{matrix::ColMatrix, Trace}; + +// PROVER +// ================================================================================================ + +/// Evaluates and proves a fractional sum circuit given a set of composition polynomials. +/// +/// For the input layer of the circuit, each individual component of the quadruple +/// [p_0, p_1, q_0, q_1] is of the form: +/// +/// m(z_0, ... , z_{μ - 1}, x_0, ... , x_{ν - 1}) = \sum_{y ∈ {0,1}^μ} EQ(z, y) * g_{[y]}(f_0(x_0, +/// ... , x_{ν - 1}), ... , f_{κ - 1}(x_0, ... , x_{ν +/// - 1})) +/// +/// where: +/// +/// 1. μ is the log_2 of the number of different numerator/denominator expressions divided by two. +/// 2. [y] := \sum_{j = 0}^{μ - 1} y_j * 2^j +/// 3. κ is the number of multi-linears (i.e., main trace columns) involved in the computation of +/// the circuit (i.e., virtual bus). +/// 4. ν is the log_2 of the trace length. +/// +/// The above `m` is usually referred to as the merge of the individual composed multi-linear +/// polynomials g_{[y]}(f_0(x_0, ... , x_{ν - 1}), ... , f_{κ - 1}(x_0, ... , x_{ν - 1})). +/// +/// The composition polynomials `g` are provided as inputs and then used in order to compute the +/// evaluations of each of the four merge polynomials over {0, 1}^{μ + ν}. The resulting evaluations +/// are then used in order to evaluate the circuit. At this point, the GKR protocol is used to prove +/// the correctness of circuit evaluation. It should be noted that the input layer, which +/// corresponds to the last layer treated by the GKR protocol, is handled differently from the other +/// layers. More specifically, the sum-check protocol used for the input layer is composed of two +/// sum-check protocols, the first one works directly with the evaluations of the `m`'s over {0, +/// 1}^{μ + ν} and runs for μ - 1 rounds. After these μ - 1 rounds, and using the resulting [`RoundClaim`], +/// we run the second and final sum-check protocol for ν rounds on the composed multi-linear +/// polynomial given by +/// +/// \sum_{y ∈ {0,1}^μ} EQ(ρ', y) * g_{[y]}(f_0(x_0, ... , x_{ν - 1}), ... , f_{κ - 1}(x_0, ... , +/// x_{ν - 1})) +/// +/// where ρ' is the randomness sampled during the first sum-check protocol. +/// +/// As part of the final sum-check protocol, the openings {f_j(ρ)} are provided as part of a +/// [`FinalOpeningClaim`]. This latter claim will be proven by the STARK prover later on using the +/// auxiliary trace. +pub fn prove_gkr( + main_trace: &impl Trace, + evaluator: &impl LogUpGkrEvaluator, + public_coin: &mut impl RandomCoin, +) -> Result, GkrProverError> { + 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")); + } + + // evaluate the GKR fractional sum circuit + let circuit = EvaluatedCircuit::new(main_trace, evaluator, &logup_randomness)?; + + // include the circuit output as part of the final proof + let CircuitLayerPolys { numerators, denominators } = circuit.output_layer().clone(); + + // run the GKR prover for all layers except the input layer + 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 = + 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)?; + + Ok(GkrCircuitProof { + circuit_outputs: CircuitOutput { numerators, denominators }, + before_final_layer_proofs, + final_layer_proof, + }) +} + +/// Proves the final GKR layer which corresponds to the input circuit layer. +fn prove_input_layer< + E: FieldElement, + C: RandomCoin, + H: ElementHasher, +>( + evaluator: &impl LogUpGkrEvaluator, + log_up_randomness: Vec, + multi_linear_ext_polys: Vec>, + claim: GkrClaim, + transcript: &mut C, +) -> Result, GkrProverError> { + // parse the [GkrClaim] resulting from the previous GKR layer + let GkrClaim { evaluation_point, claimed_evaluation } = claim; + + transcript.reseed(H::hash_elements(&[claimed_evaluation.0, claimed_evaluation.1])); + let r_batch = transcript.draw().map_err(|_| GkrProverError::FailedToGenerateChallenge)?; + let claim = claimed_evaluation.0 + claimed_evaluation.1 * r_batch; + + let proof = sum_check_prove_higher_degree( + evaluator, + evaluation_point, + claim, + r_batch, + log_up_randomness, + multi_linear_ext_polys, + transcript, + )?; + + Ok(FinalLayerProof::new(proof)) +} + +/// Builds the multi-linear extension polynomials needed to run the final sum-check of GKR for +/// LogUp-GKR. +fn build_mls_from_main_trace_segment( + oracles: &[LogUpGkrOracle], + main_trace: &ColMatrix<::BaseField>, +) -> Result>, GkrProverError> { + let mut mls = vec![]; + + for oracle in oracles { + match oracle { + LogUpGkrOracle::CurrentRow(index) => { + let col = main_trace.get_column(*index); + let values: Vec = col.iter().map(|value| E::from(*value)).collect(); + let ml = MultiLinearPoly::from_evaluations(values); + mls.push(ml) + }, + LogUpGkrOracle::NextRow(index) => { + let col = main_trace.get_column(*index); + let mut values: Vec = col.iter().map(|value| E::from(*value)).collect(); + if let Some(value) = values.last_mut() { + *value = E::ZERO + } + values.rotate_left(1); + let ml = MultiLinearPoly::from_evaluations(values); + mls.push(ml) + }, + LogUpGkrOracle::PeriodicValue(_) => unimplemented!(), + }; + } + Ok(mls) +} + +/// Proves all GKR layers except for input layer. +fn prove_intermediate_layers< + E: FieldElement, + C: RandomCoin, + H: ElementHasher, +>( + circuit: EvaluatedCircuit, + transcript: &mut C, +) -> Result<(BeforeFinalLayerProof, GkrClaim), GkrProverError> { + // absorb the circuit output layer. This corresponds to sending the four values of the output + // layer to the verifier. The verifier then replies with a challenge `r` in order to evaluate + // `p` and `q` at `r` as multi-linears. + let CircuitLayerPolys { numerators, denominators } = circuit.output_layer(); + let mut evaluations = numerators.evaluations().to_vec(); + evaluations.extend_from_slice(denominators.evaluations()); + transcript.reseed(H::hash_elements(&evaluations)); + + // generate the challenge and reduce [p0, p1, q0, q1] to [pr, qr] + let r = transcript.draw().map_err(|_| GkrProverError::FailedToGenerateChallenge)?; + let mut claimed_evaluation = circuit.evaluate_output_layer(r); + + let mut layer_proofs: Vec> = Vec::new(); + let mut evaluation_point = vec![r]; + + // Loop over all inner layers, from output to input. + // + // In a layered circuit, each layer is defined in terms of its predecessor. The first inner + // layer (starting from the output layer) is the first layer that has a predecessor. Here, we + // loop over all inner layers in order to iteratively reduce a layer in terms of its successor + // layer. Note that we don't include the input layer, since its predecessor layer will be + // reduced in terms of the input layer separately in `prove_final_circuit_layer`. + for inner_layer in circuit.layers().into_iter().skip(1).rev().skip(1) { + // construct the Lagrange kernel evaluated at the previous GKR round randomness + let mut eq_mle = EqFunction::ml_at(evaluation_point.into()); + + let (numerators, denominators) = inner_layer.into_numerators_denominators(); + + // run the sumcheck protocol + let proof = sum_check_prove_num_rounds_degree_3( + claimed_evaluation, + numerators, + denominators, + &mut eq_mle, + transcript, + )?; + + // sample a random challenge to reduce claims + transcript.reseed(H::hash_elements(&proof.openings_claim.openings)); + let r_layer = transcript.draw().map_err(|_| GkrProverError::FailedToGenerateChallenge)?; + + // reduce the claim + claimed_evaluation = { + let left_numerators_opening = proof.openings_claim.openings[0]; + let right_numerators_opening = proof.openings_claim.openings[1]; + let left_denominators_opening = proof.openings_claim.openings[2]; + let right_denominators_opening = proof.openings_claim.openings[3]; + + reduce_layer_claim( + left_numerators_opening, + right_numerators_opening, + left_denominators_opening, + right_denominators_opening, + r_layer, + ) + }; + + // collect the randomness used for the current layer + let mut ext = vec![r_layer]; + ext.extend_from_slice(&proof.openings_claim.eval_point); + evaluation_point = ext; + + layer_proofs.push(proof); + } + + Ok(( + BeforeFinalLayerProof { proof: layer_proofs }, + GkrClaim { evaluation_point, claimed_evaluation }, + )) +} + +/// Runs the sum-check prover used in all but the input layer. +#[allow(clippy::too_many_arguments)] +fn sum_check_prove_num_rounds_degree_3< + E: FieldElement, + C: RandomCoin, + H: ElementHasher, +>( + claim: (E, E), + p: MultiLinearPoly, + q: MultiLinearPoly, + eq: &mut MultiLinearPoly, + transcript: &mut C, +) -> Result, GkrProverError> { + // generate challenge to batch two sumchecks + transcript.reseed(H::hash_elements(&[claim.0, claim.1])); + let r_batch = transcript.draw().map_err(|_| GkrProverError::FailedToGenerateChallenge)?; + let claim = claim.0 + claim.1 * r_batch; + + let proof = sumcheck_prove_plain(claim, r_batch, p, q, eq, transcript)?; + + Ok(proof) +} diff --git a/prover/src/tests/mod.rs b/prover/src/tests/mod.rs index 6b44fa0e9..5132e2025 100644 --- a/prover/src/tests/mod.rs +++ b/prover/src/tests/mod.rs @@ -9,7 +9,7 @@ use air::{ Air, AirContext, Assertion, EvaluationFrame, FieldExtension, ProofOptions, TraceInfo, TransitionConstraintDegree, }; -use math::{fields::f128::BaseElement, FieldElement, StarkField}; +use math::{fields::f64::BaseElement, FieldElement, StarkField}; use crate::TraceTable; @@ -34,7 +34,7 @@ pub fn build_fib_trace(length: usize) -> TraceTable { // ================================================================================================ pub struct MockAir { - context: AirContext, + context: AirContext, assertions: Vec>, periodic_columns: Vec>, } @@ -75,8 +75,6 @@ impl MockAir { impl Air for MockAir { type BaseField = BaseElement; type PublicInputs = (); - type GkrProof = (); - type GkrVerifier = (); fn new(trace_info: TraceInfo, _pub_inputs: (), _options: ProofOptions) -> Self { let context = build_context(trace_info, 8, 1); @@ -87,7 +85,7 @@ impl Air for MockAir { } } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } @@ -115,8 +113,8 @@ fn build_context( trace_info: TraceInfo, blowup_factor: usize, num_assertions: usize, -) -> AirContext { +) -> AirContext { let options = ProofOptions::new(32, blowup_factor, 0, FieldExtension::None, 4, 31); let t_degrees = vec![TransitionConstraintDegree::new(2)]; - AirContext::new(trace_info, t_degrees, num_assertions, options) + AirContext::new(trace_info, (), t_degrees, num_assertions, options) } diff --git a/prover/src/trace/mod.rs b/prover/src/trace/mod.rs index 26b383a3b..8d7c999bf 100644 --- a/prover/src/trace/mod.rs +++ b/prover/src/trace/mod.rs @@ -5,6 +5,7 @@ use air::{Air, AuxRandElements, EvaluationFrame, LagrangeKernelBoundaryConstraint, TraceInfo}; use math::{polynom, FieldElement, StarkField}; +use sumcheck::GkrCircuitProof; use super::ColMatrix; @@ -22,7 +23,7 @@ mod tests; /// Defines an [`AuxTraceWithMetadata`] type where the type arguments use their equivalents in an /// [`Air`]. -type AirAuxTraceWithMetadata = AuxTraceWithMetadata::GkrProof>; +type AirAuxTraceWithMetadata = AuxTraceWithMetadata; // AUX TRACE WITH METADATA // ================================================================================================ @@ -30,10 +31,10 @@ type AirAuxTraceWithMetadata = AuxTraceWithMetadata::GkrProo /// Holds the auxiliary trace, the random elements used when generating the auxiliary trace, and /// optionally, a GKR proof. See [`crate::Proof`] for more information about the auxiliary /// proof. -pub struct AuxTraceWithMetadata { +pub struct AuxTraceWithMetadata { pub aux_trace: ColMatrix, pub aux_rand_elements: AuxRandElements, - pub gkr_proof: Option, + pub gkr_proof: Option>, } // TRACE TRAIT @@ -79,7 +80,7 @@ pub trait Trace: Sized { /// Returns the number of columns in the main segment of this trace. fn main_trace_width(&self) -> usize { - self.info().main_trace_width() + self.info().main_segment_width() } /// Returns the number of columns in the auxiliary trace segment. @@ -90,21 +91,18 @@ pub trait Trace: Sized { /// Checks if this trace is valid against the specified AIR, and panics if not. /// /// NOTE: this is a very expensive operation and is intended for use only in debug mode. - fn validate( - &self, - air: &A, - aux_trace_with_metadata: Option<&AirAuxTraceWithMetadata>, - ) where + fn validate(&self, air: &A, aux_trace_with_metadata: Option<&AirAuxTraceWithMetadata>) + where A: Air, E: FieldElement, { // make sure the width align; if they don't something went terribly wrong assert_eq!( self.main_trace_width(), - air.trace_info().main_trace_width(), + air.trace_info().main_segment_width(), "inconsistent trace width: expected {}, but was {}", self.main_trace_width(), - air.trace_info().main_trace_width(), + air.trace_info().main_segment_width(), ); // --- 1. make sure the assertions are valid ---------------------------------------------- diff --git a/prover/src/trace/tests.rs b/prover/src/trace/tests.rs index fc653bbde..b08771a3e 100644 --- a/prover/src/trace/tests.rs +++ b/prover/src/trace/tests.rs @@ -5,7 +5,7 @@ use alloc::vec::Vec; -use math::fields::f128::BaseElement; +use math::fields::f64::BaseElement; use crate::{tests::build_fib_trace, Trace}; diff --git a/prover/src/trace/trace_lde/default/tests.rs b/prover/src/trace/trace_lde/default/tests.rs index c06cc2e60..e1b9b6299 100644 --- a/prover/src/trace/trace_lde/default/tests.rs +++ b/prover/src/trace/trace_lde/default/tests.rs @@ -7,7 +7,7 @@ use alloc::vec::Vec; use crypto::{hashers::Blake3_256, ElementHasher, MerkleTree}; use math::{ - fields::f128::BaseElement, get_power_series, get_power_series_with_offset, polynom, + fields::f64::BaseElement, get_power_series, get_power_series_with_offset, polynom, FieldElement, StarkField, }; diff --git a/prover/src/trace/trace_table.rs b/prover/src/trace/trace_table.rs index dfbd6fe72..0d26c73a0 100644 --- a/prover/src/trace/trace_table.rs +++ b/prover/src/trace/trace_table.rs @@ -166,7 +166,7 @@ impl TraceTable { I: FnOnce(&mut [B]), U: FnMut(usize, &mut [B]), { - let mut state = vec![B::ZERO; self.info.main_trace_width()]; + let mut state = vec![B::ZERO; self.info.main_segment_width()]; init(&mut state); self.update_row(0, &state); @@ -255,7 +255,7 @@ impl TraceTable { /// Returns the number of columns in this execution trace. pub fn width(&self) -> usize { - self.info.main_trace_width() + self.info.main_segment_width() } /// Returns the entire trace column at the specified index. diff --git a/sumcheck/benches/sum_check_high_degree.rs b/sumcheck/benches/sum_check_high_degree.rs index 3db6a37e3..f32329c80 100644 --- a/sumcheck/benches/sum_check_high_degree.rs +++ b/sumcheck/benches/sum_check_high_degree.rs @@ -8,7 +8,7 @@ use std::{marker::PhantomData, time::Duration}; use air::{EvaluationFrame, LogUpGkrEvaluator, LogUpGkrOracle}; use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; use crypto::{hashers::Blake3_192, DefaultRandomCoin, RandomCoin}; -use math::{fields::f64::BaseElement, ExtensionOf, FieldElement}; +use math::{fields::f64::BaseElement, ExtensionOf, FieldElement, StarkField}; use rand_utils::{rand_value, rand_vector}; #[cfg(feature = "concurrent")] pub use rayon::prelude::*; @@ -94,22 +94,30 @@ fn setup_sum_check( } #[derive(Clone, Default)] -pub struct PlainLogUpGkrEval { +pub struct PlainLogUpGkrEval { + oracles: Vec>, _field: PhantomData, } -impl LogUpGkrEvaluator for PlainLogUpGkrEval { - type BaseField = BaseElement; - - type PublicInputs = (); - - fn get_oracles(&self) -> Vec> { +impl PlainLogUpGkrEval { + pub fn new() -> Self { let committed_0 = LogUpGkrOracle::CurrentRow(0); let committed_1 = LogUpGkrOracle::CurrentRow(1); let committed_2 = LogUpGkrOracle::CurrentRow(2); let committed_3 = LogUpGkrOracle::CurrentRow(3); let committed_4 = LogUpGkrOracle::CurrentRow(4); - vec![committed_0, committed_1, committed_2, committed_3, committed_4] + let oracles = vec![committed_0, committed_1, committed_2, committed_3, committed_4]; + Self { oracles, _field: PhantomData } + } +} + +impl LogUpGkrEvaluator for PlainLogUpGkrEval { + type BaseField = BaseElement; + + type PublicInputs = (); + + fn get_oracles(&self) -> &[LogUpGkrOracle] { + &self.oracles } fn get_num_rand_values(&self) -> usize { diff --git a/sumcheck/src/lib.rs b/sumcheck/src/lib.rs index f30db974c..5beac9bb9 100644 --- a/sumcheck/src/lib.rs +++ b/sumcheck/src/lib.rs @@ -124,8 +124,12 @@ where /// A proof for the input circuit layer i.e., the final layer in the GKR protocol. #[derive(Debug, Clone)] -pub struct FinalLayerProof { - pub proof: SumCheckProof, +pub struct FinalLayerProof(SumCheckProof); + +impl FinalLayerProof { + pub fn new(proof: SumCheckProof) -> Self { + Self(proof) + } } impl Serializable for FinalLayerProof @@ -133,8 +137,7 @@ where E: FieldElement, { fn write_into(&self, target: &mut W) { - let Self { proof } = self; - proof.write_into(target); + self.0.write_into(target); } } @@ -143,9 +146,7 @@ where E: FieldElement, { fn read_from(source: &mut R) -> Result { - Ok(Self { - proof: Deserializable::read_from(source)?, - }) + Ok(Self(Deserializable::read_from(source)?)) } } @@ -170,7 +171,7 @@ pub struct GkrCircuitProof { impl GkrCircuitProof { pub fn get_final_opening_claim(&self) -> FinalOpeningClaim { - self.final_layer_proof.proof.openings_claim.clone() + self.final_layer_proof.0.openings_claim.clone() } } @@ -181,7 +182,7 @@ where fn write_into(&self, target: &mut W) { self.circuit_outputs.write_into(target); self.before_final_layer_proofs.write_into(target); - self.final_layer_proof.proof.write_into(target); + self.final_layer_proof.0.write_into(target); } } diff --git a/sumcheck/src/prover/high_degree.rs b/sumcheck/src/prover/high_degree.rs index a96adee4c..691195925 100644 --- a/sumcheck/src/prover/high_degree.rs +++ b/sumcheck/src/prover/high_degree.rs @@ -53,7 +53,7 @@ use crate::{ /// 2. ${[w]} := \sum_i w_i \cdot 2^i$ and $w := (w_1, \cdots, w_{\mu})$. /// 3. $h_{j}$ and $g_{j}$ are multi-variate polynomials for $j = 0, \cdots, 2^{\mu} - 1$. /// 4. $n := \nu + \mu$ -/// 5. $\mathbb{B}_{\gamma} := \{0, 1\}^{\gamma}$ for positive integer $\gamma$. +/// 5. $\\B_{\gamma} := \{0, 1\}^{\gamma}$ for positive integer $\gamma$. /// /// The sum above is evaluated using a layered circuit with the equation linking the input layer /// values $p_n$ to the next layer values $p_{n-1}$ given by the following relations @@ -111,14 +111,14 @@ use crate::{ /// /// $$ /// p_{n-1}\left(v_2, \cdots, v_{\mu}, x_1, \cdots, x_{\nu}\right) = -/// \sum_{y\in\mathbb{B}_{\nu}} G(y_{1}, ..., y_{\nu}) +/// \sum_{y\in\\B_{\nu}} G(y_{1}, ..., y_{\nu}) /// $$ /// /// and /// /// $$ /// q_{n-1}\left(v_2, \cdots, v_{\mu}, x_1, \cdots, x_{\nu}\right) = -/// \sum_{y\in\mathbb{B}_{\nu}} H\left(y_1, \cdots, y_{\nu} \right) +/// \sum_{y\in\\B_{\nu}} H\left(y_1, \cdots, y_{\nu} \right) /// $$ /// /// where diff --git a/sumcheck/src/verifier/mod.rs b/sumcheck/src/verifier/mod.rs index d1cfae3a4..887598cc8 100644 --- a/sumcheck/src/verifier/mod.rs +++ b/sumcheck/src/verifier/mod.rs @@ -53,6 +53,8 @@ pub fn verify_sum_check_intermediate_layers< Ok(openings_claim.clone()) } +/// Sum-check verifier for the input layer. +/// /// 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. @@ -64,8 +66,6 @@ pub fn verify_sum_check_input_layer, ) -> 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 @@ -77,17 +77,17 @@ pub fn verify_sum_check_input_layer Option<&Vec> { - self.gkr_proof.as_ref() + /// Returns the GKR proof, if any. + pub fn read_gkr_proof(&self) -> Result, VerifierError> { + GkrCircuitProof::read_from_bytes( + self.gkr_proof.as_ref().expect("Expected a GKR proof but there was none"), + ) + .map_err(|err| VerifierError::ProofDeserializationError(err.to_string())) } /// Returns trace states at the specified positions of the LDE domain. This also checks if @@ -313,7 +318,7 @@ where ); // parse main trace segment queries - let main_segment_width = air.trace_info().main_trace_width(); + let main_segment_width = air.trace_info().main_segment_width(); let main_segment_queries = queries.remove(0); let (main_segment_query_proofs, main_segment_states) = main_segment_queries .parse::(air.lde_domain_size(), num_queries, main_segment_width) @@ -331,7 +336,7 @@ where let aux_trace_states = if air.trace_info().is_multi_segment() { let mut aux_trace_states = Vec::new(); let segment_queries = queries.remove(0); - let segment_width = air.trace_info().get_aux_segment_width(); + let segment_width = air.trace_info().aux_segment_width(); let (segment_query_proof, segment_trace_states) = segment_queries .parse::(air.lde_domain_size(), num_queries, segment_width) .map_err(|err| { diff --git a/verifier/src/evaluator.rs b/verifier/src/evaluator.rs index 10910a555..b26f7b926 100644 --- a/verifier/src/evaluator.rs +++ b/verifier/src/evaluator.rs @@ -92,25 +92,28 @@ pub fn evaluate_constraints>( let lagrange_coefficients = composition_coefficients .lagrange .expect("expected Lagrange kernel composition coefficients to be present"); - let lagrange_kernel_aux_rand_elements = { - let aux_rand_elements = - aux_rand_elements.expect("expected aux rand elements to be present"); - - aux_rand_elements - .lagrange() - .expect("expected lagrange rand elements to be present") - }; + let air::GkrData { + lagrange_kernel_eval_point: lagrange_kernel_evaluation_point, + openings_combining_randomness: _, + openings: _, + oracles: _, + } = aux_rand_elements + .expect("expected aux rand elements to be present") + .gkr_data() + .expect("expected LogUp-GKR rand elements to be present"); + + // Lagrange kernel constraints let lagrange_constraints = air .get_lagrange_kernel_constraints( lagrange_coefficients, - lagrange_kernel_aux_rand_elements, + &lagrange_kernel_evaluation_point, ) .expect("expected Lagrange kernel constraints to be present"); result += lagrange_constraints.transition.evaluate_and_combine::( lagrange_kernel_column_frame, - lagrange_kernel_aux_rand_elements, + &lagrange_kernel_evaluation_point, x, ); diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs index 2c75ecd1d..cabbda6c7 100644 --- a/verifier/src/lib.rs +++ b/verifier/src/lib.rs @@ -38,7 +38,7 @@ pub use air::{ ConstraintCompositionCoefficients, ConstraintDivisor, DeepCompositionCoefficients, EvaluationFrame, FieldExtension, ProofOptions, TraceInfo, TransitionConstraintDegree, }; -use air::{AuxRandElements, GkrVerifier}; +use air::{AuxRandElements, LogUpGkrEvaluator}; pub use crypto; use crypto::{ElementHasher, Hasher, RandomCoin, VectorCommitment}; use fri::FriVerifier; @@ -47,6 +47,7 @@ use math::{ fields::{CubeExtension, QuadExtension}, FieldElement, ToElements, }; +use sumcheck::FinalOpeningClaim; pub use utils::{ ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SliceReader, }; @@ -60,6 +61,9 @@ use evaluator::evaluate_constraints; mod composer; use composer::DeepComposer; +mod logup_gkr; +use logup_gkr::verify_gkr; + mod errors; pub use errors::VerifierError; @@ -175,35 +179,40 @@ where // process auxiliary trace segments (if any), to build a set of random elements for each segment let aux_trace_rand_elements = if air.trace_info().is_multi_segment() { - if air.context().has_lagrange_kernel_aux_column() { - let gkr_proof = { - let gkr_proof_serialized = channel - .read_gkr_proof() - .expect("Expected an a GKR proof because trace has lagrange kernel column"); - - Deserializable::read_from_bytes(gkr_proof_serialized) - .map_err(|err| VerifierError::ProofDeserializationError(err.to_string()))? - }; - let gkr_rand_elements = air - .get_gkr_proof_verifier::() - .verify::(gkr_proof, &mut public_coin) - .map_err(|err| VerifierError::GkrProofVerificationFailed(err.to_string()))?; - - let rand_elements = air.get_aux_rand_elements(&mut public_coin).expect( - "failed to generate the random elements needed to build the auxiliary trace", - ); + // build the set of random elements related to the auxiliary segment without the LogUp-GKR + // related ones. + let trace_rand_elements = air + .get_aux_rand_elements(&mut public_coin) + .expect("failed to generate the random elements needed to build the auxiliary trace"); + + // if LogUp-GKR is enabled, verify the attached proof and build an object which includes + // randomness and data related to LogUp-GKR + if air.context().logup_gkr_enabled() { + let gkr_proof = channel.read_gkr_proof()?; + let logup_gkr_evaluator = air.get_logup_gkr_evaluator(); + + let FinalOpeningClaim { eval_point, openings } = verify_gkr::( + air.context().public_inputs(), + &gkr_proof, + &logup_gkr_evaluator, + &mut public_coin, + ) + .map_err(|err| VerifierError::GkrProofVerificationFailed(err.to_string()))?; + + let gkr_data = logup_gkr_evaluator + .generate_univariate_iop_for_multi_linear_opening_data( + openings, + eval_point, + &mut public_coin, + ); public_coin.reseed(trace_commitments[AUX_TRACE_IDX]); - Some(AuxRandElements::new_with_gkr(rand_elements, gkr_rand_elements)) + Some(AuxRandElements::new(trace_rand_elements, Some(gkr_data))) } else { - let rand_elements = air.get_aux_rand_elements(&mut public_coin).expect( - "failed to generate the random elements needed to build the auxiliary trace", - ); - public_coin.reseed(trace_commitments[AUX_TRACE_IDX]); - Some(AuxRandElements::new(rand_elements)) + Some(AuxRandElements::new(trace_rand_elements, None)) } } else { None diff --git a/verifier/src/logup_gkr/mod.rs b/verifier/src/logup_gkr/mod.rs new file mode 100644 index 000000000..e317e0ab1 --- /dev/null +++ b/verifier/src/logup_gkr/mod.rs @@ -0,0 +1,115 @@ +use alloc::vec::Vec; + +use air::{Air, LogUpGkrEvaluator}; +use crypto::{ElementHasher, RandomCoin}; +use math::FieldElement; +use sumcheck::{ + verify_sum_check_input_layer, verify_sum_check_intermediate_layers, CircuitOutput, + FinalOpeningClaim, GkrCircuitProof, SumCheckVerifierError, +}; + +/// Verifies the validity of a GKR proof for a LogUp-GKR relation. +pub fn verify_gkr< + A: Air, + E: FieldElement, + C: RandomCoin, + H: ElementHasher, +>( + pub_inputs: &A::PublicInputs, + proof: &GkrCircuitProof, + evaluator: &impl LogUpGkrEvaluator, + transcript: &mut C, +) -> Result, VerifierError> { + 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(transcript.draw().expect("failed to generate randomness")); + } + + let GkrCircuitProof { + circuit_outputs, + before_final_layer_proofs, + final_layer_proof, + } = proof; + + let CircuitOutput { numerators, denominators } = circuit_outputs; + let p0 = numerators.evaluations()[0]; + let p1 = numerators.evaluations()[1]; + let q0 = denominators.evaluations()[0]; + let q1 = denominators.evaluations()[1]; + + // make sure that both denominators are not equal to E::ZERO + if q0 == E::ZERO || q1 == E::ZERO { + return Err(VerifierError::ZeroOutputDenominator); + } + + // check that the output matches the expected `claim` + let claim = evaluator.compute_claim(pub_inputs, &logup_randomness); + if (p0 * q1 + p1 * q0) / (q0 * q1) != claim { + return Err(VerifierError::MismatchingCircuitOutput); + } + + // generate the random challenge to reduce two claims into a single claim + let mut evaluations = numerators.evaluations().to_vec(); + evaluations.extend_from_slice(denominators.evaluations()); + transcript.reseed(H::hash_elements(&evaluations)); + let r = transcript.draw().map_err(|_| VerifierError::FailedToGenerateChallenge)?; + + // reduce the claim + let p_r = p0 + r * (p1 - p0); + let q_r = q0 + r * (q1 - q0); + let mut reduced_claim = (p_r, q_r); + + // verify all GKR layers but for the last one + let num_layers = before_final_layer_proofs.proof.len(); + let mut evaluation_point = vec![r]; + for i in 0..num_layers { + let FinalOpeningClaim { eval_point, openings } = verify_sum_check_intermediate_layers( + &before_final_layer_proofs.proof[i], + &evaluation_point, + reduced_claim, + transcript, + )?; + + // generate the random challenge to reduce two claims into a single claim + transcript.reseed(H::hash_elements(&openings)); + let r_layer = transcript.draw().map_err(|_| VerifierError::FailedToGenerateChallenge)?; + + let p0 = openings[0]; + let p1 = openings[1]; + let q0 = openings[2]; + let q1 = openings[3]; + reduced_claim = (p0 + r_layer * (p1 - p0), q0 + r_layer * (q1 - q0)); + + // collect the randomness used for the current layer + let rand_sumcheck = eval_point; + let mut ext = vec![r_layer]; + ext.extend_from_slice(&rand_sumcheck); + evaluation_point = ext; + } + + // verify the proof of the final GKR layer and pass final opening claim for verification + // to the STARK + verify_sum_check_input_layer( + evaluator, + final_layer_proof, + logup_randomness, + &evaluation_point, + reduced_claim, + transcript, + ) + .map_err(VerifierError::FailedToVerifySumCheck) +} + +#[derive(Debug, thiserror::Error)] +pub enum VerifierError { + #[error("one of the claimed circuit denominators is zero")] + ZeroOutputDenominator, + #[error("the output of the fraction circuit is not equal to the expected value")] + MismatchingCircuitOutput, + #[error("failed to generate the random challenge")] + FailedToGenerateChallenge, + #[error("failed to verify the sum-check proof")] + FailedToVerifySumCheck(#[from] SumCheckVerifierError), +} diff --git a/winterfell/src/lib.rs b/winterfell/src/lib.rs index 86c5e0345..c3da2fb6a 100644 --- a/winterfell/src/lib.rs +++ b/winterfell/src/lib.rs @@ -150,12 +150,13 @@ //! ```no_run //! use winterfell::{ //! math::{fields::f128::BaseElement, FieldElement, ToElements}, -//! Air, AirContext, Assertion, GkrVerifier, EvaluationFrame, +//! Air, AirContext, Assertion, EvaluationFrame, //! ProofOptions, TraceInfo, TransitionConstraintDegree, //! crypto::{hashers::Blake3_256, DefaultRandomCoin, MerkleTree}, //! }; //! //! // Public inputs for our computation will consist of the starting value and the end result. +//! #[derive(Clone)] //! pub struct PublicInputs { //! start: BaseElement, //! result: BaseElement, @@ -172,7 +173,7 @@ //! // the computation's context which we'll build in the constructor. The context is used //! // internally by the Winterfell prover/verifier when interpreting this AIR. //! pub struct WorkAir { -//! context: AirContext, +//! context: AirContext, //! start: BaseElement, //! result: BaseElement, //! } @@ -182,8 +183,6 @@ //! // the public inputs must look like. //! type BaseField = BaseElement; //! type PublicInputs = PublicInputs; -//! type GkrProof = (); -//! type GkrVerifier = (); //! //! // Here, we'll construct a new instance of our computation which is defined by 3 //! // parameters: starting value, number of steps, and the end result. Another way to @@ -206,7 +205,7 @@ //! let num_assertions = 2; //! //! WorkAir { -//! context: AirContext::new(trace_info, degrees, num_assertions, options), +//! context: AirContext::new(trace_info, pub_inputs.clone(), degrees, num_assertions, options), //! start: pub_inputs.start, //! result: pub_inputs.result, //! } @@ -246,7 +245,7 @@ //! //! // This is just boilerplate which is used by the Winterfell prover/verifier to retrieve //! // the context of the computation. -//! fn context(&self) -> &AirContext { +//! fn context(&self) -> &AirContext { //! &self.context //! } //! } @@ -269,6 +268,7 @@ //! # EvaluationFrame, TraceInfo, TransitionConstraintDegree, //! # }; //! # +//! # #[derive(Clone)] //! # pub struct PublicInputs { //! # start: BaseElement, //! # result: BaseElement, @@ -281,7 +281,7 @@ //! # } //! # //! # pub struct WorkAir { -//! # context: AirContext, +//! # context: AirContext, //! # start: BaseElement, //! # result: BaseElement, //! # } @@ -289,14 +289,12 @@ //! # impl Air for WorkAir { //! # type BaseField = BaseElement; //! # type PublicInputs = PublicInputs; -//! # type GkrProof = (); -//! # type GkrVerifier = (); //! # //! # fn new(trace_info: TraceInfo, pub_inputs: PublicInputs, options: ProofOptions) -> Self { //! # assert_eq!(1, trace_info.width()); //! # let degrees = vec![TransitionConstraintDegree::new(3)]; //! # WorkAir { -//! # context: AirContext::new(trace_info, degrees, 2, options), +//! # context: AirContext::new(trace_info, pub_inputs.clone(), degrees, 2, options), //! # start: pub_inputs.start, //! # result: pub_inputs.result, //! # } @@ -321,7 +319,7 @@ //! # ] //! # } //! # -//! # fn context(&self) -> &AirContext { +//! # fn context(&self) -> &AirContext { //! # &self.context //! # } //! # } @@ -418,7 +416,7 @@ //! # trace //! # } //! # -//! # +//! # #[derive(Clone)] //! # pub struct PublicInputs { //! # start: BaseElement, //! # result: BaseElement, @@ -431,7 +429,7 @@ //! # } //! # //! # pub struct WorkAir { -//! # context: AirContext, +//! # context: AirContext, //! # start: BaseElement, //! # result: BaseElement, //! # } @@ -439,14 +437,12 @@ //! # impl Air for WorkAir { //! # type BaseField = BaseElement; //! # type PublicInputs = PublicInputs; -//! # type GkrProof = (); -//! # type GkrVerifier = (); //! # //! # fn new(trace_info: TraceInfo, pub_inputs: PublicInputs, options: ProofOptions) -> Self { //! # assert_eq!(1, trace_info.width()); //! # let degrees = vec![TransitionConstraintDegree::new(3)]; //! # WorkAir { -//! # context: AirContext::new(trace_info, degrees, 2, options), +//! # context: AirContext::new(trace_info, pub_inputs.clone(), degrees, 2, options), //! # start: pub_inputs.start, //! # result: pub_inputs.result, //! # } @@ -471,7 +467,7 @@ //! # ] //! # } //! # -//! # fn context(&self) -> &AirContext { +//! # fn context(&self) -> &AirContext { //! # &self.context //! # } //! # } @@ -594,15 +590,14 @@ #[cfg(test)] extern crate std; -pub use air::{AuxRandElements, GkrVerifier}; +pub use air::{AuxRandElements, LogUpGkrEvaluator}; pub use prover::{ crypto, iterators, math, matrix, Air, AirContext, Assertion, AuxTraceWithMetadata, BoundaryConstraint, BoundaryConstraintGroup, CompositionPolyTrace, ConstraintCompositionCoefficients, ConstraintDivisor, ConstraintEvaluator, DeepCompositionCoefficients, DefaultConstraintEvaluator, DefaultTraceLde, EvaluationFrame, - FieldExtension, Proof, ProofOptions, Prover, ProverError, ProverGkrProof, StarkDomain, Trace, - TraceInfo, TraceLde, TracePolyTable, TraceTable, TraceTableFragment, - TransitionConstraintDegree, + FieldExtension, Proof, ProofOptions, Prover, ProverError, StarkDomain, Trace, TraceInfo, + TraceLde, TracePolyTable, TraceTable, TraceTableFragment, TransitionConstraintDegree, }; pub use verifier::{verify, AcceptableOptions, ByteWriter, VerifierError}; diff --git a/winterfell/src/tests.rs b/winterfell/src/tests.rs index 3757e2010..858f35574 100644 --- a/winterfell/src/tests.rs +++ b/winterfell/src/tests.rs @@ -3,30 +3,33 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -use std::{vec, vec::Vec}; +use std::{marker::PhantomData, vec, vec::Vec}; -use air::{GkrRandElements, LagrangeKernelRandElements}; +use air::{ + Air, AirContext, Assertion, AuxRandElements, ConstraintCompositionCoefficients, FieldExtension, + LogUpGkrEvaluator, LogUpGkrOracle, ProofOptions, TraceInfo, +}; use crypto::MerkleTree; -use prover::{ - crypto::{hashers::Blake3_256, DefaultRandomCoin, RandomCoin}, +use math::StarkField; + +use super::*; +use crate::{ + crypto::{hashers::Blake3_256, DefaultRandomCoin}, math::{fields::f64::BaseElement, ExtensionOf, FieldElement}, matrix::ColMatrix, + DefaultConstraintEvaluator, DefaultTraceLde, Prover, StarkDomain, TracePolyTable, }; -use super::*; - -const AUX_TRACE_WIDTH: usize = 2; - #[test] -fn test_complex_lagrange_kernel_air() { - let trace = LagrangeComplexTrace::new(2_usize.pow(10), AUX_TRACE_WIDTH); - - let prover = LagrangeComplexProver::new(AUX_TRACE_WIDTH); +fn test_logup_gkr() { + let aux_trace_width = 2; + let trace = LogUpGkrSimple::new(2_usize.pow(7), aux_trace_width); + let prover = LogUpGkrSimpleProver::new(aux_trace_width); let proof = prover.prove(trace).unwrap(); verify::< - LagrangeKernelComplexAir, + LogUpGkrSimpleAir, Blake3_256, DefaultRandomCoin>, MerkleTree>, @@ -34,26 +37,48 @@ fn test_complex_lagrange_kernel_air() { .unwrap() } -// LagrangeComplexTrace +// LogUpGkrSimple // ================================================================================================= #[derive(Clone, Debug)] -struct LagrangeComplexTrace { +struct LogUpGkrSimple { // dummy main trace main_trace: ColMatrix, info: TraceInfo, } -impl LagrangeComplexTrace { +impl LogUpGkrSimple { fn new(trace_len: usize, aux_segment_width: usize) -> Self { assert!(trace_len < u32::MAX.try_into().unwrap()); - let main_trace_col: Vec = + let table: Vec = (0..trace_len).map(|idx| BaseElement::from(idx as u32)).collect(); + let mut multiplicity: Vec = + (0..trace_len).map(|_idx| BaseElement::ZERO).collect(); + multiplicity[0] = BaseElement::new(3 * trace_len as u64 - 3 * 4); + multiplicity[1] = BaseElement::new(3 * 4); + + let mut values_0: Vec = (0..trace_len).map(|_idx| BaseElement::ZERO).collect(); + + for i in 0..4 { + values_0[i + 4] = BaseElement::ONE; + } + + let mut values_1: Vec = (0..trace_len).map(|_idx| BaseElement::ZERO).collect(); + + for i in 0..4 { + values_1[i + 4] = BaseElement::ONE; + } + + let mut values_2: Vec = (0..trace_len).map(|_idx| BaseElement::ZERO).collect(); + + for i in 0..4 { + values_2[i + 4] = BaseElement::ONE; + } Self { - main_trace: ColMatrix::new(vec![main_trace_col]), - info: TraceInfo::new_multi_segment(1, aux_segment_width, 0, trace_len, vec![]), + main_trace: ColMatrix::new(vec![table, multiplicity, values_0, values_1, values_2]), + info: TraceInfo::new_multi_segment(5, aux_segment_width, 0, trace_len, vec![], true), } } @@ -62,7 +87,7 @@ impl LagrangeComplexTrace { } } -impl Trace for LagrangeComplexTrace { +impl Trace for LogUpGkrSimple { type BaseField = BaseElement; fn info(&self) -> &TraceInfo { @@ -75,76 +100,37 @@ impl Trace for LagrangeComplexTrace { fn read_main_frame(&self, row_idx: usize, frame: &mut EvaluationFrame) { let next_row_idx = row_idx + 1; - assert_ne!(next_row_idx, self.len()); - self.main_trace.read_row_into(row_idx, frame.current_mut()); - self.main_trace.read_row_into(next_row_idx, frame.next_mut()); + self.main_trace.read_row_into(next_row_idx % self.len(), frame.next_mut()); } } // AIR // ================================================================================================= -#[derive(Debug, Clone, Default)] -struct DummyGkrVerifier; - -impl GkrVerifier for DummyGkrVerifier { - // `GkrProof` is log(trace_len) for this dummy example, so that the verifier knows how many aux - // random variables to generate - type GkrProof = usize; - type Error = VerifierError; - - fn verify( - &self, - gkr_proof: usize, - public_coin: &mut impl RandomCoin, - ) -> Result, Self::Error> - where - E: FieldElement, - Hasher: crypto::ElementHasher, - { - let log_trace_len = gkr_proof; - let lagrange_kernel_rand_elements: LagrangeKernelRandElements = { - let mut rand_elements = Vec::with_capacity(log_trace_len); - for _ in 0..log_trace_len { - rand_elements.push(public_coin.draw().unwrap()); - } - - LagrangeKernelRandElements::new(rand_elements) - }; - - Ok(GkrRandElements::new(lagrange_kernel_rand_elements, Vec::new())) - } +struct LogUpGkrSimpleAir { + context: AirContext, } -struct LagrangeKernelComplexAir { - context: AirContext, -} - -impl Air for LagrangeKernelComplexAir { +impl Air for LogUpGkrSimpleAir { type BaseField = BaseElement; - // `GkrProof` is log(trace_len) for this dummy example, so that the verifier knows how many aux - // random variables to generate - type GkrProof = usize; - type GkrVerifier = DummyGkrVerifier; - type PublicInputs = (); fn new(trace_info: TraceInfo, _pub_inputs: Self::PublicInputs, options: ProofOptions) -> Self { Self { - context: AirContext::new_multi_segment( + context: AirContext::with_logup_gkr( trace_info, + _pub_inputs, vec![TransitionConstraintDegree::new(1)], - vec![TransitionConstraintDegree::new(1)], - 1, + vec![], 1, - Some(1), + 0, options, ), } } - fn context(&self) -> &AirContext { + fn context(&self) -> &AirContext { &self.context } @@ -183,44 +169,122 @@ impl Air for LagrangeKernelComplexAir { &self, _aux_rand_elements: &AuxRandElements, ) -> Vec> { - vec![Assertion::single(0, 0, E::ZERO)] + vec![] } - fn get_gkr_proof_verifier>( + fn get_logup_gkr_evaluator( &self, - ) -> Self::GkrVerifier { - DummyGkrVerifier + ) -> impl LogUpGkrEvaluator + { + PlainLogUpGkrEval::new() } } -// LagrangeComplexProver +#[derive(Clone, Default)] +pub struct PlainLogUpGkrEval { + oracles: Vec>, + _field: PhantomData, +} + +impl PlainLogUpGkrEval { + pub fn new() -> Self { + let committed_0 = LogUpGkrOracle::CurrentRow(0); + let committed_1 = LogUpGkrOracle::CurrentRow(1); + let committed_2 = LogUpGkrOracle::CurrentRow(2); + let committed_3 = LogUpGkrOracle::CurrentRow(3); + let committed_4 = LogUpGkrOracle::CurrentRow(4); + let oracles = vec![committed_0, committed_1, committed_2, committed_3, committed_4]; + Self { oracles, _field: PhantomData } + } +} + +impl LogUpGkrEvaluator for PlainLogUpGkrEval { + type BaseField = BaseElement; + + type PublicInputs = (); + + fn get_oracles(&self) -> &[LogUpGkrOracle] { + &self.oracles + } + + fn get_num_rand_values(&self) -> usize { + 1 + } + + fn get_num_fractions(&self) -> usize { + 4 + } + + fn max_degree(&self) -> usize { + 3 + } + + fn build_query(&self, frame: &EvaluationFrame, _periodic_values: &[E], query: &mut [E]) + where + E: FieldElement, + { + query.iter_mut().zip(frame.current().iter()).for_each(|(q, f)| *q = *f) + } + + fn evaluate_query( + &self, + query: &[F], + rand_values: &[E], + numerator: &mut [E], + denominator: &mut [E], + ) where + F: FieldElement, + E: FieldElement + ExtensionOf, + { + assert_eq!(numerator.len(), 4); + assert_eq!(denominator.len(), 4); + assert_eq!(query.len(), 5); + numerator[0] = E::from(query[1]); + numerator[1] = E::ONE; + numerator[2] = E::ONE; + numerator[3] = E::ONE; + + denominator[0] = rand_values[0] - E::from(query[0]); + denominator[1] = -(rand_values[0] - E::from(query[2])); + denominator[2] = -(rand_values[0] - E::from(query[3])); + denominator[3] = -(rand_values[0] - E::from(query[4])); + } + + fn compute_claim(&self, _inputs: &Self::PublicInputs, _rand_values: &[E]) -> E + where + E: FieldElement, + { + E::ZERO + } +} +// Prover // ================================================================================================ -struct LagrangeComplexProver { +struct LogUpGkrSimpleProver { aux_trace_width: usize, options: ProofOptions, } -impl LagrangeComplexProver { +impl LogUpGkrSimpleProver { fn new(aux_trace_width: usize) -> Self { Self { aux_trace_width, - options: ProofOptions::new(1, 2, 0, FieldExtension::None, 2, 1), + options: ProofOptions::new(1, 8, 0, FieldExtension::None, 2, 1), } } } -impl Prover for LagrangeComplexProver { +impl Prover for LogUpGkrSimpleProver { type BaseField = BaseElement; - type Air = LagrangeKernelComplexAir; - type Trace = LagrangeComplexTrace; + type Air = LogUpGkrSimpleAir; + type Trace = LogUpGkrSimple; type HashFn = Blake3_256; type VC = MerkleTree>; type RandomCoin = DefaultRandomCoin; type TraceLde> = DefaultTraceLde; type ConstraintEvaluator<'a, E: FieldElement> = - DefaultConstraintEvaluator<'a, LagrangeKernelComplexAir, E>; + DefaultConstraintEvaluator<'a, LogUpGkrSimpleAir, E>; fn get_pub_inputs(&self, _trace: &Self::Trace) -> <::Air as Air>::PublicInputs { } @@ -253,46 +317,16 @@ impl Prover for LagrangeComplexProver { DefaultConstraintEvaluator::new(air, aux_rand_elements, composition_coefficients) } - fn generate_gkr_proof( - &self, - main_trace: &Self::Trace, - public_coin: &mut Self::RandomCoin, - ) -> (ProverGkrProof, GkrRandElements) + fn build_aux_trace(&self, main_trace: &Self::Trace, _aux_rand_elements: &[E]) -> ColMatrix where E: FieldElement, { let main_trace = main_trace.main_segment(); - let log_trace_len = main_trace.num_rows().ilog2() as usize; - let lagrange_kernel_rand_elements = { - let mut rand_elements = Vec::with_capacity(log_trace_len); - for _ in 0..log_trace_len { - rand_elements.push(public_coin.draw().unwrap()); - } - - LagrangeKernelRandElements::new(rand_elements) - }; - - (log_trace_len, GkrRandElements::new(lagrange_kernel_rand_elements, Vec::new())) - } - - fn build_aux_trace( - &self, - main_trace: &Self::Trace, - aux_rand_elements: &AuxRandElements, - ) -> ColMatrix - where - E: FieldElement, - { - let main_trace = main_trace.main_segment(); - let lagrange_kernel_rand_elements = aux_rand_elements - .lagrange() - .expect("expected lagrange random elements to be present."); let mut columns = Vec::new(); - // First all other auxiliary columns - let rand_summed = lagrange_kernel_rand_elements.iter().fold(E::ZERO, |acc, &r| acc + r); - for _ in 1..self.aux_trace_width { + let rand_summed = E::from(777_u32); + for _ in 0..self.aux_trace_width { // building a dummy auxiliary column let column = main_trace .get_column(0) @@ -303,27 +337,6 @@ impl Prover for LagrangeComplexProver { columns.push(column); } - // then build the Lagrange kernel column - { - let r = &lagrange_kernel_rand_elements; - - let mut lagrange_col = Vec::with_capacity(main_trace.num_rows()); - - for row_idx in 0..main_trace.num_rows() { - let mut row_value = E::ONE; - for (bit_idx, &r_i) in r.iter().enumerate() { - if row_idx & (1 << bit_idx) == 0 { - row_value *= E::ONE - r_i; - } else { - row_value *= r_i; - } - } - lagrange_col.push(row_value); - } - - columns.push(lagrange_col); - } - ColMatrix::new(columns) } }