diff --git a/.gitignore b/.gitignore index 628b5e4..d842ad5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /target .idea *.hex -/generated-bins/prover.bin +/generated-bins/*prover.bin \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index e73b717..8620e34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3582,9 +3582,9 @@ dependencies = [ [[package]] name = "qp-dilithium-crypto" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c3fbdb9557a22cb876fc17c483d090e9556cbfdfc52beb0696143d4313d9" +checksum = "476cab75a58360464ef58fe3bfaa01b4a450a0895d32f028a1e17a0b9031657c" dependencies = [ "log", "parity-scale-codec", @@ -3754,17 +3754,15 @@ dependencies = [ [[package]] name = "qp-rusty-crystals-hdwallet" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91781bc0b96238c7e038a2d3157410388cb0458b05d42483851e79684e92f1a8" +checksum = "113fdac36387a857ab4e09fdc050098bc7b55bcd6e5a8ab755e7f44ef10741e6" dependencies = [ "bip39", - "bs58", "getrandom 0.2.17", "hex", "hex-literal", "hmac 0.12.1", - "k256", "qp-poseidon-core", "qp-rusty-crystals-dilithium", "serde", @@ -3777,8 +3775,7 @@ dependencies = [ [[package]] name = "qp-wormhole-aggregator" version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad3d3f37af4748e635f9197b2145cf4d218b97ad361e6b696724e3ddbb4e12a" +source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=refactor%2Fsep-prover-and-circuit-build#dd187e0b3b5f0113814ffb1b3e4084b0df76175a" dependencies = [ "anyhow", "hex", @@ -3797,8 +3794,7 @@ dependencies = [ [[package]] name = "qp-wormhole-circuit" version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7cdfba4fd293063a3e9eb964e2afb58673e9a7fd6d4edb0484783e0ed600927" +source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=refactor%2Fsep-prover-and-circuit-build#dd187e0b3b5f0113814ffb1b3e4084b0df76175a" dependencies = [ "anyhow", "hex", @@ -3810,8 +3806,7 @@ dependencies = [ [[package]] name = "qp-wormhole-inputs" version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53ad195630b070fc8cd9d89c55a951abaae9694434793bc87f5ab3045ded7108" +source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=refactor%2Fsep-prover-and-circuit-build#dd187e0b3b5f0113814ffb1b3e4084b0df76175a" dependencies = [ "anyhow", ] @@ -3819,8 +3814,7 @@ dependencies = [ [[package]] name = "qp-wormhole-prover" version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d244e8514279f65d25f15ed5a6e6464905ac5276724a9233574696e11a461c3a" +source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=refactor%2Fsep-prover-and-circuit-build#dd187e0b3b5f0113814ffb1b3e4084b0df76175a" dependencies = [ "anyhow", "qp-plonky2", @@ -3832,8 +3826,7 @@ dependencies = [ [[package]] name = "qp-wormhole-verifier" version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e95153853ceceeba61295ca5f1316d12bde37677b0c1e7f0539d815f627645" +source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=refactor%2Fsep-prover-and-circuit-build#dd187e0b3b5f0113814ffb1b3e4084b0df76175a" dependencies = [ "anyhow", "qp-plonky2-verifier", @@ -3843,8 +3836,7 @@ dependencies = [ [[package]] name = "qp-zk-circuits-common" version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45c3d80adc2aecbcf27902569d3ec291f5f83e9d7d17ad12530f45102963faa" +source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=refactor%2Fsep-prover-and-circuit-build#dd187e0b3b5f0113814ffb1b3e4084b0df76175a" dependencies = [ "anyhow", "hex", @@ -3922,9 +3914,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes", "getrandom 0.3.4", diff --git a/Cargo.toml b/Cargo.toml index c08da6e..edb7925 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,12 +74,13 @@ subxt-metadata = "0.44" # ZK proof generation (aligned with chain) anyhow = "1.0" -qp-wormhole-circuit = { version = "1.0.7", default-features = false, features = ["std"] } -qp-wormhole-prover = { version = "1.0.7", default-features = false, features = ["std"] } -qp-wormhole-verifier = { version = "1.0.7", default-features = false, features = ["std"] } -qp-wormhole-aggregator = { version = "1.0.7", default-features = false, features = ["rayon", "std"] } -qp-wormhole-inputs = { version = "1.0.7", default-features = false, features = ["std"] } -qp-zk-circuits-common = { version = "1.0.7", default-features = false, features = ["std"] } +qp-wormhole-circuit = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "refactor/sep-prover-and-circuit-build", package = "qp-wormhole-circuit", default-features = false, features = ["std"] } +qp-wormhole-prover = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "refactor/sep-prover-and-circuit-build", package = "qp-wormhole-prover", default-features = false, features = ["std"] } +qp-wormhole-verifier = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "refactor/sep-prover-and-circuit-build", package = "qp-wormhole-verifier", default-features = false, features = ["std"] } +qp-wormhole-aggregator = { git = "https://github.com/Quantus-Network/qp-zk-circuits", package = "qp-wormhole-aggregator", branch = "refactor/sep-prover-and-circuit-build", default-features = false, features = ["rayon", "std"] } +# qp-wormhole-aggregator = { path = "../zk-circuits/wormhole/aggregator", package = "qp-wormhole-aggregator", default-features = false, features = ["rayon", "std"] } +qp-wormhole-inputs = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "refactor/sep-prover-and-circuit-build", package = "qp-wormhole-inputs", default-features = false, features = ["std"] } +qp-zk-circuits-common = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "refactor/sep-prover-and-circuit-build", package = "qp-zk-circuits-common", default-features = false, features = ["std"] } qp-plonky2 = { version = "1.1.3", default-features = false, features = ["rand", "std"] } [dev-dependencies] diff --git a/README.md b/README.md index bf20aba..e9ba97d 100644 --- a/README.md +++ b/README.md @@ -250,14 +250,15 @@ Build ZK circuit binaries from the `qp-zk-circuits` repository, then copy them t ```bash quantus developer build-circuits \ - --branching-factor 2 \ + --num-leaf-proofs 2 \ + --num-layer0-proofs 2 \ --depth 1 \ --circuits-path ../qp-zk-circuits \ --chain-path ../chain ``` -- `--branching-factor`: Number of proofs aggregated at each tree level. -- `--depth`: Depth of the aggregation tree. Total leaf proofs = `branching_factor ^ depth`. +- `--num-leaf-proofs`: Number of leaf proofs per layer-0 aggregation. +- `--num-layer0-proofs`: Number of inner proofs per layer-1 aggregation. - `--circuits-path`: Path to the `qp-zk-circuits` repo (default: `../qp-zk-circuits`). - `--chain-path`: Path to the chain repo (default: `../chain`). - `--skip-chain`: Skip copying binaries to the chain directory. diff --git a/generated-bins/aggregated_common.bin b/generated-bins/aggregated_common.bin index 1121006..897819f 100644 Binary files a/generated-bins/aggregated_common.bin and b/generated-bins/aggregated_common.bin differ diff --git a/generated-bins/aggregated_verifier.bin b/generated-bins/aggregated_verifier.bin index 8e3e3a5..0f517e1 100644 Binary files a/generated-bins/aggregated_verifier.bin and b/generated-bins/aggregated_verifier.bin differ diff --git a/generated-bins/config.json b/generated-bins/config.json index 2dced0c..72d2fbe 100644 --- a/generated-bins/config.json +++ b/generated-bins/config.json @@ -1,11 +1,13 @@ { - "num_leaf_proofs": 16, + "num_leaf_proofs": 8, + "num_layer0_proofs": null, "hashes": { "common": "672689a87e8ed780337c0752ebc7fd1db6a63611fbd59b4ad0cbe4a4d97edcf2", "verifier": "bb017485b12fb9c6d0b5c3db8b68f417bd3f75b2d5f3a2ea5fe12b6244233372", "prover": "78c114c7290b04bac00551a590fd652f98194653b10ac4e11b0c0ddd5c7c0976", - "aggregated_common": "af4461081f6fb527d2b9ffb74479a133ed8b92cdd3554b46adc481a0dfc38b5d", - "aggregated_verifier": "90350437c8e0e2144ca849623ea0b58edd2decd7bdf6b728b32e1aa9d8f1e337", - "dummy_proof": "8c46fd19c4c3581016ba4adcf4f0735bd04bf09f8e94e9b0f092ea522c9d3ba9" + "aggregated_common": "20a2dc0e48a9d7a69c9bffca5e9ee917769abdbd1f512dcb5ba303af4f69ed91", + "aggregated_verifier": "22913f5a8da5bfe1d3153e8fb4ca3c9bce02ad1afc7a07d229c48b40f67f5b8b", + "aggregated_prover": "168212332937960d0a8319f1944ecd5357476a2f0b7017c2dedb6ae461717d6b", + "dummy_proof": "6828bea2ccd42eac05f9e785f3834a1cc218553a68b090718d84c2d9dd381e8d" } } \ No newline at end of file diff --git a/generated-bins/dummy_proof.bin b/generated-bins/dummy_proof.bin index 7802f3d..affcb7c 100644 Binary files a/generated-bins/dummy_proof.bin and b/generated-bins/dummy_proof.bin differ diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 2d3e436..4caf840 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -298,10 +298,14 @@ pub enum DeveloperCommands { #[arg(long, default_value = "../chain")] chain_path: String, - /// Number of leaf proofs aggregated into a single proof + /// Number of leaf proofs aggregated into a single layer-0 proof #[arg(long)] num_leaf_proofs: usize, + /// Number of inner layer-0 proofs aggregated into a single layer-1 proof + #[arg(long)] + num_layer0_proofs: Option, + /// Skip copying to chain directory #[arg(long)] skip_chain: bool, @@ -569,8 +573,17 @@ pub async fn handle_developer_command(command: DeveloperCommands) -> crate::erro circuits_path, chain_path, num_leaf_proofs, + num_layer0_proofs, skip_chain, - } => build_wormhole_circuits(&circuits_path, &chain_path, num_leaf_proofs, skip_chain).await, + } => + build_wormhole_circuits( + &circuits_path, + &chain_path, + num_leaf_proofs, + num_layer0_proofs, + skip_chain, + ) + .await, } } @@ -579,11 +592,16 @@ async fn build_wormhole_circuits( circuits_path: &str, chain_path: &str, num_leaf_proofs: usize, + num_layer0_proofs: Option, skip_chain: bool, ) -> crate::error::Result<()> { use std::{path::Path, process::Command}; - log_print!("Building ZK circuit binaries (num_leaf_proofs={})", num_leaf_proofs); + log_print!( + "Building ZK circuit binaries (num_leaf_proofs={}, num_layer0_proofs={})", + num_leaf_proofs, + num_layer0_proofs.unwrap_or(0) + ); log_print!(""); let circuits_dir = Path::new(circuits_path); @@ -619,13 +637,16 @@ async fn build_wormhole_circuits( // Step 2: Run the circuit builder to generate binaries log_print!("Step 2/4: Generating circuit binaries (this may take a while)..."); let builder_path = circuits_dir.join("target/release/qp-wormhole-circuit-builder"); - let run_output = Command::new(&builder_path) - .args(["--num-leaf-proofs", &num_leaf_proofs.to_string()]) - .current_dir(circuits_dir) - .output() - .map_err(|e| { - crate::error::QuantusError::Generic(format!("Failed to run circuit builder: {}", e)) - })?; + let mut cmd = Command::new(&builder_path); + cmd.arg("--num-leaf-proofs").arg(num_leaf_proofs.to_string()); + + if let Some(num_layer0) = num_layer0_proofs { + cmd.arg("--num-layer0-proofs").arg(num_layer0.to_string()); + } + + let run_output = cmd.current_dir(circuits_dir).output().map_err(|e| { + crate::error::QuantusError::Generic(format!("Failed to run circuit builder: {}", e)) + })?; if !run_output.status.success() { let stderr = String::from_utf8_lossy(&run_output.stderr); @@ -641,17 +662,30 @@ async fn build_wormhole_circuits( let source_bins = circuits_dir.join("generated-bins"); let cli_bins = Path::new("generated-bins"); - let cli_bin_files = [ - "common.bin", - "verifier.bin", - "prover.bin", - "dummy_proof.bin", - "aggregated_common.bin", - "aggregated_verifier.bin", - "config.json", + let possible_cli_bin_files = [ + "common.bin", // leaf circuit + "verifier.bin", // leaf circuit + "prover.bin", // leaf circuit + "dummy_proof.bin", // leaf dummy proof + "aggregated_common.bin", // layer-0 aggregated circuit + "aggregated_verifier.bin", // layer-0 aggregated circuit + "aggregated_prover.bin", // layer-0 aggregated circuit + "config.json", // config file with metadata about the circuit bin data + // Layer-0 binaries are always generated, but layer-1 binaries are only generated if + // num_layer0_proofs is set + "layer1_common.bin", // layer-1 aggregated circuit + "layer1_verifier.bin", // layer-1 aggregated circuit + "layer1_prover.bin", // layer-1 aggregated circuit ]; - for file in &cli_bin_files { + let cli_bin_files = if num_layer0_proofs.is_some() { + &possible_cli_bin_files + } else { + // If num_layer0_proofs is not set, we only generate layer-0 binaries. + &possible_cli_bin_files[0..8] + }; + + for file in cli_bin_files { let src = source_bins.join(file); let dst = cli_bins.join(file); std::fs::copy(&src, &dst).map_err(|e| { diff --git a/src/cli/wormhole.rs b/src/cli/wormhole.rs index a42afc3..51e6fb7 100644 --- a/src/cli/wormhole.rs +++ b/src/cli/wormhole.rs @@ -17,6 +17,10 @@ use qp_rusty_crystals_hdwallet::{ derive_wormhole_from_mnemonic, generate_mnemonic, SensitiveBytes32, WormholePair, QUANTUS_WORMHOLE_CHAIN_ID, }; +use qp_wormhole_aggregator::{ + aggregator::{AggregationBackend, CircuitType}, + config::CircuitBinsConfig, +}; use qp_wormhole_circuit::{ inputs::{CircuitInputs, ParseAggregatedPublicInputs, PrivateCircuitInputs}, nullifier::Nullifier, @@ -52,95 +56,6 @@ pub const SCALE_DOWN_FACTOR: u128 = 10_000_000_000; /// This must match the on-chain VolumeFeeRateBps configuration pub const VOLUME_FEE_BPS: u32 = 10; -/// SHA256 hashes of circuit binary files for integrity verification. -/// Must match the BinaryHashes struct in qp-wormhole-aggregator/src/config.rs -#[derive(Debug, Clone, serde::Deserialize, Default)] -pub struct BinaryHashes { - pub prover: Option, - pub aggregated_common: Option, - pub aggregated_verifier: Option, - pub dummy_proof: Option, -} - -/// Aggregation config loaded from generated-bins/config.json. -/// Must match the CircuitBinsConfig struct in qp-wormhole-aggregator/src/config.rs -#[derive(Debug, Clone, serde::Deserialize)] -pub struct AggregationConfig { - pub num_leaf_proofs: usize, - #[serde(default)] - pub hashes: Option, -} - -impl AggregationConfig { - /// Load config from the generated-bins directory - pub fn load_from_bins() -> crate::error::Result { - let config_path = Path::new("generated-bins/config.json"); - let config_str = std::fs::read_to_string(config_path).map_err(|e| { - crate::error::QuantusError::Generic(format!( - "Failed to read aggregation config from {}: {}. Run 'quantus developer build-circuits' first.", - config_path.display(), - e - )) - })?; - serde_json::from_str(&config_str).map_err(|e| { - crate::error::QuantusError::Generic(format!( - "Failed to parse aggregation config: {}", - e - )) - }) - } - - /// Verify that the binary files in generated-bins match the stored hashes. - pub fn verify_binary_hashes(&self) -> crate::error::Result<()> { - use sha2::{Digest, Sha256}; - - let Some(ref stored_hashes) = self.hashes else { - log_verbose!(" No hashes in config.json, skipping binary verification"); - return Ok(()); - }; - - let bins_dir = Path::new("generated-bins"); - let mut mismatches = Vec::new(); - - let hash_file = |filename: &str| -> Option { - let path = bins_dir.join(filename); - std::fs::read(&path).ok().map(|bytes| { - let hash = Sha256::digest(&bytes); - hex::encode(hash) - }) - }; - - let checks = [ - ("aggregated_common.bin", &stored_hashes.aggregated_common), - ("aggregated_verifier.bin", &stored_hashes.aggregated_verifier), - ("prover.bin", &stored_hashes.prover), - ("dummy_proof.bin", &stored_hashes.dummy_proof), - ]; - for (filename, expected_hash) in checks { - if let Some(ref expected) = expected_hash { - if let Some(actual) = hash_file(filename) { - if expected != &actual { - mismatches.push(format!("{}...", filename)); - } - } - } - } - - if mismatches.is_empty() { - log_verbose!(" Binary hashes verified successfully"); - Ok(()) - } else { - Err(crate::error::QuantusError::Generic(format!( - "Binary hash mismatch detected! The circuit binaries do not match config.json.\n\ - This can happen if binaries were regenerated but the CLI wasn't rebuilt.\n\ - Mismatches:\n {}\n\n\ - To fix: Run 'quantus developer build-circuits' and then 'cargo build --release'", - mismatches.join("\n ") - ))) - } - } -} - /// Compute output amount after fee deduction /// output = input * (10000 - fee_bps) / 10000 pub fn compute_output_amount(input_amount: u32, fee_bps: u32) -> u32 { @@ -857,8 +772,7 @@ async fn aggregate_proofs( proof_files: Vec, output_file: String, ) -> crate::error::Result<()> { - use qp_wormhole_aggregator::aggregator::WormholeProofAggregator; - use qp_zk_circuits_common::aggregation::AggregationConfig as AggConfig; + use qp_wormhole_aggregator::aggregator::Layer0Aggregator; use std::path::Path; @@ -866,11 +780,12 @@ async fn aggregate_proofs( // Load config first to validate and calculate padding needs let bins_dir = Path::new("generated-bins"); - let agg_config = AggregationConfig::load_from_bins()?; - - // Verify binary hashes match config.json to detect stale binaries - log_verbose!("Verifying circuit binary integrity..."); - agg_config.verify_binary_hashes()?; + let agg_config = CircuitBinsConfig::load(bins_dir).map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Failed to load circuit bins config from {:?}: {}", + bins_dir, e + )) + })?; // Validate number of proofs before doing expensive work if proof_files.len() > agg_config.num_leaf_proofs { @@ -888,17 +803,17 @@ async fn aggregate_proofs( // This also generates the padding (dummy) proofs needed log_print!(" Loading aggregator and generating {} dummy proofs...", num_padding_proofs); - let aggr_config = AggConfig::new(agg_config.num_leaf_proofs); - let mut aggregator = WormholeProofAggregator::from_prebuilt_dir(bins_dir, aggr_config) - .map_err(|e| { - crate::error::QuantusError::Generic(format!( - "Failed to load aggregator from pre-built bins: {}", - e - )) - })?; + let mut aggregator = Layer0Aggregator::new(bins_dir).map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Failed to load aggregator from pre-built bins: {}", + e + )) + })?; - log_verbose!("Aggregation config: num_leaf_proofs={}", aggregator.config.num_leaf_proofs); - let common_data = aggregator.leaf_circuit_data.common.clone(); + log_verbose!("Aggregation config: num_leaf_proofs={}", aggregator.batch_size()); + let common_data = aggregator.load_common_data(CircuitType::Leaf).map_err(|e| { + crate::error::QuantusError::Generic(format!("Failed to get common data: {}", e)) + })?; // Load and add proofs using helper function for (idx, proof_file) in proof_files.iter().enumerate() { @@ -930,15 +845,14 @@ async fn aggregate_proofs( log_print!(" Aggregation: {:.2}s", agg_elapsed.as_secs_f64()); // Parse and display aggregated public inputs - let aggregated_public_inputs = AggregatedPublicCircuitInputs::try_from_felts( - aggregated_proof.proof.public_inputs.as_slice(), - ) - .map_err(|e| { - crate::error::QuantusError::Generic(format!( - "Failed to parse aggregated public inputs: {}", - e - )) - })?; + let aggregated_public_inputs = + AggregatedPublicCircuitInputs::try_from_felts(aggregated_proof.public_inputs.as_slice()) + .map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Failed to parse aggregated public inputs: {}", + e + )) + })?; log_verbose!("Aggregated public inputs: {:#?}", aggregated_public_inputs); @@ -966,18 +880,15 @@ async fn aggregate_proofs( // Verify the aggregated proof locally log_verbose!("Verifying aggregated proof locally..."); - aggregated_proof - .circuit_data - .verify(aggregated_proof.proof.clone()) - .map_err(|e| { - crate::error::QuantusError::Generic(format!( - "Aggregated proof verification failed: {}", - e - )) - })?; + aggregator.verify(aggregated_proof.clone()).map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Local aggregated proof verification failed: {}", + e + )) + })?; // Save aggregated proof using helper function - write_proof_file(&output_file, &aggregated_proof.proof.to_bytes()).map_err(|e| { + write_proof_file(&output_file, &aggregated_proof.to_bytes()).map_err(|e| { crate::error::QuantusError::Generic(format!("Failed to write proof: {}", e)) })?; @@ -1352,7 +1263,7 @@ fn load_multiround_wallet( fn print_multiround_config( config: &MultiroundConfig, wallet: &MultiroundWalletContext, - agg_config: &AggregationConfig, + num_leaf_proofs: usize, ) { use colored::Colorize; @@ -1367,7 +1278,7 @@ fn print_multiround_config( ); log_print!(" Proofs per round: {}", config.num_proofs); log_print!(" Rounds: {}", config.rounds); - log_print!(" Aggregation: num_leaf_proofs={}", agg_config.num_leaf_proofs); + log_print!(" Aggregation: num_leaf_proofs={}", num_leaf_proofs); log_print!(" Output directory: {}", config.output_dir); log_print!(" Keep files: {}", config.keep_files); log_print!(""); @@ -1714,7 +1625,10 @@ async fn run_multiround( log_print!(""); // Load aggregation config from generated-bins/config.json - let agg_config = AggregationConfig::load_from_bins()?; + let bins_dir = Path::new("generated-bins"); + let agg_config = CircuitBinsConfig::load(bins_dir).map_err(|e| { + crate::error::QuantusError::Generic(format!("Failed to load aggregation config: {}", e)) + })?; // Validate parameters validate_multiround_params(num_proofs, rounds, agg_config.num_leaf_proofs)?; @@ -1727,7 +1641,7 @@ async fn run_multiround( MultiroundConfig { num_proofs, rounds, amount, output_dir: output_dir.clone(), keep_files }; // Print configuration - print_multiround_config(&config, &wallet, &agg_config); + print_multiround_config(&config, &wallet, agg_config.num_leaf_proofs); log_print!(" Dry run: {}", dry_run); log_print!(""); @@ -2478,13 +2392,12 @@ async fn run_dissolve( // Load aggregation config let bins_dir = std::path::Path::new("generated-bins"); - let agg_config: AggregationConfig = - serde_json::from_reader(std::fs::File::open(bins_dir.join("config.json")).map_err( - |e| crate::error::QuantusError::Generic(format!("Failed to open config.json: {}", e)), - )?) - .map_err(|e| { - crate::error::QuantusError::Generic(format!("Failed to parse config.json: {}", e)) - })?; + let agg_config = CircuitBinsConfig::load(bins_dir).map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Failed to load aggregation circuit config: {}", + e + )) + })?; // === Layer 0: Initial funding === log_print!("{}", "Layer 0: Initial funding".bright_yellow()); @@ -2630,7 +2543,7 @@ async fn run_dissolve( // Aggregate log_print!(" Aggregating..."); let aggregated_file = format!("{}/batch{}_aggregated.hex", layer_dir, batch_idx); - aggregate_proofs_to_file(&proof_files, &aggregated_file, &agg_config)?; + aggregate_proofs_to_file(&proof_files, &aggregated_file)?; // Verify on-chain log_print!(" Verifying on-chain..."); @@ -2704,22 +2617,17 @@ async fn run_dissolve( } /// Helper to aggregate proof files and write the result -fn aggregate_proofs_to_file( - proof_files: &[String], - output_file: &str, - agg_config: &AggregationConfig, -) -> crate::error::Result<()> { - use qp_wormhole_aggregator::aggregator::WormholeProofAggregator; - use qp_zk_circuits_common::aggregation::AggregationConfig as AggConfig; +fn aggregate_proofs_to_file(proof_files: &[String], output_file: &str) -> crate::error::Result<()> { + use qp_wormhole_aggregator::aggregator::Layer0Aggregator; let bins_dir = std::path::Path::new("generated-bins"); - let aggr_config = AggConfig::new(agg_config.num_leaf_proofs); - let mut aggregator = WormholeProofAggregator::from_prebuilt_dir(bins_dir, aggr_config) - .map_err(|e| { - crate::error::QuantusError::Generic(format!("Failed to create aggregator: {}", e)) - })?; + let mut aggregator = Layer0Aggregator::new(bins_dir).map_err(|e| { + crate::error::QuantusError::Generic(format!("Failed to create aggregator: {}", e)) + })?; - let common_data = aggregator.leaf_circuit_data.common.clone(); + let common_data = aggregator.load_common_data(CircuitType::Leaf).map_err(|e| { + crate::error::QuantusError::Generic(format!("Failed to load common data: {}", e)) + })?; for proof_file in proof_files { let proof_bytes = read_hex_proof_file_to_bytes(proof_file)?; @@ -2736,13 +2644,13 @@ fn aggregate_proofs_to_file( } let agg_start = std::time::Instant::now(); - let result = aggregator + let proof = aggregator .aggregate() .map_err(|e| crate::error::QuantusError::Generic(format!("Aggregation failed: {}", e)))?; let agg_elapsed = agg_start.elapsed(); log_print!(" Aggregation: {:.2}s", agg_elapsed.as_secs_f64()); - let proof_hex = hex::encode(result.proof.to_bytes()); + let proof_hex = hex::encode(proof.to_bytes()); std::fs::write(output_file, &proof_hex).map_err(|e| { crate::error::QuantusError::Generic(format!("Failed to write proof: {}", e)) })?; @@ -3000,22 +2908,28 @@ mod tests { // If the upstream adds/removes/renames fields, this test will catch it. let json = r#"{ "num_leaf_proofs": 8, + "num_layer0_proofs": null, "hashes": { "common": "aabbcc", "verifier": "ddeeff", "prover": "112233", "aggregated_common": "445566", - "aggregated_verifier": "778899" + "aggregated_verifier": "778899", + "aggregated_prover": "99aabb", + "dummy_proof": "bbccdd" } }"#; - let config: AggregationConfig = serde_json::from_str(json).unwrap(); + let config: CircuitBinsConfig = serde_json::from_str(json).unwrap(); assert_eq!(config.num_leaf_proofs, 8); + assert_eq!(config.num_layer0_proofs, None); let hashes = config.hashes.unwrap(); assert_eq!(hashes.prover.as_deref(), Some("112233")); assert_eq!(hashes.aggregated_common.as_deref(), Some("445566")); assert_eq!(hashes.aggregated_verifier.as_deref(), Some("778899")); + assert_eq!(hashes.aggregated_prover.as_deref(), Some("99aabb")); + assert_eq!(hashes.dummy_proof.as_deref(), Some("bbccdd")); } fn mk_accounts(n: usize) -> Vec<[u8; 32]> { diff --git a/tests/wormhole_integration.rs b/tests/wormhole_integration.rs index 1353029..455405a 100644 --- a/tests/wormhole_integration.rs +++ b/tests/wormhole_integration.rs @@ -3,7 +3,7 @@ //! These tests require a local Quantus node running at ws://127.0.0.1:9944 //! with funded developer accounts (crystal_alice, crystal_bob, crystal_charlie). //! -//! Run with: `cargo test --test wormhole_integration -- --ignored --nocapture` +//! Run with: `cargo test --release --test wormhole_integration -- --ignored --nocapture` //! //! The tests verify the full end-to-end flow: //! 1. Fund an unspendable account via wormhole transfer @@ -15,6 +15,8 @@ //! with valid parent hash linkage. We use batch transfers to ensure same-block proofs. use plonky2::plonk::{circuit_data::CircuitConfig, proof::ProofWithPublicInputs}; +use qp_poseidon::ToFelts; +use qp_wormhole_aggregator::aggregator::{AggregationBackend, Layer0Aggregator}; use qp_wormhole_circuit::{ inputs::{CircuitInputs, PrivateCircuitInputs}, nullifier::Nullifier, @@ -456,7 +458,7 @@ async fn generate_proof_from_transfer( async fn submit_single_proof_for_verification( quantus_client: &QuantusClient, proof_bytes: Vec, -) -> Result<(), String> { +) -> Result { println!(" Submitting single proof for on-chain verification..."); let verify_tx = quantus_node::api::tx().wormhole().verify_aggregated_proof(proof_bytes); @@ -477,7 +479,7 @@ async fn submit_single_proof_for_verification( TxStatus::InBestBlock(tx_in_block) => { let block_hash = tx_in_block.block_hash(); println!(" ✅ Single proof verified on-chain! Block: {:?}", block_hash); - return Ok(()); + return Ok(block_hash); }, TxStatus::InFinalizedBlock(tx_in_block) => { let block_hash = tx_in_block.block_hash(); @@ -485,7 +487,7 @@ async fn submit_single_proof_for_verification( " ✅ Single proof verified on-chain (finalized)! Block: {:?}", block_hash ); - return Ok(()); + return Ok(block_hash); }, TxStatus::Error { message } | TxStatus::Invalid { message } => { return Err(format!("Transaction failed: {}", message)); @@ -502,27 +504,24 @@ fn aggregate_proofs( proof_contexts: Vec, num_leaf_proofs: usize, ) -> Result { - use qp_wormhole_aggregator::aggregator::WormholeProofAggregator; - use qp_zk_circuits_common::aggregation::AggregationConfig; - println!( " Aggregating {} proofs (num_leaf_proofs={})...", proof_contexts.len(), num_leaf_proofs, ); - let config = CircuitConfig::standard_recursion_zk_config(); - let aggregation_config = AggregationConfig::new(num_leaf_proofs); - - if proof_contexts.len() > aggregation_config.num_leaf_proofs { + if proof_contexts.len() > num_leaf_proofs { return Err(format!( "Too many proofs: {} provided, max {}", proof_contexts.len(), - aggregation_config.num_leaf_proofs, + num_leaf_proofs, )); } - let mut aggregator = WormholeProofAggregator::from_circuit_config(config, aggregation_config); + let bins_dir = std::path::Path::new("generated-bins"); + + let mut aggregator = Layer0Aggregator::new(bins_dir) + .map_err(|e| format!("Failed to create aggregator: {}", e))?; for (idx, ctx) in proof_contexts.into_iter().enumerate() { println!(" Adding proof {} to aggregator...", idx + 1); @@ -543,23 +542,21 @@ fn aggregate_proofs( } println!(" Running aggregation (this may take ~60s)..."); - let aggregated_result = + let aggregated_proof = aggregator.aggregate().map_err(|e| format!("Aggregation failed: {}", e))?; use qp_wormhole_circuit::inputs::ParseAggregatedPublicInputs; - let public_inputs = AggregatedPublicCircuitInputs::try_from_felts( - aggregated_result.proof.public_inputs.as_slice(), - ) - .map_err(|e| format!("Failed to parse aggregated public inputs: {}", e))?; + let public_inputs = + AggregatedPublicCircuitInputs::try_from_felts(aggregated_proof.public_inputs.as_slice()) + .map_err(|e| format!("Failed to parse aggregated public inputs: {}", e))?; // Verify locally first println!(" Verifying aggregated proof locally..."); - aggregated_result - .circuit_data - .verify(aggregated_result.proof.clone()) + aggregator + .verify(aggregated_proof.clone()) .map_err(|e| format!("Local verification failed: {}", e))?; - let proof_bytes = aggregated_result.proof.to_bytes(); + let proof_bytes = aggregated_proof.to_bytes(); println!( " Aggregation complete! Size: {} bytes, {} nullifiers", proof_bytes.len(), @@ -573,7 +570,7 @@ fn aggregate_proofs( async fn submit_aggregated_proof_for_verification( quantus_client: &QuantusClient, proof_bytes: Vec, -) -> Result<(), String> { +) -> Result { println!(" Submitting aggregated proof for on-chain verification..."); let verify_tx = quantus_node::api::tx().wormhole().verify_aggregated_proof(proof_bytes); @@ -594,7 +591,7 @@ async fn submit_aggregated_proof_for_verification( TxStatus::InBestBlock(tx_in_block) => { let block_hash = tx_in_block.block_hash(); println!(" ✅ Aggregated proof verified on-chain! Block: {:?}", block_hash); - return Ok(()); + return Ok(block_hash); }, TxStatus::InFinalizedBlock(tx_in_block) => { let block_hash = tx_in_block.block_hash(); @@ -602,7 +599,7 @@ async fn submit_aggregated_proof_for_verification( " ✅ Aggregated proof verified on-chain (finalized)! Block: {:?}", block_hash ); - return Ok(()); + return Ok(block_hash); }, TxStatus::Error { message } | TxStatus::Invalid { message } => { return Err(format!("Transaction failed: {}", message)); @@ -614,12 +611,20 @@ async fn submit_aggregated_proof_for_verification( Err("Transaction stream ended unexpectedly".to_string()) } +const POW_ENGINE_ID: [u8; 4] = *b"pow_"; + fn author_from_header_digest( header_digest: &subxt::config::substrate::Digest, ) -> Option { header_digest.logs.iter().find_map(|item| match item { - subxt::config::substrate::DigestItem::PreRuntime(_engine_id, data) => - SubxtAccountId::decode(&mut &data[..]).ok(), + subxt::config::substrate::DigestItem::PreRuntime(engine_id, data) + if *engine_id == POW_ENGINE_ID && data.len() == 32 => + { + let preimage: [u8; 32] = data.as_slice().try_into().ok()?; + let author_bytes = + qp_poseidon::PoseidonHasher::hash_variable_length(preimage.to_felts()); + SubxtAccountId::decode(&mut &author_bytes[..]).ok() + }, _ => None, }) } @@ -703,16 +708,11 @@ async fn test_single_proof_on_chain_verification() { // Submit for on-chain verification println!("4. Verifying proof on-chain..."); - // Submit extrinsic - submit_single_proof_for_verification(&quantus_client, proof_context.proof_bytes.clone()) - .await - .expect("On-chain verification failed"); - - // Find the block that executed verification - let verify_block_hash = quantus_client - .get_latest_block() - .await - .expect("Failed to get latest block hash"); + // Submit extrinsic and find the block that executed verification + let verify_block_hash = + submit_single_proof_for_verification(&quantus_client, proof_context.proof_bytes.clone()) + .await + .expect("On-chain verification failed"); // Extract author from block digest let verify_block = quantus_client.client().blocks().at(verify_block_hash).await.unwrap(); @@ -853,19 +853,13 @@ async fn test_aggregated_proof_on_chain_verification() { println!("5. Verifying aggregated proof on-chain..."); // Submit aggregated verification - submit_aggregated_proof_for_verification( + let verify_block_hash = submit_aggregated_proof_for_verification( &quantus_client, aggregated_context.proof_bytes.clone(), ) .await .expect("On-chain aggregated verification failed"); - // Identify verification block - let verify_block_hash = quantus_client - .get_latest_block() - .await - .expect("Failed to get latest block hash"); - // Extract author let verify_block = quantus_client.client().blocks().at(verify_block_hash).await.unwrap(); let header = verify_block.header();