add serialization to circuit data.

This commit is contained in:
M Alghazwi 2025-06-17 12:29:20 +02:00
parent ccbf63447b
commit c9224bf48f
No known key found for this signature in database
GPG Key ID: 646E567CAD7DB607
29 changed files with 378 additions and 9 deletions

0
codex-plonky2-circuits/.gitignore vendored Normal file → Executable file
View File

0
codex-plonky2-circuits/Cargo.toml Normal file → Executable file
View File

0
codex-plonky2-circuits/README.md Normal file → Executable file
View File

0
codex-plonky2-circuits/src/bn254_wrapper/bn254_fr.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/bn254_wrapper/config.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/bn254_wrapper/mod.rs Normal file → Executable file
View File

View File

View File

12
codex-plonky2-circuits/src/bn254_wrapper/wrap.rs Normal file → Executable file
View File

@ -130,16 +130,16 @@ impl<
if !path.as_ref().exists() {
fs::create_dir_all(&path)?;
}
let common_data_file = File::create(path.as_ref().join("common_circuit_data.json"))?;
let common_data_file = File::create(path.as_ref().join("verifier_data/common_circuit_data.json"))?;
serde_json::to_writer(&common_data_file, &self.common_data)?;
println!("Succesfully wrote common circuit data to common_circuit_data.json");
let verifier_data_file =
File::create(path.as_ref().join("verifier_only_circuit_data.json"))?;
File::create(path.as_ref().join("verifier_data/verifier_only_circuit_data.json"))?;
serde_json::to_writer(&verifier_data_file, &self.verifier_data)?;
println!("Succesfully wrote verifier data to verifier_only_circuit_data.json");
let proof_file = File::create(path.as_ref().join("proof_with_public_inputs.json"))?;
let proof_file = File::create(path.as_ref().join("verifier_data/proof_with_public_inputs.json"))?;
serde_json::to_writer(&proof_file, &self.proof)?;
println!("Succesfully wrote proof to proof_with_public_inputs.json");
@ -172,10 +172,6 @@ mod tests {
let conf = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(conf);
// let a = builder.add_virtual_public_input();
// let b = builder.add_virtual_public_input();
// let c = builder.add(a,b);
// builder.register_public_input(c);
for _ in 0..(4096+10) {
builder.add_gate(NoopGate, vec![]);
}
@ -185,8 +181,6 @@ mod tests {
// Set up the dummy circuit and wrapper.
let dummy_circuit = builder.build::<InnerParameters>();
let mut pw = PartialWitness::new();
// pw.set_target(a, GoldilocksField::from_canonical_u64(1))?;
// pw.set_target(b, GoldilocksField::from_canonical_u64(2))?;
pw.set_target(t, F::ZERO).expect("faulty assign");
println!(
"dummy circuit degree: {}",

0
codex-plonky2-circuits/src/bundle/mod.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/circuit_helper/mod.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/circuits/keyed_compress.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/circuits/merkle_circuit.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/circuits/mod.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/circuits/params.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/circuits/sample_cells.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/circuits/serialization.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/circuits/sponge.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/circuits/utils.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/error.rs Normal file → Executable file
View File

1
codex-plonky2-circuits/src/lib.rs Normal file → Executable file
View File

@ -4,5 +4,6 @@ pub mod error;
pub mod circuit_helper;
mod bundle;
pub mod bn254_wrapper;
pub mod serialization;
pub type Result<T> = core::result::Result<T, error::CircuitError>;

0
codex-plonky2-circuits/src/recursion/compress.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/recursion/dummy_gen.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/recursion/leaf.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/recursion/mod.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/recursion/node.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/recursion/pi_verifier.rs Normal file → Executable file
View File

0
codex-plonky2-circuits/src/recursion/utils.rs Normal file → Executable file
View File

View File

@ -0,0 +1,374 @@
use serde::Serialize;
use std::{fs, io};
use std::path::Path;
use anyhow::Context;
use plonky2::hash::hash_types::RichField;
use plonky2::plonk::circuit_data::{CircuitData, ProverCircuitData, VerifierCircuitData};
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
use plonky2_field::extension::Extendable;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use plonky2::plonk::proof::ProofWithPublicInputs;
use serde::de::DeserializeOwned;
use plonky2_poseidon2::serialization::{DefaultGateSerializer, DefaultGeneratorSerializer};
/// File constants paths - Prover
pub const PROVER_CIRC_DATA_JSON: &str = "prover_data/prover_circuit_data.bin";
pub const TARGETS_JSON: &str = "prover_data/targets.json";
/// File constants paths - Verifier
pub const VERIFIER_CIRC_DATA_JSON: &str = "verifier_data/verifier_circuit_data.bin";
pub const PROOF_JSON: &str = "verifier_data/proof_with_public_inputs.json";
// --------------------- helper fn --------------------------
/// Writes the provided bytes to the specified file path using `std::fs::write`.
pub fn write_bytes_to_file<P: AsRef<Path>>(data: &[u8], path: P) -> io::Result<()> {
fs::write(path, data)
}
/// Reads the contents of the specified file and returns them as a vector of bytes using `std::fs::read`.
pub fn read_bytes_from_file<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
fs::read(path)
}
/// Ensures that the parent directory of the given file path exists.
/// If it does not exist, the function creates the entire directory path.
pub fn ensure_parent_directory_exists<P: AsRef<Path>>(path: P) -> anyhow::Result<()> {
if let Some(parent) = path.as_ref().parent() {
fs::create_dir_all(parent)
.with_context(|| format!("Failed to create directory {:?}", parent))?;
}
Ok(())
}
//--------------------- EXPORT -----------------------------
pub fn export_prover_circuit_data<
F: RichField + Extendable<D> + Poseidon2 + Serialize,
C: GenericConfig<D, F = F> + Default + Serialize + 'static,
const D: usize,
P: AsRef<Path>,
>(
prover_data: ProverCircuitData<F, C, D>,
base_path: P,
) -> anyhow::Result<()>
where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>,
{
let gate_serializer = DefaultGateSerializer;
let generator_serializer = DefaultGeneratorSerializer::<C, D>::default();
// Serialize prover_data → Vec<u8>
let bytes = prover_data
.to_bytes(&gate_serializer, &generator_serializer)
.map_err(|e| anyhow::anyhow!("Failed to serialize prover data: {:?}", e))?;
// Build output path: `{base_path}/prover_data/prover_circ_data.bin`
let out_path = base_path.as_ref().join(PROVER_CIRC_DATA_JSON);
// Ensure parent directory exists
ensure_parent_directory_exists(&out_path)
.with_context(|| format!("Could not create directory for {:?}", out_path))?;
// Write file
write_bytes_to_file(&bytes, &out_path)
.with_context(|| format!("Failed to write prover data to {:?}", out_path))?;
Ok(())
}
/// Export only the VerifierCircuitData to `{base_path}/verifier_data/verifier_circ_data.bin`.
pub fn export_verifier_circuit_data<
F: RichField + Extendable<D> + Poseidon2 + Serialize,
C: GenericConfig<D, F = F> + Serialize,
const D: usize,
P: AsRef<Path>,
>(
verifier_data: VerifierCircuitData<F, C, D>,
base_path: P,
) -> anyhow::Result<()>
{
let gate_serializer = DefaultGateSerializer;
// Serialize verifier_data → Vec<u8>
let bytes = verifier_data
.to_bytes(&gate_serializer)
.map_err(|e| anyhow::anyhow!("Failed to serialize verifier data: {:?}", e))?;
// Build output path: `{base_path}/verifier_data/verifier_circ_data.bin`
let out_path = base_path.as_ref().join(VERIFIER_CIRC_DATA_JSON);
// Ensure parent directory exists
ensure_parent_directory_exists(&out_path)
.with_context(|| format!("Could not create directory for {:?}", out_path))?;
// Write file
write_bytes_to_file(&bytes, &out_path)
.with_context(|| format!("Failed to write verifier data to {:?}", out_path))?;
Ok(())
}
/// Export only the “targets” (any `T: Serialize`) to `{base_path}/prover_data/targets.json`.
pub fn export_circuit_targets<
T: Serialize,
P: AsRef<Path>,
>(
targets: &T,
base_path: P,
) -> anyhow::Result<()>
{
// Serialize `targets` → Vec<u8> (JSON)
let bytes = serde_json::to_vec(targets)
.context("Failed to serialize circuit targets to JSON")?;
// Build output path: `{base_path}/prover_data/targets.json`
let out_path = base_path.as_ref().join(TARGETS_JSON);
// Ensure parent directory exists
ensure_parent_directory_exists(&out_path)
.with_context(|| format!("Could not create directory for {:?}", out_path))?;
// Write file
write_bytes_to_file(&bytes, &out_path)
.with_context(|| format!("Failed to write circuit targets to {:?}", out_path))?;
Ok(())
}
/// Convenience function that calls all three exports in one shot.
/// ‣ Exports prover data, verifier data, and targets under `base_path`.
pub fn export_circuit_data<
F: RichField + Extendable<D> + Poseidon2 + Serialize,
C: GenericConfig<D, F = F> + Default + Serialize + 'static,
const D: usize,
P: AsRef<Path>,
>(
circ_data: CircuitData<F, C, D>,
targets: &impl Serialize,
base_path: P,
) -> anyhow::Result<()>
where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>,
{
// 1. Split into prover_data + verifier_data
let verifier_data: VerifierCircuitData<F, C, D> = circ_data.verifier_data();
let prover_data = circ_data.prover_data();
// 2. Export each separately
export_prover_circuit_data(prover_data, &base_path)
.context("export_prover_circuit_data failed")?;
export_verifier_circuit_data(verifier_data, &base_path)
.context("export_verifier_circuit_data failed")?;
export_circuit_targets(targets, &base_path)
.context("export_circuit_targets failed")?;
Ok(())
}
/// Serialize `proof_with_pis` into JSON and write it under a base directory
pub fn export_proof_with_pi<F, C, const D: usize, P: AsRef<Path>>(
proof_with_pis: &ProofWithPublicInputs<F, C, D>,
base_path: P,
) -> anyhow::Result<()>
where
F: RichField + Extendable<D> + Poseidon2 + Serialize,
C: GenericConfig<D, F = F> + Serialize,
{
// Serialize to JSON bytes
let proof_serialized = serde_json::to_vec(&proof_with_pis)
.map_err(|e| anyhow::anyhow!("Failed to serialize proof with public input: {:?}", e))?;
// the full file path
let proof_file_path = base_path.as_ref().join(PROOF_JSON);
// ensure parent directory exists
ensure_parent_directory_exists(&proof_file_path)?;
// write it out
write_bytes_to_file(&proof_serialized, &proof_file_path)
.with_context(|| format!("Failed to write proof to {:?}", proof_file_path))?;
Ok(())
}
//------------------------- IMPORT --------------------------
/// Import `ProverCircuitData<F, C, D>` from disk under the given `base_path`.
pub fn import_prover_circuit_data<F, C, const D: usize, P: AsRef<Path>>(
base_path: P,
) -> anyhow::Result<ProverCircuitData<F, C, D>>
where
F: RichField + Extendable<D> + Poseidon2 + Serialize,
C: GenericConfig<D, F = F> + Default + Serialize + 'static,
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>,
{
let gate_serializer = DefaultGateSerializer;
let generator_serializer = DefaultGeneratorSerializer::<C, D>::default();
// the full path`
let full_path = base_path.as_ref().join(PROVER_CIRC_DATA_JSON);
// Read raw bytes
let bytes = read_bytes_from_file(&full_path)
.with_context(|| format!("Failed to read prover circuit data from {:?}", full_path))?;
// Deserialize
let prover_data = ProverCircuitData::<F, C, D>::from_bytes(
&bytes,
&gate_serializer,
&generator_serializer,
)
.map_err(|e| anyhow::anyhow!("Failed to deserialize prover data from {:?}: {:?}", full_path, e))?;
Ok(prover_data)
}
/// Import `VerifierCircuitData<F, C, D>` from disk under `base_path`.
pub fn import_verifier_circuit_data<F, C, const D: usize, P: AsRef<Path>>(
base_path: P,
) -> anyhow::Result<VerifierCircuitData<F, C, D>>
where
F: RichField + Extendable<D> + Poseidon2 + Serialize,
C: GenericConfig<D, F = F> + Serialize,
{
let gate_serializer = DefaultGateSerializer;
let full_path = base_path.as_ref().join(VERIFIER_CIRC_DATA_JSON);
let bytes = read_bytes_from_file(&full_path)
.with_context(|| format!("Failed to read verifier circuit data from {:?}", full_path))?;
let verifier_data = VerifierCircuitData::<F, C, D>::from_bytes(bytes, &gate_serializer)
.map_err(|e| anyhow::anyhow!("Failed to deserialize verifier data from {:?}: {:?}", full_path, e))?;
Ok(verifier_data)
}
/// Import a `ProofWithPublicInputs<F, C, D>` from JSON under `base_path`.
pub fn import_proof_with_pi<F, C, const D: usize, P: AsRef<Path>>(
base_path: P,
) -> anyhow::Result<ProofWithPublicInputs<F, C, D>>
where
F: RichField + Extendable<D> + Poseidon2,
C: GenericConfig<D, F = F>,
{
// Build full path
let full_path = base_path.as_ref().join(PROOF_JSON);
// Read JSON string
let proof_json_str = fs::read_to_string(&full_path)
.with_context(|| format!("Failed to read proof from {:?}", full_path))?;
// Deserialize
let proof = serde_json::from_str(&proof_json_str)
.with_context(|| format!("Failed to deserialize proof at {:?}", full_path))?;
Ok(proof)
}
/// Import the circuit targets from the JSON file.
/// This function is generic over the type `T` that represents the targets and
/// must implement `DeserializeOwned` so that it can be deserialized.
pub fn import_targets<T, P: AsRef<Path>>(base_path: P) -> anyhow::Result<T>
where
T: DeserializeOwned,
{
let full_path = base_path.as_ref().join(TARGETS_JSON);
let targets_str = fs::read_to_string(&full_path)
.with_context(|| format!("Failed to read targets from {:?}", full_path))?;
let targets = serde_json::from_str(&targets_str)
.with_context(|| format!("Failed to deserialize targets from {:?}", full_path))?;
Ok(targets)
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
use plonky2::gates::noop::NoopGate;
use plonky2::iop::target::Target;
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::plonk::circuit_data::{CircuitConfig, ProverCircuitData, VerifierCircuitData};
use plonky2::plonk::config::PoseidonGoldilocksConfig;
use plonky2::plonk::proof::ProofWithPublicInputs;
use plonky2_field::goldilocks_field::GoldilocksField;
use plonky2_field::types::Field;
const D: usize = 2;
type F = GoldilocksField;
type C = PoseidonGoldilocksConfig;
#[test]
fn test_export_and_import_circuit_data_roundtrip() -> anyhow::Result<()> {
use serde::Serialize;
#[derive(Clone, Debug, PartialEq, Serialize, serde::Deserialize)]
struct DummyTargets {
a: Target,
}
let conf = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(conf);
for _ in 0..128 {
builder.add_gate(NoopGate, vec![]);
}
let t = builder.add_virtual_public_input();
let dummy_circuit = builder.build::<C>();
let mut pw = PartialWitness::new();
pw.set_target(t, F::ZERO).expect("faulty assign");
let dummy_inner_proof = dummy_circuit.prove(pw).unwrap();
assert!(dummy_circuit.verify(dummy_inner_proof.clone()).is_ok());
let dummy_t = DummyTargets{a: t};
let base_output = Path::new("../output/sampling_circ");
export_circuit_data::<F,C,D,_>(dummy_circuit, &dummy_t, base_output)?;
let imported_prover: ProverCircuitData<F, C, D> =
import_prover_circuit_data(base_output)?;
let imported_verifier: VerifierCircuitData<F, C, D> =
import_verifier_circuit_data(base_output)?;
let imported_target: DummyTargets = import_targets(base_output)?;
let mut pw = PartialWitness::new();
pw.set_target(imported_target.a, F::ZERO).expect("faulty assign");
let proof_with_pis = imported_prover.prove(pw).unwrap();
assert!(
imported_verifier.verify(proof_with_pis).is_ok(),
"imported verifier failed to verify"
);
Ok(())
}
#[test]
fn test_export_and_import_proof_with_pi() -> anyhow::Result<()> {
let conf = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(conf);
for _ in 0..128 {
builder.add_gate(NoopGate, vec![]);
}
let t = builder.add_virtual_public_input();
let dummy_circuit = builder.build::<C>();
let mut pw = PartialWitness::new();
pw.set_target(t, F::ZERO).expect("faulty assign");
let dummy_inner_proof = dummy_circuit.prove(pw).unwrap();
assert!(dummy_circuit.verify(dummy_inner_proof.clone()).is_ok());
let base_output = Path::new("../output/sampling_circ");
export_proof_with_pi(&dummy_inner_proof, base_output)?;
let imported_proof: ProofWithPublicInputs<F, C, D> =
import_proof_with_pi(base_output)?;
assert!(
dummy_circuit.verify(imported_proof).is_ok(),
"Imported proof failed verification"
);
Ok(())
}
}