From 7ece89bfa706e562012b9eb55c5482588d109f00 Mon Sep 17 00:00:00 2001 From: Shahar Papini Date: Thu, 14 Mar 2024 10:44:09 +0200 Subject: [PATCH] FRI in Commitment Scheme --- src/core/air/component_visitors.rs | 33 +-- src/core/commitment_scheme.rs | 190 --------------- src/core/commitment_scheme/mod.rs | 357 ++++++++++++++++++++++++++++ src/core/commitment_scheme/utils.rs | 116 +++++++++ src/core/fields/cm31.rs | 10 +- src/core/fields/qm31.rs | 10 +- src/core/mod.rs | 8 +- src/core/prover/mod.rs | 224 ++++------------- src/core/prover/utils.rs | 36 +++ src/fibonacci/mod.rs | 76 +++--- 10 files changed, 625 insertions(+), 435 deletions(-) delete mode 100644 src/core/commitment_scheme.rs create mode 100644 src/core/commitment_scheme/mod.rs create mode 100644 src/core/commitment_scheme/utils.rs create mode 100644 src/core/prover/utils.rs diff --git a/src/core/air/component_visitors.rs b/src/core/air/component_visitors.rs index 145f2434b..142f07977 100644 --- a/src/core/air/component_visitors.rs +++ b/src/core/air/component_visitors.rs @@ -12,7 +12,6 @@ use crate::core::circle::CirclePoint; use crate::core::fields::qm31::SecureField; use crate::core::fri::CirclePolyDegreeBound; use crate::core::poly::circle::{CanonicCoset, CirclePoly, SecureCirclePoly}; -use crate::core::prover::LOG_BLOWUP_FACTOR; use crate::core::{ColumnVec, ComponentVec}; pub trait AirExt: Air { @@ -84,16 +83,16 @@ pub trait AirExt: Air { fn visit>(&mut self, component: &C) { let trace = self.component_traces.next().unwrap(); let (points, values) = component.mask_points_and_values(self.point, trace); - self.component_points.push(points); - self.component_values.push(values); + self.component_points.0.push(points); + self.component_values.0.push(values); } } let mut visitor = MaskEvaluator:: { point, component_traces: component_traces.iter(), - component_points: Vec::new(), - component_values: Vec::new(), + component_points: Default::default(), + component_values: Default::default(), }; self.visit_components(&mut visitor); (visitor.component_points, visitor.component_values) @@ -116,13 +115,14 @@ pub trait AirExt: Air { .map(|&log_size| CanonicCoset::new(log_size)) .collect_vec(); self.points + .0 .push(component.mask().to_points(&domains, self.point)); } } let mut visitor = MaskPointsEvaluator { point, - points: Vec::new(), + points: Default::default(), }; self.visit_components(&mut visitor); visitor.points @@ -155,7 +155,7 @@ pub trait AirExt: Air { let mut evaluator = { ConstraintPointEvaluator { point, - mask_values: mask_values.iter(), + mask_values: mask_values.0.iter(), evaluation_accumulator: PointEvaluationAccumulator::new( random_coeff, self.max_constraint_log_degree_bound(), @@ -203,31 +203,22 @@ pub trait AirExt: Air { } /// Returns the log degree bounds of the quotient polynomials in descending order. - fn commitment_domains(&self) -> Vec { + fn column_log_sizes(&self) -> Vec { struct DomainsVisitor { - domains: Vec, + log_sizes: Vec, } impl ComponentVisitor for DomainsVisitor { fn visit>(&mut self, component: &C) { - self.domains.extend( - component - .trace_log_degree_bounds() - .iter() - .map(|&log_size| CanonicCoset::new(log_size + LOG_BLOWUP_FACTOR)), - ); + self.log_sizes.extend(component.trace_log_degree_bounds()); } } let mut domains_visitor = DomainsVisitor { - domains: Vec::new(), + log_sizes: Vec::new(), }; self.visit_components(&mut domains_visitor); - // Add the composition polynomial's domain. - domains_visitor.domains.push(CanonicCoset::new( - self.max_constraint_log_degree_bound() + LOG_BLOWUP_FACTOR, - )); - domains_visitor.domains + domains_visitor.log_sizes } fn component_traces<'a>( diff --git a/src/core/commitment_scheme.rs b/src/core/commitment_scheme.rs deleted file mode 100644 index b011bc0e0..000000000 --- a/src/core/commitment_scheme.rs +++ /dev/null @@ -1,190 +0,0 @@ -use std::collections::BTreeMap; -use std::iter::zip; -use std::ops::Deref; - -use itertools::Itertools; - -use super::backend::cpu::{CPUCircleEvaluation, CPUCirclePoly}; -use super::channel::Blake2sChannel; -use super::fields::m31::BaseField; -use super::poly::circle::CanonicCoset; -use super::poly::BitReversedOrder; -use super::queries::SparseSubCircleDomain; -use super::ColumnVec; -use crate::commitment_scheme::blake2_hash::{Blake2sHash, Blake2sHasher}; -use crate::commitment_scheme::merkle_decommitment::MerkleDecommitment; -use crate::commitment_scheme::merkle_tree::MerkleTree; -use crate::core::channel::Channel; - -/// Holds a vector for each tree, which holds a vector for each column, which holds its respective -/// opened values. -pub struct OpenedValues(pub Vec>>); - -impl Deref for OpenedValues { - type Target = Vec>>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -pub struct Decommitments(Vec>); - -impl Deref for Decommitments { - type Target = Vec>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -pub struct CommitmentSchemeProver { - pub trees: Vec, - pub log_blowup_factor: u32, -} - -impl CommitmentSchemeProver { - pub fn new(log_blowup_factor: u32) -> Self { - CommitmentSchemeProver { - trees: Vec::new(), - log_blowup_factor, - } - } - - pub fn commit(&mut self, polynomials: ColumnVec, channel: &mut Blake2sChannel) { - let tree = CommitmentTreeProver::new(polynomials, self.log_blowup_factor, channel); - self.trees.push(tree); - } - - pub fn roots(&self) -> Vec { - self.trees.iter().map(|tree| tree.root()).collect() - } - - pub fn decommit( - &self, - positions: BTreeMap, - ) -> (OpenedValues, Decommitments) { - let (values, decommitments) = self - .trees - .iter() - .map(|tree| { - tree.decommit( - positions[&(tree.polynomials[0].log_size() + self.log_blowup_factor)].flatten(), - ) - }) - .unzip(); - (OpenedValues(values), Decommitments(decommitments)) - } -} - -pub struct CommitmentTreeProver { - pub polynomials: ColumnVec, - pub evaluations: ColumnVec>, - // TODO(AlonH): Change to mixed degree merkle and remove values clone. - pub commitment: MerkleTree, -} - -impl CommitmentTreeProver { - pub fn new( - polynomials: Vec, - log_blowup_factor: u32, - channel: &mut Blake2sChannel, - ) -> Self { - let domains = polynomials - .iter() - .map(|poly| CanonicCoset::new(poly.log_size() + log_blowup_factor)) - .collect_vec(); - let evaluations = zip(&polynomials, domains) - .map(|(poly, domain)| poly.evaluate(domain.circle_domain())) - .collect_vec(); - let commitment = MerkleTree::::commit( - evaluations - .iter() - .map(|eval| eval.values.clone()) - .collect_vec(), - ); - channel.mix_digest(commitment.root()); - - CommitmentTreeProver { - polynomials, - evaluations, - commitment, - } - } - - // TODO(AlonH): change interface after integrating mixed degree merkle. - pub fn decommit( - &self, - positions: Vec, - ) -> ( - ColumnVec>, - MerkleDecommitment, - ) { - let values = self - .evaluations - .iter() - .map(|c| positions.iter().map(|p| c[*p]).collect()) - .collect(); - let decommitment = self.commitment.generate_decommitment(positions); - (values, decommitment) - } -} - -impl Deref for CommitmentTreeProver { - type Target = MerkleTree; - - fn deref(&self) -> &Self::Target { - &self.commitment - } -} - -#[derive(Default)] -pub struct CommitmentSchemeVerifier { - pub commitments: Vec, -} - -impl CommitmentSchemeVerifier { - pub fn new() -> Self { - CommitmentSchemeVerifier { - commitments: Vec::new(), - } - } - - pub fn commit(&mut self, commitment: Blake2sHash, channel: &mut Blake2sChannel) { - let verifier = CommitmentTreeVerifier::new(commitment, channel); - self.commitments.push(verifier); - } - - pub fn verify( - &self, - decommitments: &[MerkleDecommitment], - positions: &[SparseSubCircleDomain], - ) -> bool { - self.commitments - .iter() - .zip(decommitments) - .zip(positions) - .all(|((commitment, decommitment), positions)| { - commitment.verify(decommitment, &positions.flatten()) - }) - } -} - -pub struct CommitmentTreeVerifier { - pub commitment: Blake2sHash, -} - -impl CommitmentTreeVerifier { - pub fn new(commitment: Blake2sHash, channel: &mut Blake2sChannel) -> Self { - channel.mix_digest(commitment); - CommitmentTreeVerifier { commitment } - } - - pub fn verify( - &self, - decommitment: &MerkleDecommitment, - positions: &[usize], - ) -> bool { - decommitment.verify(self.commitment, positions) - } -} diff --git a/src/core/commitment_scheme/mod.rs b/src/core/commitment_scheme/mod.rs new file mode 100644 index 000000000..5253dce05 --- /dev/null +++ b/src/core/commitment_scheme/mod.rs @@ -0,0 +1,357 @@ +//! Implements a FRI polynomial commitment scheme. +//! This is a protocol where the prover can commit on a set of polynomials and then prove their +//! opening on a set of points. +//! Note: This implementation is not really a polynomial commitment scheme, because we are not in +//! the unique decoding regime. This is enough for a STARK proof though, where we onyl want to imply +//! the existence of such polynomials, and re ok with having a small decoding list. + +mod utils; + +use std::iter::zip; +use std::ops::Deref; + +use itertools::Itertools; + +pub use self::utils::{TreeColumns, TreeVec}; +use super::backend::cpu::{CPUCircleEvaluation, CPUCirclePoly}; +use super::backend::CPUBackend; +use super::channel::Blake2sChannel; +use super::circle::CirclePoint; +use super::fields::m31::BaseField; +use super::fields::qm31::SecureField; +use super::fri::{ + CirclePolyDegreeBound, FriConfig, FriProof, FriProver, FriVerifier, SparseCircleEvaluation, +}; +use super::oods::get_pair_oods_quotient; +use super::poly::circle::{CanonicCoset, CircleDomain, CircleEvaluation}; +use super::poly::BitReversedOrder; +use super::proof_of_work::{ProofOfWork, ProofOfWorkProof}; +use super::prover::{ + LOG_BLOWUP_FACTOR, LOG_LAST_LAYER_DEGREE_BOUND, N_QUERIES, PROOF_OF_WORK_BITS, +}; +use super::queries::SparseSubCircleDomain; +use super::ColumnVec; +use crate::commitment_scheme::blake2_hash::{Blake2sHash, Blake2sHasher}; +use crate::commitment_scheme::merkle_decommitment::MerkleDecommitment; +use crate::commitment_scheme::merkle_tree::MerkleTree; +use crate::core::channel::Channel; + +type MerkleHasher = Blake2sHasher; +type ProofChannel = Blake2sChannel; + +/// The prover size of a FRI polynomial commitment scheme. See [self]. +pub struct CommitmentSchemeProver { + pub trees: TreeVec, + pub log_blowup_factor: u32, +} + +impl CommitmentSchemeProver { + pub fn new(log_blowup_factor: u32) -> Self { + CommitmentSchemeProver { + trees: TreeVec::::new(), + log_blowup_factor, + } + } + + pub fn commit(&mut self, polynomials: ColumnVec, channel: &mut ProofChannel) { + let tree = CommitmentTreeProver::new(polynomials, self.log_blowup_factor, channel); + self.trees.push(tree); + } + + pub fn roots(&self) -> TreeVec { + self.trees.as_ref().map(|tree| tree.root()) + } + + pub fn polys(&self) -> TreeColumns<&CPUCirclePoly> { + self.trees.to_cols(|tree| tree.polynomials.iter().collect()) + } + + fn evaluations(&self) -> TreeColumns<&CPUCircleEvaluation> { + self.trees.to_cols(|tree| tree.evaluations.iter().collect()) + } + + pub fn open_values( + &self, + open_points: TreeColumns>>, + channel: &mut ProofChannel, + ) -> CommitmentSchemeProof { + // Evaluate polynomials on open points. + let opened_values = self.polys().zip_cols(&open_points).map(|(poly, points)| { + points + .iter() + .map(|point| poly.eval_at_point(*point)) + .collect_vec() + }); + + // Compute oods quotients for boundary constraints on open_points. + let quotients = self + .evaluations() + .zip_cols(&opened_values) + .zip_cols(&open_points) + .map(|((evaluation, values), points)| { + zip(points, values) + .map(|(&point, &value)| { + get_pair_oods_quotient(point, value, evaluation).bit_reverse() + }) + .collect_vec() + }); + + // Run FRI commitment phase on the oods quotients. + let fri_config = FriConfig::new(LOG_LAST_LAYER_DEGREE_BOUND, LOG_BLOWUP_FACTOR, N_QUERIES); + // TODO(spapini): Remove rev() when we start accumulating by size. + // This is only done because fri demands descending sizes. + let fri_prover = FriProver::::commit( + channel, + fri_config, + "ients.flatten_all_rev(), + ); + + // Proof of work. + let proof_of_work = ProofOfWork::new(PROOF_OF_WORK_BITS).prove(channel); + + // FRI decommitment phase. + let (fri_proof, fri_query_domains) = fri_prover.decommit(channel); + + // Decommit the FRI queries on the merkle trees. + let decommitment_results = self.trees.as_ref().map(|tree| { + tree.decommit( + fri_query_domains[&(tree.polynomials[0].log_size() + self.log_blowup_factor)] + .flatten(), + ) + }); + let queried_values = decommitment_results.to_cols(|(v, _)| v.clone()); + let decommitments = decommitment_results.map(|(_, d)| d); + + CommitmentSchemeProof { + opened_values, + decommitments, + queried_values, + proof_of_work, + fri_proof, + } + } +} + +pub struct CommitmentSchemeProof { + pub opened_values: TreeColumns>, + pub decommitments: TreeVec>, + pub queried_values: TreeColumns>, + pub proof_of_work: ProofOfWorkProof, + pub fri_proof: FriProof, +} + +/// Prover data for a single commitment tree in a commitment scheme. The commitment scheme allows to +/// commit on a set polynomials at a time. This corresponds to such a set. +pub struct CommitmentTreeProver { + pub polynomials: ColumnVec, + pub evaluations: ColumnVec>, + // TODO(AlonH): Change to mixed degree merkle and remove values clone. + commitment: MerkleTree, +} + +impl CommitmentTreeProver { + fn new( + polynomials: ColumnVec, + log_blowup_factor: u32, + channel: &mut ProofChannel, + ) -> Self { + let evaluations = polynomials + .iter() + .map(|poly| { + poly.evaluate( + CanonicCoset::new(poly.log_size() + log_blowup_factor).circle_domain(), + ) + }) + .collect_vec(); + let commitment = MerkleTree::::commit( + evaluations + .iter() + .map(|eval| eval.values.clone()) + .collect_vec(), + ); + channel.mix_digest(commitment.root()); + + CommitmentTreeProver { + polynomials, + evaluations, + commitment, + } + } + + // TODO(AlonH): change interface after integrating mixed degree merkle. + /// Decommits the merkle tree on the given query positions. + fn decommit( + &self, + queries: Vec, + ) -> ( + ColumnVec>, + MerkleDecommitment, + ) { + let values = self + .evaluations + .iter() + .map(|c| queries.iter().map(|p| c[*p]).collect()) + .collect(); + let decommitment = self.commitment.generate_decommitment(queries); + (values, decommitment) + } +} + +impl Deref for CommitmentTreeProver { + type Target = MerkleTree; + + fn deref(&self) -> &Self::Target { + &self.commitment + } +} + +/// The verifier side of a FRI polynomial commitment scheme. See [self]. +#[derive(Default)] +pub struct CommitmentSchemeVerifier { + pub trees: TreeVec, +} + +impl CommitmentSchemeVerifier { + pub fn new() -> Self { + Self::default() + } + + /// A [TreeColumns] of the log sizes of each column in each commitment tree. + fn column_log_sizes(&self) -> TreeColumns { + self.trees.to_cols(|tree| tree.log_sizes.to_vec()) + } + + /// Reads a commitment from the prover. + pub fn commit( + &mut self, + commitment: Blake2sHash, + log_sizes: Vec, + channel: &mut ProofChannel, + ) { + let verifier = CommitmentTreeVerifier::new(commitment, log_sizes, channel); + self.trees.push(verifier); + } + + pub fn verify_opening( + &self, + open_points: TreeColumns>>, + proof: CommitmentSchemeProof, + channel: &mut ProofChannel, + ) -> bool { + // Compute degree bounds for oods quotients without looking at the proof. + let bounds = self + .column_log_sizes() + .zip_cols(&open_points) + .map(|(log_size, open_points)| { + vec![CirclePolyDegreeBound::new(log_size); open_points.len()] + }) + .flatten_all_rev(); + + // FRI commitment phase on oods quotients. + let fri_config = FriConfig::new(LOG_LAST_LAYER_DEGREE_BOUND, LOG_BLOWUP_FACTOR, N_QUERIES); + let mut fri_verifier = + FriVerifier::commit(channel, fri_config, proof.fri_proof, bounds).unwrap(); + + // Verify proof of work. + ProofOfWork::new(PROOF_OF_WORK_BITS).verify(channel, &proof.proof_of_work); + + // Get FRI query domains. + let fri_query_domains = fri_verifier.column_opening_positions(channel); + + // Verify merkle decommitments. + if !self + .trees + .as_ref() + .zip(&proof.decommitments) + .map(|(tree, decommitment)| { + // TODO(spapini): Also very opened_value here. + tree.verify( + decommitment, + &fri_query_domains[&(tree.log_sizes[0] + 1)].flatten(), + ) + }) + .0 + .iter() + .all(|x| *x) + { + return false; + } + + // Answer FRI queries. + let mut fri_answers = self + .column_log_sizes() + .zip_cols(proof.opened_values) + .zip_cols(open_points) + .zip_cols(proof.queried_values) + .map( + // For each column. + |(((log_size, opened_values), opened_points), queried_values)| { + zip(opened_points, opened_values) + .map(|(point, value)| { + // For each opening point of that column. + eval_quotients_on_sparse_domain( + queried_values.clone(), + &fri_query_domains[&(log_size + 1)], + CanonicCoset::new(log_size + 1).circle_domain(), + point, + value, + ) + }) + .collect_vec() + }, + ) + .flatten_all(); + + // TODO(spapini): Remove reverse. + fri_answers.reverse(); + fri_verifier.decommit(fri_answers).is_ok() + } +} + +fn eval_quotients_on_sparse_domain( + queried_values: Vec, + query_domains: &SparseSubCircleDomain, + commitment_domain: CircleDomain, + point: CirclePoint, + value: SecureField, +) -> SparseCircleEvaluation { + let queried_values = &mut queried_values.clone().into_iter(); + let res = SparseCircleEvaluation::new( + query_domains + .iter() + .map(|subdomain| { + let subeval = CircleEvaluation::new( + subdomain.to_circle_domain(&commitment_domain), + queried_values.take(1 << subdomain.log_size).collect(), + ); + get_pair_oods_quotient(point, value, &subeval).bit_reverse() + }) + .collect(), + ); + assert!(queried_values.is_empty()); + res +} + +/// Verifier data for a single commitment tree in a commitment scheme. +pub struct CommitmentTreeVerifier { + pub commitment: Blake2sHash, + pub log_sizes: Vec, +} + +impl CommitmentTreeVerifier { + pub fn new(commitment: Blake2sHash, log_sizes: Vec, channel: &mut ProofChannel) -> Self { + channel.mix_digest(commitment); + CommitmentTreeVerifier { + commitment, + log_sizes, + } + } + + pub fn verify( + &self, + decommitment: &MerkleDecommitment, + positions: &[usize], + ) -> bool { + decommitment.verify(self.commitment, positions) + } +} diff --git a/src/core/commitment_scheme/utils.rs b/src/core/commitment_scheme/utils.rs new file mode 100644 index 000000000..d14598e1d --- /dev/null +++ b/src/core/commitment_scheme/utils.rs @@ -0,0 +1,116 @@ +use std::iter::zip; +use std::ops::{Deref, DerefMut}; + +use itertools::zip_eq; + +use crate::core::ColumnVec; + +/// A container that holds an element for each commitment tree. +#[derive(Debug, Clone)] +pub struct TreeVec(pub Vec); +impl TreeVec { + pub fn new() -> TreeVec { + TreeVec(Vec::new()) + } + /// Creates a [TreeColumns] instance by mapping each element of the [TreeVec] to a [ColumnVec]. + pub fn to_cols<'a, U: 'a, F: Fn(&'a T) -> ColumnVec>(&'a self, f: F) -> TreeColumns { + TreeColumns(TreeVec(self.0.iter().map(f).collect())) + } + pub fn map U>(self, f: F) -> TreeVec { + TreeVec(self.0.into_iter().map(f).collect()) + } + pub fn zip(self, other: impl Into>) -> TreeVec<(T, U)> { + let other = other.into(); + assert_eq!(self.0.len(), other.len()); + TreeVec(zip(self.0, other.0).collect()) + } + pub fn as_ref(&self) -> TreeVec<&T> { + TreeVec(self.0.iter().collect()) + } +} +// Converts &TreeVec to TreeVec<&T>. +impl<'a, T> From<&'a TreeVec> for TreeVec<&'a T> { + fn from(val: &'a TreeVec) -> Self { + val.as_ref() + } +} +impl Deref for TreeVec { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for TreeVec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl Default for TreeVec { + fn default() -> Self { + TreeVec(Vec::new()) + } +} + +/// A container that holds an element for each column of each commitment tree. +#[derive(Debug, Clone)] +pub struct TreeColumns(pub TreeVec>); +impl TreeColumns { + pub fn new(values: Vec>) -> Self { + TreeColumns(TreeVec(values)) + } + pub fn map U>(self, mut f: F) -> TreeColumns { + TreeColumns(TreeVec( + self.0 + .0 + .into_iter() + .map(|column| column.into_iter().map(&mut f).collect()) + .collect(), + )) + } + /// Zips two [TreeColumns] with the same structure (number of columns in each tree). + /// The resulting [TreeColumns] has the same structure, with each value being a tuple of the + /// corresponding values from the input [TreeColumns]. + pub fn zip_cols(self, other: impl Into>) -> TreeColumns<(T, U)> { + let other = other.into(); + assert_eq!(self.0.len(), other.0.len()); + TreeColumns(TreeVec( + self.0 + .0 + .into_iter() + .zip(other.0 .0) + .map(|(column1, column2)| zip_eq(column1, column2).collect()) + .collect(), + )) + } + pub fn as_ref(&self) -> TreeColumns<&T> { + TreeColumns(TreeVec( + self.0 + .0 + .iter() + .map(|column| column.iter().collect()) + .collect(), + )) + } + /// Flattens the [TreeColumns] into a single [ColumnVec] with all the columns combined. + pub fn flatten(self) -> ColumnVec { + self.0 .0.into_iter().flatten().collect() + } +} +impl<'a, T> From<&'a TreeColumns> for TreeColumns<&'a T> { + fn from(val: &'a TreeColumns) -> Self { + val.as_ref() + } +} +impl TreeColumns> { + /// Flattens a [TreeColumns] of [Vec]s into a single [Vec] with all the elements combined. + pub fn flatten_all(self) -> Vec { + self.flatten().into_iter().flatten().collect() + } + + // TODO(spapini): Remove after accumulating oods quotients by size. + /// Flattens a [TreeColumns] of [Vec]s into a single [Vec] with all the elements combined, in + /// reverse order. + pub fn flatten_all_rev(self) -> Vec { + self.flatten().into_iter().flatten().rev().collect() + } +} diff --git a/src/core/fields/cm31.rs b/src/core/fields/cm31.rs index a294ab1f0..c585855b6 100644 --- a/src/core/fields/cm31.rs +++ b/src/core/fields/cm31.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::fmt::{Debug, Display}; use std::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, }; @@ -12,7 +12,7 @@ pub const P2: u64 = 4611686014132420609; // (2 ** 31 - 1) ** 2 /// Complex extension field of M31. /// Equivalent to M31\[x\] over (x^2 + 1) as the irreducible polynomial. /// Represented as (a, b) of a + bi. -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct CM31(pub M31, pub M31); impl_field!(CM31, P2); @@ -34,6 +34,12 @@ impl Display for CM31 { } } +impl Debug for CM31 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} + {}i", self.0, self.1) + } +} + impl Mul for CM31 { type Output = Self; diff --git a/src/core/fields/qm31.rs b/src/core/fields/qm31.rs index 0692f3847..b2df12dd2 100644 --- a/src/core/fields/qm31.rs +++ b/src/core/fields/qm31.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::fmt::{Debug, Display}; use std::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, }; @@ -15,7 +15,7 @@ pub const R: CM31 = CM31::from_u32_unchecked(1, 2); /// Extension field of CM31. /// Equivalent to CM31\[x\] over (x^2 - 1 - 2i) as the irreducible polynomial. /// Represented as ((a, b), (c, d)) of (a + bi) + (c + di)u. -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct QM31(pub CM31, pub CM31); pub type SecureField = QM31; @@ -49,6 +49,12 @@ impl Display for QM31 { } } +impl Debug for QM31 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "({}) + ({})u", self.0, self.1) + } +} + impl Mul for QM31 { type Output = Self; diff --git a/src/core/mod.rs b/src/core/mod.rs index 9dfcadeff..c9a8078b6 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -17,4 +17,10 @@ pub mod utils; /// A vector in which each element relates (by index) to a column in the trace. pub type ColumnVec = Vec; /// A vector of [ColumnVec]s. Each [ColumnVec] relates (by index) to a component in the air. -pub type ComponentVec = Vec>; +#[derive(Debug, Clone)] +pub struct ComponentVec(pub Vec>); +impl Default for ComponentVec { + fn default() -> Self { + Self(Vec::new()) + } +} diff --git a/src/core/prover/mod.rs b/src/core/prover/mod.rs index f67bbe155..2a62ee821 100644 --- a/src/core/prover/mod.rs +++ b/src/core/prover/mod.rs @@ -1,29 +1,23 @@ -use std::iter::zip; +mod utils; -use itertools::{enumerate, Itertools}; +use itertools::Itertools; -use super::poly::circle::CanonicCoset; -use super::queries::SparseSubCircleDomain; +use self::utils::component_wise_to_tree_wise; +use super::commitment_scheme::{CommitmentSchemeProof, TreeVec}; use super::ColumnVec; use crate::commitment_scheme::blake2_hash::Blake2sHasher; use crate::commitment_scheme::hasher::Hasher; -use crate::core::air::evaluation::SECURE_EXTENSION_DEGREE; use crate::core::air::{Air, AirExt}; use crate::core::backend::cpu::CPUCircleEvaluation; use crate::core::backend::CPUBackend; use crate::core::channel::{Blake2sChannel, Channel as ChannelTrait}; use crate::core::circle::CirclePoint; -use crate::core::commitment_scheme::{ - CommitmentSchemeProver, CommitmentSchemeVerifier, Decommitments, OpenedValues, -}; +use crate::core::commitment_scheme::{CommitmentSchemeProver, CommitmentSchemeVerifier}; use crate::core::fields::m31::BaseField; use crate::core::fields::qm31::SecureField; -use crate::core::fri::{FriConfig, FriProof, FriProver, FriVerifier, SparseCircleEvaluation}; -use crate::core::oods::get_pair_oods_quotient; use crate::core::poly::circle::{combine_secure_value, CircleEvaluation}; use crate::core::poly::BitReversedOrder; -use crate::core::proof_of_work::{ProofOfWork, ProofOfWorkProof}; -use crate::core::ComponentVec; +use crate::core::prover::utils::tree_wise_to_component_wise; type Channel = Blake2sChannel; type MerkleHasher = Blake2sHasher; @@ -34,14 +28,8 @@ pub const PROOF_OF_WORK_BITS: u32 = 12; pub const N_QUERIES: usize = 3; pub struct StarkProof { - pub commitments: Vec<::Hash>, - pub decommitments: Decommitments, - pub trace_oods_values: ComponentVec>, - pub composition_polynomial_column_oods_values: [SecureField; SECURE_EXTENSION_DEGREE], - pub opened_values: OpenedValues, - pub proof_of_work: ProofOfWorkProof, - pub fri_proof: FriProof, - pub additional_proof_data: AdditionalProofData, + pub commitments: TreeVec<::Hash>, + pub commitment_scheme_proof: CommitmentSchemeProof, } pub struct AdditionalProofData { @@ -57,6 +45,7 @@ pub fn prove( trace: ColumnVec>, ) -> StarkProof { // Evaluate and commit on trace. + // TODO(spapini): Commit on trace outside. let trace_polys = trace.into_iter().map(|poly| poly.interpolate()).collect(); let mut commitment_scheme = CommitmentSchemeProver::new(LOG_BLOWUP_FACTOR); commitment_scheme.commit(trace_polys, channel); @@ -71,181 +60,72 @@ pub fn prove( // Evaluate the trace mask and the composition polynomial on the OODS point. let oods_point = CirclePoint::::get_random_point(channel); - let (trace_oods_points, trace_oods_values) = air.mask_points_and_values( + // TODO(spapini): This values are evaluted twice right now. + let (open_points, _trace_oods_values) = air.mask_points_and_values( oods_point, &air.component_traces(&commitment_scheme.trees[0].polynomials), ); - let composition_polynomial_oods_value = composition_polynomial_poly.eval_at_point(oods_point); - - // Calculate a quotient polynomial for each trace mask item and one for the composition - // polynomial. - let mut oods_quotients = Vec::with_capacity(trace_oods_points.len() + SECURE_EXTENSION_DEGREE); - let composition_polynomial_column_oods_values = - composition_polynomial_poly.eval_columns_at_point(oods_point); - for (evaluation, value) in zip( - &commitment_scheme.trees[1].evaluations, - composition_polynomial_column_oods_values, - ) { - oods_quotients.push(get_pair_oods_quotient(oods_point, value, evaluation).bit_reverse()); - } - for (component_points, component_values) in zip(&trace_oods_points, &trace_oods_values) { - for (i, (column_points, column_values)) in - enumerate(zip(component_points, component_values)) - { - for (point, value) in zip(column_points, column_values) { - oods_quotients.push( - get_pair_oods_quotient( - *point, - *value, - &commitment_scheme.trees[0].evaluations[i], - ) - .bit_reverse(), - ); - } - } - } - let fri_config = FriConfig::new(LOG_LAST_LAYER_DEGREE_BOUND, LOG_BLOWUP_FACTOR, N_QUERIES); - let fri_prover = FriProver::commit(channel, fri_config, &oods_quotients); + // Add composiion poly points. + let mut open_points = component_wise_to_tree_wise(air, open_points); + open_points.0.push(vec![ + vec![oods_point], + vec![oods_point], + vec![oods_point], + vec![oods_point], + ]); - let proof_of_work = ProofOfWork::new(PROOF_OF_WORK_BITS).prove(channel); - let (fri_proof, fri_opening_positions) = fri_prover.decommit(channel); - - let (opened_values, decommitments) = commitment_scheme.decommit(fri_opening_positions); + let commitment_scheme_proof = commitment_scheme.open_values(open_points, channel); StarkProof { commitments: commitment_scheme.roots(), - decommitments, - trace_oods_values, - composition_polynomial_column_oods_values, - opened_values, - proof_of_work, - fri_proof, - additional_proof_data: AdditionalProofData { - composition_polynomial_oods_value, - composition_polynomial_random_coeff: random_coeff, - oods_point, - oods_quotients, - }, + commitment_scheme_proof, } } pub fn verify(proof: StarkProof, air: &impl Air, channel: &mut Channel) -> bool { // Read trace commitment. let mut commitment_scheme = CommitmentSchemeVerifier::new(); - commitment_scheme.commit(proof.commitments[0], channel); + commitment_scheme.commit(proof.commitments[0], air.column_log_sizes(), channel); let random_coeff = channel.draw_felt(); // Read composition polynomial commitment. - commitment_scheme.commit(proof.commitments[1], channel); + commitment_scheme.commit( + proof.commitments[1], + vec![air.max_constraint_log_degree_bound(); 4], + channel, + ); // Calculate the composition polynomial value on the OODS point using the trace values sent and // verify it is equal to the composition polynomial value sent. let oods_point = CirclePoint::::get_random_point(channel); - let trace_oods_points = air.mask_points(oods_point); - let composition_polynomial_oods_value = air.eval_composition_polynomial_at_point( - oods_point, - &proof.trace_oods_values, - random_coeff, - ); + let mut open_points = component_wise_to_tree_wise(air, air.mask_points(oods_point)); + open_points.0.push(vec![ + vec![oods_point], + vec![oods_point], + vec![oods_point], + vec![oods_point], + ]); + + // TODO(spapini): Save clone. + let mut opened_values = proof.commitment_scheme_proof.opened_values.clone(); + let composition_oods_values = opened_values.0.pop().unwrap(); + let trace_oods_values = tree_wise_to_component_wise(air, opened_values); + + let composition_polynomial_oods_value = + air.eval_composition_polynomial_at_point(oods_point, &trace_oods_values, random_coeff); assert_eq!( composition_polynomial_oods_value, - combine_secure_value(proof.composition_polynomial_column_oods_values) + combine_secure_value( + composition_oods_values + .iter() + .flatten() + .cloned() + .collect_vec() + .try_into() + .unwrap() + ) ); - let bounds = air.quotient_log_bounds(); - let fri_config = FriConfig::new(LOG_LAST_LAYER_DEGREE_BOUND, LOG_BLOWUP_FACTOR, N_QUERIES); - let mut fri_verifier = - FriVerifier::commit(channel, fri_config, proof.fri_proof, bounds).unwrap(); - - ProofOfWork::new(PROOF_OF_WORK_BITS).verify(channel, &proof.proof_of_work); - let opening_positions = fri_verifier - .column_opening_positions(channel) - .into_values() - .collect_vec(); - commitment_scheme.verify(&proof.decommitments, &opening_positions); - - let commitment_domains = air.commitment_domains(); - // Prepare the quotient evaluations needed for the FRI verifier. - let sparse_circle_evaluations = prepare_fri_evaluations( - opening_positions, - proof.opened_values, - trace_oods_points, - proof.trace_oods_values, - proof.composition_polynomial_column_oods_values, - commitment_domains, - oods_point, - ); - - fri_verifier.decommit(sparse_circle_evaluations).unwrap(); - - true -} - -fn prepare_fri_evaluations( - opening_positions: Vec, - opened_values: OpenedValues, - trace_oods_points: ComponentVec>>, - trace_oods_values: ComponentVec>, - composition_polynomial_column_oods_values: [SecureField; SECURE_EXTENSION_DEGREE], - commitment_domains: Vec, - oods_point: CirclePoint, -) -> Vec> { - // TODO(AlonH): Generalize when introducing mixed degree. - let trace_commitment_domain = commitment_domains[0]; - let composition_polynomial_commitment_domain = commitment_domains.last().unwrap(); - let mut sparse_circle_evaluations = Vec::new(); - for (opened_values, oods_value) in - zip(&opened_values[1], composition_polynomial_column_oods_values) - { - let mut evaluation = Vec::new(); - let mut opened_values_iter = opened_values.iter(); - for sub_circle_domain in opening_positions[1].iter() { - let values = (&mut opened_values_iter) - .take(1 << sub_circle_domain.log_size) - .copied() - .collect(); - let sub_circle_evaluation = CircleEvaluation::new( - sub_circle_domain - .to_circle_domain(&composition_polynomial_commitment_domain.circle_domain()), - values, - ); - evaluation.push( - get_pair_oods_quotient(oods_point, oods_value, &sub_circle_evaluation) - .bit_reverse(), - ); - } - assert!( - opened_values_iter.next().is_none(), - "Not all values were used." - ); - sparse_circle_evaluations.push(SparseCircleEvaluation::new(evaluation)); - } - for (component_points, component_values) in zip(&trace_oods_points, &trace_oods_values) { - for (i, (column_points, column_values)) in - enumerate(zip(component_points, component_values)) - { - for (oods_point, oods_value) in zip(column_points, column_values) { - let mut evaluation = Vec::new(); - let mut opened_values = opened_values[0][i].iter().copied(); - for sub_circle_domain in opening_positions[0].iter() { - let values = (&mut opened_values) - .take(1 << sub_circle_domain.log_size) - .collect(); - let sub_circle_evaluation = CircleEvaluation::new( - sub_circle_domain - .to_circle_domain(&trace_commitment_domain.circle_domain()), - values, - ); - evaluation.push( - get_pair_oods_quotient(*oods_point, *oods_value, &sub_circle_evaluation) - .bit_reverse(), - ); - } - assert!(opened_values.next().is_none(), "Not all values were used."); - sparse_circle_evaluations.push(SparseCircleEvaluation::new(evaluation)); - } - } - } - sparse_circle_evaluations + commitment_scheme.verify_opening(open_points, proof.commitment_scheme_proof, channel) } diff --git a/src/core/prover/utils.rs b/src/core/prover/utils.rs new file mode 100644 index 000000000..94bff9db5 --- /dev/null +++ b/src/core/prover/utils.rs @@ -0,0 +1,36 @@ +use crate::core::air::{Air, Component, ComponentVisitor}; +use crate::core::backend::CPUBackend; +use crate::core::commitment_scheme::TreeColumns; +use crate::core::ComponentVec; + +pub fn component_wise_to_tree_wise( + _air: &impl Air, + values: ComponentVec, +) -> TreeColumns { + TreeColumns::new(vec![values.0.into_iter().flatten().collect()]) +} + +pub fn tree_wise_to_component_wise( + air: &impl Air, + mut values: TreeColumns, +) -> ComponentVec { + // Recombine the trace values by component, and not by tree. + struct Visitor<'a, T> { + by_tree: &'a mut std::vec::IntoIter, + by_component: ComponentVec, + } + impl ComponentVisitor for Visitor<'_, T> { + fn visit>(&mut self, component: &C) { + self.by_component + .0 + .push(self.by_tree.take(component.mask().len()).collect()); + } + } + let values = values.0.remove(0); + let mut recombiner = Visitor { + by_tree: &mut values.into_iter(), + by_component: ComponentVec::default(), + }; + air.visit_components(&mut recombiner); + recombiner.by_component +} diff --git a/src/fibonacci/mod.rs b/src/fibonacci/mod.rs index bc0b43047..750851b30 100644 --- a/src/fibonacci/mod.rs +++ b/src/fibonacci/mod.rs @@ -63,6 +63,8 @@ pub fn verify_proof(proof: StarkProof, claim: BaseField) -> b verify(proof, &fib.air, channel) } +#[allow(unused)] +#[allow(warnings)] #[cfg(test)] mod tests { use itertools::Itertools; @@ -127,24 +129,22 @@ mod tests { fn test_oods_quotients_are_low_degree() { const FIB_LOG_SIZE: u32 = 5; let fib = Fibonacci::new(FIB_LOG_SIZE, m31!(443693538)); + let poly = fib.get_trace().interpolate(); + let trace = [ComponentTrace::new(vec![&poly])]; + let channel = &mut Blake2sChannel::new(Blake2sHasher::hash(BaseField::into_slice(&[]))); + let coeff = channel.draw_felt(); + let point = CirclePoint::::get_random_point(channel); - let proof = fib.prove(); - let (composition_polynomial_quotient, trace_quotients) = proof - .additional_proof_data - .oods_quotients - .split_first() - .unwrap(); - - // Assert that the trace quotients are low degree. - for quotient in trace_quotients.iter() { - let interpolated_quotient_poly = secure_eval_to_base_eval(quotient).interpolate(); - assert!(interpolated_quotient_poly.is_in_fft_space(FIB_LOG_SIZE)); - } - - // Assert that the composition polynomial quotient is low degree. - let interpolated_quotient_poly = - secure_eval_to_base_eval(composition_polynomial_quotient).interpolate(); - assert!(interpolated_quotient_poly.is_in_fft_space(FIB_LOG_SIZE + 1)); + assert_eq!( + fib.air + .compute_composition_polynomial(coeff, &trace) + .eval_at_point(point), + fib.air.eval_composition_polynomial_at_point( + point, + &fib.air.mask_points_and_values(point, &trace).1, + coeff + ) + ); } #[test] @@ -187,46 +187,24 @@ mod tests { let fib = Fibonacci::new(FIB_LOG_SIZE, m31!(443693538)); let trace = fib.get_trace(); let trace_poly = trace.interpolate(); - let trace = ComponentTrace::new(vec![&trace_poly]); + let _trace = ComponentTrace::new(vec![&trace_poly]); let proof = fib.prove(); - let oods_point = proof.additional_proof_data.oods_point; - - let (_, mask_values) = fib.air.component.mask_points_and_values(oods_point, &trace); - let mut evaluation_accumulator = PointEvaluationAccumulator::new( - proof - .additional_proof_data - .composition_polynomial_random_coeff, - fib.air.max_constraint_log_degree_bound(), - ); - fib.air.component.evaluate_constraint_quotients_at_point( - oods_point, - &mask_values, - &mut evaluation_accumulator, - ); - let hz = evaluation_accumulator.finalize(); - - assert_eq!( - proof - .additional_proof_data - .composition_polynomial_oods_value, - hz - ); assert!(verify_proof::(proof, fib.claim)); } - // TODO(AlonH): Check the correct error occurs after introducing errors instead of - // #[should_panic]. #[test] - #[should_panic] fn test_prove_invalid_trace_value() { const FIB_LOG_SIZE: u32 = 5; let fib = Fibonacci::new(FIB_LOG_SIZE, m31!(443693538)); let mut invalid_proof = fib.prove(); - invalid_proof.opened_values.0[0][0][4] += BaseField::one(); + invalid_proof.commitment_scheme_proof.queried_values.0[0][0][4] += BaseField::one(); - verify_proof::(invalid_proof, fib.claim); + assert_eq!( + verify_proof::(invalid_proof, fib.claim), + false + ); } // TODO(AlonH): Check the correct error occurs after introducing errors instead of @@ -238,7 +216,11 @@ mod tests { let fib = Fibonacci::new(FIB_LOG_SIZE, m31!(443693538)); let mut invalid_proof = fib.prove(); - invalid_proof.trace_oods_values.swap(0, 1); + invalid_proof + .commitment_scheme_proof + .opened_values + .0 + .swap(0, 1); verify_proof::(invalid_proof, fib.claim); } @@ -252,7 +234,7 @@ mod tests { let fib = Fibonacci::new(FIB_LOG_SIZE, m31!(443693538)); let mut invalid_proof = fib.prove(); - invalid_proof.opened_values.0[0][0].pop(); + invalid_proof.commitment_scheme_proof.queried_values.0[0][0].pop(); verify_proof::(invalid_proof, fib.claim); }