use rayon::prelude::*; use serde::{Deserialize, Serialize}; 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::{CompressedFriProof, FriProof, FriProofTarget}; use crate::hash::hash_types::{HashOut, MerkleCapTarget}; use crate::hash::hashing::hash_n_to_hash; use crate::hash::merkle_tree::MerkleCap; use crate::iop::target::Target; use crate::plonk::circuit_data::{CommonCircuitData, VerifierOnlyCircuitData}; use crate::plonk::verifier::verify_with_challenges; use crate::util::serialization::Buffer; #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[serde(bound = "")] pub struct Proof, const D: usize> { /// Merkle cap of LDEs of wire values. pub wires_cap: MerkleCap, /// Merkle cap of LDEs of Z, in the context of Plonk's permutation argument. pub plonk_zs_partial_products_cap: MerkleCap, /// Merkle cap of LDEs of the quotient polynomial components. pub quotient_polys_cap: MerkleCap, /// Purported values of each polynomial at the challenge point. pub openings: OpeningSet, /// A batch FRI argument for all openings. pub opening_proof: FriProof, } pub struct ProofTarget { pub wires_cap: MerkleCapTarget, pub plonk_zs_partial_products_cap: MerkleCapTarget, pub quotient_polys_cap: MerkleCapTarget, pub openings: OpeningSetTarget, pub opening_proof: FriProofTarget, } impl, const D: usize> Proof { /// Compress the proof. pub fn compress( self, indices: &[usize], common_data: &CommonCircuitData, ) -> CompressedProof { let Proof { wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, openings, opening_proof, } = self; CompressedProof { wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, openings, opening_proof: opening_proof.compress(indices, common_data), } } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[serde(bound = "")] pub struct ProofWithPublicInputs, const D: usize> { pub proof: Proof, pub public_inputs: Vec, } impl, const D: usize> ProofWithPublicInputs { pub fn compress( self, common_data: &CommonCircuitData, ) -> anyhow::Result> { let indices = self.fri_query_indices(common_data)?; let compressed_proof = self.proof.compress(&indices, common_data); Ok(CompressedProofWithPublicInputs { public_inputs: self.public_inputs, proof: compressed_proof, }) } pub(crate) fn get_public_inputs_hash(&self) -> HashOut { hash_n_to_hash(self.public_inputs.clone(), true) } pub fn to_bytes(&self) -> anyhow::Result> { let mut buffer = Buffer::new(Vec::new()); buffer.write_proof_with_public_inputs(self)?; Ok(buffer.bytes()) } pub fn from_bytes( bytes: Vec, common_data: &CommonCircuitData, ) -> anyhow::Result { let mut buffer = Buffer::new(bytes); let proof = buffer.read_proof_with_public_inputs(common_data)?; Ok(proof) } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[serde(bound = "")] pub struct CompressedProof, const D: usize> { /// Merkle cap of LDEs of wire values. pub wires_cap: MerkleCap, /// Merkle cap of LDEs of Z, in the context of Plonk's permutation argument. pub plonk_zs_partial_products_cap: MerkleCap, /// Merkle cap of LDEs of the quotient polynomial components. pub quotient_polys_cap: MerkleCap, /// Purported values of each polynomial at the challenge point. pub openings: OpeningSet, /// A compressed batch FRI argument for all openings. pub opening_proof: CompressedFriProof, } impl, const D: usize> CompressedProof { /// Decompress the proof. pub(crate) fn decompress( self, challenges: &ProofChallenges, common_data: &CommonCircuitData, ) -> Proof { let CompressedProof { wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, openings, opening_proof, } = self; Proof { wires_cap, plonk_zs_partial_products_cap, quotient_polys_cap, openings, opening_proof: opening_proof.decompress(challenges, common_data), } } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[serde(bound = "")] pub struct CompressedProofWithPublicInputs, const D: usize> { pub proof: CompressedProof, pub public_inputs: Vec, } impl, const D: usize> CompressedProofWithPublicInputs { pub fn decompress( self, common_data: &CommonCircuitData, ) -> anyhow::Result> { let challenges = self.get_challenges(common_data)?; let compressed_proof = self.proof.decompress(&challenges, common_data); Ok(ProofWithPublicInputs { public_inputs: self.public_inputs, proof: compressed_proof, }) } pub(crate) fn verify( self, verifier_data: &VerifierOnlyCircuitData, common_data: &CommonCircuitData, ) -> anyhow::Result<()> { let challenges = self.get_challenges(common_data)?; let compressed_proof = self.proof.decompress(&challenges, common_data); verify_with_challenges( ProofWithPublicInputs { public_inputs: self.public_inputs, proof: compressed_proof, }, challenges, verifier_data, common_data, ) } pub(crate) fn get_public_inputs_hash(&self) -> HashOut { hash_n_to_hash(self.public_inputs.clone(), true) } pub fn to_bytes(&self) -> anyhow::Result> { let mut buffer = Buffer::new(Vec::new()); buffer.write_compressed_proof_with_public_inputs(self)?; Ok(buffer.bytes()) } pub fn from_bytes( bytes: Vec, common_data: &CommonCircuitData, ) -> anyhow::Result { let mut buffer = Buffer::new(bytes); let proof = buffer.read_compressed_proof_with_public_inputs(common_data)?; Ok(proof) } } pub(crate) struct ProofChallenges, const D: usize> { // Random values used in Plonk's permutation argument. pub plonk_betas: Vec, // Random values used in Plonk's permutation argument. pub plonk_gammas: Vec, // Random values used to combine PLONK constraints. pub plonk_alphas: Vec, // Point at which the PLONK polynomials are opened. pub plonk_zeta: F::Extension, // Scaling factor to combine polynomials. pub fri_alpha: F::Extension, // Betas used in the FRI commit phase reductions. pub fri_betas: Vec, pub fri_pow_response: F, // Indices at which the oracle is queried in FRI. pub fri_query_indices: Vec, // Coset element that can be inferred in the FRI reduction step. // Is typically set to None iff the challenges are computed from a non-compressed proof. pub fri_query_inferred_elements: Option>, } pub struct ProofWithPublicInputsTarget { pub proof: ProofTarget, pub public_inputs: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] /// The purported values of each polynomial at a single point. pub struct OpeningSet, const D: usize> { pub constants: Vec, pub plonk_sigmas: Vec, pub wires: Vec, pub plonk_zs: Vec, pub plonk_zs_right: Vec, pub partial_products: Vec, pub quotient_polys: Vec, } impl, const D: usize> OpeningSet { pub fn new( z: F::Extension, g: F::Extension, constants_sigmas_commitment: &PolynomialBatchCommitment, wires_commitment: &PolynomialBatchCommitment, zs_partial_products_commitment: &PolynomialBatchCommitment, quotient_polys_commitment: &PolynomialBatchCommitment, common_data: &CommonCircuitData, ) -> Self { let eval_commitment = |z: F::Extension, c: &PolynomialBatchCommitment| { c.polynomials .par_iter() .map(|p| p.to_extension().eval(z)) .collect::>() }; let constants_sigmas_eval = eval_commitment(z, constants_sigmas_commitment); let zs_partial_products_eval = eval_commitment(z, zs_partial_products_commitment); Self { constants: constants_sigmas_eval[common_data.constants_range()].to_vec(), plonk_sigmas: constants_sigmas_eval[common_data.sigmas_range()].to_vec(), wires: eval_commitment(z, wires_commitment), plonk_zs: zs_partial_products_eval[common_data.zs_range()].to_vec(), plonk_zs_right: eval_commitment(g * z, zs_partial_products_commitment) [common_data.zs_range()] .to_vec(), partial_products: zs_partial_products_eval[common_data.partial_products_range()] .to_vec(), quotient_polys: eval_commitment(z, quotient_polys_commitment), } } } /// The purported values of each polynomial at a single point. #[derive(Clone, Debug)] pub struct OpeningSetTarget { pub constants: Vec>, pub plonk_sigmas: Vec>, pub wires: Vec>, pub plonk_zs: Vec>, pub plonk_zs_right: Vec>, pub partial_products: Vec>, pub quotient_polys: Vec>, } #[cfg(test)] mod tests { use anyhow::Result; use crate::field::crandall_field::CrandallField; use crate::field::field_types::Field; use crate::fri::reduction_strategies::FriReductionStrategy; use crate::iop::witness::PartialWitness; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::circuit_data::CircuitConfig; use crate::plonk::verifier::verify; #[test] fn test_proof_compression() -> Result<()> { type F = CrandallField; const D: usize = 4; let mut config = CircuitConfig::large_config(); config.fri_config.reduction_strategy = FriReductionStrategy::Fixed(vec![2, 1]); config.fri_config.num_query_rounds = 50; let pw = PartialWitness::new(); let mut builder = CircuitBuilder::::new(config); // Build dummy circuit to get a valid proof. let x = F::rand(); let y = F::rand(); let z = x * y; let xt = builder.constant(x); let yt = builder.constant(y); let zt = builder.constant(z); let comp_zt = builder.mul(xt, yt); builder.connect(zt, comp_zt); let data = builder.build(); let proof = data.prove(pw)?; verify(proof.clone(), &data.verifier_only, &data.common)?; // Verify that `decompress ∘ compress = identity`. let compressed_proof = proof.clone().compress(&data.common)?; let decompressed_compressed_proof = compressed_proof.clone().decompress(&data.common)?; assert_eq!(proof, decompressed_compressed_proof); verify(proof, &data.verifier_only, &data.common)?; compressed_proof.verify(&data.verifier_only, &data.common) } }