(De)Serializer for CompressedProof

This commit is contained in:
wborgeaud 2021-10-01 16:54:14 +02:00
parent 8f212d31ef
commit fb585064a9
4 changed files with 301 additions and 64 deletions

View File

@ -83,6 +83,8 @@ pub struct FriQueryRoundTarget<const D: usize> {
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(bound = "")]
pub struct CompressedFriQueryRounds<F: Extendable<D>, const D: usize> {
/// Query indices.
pub indices: Vec<usize>,
/// Map from initial indices `i` to the `FriInitialProof` for the `i`th leaf.
pub initial_trees_proofs: HashMap<usize, FriInitialTreeProof<F>>,
/// For each FRI query step, a map from initial indices `i` to the `FriInitialProof` for the `i`th leaf.
@ -182,6 +184,7 @@ impl<F: RichField + Extendable<D>, const D: usize> FriProof<F, D> {
.collect::<Vec<_>>();
let mut compressed_query_proofs = CompressedFriQueryRounds {
indices: indices.to_vec(),
initial_trees_proofs: HashMap::new(),
steps: vec![HashMap::new(); num_reductions],
};

View File

@ -10,7 +10,8 @@ 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;
use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData};
use crate::util::serialization::Buffer;
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(bound = "")]
@ -83,6 +84,22 @@ impl<F: RichField + Extendable<D>, const D: usize> ProofWithPublicInputs<F, D> {
pub(crate) fn get_public_inputs_hash(&self) -> HashOut<F> {
hash_n_to_hash(self.public_inputs.clone(), true)
}
pub fn to_bytes(&self) -> anyhow::Result<Vec<u8>> {
let mut buffer = Buffer::new(Vec::new());
buffer.write_proof_with_public_inputs(&self)?;
Ok(buffer.bytes())
}
pub fn from_bytes(
bytes: Vec<u8>,
common_data: &CommonCircuitData<F, D>,
config: &CircuitConfig,
) -> anyhow::Result<Self> {
let mut buffer = Buffer::new(bytes);
let proof = buffer.read_proof_with_public_inputs(common_data, config)?;
Ok(proof)
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
@ -148,6 +165,22 @@ impl<F: RichField + Extendable<D>, const D: usize> CompressedProofWithPublicInpu
pub(crate) fn get_public_inputs_hash(&self) -> HashOut<F> {
hash_n_to_hash(self.public_inputs.clone(), true)
}
pub fn to_bytes(&self) -> anyhow::Result<Vec<u8>> {
let mut buffer = Buffer::new(Vec::new());
buffer.write_compressed_proof_with_public_inputs(&self)?;
Ok(buffer.bytes())
}
pub fn from_bytes(
bytes: Vec<u8>,
common_data: &CommonCircuitData<F, D>,
config: &CircuitConfig,
) -> anyhow::Result<Self> {
let mut buffer = Buffer::new(bytes);
let proof = buffer.read_compressed_proof_with_public_inputs(common_data, config)?;
Ok(proof)
}
}
pub(crate) struct ProofChallenges<F: RichField + Extendable<D>, const D: usize> {

View File

@ -136,7 +136,10 @@ mod tests {
use crate::gadgets::polynomial::PolynomialCoeffsExtTarget;
use crate::hash::merkle_proofs::MerkleProofTarget;
use crate::iop::witness::{PartialWitness, Witness};
use crate::plonk::proof::{OpeningSetTarget, Proof, ProofTarget, ProofWithPublicInputs};
use crate::plonk::proof::{
CompressedProofWithPublicInputs, OpeningSetTarget, Proof, ProofTarget,
ProofWithPublicInputs,
};
use crate::plonk::verifier::verify;
use crate::util::log2_strict;
use crate::util::serialization::Buffer;
@ -481,23 +484,25 @@ mod tests {
builder.print_gate_counts(0);
let data = builder.build();
let recursive_proof = data.prove(pw)?;
let mut buffer = Buffer::new(Vec::new());
buffer.write_proof(&recursive_proof.proof).unwrap();
dbg!(recursive_proof.public_inputs.len());
dbg!(buffer.len());
let mut buffer = Buffer::new(buffer.bytes());
let proof = buffer.read_proof(&data.common, &config).unwrap();
assert_eq!(recursive_proof.proof, proof);
let proof_bytes = recursive_proof.to_bytes()?;
info!("Proof length: {} bytes", proof_bytes.len());
let proof_from_bytes =
ProofWithPublicInputs::from_bytes(proof_bytes, &data.common, &config)?;
assert_eq!(recursive_proof, proof_from_bytes);
let now = std::time::Instant::now();
let compressed_recursive_proof = recursive_proof.clone().compress(&data.common)?;
info!("{:.4} to compress proof", now.elapsed().as_secs_f64());
let proof_bytes = serde_cbor::to_vec(&recursive_proof).unwrap();
info!("Proof length: {} bytes", proof_bytes.len());
let compressed_proof_bytes = serde_cbor::to_vec(&compressed_recursive_proof).unwrap();
let compressed_proof_bytes = compressed_recursive_proof.to_bytes()?;
info!(
"Compressed proof length: {} bytes",
compressed_proof_bytes.len()
);
let compressed_proof_from_bytes = CompressedProofWithPublicInputs::from_bytes(
compressed_proof_bytes,
&data.common,
&config,
)?;
assert_eq!(compressed_recursive_proof, compressed_proof_from_bytes);
verify(recursive_proof, &data.verifier_only, &data.common)
}
}

View File

@ -1,18 +1,25 @@
use std::collections::HashMap;
use std::convert::TryInto;
use std::fmt;
use std::io::Cursor;
use std::io::{Error, ErrorKind, Read, Result, Write};
use std::iter::FromIterator;
use crate::field::crandall_field::CrandallField;
use crate::field::extension_field::quartic::QuarticExtension;
use crate::field::extension_field::{Extendable, FieldExtension};
use crate::field::field_types::{Field, PrimeField, RichField};
use crate::fri::proof::{FriInitialTreeProof, FriProof, FriQueryRound, FriQueryStep};
use crate::fri::proof::{
CompressedFriProof, CompressedFriQueryRounds, FriInitialTreeProof, FriProof, FriQueryRound,
FriQueryStep,
};
use crate::hash::hash_types::HashOut;
use crate::hash::merkle_proofs::MerkleProof;
use crate::hash::merkle_tree::{MerkleCap, MerkleTree};
use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData};
use crate::plonk::proof::{OpeningSet, Proof};
use crate::plonk::proof::{
CompressedProof, CompressedProofWithPublicInputs, OpeningSet, Proof, ProofWithPublicInputs,
};
use crate::polynomial::polynomial::PolynomialCoeffs;
#[derive(Debug)]
@ -31,19 +38,28 @@ impl Buffer {
self.0.into_inner()
}
pub fn write_u8(&mut self, x: u8) -> Result<()> {
fn write_u8(&mut self, x: u8) -> Result<()> {
self.0.write_all(&[x])
}
pub fn read_u8(&mut self) -> Result<u8> {
let mut buf = [0; 1];
fn read_u8(&mut self) -> Result<u8> {
let mut buf = [0; std::mem::size_of::<u8>()];
self.0.read_exact(&mut buf)?;
Ok(buf[0])
}
pub fn write_field<F: PrimeField>(&mut self, x: F) -> Result<()> {
fn write_u32(&mut self, x: u32) -> Result<()> {
self.0.write_all(&x.to_le_bytes())
}
fn read_u32(&mut self) -> Result<u32> {
let mut buf = [0; std::mem::size_of::<u32>()];
self.0.read_exact(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
fn write_field<F: PrimeField>(&mut self, x: F) -> Result<()> {
self.0.write_all(&x.to_canonical_u64().to_le_bytes())
}
pub fn read_field<F: PrimeField>(&mut self) -> Result<F> {
fn read_field<F: PrimeField>(&mut self) -> Result<F> {
let mut buf = [0; std::mem::size_of::<u64>()];
self.0.read_exact(&mut buf)?;
Ok(F::from_canonical_u64(u64::from_le_bytes(
@ -51,16 +67,13 @@ impl Buffer {
)))
}
pub fn write_field_ext<F: Extendable<D>, const D: usize>(
&mut self,
x: F::Extension,
) -> Result<()> {
fn write_field_ext<F: Extendable<D>, const D: usize>(&mut self, x: F::Extension) -> Result<()> {
for &a in &x.to_basefield_array() {
self.write_field(a)?;
}
Ok(())
}
pub fn read_field_ext<F: Extendable<D>, const D: usize>(&mut self) -> Result<F::Extension> {
fn read_field_ext<F: Extendable<D>, const D: usize>(&mut self) -> Result<F::Extension> {
let mut arr = [F::ZERO; D];
for a in arr.iter_mut() {
*a = self.read_field()?;
@ -70,13 +83,13 @@ impl Buffer {
))
}
pub fn write_hash<F: PrimeField>(&mut self, h: HashOut<F>) -> Result<()> {
fn write_hash<F: PrimeField>(&mut self, h: HashOut<F>) -> Result<()> {
for &a in &h.elements {
self.write_field(a)?;
}
Ok(())
}
pub fn read_hash<F: PrimeField>(&mut self) -> Result<HashOut<F>> {
fn read_hash<F: PrimeField>(&mut self) -> Result<HashOut<F>> {
let mut elements = [F::ZERO; 4];
for a in elements.iter_mut() {
*a = self.read_field()?;
@ -84,13 +97,13 @@ impl Buffer {
Ok(HashOut { elements })
}
pub fn write_merkle_cap<F: PrimeField>(&mut self, cap: &MerkleCap<F>) -> Result<()> {
fn write_merkle_cap<F: PrimeField>(&mut self, cap: &MerkleCap<F>) -> Result<()> {
for &a in &cap.0 {
self.write_hash(a)?;
}
Ok(())
}
pub fn read_merkle_cap<F: PrimeField>(&mut self, cap_height: usize) -> Result<MerkleCap<F>> {
fn read_merkle_cap<F: PrimeField>(&mut self, cap_height: usize) -> Result<MerkleCap<F>> {
let cap_length = 1 << cap_height;
Ok(MerkleCap(
(0..cap_length)
@ -99,19 +112,19 @@ impl Buffer {
))
}
pub fn write_field_vec<F: PrimeField>(&mut self, v: &[F]) -> Result<()> {
fn write_field_vec<F: PrimeField>(&mut self, v: &[F]) -> Result<()> {
for &a in v {
self.write_field(a)?;
}
Ok(())
}
pub fn read_field_vec<F: PrimeField>(&mut self, length: usize) -> Result<Vec<F>> {
fn read_field_vec<F: PrimeField>(&mut self, length: usize) -> Result<Vec<F>> {
Ok((0..length)
.map(|_| self.read_field())
.collect::<Result<Vec<_>>>()?)
}
pub fn write_field_ext_vec<F: Extendable<D>, const D: usize>(
fn write_field_ext_vec<F: Extendable<D>, const D: usize>(
&mut self,
v: &[F::Extension],
) -> Result<()> {
@ -120,7 +133,7 @@ impl Buffer {
}
Ok(())
}
pub fn read_field_ext_vec<F: Extendable<D>, const D: usize>(
fn read_field_ext_vec<F: Extendable<D>, const D: usize>(
&mut self,
length: usize,
) -> Result<Vec<F::Extension>> {
@ -129,7 +142,7 @@ impl Buffer {
.collect::<Result<Vec<_>>>()?)
}
pub fn write_opening_set<F: Extendable<D>, const D: usize>(
fn write_opening_set<F: Extendable<D>, const D: usize>(
&mut self,
os: &OpeningSet<F, D>,
) -> Result<()> {
@ -141,7 +154,7 @@ impl Buffer {
self.write_field_ext_vec::<F, D>(&os.partial_products)?;
self.write_field_ext_vec::<F, D>(&os.quotient_polys)
}
pub fn read_opening_set<F: RichField + Extendable<D>, const D: usize>(
fn read_opening_set<F: RichField + Extendable<D>, const D: usize>(
&mut self,
common_data: &CommonCircuitData<F, D>,
config: &CircuitConfig,
@ -168,7 +181,7 @@ impl Buffer {
})
}
pub fn write_merkle_proof<F: PrimeField>(&mut self, p: &MerkleProof<F>) -> Result<()> {
fn write_merkle_proof<F: PrimeField>(&mut self, p: &MerkleProof<F>) -> Result<()> {
let length = p.siblings.len();
self.write_u8(
length
@ -180,7 +193,7 @@ impl Buffer {
}
Ok(())
}
pub fn read_merkle_proof<F: PrimeField>(&mut self) -> Result<MerkleProof<F>> {
fn read_merkle_proof<F: PrimeField>(&mut self) -> Result<MerkleProof<F>> {
let length = self.read_u8()?;
Ok(MerkleProof {
siblings: (0..length)
@ -189,7 +202,7 @@ impl Buffer {
})
}
pub fn write_fri_initial_proof<F: PrimeField>(
fn write_fri_initial_proof<F: PrimeField>(
&mut self,
fitp: &FriInitialTreeProof<F>,
) -> Result<()> {
@ -199,7 +212,7 @@ impl Buffer {
}
Ok(())
}
pub fn read_fri_initial_proof<F: RichField + Extendable<D>, const D: usize>(
fn read_fri_initial_proof<F: RichField + Extendable<D>, const D: usize>(
&mut self,
common_data: &CommonCircuitData<F, D>,
config: &CircuitConfig,
@ -228,43 +241,38 @@ impl Buffer {
Ok(FriInitialTreeProof { evals_proofs })
}
pub fn write_fri_query_steps<F: Extendable<D>, const D: usize>(
fn write_fri_query_step<F: Extendable<D>, const D: usize>(
&mut self,
fqss: &[FriQueryStep<F, D>],
fqs: &FriQueryStep<F, D>,
) -> Result<()> {
for fqs in fqss {
self.write_field_ext_vec::<F, D>(&fqs.evals)?;
self.write_merkle_proof(&fqs.merkle_proof)?;
}
Ok(())
self.write_field_ext_vec::<F, D>(&fqs.evals)?;
self.write_merkle_proof(&fqs.merkle_proof)
}
pub fn read_fri_query_steps<F: Extendable<D>, const D: usize>(
fn read_fri_query_step<F: Extendable<D>, const D: usize>(
&mut self,
config: &CircuitConfig,
) -> Result<Vec<FriQueryStep<F, D>>> {
let mut fqss = Vec::with_capacity(config.fri_config.reduction_arity_bits.len());
for &arity_bits in &config.fri_config.reduction_arity_bits {
let evals = self.read_field_ext_vec::<F, D>(1 << arity_bits)?;
let merkle_proof = self.read_merkle_proof()?;
fqss.push(FriQueryStep {
evals,
merkle_proof,
})
}
Ok(fqss)
arity: usize,
) -> Result<FriQueryStep<F, D>> {
let evals = self.read_field_ext_vec::<F, D>(arity)?;
let merkle_proof = self.read_merkle_proof()?;
Ok(FriQueryStep {
evals,
merkle_proof,
})
}
pub fn write_fri_query_rounds<F: Extendable<D>, const D: usize>(
fn write_fri_query_rounds<F: Extendable<D>, const D: usize>(
&mut self,
fqrs: &[FriQueryRound<F, D>],
) -> Result<()> {
for fqr in fqrs {
self.write_fri_initial_proof(&fqr.initial_trees_proof)?;
self.write_fri_query_steps(&fqr.steps)?;
for fqs in &fqr.steps {
self.write_fri_query_step(fqs)?;
}
}
Ok(())
}
pub fn read_fri_query_rounds<F: RichField + Extendable<D>, const D: usize>(
fn read_fri_query_rounds<F: RichField + Extendable<D>, const D: usize>(
&mut self,
common_data: &CommonCircuitData<F, D>,
config: &CircuitConfig,
@ -272,7 +280,12 @@ impl Buffer {
let mut fqrs = Vec::with_capacity(config.fri_config.num_query_rounds);
for i in 0..config.fri_config.num_query_rounds {
let initial_trees_proof = self.read_fri_initial_proof(common_data, config)?;
let steps = self.read_fri_query_steps(config)?;
let steps = config
.fri_config
.reduction_arity_bits
.iter()
.map(|&ar| self.read_fri_query_step(1 << ar))
.collect::<Result<_>>()?;
fqrs.push(FriQueryRound {
initial_trees_proof,
steps,
@ -281,7 +294,7 @@ impl Buffer {
Ok(fqrs)
}
pub fn write_fri_proof<F: Extendable<D>, const D: usize>(
fn write_fri_proof<F: Extendable<D>, const D: usize>(
&mut self,
fp: &FriProof<F, D>,
) -> Result<()> {
@ -292,7 +305,7 @@ impl Buffer {
self.write_field_ext_vec::<F, D>(&fp.final_poly.coeffs)?;
self.write_field(fp.pow_witness)
}
pub fn read_fri_proof<F: RichField + Extendable<D>, const D: usize>(
fn read_fri_proof<F: RichField + Extendable<D>, const D: usize>(
&mut self,
common_data: &CommonCircuitData<F, D>,
config: &CircuitConfig,
@ -343,6 +356,189 @@ impl Buffer {
opening_proof,
})
}
pub fn write_proof_with_public_inputs<F: RichField + Extendable<D>, const D: usize>(
&mut self,
proof_with_pis: &ProofWithPublicInputs<F, D>,
) -> Result<()> {
let ProofWithPublicInputs {
proof,
public_inputs,
} = proof_with_pis;
self.write_proof(proof)?;
self.write_field_vec(public_inputs)
}
pub fn read_proof_with_public_inputs<F: RichField + Extendable<D>, const D: usize>(
&mut self,
common_data: &CommonCircuitData<F, D>,
config: &CircuitConfig,
) -> Result<ProofWithPublicInputs<F, D>> {
let proof = self.read_proof(common_data, config)?;
let public_inputs = self.read_field_vec(self.len() - self.0.position() as usize)?;
Ok(ProofWithPublicInputs {
proof,
public_inputs,
})
}
fn write_compressed_fri_query_rounds<F: Extendable<D>, const D: usize>(
&mut self,
cfqrs: &CompressedFriQueryRounds<F, D>,
) -> Result<()> {
for &i in &cfqrs.indices {
self.write_u32(i as u32)?;
}
let mut initial_trees_proofs = cfqrs.initial_trees_proofs.iter().collect::<Vec<_>>();
initial_trees_proofs.sort_by_key(|&x| x.0);
for (_, itp) in initial_trees_proofs {
self.write_fri_initial_proof(itp)?;
}
for h in &cfqrs.steps {
let mut fri_query_steps = h.iter().collect::<Vec<_>>();
fri_query_steps.sort_by_key(|&x| x.0);
for (_, fqs) in fri_query_steps {
self.write_fri_query_step(fqs)?;
}
}
Ok(())
}
fn read_compressed_fri_query_rounds<F: RichField + Extendable<D>, const D: usize>(
&mut self,
common_data: &CommonCircuitData<F, D>,
config: &CircuitConfig,
) -> Result<CompressedFriQueryRounds<F, D>> {
let original_indices = (0..config.fri_config.num_query_rounds)
.map(|_| self.read_u32().map(|i| i as usize))
.collect::<Result<Vec<_>>>()?;
let mut indices = original_indices.clone();
indices.sort();
indices.dedup();
let mut pairs = Vec::new();
for &i in &indices {
pairs.push((i, self.read_fri_initial_proof(common_data, config)?));
}
let initial_trees_proofs = HashMap::from_iter(pairs);
let mut steps = Vec::with_capacity(config.fri_config.reduction_arity_bits.len());
for &a in &config.fri_config.reduction_arity_bits {
indices.iter_mut().for_each(|x| {
*x >>= a;
});
indices.dedup();
let query_steps = (0..indices.len())
.map(|_| self.read_fri_query_step(1 << a))
.collect::<Result<Vec<_>>>()?;
steps.push(
indices
.iter()
.copied()
.zip(query_steps)
.collect::<HashMap<_, _>>(),
);
}
Ok(CompressedFriQueryRounds {
indices: original_indices,
initial_trees_proofs,
steps,
})
}
fn write_compressed_fri_proof<F: Extendable<D>, const D: usize>(
&mut self,
fp: &CompressedFriProof<F, D>,
) -> Result<()> {
for cap in &fp.commit_phase_merkle_caps {
self.write_merkle_cap(cap)?;
}
self.write_compressed_fri_query_rounds(&fp.query_round_proofs)?;
self.write_field_ext_vec::<F, D>(&fp.final_poly.coeffs)?;
self.write_field(fp.pow_witness)
}
fn read_compressed_fri_proof<F: RichField + Extendable<D>, const D: usize>(
&mut self,
common_data: &CommonCircuitData<F, D>,
config: &CircuitConfig,
) -> Result<CompressedFriProof<F, D>> {
let commit_phase_merkle_caps = (0..config.fri_config.reduction_arity_bits.len())
.map(|_| self.read_merkle_cap(config.cap_height))
.collect::<Result<Vec<_>>>()?;
let query_round_proofs = self.read_compressed_fri_query_rounds(common_data, config)?;
let final_poly = PolynomialCoeffs::new(self.read_field_ext_vec::<F, D>(
1 << (common_data.degree_bits
- config.fri_config.reduction_arity_bits.iter().sum::<usize>()),
)?);
let pow_witness = self.read_field()?;
Ok(CompressedFriProof {
commit_phase_merkle_caps,
query_round_proofs,
final_poly,
pow_witness,
})
}
pub fn write_compressed_proof<F: Extendable<D>, const D: usize>(
&mut self,
proof: &CompressedProof<F, D>,
) -> Result<()> {
self.write_merkle_cap(&proof.wires_cap)?;
self.write_merkle_cap(&proof.plonk_zs_partial_products_cap)?;
self.write_merkle_cap(&proof.quotient_polys_cap)?;
self.write_opening_set(&proof.openings)?;
self.write_compressed_fri_proof(&proof.opening_proof)
}
pub fn read_compressed_proof<F: RichField + Extendable<D>, const D: usize>(
&mut self,
common_data: &CommonCircuitData<F, D>,
config: &CircuitConfig,
) -> Result<CompressedProof<F, D>> {
let wires_cap = self.read_merkle_cap(config.cap_height)?;
let plonk_zs_partial_products_cap = self.read_merkle_cap(config.cap_height)?;
let quotient_polys_cap = self.read_merkle_cap(config.cap_height)?;
let openings = self.read_opening_set(common_data, config)?;
let opening_proof = self.read_compressed_fri_proof(common_data, config)?;
Ok(CompressedProof {
wires_cap,
plonk_zs_partial_products_cap,
quotient_polys_cap,
openings,
opening_proof,
})
}
pub fn write_compressed_proof_with_public_inputs<
F: RichField + Extendable<D>,
const D: usize,
>(
&mut self,
proof_with_pis: &CompressedProofWithPublicInputs<F, D>,
) -> Result<()> {
let CompressedProofWithPublicInputs {
proof,
public_inputs,
} = proof_with_pis;
self.write_compressed_proof(proof)?;
self.write_field_vec(public_inputs)
}
pub fn read_compressed_proof_with_public_inputs<
F: RichField + Extendable<D>,
const D: usize,
>(
&mut self,
common_data: &CommonCircuitData<F, D>,
config: &CircuitConfig,
) -> Result<CompressedProofWithPublicInputs<F, D>> {
let proof = self.read_compressed_proof(common_data, config)?;
let public_inputs = self.read_field_vec(self.len() - self.0.position() as usize)?;
Ok(CompressedProofWithPublicInputs {
proof,
public_inputs,
})
}
}
#[test]