diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb834ff979..bf67ce45ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -657,9 +657,33 @@ 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_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 + 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: 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 ef5837fcdf..b0052643f8 100644 --- a/crates/events/src/enclave_event/proof.rs +++ b/crates/events/src/enclave_event/proof.rs @@ -33,6 +33,47 @@ impl Proof { } } +/// 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 CircuitVariant { + /// noir-recursive-no-zk: for wrapper & fold proofs — poseidon, no ZK blinding. + #[default] + Default, + /// noir-recursive: for inner/base proofs — poseidon with ZK blinding. + Recursive, + /// evm: keccak-based for on-chain Solidity verification. + Evm, +} + +impl CircuitVariant { + pub fn as_str(&self) -> &'static str { + match self { + CircuitVariant::Default => "default", + CircuitVariant::Recursive => "recursive", + CircuitVariant::Evm => "evm", + } + } + + /// Returns the bb verifier target flag value for this variant. + pub fn verifier_target(&self) -> &'static str { + match self { + CircuitVariant::Default => "noir-recursive-no-zk", + CircuitVariant::Recursive => "noir-recursive", + CircuitVariant::Evm => "evm", + } + } +} + +impl fmt::Display for CircuitVariant { + 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..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::{CircuitName, Proof}; +use e3_events::{CircuitName, CircuitVariant, 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(CircuitVariant::Recursive), 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(CircuitVariant::Default) .join(&dir_path) .join(format!("{}.json", circuit.as_str())); let compiled = CompiledCircuit::from_file(&circuit_path)?; @@ -167,8 +168,14 @@ 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(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)?; @@ -200,7 +207,7 @@ pub fn generate_fold_proof( let dir_path = CircuitName::Fold.dir_path(); let circuit_path = prover - .circuits_dir() + .circuits_dir(CircuitVariant::Default) .join(&dir_path) .join(format!("{}.json", CircuitName::Fold.as_str())); let compiled = CompiledCircuit::from_file(&circuit_path)?; @@ -275,11 +282,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 +296,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() @@ -304,10 +313,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 Variant) + 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); @@ -332,12 +347,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 +362,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", @@ -372,10 +389,19 @@ mod tests { .expect("sample B generation should succeed"); let e3_id = "aggregation-2proof-wrapper"; + + // 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"); + 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); @@ -404,43 +430,45 @@ mod tests { backend.ensure_installed().await.expect("ensure_installed"); let dist = dist_circuits_path(); - let pk_wrapper = 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 = dist + let share_enc_wrapper = default_dir .join("recursive_aggregation") .join("wrapper") .join("dkg") .join("share_encryption"); - let fold_dir = dist.join("recursive_aggregation").join("fold"); - if pk_wrapper.join("pk.json").exists() - && pk_wrapper.join("pk.vk_recursive").exists() - && share_enc_wrapper.join("share_encryption.json").exists() - && share_enc_wrapper - .join("share_encryption.vk_recursive") - .exists() - && fold_dir.join("fold.json").exists() - && fold_dir.join("fold.vk_recursive").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_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() @@ -473,15 +501,36 @@ mod tests { let e3_id = "aggregation-test-fold"; + // 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"); + 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, ) @@ -496,8 +545,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/circuits/recursive_aggregation/vk.rs b/crates/zk-prover/src/circuits/recursive_aggregation/vk.rs index dc58a2bbf6..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). -//! Reads `.vk_recursive` and `.vk_recursive_hash` (poseidon2/noir-recursive-no-zk format). +//! In the default variant 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, variant: CircuitVariant) -> PathBuf { + self.circuits_dir.join(variant.as_str()) } pub fn work_dir(&self) -> &PathBuf { @@ -46,28 +46,29 @@ 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_variant(circuit, witness_data, e3_id, CircuitVariant::Recursive) } - /// Generates a proof for recursive aggregation (poseidon2, noir-recursive-no-zk). - /// Uses inner circuit dir and `.vk_recursive`. - pub fn generate_recursive_proof( + pub fn generate_evm_proof( &self, circuit: CircuitName, witness_data: &[u8], e3_id: &str, ) -> Result { - self.generate_proof_impl( - circuit, - witness_data, - e3_id, - &circuit.dir_path(), - Some("noir-recursive-no-zk"), - ) + self.generate_proof_with_variant(circuit, witness_data, e3_id, CircuitVariant::Evm) } - /// Generates a proof of the wrapper circuit (for aggregation output). - /// Uses wrapper dir; verifier_target determines proof format and VK suffix. + pub fn generate_proof_with_variant( + &self, + circuit: CircuitName, + witness_data: &[u8], + e3_id: &str, + variant: CircuitVariant, + ) -> Result { + self.generate_proof_impl(circuit, witness_data, e3_id, &circuit.dir_path(), variant) + } + + /// Wrapper proof (Default variant, wrapper dir). pub fn generate_wrapper_proof( &self, circuit: CircuitName, @@ -79,13 +80,11 @@ impl ZkProver { witness_data, e3_id, &circuit.wrapper_dir_path(), - Some("noir-recursive-no-zk"), + CircuitVariant::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`. + /// 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( @@ -93,18 +92,24 @@ impl ZkProver { witness_data, e3_id, &dir, - Some("noir-recursive-no-zk"), + CircuitVariant::Default, ) } - /// Generates the final fold proof for on-chain verification (evm target). + /// Final fold proof for on-chain verification (Evm variant). 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, + CircuitVariant::Evm, + ) } fn generate_proof_impl( @@ -113,20 +118,17 @@ impl ZkProver { witness_data: &[u8], e3_id: &str, dir_path: &str, - verifier_target: Option<&str>, + variant: CircuitVariant, ) -> 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 = variant.verifier_target(); - let circuit_dir = self.circuits_dir.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{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 +164,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 +175,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()?; @@ -208,7 +207,18 @@ impl ZkProver { )) } + /// 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,11 +226,20 @@ impl ZkProver { proof.circuit.dir_path(), e3_id, party_id, - None, + variant, ) } - /// Verifies a wrapper/aggregation proof using the wrapper circuit's recursive VK. + pub fn verify_evm_proof( + &self, + proof: &Proof, + e3_id: &str, + party_id: u64, + ) -> Result { + self.verify_proof_with_variant(proof, e3_id, party_id, CircuitVariant::Evm) + } + + /// Verifies a wrapper proof (Default Variant, wrapper dir). pub fn verify_wrapper_proof( &self, proof: &Proof, @@ -234,11 +253,11 @@ impl ZkProver { proof.circuit.wrapper_dir_path(), e3_id, party_id, - Some("noir-recursive-no-zk"), + CircuitVariant::Default, ) } - /// Verifies a fold proof using the fold circuit's recursive VK. + /// Verifies a fold proof (Default variant). pub fn verify_fold_proof( &self, proof: &Proof, @@ -259,7 +278,7 @@ impl ZkProver { proof.circuit.dir_path(), e3_id, party_id, - Some("noir-recursive-no-zk"), + CircuitVariant::Default, ) } @@ -271,20 +290,17 @@ impl ZkProver { dir_path: String, e3_id: &str, party_id: u64, - verifier_target: Option<&str>, + variant: CircuitVariant, ) -> 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 = variant.verifier_target(); let vk_path = self - .circuits_dir + .circuits_dir(variant) .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 +331,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 +341,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..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::{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() + .circuits_dir(variant) .join(resolved_name.dir_path()) .join(format!("{}.json", resolved_name.as_str())); @@ -77,60 +92,30 @@ 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) } - /// 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() - .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), @@ -144,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!( @@ -157,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 47fb0c4838..3cd01581bc 100644 --- a/crates/zk-prover/tests/common/helpers.rs +++ b/crates/zk-prover/tests/common/helpers.rs @@ -46,7 +46,12 @@ 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_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(), @@ -54,19 +59,99 @@ 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 variant directory (keccak VK + hash) + 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(); + 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 variant directory (noir-recursive-no-zk VK for wrapper/fold proofs) + 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 .vk_recursive (noir-recursive-no-zk) if available, otherwise fall back to .vk + 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_hash_path) + }; + fs::copy( + default_vk_src, + default_dir.join(format!("{circuit_name}.vk")), + ) + .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 variant 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, 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_hash_path) + } else { + (&vk_evm_path, &vk_evm_hash_path) + }; + fs::copy( + recursive_vk_src, + recursive_dir.join(format!("{circuit_name}.vk")), + ) + .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 { @@ -98,9 +183,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/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 169d769c27..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, type CircuitGroup } from './circuit-constants' +import { ALL_GROUPS, CIRCUIT_GROUPS, CIRCUIT_VARIANTS, type CircuitGroup, type CircuitVariant } from './circuit-constants' interface CircuitInfo { name: string @@ -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,25 +344,44 @@ 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 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 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 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 @@ -384,32 +419,62 @@ class NoirCircuitBuilder { const checksums: Record = {} for (const c of compiled) { - const prefix = `${c.group}/${c.name}` - if (c.checksums.json && c.artifacts.json) { - const f = `${prefix}/${basename(c.artifacts.json)}` - checksums[f] = c.checksums.json - lines.push(`${c.checksums.json} ${f}`) - } + const packageName = basename(c.artifacts.json ?? '', '.json') + + // evm/ variant checksums (only for circuits that have an evm VK) if (c.checksums.vk && c.artifacts.vk) { - const f = `${prefix}/${basename(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 fHash = `${evmPrefix}/${basename(c.artifacts.vkHash)}` + checksums[fHash] = c.checksums.vkHash + lines.push(`${c.checksums.vkHash} ${fHash}`) + } } - if (c.checksums.vkHash && c.artifacts.vkHash) { - const f = `${prefix}/${basename(c.artifacts.vkHash)}` - checksums[f] = c.checksums.vkHash - lines.push(`${c.checksums.vkHash} ${f}`) + + // 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) { - const f = `${prefix}/${basename(c.artifacts.vkRecursive)}` + // 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) { - const f = `${prefix}/${basename(c.artifacts.vkRecursiveHash)}` + // 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/ variant checksums (noir-recursive VKs for inner proofs) + if (c.checksums.vkNoir && c.artifacts.vkNoir) { + 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 + 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! @@ -425,13 +490,38 @@ 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/ 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/ 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) { + copyFileSync(c.artifacts.vkRecursive, join(defaultDir, `${packageName}.vk`)) + } + if (c.artifacts.vkRecursiveHash) { + copyFileSync(c.artifacts.vkRecursiveHash, join(defaultDir, `${packageName}.vk_hash`)) + } + + // Copy to recursive/ variant: .json + noir-recursive .vk (inner/base proofs fed into wrapper) + if (c.artifacts.vkNoir) { + 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`)) + 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 a0fd798130..3cdeccca28 100644 --- a/scripts/circuit-constants.ts +++ b/scripts/circuit-constants.ts @@ -17,3 +17,20 @@ 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 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_VARIANTS = { + DEFAULT: 'default', + RECURSIVE: 'recursive', + EVM: 'evm', +} as const + +export type CircuitVariant = (typeof CIRCUIT_VARIANTS)[keyof typeof CIRCUIT_VARIANTS] + +export const ALL_VARIANTS: CircuitVariant[] = [CIRCUIT_VARIANTS.DEFAULT, CIRCUIT_VARIANTS.RECURSIVE, CIRCUIT_VARIANTS.EVM]