Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 8 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 12 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -121,3 +121,13 @@ opt-level = 3

[profile.release.build-override]
opt-level = 3

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLI version not bumped

Medium Severity

The crate version stays at 1.5.0 while this change adds public-batch circuit parameters (DEFAULT_NUM_PRIVATE_BATCH_PROOFS), new required bin artifacts, and [patch.crates-io] overrides for the wormhole circuit crates. Without a CLI version bump, is_ready()’s .quantus-cli-version marker can still match for installs that already had 1.5.0 but stale generated bins from an earlier build with the same version string.

Additional Locations (2)
Fix in Cursor Fix in Web

Triggered by learned rule: Bump CLI version when proof system parameters or circuit crate versions change

Reviewed by Cursor Bugbot for commit c6fadf8. Configure here.

# 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" }
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
20 changes: 14 additions & 6 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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();
Expand All @@ -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");

Expand All @@ -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
Expand Down
10 changes: 5 additions & 5 deletions examples/wormhole_sdk_e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -307,18 +307,18 @@ 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);

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);
Expand Down
14 changes: 7 additions & 7 deletions examples/wormhole_sdk_usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<NativeTransferred> with the minted amounts at the exit accounts
Expand Down Expand Up @@ -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.");
},
}

Expand Down Expand Up @@ -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!(" }}");
Expand Down
44 changes: 32 additions & 12 deletions src/bins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
];

Expand Down Expand Up @@ -72,7 +76,8 @@ pub fn ensure_bins_dir() -> Result<PathBuf> {
}

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)
}

Expand All @@ -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<usize>,
}
match serde_json::from_str::<ConfigCheck>(&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,
}
},
Expand All @@ -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))
})?;
Expand All @@ -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)))?;
Expand Down
6 changes: 6 additions & 0 deletions src/bins_consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Loading
Loading