From c485aef58468a317f82019cc94e22975e5c26dfa Mon Sep 17 00:00:00 2001 From: Alon Haramati <91828241+alonh5@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:13:30 +0200 Subject: [PATCH] Get opening positions from FRI. (#354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change is [Reviewable](https://reviewable.io/reviews/starkware-industries/prover-research/354) --- src/core/fri.rs | 118 +++++++++++++++++++++++++++++++++---------- src/fibonacci/mod.rs | 24 ++------- 2 files changed, 97 insertions(+), 45 deletions(-) diff --git a/src/core/fri.rs b/src/core/fri.rs index 40c7e1ce9..60ef4131c 100644 --- a/src/core/fri.rs +++ b/src/core/fri.rs @@ -3,6 +3,7 @@ use std::fmt::Debug; use std::iter::zip; use std::ops::RangeInclusive; +use itertools::Itertools; use num_traits::Zero; use thiserror::Error; @@ -14,7 +15,7 @@ use super::poly::circle::CircleEvaluation; use super::poly::line::{LineEvaluation, LinePoly}; use super::poly::BitReversedOrder; // TODO(andrew): Create fri/ directory, move queries.rs there and split this file up. -use super::queries::Queries; +use super::queries::{Queries, SparseSubCircleDomain}; use crate::commitment_scheme::hasher::Hasher; use crate::commitment_scheme::merkle_decommitment::MerkleDecommitment; use crate::commitment_scheme::merkle_tree::MerkleTree; @@ -29,7 +30,8 @@ use crate::core::utils::bit_reverse_index; pub struct FriConfig { log_blowup_factor: u32, log_last_layer_degree_bound: u32, - // TODO(andrew): Add num_queries, fold_steps. + n_queries: usize, + // TODO(andrew): fold_steps. } impl FriConfig { @@ -50,12 +52,13 @@ impl FriConfig { /// Panics if: /// * `log_last_layer_degree_bound` is greater than 10. /// * `log_blowup_factor` is equal to zero or greater than 16. - pub fn new(log_last_layer_degree_bound: u32, log_blowup_factor: u32) -> Self { + pub fn new(log_last_layer_degree_bound: u32, log_blowup_factor: u32, n_queries: usize) -> Self { assert!(Self::LOG_LAST_LAYER_DEGREE_BOUND_RANGE.contains(&log_last_layer_degree_bound)); assert!(Self::LOG_BLOWUP_FACTOR_RANGE.contains(&log_blowup_factor)); Self { log_blowup_factor, log_last_layer_degree_bound, + n_queries, } } @@ -66,14 +69,17 @@ impl FriConfig { /// A FRI prover that applies the FRI protocol to prove a set of polynomials are of low degree. pub struct FriProver { + config: FriConfig, inner_layers: Vec>, last_layer_poly: LinePoly, + /// Unique sizes of committed columns sorted in descending order. + column_log_sizes: Vec, } impl> FriProver { /// Commits to multiple [CircleEvaluation]s. /// - /// `evals` must be provided in descending order by size. + /// `columns` must be provided in descending order by size. /// /// Mixed degree STARKs involve polynomials evaluated on multiple domains of different size. /// Combining evaluations on different sized domains into an evaluation of a single polynomial @@ -84,7 +90,7 @@ impl> FriProver { /// # Panics /// /// Panics if: - /// * `evals` is empty or not sorted in ascending order by domain size. + /// * `columns` is empty or not sorted in ascending order by domain size. /// * An evaluation is not from a sufficiently low degree circle polynomial. /// * An evaluation's domain is smaller than the last layer. /// * An evaluation's domain is not a canonic circle domain. @@ -98,14 +104,23 @@ impl> FriProver { F: ExtensionOf, SecureField: ExtensionOf, { + assert!(!columns.is_empty(), "no columns"); assert!(columns.is_sorted_by_key(|e| Reverse(e.len())), "not sorted"); assert!(columns.iter().all(|e| e.domain.is_canonic()), "not canonic"); let (inner_layers, last_layer_evaluation) = Self::commit_inner_layers(channel, config, columns); let last_layer_poly = Self::commit_last_layer(channel, config, last_layer_evaluation); + + let column_log_sizes = columns + .iter() + .map(|e| e.domain.log_size()) + .dedup() + .collect(); Self { + config, inner_layers, last_layer_poly, + column_log_sizes, } } @@ -191,10 +206,25 @@ impl> FriProver { last_layer_poly } + /// Samples queries and decommits on them. Returns the FRI proof and the positions of the value + /// positions needed for verification. + pub fn decommit( + self, + channel: &mut impl Channel, + ) -> (FriProof, Vec) { + let max_column_log_size = self.column_log_sizes[0]; + let queries = Queries::generate(channel, max_column_log_size, self.config.n_queries); + let positions = get_opening_positions(&queries, &self.column_log_sizes); + let proof = self.decommit_on_queries(&queries); + (proof, positions) + } + /// # Panics /// /// Panics if the queries were sampled on the wrong domain size. - pub fn decommit(self, queries: &Queries) -> FriProof { + fn decommit_on_queries(self, queries: &Queries) -> FriProof { + let max_column_log_size = self.column_log_sizes[0]; + assert_eq!(queries.log_domain_size, max_column_log_size); let first_layer_queries = queries.fold(CIRCLE_TO_LINE_FOLD_STEP); let inner_layers = self .inner_layers @@ -405,6 +435,25 @@ impl> FriVerifier { } } +/// Returns the column opening positions needed for verification. +fn get_opening_positions( + queries: &Queries, + domain_log_sizes: &[u32], +) -> Vec { + let mut prev_log_size = domain_log_sizes[0]; + assert!(prev_log_size == queries.log_domain_size); + let mut prev_queries = queries.clone(); + let mut positions = vec![prev_queries.opening_positions(FOLD_STEP)]; + for log_size in domain_log_sizes.iter().skip(1) { + let n_folds = prev_log_size - log_size; + let queries = prev_queries.fold(n_folds); + positions.push(queries.opening_positions(FOLD_STEP)); + prev_log_size = *log_size; + prev_queries = queries; + } + positions +} + pub trait FriChannel { type Digest; @@ -919,7 +968,7 @@ mod tests { fn committing_high_degree_polynomial_fails() { const LOG_EXPECTED_BLOWUP_FACTOR: u32 = LOG_BLOWUP_FACTOR; const LOG_INVALID_BLOWUP_FACTOR: u32 = LOG_BLOWUP_FACTOR - 1; - let config = FriConfig::new(2, LOG_EXPECTED_BLOWUP_FACTOR); + let config = FriConfig::new(2, LOG_EXPECTED_BLOWUP_FACTOR, 3); let evaluation = polynomial_evaluation::(6, LOG_INVALID_BLOWUP_FACTOR); FriProver::commit(&mut test_channel(), config, &[evaluation]); @@ -932,19 +981,19 @@ mod tests { assert!(!invalid_domain.is_canonic(), "must be an invalid domain"); let evaluation = CircleEvaluation::new(invalid_domain, vec![BaseField::one(); 1 << 4]); - FriProver::commit(&mut test_channel(), FriConfig::new(2, 2), &[evaluation]); + FriProver::commit(&mut test_channel(), FriConfig::new(2, 2, 3), &[evaluation]); } #[test] fn valid_proof_passes_verification() -> Result<(), VerificationError> { const LOG_DEGREE: u32 = 3; - let config = FriConfig::new(1, LOG_BLOWUP_FACTOR); let polynomial = polynomial_evaluation::(LOG_DEGREE, LOG_BLOWUP_FACTOR); let log_domain_size = polynomial.domain.log_size(); let queries = Queries::from_positions(vec![5], log_domain_size); + let config = FriConfig::new(1, LOG_BLOWUP_FACTOR, queries.len()); let decommitment_value = query_polynomial(&polynomial, &queries); let prover = FriProver::commit(&mut test_channel(), config, &[polynomial]); - let proof = prover.decommit(&queries); + let proof = prover.decommit_on_queries(&queries); let bound = vec![CirclePolyDegreeBound::new(LOG_DEGREE)]; let verifier = FriVerifier::commit(&mut test_channel(), config, proof, bound).unwrap(); @@ -954,14 +1003,31 @@ mod tests { #[test] fn valid_mixed_degree_proof_passes_verification() -> Result<(), VerificationError> { const LOG_DEGREES: [u32; 3] = [6, 5, 4]; - let config = FriConfig::new(2, LOG_BLOWUP_FACTOR); let polynomials = LOG_DEGREES.map(|log_d| polynomial_evaluation::(log_d, LOG_BLOWUP_FACTOR)); let log_domain_size = polynomials[0].domain.log_size(); let queries = Queries::from_positions(vec![7, 70], log_domain_size); + let config = FriConfig::new(2, LOG_BLOWUP_FACTOR, queries.len()); + let prover = FriProver::commit(&mut test_channel(), config, &polynomials); + let decommitment_values = polynomials.map(|p| query_polynomial(&p, &queries)).to_vec(); + let proof = prover.decommit_on_queries(&queries); + let bounds = LOG_DEGREES.map(CirclePolyDegreeBound::new).to_vec(); + let verifier = FriVerifier::commit(&mut test_channel(), config, proof, bounds).unwrap(); + + verifier.decommit(&queries, decommitment_values) + } + + #[test] + fn valid_mixed_degree_end_to_end_proof_passes_verification() -> Result<(), VerificationError> { + const LOG_DEGREES: [u32; 3] = [6, 5, 4]; + let polynomials = + LOG_DEGREES.map(|log_d| polynomial_evaluation::(log_d, LOG_BLOWUP_FACTOR)); + let max_column_log_size = polynomials[0].domain.log_size(); + let config = FriConfig::new(2, LOG_BLOWUP_FACTOR, 3); + let queries = Queries::generate(&mut test_channel(), max_column_log_size, config.n_queries); let prover = FriProver::commit(&mut test_channel(), config, &polynomials); let decommitment_values = polynomials.map(|p| query_polynomial(&p, &queries)).to_vec(); - let proof = prover.decommit(&queries); + let (proof, _) = prover.decommit(&mut test_channel()); let bounds = LOG_DEGREES.map(CirclePolyDegreeBound::new).to_vec(); let verifier = FriVerifier::commit(&mut test_channel(), config, proof, bounds).unwrap(); @@ -971,12 +1037,12 @@ mod tests { #[test] fn proof_with_removed_layer_fails_verification() { const LOG_DEGREE: u32 = 6; - let config = FriConfig::new(2, LOG_BLOWUP_FACTOR); let polynomial = polynomial_evaluation::(6, LOG_BLOWUP_FACTOR); let log_domain_size = polynomial.domain.log_size(); let queries = Queries::from_positions(vec![1], log_domain_size); + let config = FriConfig::new(2, LOG_BLOWUP_FACTOR, queries.len()); let prover = FriProver::commit(&mut test_channel(), config, &[polynomial]); - let proof = prover.decommit(&queries); + let proof = prover.decommit_on_queries(&queries); let bound = vec![CirclePolyDegreeBound::new(LOG_DEGREE)]; // Set verifier's config to expect one extra layer than prover config. let mut invalid_config = config; @@ -993,12 +1059,12 @@ mod tests { #[test] fn proof_with_added_layer_fails_verification() { const LOG_DEGREE: u32 = 6; - let config = FriConfig::new(2, LOG_BLOWUP_FACTOR); let polynomial = polynomial_evaluation::(LOG_DEGREE, LOG_BLOWUP_FACTOR); let log_domain_size = polynomial.domain.log_size(); let queries = Queries::from_positions(vec![1], log_domain_size); + let config = FriConfig::new(2, LOG_BLOWUP_FACTOR, queries.len()); let prover = FriProver::commit(&mut test_channel(), config, &[polynomial]); - let proof = prover.decommit(&queries); + let proof = prover.decommit_on_queries(&queries); let bound = vec![CirclePolyDegreeBound::new(LOG_DEGREE)]; // Set verifier's config to expect one less layer than prover config. let mut invalid_config = config; @@ -1015,14 +1081,14 @@ mod tests { #[test] fn proof_with_invalid_inner_layer_evaluation_fails_verification() { const LOG_DEGREE: u32 = 6; - let config = FriConfig::new(2, LOG_BLOWUP_FACTOR); let polynomial = polynomial_evaluation::(LOG_DEGREE, LOG_BLOWUP_FACTOR); let log_domain_size = polynomial.domain.log_size(); let queries = Queries::from_positions(vec![5], log_domain_size); + let config = FriConfig::new(2, LOG_BLOWUP_FACTOR, queries.len()); let decommitment_value = query_polynomial(&polynomial, &queries); let prover = FriProver::commit(&mut test_channel(), config, &[polynomial]); let bound = vec![CirclePolyDegreeBound::new(LOG_DEGREE)]; - let mut proof = prover.decommit(&queries); + let mut proof = prover.decommit_on_queries(&queries); // Remove an evaluation from the second layer's proof. proof.inner_layers[1].evals_subset.pop(); let verifier = FriVerifier::commit(&mut test_channel(), config, proof, bound).unwrap(); @@ -1038,14 +1104,14 @@ mod tests { #[test] fn proof_with_invalid_inner_layer_decommitment_fails_verification() { const LOG_DEGREE: u32 = 6; - let config = FriConfig::new(2, LOG_BLOWUP_FACTOR); let polynomial = polynomial_evaluation::(LOG_DEGREE, LOG_BLOWUP_FACTOR); let log_domain_size = polynomial.domain.log_size(); let queries = Queries::from_positions(vec![5], log_domain_size); + let config = FriConfig::new(2, LOG_BLOWUP_FACTOR, queries.len()); let decommitment_value = query_polynomial(&polynomial, &queries); let prover = FriProver::commit(&mut test_channel(), config, &[polynomial]); let bound = vec![CirclePolyDegreeBound::new(LOG_DEGREE)]; - let mut proof = prover.decommit(&queries); + let mut proof = prover.decommit_on_queries(&queries); // Modify the committed values in the second layer. proof.inner_layers[1].evals_subset[0] += BaseField::one(); let verifier = FriVerifier::commit(&mut test_channel(), config, proof, bound).unwrap(); @@ -1062,13 +1128,13 @@ mod tests { fn proof_with_invalid_last_layer_degree_fails_verification() { const LOG_DEGREE: u32 = 6; const LOG_MAX_LAST_LAYER_DEGREE: u32 = 2; - let config = FriConfig::new(LOG_MAX_LAST_LAYER_DEGREE, LOG_BLOWUP_FACTOR); let polynomial = polynomial_evaluation::(LOG_DEGREE, LOG_BLOWUP_FACTOR); let log_domain_size = polynomial.domain.log_size(); let queries = Queries::from_positions(vec![1, 7, 8], log_domain_size); + let config = FriConfig::new(LOG_MAX_LAST_LAYER_DEGREE, LOG_BLOWUP_FACTOR, queries.len()); let prover = FriProver::commit(&mut test_channel(), config, &[polynomial]); let bound = vec![CirclePolyDegreeBound::new(LOG_DEGREE)]; - let mut proof = prover.decommit(&queries); + let mut proof = prover.decommit_on_queries(&queries); let bad_last_layer_coeffs = vec![One::one(); 1 << (LOG_MAX_LAST_LAYER_DEGREE + 1)]; proof.last_layer_poly = LinePoly::new(bad_last_layer_coeffs); @@ -1083,14 +1149,14 @@ mod tests { #[test] fn proof_with_invalid_last_layer_fails_verification() { const LOG_DEGREE: u32 = 6; - let config = FriConfig::new(2, LOG_BLOWUP_FACTOR); let polynomial = polynomial_evaluation::(LOG_DEGREE, LOG_BLOWUP_FACTOR); let log_domain_size = polynomial.domain.log_size(); let queries = Queries::from_positions(vec![1, 7, 8], log_domain_size); + let config = FriConfig::new(2, LOG_BLOWUP_FACTOR, queries.len()); let decommitment_value = query_polynomial(&polynomial, &queries); let prover = FriProver::commit(&mut test_channel(), config, &[polynomial]); let bound = vec![CirclePolyDegreeBound::new(LOG_DEGREE)]; - let mut proof = prover.decommit(&queries); + let mut proof = prover.decommit_on_queries(&queries); // Compromise the last layer polynomial's first coefficient. proof.last_layer_poly[0] += BaseField::one(); let verifier = FriVerifier::commit(&mut test_channel(), config, proof, bound).unwrap(); @@ -1107,13 +1173,13 @@ mod tests { #[should_panic] fn decommit_queries_on_invalid_domain_fails_verification() { const LOG_DEGREE: u32 = 3; - let config = FriConfig::new(1, LOG_BLOWUP_FACTOR); let polynomial = polynomial_evaluation::(LOG_DEGREE, LOG_BLOWUP_FACTOR); let log_domain_size = polynomial.domain.log_size(); let queries = Queries::from_positions(vec![5], log_domain_size); + let config = FriConfig::new(1, LOG_BLOWUP_FACTOR, queries.len()); let decommitment_value = query_polynomial(&polynomial, &queries); let prover = FriProver::commit(&mut test_channel(), config, &[polynomial]); - let proof = prover.decommit(&queries); + let proof = prover.decommit_on_queries(&queries); let bound = vec![CirclePolyDegreeBound::new(LOG_DEGREE)]; let verifier = FriVerifier::commit(&mut test_channel(), config, proof, bound).unwrap(); // Simulate the verifier sampling queries on a smaller domain. diff --git a/src/fibonacci/mod.rs b/src/fibonacci/mod.rs index 33d85fed9..6a437eeca 100644 --- a/src/fibonacci/mod.rs +++ b/src/fibonacci/mod.rs @@ -159,27 +159,14 @@ impl Fibonacci { ); } - let fri_config = FriConfig::new(LOG_LAST_LAYER_DEGREE_BOUND, LOG_BLOWUP_FACTOR); + 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); let proof_of_work = ProofOfWork::new(PROOF_OF_WORK_BITS).prove(channel); - // TODO(AlonH): Get opening positions from FRI. - let composition_polynomial_queries = Queries::generate( - channel, - self.composition_polynomial_commitment_domain.log_size(), - N_QUERIES, - ); - let trace_queries = composition_polynomial_queries.fold( - self.composition_polynomial_commitment_domain.log_size() - - self.trace_commitment_domain.log_size(), - ); - let fri_proof = fri_prover.decommit(&composition_polynomial_queries); + let (fri_proof, fri_opening_positions) = fri_prover.decommit(channel); - const FRI_STEP_SIZE: u32 = 1; - let composition_polynomial_decommitment_positions = composition_polynomial_queries - .opening_positions(FRI_STEP_SIZE) - .flatten(); - let trace_decommitment_positions = trace_queries.opening_positions(FRI_STEP_SIZE).flatten(); + let composition_polynomial_decommitment_positions = fri_opening_positions[0].flatten(); + let trace_decommitment_positions = fri_opening_positions[1].flatten(); // Decommit and get the values in the opening positions. let composition_polynomial_opened_values = composition_polynomial_decommitment_positions @@ -244,13 +231,12 @@ pub fn verify_proof(proof: FibonacciProof) -> bool { ); let composition_polynomial_oods_value = evaluation_accumulator.finalize(); - let fri_config = FriConfig::new(LOG_LAST_LAYER_DEGREE_BOUND, LOG_BLOWUP_FACTOR); - // TODO(AlonH): Get bounds from air. let mut bounds = vec![CirclePolyDegreeBound::new( fib.component.max_constraint_log_degree_bound(), )]; bounds.append(&mut quotient_log_bounds(fib.component)); + let fri_config = FriConfig::new(LOG_LAST_LAYER_DEGREE_BOUND, LOG_BLOWUP_FACTOR, N_QUERIES); let fri_verifier = FriVerifier::commit(channel, fri_config, proof.fri_proof, bounds).unwrap(); ProofOfWork::new(PROOF_OF_WORK_BITS).verify(channel, &proof.proof_of_work);