diff --git a/Cargo.lock b/Cargo.lock index afce9a5..7cf50a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3907,6 +3907,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78172860cd960773c72e97fba07ead9e5a1c13086c5746283744933e2e4a577b" +[[package]] +name = "qp-poseidon-core" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd767cf8aaecf9369fab79255ff9e0ea439cdfef941976edbfb8ba70435b63" + [[package]] name = "qp-rusty-crystals-dilithium" version = "2.5.0" @@ -3939,8 +3945,6 @@ dependencies = [ [[package]] name = "qp-wormhole-aggregator" version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9cd1dd3efde4e405a1d7c44852cee799fce30d82f0741c0b5c2b3ec024e3ae9" dependencies = [ "anyhow", "hex", @@ -3958,8 +3962,6 @@ dependencies = [ [[package]] name = "qp-wormhole-circuit" version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c616ca2d392b3701fdd97982c3b894e272245b751e4e669fac7bf6aa4cd977" dependencies = [ "anyhow", "hex", @@ -3971,8 +3973,6 @@ dependencies = [ [[package]] name = "qp-wormhole-circuit-builder" version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dedb083e6b1567354837930881e7bead77c84973422707c0604ed1234f12b1fd" dependencies = [ "anyhow", "clap", @@ -3985,8 +3985,6 @@ dependencies = [ [[package]] name = "qp-wormhole-inputs" version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6416e8b025382d5bc00d1418ca28a25c15f1fdfe351f793e83f6aae16b8ad2bf" dependencies = [ "anyhow", ] @@ -3994,8 +3992,6 @@ dependencies = [ [[package]] name = "qp-wormhole-prover" version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167337f6e3d65e6b67cace22f72537a86ee061b9880dc7fe6e5b617d33ad279b" dependencies = [ "anyhow", "qp-plonky2", @@ -4006,8 +4002,6 @@ dependencies = [ [[package]] name = "qp-wormhole-verifier" version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce89292e75e323ad792d169c5c725a2b534b4b6ca6108400d1649b2c972681ff" dependencies = [ "anyhow", "qp-plonky2-verifier", @@ -4017,13 +4011,11 @@ dependencies = [ [[package]] name = "qp-zk-circuits-common" version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39bb72c67529758341ab094f7b6576379c5d704992df254d09282e9a99eec856" dependencies = [ "anyhow", "qp-plonky2", "qp-poseidon-constants", - "qp-poseidon-core 2.1.0", + "qp-poseidon-core 3.0.0", "qp-wormhole-inputs", "rand 0.8.5", "serde", @@ -4048,7 +4040,7 @@ dependencies = [ "parity-scale-codec", "qp-dilithium-crypto", "qp-plonky2", - "qp-poseidon-core 2.1.0", + "qp-poseidon-core 3.0.0", "qp-rusty-crystals-dilithium", "qp-rusty-crystals-hdwallet", "qp-wormhole-aggregator", diff --git a/Cargo.toml b/Cargo.toml index 5acbbc1..e273ef0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,11 +105,11 @@ qp-zk-circuits-common = { version = "3.0.0", default-features = false, features [build-dependencies] hex = "0.4" -qp-poseidon-core = "2.1.0" +qp-poseidon-core = "3.0.0" qp-wormhole-circuit-builder = { version = "3.0.0" } [dev-dependencies] -qp-poseidon-core = "2.1.0" +qp-poseidon-core = "3.0.0" serial_test = "3.1" tempfile = "3.8.1" @@ -121,3 +121,13 @@ opt-level = 3 [profile.release.build-override] opt-level = 3 + +# TEMPORARY: local circuits repo until the private/public batch rename ships as 3.1.0. +[patch.crates-io] +qp-wormhole-aggregator = { path = "../qp-zk-circuits/wormhole/aggregator" } +qp-wormhole-circuit = { path = "../qp-zk-circuits/wormhole/circuit" } +qp-wormhole-circuit-builder = { path = "../qp-zk-circuits/wormhole/circuit-builder" } +qp-wormhole-inputs = { path = "../qp-zk-circuits/wormhole/inputs" } +qp-wormhole-prover = { path = "../qp-zk-circuits/wormhole/prover" } +qp-wormhole-verifier = { path = "../qp-zk-circuits/wormhole/verifier" } +qp-zk-circuits-common = { path = "../qp-zk-circuits/common" } diff --git a/README.md b/README.md index 54b9fc4..c6a65f9 100644 --- a/README.md +++ b/README.md @@ -350,8 +350,8 @@ quantus developer build-circuits \ **What it does (3 steps):** 1. Clears stale artifacts from the CLI's `generated-bins/` directory. -2. Calls the `qp-wormhole-circuit-builder` library directly to regenerate binary files in `generated-bins/` (`verifier.bin`, `common.bin`, `aggregated_verifier.bin`, `aggregated_common.bin`, `config.json`, plus prover binaries unless `--skip-prover` is set). -3. Copies chain-relevant binaries (`aggregated_common.bin`, `aggregated_verifier.bin`, `config.json`) to `chain/pallets/wormhole/` and touches the pallet source. +2. Calls the `qp-wormhole-circuit-builder` library directly to regenerate binary files in `generated-bins/` (`verifier.bin`, `common.bin`, `private_batch_verifier.bin`, `private_batch_common.bin`, `config.json`, plus prover binaries unless `--skip-prover` is set). +3. Copies chain-relevant binaries (`private_batch_common.bin`, `private_batch_verifier.bin`, `config.json`) to `chain/pallets/wormhole/` and touches the pallet source. After running, rebuild the chain (`cargo build --release` in the chain directory) so `include_bytes!()` picks up the new binaries. diff --git a/build.rs b/build.rs index 2e5e101..f30fc5f 100644 --- a/build.rs +++ b/build.rs @@ -56,6 +56,10 @@ fn main() { .map(|v| v.parse().expect("QP_NUM_LEAF_PROOFS must be a valid usize")) .unwrap_or(DEFAULT_NUM_LEAF_PROOFS); + let num_private_batch_proofs: usize = env::var("QP_NUM_PRIVATE_BATCH_PROOFS") + .map(|v| v.parse().expect("QP_NUM_PRIVATE_BATCH_PROOFS must be a valid usize")) + .unwrap_or(DEFAULT_NUM_PRIVATE_BATCH_PROOFS); + // Re-run when QP_NUM_LEAF_PROOFS env var changes. Note: emitting any `rerun-if-*` // directive opts out of Cargo's default "re-run when any package file changes" // behavior. However, the important cases still work: @@ -66,10 +70,11 @@ fn main() { // For installed binaries, runtime detection in bins.rs `is_ready()` handles leaf // count mismatches by regenerating on first use. println!("cargo:rerun-if-env-changed=QP_NUM_LEAF_PROOFS"); + println!("cargo:rerun-if-env-changed=QP_NUM_PRIVATE_BATCH_PROOFS"); println!( - "cargo:warning=[quantus-cli] Generating ZK circuit binaries (num_leaf_proofs={})...", - num_leaf_proofs + "cargo:warning=[quantus-cli] Generating ZK circuit binaries (num_leaf_proofs={}, num_private_batch_proofs={})...", + num_leaf_proofs, num_private_batch_proofs ); let start = Instant::now(); @@ -81,7 +86,7 @@ fn main() { &build_output_dir, true, num_leaf_proofs, - None, + Some(num_private_batch_proofs), ) .expect("Failed to generate circuit binaries"); @@ -100,9 +105,12 @@ fn main() { print_bin_hash(&build_output_dir, "verifier.bin"); print_bin_hash(&build_output_dir, "prover.bin"); print_bin_hash(&build_output_dir, "dummy_proof.bin"); - print_bin_hash(&build_output_dir, "aggregated_common.bin"); - print_bin_hash(&build_output_dir, "aggregated_verifier.bin"); - print_bin_hash(&build_output_dir, "aggregated_prover.bin"); + print_bin_hash(&build_output_dir, "private_batch_common.bin"); + print_bin_hash(&build_output_dir, "private_batch_verifier.bin"); + print_bin_hash(&build_output_dir, "private_batch_prover.bin"); + print_bin_hash(&build_output_dir, "public_batch_common.bin"); + print_bin_hash(&build_output_dir, "public_batch_verifier.bin"); + print_bin_hash(&build_output_dir, "public_batch_prover.bin"); // Copy bins to project root for runtime access, but only during local source // builds — never during `cargo publish` verification (manifest_dir is inside diff --git a/examples/wormhole_sdk_e2e.rs b/examples/wormhole_sdk_e2e.rs index 76afc2f..3d36e7f 100644 --- a/examples/wormhole_sdk_e2e.rs +++ b/examples/wormhole_sdk_e2e.rs @@ -13,7 +13,7 @@ //! 6. compute_merkle_positions -> (siblings, positions) //! 7. wormhole_lib::generate_proof -> leaf proof bytes //! 8. aggregate_proofs([leaf]) -> agg.hex -//! 9. verify_aggregated_and_get_events -> minted NativeTransferred +//! 9. verify_private_batch_and_get_events -> minted NativeTransferred //! ``` //! //! All helpers are the same ones consumed by `stress-test`. The example is @@ -54,7 +54,7 @@ use quantus_cli::{ cli::{address_format::bytes_to_quantus_ss58, common::ExecutionMode, send::parse_amount}, compute_merkle_positions, compute_wormhole_address, decode_full_leaf_data, error::{QuantusError, Result}, - get_zk_merkle_proof, parse_transfer_events, transfer, verify_aggregated_and_get_events, + get_zk_merkle_proof, parse_transfer_events, transfer, verify_private_batch_and_get_events, wallet::WalletManager, wormhole_lib::{ self, ProofGenerationInput, NATIVE_ASSET_ID, SCALE_DOWN_FACTOR, VOLUME_FEE_BPS, @@ -307,10 +307,10 @@ async fn main() -> Result<()> { // 9. verify + submit ------------------------------------------------------- println!(); - println!("[8/9] verify_aggregated_and_get_events (off-chain verify + on-chain submit)"); + println!("[8/9] verify_private_batch_and_get_events (off-chain verify + on-chain submit)"); let verify_start = std::time::Instant::now(); let (mint_block, mint_tx, transfers) = - verify_aggregated_and_get_events(agg_path.to_str().unwrap(), &client).await?; + verify_private_batch_and_get_events(agg_path.to_str().unwrap(), &client).await?; println!(" verified+included in {:.2}s", verify_start.elapsed().as_secs_f64()); println!(" mint block : {:?}", mint_block); println!(" mint tx : {:?}", mint_tx); @@ -318,7 +318,7 @@ async fn main() -> Result<()> { println!(); println!("[9/9] minted NativeTransferred events:"); if transfers.is_empty() { - println!(" (none — verify_aggregated_proof did not emit any events?)"); + println!(" (none — verify_private_batch did not emit any events?)"); } for (i, ev) in transfers.iter().enumerate() { let to_ss58 = bytes_to_quantus_ss58(&ev.to.0); diff --git a/examples/wormhole_sdk_usage.rs b/examples/wormhole_sdk_usage.rs index d96eef2..b5a6a22 100644 --- a/examples/wormhole_sdk_usage.rs +++ b/examples/wormhole_sdk_usage.rs @@ -11,8 +11,8 @@ //! submitting anything: `at_best_block`, scans recent blocks for a real `NativeTransferred` //! event, runs `parse_transfer_events`, fetches the ZK Merkle proof via `get_zk_merkle_proof`, //! computes positions with `compute_merkle_positions` and decodes the leaf bytes with -//! `decode_full_leaf_data`. The submission path (`submit_unsigned_verify_aggregated_proof`, -//! `verify_aggregated_and_get_events`) is shown only as pseudocode — it requires a funded +//! `decode_full_leaf_data`. The submission path (`submit_unsigned_verify_private_batch`, +//! `verify_private_batch_and_get_events`) is shown only as pseudocode — it requires a funded //! deposit + ZK proof generation, which [`wormhole_sdk_e2e.rs`](./wormhole_sdk_e2e.rs) //! demonstrates end-to-end. If the node is unreachable the example exits cleanly with hints; CI //! without a node still builds and runs it green. @@ -34,8 +34,8 @@ //! aggregate_proofs(leaf_files, "agg.hex") → aggregated proof file //! │ //! ▼ -//! verify_aggregated_and_get_events("agg.hex", &client) -//! │ (locally verifies + submits unsigned `verify_aggregated_proof` +//! verify_private_batch_and_get_events("agg.hex", &client) +//! │ (locally verifies + submits unsigned `verify_private_batch` //! │ + waits for inclusion + collects NativeTransferred events) //! ▼ //! Vec with the minted amounts at the exit accounts @@ -219,7 +219,7 @@ async fn online_demo() -> Result<()> { println!( " This is normal on a fresh dev chain. Run examples/wormhole_sdk_e2e.rs first" ); - println!(" (it submits a deposit + verify_aggregated_proof) to populate the chain."); + println!(" (it submits a deposit + verify_private_batch) to populate the chain."); }, } @@ -332,13 +332,13 @@ fn print_online_recipe() { println!(" let bytes = std::fs::read(\"agg.hex\")?;"); println!(" let bytes = hex::decode(bytes.trim_ascii())?;"); println!(" let (included_at, block_hash, tx_hash) ="); - println!(" submit_unsigned_verify_aggregated_proof(&client, bytes).await?;"); + println!(" submit_unsigned_verify_private_batch(&client, bytes).await?;"); println!(" println!(\"included @ {{}} block={{:?}} tx={{:?}}\","); println!(" included_at, block_hash, tx_hash);"); println!(); println!(" // Or, with local verify + event collection:"); println!(" let (block_hash, tx_hash, transfers) ="); - println!(" verify_aggregated_and_get_events(\"agg.hex\", &client).await?;"); + println!(" verify_private_batch_and_get_events(\"agg.hex\", &client).await?;"); println!(" for ev in transfers {{"); println!(" println!(\" -> {{}} planck to {{}}\", ev.amount, ev.to.to_ss58check());"); println!(" }}"); diff --git a/src/bins.rs b/src/bins.rs index 42371c9..b43f177 100644 --- a/src/bins.rs +++ b/src/bins.rs @@ -28,10 +28,14 @@ const REQUIRED_FILES: &[&str] = &[ "prover.bin", "verifier.bin", "common.bin", - "aggregated_prover.bin", - "aggregated_verifier.bin", - "aggregated_common.bin", + "private_batch_prover.bin", + "private_batch_verifier.bin", + "private_batch_common.bin", + "public_batch_prover.bin", + "public_batch_verifier.bin", + "public_batch_common.bin", "dummy_proof.bin", + "dummy_private_batch_proof.bin", "config.json", ]; @@ -72,7 +76,8 @@ pub fn ensure_bins_dir() -> Result { } let num_leaf_proofs = env_num_leaf_proofs(); - generate(&dir, num_leaf_proofs)?; + let num_private_batch_proofs = env_num_private_batch_proofs(); + generate(&dir, num_leaf_proofs, num_private_batch_proofs)?; Ok(dir) } @@ -88,17 +93,21 @@ fn is_ready(dir: &Path) -> bool { if !version_ok { return false; } - // Check num_leaf_proofs in config.json matches current setting + // Check circuit sizing in config.json matches current settings let config_path = dir.join("config.json"); match std::fs::read_to_string(&config_path) { Ok(content) => { - // Parse just the num_leaf_proofs field to avoid pulling in full config dependency + // Parse just the sizing fields to avoid pulling in full config dependency #[derive(serde::Deserialize)] struct ConfigCheck { num_leaf_proofs: usize, + #[serde(default, alias = "num_layer0_proofs")] + num_private_batch_proofs: Option, } match serde_json::from_str::(&content) { - Ok(config) => config.num_leaf_proofs == env_num_leaf_proofs(), + Ok(config) => + config.num_leaf_proofs == env_num_leaf_proofs() && + config.num_private_batch_proofs == Some(env_num_private_batch_proofs()), Err(_) => false, } }, @@ -113,7 +122,14 @@ fn env_num_leaf_proofs() -> usize { .unwrap_or(DEFAULT_NUM_LEAF_PROOFS) } -fn generate(dir: &Path, num_leaf_proofs: usize) -> Result<()> { +fn env_num_private_batch_proofs() -> usize { + std::env::var("QP_NUM_PRIVATE_BATCH_PROOFS") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(DEFAULT_NUM_PRIVATE_BATCH_PROOFS) +} + +fn generate(dir: &Path, num_leaf_proofs: usize, num_private_batch_proofs: usize) -> Result<()> { std::fs::create_dir_all(dir).map_err(|e| { QuantusError::Generic(format!("Failed to create bins directory {}: {}", dir.display(), e)) })?; @@ -122,12 +138,16 @@ fn generate(dir: &Path, num_leaf_proofs: usize) -> Result<()> { log_print!("🛠️ Generating ZK circuit binaries (first-time setup, ~30s)..."); log_print!(" Target: {}", dir.display()); log_print!(" num_leaf_proofs: {}", num_leaf_proofs); + log_print!(" num_private_batch_proofs: {}", num_private_batch_proofs); let start = std::time::Instant::now(); - qp_wormhole_circuit_builder::generate_all_circuit_binaries(dir, true, num_leaf_proofs, None) - .map_err(|e| { - QuantusError::Generic(format!("Failed to generate circuit binaries: {}", e)) - })?; + qp_wormhole_circuit_builder::generate_all_circuit_binaries( + dir, + true, + num_leaf_proofs, + Some(num_private_batch_proofs), + ) + .map_err(|e| QuantusError::Generic(format!("Failed to generate circuit binaries: {}", e)))?; std::fs::write(dir.join(VERSION_MARKER), env!("CARGO_PKG_VERSION")) .map_err(|e| QuantusError::Generic(format!("Failed to write version marker: {}", e)))?; diff --git a/src/bins_consts.rs b/src/bins_consts.rs index b0faf17..76fc4bf 100644 --- a/src/bins_consts.rs +++ b/src/bins_consts.rs @@ -13,3 +13,9 @@ const VERSION_MARKER: &str = ".quantus-cli-version"; /// - bins.rs: runtime lazy circuit generation /// - collect_rewards_lib.rs: batching proofs for aggregation pub const DEFAULT_NUM_LEAF_PROOFS: usize = 7; + +/// Number of private-batch proofs aggregated into a single public batch. +/// +/// Must match the chain's pallet-wormhole build default (QP_NUM_PRIVATE_BATCH_PROOFS) +/// or on-chain verification of public batches will fail. +pub const DEFAULT_NUM_PRIVATE_BATCH_PROOFS: usize = 4; diff --git a/src/chain/quantus_subxt.rs b/src/chain/quantus_subxt.rs index b07e4cc..30d512e 100644 --- a/src/chain/quantus_subxt.rs +++ b/src/chain/quantus_subxt.rs @@ -1565,10 +1565,9 @@ pub mod api { "query_call_info", types::QueryCallInfo { call, len }, [ - 32u8, 234u8, 47u8, 160u8, 157u8, 142u8, 236u8, 40u8, 222u8, 22u8, 75u8, - 92u8, 244u8, 90u8, 235u8, 228u8, 137u8, 249u8, 207u8, 89u8, 87u8, - 244u8, 41u8, 20u8, 102u8, 145u8, 48u8, 54u8, 164u8, 114u8, 122u8, - 182u8, + 90u8, 50u8, 211u8, 137u8, 17u8, 184u8, 134u8, 190u8, 29u8, 156u8, 36u8, + 214u8, 214u8, 65u8, 28u8, 181u8, 114u8, 218u8, 8u8, 229u8, 48u8, 10u8, + 183u8, 105u8, 113u8, 49u8, 40u8, 174u8, 69u8, 246u8, 158u8, 197u8, ], ) } @@ -1586,10 +1585,9 @@ pub mod api { "query_call_fee_details", types::QueryCallFeeDetails { call, len }, [ - 135u8, 197u8, 88u8, 132u8, 222u8, 66u8, 219u8, 154u8, 162u8, 155u8, - 161u8, 58u8, 176u8, 115u8, 33u8, 214u8, 171u8, 221u8, 230u8, 21u8, - 60u8, 15u8, 36u8, 117u8, 182u8, 70u8, 181u8, 108u8, 125u8, 72u8, 252u8, - 40u8, + 34u8, 56u8, 164u8, 48u8, 105u8, 57u8, 32u8, 57u8, 215u8, 116u8, 66u8, + 5u8, 117u8, 168u8, 51u8, 154u8, 198u8, 60u8, 29u8, 154u8, 73u8, 240u8, + 9u8, 177u8, 134u8, 205u8, 14u8, 43u8, 39u8, 95u8, 12u8, 151u8, ], ) } @@ -1980,6 +1978,9 @@ pub mod api { pub fn scheduler(&self) -> scheduler::storage::StorageApi { scheduler::storage::StorageApi } + pub fn utility(&self) -> utility::storage::StorageApi { + utility::storage::StorageApi + } pub fn reversible_transfers(&self) -> reversible_transfers::storage::StorageApi { reversible_transfers::storage::StorageApi } @@ -2068,9 +2069,9 @@ pub mod api { .hash(); runtime_metadata_hash == [ - 0u8, 140u8, 5u8, 40u8, 237u8, 46u8, 42u8, 0u8, 228u8, 200u8, 1u8, 226u8, 132u8, - 185u8, 140u8, 253u8, 54u8, 127u8, 56u8, 200u8, 164u8, 237u8, 198u8, 151u8, 133u8, - 33u8, 239u8, 56u8, 190u8, 69u8, 228u8, 26u8, + 177u8, 167u8, 26u8, 148u8, 155u8, 122u8, 131u8, 26u8, 195u8, 30u8, 42u8, 141u8, + 122u8, 36u8, 59u8, 111u8, 24u8, 134u8, 210u8, 109u8, 0u8, 208u8, 83u8, 70u8, 46u8, + 87u8, 68u8, 250u8, 165u8, 29u8, 29u8, 127u8, ] } pub mod system { @@ -3198,9 +3199,10 @@ pub mod api { "Events", (), [ - 12u8, 200u8, 85u8, 114u8, 205u8, 35u8, 13u8, 101u8, 195u8, 51u8, 123u8, - 145u8, 74u8, 61u8, 217u8, 71u8, 184u8, 61u8, 132u8, 83u8, 214u8, 117u8, - 222u8, 18u8, 13u8, 229u8, 195u8, 147u8, 223u8, 19u8, 66u8, 105u8, + 113u8, 0u8, 253u8, 103u8, 151u8, 44u8, 145u8, 151u8, 127u8, 138u8, + 40u8, 186u8, 228u8, 160u8, 222u8, 205u8, 105u8, 209u8, 1u8, 48u8, + 122u8, 152u8, 43u8, 253u8, 29u8, 73u8, 224u8, 181u8, 90u8, 221u8, 5u8, + 209u8, ], ) } @@ -7317,9 +7319,10 @@ pub mod api { "batch", types::Batch { calls }, [ - 91u8, 52u8, 14u8, 228u8, 168u8, 75u8, 218u8, 19u8, 121u8, 112u8, 126u8, - 236u8, 87u8, 77u8, 194u8, 140u8, 196u8, 15u8, 84u8, 117u8, 80u8, 101u8, - 217u8, 62u8, 28u8, 83u8, 226u8, 233u8, 176u8, 2u8, 158u8, 79u8, + 135u8, 158u8, 49u8, 34u8, 225u8, 221u8, 148u8, 92u8, 90u8, 100u8, + 166u8, 203u8, 132u8, 97u8, 40u8, 94u8, 123u8, 153u8, 237u8, 174u8, + 111u8, 204u8, 191u8, 249u8, 58u8, 94u8, 185u8, 231u8, 204u8, 56u8, + 141u8, 234u8, ], ) } @@ -7349,10 +7352,10 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 78u8, 239u8, 51u8, 124u8, 113u8, 201u8, 252u8, 171u8, 182u8, 113u8, - 155u8, 193u8, 125u8, 113u8, 241u8, 199u8, 215u8, 237u8, 55u8, 121u8, - 247u8, 255u8, 41u8, 35u8, 206u8, 143u8, 157u8, 90u8, 203u8, 106u8, - 252u8, 91u8, + 250u8, 88u8, 125u8, 217u8, 141u8, 180u8, 83u8, 152u8, 176u8, 72u8, + 192u8, 96u8, 124u8, 89u8, 66u8, 74u8, 237u8, 221u8, 238u8, 59u8, 40u8, + 119u8, 147u8, 155u8, 130u8, 213u8, 199u8, 186u8, 246u8, 227u8, 27u8, + 23u8, ], ) } @@ -7378,9 +7381,10 @@ pub mod api { "batch_all", types::BatchAll { calls }, [ - 9u8, 111u8, 191u8, 88u8, 100u8, 224u8, 185u8, 197u8, 208u8, 26u8, - 198u8, 42u8, 215u8, 246u8, 54u8, 120u8, 3u8, 148u8, 182u8, 44u8, 233u8, - 60u8, 26u8, 128u8, 101u8, 87u8, 141u8, 143u8, 9u8, 197u8, 184u8, 64u8, + 41u8, 34u8, 6u8, 168u8, 67u8, 1u8, 242u8, 173u8, 200u8, 209u8, 197u8, + 225u8, 4u8, 115u8, 216u8, 144u8, 128u8, 59u8, 200u8, 111u8, 188u8, + 123u8, 242u8, 181u8, 189u8, 132u8, 82u8, 254u8, 216u8, 14u8, 144u8, + 81u8, ], ) } @@ -7403,10 +7407,9 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 252u8, 234u8, 74u8, 189u8, 195u8, 91u8, 133u8, 238u8, 206u8, 171u8, - 160u8, 141u8, 58u8, 250u8, 221u8, 113u8, 192u8, 201u8, 95u8, 103u8, - 209u8, 0u8, 110u8, 235u8, 38u8, 225u8, 79u8, 73u8, 19u8, 75u8, 138u8, - 146u8, + 229u8, 3u8, 92u8, 197u8, 150u8, 217u8, 187u8, 58u8, 48u8, 34u8, 206u8, + 72u8, 84u8, 115u8, 210u8, 133u8, 173u8, 206u8, 106u8, 64u8, 3u8, 27u8, + 65u8, 164u8, 175u8, 238u8, 237u8, 137u8, 246u8, 15u8, 79u8, 208u8, ], ) } @@ -7432,9 +7435,10 @@ pub mod api { "force_batch", types::ForceBatch { calls }, [ - 130u8, 189u8, 219u8, 64u8, 240u8, 89u8, 49u8, 86u8, 163u8, 42u8, 52u8, - 8u8, 152u8, 154u8, 147u8, 236u8, 209u8, 30u8, 243u8, 194u8, 123u8, - 97u8, 97u8, 239u8, 97u8, 57u8, 104u8, 191u8, 235u8, 221u8, 1u8, 227u8, + 100u8, 187u8, 166u8, 123u8, 181u8, 217u8, 198u8, 93u8, 192u8, 43u8, + 120u8, 250u8, 106u8, 187u8, 184u8, 212u8, 24u8, 105u8, 102u8, 29u8, + 145u8, 220u8, 193u8, 41u8, 113u8, 92u8, 208u8, 233u8, 80u8, 3u8, 72u8, + 142u8, ], ) } @@ -7457,9 +7461,10 @@ pub mod api { weight, }, [ - 22u8, 169u8, 85u8, 171u8, 100u8, 38u8, 252u8, 11u8, 198u8, 55u8, 150u8, - 190u8, 59u8, 5u8, 143u8, 123u8, 107u8, 23u8, 202u8, 0u8, 237u8, 255u8, - 77u8, 19u8, 113u8, 77u8, 133u8, 52u8, 176u8, 110u8, 214u8, 221u8, + 248u8, 233u8, 49u8, 17u8, 159u8, 160u8, 133u8, 104u8, 56u8, 56u8, 78u8, + 243u8, 14u8, 78u8, 193u8, 76u8, 133u8, 40u8, 212u8, 235u8, 214u8, + 120u8, 131u8, 18u8, 206u8, 27u8, 128u8, 210u8, 23u8, 109u8, 209u8, + 52u8, ], ) } @@ -7499,9 +7504,10 @@ pub mod api { fallback: ::subxt::ext::subxt_core::alloc::boxed::Box::new(fallback), }, [ - 210u8, 174u8, 69u8, 89u8, 81u8, 251u8, 211u8, 77u8, 44u8, 88u8, 137u8, - 153u8, 221u8, 116u8, 10u8, 183u8, 158u8, 145u8, 229u8, 108u8, 168u8, - 123u8, 100u8, 162u8, 121u8, 201u8, 158u8, 103u8, 96u8, 96u8, 89u8, 4u8, + 246u8, 98u8, 179u8, 32u8, 49u8, 82u8, 125u8, 199u8, 214u8, 143u8, + 112u8, 129u8, 243u8, 115u8, 206u8, 121u8, 121u8, 138u8, 185u8, 205u8, + 207u8, 224u8, 58u8, 32u8, 231u8, 51u8, 29u8, 0u8, 16u8, 237u8, 132u8, + 91u8, ], ) } @@ -7524,10 +7530,9 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 68u8, 190u8, 238u8, 69u8, 88u8, 148u8, 111u8, 242u8, 200u8, 228u8, - 47u8, 232u8, 101u8, 173u8, 104u8, 140u8, 30u8, 113u8, 236u8, 193u8, - 48u8, 47u8, 116u8, 169u8, 97u8, 77u8, 17u8, 82u8, 37u8, 190u8, 158u8, - 66u8, + 13u8, 36u8, 191u8, 209u8, 109u8, 31u8, 206u8, 217u8, 67u8, 167u8, + 250u8, 116u8, 29u8, 37u8, 151u8, 20u8, 172u8, 101u8, 79u8, 112u8, 15u8, + 154u8, 96u8, 16u8, 249u8, 103u8, 208u8, 101u8, 119u8, 2u8, 44u8, 109u8, ], ) } @@ -7670,6 +7675,67 @@ pub mod api { const EVENT: &'static str = "IfElseFallbackCalled"; } } + pub mod storage { + use super::runtime_types; + pub mod types { + use super::runtime_types; + pub mod known_derivatives { + use super::runtime_types; + pub type KnownDerivatives = (); + pub type Param0 = ::subxt::ext::subxt_core::utils::AccountId32; + } + } + pub struct StorageApi; + impl StorageApi { + #[doc = " Derivative pseudonym accounts that have been used with [`Pallet::as_derivative`] at least"] + #[doc = " once and have therefore been revealed to the wormhole soundness counter."] + pub fn known_derivatives_iter( + &self, + ) -> ::subxt::ext::subxt_core::storage::address::StaticAddress< + (), + types::known_derivatives::KnownDerivatives, + (), + (), + ::subxt::ext::subxt_core::utils::Yes, + > { + ::subxt::ext::subxt_core::storage::address::StaticAddress::new_static( + "Utility", + "KnownDerivatives", + (), + [ + 7u8, 191u8, 89u8, 10u8, 69u8, 99u8, 173u8, 186u8, 52u8, 139u8, 104u8, + 183u8, 71u8, 107u8, 206u8, 3u8, 88u8, 160u8, 238u8, 53u8, 84u8, 31u8, + 171u8, 151u8, 88u8, 158u8, 134u8, 111u8, 200u8, 72u8, 151u8, 158u8, + ], + ) + } + #[doc = " Derivative pseudonym accounts that have been used with [`Pallet::as_derivative`] at least"] + #[doc = " once and have therefore been revealed to the wormhole soundness counter."] + pub fn known_derivatives( + &self, + _0: types::known_derivatives::Param0, + ) -> ::subxt::ext::subxt_core::storage::address::StaticAddress< + ::subxt::ext::subxt_core::storage::address::StaticStorageKey< + types::known_derivatives::Param0, + >, + types::known_derivatives::KnownDerivatives, + ::subxt::ext::subxt_core::utils::Yes, + (), + (), + > { + ::subxt::ext::subxt_core::storage::address::StaticAddress::new_static( + "Utility", + "KnownDerivatives", + ::subxt::ext::subxt_core::storage::address::StaticStorageKey::new(_0), + [ + 7u8, 191u8, 89u8, 10u8, 69u8, 99u8, 173u8, 186u8, 52u8, 139u8, 104u8, + 183u8, 71u8, 107u8, 206u8, 3u8, 88u8, 160u8, 238u8, 53u8, 84u8, 31u8, + 171u8, 151u8, 88u8, 158u8, 134u8, 111u8, 200u8, 72u8, 151u8, 158u8, + ], + ) + } + } + } pub mod constants { use super::runtime_types; pub struct ConstantsApi; @@ -11603,9 +11669,9 @@ pub mod api { call: ::subxt::ext::subxt_core::alloc::boxed::Box::new(call), }, [ - 83u8, 241u8, 200u8, 95u8, 176u8, 73u8, 86u8, 254u8, 102u8, 49u8, 113u8, - 102u8, 55u8, 250u8, 115u8, 98u8, 254u8, 96u8, 27u8, 19u8, 132u8, 79u8, - 207u8, 104u8, 114u8, 167u8, 142u8, 208u8, 170u8, 125u8, 198u8, 136u8, + 79u8, 227u8, 217u8, 50u8, 232u8, 47u8, 229u8, 224u8, 15u8, 184u8, + 179u8, 233u8, 18u8, 71u8, 96u8, 44u8, 175u8, 22u8, 130u8, 36u8, 30u8, + 185u8, 23u8, 164u8, 113u8, 47u8, 169u8, 99u8, 252u8, 61u8, 41u8, 21u8, ], ) } @@ -17559,46 +17625,94 @@ pub mod api { #[encode_as_type( crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" )] - #[doc = "Verify an aggregated wormhole proof and process all transfers in the batch."] + #[doc = "Verify a private-batch wormhole proof and process all exits in the batch."] #[doc = ""] #[doc = "Returns `DispatchResultWithPostInfo` to allow weight correction on early failures."] #[doc = "If validation fails before ZK verification, we return minimal weight."] #[doc = "If ZK verification fails, we return full weight since the work was done."] - pub struct VerifyAggregatedProof { - pub proof_bytes: verify_aggregated_proof::ProofBytes, + pub struct VerifyPrivateBatch { + pub proof_bytes: verify_private_batch::ProofBytes, } - pub mod verify_aggregated_proof { + pub mod verify_private_batch { use super::runtime_types; pub type ProofBytes = ::subxt::ext::subxt_core::alloc::vec::Vec<::core::primitive::u8>; } - impl ::subxt::ext::subxt_core::blocks::StaticExtrinsic for VerifyAggregatedProof { + impl ::subxt::ext::subxt_core::blocks::StaticExtrinsic for VerifyPrivateBatch { const PALLET: &'static str = "Wormhole"; - const CALL: &'static str = "verify_aggregated_proof"; + const CALL: &'static str = "verify_private_batch"; + } + #[derive( + :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, + :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, + Debug, + )] + #[decode_as_type( + crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode" + )] + #[encode_as_type( + crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode" + )] + #[doc = "Verify a public-batch wormhole proof and process all valid exit segments."] + #[doc = ""] + #[doc = "Invalid segments (already-spent nullifiers) are denied individually; dummy-padded"] + #[doc = "segments (all-zero nullifiers) are skipped silently. A portion of the burn bucket"] + #[doc = "is minted to the proof's `aggregator_address`."] + pub struct VerifyPublicBatch { + pub proof_bytes: verify_public_batch::ProofBytes, + } + pub mod verify_public_batch { + use super::runtime_types; + pub type ProofBytes = + ::subxt::ext::subxt_core::alloc::vec::Vec<::core::primitive::u8>; + } + impl ::subxt::ext::subxt_core::blocks::StaticExtrinsic for VerifyPublicBatch { + const PALLET: &'static str = "Wormhole"; + const CALL: &'static str = "verify_public_batch"; } } pub struct TransactionApi; impl TransactionApi { - #[doc = "Verify an aggregated wormhole proof and process all transfers in the batch."] + #[doc = "Verify a private-batch wormhole proof and process all exits in the batch."] #[doc = ""] #[doc = "Returns `DispatchResultWithPostInfo` to allow weight correction on early failures."] #[doc = "If validation fails before ZK verification, we return minimal weight."] #[doc = "If ZK verification fails, we return full weight since the work was done."] - pub fn verify_aggregated_proof( + pub fn verify_private_batch( &self, - proof_bytes: types::verify_aggregated_proof::ProofBytes, - ) -> ::subxt::ext::subxt_core::tx::payload::StaticPayload< - types::VerifyAggregatedProof, - > { + proof_bytes: types::verify_private_batch::ProofBytes, + ) -> ::subxt::ext::subxt_core::tx::payload::StaticPayload + { ::subxt::ext::subxt_core::tx::payload::StaticPayload::new_static( "Wormhole", - "verify_aggregated_proof", - types::VerifyAggregatedProof { proof_bytes }, + "verify_private_batch", + types::VerifyPrivateBatch { proof_bytes }, [ - 122u8, 42u8, 194u8, 153u8, 22u8, 66u8, 199u8, 173u8, 19u8, 107u8, - 232u8, 147u8, 77u8, 40u8, 115u8, 208u8, 183u8, 77u8, 46u8, 149u8, 56u8, - 225u8, 146u8, 168u8, 49u8, 248u8, 42u8, 195u8, 180u8, 91u8, 75u8, - 102u8, + 138u8, 4u8, 114u8, 57u8, 176u8, 171u8, 151u8, 90u8, 93u8, 19u8, 57u8, + 176u8, 66u8, 92u8, 244u8, 111u8, 52u8, 102u8, 210u8, 239u8, 220u8, + 14u8, 150u8, 134u8, 251u8, 90u8, 34u8, 237u8, 117u8, 125u8, 115u8, + 69u8, + ], + ) + } + #[doc = "Verify a public-batch wormhole proof and process all valid exit segments."] + #[doc = ""] + #[doc = "Invalid segments (already-spent nullifiers) are denied individually; dummy-padded"] + #[doc = "segments (all-zero nullifiers) are skipped silently. A portion of the burn bucket"] + #[doc = "is minted to the proof's `aggregator_address`."] + pub fn verify_public_batch( + &self, + proof_bytes: types::verify_public_batch::ProofBytes, + ) -> ::subxt::ext::subxt_core::tx::payload::StaticPayload + { + ::subxt::ext::subxt_core::tx::payload::StaticPayload::new_static( + "Wormhole", + "verify_public_batch", + types::VerifyPublicBatch { proof_bytes }, + [ + 169u8, 13u8, 181u8, 227u8, 212u8, 168u8, 177u8, 140u8, 2u8, 34u8, 84u8, + 69u8, 178u8, 235u8, 250u8, 24u8, 126u8, 60u8, 52u8, 76u8, 79u8, 7u8, + 110u8, 1u8, 77u8, 209u8, 62u8, 137u8, 212u8, 98u8, 93u8, 4u8, ], ) } @@ -17691,6 +17805,28 @@ pub mod api { const PALLET: &'static str = "Wormhole"; const EVENT: &'static str = "ProofVerified"; } + #[derive( + :: subxt :: ext :: subxt_core :: ext :: scale_decode :: DecodeAsType, + :: subxt :: ext :: subxt_core :: ext :: scale_encode :: EncodeAsType, + Debug, + )] + #[decode_as_type(crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_decode")] + #[encode_as_type(crate_path = ":: subxt :: ext :: subxt_core :: ext :: scale_encode")] + #[doc = "Some segments of an exit bundle were denied (their nullifiers were already"] + #[doc = "used, e.g. because the underlying private batch landed on-chain separately)."] + #[doc = "The remaining segments were processed normally."] + pub struct SegmentsDenied { + pub indices: segments_denied::Indices, + } + pub mod segments_denied { + use super::runtime_types; + pub type Indices = + ::subxt::ext::subxt_core::alloc::vec::Vec<::core::primitive::u32>; + } + impl ::subxt::ext::subxt_core::events::StaticEvent for SegmentsDenied { + const PALLET: &'static str = "Wormhole"; + const EVENT: &'static str = "SegmentsDenied"; + } } pub mod storage { use super::runtime_types; @@ -17970,6 +18106,24 @@ pub mod api { ], ) } + #[doc = " For public-batch proofs, the proportion of the burn bucket redirected to the"] + #[doc = " aggregator instead of being destroyed. The miner's share is unchanged."] + #[doc = " Example: Permill::from_percent(50) means half the burn portion goes to the aggregator."] + pub fn volume_fees_aggregator_rate( + &self, + ) -> ::subxt::ext::subxt_core::constants::address::StaticAddress< + runtime_types::sp_arithmetic::per_things::Permill, + > { + ::subxt::ext::subxt_core::constants::address::StaticAddress::new_static( + "Wormhole", + "VolumeFeesAggregatorRate", + [ + 65u8, 93u8, 120u8, 165u8, 204u8, 81u8, 159u8, 163u8, 93u8, 135u8, + 114u8, 121u8, 147u8, 35u8, 215u8, 213u8, 4u8, 223u8, 83u8, 37u8, 225u8, + 200u8, 189u8, 156u8, 140u8, 36u8, 58u8, 46u8, 42u8, 232u8, 155u8, 0u8, + ], + ) + } } } } @@ -23429,12 +23583,22 @@ pub mod api { #[doc = "Contains a variant per dispatchable extrinsic that this pallet has."] pub enum Call { #[codec(index = 2)] - #[doc = "Verify an aggregated wormhole proof and process all transfers in the batch."] + #[doc = "Verify a private-batch wormhole proof and process all exits in the batch."] #[doc = ""] #[doc = "Returns `DispatchResultWithPostInfo` to allow weight correction on early failures."] #[doc = "If validation fails before ZK verification, we return minimal weight."] #[doc = "If ZK verification fails, we return full weight since the work was done."] - verify_aggregated_proof { + verify_private_batch { + proof_bytes: + ::subxt::ext::subxt_core::alloc::vec::Vec<::core::primitive::u8>, + }, + #[codec(index = 3)] + #[doc = "Verify a public-batch wormhole proof and process all valid exit segments."] + #[doc = ""] + #[doc = "Invalid segments (already-spent nullifiers) are denied individually; dummy-padded"] + #[doc = "segments (all-zero nullifiers) are skipped silently. A portion of the burn bucket"] + #[doc = "is minted to the proof's `aggregator_address`."] + verify_public_batch { proof_bytes: ::subxt::ext::subxt_core::alloc::vec::Vec<::core::primitive::u8>, }, @@ -23455,17 +23619,19 @@ pub mod api { #[codec(index = 0)] InvalidPublicInputs, #[codec(index = 1)] + #[doc = "No segment of the bundle is spendable: every segment contains a nullifier that"] + #[doc = "is already used (or the single segment of a private-batch proof does)."] NullifierAlreadyUsed, #[codec(index = 2)] BlockNotFound, #[codec(index = 3)] - AggregatedVerifierNotAvailable, + VerifierNotAvailable, #[codec(index = 4)] - AggregatedProofDeserializationFailed, + ProofDeserializationFailed, #[codec(index = 5)] - AggregatedVerificationFailed, + ProofVerificationFailed, #[codec(index = 6)] - InvalidAggregatedPublicInputs, + InvalidProofPublicInputs, #[codec(index = 7)] #[doc = "The volume fee rate in the proof doesn't match the configured rate"] InvalidVolumeFeeRate, @@ -23526,6 +23692,13 @@ pub mod api { [::core::primitive::u8; 32usize], >, }, + #[codec(index = 3)] + #[doc = "Some segments of an exit bundle were denied (their nullifiers were already"] + #[doc = "used, e.g. because the underlying private batch landed on-chain separately)."] + #[doc = "The remaining segments were processed normally."] + SegmentsDenied { + indices: ::subxt::ext::subxt_core::alloc::vec::Vec<::core::primitive::u32>, + }, } } } diff --git a/src/cli/exercise/scenarios/wormhole.rs b/src/cli/exercise/scenarios/wormhole.rs index 23ec9db..2fcf482 100644 --- a/src/cli/exercise/scenarios/wormhole.rs +++ b/src/cli/exercise/scenarios/wormhole.rs @@ -22,6 +22,7 @@ async fn multiround(ctx: &mut ExerciseCtx) -> Result { keep_files: false, output_dir: "/tmp/wormhole_exercise".to_string(), dry_run: false, + public: false, }; crate::cli::wormhole::handle_wormhole_command(command, &ctx.node_url).await?; Ok("wormhole multiround (5 rounds, 5 proofs each) completed".to_string()) diff --git a/src/cli/wormhole.rs b/src/cli/wormhole.rs index f1d8422..1b64ac4 100644 --- a/src/cli/wormhole.rs +++ b/src/cli/wormhole.rs @@ -22,8 +22,8 @@ use qp_wormhole_aggregator::{ aggregator::{AggregationBackend, CircuitType}, config::CircuitBinsConfig, }; -use qp_wormhole_circuit::inputs::ParseAggregatedPublicInputs; -use qp_wormhole_inputs::AggregatedPublicCircuitInputs; +use qp_wormhole_circuit::inputs::ParsePrivateBatchPublicInputs; +use qp_wormhole_inputs::PrivateBatchPublicInputs; use qp_zk_circuits_common::{ circuit::{C, D, F}, utils::BytesDigest, @@ -696,6 +696,28 @@ pub enum WormholeCommands { #[arg(short, long, default_value = "aggregated_proof.hex")] proof: String, }, + /// Aggregate private-batch proofs into a public batch (delegated, non-private + /// aggregation). The aggregator address earns a rebate from the burn portion + /// of the volume fee when the proof is verified on-chain. + AggregatePublic { + /// Input private-batch proof files (hex-encoded, from `wormhole aggregate`) + #[arg(short, long, num_args = 1..)] + proofs: Vec, + + /// Aggregator address receiving the fee rebate (hex or SS58) + #[arg(short, long)] + aggregator: String, + + /// Output file for the public-batch proof + #[arg(short, long, default_value = "public_batch_proof.hex")] + output: String, + }, + /// Verify a public-batch wormhole proof on-chain + VerifyPublicBatch { + /// Path to the public-batch proof file (hex-encoded) + #[arg(short, long, default_value = "public_batch_proof.hex")] + proof: String, + }, /// Parse and display the contents of a proof file (for debugging) ParseProof { /// Path to the proof file (hex-encoded) @@ -703,9 +725,13 @@ pub enum WormholeCommands { proof: String, /// Parse as aggregated proof (default: false, parses as leaf proof) - #[arg(long)] + #[arg(long, conflicts_with = "public_batch")] aggregated: bool, + /// Parse as public-batch proof + #[arg(long)] + public_batch: bool, + /// Verify the proof cryptographically (local verification, not on-chain) #[arg(long)] verify: bool, @@ -747,6 +773,11 @@ pub enum WormholeCommands { /// Dry run - show what would be done without executing #[arg(long)] dry_run: bool, + + /// Route each round through a public batch (second aggregation layer). + /// The wallet address is used as the aggregator and earns the fee rebate. + #[arg(long)] + public: bool, }, /// Dissolve a large wormhole deposit into many small outputs for better privacy. /// @@ -948,10 +979,12 @@ pub async fn handle_wormhole_command( Ok(()) }, WormholeCommands::Aggregate { proofs, output } => aggregate_proofs(proofs, output).await, - WormholeCommands::VerifyAggregated { proof } => - verify_aggregated_proof(proof, node_url).await, - WormholeCommands::ParseProof { proof, aggregated, verify } => - parse_proof_file(proof, aggregated, verify).await, + WormholeCommands::VerifyAggregated { proof } => verify_private_batch(proof, node_url).await, + WormholeCommands::AggregatePublic { proofs, aggregator, output } => + aggregate_public_batch(proofs, aggregator, output).await, + WormholeCommands::VerifyPublicBatch { proof } => verify_public_batch(proof, node_url).await, + WormholeCommands::ParseProof { proof, aggregated, public_batch, verify } => + parse_proof_file(proof, aggregated, public_batch, verify).await, WormholeCommands::Multiround { num_proofs, rounds, @@ -962,6 +995,7 @@ pub async fn handle_wormhole_command( keep_files, output_dir, dry_run, + public, } => { // Convert DEV to planck and align to SCALE_DOWN_FACTOR for clean quantization let amount_planck = (amount * 1_000_000_000_000.0) as u128; @@ -976,6 +1010,7 @@ pub async fn handle_wormhole_command( keep_files, output_dir, dry_run, + public, node_url, ) .await @@ -1128,7 +1163,9 @@ pub async fn aggregate_proofs( proof_files: Vec, output_file: String, ) -> crate::error::Result<()> { - use qp_wormhole_aggregator::aggregator::{AggregationBackend, CircuitType, Layer0Aggregator}; + use qp_wormhole_aggregator::aggregator::{ + AggregationBackend, CircuitType, PrivateBatchAggregator, + }; log_print!("Aggregating {} proofs...", proof_files.len()); @@ -1153,7 +1190,7 @@ pub async fn aggregate_proofs( log_print!(" Loading aggregator and generating {} dummy proofs...", num_padding_proofs); - let mut aggregator = Layer0Aggregator::new(&bins_dir).map_err(|e| { + let mut aggregator = PrivateBatchAggregator::new(&bins_dir).map_err(|e| { crate::error::QuantusError::Generic(format!( "Failed to load aggregator from pre-built bins: {}", e @@ -1196,7 +1233,7 @@ pub async fn aggregate_proofs( // Parse and display aggregated public inputs let aggregated_public_inputs = - AggregatedPublicCircuitInputs::try_from_felts(aggregated_proof.public_inputs.as_slice()) + PrivateBatchPublicInputs::try_from_felts(aggregated_proof.public_inputs.as_slice()) .map_err(|e| { crate::error::QuantusError::Generic(format!( "Failed to parse aggregated public inputs: {}", @@ -1251,11 +1288,159 @@ pub async fn aggregate_proofs( Ok(()) } +/// Aggregate private-batch proofs into a public batch with the given aggregator address. +/// +/// Partial batches are padded with dummy private-batch proofs by the prover, so any +/// number of proofs from 1 up to the circuit's `num_private_batch_proofs` is accepted. +pub async fn aggregate_public_batch( + proof_files: Vec, + aggregator_address_str: String, + output_file: String, +) -> crate::error::Result<()> { + use plonky2::field::types::PrimeField64; + use qp_wormhole_aggregator::aggregator::PublicBatchAggregator; + use qp_wormhole_inputs::PublicBatchPublicInputs; + + log_print!("Aggregating {} private-batch proofs into a public batch...", proof_files.len()); + + let aggregator_bytes = parse_exit_account(&aggregator_address_str).map_err(|e| { + crate::error::QuantusError::Generic(format!("Invalid aggregator address: {}", e)) + })?; + let aggregator_address = BytesDigest::try_from(aggregator_bytes).map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Aggregator address is not representable as field elements (must be a hash-derived account): {}", + e + )) + })?; + log_print!(" Aggregator (fee rebate recipient): {}", slice_to_quantus_ss58(&aggregator_bytes)); + + let bins_dir = crate::bins::ensure_bins_dir()?; + 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 + )) + })?; + let num_private_batch_proofs = agg_config.num_private_batch_proofs.ok_or_else(|| { + crate::error::QuantusError::Generic( + "Circuit binaries were generated without public-batch support; \ + delete the generated-bins directory to regenerate them" + .to_string(), + ) + })?; + + if proof_files.is_empty() { + return Err(crate::error::QuantusError::Generic( + "At least one private-batch proof is required".to_string(), + )); + } + if proof_files.len() > num_private_batch_proofs { + return Err(crate::error::QuantusError::Generic(format!( + "Too many proofs: {} provided, max {} supported by circuit", + proof_files.len(), + num_private_batch_proofs + ))); + } + + let mut aggregator = + PublicBatchAggregator::new(&bins_dir, aggregator_address).map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Failed to load public-batch aggregator from pre-built bins: {}", + e + )) + })?; + + // For the public-batch aggregator, "leaf" circuit data is the private-batch common data. + let common_data = aggregator.load_common_data(CircuitType::Leaf).map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Failed to load private-batch circuit data: {}", + e + )) + })?; + + for (idx, proof_file) in proof_files.iter().enumerate() { + log_verbose!("Loading proof {}/{}: {}", idx + 1, proof_files.len(), proof_file); + let proof_bytes = read_hex_proof_file_to_bytes(proof_file)?; + let proof = ProofWithPublicInputs::::from_bytes(proof_bytes, &common_data) + .map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Failed to deserialize private-batch proof from {}: {}", + proof_file, e + )) + })?; + aggregator.push_proof(proof).map_err(|e| { + crate::error::QuantusError::Generic(format!("Failed to add proof: {}", e)) + })?; + } + + let num_dummies = num_private_batch_proofs - proof_files.len(); + if num_dummies > 0 { + log_print!(" Padding with {} dummy private-batch proof(s)...", num_dummies); + } + + log_print!(" Running public-batch aggregation..."); + let agg_start = std::time::Instant::now(); + let public_batch_proof = aggregator.aggregate().map_err(|e| { + crate::error::QuantusError::Generic(format!("Public-batch aggregation failed: {}", e)) + })?; + log_print!(" Aggregation: {:.2}s", agg_start.elapsed().as_secs_f64()); + + // Parse and display the public-batch public inputs + let pi_u64s: Vec = + public_batch_proof.public_inputs.iter().map(|f| f.to_canonical_u64()).collect(); + let public_inputs = PublicBatchPublicInputs::try_from_u64_slice( + &pi_u64s, + num_private_batch_proofs, + agg_config.num_leaf_proofs, + ) + .map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Failed to parse public-batch public inputs: {}", + e + )) + })?; + + log_verbose!("Public-batch public inputs: {:#?}", public_inputs); + log_print!(" Exit accounts in public batch:"); + for (idx, account_data) in public_inputs.account_data.iter().enumerate() { + let exit_bytes: &[u8] = account_data.exit_account.as_ref(); + let is_dummy = exit_bytes.iter().all(|&b| b == 0) || account_data.summed_output_amount == 0; + if is_dummy { + log_verbose!(" [{}] DUMMY (skipped)", idx); + } else { + let dequantized_amount = + (account_data.summed_output_amount as u128) * SCALE_DOWN_FACTOR; + log_print!( + " [{}] {} -> {}", + idx, + slice_to_quantus_ss58(exit_bytes), + format_balance(dequantized_amount) + ); + } + } + + log_verbose!("Verifying public-batch proof locally..."); + aggregator.verify(public_batch_proof.clone()).map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Public-batch proof verification failed: {}", + e + )) + })?; + + write_proof_file(&output_file, &public_batch_proof.to_bytes()).map_err(|e| { + crate::error::QuantusError::Generic(format!("Failed to write proof: {}", e)) + })?; + + log_success!("Public-batch aggregation complete!"); + log_success!("Output: {}", output_file); + Ok(()) +} + /// Where in the chain a submitted extrinsic has been observed. /// /// `Best` means it landed in the current best block (not yet finalised); /// `Finalized` means it's in a block past the finality gadget. Returned by -/// [`submit_unsigned_verify_aggregated_proof`] alongside the block + tx hash. +/// [`submit_unsigned_verify_private_batch`] alongside the block + tx hash. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum IncludedAt { Best, @@ -1289,15 +1474,15 @@ fn read_hex_proof_file_to_bytes(proof_file: &str) -> crate::error::Result, ) -> crate::error::Result<(IncludedAt, subxt::utils::H256, subxt::utils::H256)> { use subxt::tx::TxStatus; - let verify_tx = quantus_node::api::tx().wormhole().verify_aggregated_proof(proof_bytes); + let verify_tx = quantus_node::api::tx().wormhole().verify_private_batch(proof_bytes); let unsigned_tx = quantus_client.client().tx().create_unsigned(&verify_tx).map_err(|e| { crate::error::QuantusError::Generic(format!("Failed to create unsigned tx: {}", e)) @@ -1409,7 +1594,7 @@ async fn collect_wormhole_events_for_extrinsic( Ok((found_proof_verified, transfer_events)) } -async fn verify_aggregated_proof(proof_file: String, node_url: &str) -> crate::error::Result<()> { +async fn verify_private_batch(proof_file: String, node_url: &str) -> crate::error::Result<()> { log_print!("Verifying aggregated wormhole proof on-chain..."); let proof_bytes = read_hex_proof_file_to_bytes(&proof_file)?; @@ -1424,7 +1609,7 @@ async fn verify_aggregated_proof(proof_file: String, node_url: &str) -> crate::e log_verbose!("Submitting unsigned aggregated verification transaction..."); let (included_at, block_hash, tx_hash) = - submit_unsigned_verify_aggregated_proof(&quantus_client, proof_bytes).await?; + submit_unsigned_verify_private_batch(&quantus_client, proof_bytes).await?; // One unified check (no best/finalized copy-paste) let result = check_proof_verification_events( @@ -1454,6 +1639,97 @@ async fn verify_aggregated_proof(proof_file: String, node_url: &str) -> crate::e Err(crate::error::QuantusError::Generic(error_msg)) } +/// Submit unsigned verify_public_batch(proof_bytes) and return (included_at, block_hash, +/// tx_hash). +pub async fn submit_unsigned_verify_public_batch( + quantus_client: &QuantusClient, + proof_bytes: Vec, +) -> crate::error::Result<(IncludedAt, subxt::utils::H256, subxt::utils::H256)> { + use subxt::tx::TxStatus; + + let verify_tx = quantus_node::api::tx().wormhole().verify_public_batch(proof_bytes); + + let unsigned_tx = quantus_client.client().tx().create_unsigned(&verify_tx).map_err(|e| { + crate::error::QuantusError::Generic(format!("Failed to create unsigned tx: {}", e)) + })?; + + let mut tx_progress = unsigned_tx + .submit_and_watch() + .await + .map_err(|e| crate::error::QuantusError::Generic(format!("Failed to submit tx: {}", e)))?; + + while let Some(Ok(status)) = tx_progress.next().await { + match status { + TxStatus::InBestBlock(tx_in_block) => { + return Ok(( + IncludedAt::Best, + tx_in_block.block_hash(), + tx_in_block.extrinsic_hash(), + )); + }, + TxStatus::InFinalizedBlock(tx_in_block) => { + return Ok(( + IncludedAt::Finalized, + tx_in_block.block_hash(), + tx_in_block.extrinsic_hash(), + )); + }, + TxStatus::Error { message } | TxStatus::Invalid { message } => { + return Err(crate::error::QuantusError::Generic(format!( + "Transaction failed: {}", + message + ))); + }, + _ => continue, + } + } + + Err(crate::error::QuantusError::Generic("Transaction stream ended unexpectedly".to_string())) +} + +async fn verify_public_batch(proof_file: String, node_url: &str) -> crate::error::Result<()> { + log_print!("Verifying public-batch wormhole proof on-chain..."); + + let proof_bytes = read_hex_proof_file_to_bytes(&proof_file)?; + log_verbose!("Public-batch proof size: {} bytes", proof_bytes.len()); + + let quantus_client = QuantusClient::new(node_url) + .await + .map_err(|e| crate::error::QuantusError::Generic(format!("Failed to connect: {}", e)))?; + log_verbose!("Connected to node"); + + log_verbose!("Submitting unsigned public-batch verification transaction..."); + + let (included_at, block_hash, tx_hash) = + submit_unsigned_verify_public_batch(&quantus_client, proof_bytes).await?; + + let result = check_proof_verification_events( + quantus_client.client(), + &block_hash, + &tx_hash, + crate::log::is_verbose(), + ) + .await?; + + if result.success { + log_success!("Public-batch proof verified successfully on-chain!"); + if let Some(amount) = result.exit_amount { + log_success!("Total exit amount: {}", format_balance(amount)); + } + + log_print!(" Block: 0x{}", hex::encode(block_hash.0)); + log_print!(" Extrinsic: 0x{}", hex::encode(tx_hash.0)); + log_verbose!("Included in {}: {:?}", included_at.label(), block_hash); + return Ok(()); + } + + let error_msg = result.error_message.unwrap_or_else(|| { + "Public-batch proof verification failed - no ProofVerified event found".to_string() + }); + log_error!("❌ {}", error_msg); + Err(crate::error::QuantusError::Generic(error_msg)) +} + // ============================================================================ // Multi-round wormhole flow implementation // ============================================================================ @@ -2031,6 +2307,7 @@ async fn run_multiround( keep_files: bool, output_dir: String, dry_run: bool, + public: bool, node_url: &str, ) -> crate::error::Result<()> { use colored::Colorize; @@ -2177,11 +2454,24 @@ async fn run_multiround( log_print!(" Aggregated proof saved to {}", aggregated_file); - // Step 4: Verify aggregated proof on-chain - log_print!("{}", "Step 4: Submitting aggregated proof on-chain...".bright_yellow()); - - let (verification_block, extrinsic_hash, transfer_events) = - verify_aggregated_and_get_events(&aggregated_file, &quantus_client).await?; + // Step 4: Verify on-chain (optionally wrapping in a public batch first) + let (verification_block, extrinsic_hash, transfer_events) = if public { + log_print!( + "{}", + "Step 4: Wrapping in a public batch and submitting on-chain...".bright_yellow() + ); + let public_batch_file = format!("{}/public_batch.hex", round_dir); + aggregate_public_batch( + vec![aggregated_file.clone()], + wallet.wallet_address.clone(), + public_batch_file.clone(), + ) + .await?; + verify_public_batch_and_get_events(&public_batch_file, &quantus_client).await? + } else { + log_print!("{}", "Step 4: Submitting aggregated proof on-chain...".bright_yellow()); + verify_private_batch_and_get_events(&aggregated_file, &quantus_client).await? + }; log_print!( " {} Proof verified in block {} (extrinsic: 0x{})", @@ -2454,7 +2744,7 @@ pub fn decode_full_leaf_data(leaf_data: &[u8]) -> crate::error::Result<([u8; 32] } /// Verify an aggregated proof and return the block hash, extrinsic hash, and transfer events -pub async fn verify_aggregated_and_get_events( +pub async fn verify_private_batch_and_get_events( proof_file: &str, quantus_client: &QuantusClient, ) -> crate::error::Result<( @@ -2469,15 +2759,19 @@ pub async fn verify_aggregated_and_get_events( log_verbose!("Verifying aggregated proof locally before on-chain submission..."); let bins_dir = crate::bins::ensure_bins_dir()?; - let common_bytes = std::fs::read(bins_dir.join("aggregated_common.bin")).map_err(|e| { - crate::error::QuantusError::Generic(format!("Failed to read aggregated_common.bin: {}", e)) - })?; - let verifier_bytes = std::fs::read(bins_dir.join("aggregated_verifier.bin")).map_err(|e| { + let common_bytes = std::fs::read(bins_dir.join("private_batch_common.bin")).map_err(|e| { crate::error::QuantusError::Generic(format!( - "Failed to read aggregated_verifier.bin: {}", + "Failed to read private_batch_common.bin: {}", e )) })?; + let verifier_bytes = + std::fs::read(bins_dir.join("private_batch_verifier.bin")).map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Failed to read private_batch_verifier.bin: {}", + e + )) + })?; log_verbose!( "Circuit binaries: common_bytes.len={}, verifier_bytes.len={}, common_hash={}, verifier_hash={}", common_bytes.len(), @@ -2487,8 +2781,8 @@ pub async fn verify_aggregated_and_get_events( ); let verifier = WormholeVerifier::new_from_files( - &bins_dir.join("aggregated_verifier.bin"), - &bins_dir.join("aggregated_common.bin"), + &bins_dir.join("private_batch_verifier.bin"), + &bins_dir.join("private_batch_common.bin"), ) .map_err(|e| { crate::error::QuantusError::Generic(format!("Failed to load aggregated verifier: {}", e)) @@ -2516,7 +2810,7 @@ pub async fn verify_aggregated_and_get_events( // Submit unsigned tx + wait for inclusion (best or finalized) let (included_at, block_hash, tx_hash) = - submit_unsigned_verify_aggregated_proof(quantus_client, proof_bytes).await?; + submit_unsigned_verify_private_batch(quantus_client, proof_bytes).await?; log_verbose!( "Submitted tx included in {}: block={:?}, tx={:?}", @@ -2551,6 +2845,87 @@ pub async fn verify_aggregated_and_get_events( Ok((block_hash, tx_hash, transfer_events)) } +/// Verify a public-batch proof and return the block hash, extrinsic hash, and transfer events +pub async fn verify_public_batch_and_get_events( + proof_file: &str, + quantus_client: &QuantusClient, +) -> crate::error::Result<( + subxt::utils::H256, + subxt::utils::H256, + Vec, +)> { + use qp_wormhole_verifier::WormholeVerifier; + + let proof_bytes = read_hex_proof_file_to_bytes(proof_file)?; + + log_verbose!("Verifying public-batch proof locally before on-chain submission..."); + let bins_dir = crate::bins::ensure_bins_dir()?; + + let verifier = WormholeVerifier::new_from_files( + &bins_dir.join("public_batch_verifier.bin"), + &bins_dir.join("public_batch_common.bin"), + ) + .map_err(|e| { + crate::error::QuantusError::Generic(format!("Failed to load public-batch verifier: {}", e)) + })?; + + let proof = qp_wormhole_verifier::ProofWithPublicInputs::< + qp_wormhole_verifier::F, + qp_wormhole_verifier::C, + { qp_wormhole_verifier::D }, + >::from_bytes(proof_bytes.clone(), &verifier.circuit_data.common) + .map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Failed to deserialize public-batch proof: {}", + e + )) + })?; + + verifier.verify(proof).map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Local public-batch proof verification failed: {}", + e + )) + })?; + log_verbose!("Local verification passed!"); + + // Submit unsigned tx + wait for inclusion (best or finalized) + let (included_at, block_hash, tx_hash) = + submit_unsigned_verify_public_batch(quantus_client, proof_bytes).await?; + + log_verbose!( + "Submitted tx included in {}: block={:?}, tx={:?}", + included_at.label(), + block_hash, + tx_hash + ); + + // Collect events for our extrinsic only + let (found_proof_verified, transfer_events) = + collect_wormhole_events_for_extrinsic(quantus_client, block_hash, tx_hash).await?; + + if !found_proof_verified { + return Err(crate::error::QuantusError::Generic( + "Public-batch proof verification failed - no ProofVerified event".to_string(), + )); + } + + // Log minted amounts + log_print!(" Tokens minted (from NativeTransferred events):"); + for (idx, transfer) in transfer_events.iter().enumerate() { + let ss58_address = bytes_to_quantus_ss58(&transfer.to.0); + log_print!( + " [{}] {} -> {} planck ({})", + idx, + ss58_address, + transfer.amount, + format_balance(transfer.amount) + ); + } + + Ok((block_hash, tx_hash, transfer_events)) +} + /// Dry run - show what would happen without executing fn run_multiround_dry_run( mnemonic: &str, @@ -2617,6 +2992,7 @@ fn run_multiround_dry_run( async fn parse_proof_file( proof_file: String, aggregated: bool, + public_batch: bool, verify: bool, ) -> crate::error::Result<()> { use qp_wormhole_verifier::WormholeVerifier; @@ -2631,11 +3007,96 @@ async fn parse_proof_file( let bins_dir = crate::bins::ensure_bins_dir()?; - if aggregated { + if public_batch { + use plonky2::field::types::PrimeField64; + use qp_wormhole_inputs::PublicBatchPublicInputs; + + let agg_config = CircuitBinsConfig::load(&bins_dir).map_err(|e| { + crate::error::QuantusError::Generic(format!("Failed to load bins config: {}", e)) + })?; + let num_private_batch_proofs = agg_config.num_private_batch_proofs.ok_or_else(|| { + crate::error::QuantusError::Generic( + "Circuit binaries lack public-batch support; regenerate them".to_string(), + ) + })?; + + let verifier = WormholeVerifier::new_from_files( + &bins_dir.join("public_batch_verifier.bin"), + &bins_dir.join("public_batch_common.bin"), + ) + .map_err(|e| { + crate::error::QuantusError::Generic(format!("Failed to load verifier: {}", e)) + })?; + + let proof = qp_wormhole_verifier::ProofWithPublicInputs::< + qp_wormhole_verifier::F, + qp_wormhole_verifier::C, + { qp_wormhole_verifier::D }, + >::from_bytes(proof_bytes.clone(), &verifier.circuit_data.common) + .map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Failed to deserialize public-batch proof: {:?}", + e + )) + })?; + + log_print!("\nPublic inputs count: {}", proof.public_inputs.len()); + + let pi_u64s: Vec = proof.public_inputs.iter().map(|f| f.to_canonical_u64()).collect(); + match PublicBatchPublicInputs::try_from_u64_slice( + &pi_u64s, + num_private_batch_proofs, + agg_config.num_leaf_proofs, + ) { + Ok(inputs) => { + log_print!("\n=== Parsed Public-Batch Public Inputs ==="); + log_print!( + "Aggregator: 0x{} ({})", + hex::encode(inputs.aggregator_address.as_ref()), + slice_to_quantus_ss58(inputs.aggregator_address.as_ref()) + ); + log_print!("Asset ID: {}", inputs.asset_id); + log_print!("Volume Fee BPS: {}", inputs.volume_fee_bps); + log_print!("Block Hash: 0x{}", hex::encode(inputs.block_data.block_hash.as_ref())); + log_print!("Block Number: {}", inputs.block_data.block_number); + log_print!("Total Exit Slots: {}", inputs.total_exit_slots); + log_print!("\nAccount Data ({} slots):", inputs.account_data.len()); + for (i, acct) in inputs.account_data.iter().enumerate() { + log_print!( + " [{}] amount={}, exit=0x{}", + i, + acct.summed_output_amount, + hex::encode(acct.exit_account.as_ref()) + ); + } + log_print!("\nNullifiers ({} nullifiers):", inputs.nullifiers.len()); + for (i, nullifier) in inputs.nullifiers.iter().enumerate() { + log_print!(" [{}] 0x{}", i, hex::encode(nullifier.as_ref())); + } + }, + Err(e) => { + log_print!("Failed to parse as public-batch inputs: {}", e); + }, + } + + if verify { + log_print!("\n=== Verifying Proof ==="); + match verifier.verify(proof) { + Ok(()) => log_success!("Proof verification PASSED"), + Err(e) => { + log_error!("Proof verification FAILED: {}", e); + return Err(crate::error::QuantusError::Generic(format!( + "Proof verification failed: {}", + e + ))); + }, + } + } + } else if aggregated { // Load aggregated verifier let verifier = WormholeVerifier::new_from_files( - &bins_dir.join("aggregated_verifier.bin"), - &bins_dir.join("aggregated_common.bin"), + &bins_dir.join("private_batch_verifier.bin"), + &bins_dir.join("private_batch_common.bin"), ) .map_err(|e| { crate::error::QuantusError::Generic(format!("Failed to load verifier: {}", e)) @@ -2658,7 +3119,7 @@ async fn parse_proof_file( log_verbose!("\nPublic inputs count: {}", proof.public_inputs.len()); // Try to parse as aggregated - match qp_wormhole_verifier::parse_aggregated_public_inputs(&proof) { + match qp_wormhole_verifier::parse_private_batch_public_inputs(&proof) { Ok(agg_inputs) => { log_print!("\n=== Parsed Aggregated Public Inputs ==="); log_print!("Asset ID: {}", agg_inputs.asset_id); @@ -3005,7 +3466,7 @@ async fn run_dissolve( // Verify on-chain log_print!(" Verifying on-chain..."); let (verification_block, _extrinsic_hash, transfer_events) = - verify_aggregated_and_get_events(&aggregated_file, &quantus_client).await?; + verify_private_batch_and_get_events(&aggregated_file, &quantus_client).await?; log_success!(" Verified in block 0x{}", hex::encode(verification_block.0)); @@ -3247,10 +3708,10 @@ async fn run_collect_rewards( /// Helper to aggregate proof files and write the result (used by dissolve command) fn aggregate_proofs_to_file(proof_files: &[String], output_file: &str) -> crate::error::Result<()> { - use qp_wormhole_aggregator::aggregator::Layer0Aggregator; + use qp_wormhole_aggregator::aggregator::PrivateBatchAggregator; let bins_dir = crate::bins::ensure_bins_dir()?; - let mut aggregator = Layer0Aggregator::new(&bins_dir).map_err(|e| { + let mut aggregator = PrivateBatchAggregator::new(&bins_dir).map_err(|e| { crate::error::QuantusError::Generic(format!("Failed to create aggregator: {}", e)) })?; @@ -3712,12 +4173,22 @@ 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 + "num_private_batch_proofs": null }"#; let config: CircuitBinsConfig = serde_json::from_str(json).unwrap(); assert_eq!(config.num_leaf_proofs, 8); - assert_eq!(config.num_layer0_proofs, None); + assert_eq!(config.num_private_batch_proofs, None); + + // Legacy config.json files (pre-rename) must still deserialize via the + // serde alias on the upstream field. + let legacy_json = r#"{ + "num_leaf_proofs": 8, + "num_layer0_proofs": 4 + }"#; + + let legacy: CircuitBinsConfig = serde_json::from_str(legacy_json).unwrap(); + assert_eq!(legacy.num_private_batch_proofs, Some(4)); } fn mk_accounts(n: usize) -> Vec<[u8; 32]> { diff --git a/src/collect_rewards_lib.rs b/src/collect_rewards_lib.rs index 1c26990..34bd747 100644 --- a/src/collect_rewards_lib.rs +++ b/src/collect_rewards_lib.rs @@ -29,7 +29,7 @@ use crate::{ use plonky2::plonk::proof::ProofWithPublicInputs; use qp_rusty_crystals_hdwallet::{derive_wormhole_from_mnemonic, QUANTUS_WORMHOLE_CHAIN_ID}; use qp_wormhole_aggregator::{ - aggregator::{AggregationBackend, CircuitType, Layer0Aggregator}, + aggregator::{AggregationBackend, CircuitType, PrivateBatchAggregator}, config::CircuitBinsConfig, }; use qp_zk_circuits_common::circuit::{C, D, F}; @@ -753,7 +753,7 @@ fn aggregate_proof_bytes(proof_bytes_list: &[Vec], bins_dir: &Path) -> Resul ))); } - let mut aggregator = Layer0Aggregator::new(bins_dir) + let mut aggregator = PrivateBatchAggregator::new(bins_dir) .map_err(|e| CollectRewardsError::from(format!("Failed to load aggregator: {}", e)))?; let common_data = aggregator.load_common_data(CircuitType::Leaf).map_err(|e| { @@ -788,8 +788,8 @@ async fn submit_and_get_events( // Verify locally first let verifier = qp_wormhole_verifier::WormholeVerifier::new_from_files( - &bins_dir.join("aggregated_verifier.bin"), - &bins_dir.join("aggregated_common.bin"), + &bins_dir.join("private_batch_verifier.bin"), + &bins_dir.join("private_batch_common.bin"), ) .map_err(|e| CollectRewardsError::from(format!("Failed to load verifier: {}", e)))?; @@ -805,7 +805,7 @@ async fn submit_and_get_events( .map_err(|e| CollectRewardsError::from(format!("Local verification failed: {}", e)))?; // Parse public inputs to do pre-submission validation - let inputs = qp_wormhole_verifier::parse_aggregated_public_inputs(&proof).map_err(|e| { + let inputs = qp_wormhole_verifier::parse_private_batch_public_inputs(&proof).map_err(|e| { CollectRewardsError::from(format!("Failed to parse public inputs: {:?}", e)) })?; @@ -890,7 +890,7 @@ async fn submit_and_get_events( } // Submit unsigned tx - let verify_tx = quantus_node::api::tx().wormhole().verify_aggregated_proof(proof_bytes); + let verify_tx = quantus_node::api::tx().wormhole().verify_private_batch(proof_bytes); let unsigned_tx = quantus_client.client().tx().create_unsigned(&verify_tx).map_err(|e| { diff --git a/src/lib.rs b/src/lib.rs index af8f9a9..0ae2af1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,7 @@ pub use wormhole_lib::{ // Re-export wormhole on-chain helpers for SDK usage. // These are the on-chain side of the wormhole flow (proof aggregation, unsigned -// `verify_aggregated_proof` submission, transfer-event parsing, leaf decoding) +// `verify_private_batch` submission, transfer-event parsing, leaf decoding) // that complement the off-chain proof-generation functions in `wormhole_lib`. // // `NativeTransferred` is the subxt-generated event type required by @@ -60,7 +60,7 @@ pub use chain::quantus_subxt::api::wormhole::events::NativeTransferred; pub use cli::wormhole::{ aggregate_proofs, at_best_block, compute_merkle_positions, decode_full_leaf_data, get_zk_merkle_proof, parse_transfer_events, read_proof_file, - submit_unsigned_verify_aggregated_proof, verify_aggregated_and_get_events, write_proof_file, + submit_unsigned_verify_private_batch, verify_private_batch_and_get_events, write_proof_file, IncludedAt, TransferInfo, }; diff --git a/src/quantus_metadata.scale b/src/quantus_metadata.scale index 2ab234b..7f72abd 100644 Binary files a/src/quantus_metadata.scale and b/src/quantus_metadata.scale differ