From fc7ccc62491d9c3531b21e5f79efc47466b42e7b Mon Sep 17 00:00:00 2001 From: Shahar Papini Date: Fri, 15 Mar 2024 09:42:15 +0200 Subject: [PATCH] Commitment Scheme evaluation per size --- src/core/commitment_scheme/prover.rs | 56 ++++---- src/core/commitment_scheme/quotients.rs | 165 +++++++++++++++++++++++- src/core/commitment_scheme/verifier.rs | 92 +++++-------- src/core/fields/secure_column.rs | 5 + src/core/fri.rs | 10 ++ src/core/oods.rs | 48 ------- src/core/poly/circle/mod.rs | 2 +- src/core/poly/circle/secure_poly.rs | 22 +++- 8 files changed, 258 insertions(+), 142 deletions(-) diff --git a/src/core/commitment_scheme/prover.rs b/src/core/commitment_scheme/prover.rs index fd5a0cfcc..43ebe8399 100644 --- a/src/core/commitment_scheme/prover.rs +++ b/src/core/commitment_scheme/prover.rs @@ -5,7 +5,6 @@ //! 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. -use std::iter::zip; use std::ops::Deref; use itertools::Itertools; @@ -17,7 +16,6 @@ use super::super::circle::CirclePoint; use super::super::fields::m31::BaseField; use super::super::fields::qm31::SecureField; use super::super::fri::{FriConfig, FriProof, FriProver}; -use super::super::oods::get_pair_oods_quotient; use super::super::poly::circle::CanonicCoset; use super::super::poly::BitReversedOrder; use super::super::proof_of_work::{ProofOfWork, ProofOfWorkProof}; @@ -25,12 +23,14 @@ use super::super::prover::{ LOG_BLOWUP_FACTOR, LOG_LAST_LAYER_DEGREE_BOUND, N_QUERIES, PROOF_OF_WORK_BITS, }; use super::super::ColumnVec; +use super::quotients::{compute_fri_quotients, PointSample}; use super::utils::TreeVec; use crate::commitment_scheme::blake2_hash::{Blake2sHash, Blake2sHasher}; use crate::commitment_scheme::merkle_input::{MerkleTreeColumnLayout, MerkleTreeInput}; use crate::commitment_scheme::mixed_degree_decommitment::MixedDecommitment; use crate::commitment_scheme::mixed_degree_merkle_tree::MixedDegreeMerkleTree; use crate::core::channel::Channel; +use crate::core::poly::circle::SecureEvaluation; type MerkleHasher = Blake2sHasher; type ProofChannel = Blake2sChannel; @@ -76,39 +76,39 @@ impl CommitmentSchemeProver { channel: &mut ProofChannel, ) -> CommitmentSchemeProof { // Evaluate polynomials on open points. - let proved_values = - self.polynomials() - .zip_cols(&prove_points) - .map_cols(|(poly, points)| { - points - .iter() - .map(|point| poly.eval_at_point(*point)) - .collect_vec() - }); - channel.mix_felts(&proved_values.clone().flatten_cols()); - - // Compute oods quotients for boundary constraints on prove_points. - let quotients = self - .evaluations() - .zip_cols(&proved_values) + let openings = self + .polynomials() .zip_cols(&prove_points) - .map_cols(|((evaluation, values), points)| { - zip(points, values) - .map(|(&point, &value)| { - get_pair_oods_quotient(point, value, evaluation).bit_reverse() + .map_cols(|(poly, points)| { + points + .iter() + .map(|&point| PointSample { + point, + value: poly.eval_at_point(point), }) .collect_vec() }); + let proved_values = openings + .as_cols_ref() + .map_cols(|x| x.iter().map(|o| o.value).collect()); + channel.mix_felts(&proved_values.clone().flatten_cols()); + + // Compute oods quotients for boundary constraints on prove_points. + let columns = self.evaluations().flatten(); + let quotients = + compute_fri_quotients(&columns[..], &openings.flatten(), channel.draw_felt()); + + // TODO(spapini): Conversion to CircleEvaluation can be removed when FRI supports + // SecureColumn. + let quotients = quotients + .into_iter() + .map(SecureEvaluation::to_cpu) + .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_cols_rev(), - ); + let fri_prover = + FriProver::::commit(channel, fri_config, "ients); // Proof of work. let proof_of_work = ProofOfWork::new(PROOF_OF_WORK_BITS).prove(channel); diff --git a/src/core/commitment_scheme/quotients.rs b/src/core/commitment_scheme/quotients.rs index a0792de6c..cb368bce0 100644 --- a/src/core/commitment_scheme/quotients.rs +++ b/src/core/commitment_scheme/quotients.rs @@ -1,10 +1,20 @@ +use std::cmp::Reverse; +use std::collections::BTreeMap; + +use itertools::{izip, multiunzip, Itertools}; + +use crate::core::backend::cpu::quotients::accumulate_row_quotients; use crate::core::backend::Backend; use crate::core::circle::CirclePoint; use crate::core::fields::m31::BaseField; use crate::core::fields::qm31::SecureField; use crate::core::fields::secure_column::SecureColumn; -use crate::core::poly::circle::{CircleDomain, CircleEvaluation}; +use crate::core::fri::SparseCircleEvaluation; +use crate::core::poly::circle::{CanonicCoset, CircleDomain, CircleEvaluation, SecureEvaluation}; use crate::core::poly::BitReversedOrder; +use crate::core::prover::VerificationError; +use crate::core::queries::SparseSubCircleDomain; +use crate::core::utils::bit_reverse_index; pub trait QuotientOps: Backend { /// Accumulates the quotients of the columns at the given domain. @@ -28,3 +38,156 @@ pub struct ColumnSampleBatch { /// The sampled column indices and their values at the point. pub column_indices_and_values: Vec<(usize, SecureField)>, } +impl ColumnSampleBatch { + /// Groups column opening by opening point. + /// # Arguments + /// opening: For each column, a vector of openings. + pub fn new(openings: &[&Vec]) -> Vec { + openings + .iter() + .enumerate() + .flat_map(|(column_index, openings)| { + openings.iter().map(move |opening| (column_index, opening)) + }) + .group_by(|(_, opening)| opening.point) + .into_iter() + .map(|(point, column_openings)| Self { + point, + column_indices_and_values: column_openings + .map(|(column_index, opening)| (column_index, opening.value)) + .collect(), + }) + .collect() + } +} + +pub struct PointSample { + pub point: CirclePoint, + pub value: SecureField, +} + +pub fn compute_fri_quotients( + columns: &[&CircleEvaluation], + samples: &[Vec], + random_coeff: SecureField, +) -> Vec> { + izip!(columns, samples) + .group_by(|(c, _)| c.domain.log_size()) + .into_iter() + .sorted_by_key(|(log_size, _)| Reverse(*log_size)) + .map(|(log_size, tuples)| { + let (columns, openings): (Vec<_>, Vec<_>) = multiunzip(tuples); + let domain = CanonicCoset::new(log_size).circle_domain(); + // TODO: slice. + let batched_openings = ColumnSampleBatch::new(&openings); + let values = B::accumulate_quotients(domain, &columns, random_coeff, &batched_openings); + SecureEvaluation { domain, values } + }) + .collect() +} + +pub fn fri_answers( + column_log_sizes: Vec, + openings: &[Vec], + random_coeff: SecureField, + query_domain_per_log_size: BTreeMap, + queried_values_per_column: &[Vec], +) -> Result>, VerificationError> { + izip!(column_log_sizes, openings, queried_values_per_column) + .group_by(|(c, ..)| *c) + .into_iter() + .sorted_by_key(|(log_size, _)| Reverse(*log_size)) + .map(|(log_size, tuples)| { + let (_, openings, queried_valued_per_column): (Vec<_>, Vec<_>, Vec<_>) = + multiunzip(tuples); + fri_answers_for_log_size( + log_size, + &openings, + random_coeff, + &query_domain_per_log_size[&log_size], + &queried_valued_per_column, + ) + }) + .collect() +} + +pub fn fri_answers_for_log_size( + log_size: u32, + openings: &[&Vec], + random_coeff: SecureField, + query_domain: &SparseSubCircleDomain, + queried_values_per_column: &[&Vec], +) -> Result, VerificationError> { + let commitment_domain = CanonicCoset::new(log_size).circle_domain(); + let batched_openings = ColumnSampleBatch::new(openings); + for x in queried_values_per_column { + if x.len() != query_domain.flatten().len() { + return Err(VerificationError::InvalidStructure); + } + } + let mut queried_values_per_column = queried_values_per_column + .iter() + .map(|q| q.iter()) + .collect_vec(); + + let res = SparseCircleEvaluation::new( + query_domain + .iter() + .map(|subdomain| { + let domain = subdomain.to_circle_domain(&commitment_domain); + let column_evals = queried_values_per_column + .iter_mut() + .map(|q| { + CircleEvaluation::new(domain, q.take(domain.size()).copied().collect_vec()) + }) + .collect_vec(); + // TODO(spapini): bit reverse iterator. + let values = (0..domain.size()) + .map(|row| { + let domain_point = domain.at(bit_reverse_index(row, log_size)); + accumulate_row_quotients( + &batched_openings, + &column_evals.iter().collect_vec(), + row, + random_coeff, + domain_point, + ) + }) + .collect(); + CircleEvaluation::new(domain, values) + }) + .collect(), + ); + if !queried_values_per_column.iter().all(|x| x.is_empty()) { + return Err(VerificationError::InvalidStructure); + } + Ok(res) +} + +#[cfg(test)] +mod tests { + use crate::core::backend::cpu::{CPUCircleEvaluation, CPUCirclePoly}; + use crate::core::circle::SECURE_FIELD_CIRCLE_GEN; + use crate::core::commitment_scheme::quotients::{compute_fri_quotients, PointSample}; + use crate::core::poly::circle::CanonicCoset; + use crate::{m31, qm31}; + + #[test] + fn test_quotients_are_low_degree() { + const LOG_SIZE: u32 = 7; + let polynomial = CPUCirclePoly::new((0..1 << LOG_SIZE).map(|i| m31!(i)).collect()); + let eval_domain = CanonicCoset::new(LOG_SIZE + 1).circle_domain(); + let eval = polynomial.evaluate(eval_domain); + let point = SECURE_FIELD_CIRCLE_GEN; + let value = polynomial.eval_at_point(point); + let coeff = qm31!(1, 2, 3, 4); + let quot_eval = + compute_fri_quotients(&[&eval], &[vec![PointSample { point, value }]], coeff) + .pop() + .unwrap(); + let quot_poly_base_field = + CPUCircleEvaluation::new(eval_domain, quot_eval.values.columns[0].clone()) + .interpolate(); + assert!(quot_poly_base_field.is_in_fft_space(LOG_SIZE)); + } +} diff --git a/src/core/commitment_scheme/verifier.rs b/src/core/commitment_scheme/verifier.rs index 416a1b58e..63e4158d8 100644 --- a/src/core/commitment_scheme/verifier.rs +++ b/src/core/commitment_scheme/verifier.rs @@ -6,14 +6,12 @@ use super::super::channel::Blake2sChannel; use super::super::circle::CirclePoint; use super::super::fields::m31::BaseField; use super::super::fields::qm31::SecureField; -use super::super::fri::{CirclePolyDegreeBound, FriConfig, FriVerifier, SparseCircleEvaluation}; -use super::super::oods::get_pair_oods_quotient; -use super::super::poly::circle::{CanonicCoset, CircleDomain, CircleEvaluation}; +use super::super::fri::{CirclePolyDegreeBound, FriConfig, FriVerifier}; use super::super::proof_of_work::ProofOfWork; use super::super::prover::{ LOG_BLOWUP_FACTOR, LOG_LAST_LAYER_DEGREE_BOUND, N_QUERIES, PROOF_OF_WORK_BITS, }; -use super::super::queries::SparseSubCircleDomain; +use super::quotients::{fri_answers, PointSample}; use super::utils::TreeVec; use super::CommitmentSchemeProof; use crate::commitment_scheme::blake2_hash::{Blake2sHash, Blake2sHasher}; @@ -58,15 +56,20 @@ impl CommitmentSchemeVerifier { channel: &mut ProofChannel, ) -> Result<(), VerificationError> { channel.mix_felts(&proof.proved_values.clone().flatten_cols()); + let random_coeff = channel.draw_felt(); - // Compute degree bounds for OODS quotients without looking at the proof. let bounds = self .column_log_sizes() .zip_cols(&prove_points) .map_cols(|(log_size, prove_points)| { vec![CirclePolyDegreeBound::new(log_size); prove_points.len()] }) - .flatten_cols_rev(); + .flatten_cols() + .into_iter() + .sorted() + .rev() + .dedup() + .collect_vec(); // FRI commitment phase on OODS quotients. let fri_config = FriConfig::new(LOG_LAST_LAYER_DEGREE_BOUND, LOG_BLOWUP_FACTOR, N_QUERIES); @@ -106,69 +109,34 @@ impl CommitmentSchemeVerifier { } // Answer FRI queries. - let mut fri_answers = self - .column_log_sizes() + let openings = prove_points .zip_cols(proof.proved_values) - .zip_cols(prove_points) - .zip_cols(proof.queried_values) - .map_cols( - // For each column. - |(((log_size, proved_values), opened_points), queried_values)| { - zip(opened_points, proved_values) - .map(|(point, value)| { - // For each opening point of that column. - eval_quotients_on_sparse_domain( - queried_values.clone(), - &fri_query_domains[&(log_size + LOG_BLOWUP_FACTOR)], - CanonicCoset::new(log_size + LOG_BLOWUP_FACTOR).circle_domain(), - point, - value, - ) - }) - .collect_vec() - }, - ) - .flatten_cols() - .into_iter() - .collect::, _>>()?; + .map_cols(|(prove_points, proved_values)| { + zip(prove_points, proved_values) + .map(|(point, value)| PointSample { point, value }) + .collect_vec() + }) + .flatten(); + + // TODO(spapini): Properly defined column log size and dinstinguish between poly and + // commitment. + let fri_answers = fri_answers( + self.column_log_sizes() + .flatten() + .into_iter() + .map(|x| x + LOG_BLOWUP_FACTOR) + .collect(), + &openings, + random_coeff, + fri_query_domains, + &proof.queried_values.flatten(), + )?; - // TODO(spapini): Remove reverse. - fri_answers.reverse(); fri_verifier.decommit(fri_answers)?; Ok(()) } } -/// Evaluates the oods quotients on the sparse domain. -fn eval_quotients_on_sparse_domain( - queried_values: Vec, - query_domains: &SparseSubCircleDomain, - commitment_domain: CircleDomain, - point: CirclePoint, - value: SecureField, -) -> Result, VerificationError> { - let queried_values = &mut queried_values.into_iter(); - let res = SparseCircleEvaluation::new( - query_domains - .iter() - .map(|subdomain| { - let values = queried_values.take(1 << subdomain.log_size).collect_vec(); - if values.len() != 1 << subdomain.log_size { - return Err(VerificationError::InvalidStructure); - } - let subeval = - CircleEvaluation::new(subdomain.to_circle_domain(&commitment_domain), values); - Ok(get_pair_oods_quotient(point, value, &subeval).bit_reverse()) - }) - .collect::>()?, - ); - assert!( - queried_values.is_empty(), - "Not all queried values were used" - ); - Ok(res) -} - /// Verifier data for a single commitment tree in a commitment scheme. pub struct CommitmentTreeVerifier { pub commitment: Blake2sHash, diff --git a/src/core/fields/secure_column.rs b/src/core/fields/secure_column.rs index 99b6ffdb9..831891b53 100644 --- a/src/core/fields/secure_column.rs +++ b/src/core/fields/secure_column.rs @@ -23,6 +23,11 @@ impl SecureColumn { .map(|c| &mut c[index]) .assign(value.to_m31_array()); } + + // TODO(spapini): Remove when we no longer use CircleEvaluation. + pub fn to_cpu(&self) -> Vec { + (0..self.len()).map(|i| self.at(i)).collect() + } } impl SecureColumn { pub fn zeros(len: usize) -> Self { diff --git a/src/core/fri.rs b/src/core/fri.rs index 15422f5a4..5c9642c85 100644 --- a/src/core/fri.rs +++ b/src/core/fri.rs @@ -422,6 +422,7 @@ impl> FriVerifier { { assert_eq!(queries.log_domain_size, self.expected_query_log_domain_size); assert_eq!(decommitted_values.len(), self.column_bounds.len()); + println!("decommitted_values: {decommitted_values:#?}"); let (last_layer_queries, last_layer_query_evals) = self.decommit_inner_layers(queries, decommitted_values)?; @@ -881,6 +882,15 @@ impl> SparseCircleEvaluation { } } +impl<'a, F: ExtensionOf> IntoIterator for &'a mut SparseCircleEvaluation { + type Item = &'a mut CircleEvaluation; + type IntoIter = std::slice::IterMut<'a, CircleEvaluation>; + + fn into_iter(self) -> Self::IntoIter { + self.subcircle_evals.iter_mut() + } +} + /// Holds a small foldable subset of univariate SecureField polynomial evaluations. /// Evaluation is held at the CPU backend. #[derive(Debug, Clone)] diff --git a/src/core/oods.rs b/src/core/oods.rs index 61b6ad8ed..129dface6 100644 --- a/src/core/oods.rs +++ b/src/core/oods.rs @@ -54,51 +54,3 @@ pub fn get_oods_quotient( } CircleEvaluation::new(eval.domain, values) } - -/// Returns the pair OODS quotient (i.e quotienting out both the oods point and its complex -/// conjugate) polynomial evaluation over the whole domain. Used in case we don't want the highest -/// monomial of the resulting quotient polynomial to increase which might take it out of the fft -/// space. -pub fn get_pair_oods_quotient( - oods_point: CirclePoint, - oods_value: SecureField, - eval: &CPUCircleEvaluation, -) -> CPUCircleEvaluation { - let mut values = Vec::with_capacity(eval.domain.size()); - for (i, point) in enumerate(eval.domain.iter()) { - let index = bit_reverse_index(i, eval.domain.log_size()); - values.push(eval_pair_oods_quotient_at_point( - point, - eval.values[index], - oods_point, - oods_value, - )); - } - CircleEvaluation::new(eval.domain, values) -} - -#[cfg(test)] -mod tests { - use crate::core::backend::cpu::{CPUCircleEvaluation, CPUCirclePoly}; - use crate::core::circle::SECURE_FIELD_CIRCLE_GEN; - use crate::core::oods::get_pair_oods_quotient; - use crate::core::poly::circle::CanonicCoset; - use crate::m31; - - #[test] - fn test_oods_quotients_are_low_degree() { - const LOG_SIZE: u32 = 7; - let polynomial = CPUCirclePoly::new((0..1 << LOG_SIZE).map(|i| m31!(i)).collect()); - let eval_domain = CanonicCoset::new(LOG_SIZE + 1).circle_domain(); - let eval = polynomial.evaluate(eval_domain); - let oods_point = SECURE_FIELD_CIRCLE_GEN; - let oods_value = polynomial.eval_at_point(oods_point); - let quot_eval = get_pair_oods_quotient(oods_point, oods_value, &eval).bit_reverse(); - let quot_eval_base_field = CPUCircleEvaluation::new( - eval_domain, - quot_eval.values.iter().map(|v| v.0 .0).collect(), - ); - let quot_poly_base_field = quot_eval_base_field.interpolate(); - assert!(quot_poly_base_field.is_in_fft_space(LOG_SIZE)); - } -} diff --git a/src/core/poly/circle/mod.rs b/src/core/poly/circle/mod.rs index 903a1dcc5..76973d3d1 100644 --- a/src/core/poly/circle/mod.rs +++ b/src/core/poly/circle/mod.rs @@ -10,7 +10,7 @@ pub use domain::{CircleDomain, MAX_CIRCLE_DOMAIN_LOG_SIZE}; pub use evaluation::{CircleEvaluation, CosetSubEvaluation}; pub use ops::PolyOps; pub use poly::CirclePoly; -pub use secure_poly::SecureCirclePoly; +pub use secure_poly::{SecureCirclePoly, SecureEvaluation}; #[cfg(test)] mod tests { diff --git a/src/core/poly/circle/secure_poly.rs b/src/core/poly/circle/secure_poly.rs index c2783afb2..b019dfbdb 100644 --- a/src/core/poly/circle/secure_poly.rs +++ b/src/core/poly/circle/secure_poly.rs @@ -1,9 +1,12 @@ use std::ops::Deref; -use crate::core::backend::cpu::CPUCirclePoly; +use super::CircleDomain; +use crate::core::backend::cpu::{CPUCircleEvaluation, CPUCirclePoly}; +use crate::core::backend::{Backend, CPUBackend}; use crate::core::circle::CirclePoint; use crate::core::fields::qm31::SecureField; -use crate::core::fields::secure_column::SECURE_EXTENSION_DEGREE; +use crate::core::fields::secure_column::{SecureColumn, SECURE_EXTENSION_DEGREE}; +use crate::core::poly::BitReversedOrder; pub struct SecureCirclePoly(pub [CPUCirclePoly; SECURE_EXTENSION_DEGREE]); @@ -24,6 +27,10 @@ impl SecureCirclePoly { ] } + pub fn log_size(&self) -> u32 { + self[0].log_size() + } + /// Evaluates the polynomial at a point, given evaluations of its composing base field /// polynomials at that point. pub fn eval_from_partial_evals(evals: [SecureField; SECURE_EXTENSION_DEGREE]) -> SecureField { @@ -42,3 +49,14 @@ impl Deref for SecureCirclePoly { &self.0 } } + +pub struct SecureEvaluation { + pub domain: CircleDomain, + pub values: SecureColumn, +} +impl SecureEvaluation { + // TODO(spapini): Remove when we no longer use CircleEvaluation. + pub fn to_cpu(self) -> CPUCircleEvaluation { + CPUCircleEvaluation::new(self.domain, self.values.to_cpu()) + } +}