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;