Add compressed FRI proof type using a HashMap

This commit is contained in:
wborgeaud 2021-09-29 21:01:15 +02:00
parent 3f22663296
commit a97b9a7112
8 changed files with 147 additions and 70 deletions

View File

@ -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<F: RichField> PolynomialBatchCommitment<F> {
challenger: &mut Challenger<F>,
common_data: &CommonCircuitData<F, D>,
timing: &mut TimingTree,
) -> (FriProof<F, D>, OpeningSet<F, D>)
) -> (DecompressedFriProof<F, D>, OpeningSet<F, D>)
where
F: RichField + Extendable<D>,
{

View File

@ -1,3 +1,5 @@
use std::collections::HashMap;
use itertools::izip;
use serde::{Deserialize, Serialize};
@ -77,9 +79,24 @@ pub struct FriQueryRoundTarget<const D: usize> {
pub steps: Vec<FriQueryStepTarget<D>>,
}
/// Compressed proofs for FRI query rounds.
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(bound = "")]
pub struct FriProof<F: Extendable<D>, const D: usize> {
pub struct CompressedFriQueryRounds<F: Extendable<D>, const D: usize> {
pub initial_trees_proofs: HashMap<usize, FriInitialTreeProof<F>>,
pub steps: Vec<HashMap<usize, FriQueryStep<F, D>>>,
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(bound = "")]
pub enum FriProof<F: Extendable<D>, const D: usize> {
Decompressed(DecompressedFriProof<F, D>),
Compressed(CompressedFriProof<F, D>),
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(bound = "")]
pub struct DecompressedFriProof<F: Extendable<D>, const D: usize> {
/// A Merkle cap for each reduced polynomial in the commit phase.
pub commit_phase_merkle_caps: Vec<MerkleCap<F>>,
/// Query rounds proofs
@ -88,10 +105,9 @@ pub struct FriProof<F: Extendable<D>, const D: usize> {
pub final_poly: PolynomialCoeffs<F::Extension>,
/// 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<const D: usize> {
pub commit_phase_merkle_caps: Vec<MerkleCapTarget>,
pub query_round_proofs: Vec<FriQueryRoundTarget<D>>,
@ -99,15 +115,30 @@ pub struct FriProofTarget<const D: usize> {
pub pow_witness: Target,
}
impl<F: RichField + Extendable<D>, const D: usize> FriProof<F, D> {
/// Compress all the Merkle paths in the FRI proof.
pub fn compress(self, indices: &[usize], common_data: &CommonCircuitData<F, D>) -> Self {
if self.is_compressed {
panic!("Proof is already compressed.");
}
let FriProof {
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(bound = "")]
pub struct CompressedFriProof<F: Extendable<D>, const D: usize> {
/// A Merkle cap for each reduced polynomial in the commit phase.
pub commit_phase_merkle_caps: Vec<MerkleCap<F>>,
/// Query rounds proofs
pub query_round_proofs: CompressedFriQueryRounds<F, D>,
/// The final polynomial in coefficient form.
pub final_poly: PolynomialCoeffs<F::Extension>,
/// Witness showing that the prover did PoW.
pub pow_witness: F,
}
impl<F: RichField + Extendable<D>, const D: usize> DecompressedFriProof<F, D> {
/// Compress all the Merkle paths in the FRI proof and remove duplicate indices.
pub fn compress(
self,
indices: &[usize],
common_data: &CommonCircuitData<F, D>,
with_indices: bool,
) -> CompressedFriProof<F, D> {
let DecompressedFriProof {
commit_phase_merkle_caps,
mut query_round_proofs,
query_round_proofs,
final_poly,
pow_witness,
..
@ -157,9 +188,14 @@ impl<F: RichField + Extendable<D>, const D: usize> FriProof<F, D> {
.map(|(is, ps)| compress_merkle_proofs(cap_height, is, &ps))
.collect::<Vec<_>>();
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<F: RichField + Extendable<D>, const D: usize> FriProof<F, D> {
})
.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<F, D>) -> Self {
if !self.is_compressed {
panic!("Proof is not compressed.");
}
let FriProof {
impl<F: RichField + Extendable<D>, const D: usize> CompressedFriProof<F, D> {
/// Decompress all the Merkle paths in the FRI proof and add duplicate indices.
pub fn decompress(
self,
indices: &[usize],
common_data: &CommonCircuitData<F, D>,
) -> DecompressedFriProof<F, D> {
let CompressedFriProof {
commit_phase_merkle_caps,
mut query_round_proofs,
query_round_proofs,
final_poly,
pow_witness,
..
@ -201,7 +244,13 @@ impl<F: RichField + Extendable<D>, const D: usize> FriProof<F, D> {
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<F: RichField + Extendable<D>, const D: usize> FriProof<F, D> {
})
.collect::<Vec<_>>();
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<F: RichField + Extendable<D>, const D: usize> FriProof<F, D> {
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<F: RichField + Extendable<D>, const D: usize> FriProof<F, D> {
.map(|(ls, is, ps, h)| decompress_merkle_proofs(ls, is, &ps, h, cap_height))
.collect::<Vec<_>>();
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<F: RichField + Extendable<D>, const D: usize> FriProof<F, D> {
})
.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,
}
}
}

View File

@ -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<F: RichField + Extendable<D>, const D: usize>(
challenger: &mut Challenger<F>,
config: &CircuitConfig,
timing: &mut TimingTree,
) -> FriProof<F, D> {
) -> DecompressedFriProof<F, D> {
let n = lde_polynomial_values.values.len();
assert_eq!(lde_polynomial_coeffs.coeffs.len(), n);
@ -58,12 +58,11 @@ pub fn fri_proof<F: RichField + Extendable<D>, 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,
}
}

View File

@ -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<F: RichField + Extendable<D>, const D: usize>(
os: &OpeningSet<F, D>,
challenges: &ProofChallenges<F, D>,
initial_merkle_caps: &[MerkleCap<F>],
proof: &FriProof<F, D>,
proof: &DecompressedFriProof<F, D>,
common_data: &CommonCircuitData<F, D>,
) -> Result<()> {
let config = &common_data.config;
@ -220,7 +220,7 @@ fn fri_verifier_query_round<F: RichField + Extendable<D>, const D: usize>(
challenges: &ProofChallenges<F, D>,
precomputed_reduced_evals: PrecomputedReducedEvals<F, D>,
initial_merkle_caps: &[MerkleCap<F>],
proof: &FriProof<F, D>,
proof: &DecompressedFriProof<F, D>,
mut x_index: usize,
n: usize,
round_proof: &FriQueryRound<F, D>,

View File

@ -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<F: RichField + Extendable<D>, const D: usize> ProofWithPublicInputs<F, D> {
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<F: RichField + Extendable<D>, const D: usize> ProofWithPublicInputs<F, D> {
.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,
);

View File

@ -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<const D: usize> {
impl<F: RichField + Extendable<D>, const D: usize> Proof<F, D> {
/// 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<F, D>) -> 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<F, D>) -> 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<F: RichField + Extendable<D>, const D: usize> {
impl<F: RichField + Extendable<D>, const D: usize> ProofWithPublicInputs<F, D> {
/// Returns `true` iff the opening proof is compressed.
pub fn is_compressed(&self) -> bool {
self.proof.is_compressed()
todo!()
}
/// Compress the opening proof.

View File

@ -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<F: RichField + Extendable<D>, 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,

View File

@ -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<F: RichField + Extendable<D>, 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,
)?;