#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
use anyhow::ensure;
use plonky2_maybe_rayon::*;
use serde::{Deserialize, Serialize};
use crate::field::extension::Extendable;
use crate::fri::oracle::PolynomialBatch;
use crate::fri::proof::{CompressedFriProof, FriChallengesTarget, FriProof, FriProofTarget};
use crate::fri::structure::{
FriOpeningBatch, FriOpeningBatchTarget, FriOpenings, FriOpeningsTarget,
};
use crate::fri::FriParams;
use crate::hash::hash_types::{MerkleCapTarget, RichField};
use crate::hash::merkle_tree::MerkleCap;
use crate::iop::ext_target::ExtensionTarget;
use crate::iop::target::Target;
use crate::plonk::circuit_data::{CommonCircuitData, VerifierOnlyCircuitData};
use crate::plonk::config::{GenericConfig, Hasher};
use crate::plonk::verifier::verify_with_challenges;
use crate::plonk::zk::LogicalPolynomialBatch;
use crate::util::serialization::{Buffer, Read, Write};
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(bound = "")]
pub struct Proof<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize> {
pub wires_cap: MerkleCap<F, C::Hasher>,
pub plonk_zs_partial_products_cap: MerkleCap<F, C::Hasher>,
pub quotient_polys_cap: MerkleCap<F, C::Hasher>,
pub openings: OpeningSet<F, D>,
pub opening_proof: FriProof<F, C::Hasher, D>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ProofTarget<const D: usize> {
pub wires_cap: MerkleCapTarget,
pub plonk_zs_partial_products_cap: MerkleCapTarget,
pub quotient_polys_cap: MerkleCapTarget,
pub openings: OpeningSetTarget<D>,
pub opening_proof: FriProofTarget<D>,
}
impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize> Proof<F, C, D> {
pub fn compress(self, indices: &[usize], params: &FriParams) -> CompressedProof<F, C, D> {
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, params),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(bound = "")]
pub struct ProofWithPublicInputs<
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
const D: usize,
> {
pub proof: Proof<F, C, D>,
pub public_inputs: Vec<F>,
}
impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
ProofWithPublicInputs<F, C, D>
{
pub fn compress(
self,
circuit_digest: &<<C as GenericConfig<D>>::Hasher as Hasher<C::F>>::Hash,
common_data: &CommonCircuitData<F, D>,
) -> anyhow::Result<CompressedProofWithPublicInputs<F, C, D>> {
let indices = self.fri_query_indices(circuit_digest, common_data)?;
let compressed_proof = self.proof.compress(&indices, &common_data.fri_params);
Ok(CompressedProofWithPublicInputs {
public_inputs: self.public_inputs,
proof: compressed_proof,
})
}
pub fn get_public_inputs_hash(
&self,
) -> <<C as GenericConfig<D>>::InnerHasher as Hasher<F>>::Hash {
C::InnerHasher::hash_no_pad(&self.public_inputs)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buffer = Vec::new();
buffer
.write_proof_with_public_inputs(self)
.expect("Writing to a byte-vector cannot fail.");
buffer
}
pub fn from_bytes(
bytes: Vec<u8>,
common_data: &CommonCircuitData<F, D>,
) -> anyhow::Result<Self> {
let mut buffer = Buffer::new(&bytes);
let proof = buffer
.read_proof_with_public_inputs(common_data)
.map_err(anyhow::Error::msg)?;
Ok(proof)
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(bound = "")]
pub struct CompressedProof<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
{
pub wires_cap: MerkleCap<F, C::Hasher>,
pub plonk_zs_partial_products_cap: MerkleCap<F, C::Hasher>,
pub quotient_polys_cap: MerkleCap<F, C::Hasher>,
pub openings: OpeningSet<F, D>,
pub opening_proof: CompressedFriProof<F, C::Hasher, D>,
}
impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
CompressedProof<F, C, D>
{
pub(crate) fn decompress(
self,
challenges: &ProofChallenges<F, D>,
fri_inferred_elements: FriInferredElements<F, D>,
params: &FriParams,
) -> Proof<F, C, D> {
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, fri_inferred_elements, params),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(bound = "")]
pub struct CompressedProofWithPublicInputs<
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
const D: usize,
> {
pub proof: CompressedProof<F, C, D>,
pub public_inputs: Vec<F>,
}
impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
CompressedProofWithPublicInputs<F, C, D>
{
pub fn decompress(
self,
circuit_digest: &<<C as GenericConfig<D>>::Hasher as Hasher<C::F>>::Hash,
common_data: &CommonCircuitData<F, D>,
) -> anyhow::Result<ProofWithPublicInputs<F, C, D>> {
let challenges =
self.get_challenges(self.get_public_inputs_hash(), circuit_digest, common_data)?;
let fri_inferred_elements = self.get_inferred_elements(&challenges, common_data);
let decompressed_proof =
self.proof
.decompress(&challenges, fri_inferred_elements, &common_data.fri_params);
Ok(ProofWithPublicInputs {
public_inputs: self.public_inputs,
proof: decompressed_proof,
})
}
pub(crate) fn verify(
self,
verifier_data: &VerifierOnlyCircuitData<C, D>,
common_data: &CommonCircuitData<F, D>,
) -> anyhow::Result<()> {
ensure!(
self.public_inputs.len() == common_data.num_public_inputs,
"Number of public inputs doesn't match circuit data."
);
let public_inputs_hash = self.get_public_inputs_hash();
let challenges = self.get_challenges(
public_inputs_hash,
&verifier_data.circuit_digest,
common_data,
)?;
let fri_inferred_elements = self.get_inferred_elements(&challenges, common_data);
let decompressed_proof =
self.proof
.decompress(&challenges, fri_inferred_elements, &common_data.fri_params);
verify_with_challenges::<F, C, D>(
decompressed_proof,
public_inputs_hash,
challenges,
verifier_data,
common_data,
)
}
pub(crate) fn get_public_inputs_hash(
&self,
) -> <<C as GenericConfig<D>>::InnerHasher as Hasher<F>>::Hash {
C::InnerHasher::hash_no_pad(&self.public_inputs)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buffer = Vec::new();
buffer
.write_compressed_proof_with_public_inputs(self)
.expect("Writing to a byte-vector cannot fail.");
buffer
}
pub fn from_bytes(
bytes: Vec<u8>,
common_data: &CommonCircuitData<F, D>,
) -> anyhow::Result<Self> {
let mut buffer = Buffer::new(&bytes);
let proof = buffer
.read_compressed_proof_with_public_inputs(common_data)
.map_err(anyhow::Error::msg)?;
Ok(proof)
}
}
pub use qp_plonky2_core::proof::{FriInferredElements, ProofChallenges};
pub(crate) struct ProofChallengesTarget<const D: usize> {
pub plonk_betas: Vec<Target>,
pub plonk_gammas: Vec<Target>,
pub plonk_alphas: Vec<Target>,
pub plonk_deltas: Vec<Target>,
pub plonk_zeta: ExtensionTarget<D>,
pub fri_challenges: FriChallengesTarget<D>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ProofWithPublicInputsTarget<const D: usize> {
pub proof: ProofTarget<D>,
pub public_inputs: Vec<Target>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
pub struct OpeningSet<F: RichField + Extendable<D>, const D: usize> {
pub constants: Vec<F::Extension>,
pub plonk_sigmas: Vec<F::Extension>,
pub wires: Vec<F::Extension>,
pub plonk_zs: Vec<F::Extension>,
pub plonk_zs_next: Vec<F::Extension>,
pub partial_products: Vec<F::Extension>,
pub quotient_polys: Vec<F::Extension>,
pub lookup_zs: Vec<F::Extension>,
pub lookup_zs_next: Vec<F::Extension>,
}
impl<F: RichField + Extendable<D>, const D: usize> OpeningSet<F, D> {
pub fn new<C: GenericConfig<D, F = F>>(
zeta: F::Extension,
g: F::Extension,
constants_sigmas_commitment: &PolynomialBatch<F, C, D>,
wires_commitment: &LogicalPolynomialBatch<F, C, D>,
zs_partial_products_lookup_commitment: &LogicalPolynomialBatch<F, C, D>,
quotient_polys_commitment: &LogicalPolynomialBatch<F, C, D>,
common_data: &CommonCircuitData<F, D>,
) -> Self {
let eval_raw_commitment = |z: F::Extension, c: &PolynomialBatch<F, C, D>| {
c.polynomials
.par_iter()
.map(|p| p.to_extension().eval(z))
.collect::<Vec<_>>()
};
let eval_logical_commitment =
|z: F::Extension, c: &LogicalPolynomialBatch<F, C, D>| c.logical_evals(z);
let constants_sigmas_eval = eval_raw_commitment(zeta, constants_sigmas_commitment);
let zs_partial_products_lookup_eval =
eval_logical_commitment(zeta, zs_partial_products_lookup_commitment);
let zs_partial_products_lookup_next_eval =
eval_logical_commitment(g * zeta, zs_partial_products_lookup_commitment);
let quotient_polys = eval_logical_commitment(zeta, quotient_polys_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_logical_commitment(zeta, wires_commitment),
plonk_zs: zs_partial_products_lookup_eval[common_data.zs_range()].to_vec(),
plonk_zs_next: zs_partial_products_lookup_next_eval[common_data.zs_range()].to_vec(),
partial_products: zs_partial_products_lookup_eval[common_data.partial_products_range()]
.to_vec(),
quotient_polys,
lookup_zs: zs_partial_products_lookup_eval[common_data.lookup_range()].to_vec(),
lookup_zs_next: zs_partial_products_lookup_next_eval[common_data.lookup_range()]
.to_vec(),
}
}
pub(crate) fn to_fri_openings(&self) -> FriOpenings<F, D> {
let has_lookup = !self.lookup_zs.is_empty();
let zeta_batch = if has_lookup {
FriOpeningBatch {
values: [
self.constants.as_slice(),
self.plonk_sigmas.as_slice(),
self.wires.as_slice(),
self.plonk_zs.as_slice(),
self.partial_products.as_slice(),
self.quotient_polys.as_slice(),
self.lookup_zs.as_slice(),
]
.concat(),
}
} else {
FriOpeningBatch {
values: [
self.constants.as_slice(),
self.plonk_sigmas.as_slice(),
self.wires.as_slice(),
self.plonk_zs.as_slice(),
self.partial_products.as_slice(),
self.quotient_polys.as_slice(),
]
.concat(),
}
};
let zeta_next_batch = if has_lookup {
FriOpeningBatch {
values: [self.plonk_zs_next.clone(), self.lookup_zs_next.clone()].concat(),
}
} else {
FriOpeningBatch {
values: self.plonk_zs_next.clone(),
}
};
FriOpenings {
batches: vec![zeta_batch, zeta_next_batch],
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct OpeningSetTarget<const D: usize> {
pub constants: Vec<ExtensionTarget<D>>,
pub plonk_sigmas: Vec<ExtensionTarget<D>>,
pub wires: Vec<ExtensionTarget<D>>,
pub plonk_zs: Vec<ExtensionTarget<D>>,
pub plonk_zs_next: Vec<ExtensionTarget<D>>,
pub lookup_zs: Vec<ExtensionTarget<D>>,
pub next_lookup_zs: Vec<ExtensionTarget<D>>,
pub partial_products: Vec<ExtensionTarget<D>>,
pub quotient_polys: Vec<ExtensionTarget<D>>,
}
impl<const D: usize> OpeningSetTarget<D> {
pub(crate) fn to_fri_openings(&self) -> FriOpeningsTarget<D> {
let has_lookup = !self.lookup_zs.is_empty();
let zeta_batch = if has_lookup {
FriOpeningBatchTarget {
values: [
self.constants.as_slice(),
self.plonk_sigmas.as_slice(),
self.wires.as_slice(),
self.plonk_zs.as_slice(),
self.partial_products.as_slice(),
self.quotient_polys.as_slice(),
self.lookup_zs.as_slice(),
]
.concat(),
}
} else {
FriOpeningBatchTarget {
values: [
self.constants.as_slice(),
self.plonk_sigmas.as_slice(),
self.wires.as_slice(),
self.plonk_zs.as_slice(),
self.partial_products.as_slice(),
self.quotient_polys.as_slice(),
]
.concat(),
}
};
let zeta_next_batch = if has_lookup {
FriOpeningBatchTarget {
values: [self.plonk_zs_next.clone(), self.next_lookup_zs.clone()].concat(),
}
} else {
FriOpeningBatchTarget {
values: self.plonk_zs_next.clone(),
}
};
FriOpeningsTarget {
batches: vec![zeta_batch, zeta_next_batch],
}
}
}
#[cfg(test)]
mod tests {
#[cfg(not(feature = "std"))]
use alloc::sync::Arc;
#[cfg(feature = "std")]
use std::sync::Arc;
use anyhow::Result;
use itertools::Itertools;
use plonky2_field::types::{Field, Sample};
use super::*;
use crate::field::extension::Extendable;
use crate::fri::FriReductionStrategy;
use crate::gates::lookup_table::LookupTable;
use crate::gates::noop::NoopGate;
use crate::iop::witness::PartialWitness;
use crate::plonk::circuit_builder::CircuitBuilder;
use crate::plonk::circuit_data::{CircuitConfig, CircuitData};
use crate::plonk::config::PoseidonGoldilocksConfig;
use crate::plonk::plonk_common::{salt_size, PlonkOracle};
use crate::plonk::verifier::verify;
#[cfg(feature = "rand")]
fn build_polyfri_compression_fixture() -> Result<(
CircuitData<<PoseidonGoldilocksConfig as GenericConfig<2>>::F, PoseidonGoldilocksConfig, 2>,
ProofWithPublicInputs<
<PoseidonGoldilocksConfig as GenericConfig<2>>::F,
PoseidonGoldilocksConfig,
2,
>,
)> {
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
let config = CircuitConfig::standard_recursion_polyfri_zk_config();
let pw = PartialWitness::new();
let mut builder = CircuitBuilder::<F, D>::new(config);
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);
for _ in 0..32 {
builder.add_gate(NoopGate, vec![]);
}
let data = builder.build::<C>();
let proof = data.prove(pw)?;
verify(proof.clone(), &data.verifier_only, &data.common)?;
Ok((data, proof))
}
#[test]
#[cfg(feature = "rand")]
fn test_proof_compression() -> Result<()> {
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
let mut config = CircuitConfig::standard_recursion_config();
config.fri_config.reduction_strategy = FriReductionStrategy::Fixed(vec![1, 1]);
config.fri_config.num_query_rounds = 50;
let pw = PartialWitness::new();
let mut builder = CircuitBuilder::<F, D>::new(config);
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);
for _ in 0..100 {
builder.add_gate(NoopGate, vec![]);
}
let data = builder.build::<C>();
let proof = data.prove(pw)?;
verify(proof.clone(), &data.verifier_only, &data.common)?;
let compressed_proof = data.compress(proof.clone())?;
let decompressed_compressed_proof = data.decompress(compressed_proof.clone())?;
assert_eq!(proof, decompressed_compressed_proof);
verify(proof, &data.verifier_only, &data.common)?;
data.verify_compressed(compressed_proof)
}
#[test]
#[cfg(feature = "rand")]
fn test_proof_compression_polyfri() -> Result<()> {
let (data, proof) = build_polyfri_compression_fixture()?;
let compressed_proof = data.compress(proof.clone())?;
assert!(compressed_proof
.proof
.opening_proof
.batch_mask_proof
.is_some());
let compressed_proof_bytes = compressed_proof.to_bytes();
let compressed_proof_from_bytes = CompressedProofWithPublicInputs::from_bytes(
compressed_proof_bytes.clone(),
&data.common,
)?;
assert_eq!(compressed_proof, compressed_proof_from_bytes);
let mut legacy_style = compressed_proof.clone();
legacy_style.proof.opening_proof.batch_mask_proof =
proof.proof.opening_proof.batch_mask_proof.clone();
assert!(
compressed_proof_bytes.len() < legacy_style.to_bytes().len(),
"compressing batch-mask Merkle paths should shrink representative masked proofs",
);
let decompressed = data.decompress(compressed_proof.clone())?;
assert_eq!(proof, decompressed);
verify(proof, &data.verifier_only, &data.common)?;
data.verify_compressed(compressed_proof)
}
#[test]
#[cfg(feature = "rand")]
fn test_polyfri_initial_leaf_widths_are_logical_not_split() -> Result<()> {
let (data, proof) = build_polyfri_compression_fixture()?;
let query_round = &proof.proof.opening_proof.query_round_proofs[0];
let wires_leaf = &query_round.initial_trees_proof.evals_proofs[PlonkOracle::WIRES.index].0;
let zs_leaf =
&query_round.initial_trees_proof.evals_proofs[PlonkOracle::ZS_PARTIAL_PRODUCTS.index].0;
let expected_wires_leaf_len = data.common.fri_oracle_layouts[PlonkOracle::WIRES.index]
.logical_polys
+ salt_size(data.common.fri_params.leaf_hiding && PlonkOracle::WIRES.blinding);
let expected_zs_leaf_len =
data.common.fri_oracle_layouts[PlonkOracle::ZS_PARTIAL_PRODUCTS.index].logical_polys
+ salt_size(
data.common.fri_params.leaf_hiding && PlonkOracle::ZS_PARTIAL_PRODUCTS.blinding,
);
assert_eq!(wires_leaf.len(), expected_wires_leaf_len);
assert_eq!(zs_leaf.len(), expected_zs_leaf_len);
Ok(())
}
#[test]
#[cfg(feature = "rand")]
fn test_polyfri_initial_leaf_value_tamper_fails() -> Result<()> {
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
let (data, mut proof) = build_polyfri_compression_fixture()?;
proof.proof.opening_proof.query_round_proofs[0]
.initial_trees_proof
.evals_proofs[PlonkOracle::WIRES.index]
.0[0] += <<C as GenericConfig<D>>::F as crate::field::types::Field>::ONE;
assert!(data.verify(proof).is_err());
Ok(())
}
#[test]
#[cfg(feature = "rand")]
fn test_polyfri_batch_mask_query_value_tamper_fails() -> Result<()> {
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
let (data, mut proof) = build_polyfri_compression_fixture()?;
proof
.proof
.opening_proof
.batch_mask_proof
.as_mut()
.expect("PolyFri proofs must carry an explicit batch-mask proof")
.query_openings[0]
.values[0] += <<C as GenericConfig<D>>::F as Extendable<D>>::Extension::ONE;
assert!(data.verify(proof).is_err());
Ok(())
}
#[test]
#[cfg(feature = "rand")]
fn test_compressed_polyfri_batch_mask_value_tamper_fails() -> Result<()> {
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
let (data, proof) = build_polyfri_compression_fixture()?;
let mut compressed_proof = data.compress(proof)?;
compressed_proof
.proof
.opening_proof
.batch_mask_proof
.as_mut()
.expect("PolyFri compressed proofs must carry the explicit batch-mask proof")
.query_openings[0]
.values[0] += <F as Extendable<D>>::Extension::ONE;
assert!(data.verify_compressed(compressed_proof).is_err());
Ok(())
}
#[test]
#[cfg(feature = "rand")]
fn test_compressed_polyfri_batch_mask_path_tamper_fails() -> Result<()> {
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<2>>::F;
let (data, proof) = build_polyfri_compression_fixture()?;
let mut compressed_proof = data.compress(proof)?;
let sibling = compressed_proof
.proof
.opening_proof
.batch_mask_proof
.as_mut()
.expect("PolyFri compressed proofs must carry the explicit batch-mask proof")
.query_openings
.iter_mut()
.flat_map(|query_opening| query_opening.merkle_proof.siblings.iter_mut())
.next()
.expect("compressed batch-mask proof should retain at least one sibling");
sibling.elements[0] += <F as Field>::ONE;
assert!(data.verify_compressed(compressed_proof).is_err());
Ok(())
}
#[test]
fn test_proof_compression_lookup() -> Result<()> {
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
use plonky2_field::types::Field;
type F = <C as GenericConfig<D>>::F;
let mut config = CircuitConfig::standard_recursion_config();
config.fri_config.reduction_strategy = FriReductionStrategy::Fixed(vec![1, 1]);
config.fri_config.num_query_rounds = 50;
let pw = PartialWitness::new();
let tip5_table = vec![
0, 7, 26, 63, 124, 215, 85, 254, 214, 228, 45, 185, 140, 173, 33, 240, 29, 177, 176,
32, 8, 110, 87, 202, 204, 99, 150, 106, 230, 14, 235, 128, 213, 239, 212, 138, 23, 130,
208, 6, 44, 71, 93, 116, 146, 189, 251, 81, 199, 97, 38, 28, 73, 179, 95, 84, 152, 48,
35, 119, 49, 88, 242, 3, 148, 169, 72, 120, 62, 161, 166, 83, 175, 191, 137, 19, 100,
129, 112, 55, 221, 102, 218, 61, 151, 237, 68, 164, 17, 147, 46, 234, 203, 216, 22,
141, 65, 57, 123, 12, 244, 54, 219, 231, 96, 77, 180, 154, 5, 253, 133, 165, 98, 195,
205, 134, 245, 30, 9, 188, 59, 142, 186, 197, 181, 144, 92, 31, 224, 163, 111, 74, 58,
69, 113, 196, 67, 246, 225, 10, 121, 50, 60, 157, 90, 122, 2, 250, 101, 75, 178, 159,
24, 36, 201, 11, 243, 132, 198, 190, 114, 233, 39, 52, 21, 209, 108, 238, 91, 187, 18,
104, 194, 37, 153, 34, 200, 143, 126, 155, 236, 118, 64, 80, 172, 89, 94, 193, 135,
183, 86, 107, 252, 13, 167, 206, 136, 220, 207, 103, 171, 160, 76, 182, 227, 217, 158,
56, 174, 4, 66, 109, 139, 162, 184, 211, 249, 47, 125, 232, 117, 43, 16, 42, 127, 20,
241, 25, 149, 105, 156, 51, 53, 168, 145, 247, 223, 79, 78, 226, 15, 222, 82, 115, 70,
210, 27, 41, 1, 170, 40, 131, 192, 229, 248, 255,
];
let table: LookupTable = Arc::new((0..256).zip_eq(tip5_table).collect());
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let lut_index = builder.add_lookup_table_from_pairs(table);
let x = F::TWO;
let out = builder.constant(F::from_canonical_usize(26));
let xt = builder.constant(x);
let look_out = builder.add_lookup_from_index(xt, lut_index);
builder.connect(look_out, out);
for _ in 0..100 {
builder.add_gate(NoopGate, vec![]);
}
let data = builder.build::<C>();
let proof = data.prove(pw)?;
verify(proof.clone(), &data.verifier_only, &data.common)?;
let compressed_proof = data.compress(proof.clone())?;
let decompressed_compressed_proof = data.decompress(compressed_proof.clone())?;
assert_eq!(proof, decompressed_compressed_proof);
verify(proof, &data.verifier_only, &data.common)?;
data.verify_compressed(compressed_proof)
}
}