diff --git a/src/fri/commitment.rs b/src/fri/commitment.rs index c704c33c..738d7b8f 100644 --- a/src/fri/commitment.rs +++ b/src/fri/commitment.rs @@ -3,7 +3,7 @@ use rayon::prelude::*; use crate::field::extension_field::Extendable; use crate::field::fft::FftRootTable; use crate::field::field_types::{Field, RichField}; -use crate::fri::proof::FriProof; +use crate::fri::proof::DecompressedFriProof; use crate::fri::prover::fri_proof; use crate::hash::merkle_tree::MerkleTree; use crate::iop::challenger::Challenger; @@ -128,7 +128,7 @@ impl PolynomialBatchCommitment { challenger: &mut Challenger, common_data: &CommonCircuitData, timing: &mut TimingTree, - ) -> (FriProof, OpeningSet) + ) -> (DecompressedFriProof, OpeningSet) where F: RichField + Extendable, { diff --git a/src/fri/proof.rs b/src/fri/proof.rs index 5fc6a9a8..8b6f98fe 100644 --- a/src/fri/proof.rs +++ b/src/fri/proof.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use itertools::izip; use serde::{Deserialize, Serialize}; @@ -77,9 +79,24 @@ pub struct FriQueryRoundTarget { pub steps: Vec>, } +/// Compressed proofs for FRI query rounds. #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[serde(bound = "")] -pub struct FriProof, const D: usize> { +pub struct CompressedFriQueryRounds, const D: usize> { + pub initial_trees_proofs: HashMap>, + pub steps: Vec>>, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(bound = "")] +pub enum FriProof, const D: usize> { + Decompressed(DecompressedFriProof), + Compressed(CompressedFriProof), +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(bound = "")] +pub struct DecompressedFriProof, const D: usize> { /// A Merkle cap for each reduced polynomial in the commit phase. pub commit_phase_merkle_caps: Vec>, /// Query rounds proofs @@ -88,10 +105,9 @@ pub struct FriProof, const D: usize> { pub final_poly: PolynomialCoeffs, /// Witness showing that the prover did PoW. pub pow_witness: F, - /// Flag set to true if path compression has been applied to the proof's Merkle proofs. - pub is_compressed: bool, } +/// Corresponds to `DecompressedFriProof`. pub struct FriProofTarget { pub commit_phase_merkle_caps: Vec, pub query_round_proofs: Vec>, @@ -99,15 +115,30 @@ pub struct FriProofTarget { pub pow_witness: Target, } -impl, const D: usize> FriProof { - /// Compress all the Merkle paths in the FRI proof. - pub fn compress(self, indices: &[usize], common_data: &CommonCircuitData) -> Self { - if self.is_compressed { - panic!("Proof is already compressed."); - } - let FriProof { +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(bound = "")] +pub struct CompressedFriProof, const D: usize> { + /// A Merkle cap for each reduced polynomial in the commit phase. + pub commit_phase_merkle_caps: Vec>, + /// Query rounds proofs + pub query_round_proofs: CompressedFriQueryRounds, + /// The final polynomial in coefficient form. + pub final_poly: PolynomialCoeffs, + /// Witness showing that the prover did PoW. + pub pow_witness: F, +} + +impl, const D: usize> DecompressedFriProof { + /// Compress all the Merkle paths in the FRI proof and remove duplicate indices. + pub fn compress( + self, + indices: &[usize], + common_data: &CommonCircuitData, + with_indices: bool, + ) -> CompressedFriProof { + let DecompressedFriProof { commit_phase_merkle_caps, - mut query_round_proofs, + query_round_proofs, final_poly, pow_witness, .. @@ -157,9 +188,14 @@ impl, const D: usize> FriProof { .map(|(is, ps)| compress_merkle_proofs(cap_height, is, &ps)) .collect::>(); + let mut compressed_query_proofs = CompressedFriQueryRounds { + initial_trees_proofs: HashMap::new(), + steps: vec![HashMap::new(); num_reductions], + }; + // Replace the query round proofs with the compressed versions. - for (i, qrp) in query_round_proofs.iter_mut().enumerate() { - qrp.initial_trees_proof = FriInitialTreeProof { + for (i, (mut index, qrp)) in indices.iter().cloned().zip(&query_round_proofs).enumerate() { + let initial_proof = FriInitialTreeProof { evals_proofs: (0..num_initial_trees) .map(|j| { ( @@ -169,31 +205,38 @@ impl, const D: usize> FriProof { }) .collect(), }; - qrp.steps = (0..num_reductions) - .map(|j| FriQueryStep { + compressed_query_proofs + .initial_trees_proofs + .insert(index, initial_proof); + for j in 0..num_reductions { + index >>= reduction_arity_bits[i]; + let query_step = FriQueryStep { evals: steps_evals[j][i].clone(), merkle_proof: steps_proofs[j][i].clone(), - }) - .collect(); + }; + compressed_query_proofs.steps[j].insert(index, query_step); + } } - FriProof { + CompressedFriProof { commit_phase_merkle_caps, - query_round_proofs, + query_round_proofs: compressed_query_proofs, final_poly, pow_witness, - is_compressed: true, } } +} - /// Decompress all the Merkle paths in the FRI proof. - pub fn decompress(self, indices: &[usize], common_data: &CommonCircuitData) -> Self { - if !self.is_compressed { - panic!("Proof is not compressed."); - } - let FriProof { +impl, const D: usize> CompressedFriProof { + /// Decompress all the Merkle paths in the FRI proof and add duplicate indices. + pub fn decompress( + self, + indices: &[usize], + common_data: &CommonCircuitData, + ) -> DecompressedFriProof { + let CompressedFriProof { commit_phase_merkle_caps, - mut query_round_proofs, + query_round_proofs, final_poly, pow_witness, .. @@ -201,7 +244,13 @@ impl, const D: usize> FriProof { let cap_height = common_data.config.cap_height; let reduction_arity_bits = &common_data.config.fri_config.reduction_arity_bits; let num_reductions = reduction_arity_bits.len(); - let num_initial_trees = query_round_proofs[0].initial_trees_proof.evals_proofs.len(); + let num_initial_trees = query_round_proofs + .initial_trees_proofs + .values() + .next() + .unwrap() + .evals_proofs + .len(); // "Transpose" the query round proofs, so that information for each Merkle tree is collected together. let mut initial_trees_indices = vec![vec![]; num_initial_trees]; @@ -219,11 +268,8 @@ impl, const D: usize> FriProof { }) .collect::>(); - for (mut index, qrp) in indices.iter().cloned().zip(&query_round_proofs) { - let FriQueryRound { - initial_trees_proof, - steps, - } = qrp.clone(); + for mut index in indices.iter().cloned() { + let initial_trees_proof = query_round_proofs.initial_trees_proofs[&index].clone(); for (i, (leaves_data, proof)) in initial_trees_proof.evals_proofs.into_iter().enumerate() { @@ -231,8 +277,9 @@ impl, const D: usize> FriProof { initial_trees_leaves[i].push(leaves_data); initial_trees_proofs[i].push(proof); } - for (i, query_step) in steps.into_iter().enumerate() { + for i in 0..num_reductions { index >>= reduction_arity_bits[i]; + let query_step = query_round_proofs.steps[i][&index].clone(); steps_indices[i].push(index); steps_evals[i].push(flatten(&query_step.evals)); steps_proofs[i].push(query_step.merkle_proof); @@ -251,9 +298,10 @@ impl, const D: usize> FriProof { .map(|(ls, is, ps, h)| decompress_merkle_proofs(ls, is, &ps, h, cap_height)) .collect::>(); + let mut decompressed_query_proofs = Vec::with_capacity(num_reductions); // Replace the query round proofs with the decompressed versions. - for (i, qrp) in query_round_proofs.iter_mut().enumerate() { - qrp.initial_trees_proof = FriInitialTreeProof { + for i in 0..num_reductions { + let initial_trees_proof = FriInitialTreeProof { evals_proofs: (0..num_initial_trees) .map(|j| { ( @@ -263,20 +311,23 @@ impl, const D: usize> FriProof { }) .collect(), }; - qrp.steps = (0..num_reductions) + let steps = (0..num_reductions) .map(|j| FriQueryStep { evals: unflatten(&steps_evals[j][i]), merkle_proof: steps_proofs[j][i].clone(), }) .collect(); + decompressed_query_proofs.push(FriQueryRound { + initial_trees_proof, + steps, + }) } - FriProof { + DecompressedFriProof { commit_phase_merkle_caps, - query_round_proofs, + query_round_proofs: decompressed_query_proofs, final_poly, pow_witness, - is_compressed: false, } } } diff --git a/src/fri/prover.rs b/src/fri/prover.rs index a2534e3f..7bbff557 100644 --- a/src/fri/prover.rs +++ b/src/fri/prover.rs @@ -2,7 +2,7 @@ use rayon::prelude::*; use crate::field::extension_field::{flatten, unflatten, Extendable}; use crate::field::field_types::RichField; -use crate::fri::proof::{FriInitialTreeProof, FriProof, FriQueryRound, FriQueryStep}; +use crate::fri::proof::{DecompressedFriProof, FriInitialTreeProof, FriQueryRound, FriQueryStep}; use crate::fri::FriConfig; use crate::hash::hash_types::HashOut; use crate::hash::hashing::hash_n_to_1; @@ -25,7 +25,7 @@ pub fn fri_proof, const D: usize>( challenger: &mut Challenger, config: &CircuitConfig, timing: &mut TimingTree, -) -> FriProof { +) -> DecompressedFriProof { let n = lde_polynomial_values.values.len(); assert_eq!(lde_polynomial_coeffs.coeffs.len(), n); @@ -58,12 +58,11 @@ pub fn fri_proof, const D: usize>( &config.fri_config, ); - FriProof { + DecompressedFriProof { commit_phase_merkle_caps: trees.iter().map(|t| t.cap.clone()).collect(), query_round_proofs, final_poly: final_coeffs, pow_witness, - is_compressed: false, } } diff --git a/src/fri/verifier.rs b/src/fri/verifier.rs index 6662830a..914dd34e 100644 --- a/src/fri/verifier.rs +++ b/src/fri/verifier.rs @@ -3,7 +3,7 @@ use anyhow::{ensure, Result}; use crate::field::extension_field::{flatten, Extendable, FieldExtension}; use crate::field::field_types::{Field, RichField}; use crate::field::interpolation::{barycentric_weights, interpolate, interpolate2}; -use crate::fri::proof::{FriInitialTreeProof, FriProof, FriQueryRound}; +use crate::fri::proof::{DecompressedFriProof, FriInitialTreeProof, FriQueryRound}; use crate::fri::FriConfig; use crate::hash::merkle_proofs::verify_merkle_proof; use crate::hash::merkle_tree::MerkleCap; @@ -60,7 +60,7 @@ pub(crate) fn verify_fri_proof, const D: usize>( os: &OpeningSet, challenges: &ProofChallenges, initial_merkle_caps: &[MerkleCap], - proof: &FriProof, + proof: &DecompressedFriProof, common_data: &CommonCircuitData, ) -> Result<()> { let config = &common_data.config; @@ -220,7 +220,7 @@ fn fri_verifier_query_round, const D: usize>( challenges: &ProofChallenges, precomputed_reduced_evals: PrecomputedReducedEvals, initial_merkle_caps: &[MerkleCap], - proof: &FriProof, + proof: &DecompressedFriProof, mut x_index: usize, n: usize, round_proof: &FriQueryRound, diff --git a/src/plonk/get_challenges.rs b/src/plonk/get_challenges.rs index d63c2550..5ff8bccb 100644 --- a/src/plonk/get_challenges.rs +++ b/src/plonk/get_challenges.rs @@ -1,5 +1,6 @@ use crate::field::extension_field::Extendable; use crate::field::field_types::RichField; +use crate::fri::proof::FriProof; use crate::hash::hashing::hash_n_to_1; use crate::iop::challenger::Challenger; use crate::plonk::circuit_data::CommonCircuitData; @@ -44,18 +45,24 @@ impl, const D: usize> ProofWithPublicInputs { let fri_alpha = challenger.get_extension_challenge(); // Recover the random betas used in the FRI reductions. - let fri_betas = self - .proof - .opening_proof - .commit_phase_merkle_caps - .iter() - .map(|cap| { - challenger.observe_cap(cap); - challenger.get_extension_challenge() - }) - .collect(); + let fri_betas = match &self.proof.opening_proof { + FriProof::Decompressed(p) => &p.commit_phase_merkle_caps, + FriProof::Compressed(p) => &p.commit_phase_merkle_caps, + } + .iter() + .map(|cap| { + challenger.observe_cap(cap); + challenger.get_extension_challenge() + }) + .collect(); - challenger.observe_extension_elements(&self.proof.opening_proof.final_poly.coeffs); + challenger.observe_extension_elements( + &match &self.proof.opening_proof { + FriProof::Decompressed(p) => &p.final_poly, + FriProof::Compressed(p) => &p.final_poly, + } + .coeffs, + ); let fri_pow_response = hash_n_to_1( challenger @@ -63,7 +70,10 @@ impl, const D: usize> ProofWithPublicInputs { .elements .iter() .copied() - .chain(Some(self.proof.opening_proof.pow_witness)) + .chain(Some(match &self.proof.opening_proof { + FriProof::Decompressed(p) => p.pow_witness, + FriProof::Compressed(p) => p.pow_witness, + })) .collect(), false, ); diff --git a/src/plonk/proof.rs b/src/plonk/proof.rs index cf8bfc74..57377741 100644 --- a/src/plonk/proof.rs +++ b/src/plonk/proof.rs @@ -5,7 +5,7 @@ use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; use crate::field::field_types::RichField; use crate::fri::commitment::PolynomialBatchCommitment; -use crate::fri::proof::{FriProof, FriProofTarget}; +use crate::fri::proof::{DecompressedFriProof, FriProof, FriProofTarget}; use crate::hash::hash_types::{HashOut, MerkleCapTarget}; use crate::hash::hashing::hash_n_to_hash; use crate::hash::merkle_tree::MerkleCap; @@ -38,18 +38,24 @@ pub struct ProofTarget { impl, const D: usize> Proof { /// Returns `true` iff the opening proof is compressed. pub fn is_compressed(&self) -> bool { - self.opening_proof.is_compressed + todo!() } /// Compress the opening proof. pub fn compress(mut self, indices: &[usize], common_data: &CommonCircuitData) -> Self { - self.opening_proof = self.opening_proof.compress(&indices, common_data); + self.opening_proof = FriProof::Compressed(match self.opening_proof { + FriProof::Decompressed(p) => p.compress(indices, common_data, true), + FriProof::Compressed(p) => p, + }); self } /// Decompress the opening proof. pub fn decompress(mut self, indices: &[usize], common_data: &CommonCircuitData) -> Self { - self.opening_proof = self.opening_proof.decompress(&indices, common_data); + self.opening_proof = FriProof::Decompressed(match self.opening_proof { + FriProof::Decompressed(p) => p, + FriProof::Compressed(p) => p.decompress(indices, common_data), + }); self } } @@ -64,7 +70,7 @@ pub struct ProofWithPublicInputs, const D: usize> { impl, const D: usize> ProofWithPublicInputs { /// Returns `true` iff the opening proof is compressed. pub fn is_compressed(&self) -> bool { - self.proof.is_compressed() + todo!() } /// Compress the opening proof. diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 19852cff..76267820 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -5,6 +5,7 @@ use rayon::prelude::*; use crate::field::extension_field::Extendable; use crate::field::field_types::RichField; use crate::fri::commitment::PolynomialBatchCommitment; +use crate::fri::proof::FriProof; use crate::hash::hash_types::HashOut; use crate::hash::hashing::hash_n_to_hash; use crate::iop::challenger::Challenger; @@ -202,7 +203,7 @@ pub(crate) fn prove, const D: usize>( plonk_zs_partial_products_cap: zs_partial_products_commitment.merkle_tree.cap, quotient_polys_cap: quotient_polys_commitment.merkle_tree.cap, openings, - opening_proof, + opening_proof: FriProof::Decompressed(opening_proof), }; Ok(ProofWithPublicInputs { proof, diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index 27038b0d..6753cfeb 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -2,10 +2,11 @@ use anyhow::{ensure, Result}; use crate::field::extension_field::Extendable; use crate::field::field_types::{Field, RichField}; +use crate::fri::proof::FriProof; use crate::fri::verifier::verify_fri_proof; use crate::plonk::circuit_data::{CommonCircuitData, VerifierOnlyCircuitData}; use crate::plonk::plonk_common::reduce_with_powers; -use crate::plonk::proof::ProofWithPublicInputs; +use crate::plonk::proof::{Proof, ProofWithPublicInputs}; use crate::plonk::vanishing_poly::eval_vanishing_poly; use crate::plonk::vars::EvaluationVars; @@ -76,11 +77,20 @@ pub(crate) fn verify, const D: usize>( proof.quotient_polys_cap, ]; + let Proof { + openings, + opening_proof, + .. + } = proof; + let opening_proof = match opening_proof { + FriProof::Decompressed(p) => p, + FriProof::Compressed(p) => p.decompress(&challenges.fri_query_indices, common_data), + }; verify_fri_proof( - &proof.openings, + &openings, &challenges, merkle_caps, - &proof.opening_proof, + &opening_proof, common_data, )?;