From 8cc8fdabd408cfe2101465aa6e700ebc2b52a71f Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 5 Mar 2026 12:58:05 +0500 Subject: [PATCH 1/6] feat(zk-prover): dual-circuit flavor architecture (poseidon default, keccak evm) --- .github/workflows/ci.yml | 4 +- crates/events/src/enclave_event/proof.rs | 36 ++++++ crates/zk-prover/src/backend/download.rs | 53 +-------- .../src/circuits/recursive_aggregation/mod.rs | 50 ++++---- .../src/circuits/recursive_aggregation/vk.rs | 13 +- crates/zk-prover/src/lib.rs | 1 + crates/zk-prover/src/prover.rs | 112 +++++++++++------- crates/zk-prover/src/traits.rs | 6 +- crates/zk-prover/tests/common/helpers.rs | 48 ++++++-- crates/zk-prover/tests/integration_tests.rs | 9 ++ scripts/build-circuits.ts | 55 ++++++--- scripts/circuit-constants.ts | 15 +++ 12 files changed, 254 insertions(+), 148 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb834ff979..8200b573a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -657,9 +657,9 @@ jobs: - name: Verify circuit artifacts run: | echo "DKG circuits:" - ls -la circuits/bin/dkg/target/*.json circuits/bin/dkg/target/*.vk + ls -la circuits/bin/dkg/target/*.json circuits/bin/dkg/target/*.vk circuits/bin/dkg/target/*.vk_recursive echo "Threshold circuits:" - ls -la circuits/bin/threshold/target/*.json circuits/bin/threshold/target/*.vk + ls -la circuits/bin/threshold/target/*.json circuits/bin/threshold/target/*.vk circuits/bin/threshold/target/*.vk_recursive - name: Run ZK prover e2e tests run: cargo test -p e3-zk-prover --test local_e2e_tests -- --nocapture diff --git a/crates/events/src/enclave_event/proof.rs b/crates/events/src/enclave_event/proof.rs index ef5837fcdf..e5ac8f08c8 100644 --- a/crates/events/src/enclave_event/proof.rs +++ b/crates/events/src/enclave_event/proof.rs @@ -33,6 +33,42 @@ impl Proof { } } +/// Circuit flavors determine the hash oracle used for VK generation and proving. +/// +/// - `Default`: Uses poseidon hash — for off-chain ciphernode-to-ciphernode verification. +/// - `Evm`: Uses keccak hash — for on-chain EVM-verifiable proofs. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)] +pub enum CircuitFlavor { + /// Poseidon-based circuits for off-chain ciphernode verification (default). + #[default] + Default, + /// Keccak-based circuits for on-chain EVM verification. + Evm, +} + +impl CircuitFlavor { + pub fn as_str(&self) -> &'static str { + match self { + CircuitFlavor::Default => "default", + CircuitFlavor::Evm => "evm", + } + } + + /// Returns the bb verifier target flag value for this flavor. + pub fn verifier_target(&self) -> &'static str { + match self { + CircuitFlavor::Default => "noir-recursive-no-zk", + CircuitFlavor::Evm => "evm", + } + } +} + +impl fmt::Display for CircuitFlavor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + /// Circuit identifiers for ZK proofs. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum CircuitName { diff --git a/crates/zk-prover/src/backend/download.rs b/crates/zk-prover/src/backend/download.rs index 5b27ec1e9f..1abbb68a64 100644 --- a/crates/zk-prover/src/backend/download.rs +++ b/crates/zk-prover/src/backend/download.rs @@ -13,7 +13,7 @@ use std::path::{Path, PathBuf}; use std::time::Duration; use tar::Archive; use tokio::fs; -use tracing::{info, warn}; +use tracing::info; use walkdir::WalkDir; use super::ZkBackend; @@ -107,17 +107,10 @@ impl ZkBackend { info!("installed circuits v{}", version); } Err(e) => { - warn!( - "could not download circuits ({}), creating placeholder for testing", - e - ); - create_placeholder_circuits(&self.circuits_dir).await?; - - version_info.circuits_version = Some("0.0.0-placeholder".to_string()); - version_info.last_updated = Some(chrono::Utc::now().to_rfc3339()); - version_info.save(&self.version_file()).await?; - - info!("created placeholder circuits (will retry download on next setup)"); + return Err(ZkError::DownloadFailed( + url, + format!("could not download circuits: {}", e), + )); } } @@ -210,39 +203,3 @@ async fn download_with_progress(url: &str, message: &str) -> Result, ZkE pb.finish_with_message("download complete"); Ok(bytes) } - -async fn create_placeholder_circuits(circuits_dir: &Path) -> Result<(), ZkError> { - fs::create_dir_all(circuits_dir).await?; - - let placeholder = serde_json::json!({ - "noir_version":"1.0.0-beta.15+83245db91dcf63420ef4bcbbd85b98f397fee663", - "hash":"15412581843239610929", - "abi":{ - "parameters":[ - {"name":"x","type":{"kind":"field"},"visibility":"private"}, - {"name":"y","type":{"kind":"field"},"visibility":"private"}, - {"name":"_sum","type":{"kind":"field"},"visibility":"public"} - ], - "return_type":null, - "error_types":{} - }, - "bytecode":"H4sIAAAAAAAA/5WOMQ5AMBRA/y8HMbIRRxCJSYwWg8RiIGIz9gjiAk4hHKeb0WLX0KHRDu1bXvL/y89H+HCFu7rtCTeCiiPsgRFo06LUhk0+smgN9iLdKC0rPz6z6RjmhN3LxffE/O7byg+hZv7nAb2HRPkUAQAA", - "debug_symbols":"jZDRCoMwDEX/Jc996MbG1F8ZQ2qNUghtie1giP++KLrpw2BPaXJ7bsgdocUm97XzXRiguo/QsCNyfU3BmuSCl+k4KdjaOjGijGCnCxUNo09Q+Uyk4GkoL5+GaPxSk2FRtQL0rVQx7Bzh/JrUl9a/0Vu5ssXlA1//psvbSp90ccAf0hnr+HAuaKjO0+zGzjSEawRd9naXSHrFTdkyixwstplxtls0WfAG", - "file_map":{ - "50":{"source":"pub fn main(\n x: Field,\n y: Field,\n _sum: pub Field\n) {\n let sum = x + y;\n assert(sum == _sum);\n}\n", - "path":"./enclave/circuits/bin/dummy/src/main.nr"} - },"expression_width":{"Bounded":{"width":4}} - }); - - let circuit_path = circuits_dir.join("pk.json"); - fs::write(&circuit_path, serde_json::to_string_pretty(&placeholder)?).await?; - - fs::create_dir_all(circuits_dir.join("vk")).await?; - - // Create a minimal placeholder VK file so proof generation doesn't fail - // This is just for testing when actual circuits can't be downloaded - let vk_path = circuits_dir.join("vk").join("pk.vk"); - fs::write(&vk_path, b"placeholder_vk").await?; - - Ok(()) -} diff --git a/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs b/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs index 7b7c4008b1..9b4c7247f3 100644 --- a/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs +++ b/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs @@ -16,7 +16,7 @@ use crate::circuits::utils::inputs_json_to_input_map; use crate::error::ZkError; use crate::prover::ZkProver; use crate::witness::{CompiledCircuit, WitnessGenerator}; -use e3_events::{CircuitName, Proof}; +use e3_events::{CircuitFlavor, CircuitName, Proof}; use self::utils::bytes_to_field_strings; @@ -96,7 +96,8 @@ pub fn generate_wrapper_proof( }; let circuit = proofs[0].circuit; - let vk_artifacts = vk::load_vk_artifacts(prover.circuits_dir(), circuit)?; + let vk_artifacts = + vk::load_vk_artifacts(&prover.circuits_dir(CircuitFlavor::Default), circuit)?; let full_input = WrapperInput { verification_key: vk_artifacts.verification_key, @@ -107,7 +108,7 @@ pub fn generate_wrapper_proof( let dir_path = circuit.wrapper_dir_path(); let circuit_path = prover - .circuits_dir() + .circuits_dir(CircuitFlavor::Default) .join(&dir_path) .join(format!("{}.json", circuit.as_str())); let compiled = CompiledCircuit::from_file(&circuit_path)?; @@ -167,8 +168,10 @@ pub fn generate_fold_proof( proof2: &Proof, e3_id: &str, ) -> Result { - let vk1 = vk::load_vk_for_fold_input(prover.circuits_dir(), proof1.circuit)?; - let vk2 = vk::load_vk_for_fold_input(prover.circuits_dir(), proof2.circuit)?; + let vk1 = + vk::load_vk_for_fold_input(&prover.circuits_dir(CircuitFlavor::Default), proof1.circuit)?; + let vk2 = + vk::load_vk_for_fold_input(&prover.circuits_dir(CircuitFlavor::Default), proof2.circuit)?; // Both wrapper and fold output [key_hash, commitment]. let proof1_public_inputs = bytes_to_field_strings(&proof1.public_signals)?; @@ -200,7 +203,7 @@ pub fn generate_fold_proof( let dir_path = CircuitName::Fold.dir_path(); let circuit_path = prover - .circuits_dir() + .circuits_dir(CircuitFlavor::Default) .join(&dir_path) .join(format!("{}.json", CircuitName::Fold.as_str())); let compiled = CompiledCircuit::from_file(&circuit_path)?; @@ -275,11 +278,12 @@ mod tests { let dist = dist_circuits_path(); let wrapper_src = dist + .join("default") .join("recursive_aggregation") .join("wrapper") .join("dkg") .join("pk"); - if wrapper_src.join("pk.json").exists() && wrapper_src.join("pk.vk_recursive").exists() { + if wrapper_src.join("pk.json").exists() && wrapper_src.join("pk.vk").exists() { // Use dist entirely so inner + wrapper circuits match (same build). backend.circuits_dir = dist.clone(); } @@ -288,11 +292,12 @@ mod tests { let wrapper_dir = backend .circuits_dir + .join("default") .join("recursive_aggregation") .join("wrapper") .join("dkg") .join("pk"); - if !wrapper_dir.join("pk.json").exists() || !wrapper_dir.join("pk.vk_recursive").exists() { + if !wrapper_dir.join("pk.json").exists() || !wrapper_dir.join("pk.vk").exists() { panic!( "wrapper circuit not found at {} — run pnpm build:circuits and set circuits_dir to dist/circuits, or ensure the release includes recursive_aggregation", wrapper_dir.display() @@ -332,12 +337,13 @@ mod tests { let dist = dist_circuits_path(); let wrapper_src = dist + .join("default") .join("recursive_aggregation") .join("wrapper") .join("dkg") .join("share_decryption"); if wrapper_src.join("share_decryption.json").exists() - && wrapper_src.join("share_decryption.vk_recursive").exists() + && wrapper_src.join("share_decryption.vk").exists() { backend.circuits_dir = dist.clone(); } @@ -346,12 +352,13 @@ mod tests { let wrapper_dir = backend .circuits_dir + .join("default") .join("recursive_aggregation") .join("wrapper") .join("dkg") .join("share_decryption"); if !wrapper_dir.join("share_decryption.json").exists() - || !wrapper_dir.join("share_decryption.vk_recursive").exists() + || !wrapper_dir.join("share_decryption.vk").exists() { panic!( "2-proof wrapper circuit not found at {} — run pnpm build:circuits and set circuits_dir to dist/circuits", @@ -404,25 +411,24 @@ mod tests { backend.ensure_installed().await.expect("ensure_installed"); let dist = dist_circuits_path(); - let pk_wrapper = dist + let default_dist = dist.join("default"); + let pk_wrapper = default_dist .join("recursive_aggregation") .join("wrapper") .join("dkg") .join("pk"); - let share_enc_wrapper = dist + let share_enc_wrapper = default_dist .join("recursive_aggregation") .join("wrapper") .join("dkg") .join("share_encryption"); - let fold_dir = dist.join("recursive_aggregation").join("fold"); + let fold_dir = default_dist.join("recursive_aggregation").join("fold"); if pk_wrapper.join("pk.json").exists() - && pk_wrapper.join("pk.vk_recursive").exists() + && pk_wrapper.join("pk.vk").exists() && share_enc_wrapper.join("share_encryption.json").exists() - && share_enc_wrapper - .join("share_encryption.vk_recursive") - .exists() + && share_enc_wrapper.join("share_encryption.vk").exists() && fold_dir.join("fold.json").exists() - && fold_dir.join("fold.vk_recursive").exists() + && fold_dir.join("fold.vk").exists() { backend.circuits_dir = dist.clone(); } @@ -430,17 +436,15 @@ mod tests { let prover = ZkProver::new(&backend); if !pk_wrapper.join("pk.json").exists() - || !pk_wrapper.join("pk.vk_recursive").exists() + || !pk_wrapper.join("pk.vk").exists() || !share_enc_wrapper.join("share_encryption.json").exists() - || !share_enc_wrapper - .join("share_encryption.vk_recursive") - .exists() + || !share_enc_wrapper.join("share_encryption.vk").exists() { panic!( "wrapper circuits not found — run pnpm build:circuits and ensure dist/circuits includes recursive_aggregation wrappers for pk and share_encryption", ); } - if !fold_dir.join("fold.json").exists() || !fold_dir.join("fold.vk_recursive").exists() { + if !fold_dir.join("fold.json").exists() || !fold_dir.join("fold.vk").exists() { panic!( "fold circuit not found at {} — run pnpm build:circuits", fold_dir.display() diff --git a/crates/zk-prover/src/circuits/recursive_aggregation/vk.rs b/crates/zk-prover/src/circuits/recursive_aggregation/vk.rs index dc58a2bbf6..cf55a230a1 100644 --- a/crates/zk-prover/src/circuits/recursive_aggregation/vk.rs +++ b/crates/zk-prover/src/circuits/recursive_aggregation/vk.rs @@ -5,7 +5,7 @@ // or FITNESS FOR A PARTICULAR PURPOSE. //! Loads verification key and hash for inner circuits (wrapper proof aggregation). -//! Reads `.vk_recursive` and `.vk_recursive_hash` (poseidon2/noir-recursive-no-zk format). +//! In the default flavor directory, these are stored as `.vk` and `.vk_hash`. use super::utils::bytes_to_field_strings; use crate::error::ZkError; @@ -20,8 +20,8 @@ pub struct VkArtifacts { } fn load_vk_from_dir(circuit_dir: &Path, circuit_name: &str) -> Result { - let vk_path = circuit_dir.join(format!("{}.vk_recursive", circuit_name)); - let vk_hash_path = circuit_dir.join(format!("{}.vk_recursive_hash", circuit_name)); + let vk_path = circuit_dir.join(format!("{}.vk", circuit_name)); + let vk_hash_path = circuit_dir.join(format!("{}.vk_hash", circuit_name)); let vk_bytes = fs::read(&vk_path) .map_err(|e| ZkError::CircuitNotFound(format!("{}: {}", vk_path.display(), e)))?; @@ -45,7 +45,7 @@ fn load_vk_from_dir(circuit_dir: &Path, circuit_name: &str) -> Result &PathBuf { - &self.circuits_dir + pub fn circuits_dir(&self, flavor: CircuitFlavor) -> PathBuf { + self.circuits_dir.join(flavor.as_str()) } pub fn work_dir(&self) -> &PathBuf { @@ -46,11 +46,30 @@ impl ZkProver { witness_data: &[u8], e3_id: &str, ) -> Result { - self.generate_proof_impl(circuit, witness_data, e3_id, &circuit.dir_path(), None) + self.generate_proof_with_flavor(circuit, witness_data, e3_id, CircuitFlavor::Default) + } + + pub fn generate_evm_proof( + &self, + circuit: CircuitName, + witness_data: &[u8], + e3_id: &str, + ) -> Result { + self.generate_proof_with_flavor(circuit, witness_data, e3_id, CircuitFlavor::Evm) + } + + pub fn generate_proof_with_flavor( + &self, + circuit: CircuitName, + witness_data: &[u8], + e3_id: &str, + flavor: CircuitFlavor, + ) -> Result { + self.generate_proof_impl(circuit, witness_data, e3_id, &circuit.dir_path(), flavor) } /// Generates a proof for recursive aggregation (poseidon2, noir-recursive-no-zk). - /// Uses inner circuit dir and `.vk_recursive`. + /// Uses the default (poseidon) flavor since recursive proofs require poseidon. pub fn generate_recursive_proof( &self, circuit: CircuitName, @@ -62,12 +81,12 @@ impl ZkProver { witness_data, e3_id, &circuit.dir_path(), - Some("noir-recursive-no-zk"), + CircuitFlavor::Default, ) } /// Generates a proof of the wrapper circuit (for aggregation output). - /// Uses wrapper dir; verifier_target determines proof format and VK suffix. + /// Uses wrapper dir; always uses default (poseidon) flavor for recursive compatibility. pub fn generate_wrapper_proof( &self, circuit: CircuitName, @@ -79,13 +98,13 @@ impl ZkProver { witness_data, e3_id, &circuit.wrapper_dir_path(), - Some("noir-recursive-no-zk"), + CircuitFlavor::Default, ) } /// Generates a proof of the fold circuit (for aggregation output). /// The fold circuit is independent; uses fixed path `recursive_aggregation/fold`. - /// Verifier target: `noir-recursive-no-zk`. + /// Uses default (poseidon) flavor for recursive compatibility. pub fn generate_fold_proof(&self, witness_data: &[u8], e3_id: &str) -> Result { let dir = CircuitName::Fold.dir_path(); self.generate_proof_impl( @@ -93,18 +112,24 @@ impl ZkProver { witness_data, e3_id, &dir, - Some("noir-recursive-no-zk"), + CircuitFlavor::Default, ) } - /// Generates the final fold proof for on-chain verification (evm target). + /// Generates the final fold proof for on-chain verification (evm/keccak target). pub fn generate_final_fold_proof( &self, witness_data: &[u8], e3_id: &str, ) -> Result { let dir = CircuitName::Fold.dir_path(); - self.generate_proof_impl(CircuitName::Fold, witness_data, e3_id, &dir, Some("evm")) + self.generate_proof_impl( + CircuitName::Fold, + witness_data, + e3_id, + &dir, + CircuitFlavor::Evm, + ) } fn generate_proof_impl( @@ -113,20 +138,17 @@ impl ZkProver { witness_data: &[u8], e3_id: &str, dir_path: &str, - verifier_target: Option<&str>, + flavor: CircuitFlavor, ) -> Result { if !self.bb_binary.exists() { return Err(ZkError::BbNotInstalled); } - let vk_suffix = match verifier_target { - Some("noir-recursive") | Some("noir-recursive-no-zk") => "_recursive", - _ => "", - }; + let verifier_target = flavor.verifier_target(); - let circuit_dir = self.circuits_dir.join(dir_path); + let circuit_dir = self.circuits_dir(flavor).join(dir_path); let circuit_path = circuit_dir.join(format!("{}.json", circuit.as_str())); - let vk_path = circuit_dir.join(format!("{}.vk{vk_suffix}", circuit.as_str())); + let vk_path = circuit_dir.join(format!("{}.vk", circuit.as_str())); if !circuit_path.exists() { return Err(ZkError::CircuitNotFound(format!( @@ -162,7 +184,7 @@ impl ZkProver { let vk_path_s = vk_path.to_string_lossy(); let output_dir_s = output_dir.to_string_lossy(); - let mut args = vec![ + let args = vec![ "prove", "-b", circuit_path_s.as_ref(), @@ -173,12 +195,9 @@ impl ZkProver { "-o", output_dir_s.as_ref(), "-v", + "-t", + verifier_target, ]; - if let Some(t) = verifier_target { - args.extend(["-t", t]); - } else { - args.extend(["-t", "evm"]); - } let output = StdCommand::new(&self.bb_binary).args(&args).output()?; @@ -216,7 +235,24 @@ impl ZkProver { proof.circuit.dir_path(), e3_id, party_id, - None, + CircuitFlavor::Default, + ) + } + + pub fn verify_evm_proof( + &self, + proof: &Proof, + e3_id: &str, + party_id: u64, + ) -> Result { + self.verify_proof_impl( + proof.circuit, + &proof.data, + &proof.public_signals, + proof.circuit.dir_path(), + e3_id, + party_id, + CircuitFlavor::Evm, ) } @@ -234,7 +270,7 @@ impl ZkProver { proof.circuit.wrapper_dir_path(), e3_id, party_id, - Some("noir-recursive-no-zk"), + CircuitFlavor::Default, ) } @@ -259,7 +295,7 @@ impl ZkProver { proof.circuit.dir_path(), e3_id, party_id, - Some("noir-recursive-no-zk"), + CircuitFlavor::Default, ) } @@ -271,20 +307,17 @@ impl ZkProver { dir_path: String, e3_id: &str, party_id: u64, - verifier_target: Option<&str>, + flavor: CircuitFlavor, ) -> Result { if !self.bb_binary.exists() { return Err(ZkError::BbNotInstalled); } - let vk_suffix = match verifier_target { - Some("noir-recursive") | Some("noir-recursive-no-zk") => "_recursive", - _ => "", - }; + let verifier_target = flavor.verifier_target(); let vk_path = self - .circuits_dir + .circuits_dir(flavor) .join(&dir_path) - .join(format!("{}.vk{vk_suffix}", circuit.as_str())); + .join(format!("{}.vk", circuit.as_str())); if !vk_path.exists() { return Err(ZkError::CircuitNotFound(format!( "VK not found: {}", @@ -315,7 +348,7 @@ impl ZkProver { let proof_s = proof_path.to_string_lossy(); let vk_s = vk_path.to_string_lossy(); - let mut args = vec![ + let args = vec![ "verify", "--scheme", "ultra_honk", @@ -325,12 +358,9 @@ impl ZkProver { proof_s.as_ref(), "-k", vk_s.as_ref(), + "-t", + verifier_target, ]; - if let Some(t) = verifier_target { - args.extend(["-t", t]); - } else { - args.extend(["-t", "evm"]); - } let output = StdCommand::new(&self.bb_binary).args(&args).output()?; diff --git a/crates/zk-prover/src/traits.rs b/crates/zk-prover/src/traits.rs index 62f3dde783..6300953e33 100644 --- a/crates/zk-prover/src/traits.rs +++ b/crates/zk-prover/src/traits.rs @@ -11,7 +11,7 @@ use crate::circuits::utils::inputs_json_to_input_map; use crate::error::ZkError; use crate::prover::ZkProver; use crate::witness::{CompiledCircuit, WitnessGenerator}; -use e3_events::{CircuitName, Proof}; +use e3_events::{CircuitFlavor, CircuitName, Proof}; use e3_zk_helpers::Computation; use noirc_abi::InputMap; @@ -68,7 +68,7 @@ pub trait Provable: Send + Sync { let resolved_name = self.resolve_circuit_name(params, input); let circuit_path = prover - .circuits_dir() + .circuits_dir(CircuitFlavor::Default) .join(resolved_name.dir_path()) .join(format!("{}.json", resolved_name.as_str())); @@ -119,7 +119,7 @@ pub trait Provable: Send + Sync { for (i, input) in inputs.iter().enumerate() { let input_map = self.build_inputs(params, input)?; let circuit_path = prover - .circuits_dir() + .circuits_dir(CircuitFlavor::Default) .join(resolved_names[i].dir_path()) .join(format!("{}.json", resolved_names[i].as_str())); let circuit = CompiledCircuit::from_file(&circuit_path)?; diff --git a/crates/zk-prover/tests/common/helpers.rs b/crates/zk-prover/tests/common/helpers.rs index 47fb0c4838..deede36a36 100644 --- a/crates/zk-prover/tests/common/helpers.rs +++ b/crates/zk-prover/tests/common/helpers.rs @@ -46,7 +46,8 @@ fn circuits_build_root() -> PathBuf { pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_name: &str) { let target_dir = circuits_build_root().join(group).join("target"); let json_path = target_dir.join(format!("{circuit_name}.json")); - let vk_path = target_dir.join(format!("{circuit_name}.vk")); + let vk_evm_path = target_dir.join(format!("{circuit_name}.vk")); + let vk_recursive_path = target_dir.join(format!("{circuit_name}.vk_recursive")); assert!( json_path.exists(), @@ -54,19 +55,47 @@ pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_na json_path.display() ); assert!( - vk_path.exists(), - "verification key not found: {} (run `pnpm build:circuits` to compile)", - vk_path.display() + vk_evm_path.exists(), + "evm verification key not found: {} (run `pnpm build:circuits` to compile)", + vk_evm_path.display() ); - let circuit_dir = backend.circuits_dir.join(group).join(circuit_name); - fs::create_dir_all(&circuit_dir).await.unwrap(); - fs::copy(&json_path, circuit_dir.join(format!("{circuit_name}.json"))) + // Set up the evm flavor directory (keccak VK) + let evm_dir = backend + .circuits_dir + .join("evm") + .join(group) + .join(circuit_name); + fs::create_dir_all(&evm_dir).await.unwrap(); + fs::copy(&json_path, evm_dir.join(format!("{circuit_name}.json"))) .await .unwrap(); - fs::copy(&vk_path, circuit_dir.join(format!("{circuit_name}.vk"))) + fs::copy(&vk_evm_path, evm_dir.join(format!("{circuit_name}.vk"))) .await .unwrap(); + + // Set up the default flavor directory (poseidon VK, renamed from .vk_recursive) + let default_dir = backend + .circuits_dir + .join("default") + .join(group) + .join(circuit_name); + fs::create_dir_all(&default_dir).await.unwrap(); + fs::copy(&json_path, default_dir.join(format!("{circuit_name}.json"))) + .await + .unwrap(); + // Use the poseidon VK (.vk_recursive) if available, otherwise fall back to .vk + let default_vk_src = if vk_recursive_path.exists() { + &vk_recursive_path + } else { + &vk_evm_path + }; + fs::copy( + default_vk_src, + default_dir.join(format!("{circuit_name}.vk")), + ) + .await + .unwrap(); } pub async fn find_anvil() -> bool { @@ -98,9 +127,6 @@ pub async fn setup_test_prover(bb: &PathBuf) -> (ZkBackend, TempDir) { let backend = ZkBackend::new(bb_binary.clone(), circuits_dir.clone(), work_dir.clone()); fs::create_dir_all(&backend.circuits_dir).await.unwrap(); - fs::create_dir_all(backend.circuits_dir.join("vk")) - .await - .unwrap(); fs::create_dir_all(&backend.work_dir).await.unwrap(); fs::create_dir_all(backend.base_dir.join("bin")) .await diff --git a/crates/zk-prover/tests/integration_tests.rs b/crates/zk-prover/tests/integration_tests.rs index eac1265615..a91ee9a716 100644 --- a/crates/zk-prover/tests/integration_tests.rs +++ b/crates/zk-prover/tests/integration_tests.rs @@ -88,12 +88,21 @@ async fn test_full_flow_download_circuits_prove_and_verify() { assert!(backend .circuits_dir + .join("default") .join("dkg") .join("pk") .join("pk.json") .exists()); assert!(backend .circuits_dir + .join("default") + .join("dkg") + .join("pk") + .join("pk.vk") + .exists()); + assert!(backend + .circuits_dir + .join("evm") .join("dkg") .join("pk") .join("pk.vk") diff --git a/scripts/build-circuits.ts b/scripts/build-circuits.ts index 169d769c27..befaeb3a62 100644 --- a/scripts/build-circuits.ts +++ b/scripts/build-circuits.ts @@ -9,7 +9,7 @@ import { execSync } from 'child_process' import { createHash } from 'crypto' import { appendFileSync, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'fs' import { basename, join, resolve } from 'path' -import { ALL_GROUPS, CIRCUIT_GROUPS, type CircuitGroup } from './circuit-constants' +import { ALL_GROUPS, CIRCUIT_GROUPS, CIRCUIT_FLAVORS, type CircuitGroup, type CircuitFlavor } from './circuit-constants' interface CircuitInfo { name: string @@ -384,29 +384,42 @@ class NoirCircuitBuilder { const checksums: Record = {} for (const c of compiled) { - const prefix = `${c.group}/${c.name}` + const packageName = basename(c.artifacts.json ?? '', '.json') + + // evm/ flavor checksums + const evmPrefix = `${CIRCUIT_FLAVORS.EVM}/${c.group}/${c.name}` if (c.checksums.json && c.artifacts.json) { - const f = `${prefix}/${basename(c.artifacts.json)}` + const f = `${evmPrefix}/${basename(c.artifacts.json)}` checksums[f] = c.checksums.json lines.push(`${c.checksums.json} ${f}`) } if (c.checksums.vk && c.artifacts.vk) { - const f = `${prefix}/${basename(c.artifacts.vk)}` + const f = `${evmPrefix}/${basename(c.artifacts.vk)}` checksums[f] = c.checksums.vk lines.push(`${c.checksums.vk} ${f}`) } if (c.checksums.vkHash && c.artifacts.vkHash) { - const f = `${prefix}/${basename(c.artifacts.vkHash)}` + const f = `${evmPrefix}/${basename(c.artifacts.vkHash)}` checksums[f] = c.checksums.vkHash lines.push(`${c.checksums.vkHash} ${f}`) } + + // default/ flavor checksums + const defaultPrefix = `${CIRCUIT_FLAVORS.DEFAULT}/${c.group}/${c.name}` + if (c.checksums.json && c.artifacts.json) { + const f = `${defaultPrefix}/${basename(c.artifacts.json)}` + checksums[f] = c.checksums.json + lines.push(`${c.checksums.json} ${f}`) + } if (c.checksums.vkRecursive && c.artifacts.vkRecursive) { - const f = `${prefix}/${basename(c.artifacts.vkRecursive)}` + // In default/ flavor, .vk_recursive is stored as .vk + const f = `${defaultPrefix}/${packageName}.vk` checksums[f] = c.checksums.vkRecursive lines.push(`${c.checksums.vkRecursive} ${f}`) } if (c.checksums.vkRecursiveHash && c.artifacts.vkRecursiveHash) { - const f = `${prefix}/${basename(c.artifacts.vkRecursiveHash)}` + // In default/ flavor, .vk_recursive_hash is stored as .vk_hash + const f = `${defaultPrefix}/${packageName}.vk_hash` checksums[f] = c.checksums.vkRecursiveHash lines.push(`${c.checksums.vkRecursiveHash} ${f}`) } @@ -425,13 +438,27 @@ class NoirCircuitBuilder { const outputDir = this.options.outputDir! for (const c of compiled) { if (!c.artifacts.json && !c.artifacts.vk) continue - const dir = join(outputDir, c.group, c.name) - mkdirSync(dir, { recursive: true }) - if (c.artifacts.json) copyFileSync(c.artifacts.json, join(dir, basename(c.artifacts.json))) - if (c.artifacts.vk) copyFileSync(c.artifacts.vk, join(dir, basename(c.artifacts.vk))) - if (c.artifacts.vkHash) copyFileSync(c.artifacts.vkHash, join(dir, basename(c.artifacts.vkHash))) - if (c.artifacts.vkRecursive) copyFileSync(c.artifacts.vkRecursive, join(dir, basename(c.artifacts.vkRecursive))) - if (c.artifacts.vkRecursiveHash) copyFileSync(c.artifacts.vkRecursiveHash, join(dir, basename(c.artifacts.vkRecursiveHash))) + const packageName = basename(c.artifacts.json ?? '', '.json') + + // Copy to evm/ flavor: .json + evm .vk + .vk_hash + const evmDir = join(outputDir, CIRCUIT_FLAVORS.EVM, c.group, c.name) + mkdirSync(evmDir, { recursive: true }) + if (c.artifacts.json) copyFileSync(c.artifacts.json, join(evmDir, basename(c.artifacts.json))) + if (c.artifacts.vk) copyFileSync(c.artifacts.vk, join(evmDir, basename(c.artifacts.vk))) + if (c.artifacts.vkHash) copyFileSync(c.artifacts.vkHash, join(evmDir, basename(c.artifacts.vkHash))) + + // Copy to default/ flavor: .json + poseidon .vk (from .vk_recursive) + .vk_hash (from .vk_recursive_hash) + const defaultDir = join(outputDir, CIRCUIT_FLAVORS.DEFAULT, c.group, c.name) + mkdirSync(defaultDir, { recursive: true }) + if (c.artifacts.json) copyFileSync(c.artifacts.json, join(defaultDir, basename(c.artifacts.json))) + if (c.artifacts.vkRecursive) { + // Rename .vk_recursive → .vk in the default flavor directory + copyFileSync(c.artifacts.vkRecursive, join(defaultDir, `${packageName}.vk`)) + } + if (c.artifacts.vkRecursiveHash) { + // Rename .vk_recursive_hash → .vk_hash in the default flavor directory + copyFileSync(c.artifacts.vkRecursiveHash, join(defaultDir, `${packageName}.vk_hash`)) + } } return outputDir } diff --git a/scripts/circuit-constants.ts b/scripts/circuit-constants.ts index a0fd798130..7e3c6853f7 100644 --- a/scripts/circuit-constants.ts +++ b/scripts/circuit-constants.ts @@ -17,3 +17,18 @@ export const CIRCUIT_GROUPS = { export type CircuitGroup = (typeof CIRCUIT_GROUPS)[keyof typeof CIRCUIT_GROUPS] export const ALL_GROUPS: CircuitGroup[] = [CIRCUIT_GROUPS.DKG, CIRCUIT_GROUPS.THRESHOLD, CIRCUIT_GROUPS.AGGREGATION] + +/** + * Circuit flavors determine the hash oracle used for VK generation and proving. + * + * - `default`: Uses poseidon hash — for off-chain ciphernode-to-ciphernode verification. + * - `evm`: Uses keccak hash — for on-chain EVM-verifiable proofs. + */ +export const CIRCUIT_FLAVORS = { + DEFAULT: 'default', + EVM: 'evm', +} as const + +export type CircuitFlavor = (typeof CIRCUIT_FLAVORS)[keyof typeof CIRCUIT_FLAVORS] + +export const ALL_FLAVORS: CircuitFlavor[] = [CIRCUIT_FLAVORS.DEFAULT, CIRCUIT_FLAVORS.EVM] From f5076d393dfe14f72c41cbe80f530f9db0f9eee0 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 5 Mar 2026 13:32:33 +0500 Subject: [PATCH 2/6] fix: review comments [skip-ci] --- .../src/circuits/recursive_aggregation/mod.rs | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs b/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs index 9b4c7247f3..dfd564c0ee 100644 --- a/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs +++ b/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs @@ -411,29 +411,34 @@ mod tests { backend.ensure_installed().await.expect("ensure_installed"); let dist = dist_circuits_path(); - let default_dist = dist.join("default"); - let pk_wrapper = default_dist + { + let default_dist = dist.join("default"); + if default_dist + .join("recursive_aggregation") + .join("wrapper") + .join("dkg") + .join("pk") + .join("pk.json") + .exists() + { + backend.circuits_dir = dist.clone(); + } + } + + let prover = ZkProver::new(&backend); + + let default_dir = backend.circuits_dir.join("default"); + let pk_wrapper = default_dir .join("recursive_aggregation") .join("wrapper") .join("dkg") .join("pk"); - let share_enc_wrapper = default_dist + let share_enc_wrapper = default_dir .join("recursive_aggregation") .join("wrapper") .join("dkg") .join("share_encryption"); - let fold_dir = default_dist.join("recursive_aggregation").join("fold"); - if pk_wrapper.join("pk.json").exists() - && pk_wrapper.join("pk.vk").exists() - && share_enc_wrapper.join("share_encryption.json").exists() - && share_enc_wrapper.join("share_encryption.vk").exists() - && fold_dir.join("fold.json").exists() - && fold_dir.join("fold.vk").exists() - { - backend.circuits_dir = dist.clone(); - } - - let prover = ZkProver::new(&backend); + let fold_dir = default_dir.join("recursive_aggregation").join("fold"); if !pk_wrapper.join("pk.json").exists() || !pk_wrapper.join("pk.vk").exists() From b6c8e123c0fb14746df88f144be69a685e701a0a Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 5 Mar 2026 14:28:19 +0500 Subject: [PATCH 3/6] feat: add recursive proof circuits --- crates/events/src/enclave_event/proof.rs | 13 ++-- .../src/circuits/recursive_aggregation/mod.rs | 2 +- .../src/circuits/recursive_aggregation/vk.rs | 5 +- crates/zk-prover/src/prover.rs | 6 +- crates/zk-prover/src/traits.rs | 2 +- crates/zk-prover/tests/common/helpers.rs | 33 +++++++++- scripts/build-circuits.ts | 60 ++++++++++++++++++- scripts/circuit-constants.ts | 8 ++- 8 files changed, 110 insertions(+), 19 deletions(-) diff --git a/crates/events/src/enclave_event/proof.rs b/crates/events/src/enclave_event/proof.rs index e5ac8f08c8..2bb019419e 100644 --- a/crates/events/src/enclave_event/proof.rs +++ b/crates/events/src/enclave_event/proof.rs @@ -35,14 +35,17 @@ impl Proof { /// Circuit flavors determine the hash oracle used for VK generation and proving. /// -/// - `Default`: Uses poseidon hash — for off-chain ciphernode-to-ciphernode verification. -/// - `Evm`: Uses keccak hash — for on-chain EVM-verifiable proofs. +/// - `Default`: poseidon/`noir-recursive-no-zk` — wrapper & fold proofs (no ZK blinding, efficient). +/// - `Recursive`: poseidon/`noir-recursive` — inner/base proofs fed into a wrapper (ZK blinding preserved). +/// - `Evm`: keccak/`evm` — on-chain EVM-verifiable proofs. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)] pub enum CircuitFlavor { - /// Poseidon-based circuits for off-chain ciphernode verification (default). + /// noir-recursive-no-zk: for wrapper & fold proofs — poseidon, no ZK blinding. #[default] Default, - /// Keccak-based circuits for on-chain EVM verification. + /// noir-recursive: for inner/base proofs — poseidon with ZK blinding. + Recursive, + /// evm: keccak-based for on-chain Solidity verification. Evm, } @@ -50,6 +53,7 @@ impl CircuitFlavor { pub fn as_str(&self) -> &'static str { match self { CircuitFlavor::Default => "default", + CircuitFlavor::Recursive => "recursive", CircuitFlavor::Evm => "evm", } } @@ -58,6 +62,7 @@ impl CircuitFlavor { pub fn verifier_target(&self) -> &'static str { match self { CircuitFlavor::Default => "noir-recursive-no-zk", + CircuitFlavor::Recursive => "noir-recursive", CircuitFlavor::Evm => "evm", } } diff --git a/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs b/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs index dfd564c0ee..e672061eee 100644 --- a/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs +++ b/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs @@ -97,7 +97,7 @@ pub fn generate_wrapper_proof( let circuit = proofs[0].circuit; let vk_artifacts = - vk::load_vk_artifacts(&prover.circuits_dir(CircuitFlavor::Default), circuit)?; + vk::load_vk_artifacts(&prover.circuits_dir(CircuitFlavor::Recursive), circuit)?; let full_input = WrapperInput { verification_key: vk_artifacts.verification_key, diff --git a/crates/zk-prover/src/circuits/recursive_aggregation/vk.rs b/crates/zk-prover/src/circuits/recursive_aggregation/vk.rs index cf55a230a1..f4588b7485 100644 --- a/crates/zk-prover/src/circuits/recursive_aggregation/vk.rs +++ b/crates/zk-prover/src/circuits/recursive_aggregation/vk.rs @@ -56,8 +56,9 @@ pub fn load_wrapper_vk_artifacts( } /// Loads VK artifacts from `.vk` and `.vk_hash` in the flavor-specific circuits directory. -/// The caller is responsible for passing the correct circuits_dir -/// (typically `circuits_dir(CircuitFlavor::Default)` for recursive proofs). +/// The caller is responsible for passing the correct circuits_dir: +/// - `circuits_dir(CircuitFlavor::Recursive)` for inner/base proofs embedded in a wrapper +/// - `circuits_dir(CircuitFlavor::Default)` for wrapper/fold proofs pub fn load_vk_artifacts( circuits_dir: &Path, circuit: CircuitName, diff --git a/crates/zk-prover/src/prover.rs b/crates/zk-prover/src/prover.rs index 98a9bfed69..0434aab408 100644 --- a/crates/zk-prover/src/prover.rs +++ b/crates/zk-prover/src/prover.rs @@ -68,8 +68,8 @@ impl ZkProver { self.generate_proof_impl(circuit, witness_data, e3_id, &circuit.dir_path(), flavor) } - /// Generates a proof for recursive aggregation (poseidon2, noir-recursive-no-zk). - /// Uses the default (poseidon) flavor since recursive proofs require poseidon. + /// Generates a proof for recursive aggregation (poseidon, noir-recursive with ZK blinding). + /// Inner/base proofs fed into a wrapper use the Recursive flavor so the witness stays hidden. pub fn generate_recursive_proof( &self, circuit: CircuitName, @@ -81,7 +81,7 @@ impl ZkProver { witness_data, e3_id, &circuit.dir_path(), - CircuitFlavor::Default, + CircuitFlavor::Recursive, ) } diff --git a/crates/zk-prover/src/traits.rs b/crates/zk-prover/src/traits.rs index 6300953e33..cd1325e219 100644 --- a/crates/zk-prover/src/traits.rs +++ b/crates/zk-prover/src/traits.rs @@ -119,7 +119,7 @@ pub trait Provable: Send + Sync { for (i, input) in inputs.iter().enumerate() { let input_map = self.build_inputs(params, input)?; let circuit_path = prover - .circuits_dir(CircuitFlavor::Default) + .circuits_dir(CircuitFlavor::Recursive) .join(resolved_names[i].dir_path()) .join(format!("{}.json", resolved_names[i].as_str())); let circuit = CompiledCircuit::from_file(&circuit_path)?; diff --git a/crates/zk-prover/tests/common/helpers.rs b/crates/zk-prover/tests/common/helpers.rs index deede36a36..48b4c578d7 100644 --- a/crates/zk-prover/tests/common/helpers.rs +++ b/crates/zk-prover/tests/common/helpers.rs @@ -48,6 +48,7 @@ pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_na let json_path = target_dir.join(format!("{circuit_name}.json")); let vk_evm_path = target_dir.join(format!("{circuit_name}.vk")); let vk_recursive_path = target_dir.join(format!("{circuit_name}.vk_recursive")); + let vk_noir_path = target_dir.join(format!("{circuit_name}.vk_noir")); assert!( json_path.exists(), @@ -74,7 +75,7 @@ pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_na .await .unwrap(); - // Set up the default flavor directory (poseidon VK, renamed from .vk_recursive) + // Set up the default flavor directory (noir-recursive-no-zk VK for wrapper/fold proofs) let default_dir = backend .circuits_dir .join("default") @@ -84,7 +85,7 @@ pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_na fs::copy(&json_path, default_dir.join(format!("{circuit_name}.json"))) .await .unwrap(); - // Use the poseidon VK (.vk_recursive) if available, otherwise fall back to .vk + // Use .vk_recursive (noir-recursive-no-zk) if available, otherwise fall back to .vk let default_vk_src = if vk_recursive_path.exists() { &vk_recursive_path } else { @@ -96,6 +97,34 @@ pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_na ) .await .unwrap(); + + // Set up the recursive flavor directory (noir-recursive VK for inner/base proofs) + let recursive_dir = backend + .circuits_dir + .join("recursive") + .join(group) + .join(circuit_name); + fs::create_dir_all(&recursive_dir).await.unwrap(); + fs::copy( + &json_path, + recursive_dir.join(format!("{circuit_name}.json")), + ) + .await + .unwrap(); + // Use .vk_noir (noir-recursive) if available, otherwise fall back to .vk_recursive, then .vk + let recursive_vk_src = if vk_noir_path.exists() { + &vk_noir_path + } else if vk_recursive_path.exists() { + &vk_recursive_path + } else { + &vk_evm_path + }; + fs::copy( + recursive_vk_src, + recursive_dir.join(format!("{circuit_name}.vk")), + ) + .await + .unwrap(); } pub async fn find_anvil() -> bool { diff --git a/scripts/build-circuits.ts b/scripts/build-circuits.ts index befaeb3a62..17f1c134ed 100644 --- a/scripts/build-circuits.ts +++ b/scripts/build-circuits.ts @@ -25,6 +25,8 @@ interface CompiledCircuit { vkHash?: string vkRecursive?: string vkRecursiveHash?: string + vkNoir?: string + vkNoirHash?: string } checksums: { json?: string @@ -32,6 +34,8 @@ interface CompiledCircuit { vkHash?: string vkRecursive?: string vkRecursiveHash?: string + vkNoir?: string + vkNoirHash?: string } } interface BuildOptions { @@ -271,6 +275,14 @@ class NoirCircuitBuilder { result.artifacts.vkRecursiveHash = vkArtifacts.vkRecursiveHash result.checksums.vkRecursiveHash = this.checksum(vkArtifacts.vkRecursiveHash) } + if (vkArtifacts.vkNoir) { + result.artifacts.vkNoir = vkArtifacts.vkNoir + result.checksums.vkNoir = this.checksum(vkArtifacts.vkNoir) + } + if (vkArtifacts.vkNoirHash && existsSync(vkArtifacts.vkNoirHash)) { + result.artifacts.vkNoirHash = vkArtifacts.vkNoirHash + result.checksums.vkNoirHash = this.checksum(vkArtifacts.vkNoirHash) + } } console.log(` ✓ ${circuit.group}/${circuit.name}`) @@ -291,12 +303,16 @@ class NoirCircuitBuilder { vkHash: string | null vkRecursive: string | null vkRecursiveHash: string | null + vkNoir: string | null + vkNoirHash: string | null } { const result = { vk: null as string | null, vkHash: null as string | null, vkRecursive: null as string | null, vkRecursiveHash: null as string | null, + vkNoir: null as string | null, + vkNoirHash: null as string | null, } const isWrapper = this.isWrapper(circuit) @@ -328,20 +344,32 @@ class NoirCircuitBuilder { const vkHashFile = join(targetDir, `${packageName}.vk_hash`) const vkRecursiveFile = join(targetDir, `${packageName}.vk_recursive`) const vkRecursiveHashFile = join(targetDir, `${packageName}.vk_recursive_hash`) + const vkNoirFile = join(targetDir, `${packageName}.vk_noir`) + const vkNoirHashFile = join(targetDir, `${packageName}.vk_noir_hash`) if (!isWrapper) { + // evm VK: for on-chain Solidity verification if (!runWriteVk('evm', vkFile, vkHashFile)) { throw new Error(`VK generation failed for ${packageName} (evm)`) } result.vk = existsSync(vkFile) ? vkFile : null result.vkHash = existsSync(vkHashFile) ? vkHashFile : null + // noir-recursive-no-zk VK: for wrapper/fold output verification (Default flavor) if (!runWriteVk('noir-recursive-no-zk', vkRecursiveFile, vkRecursiveHashFile)) { throw new Error(`VK generation failed for ${packageName} (noir-recursive-no-zk)`) } result.vkRecursive = existsSync(vkRecursiveFile) ? vkRecursiveFile : null result.vkRecursiveHash = existsSync(vkRecursiveHashFile) ? vkRecursiveHashFile : null + + // noir-recursive VK: for inner/base proofs embedded in wrapper inputs (Recursive flavor) + if (!runWriteVk('noir-recursive', vkNoirFile, vkNoirHashFile)) { + throw new Error(`VK generation failed for ${packageName} (noir-recursive)`) + } + result.vkNoir = existsSync(vkNoirFile) ? vkNoirFile : null + result.vkNoirHash = existsSync(vkNoirHashFile) ? vkNoirHashFile : null } else { + // Wrapper/fold circuits: noir-recursive-no-zk only (Default flavor) if (!runWriteVk('noir-recursive-no-zk', vkRecursiveFile, vkRecursiveHashFile)) { throw new Error(`VK generation failed for ${packageName} (noir-recursive-no-zk)`) } @@ -423,6 +451,23 @@ class NoirCircuitBuilder { checksums[f] = c.checksums.vkRecursiveHash lines.push(`${c.checksums.vkRecursiveHash} ${f}`) } + // recursive/ flavor checksums (noir-recursive VKs for inner proofs) + if (c.checksums.vkNoir && c.artifacts.vkNoir) { + const recursivePrefix = `${CIRCUIT_FLAVORS.RECURSIVE}/${c.group}/${c.name}` + if (c.checksums.json && c.artifacts.json) { + const f = `${recursivePrefix}/${basename(c.artifacts.json)}` + checksums[f] = c.checksums.json + lines.push(`${c.checksums.json} ${f}`) + } + const fVk = `${recursivePrefix}/${packageName}.vk` + checksums[fVk] = c.checksums.vkNoir + lines.push(`${c.checksums.vkNoir} ${fVk}`) + if (c.checksums.vkNoirHash && c.artifacts.vkNoirHash) { + const fHash = `${recursivePrefix}/${packageName}.vk_hash` + checksums[fHash] = c.checksums.vkNoirHash + lines.push(`${c.checksums.vkNoirHash} ${fHash}`) + } + } } const outputDir = this.options.outputDir! @@ -447,18 +492,27 @@ class NoirCircuitBuilder { if (c.artifacts.vk) copyFileSync(c.artifacts.vk, join(evmDir, basename(c.artifacts.vk))) if (c.artifacts.vkHash) copyFileSync(c.artifacts.vkHash, join(evmDir, basename(c.artifacts.vkHash))) - // Copy to default/ flavor: .json + poseidon .vk (from .vk_recursive) + .vk_hash (from .vk_recursive_hash) + // Copy to default/ flavor: .json + noir-recursive-no-zk .vk (wrapper/fold proofs) const defaultDir = join(outputDir, CIRCUIT_FLAVORS.DEFAULT, c.group, c.name) mkdirSync(defaultDir, { recursive: true }) if (c.artifacts.json) copyFileSync(c.artifacts.json, join(defaultDir, basename(c.artifacts.json))) if (c.artifacts.vkRecursive) { - // Rename .vk_recursive → .vk in the default flavor directory copyFileSync(c.artifacts.vkRecursive, join(defaultDir, `${packageName}.vk`)) } if (c.artifacts.vkRecursiveHash) { - // Rename .vk_recursive_hash → .vk_hash in the default flavor directory copyFileSync(c.artifacts.vkRecursiveHash, join(defaultDir, `${packageName}.vk_hash`)) } + + // Copy to recursive/ flavor: .json + noir-recursive .vk (inner/base proofs fed into wrapper) + if (c.artifacts.vkNoir) { + const recursiveDir = join(outputDir, CIRCUIT_FLAVORS.RECURSIVE, c.group, c.name) + mkdirSync(recursiveDir, { recursive: true }) + if (c.artifacts.json) copyFileSync(c.artifacts.json, join(recursiveDir, basename(c.artifacts.json))) + copyFileSync(c.artifacts.vkNoir, join(recursiveDir, `${packageName}.vk`)) + if (c.artifacts.vkNoirHash) { + copyFileSync(c.artifacts.vkNoirHash, join(recursiveDir, `${packageName}.vk_hash`)) + } + } } return outputDir } diff --git a/scripts/circuit-constants.ts b/scripts/circuit-constants.ts index 7e3c6853f7..9340888b0f 100644 --- a/scripts/circuit-constants.ts +++ b/scripts/circuit-constants.ts @@ -21,14 +21,16 @@ export const ALL_GROUPS: CircuitGroup[] = [CIRCUIT_GROUPS.DKG, CIRCUIT_GROUPS.TH /** * Circuit flavors determine the hash oracle used for VK generation and proving. * - * - `default`: Uses poseidon hash — for off-chain ciphernode-to-ciphernode verification. - * - `evm`: Uses keccak hash — for on-chain EVM-verifiable proofs. + * - `default`: Uses poseidon/noir-recursive-no-zk — wrapper & fold proofs (efficient, no ZK blinding). + * - `recursive`: Uses poseidon/noir-recursive — inner/base proofs (ZK blinding preserved). + * - `evm`: Uses keccak — for on-chain EVM-verifiable proofs. */ export const CIRCUIT_FLAVORS = { DEFAULT: 'default', + RECURSIVE: 'recursive', EVM: 'evm', } as const export type CircuitFlavor = (typeof CIRCUIT_FLAVORS)[keyof typeof CIRCUIT_FLAVORS] -export const ALL_FLAVORS: CircuitFlavor[] = [CIRCUIT_FLAVORS.DEFAULT, CIRCUIT_FLAVORS.EVM] +export const ALL_FLAVORS: CircuitFlavor[] = [CIRCUIT_FLAVORS.DEFAULT, CIRCUIT_FLAVORS.RECURSIVE, CIRCUIT_FLAVORS.EVM] From e9d91c6e283c76f19ec872faf280890c2d7aae2b Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 5 Mar 2026 14:54:03 +0500 Subject: [PATCH 4/6] feat: add recursive proof circuits --- .github/workflows/ci.yml | 16 +++++++-- crates/zk-prover/tests/common/helpers.rs | 43 +++++++++++++++++++----- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8200b573a5..f837052c4e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -657,9 +657,21 @@ jobs: - name: Verify circuit artifacts run: | echo "DKG circuits:" - ls -la circuits/bin/dkg/target/*.json circuits/bin/dkg/target/*.vk circuits/bin/dkg/target/*.vk_recursive + ls -la circuits/bin/dkg/target/*.json \ + circuits/bin/dkg/target/*.vk \ + circuits/bin/dkg/target/*.vk_hash \ + circuits/bin/dkg/target/*.vk_recursive \ + circuits/bin/dkg/target/*.vk_recursive_hash \ + circuits/bin/dkg/target/*.vk_noir \ + circuits/bin/dkg/target/*.vk_noir_hash echo "Threshold circuits:" - ls -la circuits/bin/threshold/target/*.json circuits/bin/threshold/target/*.vk circuits/bin/threshold/target/*.vk_recursive + ls -la circuits/bin/threshold/target/*.json \ + circuits/bin/threshold/target/*.vk \ + circuits/bin/threshold/target/*.vk_hash \ + circuits/bin/threshold/target/*.vk_recursive \ + circuits/bin/threshold/target/*.vk_recursive_hash \ + circuits/bin/threshold/target/*.vk_noir \ + circuits/bin/threshold/target/*.vk_noir_hash - name: Run ZK prover e2e tests run: cargo test -p e3-zk-prover --test local_e2e_tests -- --nocapture diff --git a/crates/zk-prover/tests/common/helpers.rs b/crates/zk-prover/tests/common/helpers.rs index 48b4c578d7..cb13515e08 100644 --- a/crates/zk-prover/tests/common/helpers.rs +++ b/crates/zk-prover/tests/common/helpers.rs @@ -47,8 +47,11 @@ pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_na let target_dir = circuits_build_root().join(group).join("target"); let json_path = target_dir.join(format!("{circuit_name}.json")); let vk_evm_path = target_dir.join(format!("{circuit_name}.vk")); + let vk_evm_hash_path = target_dir.join(format!("{circuit_name}.vk_hash")); let vk_recursive_path = target_dir.join(format!("{circuit_name}.vk_recursive")); + let vk_recursive_hash_path = target_dir.join(format!("{circuit_name}.vk_recursive_hash")); let vk_noir_path = target_dir.join(format!("{circuit_name}.vk_noir")); + let vk_noir_hash_path = target_dir.join(format!("{circuit_name}.vk_noir_hash")); assert!( json_path.exists(), @@ -61,7 +64,7 @@ pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_na vk_evm_path.display() ); - // Set up the evm flavor directory (keccak VK) + // Set up the evm flavor directory (keccak VK + hash) let evm_dir = backend .circuits_dir .join("evm") @@ -74,6 +77,14 @@ pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_na fs::copy(&vk_evm_path, evm_dir.join(format!("{circuit_name}.vk"))) .await .unwrap(); + if vk_evm_hash_path.exists() { + fs::copy( + &vk_evm_hash_path, + evm_dir.join(format!("{circuit_name}.vk_hash")), + ) + .await + .unwrap(); + } // Set up the default flavor directory (noir-recursive-no-zk VK for wrapper/fold proofs) let default_dir = backend @@ -86,10 +97,10 @@ pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_na .await .unwrap(); // Use .vk_recursive (noir-recursive-no-zk) if available, otherwise fall back to .vk - let default_vk_src = if vk_recursive_path.exists() { - &vk_recursive_path + let (default_vk_src, default_hash_src) = if vk_recursive_path.exists() { + (&vk_recursive_path, &vk_recursive_hash_path) } else { - &vk_evm_path + (&vk_evm_path, &vk_evm_hash_path) }; fs::copy( default_vk_src, @@ -97,6 +108,14 @@ pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_na ) .await .unwrap(); + if default_hash_src.exists() { + fs::copy( + default_hash_src, + default_dir.join(format!("{circuit_name}.vk_hash")), + ) + .await + .unwrap(); + } // Set up the recursive flavor directory (noir-recursive VK for inner/base proofs) let recursive_dir = backend @@ -112,12 +131,12 @@ pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_na .await .unwrap(); // Use .vk_noir (noir-recursive) if available, otherwise fall back to .vk_recursive, then .vk - let recursive_vk_src = if vk_noir_path.exists() { - &vk_noir_path + let (recursive_vk_src, recursive_hash_src) = if vk_noir_path.exists() { + (&vk_noir_path, &vk_noir_hash_path) } else if vk_recursive_path.exists() { - &vk_recursive_path + (&vk_recursive_path, &vk_recursive_hash_path) } else { - &vk_evm_path + (&vk_evm_path, &vk_evm_hash_path) }; fs::copy( recursive_vk_src, @@ -125,6 +144,14 @@ pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_na ) .await .unwrap(); + if recursive_hash_src.exists() { + fs::copy( + recursive_hash_src, + recursive_dir.join(format!("{circuit_name}.vk_hash")), + ) + .await + .unwrap(); + } } pub async fn find_anvil() -> bool { From f0ef4319b83b584785af6813b0ea5999b591fa43 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 5 Mar 2026 17:43:07 +0500 Subject: [PATCH 5/6] fix: review comment --- .../src/circuits/recursive_aggregation/mod.rs | 57 +++++++++++++++---- crates/zk-prover/src/prover.rs | 35 +++--------- crates/zk-prover/src/traits.rs | 48 +++------------- 3 files changed, 64 insertions(+), 76 deletions(-) diff --git a/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs b/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs index e672061eee..f8dba076da 100644 --- a/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs +++ b/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs @@ -309,10 +309,16 @@ mod tests { PkCircuitData::generate_sample(preset).expect("sample data generation should succeed"); let e3_id = "aggregation-test-wrapper"; + + // First generate the inner proof with prove() (Recursive flavor) + let inner_proof = PkCircuit + .prove(&prover, &preset, &sample, &format!("{e3_id}_inner_0")) + .expect("inner prove() should succeed"); + let start = std::time::Instant::now(); let wrapper_proof = PkCircuit - .aggregate_proof(&prover, &preset, &[sample], None, e3_id) - .expect("aggregate_proof (1 input) should succeed"); + .aggregate_proof(&prover, &[inner_proof], None, e3_id) + .expect("aggregate_proof (1 proof) should succeed"); let elapsed = start.elapsed(); eprintln!("1-proof wrapper generation: {:?}", elapsed); @@ -379,10 +385,19 @@ mod tests { .expect("sample B generation should succeed"); let e3_id = "aggregation-2proof-wrapper"; + + // First generate the inner proofs with prove() (Recursive flavor) + let inner_proof_a = ShareDecryptionCircuit + .prove(&prover, &preset, &sample_a, &format!("{e3_id}_inner_0")) + .expect("inner prove() A should succeed"); + let inner_proof_b = ShareDecryptionCircuit + .prove(&prover, &preset, &sample_b, &format!("{e3_id}_inner_1")) + .expect("inner prove() B should succeed"); + let start = std::time::Instant::now(); let wrapper_proof = ShareDecryptionCircuit - .aggregate_proof(&prover, &preset, &[sample_a, sample_b], None, e3_id) - .expect("aggregate_proof (2 inputs) should succeed"); + .aggregate_proof(&prover, &[inner_proof_a, inner_proof_b], None, e3_id) + .expect("aggregate_proof (2 proofs) should succeed"); let elapsed = start.elapsed(); eprintln!("2-proof wrapper generation: {:?}", elapsed); @@ -482,15 +497,36 @@ mod tests { let e3_id = "aggregation-test-fold"; + // Generate inner proofs first with prove() (Recursive flavor) + let pk_inner_proof = PkCircuit + .prove(&prover, &preset, &pk_sample, &format!("{e3_id}_pk_inner_0")) + .expect("pk inner prove() should succeed"); + let pk_wrapper_proof = PkCircuit - .aggregate_proof(&prover, &preset, &[pk_sample], None, e3_id) - .expect("pk aggregate_proof (1 input) should succeed"); + .aggregate_proof(&prover, &[pk_inner_proof], None, e3_id) + .expect("pk aggregate_proof (1 proof) should succeed"); + + let enc_inner_secret = ShareEncryptionCircuit + .prove( + &prover, + &preset, + &share_enc_sample_secret, + &format!("{e3_id}_enc_inner_0"), + ) + .expect("share_encryption inner prove() (secret) should succeed"); + let enc_inner_noise = ShareEncryptionCircuit + .prove( + &prover, + &preset, + &share_enc_sample_noise, + &format!("{e3_id}_enc_inner_1"), + ) + .expect("share_encryption inner prove() (noise) should succeed"); let fold_proof = ShareEncryptionCircuit .aggregate_proof( &prover, - &preset, - &[share_enc_sample_secret, share_enc_sample_noise], + &[enc_inner_secret, enc_inner_noise], Some(&pk_wrapper_proof), e3_id, ) @@ -505,8 +541,9 @@ mod tests { .expect("verification should not error"); assert!(verified, "fold proof should verify successfully"); - prover.cleanup(&format!("{}_inner_0", e3_id)).unwrap(); - prover.cleanup(&format!("{}_inner_1", e3_id)).unwrap(); + prover.cleanup(&format!("{}_pk_inner_0", e3_id)).unwrap(); + prover.cleanup(&format!("{}_enc_inner_0", e3_id)).unwrap(); + prover.cleanup(&format!("{}_enc_inner_1", e3_id)).unwrap(); prover.cleanup(e3_id).unwrap(); } } diff --git a/crates/zk-prover/src/prover.rs b/crates/zk-prover/src/prover.rs index 0434aab408..106bd36c0e 100644 --- a/crates/zk-prover/src/prover.rs +++ b/crates/zk-prover/src/prover.rs @@ -46,7 +46,7 @@ impl ZkProver { witness_data: &[u8], e3_id: &str, ) -> Result { - self.generate_proof_with_flavor(circuit, witness_data, e3_id, CircuitFlavor::Default) + self.generate_proof_with_flavor(circuit, witness_data, e3_id, CircuitFlavor::Recursive) } pub fn generate_evm_proof( @@ -68,25 +68,7 @@ impl ZkProver { self.generate_proof_impl(circuit, witness_data, e3_id, &circuit.dir_path(), flavor) } - /// Generates a proof for recursive aggregation (poseidon, noir-recursive with ZK blinding). - /// Inner/base proofs fed into a wrapper use the Recursive flavor so the witness stays hidden. - pub fn generate_recursive_proof( - &self, - circuit: CircuitName, - witness_data: &[u8], - e3_id: &str, - ) -> Result { - self.generate_proof_impl( - circuit, - witness_data, - e3_id, - &circuit.dir_path(), - CircuitFlavor::Recursive, - ) - } - - /// Generates a proof of the wrapper circuit (for aggregation output). - /// Uses wrapper dir; always uses default (poseidon) flavor for recursive compatibility. + /// Wrapper proof (Default flavor, wrapper dir). pub fn generate_wrapper_proof( &self, circuit: CircuitName, @@ -102,9 +84,7 @@ impl ZkProver { ) } - /// Generates a proof of the fold circuit (for aggregation output). - /// The fold circuit is independent; uses fixed path `recursive_aggregation/fold`. - /// Uses default (poseidon) flavor for recursive compatibility. + /// Fold proof (Default flavor). pub fn generate_fold_proof(&self, witness_data: &[u8], e3_id: &str) -> Result { let dir = CircuitName::Fold.dir_path(); self.generate_proof_impl( @@ -116,7 +96,7 @@ impl ZkProver { ) } - /// Generates the final fold proof for on-chain verification (evm/keccak target). + /// Final fold proof for on-chain verification (Evm flavor). pub fn generate_final_fold_proof( &self, witness_data: &[u8], @@ -227,6 +207,7 @@ impl ZkProver { )) } + /// Verifies a proof (Recursive flavor, matching `prove()`). pub fn verify_proof(&self, proof: &Proof, e3_id: &str, party_id: u64) -> Result { self.verify_proof_impl( proof.circuit, @@ -235,7 +216,7 @@ impl ZkProver { proof.circuit.dir_path(), e3_id, party_id, - CircuitFlavor::Default, + CircuitFlavor::Recursive, ) } @@ -256,7 +237,7 @@ impl ZkProver { ) } - /// Verifies a wrapper/aggregation proof using the wrapper circuit's recursive VK. + /// Verifies a wrapper proof (Default flavor, wrapper dir). pub fn verify_wrapper_proof( &self, proof: &Proof, @@ -274,7 +255,7 @@ impl ZkProver { ) } - /// Verifies a fold proof using the fold circuit's recursive VK. + /// Verifies a fold proof (Default flavor). pub fn verify_fold_proof( &self, proof: &Proof, diff --git a/crates/zk-prover/src/traits.rs b/crates/zk-prover/src/traits.rs index cd1325e219..0d5845990c 100644 --- a/crates/zk-prover/src/traits.rs +++ b/crates/zk-prover/src/traits.rs @@ -68,7 +68,7 @@ pub trait Provable: Send + Sync { let resolved_name = self.resolve_circuit_name(params, input); let circuit_path = prover - .circuits_dir(CircuitFlavor::Default) + .circuits_dir(CircuitFlavor::Recursive) .join(resolved_name.dir_path()) .join(format!("{}.json", resolved_name.as_str())); @@ -80,57 +80,27 @@ pub trait Provable: Send + Sync { prover.generate_proof(resolved_name, &witness, e3_id) } - /// Proves for recursive aggregation (poseidon2). Accepts 1 or 2 inputs of the same circuit, - /// generates recursive proof(s), wraps them with the wrapper circuit. - /// When `aggregated_proof` is provided: if it is a wrapper proof, does initial fold (two wrappers → fold); - /// if it is a fold proof, folds the wrapper with it. When `None`, returns the wrapper proof. + /// Wraps 1–2 proofs (from `prove()`) and optionally folds with `aggregated_proof`. fn aggregate_proof( &self, prover: &ZkProver, - params: &Self::Params, - inputs: &[Self::Input], + proofs: &[Proof], aggregated_proof: Option<&Proof>, e3_id: &str, - ) -> Result - where - Self::Inputs: Computation + serde::Serialize, - ::Error: Display, - { - if !matches!(inputs.len(), 1 | 2) { + ) -> Result { + if !matches!(proofs.len(), 1 | 2) { return Err(ZkError::InvalidInput( - "aggregate_proof requires 1 or 2 inputs".into(), + "aggregate_proof requires 1 or 2 proofs".into(), )); } - let resolved_names: Vec<_> = inputs - .iter() - .map(|input| self.resolve_circuit_name(params, input)) - .collect(); - - if resolved_names.len() == 2 && resolved_names[0] != resolved_names[1] { + if proofs.len() == 2 && proofs[0].circuit != proofs[1].circuit { return Err(ZkError::InvalidInput( - "aggregate_proof requires both inputs to use the same circuit".into(), + "aggregate_proof requires all proofs to use the same circuit".into(), )); } - let mut recursive_proofs = Vec::with_capacity(inputs.len()); - let witness_gen = WitnessGenerator::new(); - - for (i, input) in inputs.iter().enumerate() { - let input_map = self.build_inputs(params, input)?; - let circuit_path = prover - .circuits_dir(CircuitFlavor::Recursive) - .join(resolved_names[i].dir_path()) - .join(format!("{}.json", resolved_names[i].as_str())); - let circuit = CompiledCircuit::from_file(&circuit_path)?; - let witness = witness_gen.generate_witness(&circuit, input_map)?; - let inner_e3_id = format!("{}_inner_{}", e3_id, i); - let proof = - prover.generate_recursive_proof(resolved_names[i], &witness, &inner_e3_id)?; - recursive_proofs.push(proof); - } - - let wrapper_proof = generate_wrapper_proof(prover, &recursive_proofs, e3_id)?; + let wrapper_proof = generate_wrapper_proof(prover, proofs, e3_id)?; match aggregated_proof { Some(ap) => generate_fold_proof(prover, &wrapper_proof, ap, e3_id), From 6d879ce369b10c0a8a77251e1460f11f19bc4690 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 6 Mar 2026 02:27:49 +0500 Subject: [PATCH 6/6] fix: replace flavor with varient --- .github/workflows/ci.yml | 12 +++ crates/events/src/enclave_event/proof.rs | 22 +++--- .../src/circuits/recursive_aggregation/mod.rs | 26 ++++--- .../src/circuits/recursive_aggregation/vk.rs | 8 +- crates/zk-prover/src/lib.rs | 2 +- crates/zk-prover/src/prover.rs | 72 +++++++++--------- crates/zk-prover/src/traits.rs | 34 ++++++++- crates/zk-prover/tests/common/helpers.rs | 6 +- .../tests/onchain_verification_tests.rs | 7 +- scripts/build-circuits.ts | 73 +++++++++++-------- scripts/circuit-constants.ts | 8 +- 11 files changed, 162 insertions(+), 108 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f837052c4e..bf67ce45ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -673,6 +673,18 @@ jobs: circuits/bin/threshold/target/*.vk_noir \ circuits/bin/threshold/target/*.vk_noir_hash + - name: Verify variant output directories + run: | + echo "Checking default/ variant (noir-recursive-no-zk VKs):" + find circuits/bin/default -name '*.json' | head -20 + find circuits/bin/default -name '*.vk' | head -20 + echo "Checking recursive/ variant (noir-recursive VKs):" + find circuits/bin/recursive -name '*.json' | head -20 + find circuits/bin/recursive -name '*.vk' | head -20 + echo "Checking evm/ variant (keccak VKs):" + find circuits/bin/evm -name '*.json' | head -20 + find circuits/bin/evm -name '*.vk' | head -20 + - name: Run ZK prover e2e tests run: cargo test -p e3-zk-prover --test local_e2e_tests -- --nocapture diff --git a/crates/events/src/enclave_event/proof.rs b/crates/events/src/enclave_event/proof.rs index 2bb019419e..b0052643f8 100644 --- a/crates/events/src/enclave_event/proof.rs +++ b/crates/events/src/enclave_event/proof.rs @@ -33,13 +33,13 @@ impl Proof { } } -/// Circuit flavors determine the hash oracle used for VK generation and proving. +/// Circuit variants determine the hash oracle used for VK generation and proving. /// /// - `Default`: poseidon/`noir-recursive-no-zk` — wrapper & fold proofs (no ZK blinding, efficient). /// - `Recursive`: poseidon/`noir-recursive` — inner/base proofs fed into a wrapper (ZK blinding preserved). /// - `Evm`: keccak/`evm` — on-chain EVM-verifiable proofs. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)] -pub enum CircuitFlavor { +pub enum CircuitVariant { /// noir-recursive-no-zk: for wrapper & fold proofs — poseidon, no ZK blinding. #[default] Default, @@ -49,26 +49,26 @@ pub enum CircuitFlavor { Evm, } -impl CircuitFlavor { +impl CircuitVariant { pub fn as_str(&self) -> &'static str { match self { - CircuitFlavor::Default => "default", - CircuitFlavor::Recursive => "recursive", - CircuitFlavor::Evm => "evm", + CircuitVariant::Default => "default", + CircuitVariant::Recursive => "recursive", + CircuitVariant::Evm => "evm", } } - /// Returns the bb verifier target flag value for this flavor. + /// Returns the bb verifier target flag value for this variant. pub fn verifier_target(&self) -> &'static str { match self { - CircuitFlavor::Default => "noir-recursive-no-zk", - CircuitFlavor::Recursive => "noir-recursive", - CircuitFlavor::Evm => "evm", + CircuitVariant::Default => "noir-recursive-no-zk", + CircuitVariant::Recursive => "noir-recursive", + CircuitVariant::Evm => "evm", } } } -impl fmt::Display for CircuitFlavor { +impl fmt::Display for CircuitVariant { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } diff --git a/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs b/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs index f8dba076da..62fed5eb01 100644 --- a/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs +++ b/crates/zk-prover/src/circuits/recursive_aggregation/mod.rs @@ -16,7 +16,7 @@ use crate::circuits::utils::inputs_json_to_input_map; use crate::error::ZkError; use crate::prover::ZkProver; use crate::witness::{CompiledCircuit, WitnessGenerator}; -use e3_events::{CircuitFlavor, CircuitName, Proof}; +use e3_events::{CircuitName, CircuitVariant, Proof}; use self::utils::bytes_to_field_strings; @@ -97,7 +97,7 @@ pub fn generate_wrapper_proof( let circuit = proofs[0].circuit; let vk_artifacts = - vk::load_vk_artifacts(&prover.circuits_dir(CircuitFlavor::Recursive), circuit)?; + vk::load_vk_artifacts(&prover.circuits_dir(CircuitVariant::Recursive), circuit)?; let full_input = WrapperInput { verification_key: vk_artifacts.verification_key, @@ -108,7 +108,7 @@ pub fn generate_wrapper_proof( let dir_path = circuit.wrapper_dir_path(); let circuit_path = prover - .circuits_dir(CircuitFlavor::Default) + .circuits_dir(CircuitVariant::Default) .join(&dir_path) .join(format!("{}.json", circuit.as_str())); let compiled = CompiledCircuit::from_file(&circuit_path)?; @@ -168,10 +168,14 @@ pub fn generate_fold_proof( proof2: &Proof, e3_id: &str, ) -> Result { - let vk1 = - vk::load_vk_for_fold_input(&prover.circuits_dir(CircuitFlavor::Default), proof1.circuit)?; - let vk2 = - vk::load_vk_for_fold_input(&prover.circuits_dir(CircuitFlavor::Default), proof2.circuit)?; + let vk1 = vk::load_vk_for_fold_input( + &prover.circuits_dir(CircuitVariant::Default), + proof1.circuit, + )?; + let vk2 = vk::load_vk_for_fold_input( + &prover.circuits_dir(CircuitVariant::Default), + proof2.circuit, + )?; // Both wrapper and fold output [key_hash, commitment]. let proof1_public_inputs = bytes_to_field_strings(&proof1.public_signals)?; @@ -203,7 +207,7 @@ pub fn generate_fold_proof( let dir_path = CircuitName::Fold.dir_path(); let circuit_path = prover - .circuits_dir(CircuitFlavor::Default) + .circuits_dir(CircuitVariant::Default) .join(&dir_path) .join(format!("{}.json", CircuitName::Fold.as_str())); let compiled = CompiledCircuit::from_file(&circuit_path)?; @@ -310,7 +314,7 @@ mod tests { let e3_id = "aggregation-test-wrapper"; - // First generate the inner proof with prove() (Recursive flavor) + // First generate the inner proof with prove() (Recursive Variant) let inner_proof = PkCircuit .prove(&prover, &preset, &sample, &format!("{e3_id}_inner_0")) .expect("inner prove() should succeed"); @@ -386,7 +390,7 @@ mod tests { let e3_id = "aggregation-2proof-wrapper"; - // First generate the inner proofs with prove() (Recursive flavor) + // First generate the inner proofs with prove() (Recursive Variant) let inner_proof_a = ShareDecryptionCircuit .prove(&prover, &preset, &sample_a, &format!("{e3_id}_inner_0")) .expect("inner prove() A should succeed"); @@ -497,7 +501,7 @@ mod tests { let e3_id = "aggregation-test-fold"; - // Generate inner proofs first with prove() (Recursive flavor) + // Generate inner proofs first with prove() (Recursive Variant) let pk_inner_proof = PkCircuit .prove(&prover, &preset, &pk_sample, &format!("{e3_id}_pk_inner_0")) .expect("pk inner prove() should succeed"); diff --git a/crates/zk-prover/src/circuits/recursive_aggregation/vk.rs b/crates/zk-prover/src/circuits/recursive_aggregation/vk.rs index f4588b7485..53bcf636ab 100644 --- a/crates/zk-prover/src/circuits/recursive_aggregation/vk.rs +++ b/crates/zk-prover/src/circuits/recursive_aggregation/vk.rs @@ -5,7 +5,7 @@ // or FITNESS FOR A PARTICULAR PURPOSE. //! Loads verification key and hash for inner circuits (wrapper proof aggregation). -//! In the default flavor directory, these are stored as `.vk` and `.vk_hash`. +//! In the default variant directory, these are stored as `.vk` and `.vk_hash`. use super::utils::bytes_to_field_strings; use crate::error::ZkError; @@ -55,10 +55,10 @@ pub fn load_wrapper_vk_artifacts( load_vk_from_dir(&circuit_dir, circuit.as_str()) } -/// Loads VK artifacts from `.vk` and `.vk_hash` in the flavor-specific circuits directory. +/// Loads VK artifacts from `.vk` and `.vk_hash` in the variant-specific circuits directory. /// The caller is responsible for passing the correct circuits_dir: -/// - `circuits_dir(CircuitFlavor::Recursive)` for inner/base proofs embedded in a wrapper -/// - `circuits_dir(CircuitFlavor::Default)` for wrapper/fold proofs +/// - `circuits_dir(CircuitVariant::Recursive)` for inner/base proofs embedded in a wrapper +/// - `circuits_dir(CircuitVariant::Default)` for wrapper/fold proofs pub fn load_vk_artifacts( circuits_dir: &Path, circuit: CircuitName, diff --git a/crates/zk-prover/src/lib.rs b/crates/zk-prover/src/lib.rs index 5f4f9a933b..f2c4e54306 100644 --- a/crates/zk-prover/src/lib.rs +++ b/crates/zk-prover/src/lib.rs @@ -22,7 +22,7 @@ pub use actors::{ pub use backend::{SetupStatus, ZkBackend}; pub use circuits::recursive_aggregation::{generate_fold_proof, generate_wrapper_proof}; pub use config::{verify_checksum, BbTarget, CircuitInfo, VersionInfo, ZkConfig}; -pub use e3_events::CircuitFlavor; +pub use e3_events::CircuitVariant; pub use e3_zk_helpers::circuits::dkg::pk::circuit::PkCircuit; pub use error::ZkError; pub use prover::ZkProver; diff --git a/crates/zk-prover/src/prover.rs b/crates/zk-prover/src/prover.rs index 106bd36c0e..39ef9becdf 100644 --- a/crates/zk-prover/src/prover.rs +++ b/crates/zk-prover/src/prover.rs @@ -6,7 +6,7 @@ use crate::backend::ZkBackend; use crate::error::ZkError; -use e3_events::{CircuitFlavor, CircuitName, Proof}; +use e3_events::{CircuitName, CircuitVariant, Proof}; use e3_utils::utility_types::ArcBytes; use std::fs; use std::path::PathBuf; @@ -28,8 +28,8 @@ impl ZkProver { } } - pub fn circuits_dir(&self, flavor: CircuitFlavor) -> PathBuf { - self.circuits_dir.join(flavor.as_str()) + pub fn circuits_dir(&self, variant: CircuitVariant) -> PathBuf { + self.circuits_dir.join(variant.as_str()) } pub fn work_dir(&self) -> &PathBuf { @@ -46,7 +46,7 @@ impl ZkProver { witness_data: &[u8], e3_id: &str, ) -> Result { - self.generate_proof_with_flavor(circuit, witness_data, e3_id, CircuitFlavor::Recursive) + self.generate_proof_with_variant(circuit, witness_data, e3_id, CircuitVariant::Recursive) } pub fn generate_evm_proof( @@ -55,20 +55,20 @@ impl ZkProver { witness_data: &[u8], e3_id: &str, ) -> Result { - self.generate_proof_with_flavor(circuit, witness_data, e3_id, CircuitFlavor::Evm) + self.generate_proof_with_variant(circuit, witness_data, e3_id, CircuitVariant::Evm) } - pub fn generate_proof_with_flavor( + pub fn generate_proof_with_variant( &self, circuit: CircuitName, witness_data: &[u8], e3_id: &str, - flavor: CircuitFlavor, + variant: CircuitVariant, ) -> Result { - self.generate_proof_impl(circuit, witness_data, e3_id, &circuit.dir_path(), flavor) + self.generate_proof_impl(circuit, witness_data, e3_id, &circuit.dir_path(), variant) } - /// Wrapper proof (Default flavor, wrapper dir). + /// Wrapper proof (Default variant, wrapper dir). pub fn generate_wrapper_proof( &self, circuit: CircuitName, @@ -80,11 +80,11 @@ impl ZkProver { witness_data, e3_id, &circuit.wrapper_dir_path(), - CircuitFlavor::Default, + CircuitVariant::Default, ) } - /// Fold proof (Default flavor). + /// Fold proof (Default variant). pub fn generate_fold_proof(&self, witness_data: &[u8], e3_id: &str) -> Result { let dir = CircuitName::Fold.dir_path(); self.generate_proof_impl( @@ -92,11 +92,11 @@ impl ZkProver { witness_data, e3_id, &dir, - CircuitFlavor::Default, + CircuitVariant::Default, ) } - /// Final fold proof for on-chain verification (Evm flavor). + /// Final fold proof for on-chain verification (Evm variant). pub fn generate_final_fold_proof( &self, witness_data: &[u8], @@ -108,7 +108,7 @@ impl ZkProver { witness_data, e3_id, &dir, - CircuitFlavor::Evm, + CircuitVariant::Evm, ) } @@ -118,15 +118,15 @@ impl ZkProver { witness_data: &[u8], e3_id: &str, dir_path: &str, - flavor: CircuitFlavor, + variant: CircuitVariant, ) -> Result { if !self.bb_binary.exists() { return Err(ZkError::BbNotInstalled); } - let verifier_target = flavor.verifier_target(); + let verifier_target = variant.verifier_target(); - let circuit_dir = self.circuits_dir(flavor).join(dir_path); + let circuit_dir = self.circuits_dir(variant).join(dir_path); let circuit_path = circuit_dir.join(format!("{}.json", circuit.as_str())); let vk_path = circuit_dir.join(format!("{}.vk", circuit.as_str())); @@ -207,8 +207,18 @@ impl ZkProver { )) } - /// Verifies a proof (Recursive flavor, matching `prove()`). + /// Verifies a proof (Recursive variant, matching `prove()`). pub fn verify_proof(&self, proof: &Proof, e3_id: &str, party_id: u64) -> Result { + self.verify_proof_with_variant(proof, e3_id, party_id, CircuitVariant::Recursive) + } + + pub fn verify_proof_with_variant( + &self, + proof: &Proof, + e3_id: &str, + party_id: u64, + variant: CircuitVariant, + ) -> Result { self.verify_proof_impl( proof.circuit, &proof.data, @@ -216,7 +226,7 @@ impl ZkProver { proof.circuit.dir_path(), e3_id, party_id, - CircuitFlavor::Recursive, + variant, ) } @@ -226,18 +236,10 @@ impl ZkProver { e3_id: &str, party_id: u64, ) -> Result { - self.verify_proof_impl( - proof.circuit, - &proof.data, - &proof.public_signals, - proof.circuit.dir_path(), - e3_id, - party_id, - CircuitFlavor::Evm, - ) + self.verify_proof_with_variant(proof, e3_id, party_id, CircuitVariant::Evm) } - /// Verifies a wrapper proof (Default flavor, wrapper dir). + /// Verifies a wrapper proof (Default Variant, wrapper dir). pub fn verify_wrapper_proof( &self, proof: &Proof, @@ -251,11 +253,11 @@ impl ZkProver { proof.circuit.wrapper_dir_path(), e3_id, party_id, - CircuitFlavor::Default, + CircuitVariant::Default, ) } - /// Verifies a fold proof (Default flavor). + /// Verifies a fold proof (Default variant). pub fn verify_fold_proof( &self, proof: &Proof, @@ -276,7 +278,7 @@ impl ZkProver { proof.circuit.dir_path(), e3_id, party_id, - CircuitFlavor::Default, + CircuitVariant::Default, ) } @@ -288,15 +290,15 @@ impl ZkProver { dir_path: String, e3_id: &str, party_id: u64, - flavor: CircuitFlavor, + variant: CircuitVariant, ) -> Result { if !self.bb_binary.exists() { return Err(ZkError::BbNotInstalled); } - let verifier_target = flavor.verifier_target(); + let verifier_target = variant.verifier_target(); let vk_path = self - .circuits_dir(flavor) + .circuits_dir(variant) .join(&dir_path) .join(format!("{}.vk", circuit.as_str())); if !vk_path.exists() { diff --git a/crates/zk-prover/src/traits.rs b/crates/zk-prover/src/traits.rs index 0d5845990c..0152b87479 100644 --- a/crates/zk-prover/src/traits.rs +++ b/crates/zk-prover/src/traits.rs @@ -11,7 +11,7 @@ use crate::circuits::utils::inputs_json_to_input_map; use crate::error::ZkError; use crate::prover::ZkProver; use crate::witness::{CompiledCircuit, WitnessGenerator}; -use e3_events::{CircuitFlavor, CircuitName, Proof}; +use e3_events::{CircuitName, CircuitVariant, Proof}; use e3_zk_helpers::Computation; use noirc_abi::InputMap; @@ -60,6 +60,21 @@ pub trait Provable: Send + Sync { input: &Self::Input, e3_id: &str, ) -> Result + where + Self::Inputs: Computation + serde::Serialize, + ::Error: Display, + { + self.prove_with_variant(prover, params, input, e3_id, CircuitVariant::Recursive) + } + + fn prove_with_variant( + &self, + prover: &ZkProver, + params: &Self::Params, + input: &Self::Input, + e3_id: &str, + variant: CircuitVariant, + ) -> Result where Self::Inputs: Computation + serde::Serialize, ::Error: Display, @@ -68,7 +83,7 @@ pub trait Provable: Send + Sync { let resolved_name = self.resolve_circuit_name(params, input); let circuit_path = prover - .circuits_dir(CircuitFlavor::Recursive) + .circuits_dir(variant) .join(resolved_name.dir_path()) .join(format!("{}.json", resolved_name.as_str())); @@ -77,7 +92,7 @@ pub trait Provable: Send + Sync { let witness_gen = WitnessGenerator::new(); let witness = witness_gen.generate_witness(&circuit, inputs)?; - prover.generate_proof(resolved_name, &witness, e3_id) + prover.generate_proof_with_variant(resolved_name, &witness, e3_id, variant) } /// Wraps 1–2 proofs (from `prove()`) and optionally folds with `aggregated_proof`. @@ -114,6 +129,17 @@ pub trait Provable: Send + Sync { proof: &Proof, e3_id: &str, party_id: u64, + ) -> Result { + self.verify_with_variant(prover, proof, e3_id, party_id, CircuitVariant::Recursive) + } + + fn verify_with_variant( + &self, + prover: &ZkProver, + proof: &Proof, + e3_id: &str, + party_id: u64, + variant: CircuitVariant, ) -> Result { if !self.valid_circuits().contains(&proof.circuit) { return Err(ZkError::VerifyFailed(format!( @@ -127,6 +153,6 @@ pub trait Provable: Send + Sync { "Verifying proof for circuit {} with e3_id {} and party_id {}", proof.circuit, e3_id, party_id ); - prover.verify_proof(proof, e3_id, party_id) + prover.verify_proof_with_variant(proof, e3_id, party_id, variant) } } diff --git a/crates/zk-prover/tests/common/helpers.rs b/crates/zk-prover/tests/common/helpers.rs index cb13515e08..3cd01581bc 100644 --- a/crates/zk-prover/tests/common/helpers.rs +++ b/crates/zk-prover/tests/common/helpers.rs @@ -64,7 +64,7 @@ pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_na vk_evm_path.display() ); - // Set up the evm flavor directory (keccak VK + hash) + // Set up the evm variant directory (keccak VK + hash) let evm_dir = backend .circuits_dir .join("evm") @@ -86,7 +86,7 @@ pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_na .unwrap(); } - // Set up the default flavor directory (noir-recursive-no-zk VK for wrapper/fold proofs) + // Set up the default variant directory (noir-recursive-no-zk VK for wrapper/fold proofs) let default_dir = backend .circuits_dir .join("default") @@ -117,7 +117,7 @@ pub async fn setup_compiled_circuit(backend: &ZkBackend, group: &str, circuit_na .unwrap(); } - // Set up the recursive flavor directory (noir-recursive VK for inner/base proofs) + // Set up the recursive variant directory (noir-recursive VK for inner/base proofs) let recursive_dir = backend .circuits_dir .join("recursive") diff --git a/crates/zk-prover/tests/onchain_verification_tests.rs b/crates/zk-prover/tests/onchain_verification_tests.rs index 14b933c3cf..a79c6f53a5 100644 --- a/crates/zk-prover/tests/onchain_verification_tests.rs +++ b/crates/zk-prover/tests/onchain_verification_tests.rs @@ -20,7 +20,7 @@ use alloy::{ use common::{find_anvil, find_bb, setup_compiled_circuit, setup_test_prover}; use e3_fhe_params::BfvPreset; use e3_zk_helpers::circuits::dkg::pk::circuit::{PkCircuit, PkCircuitData}; -use e3_zk_prover::{Provable, ZkProver}; +use e3_zk_prover::{CircuitVariant, Provable, ZkProver}; use std::path::PathBuf; sol! { @@ -83,8 +83,9 @@ async fn test_pk_bfv_onchain_verification() { let prover = ZkProver::new(&backend); let e3_id = "0"; + // Generate proof with EVM variant (keccak) for on-chain Solidity verification let proof = PkCircuit - .prove(&prover, &preset, &sample, e3_id) + .prove_with_variant(&prover, &preset, &sample, e3_id, CircuitVariant::Evm) .expect("proof generation should succeed"); assert!(!proof.data.is_empty(), "proof data should not be empty"); @@ -93,7 +94,7 @@ async fn test_pk_bfv_onchain_verification() { "public signals should not be empty" ); - let local_ok = PkCircuit.verify(&prover, &proof, e3_id, 1); + let local_ok = PkCircuit.verify_with_variant(&prover, &proof, e3_id, 1, CircuitVariant::Evm); assert!( local_ok.as_ref().is_ok_and(|&v| v), "local proof verification failed: {local_ok:?}" diff --git a/scripts/build-circuits.ts b/scripts/build-circuits.ts index 17f1c134ed..09fa4b2f78 100644 --- a/scripts/build-circuits.ts +++ b/scripts/build-circuits.ts @@ -9,7 +9,7 @@ import { execSync } from 'child_process' import { createHash } from 'crypto' import { appendFileSync, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'fs' import { basename, join, resolve } from 'path' -import { ALL_GROUPS, CIRCUIT_GROUPS, CIRCUIT_FLAVORS, type CircuitGroup, type CircuitFlavor } from './circuit-constants' +import { ALL_GROUPS, CIRCUIT_GROUPS, CIRCUIT_VARIANTS, type CircuitGroup, type CircuitVariant } from './circuit-constants' interface CircuitInfo { name: string @@ -355,26 +355,33 @@ class NoirCircuitBuilder { result.vk = existsSync(vkFile) ? vkFile : null result.vkHash = existsSync(vkHashFile) ? vkHashFile : null - // noir-recursive-no-zk VK: for wrapper/fold output verification (Default flavor) + // noir-recursive-no-zk VK: for wrapper/fold output verification (Default variant) if (!runWriteVk('noir-recursive-no-zk', vkRecursiveFile, vkRecursiveHashFile)) { throw new Error(`VK generation failed for ${packageName} (noir-recursive-no-zk)`) } result.vkRecursive = existsSync(vkRecursiveFile) ? vkRecursiveFile : null result.vkRecursiveHash = existsSync(vkRecursiveHashFile) ? vkRecursiveHashFile : null - // noir-recursive VK: for inner/base proofs embedded in wrapper inputs (Recursive flavor) + // noir-recursive VK: for inner/base proofs embedded in wrapper inputs (Recursive variant) if (!runWriteVk('noir-recursive', vkNoirFile, vkNoirHashFile)) { throw new Error(`VK generation failed for ${packageName} (noir-recursive)`) } result.vkNoir = existsSync(vkNoirFile) ? vkNoirFile : null result.vkNoirHash = existsSync(vkNoirHashFile) ? vkNoirHashFile : null } else { - // Wrapper/fold circuits: noir-recursive-no-zk only (Default flavor) + // Wrapper circuits: noir-recursive-no-zk (Default variant) if (!runWriteVk('noir-recursive-no-zk', vkRecursiveFile, vkRecursiveHashFile)) { throw new Error(`VK generation failed for ${packageName} (noir-recursive-no-zk)`) } result.vkRecursive = existsSync(vkRecursiveFile) ? vkRecursiveFile : null result.vkRecursiveHash = existsSync(vkRecursiveHashFile) ? vkRecursiveHashFile : null + + // noir-recursive VK: needed if wrapper/fold proofs are embedded in further recursive aggregation + if (!runWriteVk('noir-recursive', vkNoirFile, vkNoirHashFile)) { + throw new Error(`VK generation failed for ${packageName} (noir-recursive)`) + } + result.vkNoir = existsSync(vkNoirFile) ? vkNoirFile : null + result.vkNoirHash = existsSync(vkNoirHashFile) ? vkNoirHashFile : null } return result @@ -414,46 +421,46 @@ class NoirCircuitBuilder { for (const c of compiled) { const packageName = basename(c.artifacts.json ?? '', '.json') - // evm/ flavor checksums - const evmPrefix = `${CIRCUIT_FLAVORS.EVM}/${c.group}/${c.name}` - if (c.checksums.json && c.artifacts.json) { - const f = `${evmPrefix}/${basename(c.artifacts.json)}` - checksums[f] = c.checksums.json - lines.push(`${c.checksums.json} ${f}`) - } + // evm/ variant checksums (only for circuits that have an evm VK) if (c.checksums.vk && c.artifacts.vk) { + const evmPrefix = `${CIRCUIT_VARIANTS.EVM}/${c.group}/${c.name}` + if (c.checksums.json && c.artifacts.json) { + const f = `${evmPrefix}/${basename(c.artifacts.json)}` + checksums[f] = c.checksums.json + lines.push(`${c.checksums.json} ${f}`) + } const f = `${evmPrefix}/${basename(c.artifacts.vk)}` checksums[f] = c.checksums.vk lines.push(`${c.checksums.vk} ${f}`) - } - if (c.checksums.vkHash && c.artifacts.vkHash) { - const f = `${evmPrefix}/${basename(c.artifacts.vkHash)}` - checksums[f] = c.checksums.vkHash - lines.push(`${c.checksums.vkHash} ${f}`) + if (c.checksums.vkHash && c.artifacts.vkHash) { + const fHash = `${evmPrefix}/${basename(c.artifacts.vkHash)}` + checksums[fHash] = c.checksums.vkHash + lines.push(`${c.checksums.vkHash} ${fHash}`) + } } - // default/ flavor checksums - const defaultPrefix = `${CIRCUIT_FLAVORS.DEFAULT}/${c.group}/${c.name}` + // default/ variant checksums + const defaultPrefix = `${CIRCUIT_VARIANTS.DEFAULT}/${c.group}/${c.name}` if (c.checksums.json && c.artifacts.json) { const f = `${defaultPrefix}/${basename(c.artifacts.json)}` checksums[f] = c.checksums.json lines.push(`${c.checksums.json} ${f}`) } if (c.checksums.vkRecursive && c.artifacts.vkRecursive) { - // In default/ flavor, .vk_recursive is stored as .vk + // In default/ variant, .vk_recursive is stored as .vk const f = `${defaultPrefix}/${packageName}.vk` checksums[f] = c.checksums.vkRecursive lines.push(`${c.checksums.vkRecursive} ${f}`) } if (c.checksums.vkRecursiveHash && c.artifacts.vkRecursiveHash) { - // In default/ flavor, .vk_recursive_hash is stored as .vk_hash + // In default/ variant, .vk_recursive_hash is stored as .vk_hash const f = `${defaultPrefix}/${packageName}.vk_hash` checksums[f] = c.checksums.vkRecursiveHash lines.push(`${c.checksums.vkRecursiveHash} ${f}`) } - // recursive/ flavor checksums (noir-recursive VKs for inner proofs) + // recursive/ variant checksums (noir-recursive VKs for inner proofs) if (c.checksums.vkNoir && c.artifacts.vkNoir) { - const recursivePrefix = `${CIRCUIT_FLAVORS.RECURSIVE}/${c.group}/${c.name}` + const recursivePrefix = `${CIRCUIT_VARIANTS.RECURSIVE}/${c.group}/${c.name}` if (c.checksums.json && c.artifacts.json) { const f = `${recursivePrefix}/${basename(c.artifacts.json)}` checksums[f] = c.checksums.json @@ -485,15 +492,17 @@ class NoirCircuitBuilder { if (!c.artifacts.json && !c.artifacts.vk) continue const packageName = basename(c.artifacts.json ?? '', '.json') - // Copy to evm/ flavor: .json + evm .vk + .vk_hash - const evmDir = join(outputDir, CIRCUIT_FLAVORS.EVM, c.group, c.name) - mkdirSync(evmDir, { recursive: true }) - if (c.artifacts.json) copyFileSync(c.artifacts.json, join(evmDir, basename(c.artifacts.json))) - if (c.artifacts.vk) copyFileSync(c.artifacts.vk, join(evmDir, basename(c.artifacts.vk))) - if (c.artifacts.vkHash) copyFileSync(c.artifacts.vkHash, join(evmDir, basename(c.artifacts.vkHash))) + // Copy to evm/ variant: .json + evm .vk + .vk_hash (only for circuits that have an evm VK) + if (c.artifacts.vk) { + const evmDir = join(outputDir, CIRCUIT_VARIANTS.EVM, c.group, c.name) + mkdirSync(evmDir, { recursive: true }) + if (c.artifacts.json) copyFileSync(c.artifacts.json, join(evmDir, basename(c.artifacts.json))) + copyFileSync(c.artifacts.vk, join(evmDir, basename(c.artifacts.vk))) + if (c.artifacts.vkHash) copyFileSync(c.artifacts.vkHash, join(evmDir, basename(c.artifacts.vkHash))) + } - // Copy to default/ flavor: .json + noir-recursive-no-zk .vk (wrapper/fold proofs) - const defaultDir = join(outputDir, CIRCUIT_FLAVORS.DEFAULT, c.group, c.name) + // Copy to default/ variant: .json + noir-recursive-no-zk .vk (wrapper/fold proofs) + const defaultDir = join(outputDir, CIRCUIT_VARIANTS.DEFAULT, c.group, c.name) mkdirSync(defaultDir, { recursive: true }) if (c.artifacts.json) copyFileSync(c.artifacts.json, join(defaultDir, basename(c.artifacts.json))) if (c.artifacts.vkRecursive) { @@ -503,9 +512,9 @@ class NoirCircuitBuilder { copyFileSync(c.artifacts.vkRecursiveHash, join(defaultDir, `${packageName}.vk_hash`)) } - // Copy to recursive/ flavor: .json + noir-recursive .vk (inner/base proofs fed into wrapper) + // Copy to recursive/ variant: .json + noir-recursive .vk (inner/base proofs fed into wrapper) if (c.artifacts.vkNoir) { - const recursiveDir = join(outputDir, CIRCUIT_FLAVORS.RECURSIVE, c.group, c.name) + const recursiveDir = join(outputDir, CIRCUIT_VARIANTS.RECURSIVE, c.group, c.name) mkdirSync(recursiveDir, { recursive: true }) if (c.artifacts.json) copyFileSync(c.artifacts.json, join(recursiveDir, basename(c.artifacts.json))) copyFileSync(c.artifacts.vkNoir, join(recursiveDir, `${packageName}.vk`)) diff --git a/scripts/circuit-constants.ts b/scripts/circuit-constants.ts index 9340888b0f..3cdeccca28 100644 --- a/scripts/circuit-constants.ts +++ b/scripts/circuit-constants.ts @@ -19,18 +19,18 @@ export type CircuitGroup = (typeof CIRCUIT_GROUPS)[keyof typeof CIRCUIT_GROUPS] export const ALL_GROUPS: CircuitGroup[] = [CIRCUIT_GROUPS.DKG, CIRCUIT_GROUPS.THRESHOLD, CIRCUIT_GROUPS.AGGREGATION] /** - * Circuit flavors determine the hash oracle used for VK generation and proving. + * Circuit variants determine the hash oracle used for VK generation and proving. * * - `default`: Uses poseidon/noir-recursive-no-zk — wrapper & fold proofs (efficient, no ZK blinding). * - `recursive`: Uses poseidon/noir-recursive — inner/base proofs (ZK blinding preserved). * - `evm`: Uses keccak — for on-chain EVM-verifiable proofs. */ -export const CIRCUIT_FLAVORS = { +export const CIRCUIT_VARIANTS = { DEFAULT: 'default', RECURSIVE: 'recursive', EVM: 'evm', } as const -export type CircuitFlavor = (typeof CIRCUIT_FLAVORS)[keyof typeof CIRCUIT_FLAVORS] +export type CircuitVariant = (typeof CIRCUIT_VARIANTS)[keyof typeof CIRCUIT_VARIANTS] -export const ALL_FLAVORS: CircuitFlavor[] = [CIRCUIT_FLAVORS.DEFAULT, CIRCUIT_FLAVORS.RECURSIVE, CIRCUIT_FLAVORS.EVM] +export const ALL_VARIANTS: CircuitVariant[] = [CIRCUIT_VARIANTS.DEFAULT, CIRCUIT_VARIANTS.RECURSIVE, CIRCUIT_VARIANTS.EVM]