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
21 changes: 7 additions & 14 deletions Cargo.lock

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

9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ tempfile = "3.8.1"
serial_test = "3.1"
qp-poseidon-core = "1.4.0"

[patch.crates-io]
qp-wormhole-aggregator = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "compact-2x8-aggr" }
qp-wormhole-circuit = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "compact-2x8-aggr" }
qp-wormhole-circuit-builder = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "compact-2x8-aggr" }
qp-wormhole-inputs = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "compact-2x8-aggr" }
qp-wormhole-prover = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "compact-2x8-aggr" }
qp-wormhole-verifier = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "compact-2x8-aggr" }
qp-zk-circuits-common = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "compact-2x8-aggr" }

# Optimize build scripts and their dependencies in dev mode.
# This is critical for circuit generation which is CPU-intensive.
# Without this, circuit generation takes ~10 minutes instead of ~30 seconds.
Expand Down
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ quantus wormhole multiround \
```

- `--num-proofs`: Number of proofs per round (1 to `num_leaf_proofs` from circuit config, default: 2).
The shipping layer-0 circuit capacity is fixed at 16 proofs.
- `--rounds`: Number of rounds (default: 2). In intermediate rounds, exit accounts are the next round's wormhole addresses; in the final round, funds exit back to the wallet.
- `--amount`: Total amount in planck to randomly partition across proofs (default: 100 DEV).
- `--wallet`: Wallet name for funding (round 1) and final exit.
Expand Down Expand Up @@ -295,11 +296,19 @@ quantus wormhole check-nullifier --secret 0x<64-hex-chars> --transfer-counts 0-5

#### `quantus developer build-circuits`

Build ZK circuit binaries from the `qp-zk-circuits` repository, then copy them to the CLI and chain directories. This is required whenever the circuit logic changes.
Build ZK circuit binaries from the local `qp-zk-circuits` checkout. This is required whenever the circuit logic changes.

```bash
quantus developer build-circuits \
--num-leaf-proofs 2 \
--num-leaf-proofs 16 \
--chain-path ../chain
```

Add `--num-layer0-proofs` only when you also want layer-1 artifacts:

```bash
quantus developer build-circuits \
--num-leaf-proofs 16 \
--num-layer0-proofs 2 \
--chain-path ../chain
```
Expand All @@ -308,24 +317,23 @@ Add `--skip-prover` when you only need verifier artifacts:

```bash
quantus developer build-circuits \
--num-leaf-proofs 2 \
--num-layer0-proofs 2 \
--num-leaf-proofs 16 \
--chain-path ../chain \
--skip-prover
```

- `--num-leaf-proofs`: Number of leaf proofs per layer-0 aggregation.
- `--num-layer0-proofs`: Number of inner proofs per layer-1 aggregation.
- `--num-leaf-proofs`: Compatibility flag retained for the builder API. Production PR #129 output is fixed at `16`, so other values are rejected.
- `--num-layer0-proofs`: Number of layer-0 proofs per layer-1 aggregation. Omit this for normal layer-0 generation.
- `--chain-path`: Path to the chain repo (default: `../chain`).
- `--skip-chain`: Skip copying binaries to the chain directory.
- `--skip-chain`: Skip clearing stale wormhole build outputs in the chain repo.
- `--skip-prover`: Skip generating prover binaries.

**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/` using the fixed shipping layer-0 output (`common.bin`, `verifier.bin`, `prover.bin`, `dummy_proof.bin`, `inner_*`, `outer_*`, `aggregated_common.bin`, `aggregated_verifier.bin`, `aggregated_prover.bin`, `aggregated_targets.bin`, and `config.json`, plus optional layer-1 files when requested).
3. Clears stale wormhole build outputs in the chain repo so the next chain build regenerates verifier artifacts from the same local `qp-zk-circuits` source.

After running, rebuild the chain (`cargo build --release` in the chain directory) so `include_bytes!()` picks up the new binaries.
After running, rebuild the chain so `include_bytes!()` picks up freshly regenerated verifier binaries from the same local dependency source.

#### `quantus developer create-test-wallets`

Expand Down
40 changes: 22 additions & 18 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ fn print_bin_hash(dir: &Path, filename: &str) {
}
}

fn remove_existing_path(path: &Path) {
if let Ok(metadata) = std::fs::symlink_metadata(path) {
if metadata.file_type().is_symlink() || !metadata.is_dir() {
std::fs::remove_file(path).expect("Failed to remove existing file/symlink");
} else {
std::fs::remove_dir_all(path).expect("Failed to remove existing directory");
}
}
}

fn main() {
// Allow skipping circuit generation for CI jobs that don't need it
if env::var("SKIP_CIRCUIT_BUILD").is_ok() {
Expand All @@ -55,6 +65,11 @@ fn main() {
let num_leaf_proofs: usize = env::var("QP_NUM_LEAF_PROOFS")
.map(|v| v.parse().expect("QP_NUM_LEAF_PROOFS must be a valid usize"))
.unwrap_or(DEFAULT_NUM_LEAF_PROOFS);
if num_leaf_proofs != DEFAULT_NUM_LEAF_PROOFS {
panic!(
"PR #129 shipping layer-0 aggregation is fixed at 16 leaves. Set QP_NUM_LEAF_PROOFS=16 or unset it."
);
}

// Don't emit any rerun-if-changed directives - this forces the build script
// to run on every build. Circuit generation is fast enough in release mode.
Expand All @@ -66,13 +81,14 @@ fn main() {

let start = Instant::now();

remove_existing_path(&build_output_dir);
std::fs::create_dir_all(&build_output_dir)
.expect("Failed to create generated-bins directory in OUT_DIR");

qp_wormhole_circuit_builder::generate_all_circuit_binaries(
&build_output_dir,
true,
num_leaf_proofs,
DEFAULT_NUM_LEAF_PROOFS,
None,
)
.expect("Failed to generate circuit binaries");
Expand All @@ -88,13 +104,9 @@ fn main() {
);

// Print hashes of generated binaries
print_bin_hash(&build_output_dir, "common.bin");
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");
for filename in REQUIRED_BIN_FILES {
print_bin_hash(&build_output_dir, filename);
}

// Copy bins to project root for runtime access, but only during local source
// builds — never during `cargo publish` verification (manifest_dir is inside
Expand All @@ -111,16 +123,7 @@ fn main() {
#[cfg(unix)]
{
use std::os::unix::fs::symlink;
// Remove any existing dir/file/symlink at destination.
if let Ok(meta) = std::fs::symlink_metadata(&project_bins) {
if meta.is_dir() {
std::fs::remove_dir_all(&project_bins)
.expect("Failed to remove existing generated-bins directory");
} else {
std::fs::remove_file(&project_bins)
.expect("Failed to remove existing generated-bins file/symlink");
}
}
remove_existing_path(&project_bins);
if let Err(e) = symlink(&build_output_dir, &project_bins) {
println!(
"cargo:warning=[quantus-cli] Failed to symlink generated-bins ({}). Falling back to copy...",
Expand All @@ -132,6 +135,7 @@ fn main() {
}
}

remove_existing_path(&project_bins);
std::fs::create_dir_all(&project_bins).expect("Failed to create generated-bins directory");
let entries = std::fs::read_dir(&build_output_dir)
.expect("Failed to read generated-bins directory in OUT_DIR");
Expand Down
62 changes: 42 additions & 20 deletions src/bins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,6 @@ include!("bins_consts.rs");
pub const BINS_DIR_ENV: &str = "QUANTUS_BINS_DIR";

/// Files that must be present for all wormhole operations to succeed.
const REQUIRED_FILES: &[&str] = &[
"prover.bin",
"verifier.bin",
"common.bin",
"aggregated_prover.bin",
"aggregated_verifier.bin",
"aggregated_common.bin",
"dummy_proof.bin",
"config.json",
];

/// Resolve the path where circuit binaries should live.
///
/// This never generates anything; see [`ensure_bins_dir`] for the full
Expand Down Expand Up @@ -71,13 +60,13 @@ pub fn ensure_bins_dir() -> Result<PathBuf> {
return Ok(dir);
}

let num_leaf_proofs = env_num_leaf_proofs();
let num_leaf_proofs = env_num_leaf_proofs()?;
generate(&dir, num_leaf_proofs)?;
Ok(dir)
}

fn is_ready(dir: &Path) -> bool {
if !REQUIRED_FILES.iter().all(|f| dir.join(f).exists()) {
if !REQUIRED_BIN_FILES.iter().all(|f| dir.join(f).exists()) {
return false;
}
match std::fs::read_to_string(dir.join(VERSION_MARKER)) {
Expand All @@ -86,14 +75,44 @@ fn is_ready(dir: &Path) -> bool {
}
}

fn env_num_leaf_proofs() -> usize {
std::env::var("QP_NUM_LEAF_PROOFS")
fn env_num_leaf_proofs() -> Result<usize> {
let num_leaf_proofs = std::env::var("QP_NUM_LEAF_PROOFS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_NUM_LEAF_PROOFS)
.unwrap_or(DEFAULT_NUM_LEAF_PROOFS);
if num_leaf_proofs != DEFAULT_NUM_LEAF_PROOFS {
return Err(QuantusError::Generic(
"PR #129 shipping layer-0 aggregation is fixed at 16 leaves. Set QP_NUM_LEAF_PROOFS=16 or unset it.".to_string(),
));
}
Ok(num_leaf_proofs)
}

fn remove_existing_path(path: &Path) -> Result<()> {
if let Ok(metadata) = std::fs::symlink_metadata(path) {
if metadata.file_type().is_symlink() || !metadata.is_dir() {
std::fs::remove_file(path).map_err(|e| {
QuantusError::Generic(format!(
"Failed to remove existing bins file {}: {}",
path.display(),
e
))
})?;
} else {
std::fs::remove_dir_all(path).map_err(|e| {
QuantusError::Generic(format!(
"Failed to remove existing bins directory {}: {}",
path.display(),
e
))
})?;
}
}
Ok(())
}

fn generate(dir: &Path, num_leaf_proofs: usize) -> Result<()> {
remove_existing_path(dir)?;
std::fs::create_dir_all(dir).map_err(|e| {
QuantusError::Generic(format!("Failed to create bins directory {}: {}", dir.display(), e))
})?;
Expand All @@ -104,10 +123,13 @@ fn generate(dir: &Path, num_leaf_proofs: usize) -> Result<()> {
log_print!(" num_leaf_proofs: {}", num_leaf_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,
DEFAULT_NUM_LEAF_PROOFS,
None,
)
.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
25 changes: 23 additions & 2 deletions src/bins_consts.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
/// Filename of the marker recording which CLI version produced the bins.
/// When the CLI is upgraded this mismatches and the runtime regenerates.
/// Shared by `build.rs` and `crate::bins` via `include!`.
const VERSION_MARKER: &str = ".quantus-cli-version";
pub(crate) const VERSION_MARKER: &str = ".quantus-cli-version";

/// Number of leaf proofs aggregated into a single batch (default for both
/// build-time generation and runtime lazy generation).
const DEFAULT_NUM_LEAF_PROOFS: usize = 16;
pub(crate) const DEFAULT_NUM_LEAF_PROOFS: usize = 16;

/// Files required for local proving, aggregation, and verifier compatibility.
pub(crate) const REQUIRED_BIN_FILES: &[&str] = &[
"common.bin",
"verifier.bin",
"prover.bin",
"dummy_proof.bin",
"inner_common.bin",
"inner_verifier.bin",
"inner_prover.bin",
"inner_targets.bin",
"outer_common.bin",
"outer_verifier.bin",
"outer_prover.bin",
"outer_targets.bin",
"aggregated_common.bin",
"aggregated_verifier.bin",
"aggregated_prover.bin",
"aggregated_targets.bin",
"config.json",
];
Loading
Loading