From 656cae77e0409f04e7135ec2fad035f1fe87fae9 Mon Sep 17 00:00:00 2001 From: "Eduard S." Date: Fri, 8 Aug 2025 09:33:44 -0700 Subject: [PATCH] Add versioning features (#387) - Add a function to calculate the hash of the `CommonCircuitData`. The hash uniquely identify the `CommonCircuitData` used for a circuit/proof. Serializing the struct is not enough because the polynomial identities of the custom gates are not serialized (only their parameters are); so I made a function to extract "fingerprints" of the custom gates by evaluating them over a predefined list of uniform values, and then doing a random linear combination over the results. - Store the full verifier only circuit data of a proof in the MainPod so that we can verify pods from old circuits in new circuits and code - Store the hash of the `CommonCircuitData` in the MainPod so that we can reject verifying old pods that use a different `CommonCircuitData` than the current one. This has two goals - If the `CommonCircuitData` changes it's very likely that the verification will fail, but it will be hard to debug. Doing this early check helps identify the origin of the verification failure as early as possible - There's a chance that the verification could succeed when the `CommonCircuitData` changes, and that could be dangerous because the verification will be doing different checks than the ones intended for the original proof, so we may be skipping some constraints that could lead to exploiting the system. For this reason, whenever the common circuit data hash changes, all previous verifying keys should be discarded (that is, not included in the VDSet) - The fingerprint only has ~64 bits and the "random evaluation point" is fixed. The assumption is that the pod developers are not malicious and are not changing the gates such that different gates give the same fingerprint. With this assumption, I find it reasonable to assume that with high probability if a gate changes, its fingerprint changes as well. - Add a github action that updates a wiki page with a table that contains: date, commit, params hash (with a link to the actual params), verifier data only circuit data hash and common circuit data hash. This will make it easy to track when the common circuit data changes as well as track the verifier data corresponding to various versions (identified by commit) - The edited page is this one https://github.com/0xPARC/pod2/wiki/MainPod-circuit-info Resolve https://github.com/0xPARC/pod2/issues/386 Summary of breaking changes: - The `RecursivePod` trait has a new method `common_hash` that needs to return the result of `hash_common_data` on the `CommonCircuitData` that the circuit uses. --- .../mainpod-circuit-info-publish.yml | 55 +++++++++ .../scripts/mainpod-circuit-info-table.sh | 14 +++ .../workflows/scripts/mainpod-circuit-info.sh | 5 + Cargo.toml | 1 + src/backends/plonky2/emptypod.rs | 35 ++++-- src/backends/plonky2/mainpod/mod.rs | 50 +++++++- src/backends/plonky2/mock/emptypod.rs | 3 + src/backends/plonky2/mock/mainpod.rs | 3 + src/backends/plonky2/mod.rs | 112 +++++++++++++++++- src/backends/plonky2/serialization.rs | 50 ++++++-- src/bin/mainpod_circuit_info.rs | 53 +++++++++ src/middleware/mod.rs | 3 + 12 files changed, 357 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/mainpod-circuit-info-publish.yml create mode 100755 .github/workflows/scripts/mainpod-circuit-info-table.sh create mode 100755 .github/workflows/scripts/mainpod-circuit-info.sh create mode 100644 src/bin/mainpod_circuit_info.rs diff --git a/.github/workflows/mainpod-circuit-info-publish.yml b/.github/workflows/mainpod-circuit-info-publish.yml new file mode 100644 index 0000000..1727342 --- /dev/null +++ b/.github/workflows/mainpod-circuit-info-publish.yml @@ -0,0 +1,55 @@ +--- +name: Publish MainPod circuit info + +on: + push: + branches: ["main"] + + # Allows to run this workflow manually from the Actions tab + workflow_dispatch: + +concurrency: + group: wiki + cancel-in-progress: true + +permissions: + contents: write + +jobs: + wiki: + name: Update Wiki with new MainPod circuit info + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + repository: ${{github.repository}} + path: ${{github.repository}} + + - name: Checkout Wiki + uses: actions/checkout@v4 + with: + repository: ${{github.repository}}.wiki + path: ${{github.repository}}.wiki + + - name: Set up Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + + - name: Push to wiki + run: | + set -e + + cd $GITHUB_WORKSPACE/${{github.repository}} + table_entry=$(.github/workflows/scripts/mainpod-circuit-info-table.sh) + params=$(.github/workflows/scripts/mainpod-circuit-info.sh params) + data=$(.github/workflows/scripts/mainpod-circuit-info.sh circuit-info) + params_hash=$(echo "${data}" | jq --raw-output .params_hash) + + cd $GITHUB_WORKSPACE/${{github.repository}}.wiki + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + mkdir -p params + echo "$params" > params/${params_hash}.json + echo "$table_entry" >> MainPod-circuit-info.md + git add . + git diff-index --quiet HEAD || git commit -m "action: update MainPod-circuit-info" && git push diff --git a/.github/workflows/scripts/mainpod-circuit-info-table.sh b/.github/workflows/scripts/mainpod-circuit-info-table.sh new file mode 100755 index 0000000..1753b3f --- /dev/null +++ b/.github/workflows/scripts/mainpod-circuit-info-table.sh @@ -0,0 +1,14 @@ +#/bin/sh + +set -e + +# Generate the markdown table entry of the MainPod circuit information + +scripts_dir=$(dirname "$0") +data=$(./${scripts_dir}/mainpod-circuit-info.sh circuit-info) +date=$(date --utc --iso-8601=minutes) +commit=$(git rev-parse HEAD) +params_hash=$(echo "$data" | jq --raw-output .params_hash) +verifier_hash=$(echo "$data" | jq --raw-output .verifier_hash) +common_hash=$(echo "$data" | jq --raw-output .common_hash) +echo "| $date | [\`${commit}\`](https://github.com/0xPARC/pod2/commit/${commit}) | [\`${params_hash}\`](https://raw.githubusercontent.com/wiki/0xPARC/pod2/params/${params_hash}.json) | \`${verifier_hash}\` | \`${common_hash}\` |" diff --git a/.github/workflows/scripts/mainpod-circuit-info.sh b/.github/workflows/scripts/mainpod-circuit-info.sh new file mode 100755 index 0000000..1d52099 --- /dev/null +++ b/.github/workflows/scripts/mainpod-circuit-info.sh @@ -0,0 +1,5 @@ +#/bin/sh + +set -e + +cargo run --release --no-default-features --features=zk,backend_plonky2,disk_cache --bin mainpod_circuit_info -- $1 diff --git a/Cargo.toml b/Cargo.toml index 458c0d8..3475717 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ minicbor-serde = { version = "0.5.0", features = ["std"], optional = true } serde_bytes = "0.11" serde_arrays = "0.2.0" sha2 = { version = "0.10.9" } +rand_chacha = "0.3.1" # Uncomment for debugging with https://github.com/ed255/plonky2/ at branch `feat/debug`. The repo directory needs to be checked out next to the pod2 repo directory. # [patch."https://github.com/0xPARC/plonky2"] diff --git a/src/backends/plonky2/emptypod.rs b/src/backends/plonky2/emptypod.rs index 15f75fa..f9eb24b 100644 --- a/src/backends/plonky2/emptypod.rs +++ b/src/backends/plonky2/emptypod.rs @@ -18,12 +18,15 @@ use crate::{ common::{Flattenable, StatementTarget}, mainpod::{calculate_id_circuit, PI_OFFSET_ID}, }, - deserialize_proof, + deserialize_proof, deserialize_verifier_only, error::{Error, Result}, + hash_common_data, mainpod::{self, calculate_id}, recursion::pad_circuit, - serialization::{CircuitDataSerializer, VerifierCircuitDataSerializer}, - serialize_proof, + serialization::{ + CircuitDataSerializer, VerifierCircuitDataSerializer, VerifierOnlyCircuitDataSerializer, + }, + serialize_proof, serialize_verifier_only, }, cache::{self, CacheEntry}, middleware::{ @@ -73,6 +76,8 @@ impl EmptyPodVerifyTarget { pub struct EmptyPod { params: Params, id: PodId, + verifier_only: VerifierOnlyCircuitDataSerializer, + common_hash: String, vd_set: VDSet, proof: Proof, } @@ -119,17 +124,19 @@ fn build() -> Result<(EmptyPodVerifyTarget, CircuitData)> { } impl EmptyPod { - pub fn new(params: &Params, vd_set: VDSet) -> Result { - let standard_empty_pod_data = cache_get_standard_empty_pod_circuit_data(); - let (empty_pod_verify_target, data) = &*standard_empty_pod_data; + fn new(params: &Params, vd_set: VDSet) -> Result { + let (empty_pod_verify_target, data) = &*cache_get_standard_empty_pod_circuit_data(); let mut pw = PartialWitness::::new(); empty_pod_verify_target.set_targets(&mut pw, vd_set.root())?; let proof = timed!("EmptyPod prove", data.prove(pw)?); let id = &proof.public_inputs[PI_OFFSET_ID..PI_OFFSET_ID + HASH_SIZE]; let id = PodId(Hash([id[0], id[1], id[2], id[3]])); + let common_hash = hash_common_data(&data.common).expect("hash ok"); Ok(EmptyPod { params: params.clone(), + verifier_only: VerifierOnlyCircuitDataSerializer(data.verifier_only.clone()), + common_hash, id, vd_set, proof: proof.proof, @@ -152,6 +159,8 @@ impl EmptyPod { #[derive(Serialize, Deserialize)] struct Data { proof: String, + verifier_only: String, + common_hash: String, } impl Pod for EmptyPod { @@ -199,6 +208,8 @@ impl Pod for EmptyPod { fn serialize_data(&self) -> serde_json::Value { serde_json::to_value(Data { proof: serialize_proof(&self.proof), + verifier_only: serialize_verifier_only(&self.verifier_only), + common_hash: self.common_hash.clone(), }) .expect("serialization to json") } @@ -206,11 +217,10 @@ impl Pod for EmptyPod { impl RecursivePod for EmptyPod { fn verifier_data(&self) -> VerifierOnlyCircuitData { - let standard_empty_pod_verifier_circuit_data = - cache_get_standard_empty_pod_verifier_circuit_data(); - standard_empty_pod_verifier_circuit_data - .verifier_only - .clone() + self.verifier_only.0.clone() + } + fn common_hash(&self) -> String { + self.common_hash.clone() } fn proof(&self) -> Proof { self.proof.clone() @@ -227,9 +237,12 @@ impl RecursivePod for EmptyPod { let data: Data = serde_json::from_value(data)?; let common_circuit_data = cache_get_standard_rec_main_pod_common_circuit_data(); let proof = deserialize_proof(&common_circuit_data, &data.proof)?; + let verifier_only = deserialize_verifier_only(&data.verifier_only)?; Ok(Box::new(Self { params, id, + verifier_only: VerifierOnlyCircuitDataSerializer(verifier_only), + common_hash: data.common_hash, vd_set, proof, })) diff --git a/src/backends/plonky2/mainpod/mod.rs b/src/backends/plonky2/mainpod/mod.rs index 7939e60..edabc74 100644 --- a/src/backends/plonky2/mainpod/mod.rs +++ b/src/backends/plonky2/mainpod/mod.rs @@ -14,9 +14,10 @@ use crate::{ cache::{self, CacheEntry}, cache_get_standard_rec_main_pod_common_circuit_data, circuits::mainpod::{CustomPredicateVerification, MainPodVerifyInput, MainPodVerifyTarget}, - deserialize_proof, + deserialize_proof, deserialize_verifier_only, emptypod::EmptyPod, error::{Error, Result}, + hash_common_data, mock::emptypod::MockEmptyPod, primitives::{ec::schnorr::SecretKey, merkletree::MerkleClaimAndProof}, recursion::{ @@ -25,7 +26,7 @@ use crate::{ serialization::{ CircuitDataSerializer, CommonCircuitDataSerializer, VerifierCircuitDataSerializer, }, - serialize_proof, + serialize_proof, serialize_verifier_only, signedpod::SignedPod, }, middleware::{ @@ -482,10 +483,12 @@ impl PodProver for Prover { // get the id out of the public statements let id: PodId = PodId(calculate_id(&public_statements, params)); + let common_hash: String = cache_get_rec_main_pod_common_hash(params).clone(); let proofs = inputs .recursive_pods .iter() .map(|pod| { + assert_eq!(pod.common_hash(), common_hash); assert_eq!(inputs.vd_set.root(), pod.vd_set().root()); ProofWithPublicInputs { proof: pod.proof(), @@ -528,6 +531,8 @@ impl PodProver for Prover { Ok(Box::new(MainPod { params: params.clone(), + verifier_only: circuit_data.verifier_only.clone(), + common_hash, id, vd_set: inputs.vd_set, public_statements, @@ -540,6 +545,8 @@ impl PodProver for Prover { pub struct MainPod { params: Params, id: PodId, + verifier_only: VerifierOnlyCircuitData, + common_hash: String, /// vds_root is the merkle-root of the `VDSet`, which contains the /// verifier_data hashes of the allowed set of VerifierOnlyCircuitData, for /// the succession of recursive MainPods, which when proving the POD, it is @@ -605,10 +612,20 @@ pub fn cache_get_rec_main_pod_common_circuit_data( .expect("cache ok") } +pub fn cache_get_rec_main_pod_common_hash(params: &Params) -> CacheEntry { + cache::get("rec_main_pod_common_hash", params, |params| { + let common = &*cache_get_rec_main_pod_common_circuit_data(params); + hash_common_data(common).expect("hash ok") + }) + .expect("cache ok") +} + #[derive(Serialize, Deserialize)] struct Data { public_statements: Vec, proof: String, + verifier_only: String, + common_hash: String, } impl MainPod { @@ -626,6 +643,14 @@ impl Pod for MainPod { &self.params } fn verify(&self) -> Result<()> { + // 0. Assert that the CommonCircuitData of the pod is the current one + let expect_common_hash = &*cache_get_rec_main_pod_common_hash(&self.params); + if &self.common_hash != expect_common_hash { + return Err(Error::custom(format!( + "The pod common_hash: {} is different than the current one: {}", + self.common_hash, expect_common_hash, + ))); + } // 2. get the id out of the public statements let id = PodId(calculate_id(&self.public_statements, &self.params)); if id != self.id { @@ -679,6 +704,8 @@ impl Pod for MainPod { serde_json::to_value(Data { proof: serialize_proof(&self.proof), public_statements: self.public_statements.clone(), + verifier_only: serialize_verifier_only(&self.verifier_only), + common_hash: self.common_hash.clone(), }) .expect("serialization to json") } @@ -686,9 +713,10 @@ impl Pod for MainPod { impl RecursivePod for MainPod { fn verifier_data(&self) -> VerifierOnlyCircuitData { - let rec_main_pod_verifier_circuit_data = - cache_get_rec_main_pod_verifier_circuit_data(&self.params); - rec_main_pod_verifier_circuit_data.verifier_only.clone() + self.verifier_only.clone() + } + fn common_hash(&self) -> String { + self.common_hash.clone() } fn proof(&self) -> Proof { self.proof.clone() @@ -705,9 +733,12 @@ impl RecursivePod for MainPod { let data: Data = serde_json::from_value(data)?; let common = cache_get_rec_main_pod_common_circuit_data(¶ms); let proof = deserialize_proof(&common, &data.proof)?; + let verifier_only = deserialize_verifier_only(&data.verifier_only)?; Ok(Box::new(Self { params, id, + verifier_only, + common_hash: data.common_hash, vd_set, proof, public_statements: data.public_statements, @@ -1019,4 +1050,13 @@ pub mod tests { let pod = (proof.pod as Box).downcast::().unwrap(); Ok(pod.verify()?) } + + #[test] + fn test_common() { + use pretty_assertions::assert_eq; + let params = Params::default(); + let main_common = &*cache_get_rec_main_pod_common_circuit_data(¶ms); + let std_common = &*cache_get_standard_rec_main_pod_common_circuit_data(); + assert_eq!(std_common.0, main_common.0); + } } diff --git a/src/backends/plonky2/mock/emptypod.rs b/src/backends/plonky2/mock/emptypod.rs index 3d0bb08..fcdd8ac 100644 --- a/src/backends/plonky2/mock/emptypod.rs +++ b/src/backends/plonky2/mock/emptypod.rs @@ -73,6 +73,9 @@ impl RecursivePod for MockEmptyPod { fn verifier_data(&self) -> VerifierOnlyCircuitData { panic!("MockEmptyPod can't be verified in a recursive MainPod circuit"); } + fn common_hash(&self) -> String { + panic!("MockEmptyPod can't be verified in a recursive MainPod circuit"); + } fn proof(&self) -> Proof { panic!("MockEmptyPod can't be verified in a recursive MainPod circuit"); } diff --git a/src/backends/plonky2/mock/mainpod.rs b/src/backends/plonky2/mock/mainpod.rs index 76b2fbe..07d91aa 100644 --- a/src/backends/plonky2/mock/mainpod.rs +++ b/src/backends/plonky2/mock/mainpod.rs @@ -373,6 +373,9 @@ impl RecursivePod for MockMainPod { fn verifier_data(&self) -> VerifierOnlyCircuitData { panic!("MockMainPod can't be verified in a recursive MainPod circuit"); } + fn common_hash(&self) -> String { + panic!("MockMainPod can't be verified in a recursive MainPod circuit"); + } fn proof(&self) -> Proof { panic!("MockMainPod can't be verified in a recursive MainPod circuit"); } diff --git a/src/backends/plonky2/mod.rs b/src/backends/plonky2/mod.rs index e15a953..ece9083 100644 --- a/src/backends/plonky2/mod.rs +++ b/src/backends/plonky2/mod.rs @@ -6,19 +6,33 @@ pub mod mainpod; pub mod mock; pub mod primitives; pub mod recursion; -mod serialization; +pub mod serialization; pub mod signedpod; +use std::iter; + use base64::{prelude::BASE64_STANDARD, Engine}; pub use error::*; -use plonky2::util::serialization::{Buffer, Read}; +use plonky2::{ + field::{ + extension::quadratic::QuadraticExtension, + types::{Field, Field64}, + }, + hash::hash_types::HashOut, + plonk::vars::EvaluationVars, + util::serialization::{Buffer, Read}, +}; +use rand::prelude::*; +use rand_chacha::ChaCha20Rng; +use serde::{ser, Deserialize, Serialize}; +use sha2::{Digest, Sha256}; use crate::{ backends::plonky2::{ - basetypes::{CommonCircuitData, Proof}, + basetypes::{CommonCircuitData, Proof, VerifierOnlyCircuitData, F}, circuits::mainpod::{MainPodVerifyTarget, NUM_PUBLIC_INPUTS}, recursion::RecursiveCircuit, - serialization::CommonCircuitDataSerializer, + serialization::{CommonCircuitDataSerializer, Pod2GateSerializer}, }, cache::{self, CacheEntry}, middleware::Params, @@ -73,9 +87,99 @@ pub fn deserialize_proof(common: &CommonCircuitData, proof: &str) -> Result String { + let bytes = verifier_only.to_bytes().expect("write to Vec"); + serialize_bytes(&bytes) +} + +pub fn deserialize_verifier_only(verifier_only: &str) -> Result { + let decoded = deserialize_bytes(verifier_only)?; + let verifier_only = VerifierOnlyCircuitData::from_bytes(&decoded).map_err(|e| { + Error::custom(format!( + "Failed to read VerifierOnlyCircuitData from buffer: {}. Value: {}", + e, verifier_only + )) + })?; + + Ok(verifier_only) +} + pub fn serialize_proof(proof: &Proof) -> String { let mut buffer = Vec::new(); use plonky2::util::serialization::Write; buffer.write_proof(proof).unwrap(); serialize_bytes(&buffer) } + +fn rand_vec(rng: &mut impl RngCore, len: usize) -> Vec { + iter::repeat_with(|| rng.next_u64()) + .filter(|v| *v < F::ORDER) + .map(F::from_canonical_u64) + .take(len) + .collect() +} + +fn base(r: F, xs: &[F]) -> F { + let mut res = F::ZERO; + for x in xs.iter().rev() { + res *= r; + res += *x; + } + res +} + +fn gate_fingerprints(common: &CommonCircuitData) -> Vec<(String, F)> { + type Ext = QuadraticExtension; + let config = &common.config; + let mut rng = ChaCha20Rng::seed_from_u64(42); + let r = rand_vec(&mut rng, 1)[0]; + let local_constants: Vec = rand_vec(&mut rng, config.num_constants) + .into_iter() + .map(Ext::from) + .collect(); + let local_wires: Vec = rand_vec(&mut rng, config.num_wires) + .into_iter() + .map(Ext::from) + .collect(); + let public_inputs_hash = HashOut::from_vec(rand_vec(&mut rng, 4)); + let vars = EvaluationVars { + local_constants: &local_constants, + local_wires: &local_wires, + public_inputs_hash: &public_inputs_hash, + }; + let mut fingerprints = Vec::new(); + for gate in &common.gates { + let eval: Vec = gate + .0 + .eval_unfiltered(vars) + .into_iter() + .map(|e| e.0[0]) + .collect(); + fingerprints.push((gate.0.id(), base(r, &eval))); + } + fingerprints +} + +pub fn hash_common_data(common: &CommonCircuitData) -> serde_json::Result { + #[derive(Serialize, Deserialize)] + pub struct CommonFingerprintData { + common: String, + gate_fingerprints: Vec<(String, F)>, + } + + let gate_serializer = Pod2GateSerializer {}; + let bytes = common + .to_bytes(&gate_serializer) + .map_err(ser::Error::custom)?; + let gate_fingerprints = gate_fingerprints(common); + let data = CommonFingerprintData { + common: serialize_bytes(&bytes), + gate_fingerprints, + }; + + let json = serde_json::to_string(&data)?; + let json_hash = Sha256::digest(&json); + let json_hash_str_long = format!("{:x}", json_hash); + let json_hash_str = json_hash_str_long[..32].to_string(); + Ok(json_hash_str) +} diff --git a/src/backends/plonky2/serialization.rs b/src/backends/plonky2/serialization.rs index aa9d145..567ba9b 100644 --- a/src/backends/plonky2/serialization.rs +++ b/src/backends/plonky2/serialization.rs @@ -18,7 +18,9 @@ use plonky2_u32::gates::comparison::{ComparisonGate, ComparisonGenerator}; use serde::{de, ser, Deserialize, Serialize}; use crate::backends::plonky2::{ - basetypes::{CircuitData, CommonCircuitData, VerifierCircuitData, C, D, F}, + basetypes::{ + CircuitData, CommonCircuitData, VerifierCircuitData, VerifierOnlyCircuitData, C, D, F, + }, circuits::{common::LtMaskGenerator, mux_table::TableGetGenerator, utils::DebugGenerator}, primitives::ec::{ bits::ConditionalZeroGenerator, @@ -33,7 +35,7 @@ use crate::backends::plonky2::{ }; #[derive(Debug)] -pub(crate) struct Pod2GateSerializer; +pub struct Pod2GateSerializer; impl GateSerializer for Pod2GateSerializer { impl_gate_serializer! { Pod2GateSerializer, @@ -171,13 +173,13 @@ impl<'de> Deserialize<'de> for CircuitDataSerializer { let generator_serializer = Pod2GeneratorSerializer {}; let circuit_data = CircuitData::from_bytes(bytes, &gate_serializer, &generator_serializer) .map_err(de::Error::custom)?; - Ok(CircuitDataSerializer(circuit_data)) + Ok(Self(circuit_data)) } } /// Helper type to serialize and deserialize the pod2 `CommonCircuitData` using serde traits. #[derive(Clone)] -pub struct CommonCircuitDataSerializer(pub(crate) CommonCircuitData); +pub struct CommonCircuitDataSerializer(pub CommonCircuitData); impl Deref for CommonCircuitDataSerializer { type Target = CommonCircuitData; @@ -210,7 +212,7 @@ impl<'de> Deserialize<'de> for CommonCircuitDataSerializer { let gate_serializer = Pod2GateSerializer {}; let circuit_data = CommonCircuitData::from_bytes(bytes, &gate_serializer).map_err(de::Error::custom)?; - Ok(CommonCircuitDataSerializer(circuit_data)) + Ok(Self(circuit_data)) } } @@ -248,9 +250,43 @@ impl<'de> Deserialize<'de> for VerifierCircuitDataSerializer { let bytes = <&'de serde_bytes::Bytes>::deserialize(deserializer)?; let gate_serializer = Pod2GateSerializer {}; - let circuit_data = + let verifier_data = VerifierCircuitData::from_bytes(bytes, &gate_serializer).map_err(de::Error::custom)?; - Ok(VerifierCircuitDataSerializer(circuit_data)) + Ok(Self(verifier_data)) + } +} + +/// Helper type to serialize and deserialize the pod2 `VerifierOnlyCircuitData` using serde traits. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct VerifierOnlyCircuitDataSerializer(pub(crate) VerifierOnlyCircuitData); + +impl Deref for VerifierOnlyCircuitDataSerializer { + type Target = VerifierOnlyCircuitData; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Serialize for VerifierOnlyCircuitDataSerializer { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let bytes = self.0.to_bytes().map_err(ser::Error::custom)?; + serde_bytes::ByteBuf::from(bytes).serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for VerifierOnlyCircuitDataSerializer { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let bytes = <&'de serde_bytes::Bytes>::deserialize(deserializer)?; + let verifier_only = + VerifierOnlyCircuitData::from_bytes(bytes).map_err(de::Error::custom)?; + Ok(Self(verifier_only)) } } diff --git a/src/bin/mainpod_circuit_info.rs b/src/bin/mainpod_circuit_info.rs new file mode 100644 index 0000000..b532de9 --- /dev/null +++ b/src/bin/mainpod_circuit_info.rs @@ -0,0 +1,53 @@ +use std::env; + +use anyhow::anyhow; +use pod2::{ + backends::plonky2::{ + hash_common_data, mainpod::cache_get_rec_main_pod_verifier_circuit_data, + recursion::circuit::hash_verifier_data, + }, + middleware::{Hash, Params}, +}; +use serde::Serialize; +use sha2::{Digest, Sha256}; + +#[derive(Serialize)] +struct Info { + params_hash: String, + verifier_hash: Hash, + common_hash: String, +} + +fn main() -> anyhow::Result<()> { + let args: Vec = env::args().collect(); + + let params = Params::default(); + match args.get(1).map(|s| s.as_str()) { + Some("params") => { + let params_json = serde_json::to_string_pretty(¶ms)?; + println!("{params_json}"); + } + Some("circuit-info") => { + let params_json = serde_json::to_string(¶ms)?; + let params_json_hash = Sha256::digest(¶ms_json); + let params_json_hash_str_long = format!("{params_json_hash:x}"); + let params_json_hash_str = params_json_hash_str_long[..32].to_string(); + + let vd = &*cache_get_rec_main_pod_verifier_circuit_data(¶ms); + let info = Info { + params_hash: params_json_hash_str, + verifier_hash: Hash(hash_verifier_data(&vd.verifier_only).elements), + common_hash: hash_common_data(&vd.common)?, + }; + let json = serde_json::to_string_pretty(&info)?; + println!("{json}"); + } + _ => { + return Err(anyhow!( + "Invalid arguments. Usage: {} params/circuit-info", + args[0] + )); + } + } + Ok(()) +} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 97b5a12..c9a5594 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -964,6 +964,9 @@ dyn_clone::clone_trait_object!(Pod); /// recursion: for example an introduction Pod in general is not recursive. pub trait RecursivePod: Pod { fn verifier_data(&self) -> VerifierOnlyCircuitData; + /// Return a hash of the CommonCircuitData that uniquely identifies the circuit + /// configuration and list of custom gates. + fn common_hash(&self) -> String; fn proof(&self) -> Proof; fn vd_set(&self) -> &VDSet;