mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-05-22 01:30:00 +00:00
Merge pull request #488 from logos-blockchain/moudy/bench-criterion
feat: migrate bench tools to criterion harness
This commit is contained in:
commit
5543e125ee
@ -16,7 +16,6 @@ ignore = [
|
||||
{ id = "RUSTSEC-2026-0097", reason = "`rand` v0.8.5 is present transitively from logos crates, modification may break integration" },
|
||||
{ id = "RUSTSEC-2026-0118", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration" },
|
||||
{ id = "RUSTSEC-2026-0119", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration" },
|
||||
{ id = "RUSTSEC-2026-0145", reason = "`astral-tokio-tar` v0.6.1 is pulled transitively via testcontainers (integration_tests dev/test path); waiting on upstream fix" },
|
||||
]
|
||||
yanked = "deny"
|
||||
unused-ignored-advisory = "deny"
|
||||
|
||||
170
Cargo.lock
generated
170
Cargo.lock
generated
@ -91,6 +91,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloca"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
@ -126,6 +135,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
@ -1405,6 +1420,12 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cbindgen"
|
||||
version = "0.29.2"
|
||||
@ -1489,6 +1510,33 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"ciborium-ll",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-io"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-ll"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
@ -1860,6 +1908,41 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3"
|
||||
dependencies = [
|
||||
"alloca",
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"itertools 0.13.0",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"page_size",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "critical-section"
|
||||
version = "1.2.0"
|
||||
@ -1942,12 +2025,10 @@ dependencies = [
|
||||
name = "crypto_primitives_bench"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"criterion",
|
||||
"key_protocol",
|
||||
"nssa_core",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1994,9 +2075,11 @@ dependencies = [
|
||||
"amm_core",
|
||||
"anyhow",
|
||||
"ata_core",
|
||||
"authenticated_transfer_core",
|
||||
"borsh",
|
||||
"clap",
|
||||
"clock_core",
|
||||
"criterion",
|
||||
"nssa",
|
||||
"nssa_core",
|
||||
"risc0-zkvm",
|
||||
@ -2098,7 +2181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de"
|
||||
dependencies = [
|
||||
"data-encoding",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2582,7 +2665,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3145,6 +3228,17 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hash32"
|
||||
version = "0.2.1"
|
||||
@ -3561,7 +3655,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.3",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@ -6583,6 +6677,12 @@ version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.1"
|
||||
@ -6737,6 +6837,16 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "page_size"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
@ -6893,6 +7003,34 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.11.0"
|
||||
@ -7235,7 +7373,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.3",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@ -7272,7 +7410,7 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.3",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
@ -8167,7 +8305,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -9138,7 +9276,7 @@ dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -9373,6 +9511,16 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.10.0"
|
||||
@ -10469,7 +10617,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -131,6 +131,7 @@ url = { version = "2.5.4", features = ["serde"] }
|
||||
tokio-retry = "0.3.0"
|
||||
schemars = "1.2"
|
||||
async-stream = "0.3.6"
|
||||
criterion = { version = "0.8", features = ["html_reports"] }
|
||||
|
||||
logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
|
||||
logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" }
|
||||
|
||||
6
Justfile
6
Justfile
@ -23,6 +23,12 @@ test:
|
||||
@echo "🧪 Running tests"
|
||||
RISC0_DEV_MODE=1 cargo nextest run --no-fail-fast
|
||||
|
||||
# Run criterion benches: fast crypto primitives, then the slow PPE verify (real proving setup).
|
||||
bench:
|
||||
@echo "📊 Running criterion benches"
|
||||
cargo bench -p crypto_primitives_bench --bench primitives
|
||||
cargo bench -p cycle_bench --features ppe --bench verify
|
||||
|
||||
# Run Bedrock node in docker
|
||||
[working-directory: 'bedrock']
|
||||
run-bedrock:
|
||||
|
||||
@ -14,15 +14,17 @@ Cryptographic primitives used by client/wallet code. Measures the per-call cost
|
||||
|
||||
## Results
|
||||
|
||||
100 timed iterations per operation, 2 warmup discarded.
|
||||
Criterion sample_size = 50, warm_up_time = 2 s, measurement_time = 10 s. Slope-regression point estimate in the middle column; 95% confidence interval bounds in the outer columns.
|
||||
|
||||
| Operation | best (µs) | mean (µs) | stdev (µs) |
|
||||
|---|---:|---:|---:|
|
||||
| KeyChain::new_os_random | 2,979.62 (2.98 ms) | 3,138.18 (3.14 ms) | 258.59 (0.26 ms) |
|
||||
| KeyChain::new_mnemonic | 2,979.12 (2.98 ms) | 3,012.76 (3.01 ms) | 46.09 (0.05 ms) |
|
||||
| SharedSecretKey::new (sender DH) | 74.17 (0.07 ms) | 74.48 (0.07 ms) | 0.22 (<0.01 ms) |
|
||||
| EncryptionScheme::encrypt | 0.88 (<0.01 ms) | 0.92 (<0.01 ms) | 0.03 (<0.01 ms) |
|
||||
| EncryptionScheme::decrypt | 0.75 (<0.01 ms) | 0.78 (<0.01 ms) | 0.04 (<0.01 ms) |
|
||||
| Operation | low | point | high | outliers (mild + severe) |
|
||||
|---|---:|---:|---:|---:|
|
||||
| keychain/new_os_random | 3.11 ms | 3.21 ms | 3.34 ms | 3 + 5 |
|
||||
| keychain/new_mnemonic | 3.05 ms | 3.11 ms | 3.23 ms | 0 + 2 |
|
||||
| shared_secret_key/sender_dh | 76.7 µs | 78.4 µs | 80.6 µs | 3 + 4 |
|
||||
| encryption/encrypt | 1.11 µs | 1.17 µs | 1.25 µs | 1 + 5 |
|
||||
| encryption/decrypt | 907 ns | 928 ns | 954 ns | 0 + 3 |
|
||||
|
||||
Numbers from a single M2 Pro dev box. For full estimates (slope, mean, median, MAD, std-dev) and the noise model, see `target/criterion/<group>/<bench>/estimates.json` after running locally.
|
||||
|
||||
## Findings
|
||||
|
||||
@ -33,10 +35,21 @@ Cryptographic primitives used by client/wallet code. Measures the per-call cost
|
||||
## Reproduce
|
||||
|
||||
```sh
|
||||
cargo run --release -p crypto_primitives_bench
|
||||
cargo bench -p crypto_primitives_bench --bench primitives
|
||||
```
|
||||
|
||||
JSON output: `target/crypto_primitives_bench.json`.
|
||||
JSON estimates: `target/criterion/<group>/<bench>/estimates.json`. HTML report: `target/criterion/report/index.html`.
|
||||
|
||||
## Baseline comparison
|
||||
|
||||
```sh
|
||||
# On main:
|
||||
cargo bench -p crypto_primitives_bench --bench primitives -- --save-baseline main
|
||||
# On your branch:
|
||||
cargo bench -p crypto_primitives_bench --bench primitives -- --baseline main
|
||||
```
|
||||
|
||||
Criterion reports per-bench change as a percentage with a 95% confidence interval; deltas within the CI are reported as "no significant change" rather than red.
|
||||
|
||||
## Caveats
|
||||
|
||||
|
||||
@ -63,23 +63,24 @@ Same `auth_transfer Transfer` instruction, standalone vs wrapped in the privacy
|
||||
|
||||
Linear fit depth=1..9: ≈ 53 s per additional chained call, intercept ≈ 73 s. Composition tax (single program PPE − standalone): ≈ 48 s. `proof_bytes` is constant: the outer succinct proof has fixed size; the journal carried alongside it scales with public state and is reported separately by `--verify`.
|
||||
|
||||
## Verifier (`--verify`)
|
||||
## Verifier (criterion bench)
|
||||
|
||||
One PPE receipt generated once (auth_transfer Transfer in PPE), then `Receipt::verify(PRIVACY_PRESERVING_CIRCUIT_ID)` measured over 1000 iterations.
|
||||
One PPE receipt generated once (auth_transfer Transfer in PPE), then `Receipt::verify(PRIVACY_PRESERVING_CIRCUIT_ID)` measured under criterion's statistical sampler. Bench file: `tools/cycle_bench/benches/verify.rs`. Setup (one full PPE prove) is outside the timed `iter` loop.
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| case | auth_transfer Transfer in PPE |
|
||||
| proof_bytes (S_agg) | 223,551 |
|
||||
| journal_bytes | 412 |
|
||||
| verify_ms (best / mean ± stdev, n=1000) | 11.71 / 12.06 ± 1.99 |
|
||||
Numbers from the most recent local run on the machine listed above. Criterion sample_size = 100, measurement_time = 15 s, warm_up_time = 2 s. Slope-regression point estimate in the middle column; 95% CI bounds on either side. Run `cargo bench -p cycle_bench --features ppe --bench verify` to refresh.
|
||||
|
||||
| Bench | low | point | high | outliers (mild + severe) |
|
||||
|---|---:|---:|---:|---:|
|
||||
| ppe/verify_auth_transfer | 12.016 ms | 12.215 ms | 12.469 ms | 1 + 10 |
|
||||
|
||||
The corresponding `proof_bytes` (S_agg) for the bench receipt is captured by `--ppe` above; the verify bench itself only times the verify call.
|
||||
|
||||
## Findings
|
||||
|
||||
- Proving cost scales with po2-bucketed `total_cycles`, not raw `user_cycles`. Trimming user_cycles only helps if it crosses a 2^N boundary.
|
||||
- Single-program PPE composition tax on M2 Pro CPU: ≈ 48 s (61.5 − 13.7).
|
||||
- Chained-call cost is linear at ≈ 53 s per call. A max-depth chain (10) would take ≈ 600 s standalone on this CPU.
|
||||
- `G_verify` is ≈ 12 ms and roughly constant per outer receipt (1000-iter stdev ≈ 2 ms). The succinct outer proof is fixed at 223,551 bytes (S_agg); verify is not on the latency critical path.
|
||||
- `G_verify` is ≈ 12 ms (criterion CI: 12.0–12.5 ms over 100 samples) and roughly constant per outer receipt. The succinct outer proof is fixed at 223,551 bytes (S_agg); verify is not on the latency critical path.
|
||||
|
||||
## Reproduce
|
||||
|
||||
@ -87,10 +88,12 @@ One PPE receipt generated once (auth_transfer Transfer in PPE), then `Receipt::v
|
||||
cargo run --release -p cycle_bench
|
||||
cargo run --release -p cycle_bench --features prove -- --prove
|
||||
cargo run --release -p cycle_bench --features ppe -- --prove --ppe
|
||||
cargo run --release -p cycle_bench --features ppe -- --verify --verify-iters 1000
|
||||
|
||||
# Verifier microbench via criterion:
|
||||
cargo bench -p cycle_bench --features ppe --bench verify
|
||||
```
|
||||
|
||||
JSON output: `target/cycle_bench.json`.
|
||||
JSON output: `target/cycle_bench.json` (bin), `target/criterion/ppe/verify_auth_transfer/` (verify bench).
|
||||
|
||||
## Caveats
|
||||
|
||||
|
||||
@ -8,11 +8,12 @@ publish = false
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
[dev-dependencies]
|
||||
key_protocol.workspace = true
|
||||
nssa_core = { workspace = true, features = ["host"] }
|
||||
|
||||
anyhow.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
rand = { workspace = true }
|
||||
criterion.workspace = true
|
||||
|
||||
[[bench]]
|
||||
name = "primitives"
|
||||
harness = false
|
||||
|
||||
@ -1,20 +1,29 @@
|
||||
# crypto_primitives_bench
|
||||
|
||||
Cryptographic primitive microbenchmarks used by client/wallet code. Single host binary, no live sequencer or Bedrock needed.
|
||||
Criterion-driven microbenchmarks for the cryptographic primitives client/wallet code uses on every transaction. No live sequencer or Bedrock needed.
|
||||
|
||||
## Run
|
||||
|
||||
```sh
|
||||
cargo run --release -p crypto_primitives_bench
|
||||
cargo bench -p crypto_primitives_bench --bench primitives
|
||||
```
|
||||
|
||||
## What you'll see
|
||||
|
||||
Per-operation `best_us`, `mean_us`, and `stdev_us` over 100 iterations (plus 2 warmup):
|
||||
Criterion's per-operation report (point estimate, 95% CI, outlier counts) for:
|
||||
|
||||
- `KeyChain::new_os_random` — full mnemonic → SSK → NSK/VSK + public-key derivation (HMAC-SHA512 PBKDF dominates).
|
||||
- `KeyChain::new_mnemonic` — same pipeline, mnemonic exposed.
|
||||
- `SharedSecretKey::new (sender DH)` — secp256k1 ECDH per recipient.
|
||||
- `EncryptionScheme::encrypt` / `decrypt` — ChaCha20 over an Account note.
|
||||
- `keychain/new_os_random`: full mnemonic → SSK → NSK/VSK + public-key derivation (HMAC-SHA512 PBKDF dominates).
|
||||
- `keychain/new_mnemonic`: same pipeline, mnemonic exposed.
|
||||
- `shared_secret_key/sender_dh`: secp256k1 ECDH per recipient (includes ephemeral key gen).
|
||||
- `encryption/encrypt` / `decrypt`: ChaCha20 over an Account note.
|
||||
|
||||
JSON output is written to `target/crypto_primitives_bench.json`.
|
||||
Per-bench JSON estimates are written under `target/criterion/<group>/<bench>/`. HTML reports at `target/criterion/report/index.html`.
|
||||
|
||||
## Baseline comparison
|
||||
|
||||
```sh
|
||||
# On main:
|
||||
cargo bench -p crypto_primitives_bench --bench primitives -- --save-baseline main
|
||||
# On your branch:
|
||||
cargo bench -p crypto_primitives_bench --bench primitives -- --baseline main
|
||||
```
|
||||
|
||||
91
tools/crypto_primitives_bench/benches/primitives.rs
Normal file
91
tools/crypto_primitives_bench/benches/primitives.rs
Normal file
@ -0,0 +1,91 @@
|
||||
//! Criterion microbenchmarks for client/wallet cryptographic primitives.
|
||||
//!
|
||||
//! Measures:
|
||||
//! - `KeyChain::new_os_random` (mnemonic → SSK → NSK/VSK + public keys)
|
||||
//! - `KeyChain::new_mnemonic` (same, but mnemonic exposed)
|
||||
//! - `SharedSecretKey::new` (Diffie-Hellman shared key derivation, the per-recipient cost)
|
||||
//! - `EncryptionScheme::encrypt` / `decrypt` (Account note encryption)
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use key_protocol::key_management::KeyChain;
|
||||
use nssa_core::{
|
||||
Commitment, EncryptionScheme, SharedSecretKey,
|
||||
account::{Account, AccountId},
|
||||
encryption::{EphemeralPublicKey, EphemeralSecretKey},
|
||||
program::PrivateAccountKind,
|
||||
};
|
||||
use rand::{RngCore as _, rngs::OsRng};
|
||||
|
||||
fn bench_keychain(c: &mut Criterion) {
|
||||
let mut g = c.benchmark_group("keychain");
|
||||
g.sample_size(50).noise_threshold(0.05);
|
||||
g.bench_function("new_os_random", |b| b.iter(KeyChain::new_os_random));
|
||||
g.bench_function("new_mnemonic", |b| {
|
||||
b.iter(|| {
|
||||
let (_kc, _mnemonic) = KeyChain::new_mnemonic("");
|
||||
});
|
||||
});
|
||||
g.finish();
|
||||
}
|
||||
|
||||
fn bench_shared_secret_key(c: &mut Criterion) {
|
||||
// One-time setup: recipient's viewing public key (sender side bench).
|
||||
let recipient_kc = KeyChain::new_os_random();
|
||||
let vpk = recipient_kc.viewing_public_key;
|
||||
|
||||
let mut g = c.benchmark_group("shared_secret_key");
|
||||
g.sample_size(50).noise_threshold(0.05);
|
||||
g.bench_function("sender_dh", |b| {
|
||||
b.iter(|| {
|
||||
let mut bytes = [0_u8; 32];
|
||||
OsRng.fill_bytes(&mut bytes);
|
||||
let esk: EphemeralSecretKey = bytes;
|
||||
let _epk = EphemeralPublicKey::from(&esk);
|
||||
SharedSecretKey::new(esk, &vpk)
|
||||
});
|
||||
});
|
||||
g.finish();
|
||||
}
|
||||
|
||||
fn bench_encryption(c: &mut Criterion) {
|
||||
// One-time setup: a fixed Account/Commitment and a SharedSecretKey to bench
|
||||
// encrypt/decrypt over a representative note. ESK gen is excluded from the
|
||||
// measured loop (covered by the SharedSecretKey bench above).
|
||||
let recipient_kc = KeyChain::new_os_random();
|
||||
let vpk = recipient_kc.viewing_public_key;
|
||||
let npk = recipient_kc.nullifier_public_key;
|
||||
let account = Account::default();
|
||||
let account_id = AccountId::for_regular_private_account(&npk, 0);
|
||||
let commitment = Commitment::new(&account_id, &account);
|
||||
let shared = {
|
||||
let mut bytes = [0_u8; 32];
|
||||
OsRng.fill_bytes(&mut bytes);
|
||||
let esk: EphemeralSecretKey = bytes;
|
||||
SharedSecretKey::new(esk, &vpk)
|
||||
};
|
||||
let kind = PrivateAccountKind::Regular(0_u128);
|
||||
let output_index: u32 = 0;
|
||||
|
||||
let mut g = c.benchmark_group("encryption");
|
||||
g.sample_size(50).noise_threshold(0.05);
|
||||
g.bench_function("encrypt", |b| {
|
||||
b.iter(|| EncryptionScheme::encrypt(&account, &kind, &shared, &commitment, output_index));
|
||||
});
|
||||
// One ciphertext for the decrypt bench (encrypt is deterministic given inputs).
|
||||
let ct = EncryptionScheme::encrypt(&account, &kind, &shared, &commitment, output_index);
|
||||
g.bench_function("decrypt", |b| {
|
||||
b.iter(|| EncryptionScheme::decrypt(&ct, &shared, &commitment, output_index));
|
||||
});
|
||||
g.finish();
|
||||
}
|
||||
|
||||
criterion_group! {
|
||||
name = benches;
|
||||
config = Criterion::default()
|
||||
.warm_up_time(Duration::from_secs(2))
|
||||
.measurement_time(Duration::from_secs(10));
|
||||
targets = bench_keychain, bench_shared_secret_key, bench_encryption
|
||||
}
|
||||
criterion_main!(benches);
|
||||
@ -1,175 +0,0 @@
|
||||
//! Cryptographic primitive microbenchmarks used by client/wallet code.
|
||||
//!
|
||||
//! Measures:
|
||||
//! - `KeyChain::new_os_random` (mnemonic → SSK → NSK/VSK + public keys)
|
||||
//! - `KeyChain::new_mnemonic` (same, but mnemonic exposed)
|
||||
//! - `SharedSecretKey::new` (Diffie-Hellman shared key derivation, the per-recipient cost)
|
||||
//! - `EncryptionScheme::encrypt` / `decrypt` (Account note encryption)
|
||||
//!
|
||||
//! Reports best-of-N wall time per operation. No live stack required.
|
||||
|
||||
#![expect(
|
||||
clippy::arithmetic_side_effects,
|
||||
clippy::as_conversions,
|
||||
clippy::cast_precision_loss,
|
||||
clippy::float_arithmetic,
|
||||
clippy::print_stdout,
|
||||
reason = "Bench tool"
|
||||
)]
|
||||
|
||||
use std::{path::PathBuf, time::Instant};
|
||||
|
||||
use anyhow::Result;
|
||||
use key_protocol::key_management::KeyChain;
|
||||
use nssa_core::{
|
||||
Commitment, EncryptionScheme, SharedSecretKey,
|
||||
account::{Account, AccountId},
|
||||
encryption::{EphemeralPublicKey, EphemeralSecretKey},
|
||||
program::PrivateAccountKind,
|
||||
};
|
||||
use rand::{RngCore as _, rngs::OsRng};
|
||||
use serde::Serialize;
|
||||
|
||||
const ITERS: usize = 100;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct OpResult {
|
||||
op: &'static str,
|
||||
iters: usize,
|
||||
best_us: f64,
|
||||
mean_us: f64,
|
||||
stdev_us: f64,
|
||||
}
|
||||
|
||||
fn time<F: FnMut()>(op: &'static str, iters: usize, mut f: F) -> OpResult {
|
||||
// Warmup
|
||||
for _ in 0..2 {
|
||||
f();
|
||||
}
|
||||
let mut samples_ns: Vec<f64> = Vec::with_capacity(iters);
|
||||
for _ in 0..iters {
|
||||
let t = Instant::now();
|
||||
f();
|
||||
samples_ns.push(t.elapsed().as_nanos() as f64);
|
||||
}
|
||||
let best_ns = samples_ns.iter().copied().fold(f64::INFINITY, f64::min);
|
||||
let mean_ns: f64 = samples_ns.iter().sum::<f64>() / iters as f64;
|
||||
let stdev_ns = if iters > 1 {
|
||||
let var: f64 = samples_ns
|
||||
.iter()
|
||||
.map(|s| (s - mean_ns).powi(2))
|
||||
.sum::<f64>()
|
||||
/ (iters - 1) as f64;
|
||||
var.sqrt()
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
OpResult {
|
||||
op,
|
||||
iters,
|
||||
best_us: best_ns / 1_000.0,
|
||||
mean_us: mean_ns / 1_000.0,
|
||||
stdev_us: stdev_ns / 1_000.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut results: Vec<OpResult> = Vec::new();
|
||||
|
||||
results.push(time("KeyChain::new_os_random", ITERS, || {
|
||||
let _kc = KeyChain::new_os_random();
|
||||
}));
|
||||
|
||||
results.push(time("KeyChain::new_mnemonic", ITERS, || {
|
||||
let (_kc, _mnemonic) = KeyChain::new_mnemonic("");
|
||||
}));
|
||||
|
||||
// SharedSecretKey: caller has ephemeral secret, recipient has VSK→VPK.
|
||||
// We bench the SENDER side: derive ephemeral pubkey, then SharedSecretKey::new(scalar, point).
|
||||
let recipient_kc = KeyChain::new_os_random();
|
||||
let vpk = recipient_kc.viewing_public_key;
|
||||
results.push(time("SharedSecretKey::new (sender DH)", ITERS, || {
|
||||
let mut bytes = [0_u8; 32];
|
||||
OsRng.fill_bytes(&mut bytes);
|
||||
let esk: EphemeralSecretKey = bytes;
|
||||
let _epk = EphemeralPublicKey::from(&esk);
|
||||
let _ssk = SharedSecretKey::new(esk, &vpk);
|
||||
}));
|
||||
|
||||
// EncryptionScheme::encrypt / decrypt over a small Account note.
|
||||
let account = Account::default();
|
||||
let account_id = AccountId::new([7; 32]);
|
||||
let commitment = Commitment::new(&account_id, &account);
|
||||
let shared = {
|
||||
let mut bytes = [0_u8; 32];
|
||||
OsRng.fill_bytes(&mut bytes);
|
||||
let esk: EphemeralSecretKey = bytes;
|
||||
SharedSecretKey::new(esk, &vpk)
|
||||
};
|
||||
let kind = PrivateAccountKind::Regular(0_u128);
|
||||
let output_index: u32 = 0;
|
||||
|
||||
let mut produced_ct = None;
|
||||
results.push(time("EncryptionScheme::encrypt", ITERS, || {
|
||||
let ct = EncryptionScheme::encrypt(&account, &kind, &shared, &commitment, output_index);
|
||||
produced_ct = Some(ct);
|
||||
}));
|
||||
let ct = produced_ct.expect("encrypt produced ciphertext");
|
||||
results.push(time("EncryptionScheme::decrypt", ITERS, || {
|
||||
let _decoded = EncryptionScheme::decrypt(&ct, &shared, &commitment, output_index);
|
||||
}));
|
||||
|
||||
print_table(&results);
|
||||
write_json(&results)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_table(results: &[OpResult]) {
|
||||
let ow = results
|
||||
.iter()
|
||||
.map(|r| r.op.len())
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
.max("op".len());
|
||||
let cw = 22_usize;
|
||||
println!(
|
||||
"{:<ow$} {:>6} {:>cw$} {:>cw$} {:>cw$}",
|
||||
"op", "iters", "best_us (ms)", "mean_us (ms)", "stdev_us (ms)",
|
||||
);
|
||||
println!("{}", "-".repeat(ow + 6 + cw * 3 + 8));
|
||||
for r in results {
|
||||
println!(
|
||||
"{:<ow$} {:>6} {:>cw$} {:>cw$} {:>cw$}",
|
||||
r.op,
|
||||
r.iters,
|
||||
fmt_us_ms(r.best_us),
|
||||
fmt_us_ms(r.mean_us),
|
||||
fmt_us_ms(r.stdev_us),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_us_ms(us: f64) -> String {
|
||||
let ms = us / 1_000.0;
|
||||
if ms < 0.01 {
|
||||
format!("{us:.2} (<0.01 ms)")
|
||||
} else {
|
||||
format!("{us:.2} ({ms:.2} ms)")
|
||||
}
|
||||
}
|
||||
|
||||
fn write_json(results: &[OpResult]) -> Result<()> {
|
||||
let workspace_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("..")
|
||||
.join("..")
|
||||
.canonicalize()?;
|
||||
let out_path = workspace_root
|
||||
.join("target")
|
||||
.join("crypto_primitives_bench.json");
|
||||
if let Some(parent) = out_path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
std::fs::write(&out_path, serde_json::to_string_pretty(&results)?)?;
|
||||
println!("\nJSON written to {}", out_path.display());
|
||||
Ok(())
|
||||
}
|
||||
@ -16,6 +16,7 @@ ppe = ["prove"]
|
||||
[dependencies]
|
||||
nssa = { workspace = true }
|
||||
nssa_core = { workspace = true, features = ["host"] }
|
||||
authenticated_transfer_core.workspace = true
|
||||
clock_core.workspace = true
|
||||
token_core.workspace = true
|
||||
amm_core.workspace = true
|
||||
@ -27,3 +28,11 @@ serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
anyhow.workspace = true
|
||||
clap = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion.workspace = true
|
||||
|
||||
[[bench]]
|
||||
name = "verify"
|
||||
harness = false
|
||||
required-features = ["ppe"]
|
||||
|
||||
@ -4,6 +4,8 @@ Per-program Risc0 cycle counts, prover wall time, PPE composition cost, and veri
|
||||
|
||||
## Run
|
||||
|
||||
The binary handles executor cycles, prover wall time, and PPE composition cost:
|
||||
|
||||
```sh
|
||||
# Executor cycles only (fast, ~seconds)
|
||||
cargo run --release -p cycle_bench
|
||||
@ -13,16 +15,30 @@ cargo run --release -p cycle_bench --features prove -- --prove
|
||||
|
||||
# + PPE composition cases (very slow, ~hour)
|
||||
cargo run --release -p cycle_bench --features ppe -- --prove --ppe
|
||||
|
||||
# + verifier microbench (G_verify): generates one PPE receipt, times verify x1000
|
||||
cargo run --release -p cycle_bench --features ppe -- --verify --verify-iters 1000
|
||||
```
|
||||
|
||||
`RISC0_DEV_MODE=1` skips proving entirely and is only useful for the executor path. Combine flags freely; output is printed to stdout and written to `target/cycle_bench.json` for regression diffs.
|
||||
The verifier microbenchmark (`G_verify`) lives in a criterion bench under `benches/verify.rs`:
|
||||
|
||||
```sh
|
||||
# Generates one PPE receipt for auth_transfer Transfer (~minutes of setup),
|
||||
# then times Receipt::verify under criterion's statistical sampler.
|
||||
cargo bench -p cycle_bench --features ppe --bench verify
|
||||
```
|
||||
|
||||
`RISC0_DEV_MODE=1` skips proving entirely and is only useful for the executor path. The bin writes to `target/cycle_bench.json`; criterion writes per-bench estimates under `target/criterion/`.
|
||||
|
||||
## What you'll see
|
||||
|
||||
- Per-program executor cycles and segments, plus exec wall time as `best / mean ± stdev (n=N)`.
|
||||
- With `--prove`: prover total cycles, paging cycles, segments, and wall time.
|
||||
- With `--ppe`: end-to-end `execute_and_prove` wall time and S_agg (the borsh-serialized InnerReceipt length) for one auth-transfer-in-PPE case and a chain-caller depth sweep.
|
||||
- With `--verify`: verify wall time `best / mean ± stdev`, plus `proof_bytes` and `journal_bytes`.
|
||||
- With `--ppe`: end-to-end `execute_and_prove` wall time and `S_agg` (the borsh-serialized InnerReceipt length) for one auth-transfer-in-PPE case and a chain-caller depth sweep.
|
||||
- From the `verify` criterion bench: `ppe/verify_auth_transfer` slope-regression point estimate with 95% CI bounds.
|
||||
|
||||
## Baseline comparison (verify bench)
|
||||
|
||||
```sh
|
||||
# On main:
|
||||
cargo bench -p cycle_bench --features ppe --bench verify -- --save-baseline main
|
||||
# On your branch:
|
||||
cargo bench -p cycle_bench --features ppe --bench verify -- --baseline main
|
||||
```
|
||||
|
||||
47
tools/cycle_bench/benches/verify.rs
Normal file
47
tools/cycle_bench/benches/verify.rs
Normal file
@ -0,0 +1,47 @@
|
||||
//! Criterion bench for `Receipt::verify(PRIVACY_PRESERVING_CIRCUIT_ID)`.
|
||||
//!
|
||||
//! Produces the `G_verify` fee-model parameter. Setup: one full PPE prove of an
|
||||
//! `auth_transfer` Transfer (minutes, runs once outside the timed loop). Measured
|
||||
//! op: `Receipt::verify` over a real PPE receipt.
|
||||
//!
|
||||
//! Run with: `cargo bench -p cycle_bench --features ppe --bench verify`.
|
||||
|
||||
use std::{hint::black_box, time::Duration};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use cycle_bench::ppe::prove_auth_transfer_in_ppe;
|
||||
use nssa::program_methods::PRIVACY_PRESERVING_CIRCUIT_ID;
|
||||
use risc0_zkvm::{InnerReceipt, Receipt};
|
||||
|
||||
fn bench_verify(c: &mut Criterion) {
|
||||
let (output, proof) = prove_auth_transfer_in_ppe().expect("prove auth_transfer in PPE");
|
||||
let journal = output.to_bytes();
|
||||
let proof_bytes = proof.into_inner();
|
||||
let inner: InnerReceipt = borsh::from_slice(&proof_bytes)
|
||||
.context("decode InnerReceipt")
|
||||
.expect("InnerReceipt deserialize");
|
||||
let receipt = Receipt::new(inner, journal);
|
||||
|
||||
// Sanity check before the timed loop.
|
||||
receipt
|
||||
.verify(PRIVACY_PRESERVING_CIRCUIT_ID)
|
||||
.expect("verify sanity check");
|
||||
|
||||
let mut g = c.benchmark_group("ppe");
|
||||
g.sample_size(100)
|
||||
.warm_up_time(Duration::from_secs(2))
|
||||
.measurement_time(Duration::from_secs(15))
|
||||
.noise_threshold(0.05);
|
||||
g.bench_function("verify_auth_transfer", |b| {
|
||||
b.iter(|| {
|
||||
receipt
|
||||
.verify(black_box(PRIVACY_PRESERVING_CIRCUIT_ID))
|
||||
.expect("verify failed mid-loop");
|
||||
});
|
||||
});
|
||||
g.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_verify);
|
||||
criterion_main!(benches);
|
||||
23
tools/cycle_bench/src/lib.rs
Normal file
23
tools/cycle_bench/src/lib.rs
Normal file
@ -0,0 +1,23 @@
|
||||
//! `cycle_bench` library: per-program executor/prover cycle measurement helpers
|
||||
//! shared between the `cycle_bench` binary and the `verify` criterion bench.
|
||||
|
||||
#![expect(
|
||||
clippy::arithmetic_side_effects,
|
||||
clippy::as_conversions,
|
||||
clippy::cast_precision_loss,
|
||||
clippy::float_arithmetic,
|
||||
clippy::print_literal,
|
||||
clippy::print_stdout,
|
||||
reason = "Bench library: stats arithmetic and table printing are bench-style"
|
||||
)]
|
||||
#![cfg_attr(
|
||||
feature = "ppe",
|
||||
expect(
|
||||
clippy::arbitrary_source_item_ordering,
|
||||
clippy::print_stderr,
|
||||
reason = "PPE module: re-export ordering and eprintln progress trip strict lints"
|
||||
)
|
||||
)]
|
||||
|
||||
pub mod ppe;
|
||||
pub mod stats;
|
||||
@ -9,15 +9,11 @@
|
||||
|
||||
#![expect(
|
||||
clippy::arithmetic_side_effects,
|
||||
clippy::as_conversions,
|
||||
clippy::cast_precision_loss,
|
||||
clippy::float_arithmetic,
|
||||
clippy::missing_const_for_fn,
|
||||
clippy::non_ascii_literal,
|
||||
clippy::print_literal,
|
||||
clippy::print_stderr,
|
||||
clippy::print_stdout,
|
||||
clippy::ref_patterns,
|
||||
reason = "Bench tool: matches test-style fixture code"
|
||||
)]
|
||||
|
||||
@ -31,6 +27,7 @@ use clock_core::{
|
||||
CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID,
|
||||
ClockAccountData,
|
||||
};
|
||||
use cycle_bench::{ppe, stats::Stats};
|
||||
use nssa::program_methods::{
|
||||
AMM_ELF, AMM_ID, ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID,
|
||||
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, CLOCK_ELF, CLOCK_ID, TOKEN_ELF,
|
||||
@ -43,12 +40,8 @@ use nssa_core::{
|
||||
};
|
||||
use risc0_zkvm::{ExecutorEnv, default_executor, default_prover};
|
||||
use serde::Serialize;
|
||||
use stats::Stats;
|
||||
use token_core::{TokenDefinition, TokenHolding};
|
||||
|
||||
mod ppe;
|
||||
mod stats;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(about = "Per-program executor and (optionally) prover cycle measurements")]
|
||||
struct Cli {
|
||||
@ -62,16 +55,6 @@ struct Cli {
|
||||
#[arg(long)]
|
||||
ppe: bool,
|
||||
|
||||
/// After running --ppe-style proving once for auth_transfer-in-PPE, time
|
||||
/// `receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID)` over many iterations.
|
||||
/// Produces `G_verify` for the fee model. Requires --features ppe.
|
||||
#[arg(long)]
|
||||
verify: bool,
|
||||
|
||||
/// Iterations for --verify. Default matches the fee-model handoff target.
|
||||
#[arg(long, default_value_t = 1000)]
|
||||
verify_iters: usize,
|
||||
|
||||
/// Iterations for executor wall-time sampling per case. First iter is
|
||||
/// discarded as warmup, remaining N feed the stats.
|
||||
#[arg(long, default_value_t = 5)]
|
||||
@ -428,7 +411,7 @@ fn main() -> Result<()> {
|
||||
AUTHENTICATED_TRANSFER_ELF,
|
||||
AUTHENTICATED_TRANSFER_ID,
|
||||
authenticated_transfer_transfer(),
|
||||
&5_000_u128,
|
||||
&authenticated_transfer_core::Instruction::Transfer { amount: 5_000 },
|
||||
)?,
|
||||
Case::new(
|
||||
"authenticated_transfer",
|
||||
@ -436,7 +419,7 @@ fn main() -> Result<()> {
|
||||
AUTHENTICATED_TRANSFER_ELF,
|
||||
AUTHENTICATED_TRANSFER_ID,
|
||||
authenticated_transfer_init(),
|
||||
&0_u128,
|
||||
&authenticated_transfer_core::Instruction::Initialize,
|
||||
)?,
|
||||
Case::new(
|
||||
"token",
|
||||
@ -532,23 +515,6 @@ fn main() -> Result<()> {
|
||||
ppe::print_table(&ppe_results);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ppe")]
|
||||
let verify_result = if cli.verify {
|
||||
Some(ppe::run_verify(cli.verify_iters)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
#[cfg(not(feature = "ppe"))]
|
||||
let verify_result: Option<ppe::VerifyBenchResult> = {
|
||||
if cli.verify {
|
||||
eprintln!("cycle_bench: --verify requires --features ppe at build time. Ignoring.");
|
||||
}
|
||||
None
|
||||
};
|
||||
if let Some(ref vr) = verify_result {
|
||||
ppe::print_verify(vr);
|
||||
}
|
||||
|
||||
let workspace_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("..")
|
||||
.join("..")
|
||||
@ -560,7 +526,6 @@ fn main() -> Result<()> {
|
||||
let combined = serde_json::json!({
|
||||
"standalone": results,
|
||||
"ppe": ppe_results,
|
||||
"verify": verify_result,
|
||||
});
|
||||
std::fs::write(&out_path, serde_json::to_string_pretty(&combined)?)?;
|
||||
println!("\nJSON written to {}", out_path.display());
|
||||
|
||||
@ -5,24 +5,23 @@
|
||||
//! that wraps the same program in the privacy circuit. Chained-call depth sweep
|
||||
//! uses the `chain_caller` test program (loaded from artifacts/) with N=1, 3, 5, 9.
|
||||
//!
|
||||
//! `run_verify` produces `G_verify` for the fee model: it generates one PPE
|
||||
//! receipt (`auth_transfer` Transfer in PPE) and times `Receipt::verify` over
|
||||
//! `iters` iterations. The proof bytes captured here are also the on-wire
|
||||
//! "outer proof" payload (`S_agg` in the fee model).
|
||||
//! `Receipt::verify(PRIVACY_PRESERVING_CIRCUIT_ID)` timings (the `G_verify` fee-model
|
||||
//! parameter) are measured by the `verify` criterion bench under `benches/verify.rs`,
|
||||
//! which reuses the `prove_auth_transfer_in_ppe` setup helper re-exported below.
|
||||
|
||||
#![allow(
|
||||
dead_code,
|
||||
reason = "Stubs are used when the `ppe` feature is disabled."
|
||||
)]
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::stats::Stats;
|
||||
|
||||
#[cfg(feature = "ppe")]
|
||||
mod ppe_impl;
|
||||
|
||||
#[cfg(feature = "ppe")]
|
||||
pub use ppe_impl::prove_auth_transfer_in_ppe;
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub struct PpeBenchResult {
|
||||
pub label: String,
|
||||
@ -33,20 +32,14 @@ pub struct PpeBenchResult {
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub struct VerifyBenchResult {
|
||||
pub label: String,
|
||||
pub stats: Stats,
|
||||
pub proof_bytes: usize,
|
||||
pub journal_bytes: usize,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ppe"))]
|
||||
pub fn run_all() -> Vec<PpeBenchResult> {
|
||||
#[must_use]
|
||||
pub const fn run_all() -> Vec<PpeBenchResult> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
#[cfg(feature = "ppe")]
|
||||
#[must_use]
|
||||
pub fn run_all() -> Vec<PpeBenchResult> {
|
||||
let mut results = Vec::new();
|
||||
|
||||
@ -61,16 +54,6 @@ pub fn run_all() -> Vec<PpeBenchResult> {
|
||||
results
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ppe"))]
|
||||
pub fn run_verify(_iters: usize) -> Result<VerifyBenchResult> {
|
||||
anyhow::bail!("--verify requires --features ppe at build time")
|
||||
}
|
||||
|
||||
#[cfg(feature = "ppe")]
|
||||
pub fn run_verify(iters: usize) -> Result<VerifyBenchResult> {
|
||||
ppe_impl::run_verify(iters)
|
||||
}
|
||||
|
||||
pub fn print_table(results: &[PpeBenchResult]) {
|
||||
let lw = results
|
||||
.iter()
|
||||
@ -109,14 +92,3 @@ pub fn print_table(results: &[PpeBenchResult]) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_verify(r: &VerifyBenchResult) {
|
||||
println!("\nVerify (G_verify):");
|
||||
println!(" case : {}", r.label);
|
||||
println!(
|
||||
" proof_bytes : {} (borsh InnerReceipt, S_agg)",
|
||||
r.proof_bytes
|
||||
);
|
||||
println!(" journal_bytes : {}", r.journal_bytes);
|
||||
println!(" verify_ms : {}", r.stats);
|
||||
}
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
//! Feature-gated implementation of PPE composition and verify benches.
|
||||
//! Feature-gated implementation of PPE composition benches.
|
||||
//!
|
||||
//! `prove_auth_transfer_in_ppe` is reused by the `verify` criterion bench under
|
||||
//! `benches/verify.rs` (re-exported via `super::prove_auth_transfer_in_ppe`).
|
||||
|
||||
use std::{collections::HashMap, time::Instant};
|
||||
|
||||
@ -6,17 +9,15 @@ use nssa::{
|
||||
execute_and_prove,
|
||||
privacy_preserving_transaction::circuit::{ProgramWithDependencies, Proof},
|
||||
program::Program,
|
||||
program_methods::PRIVACY_PRESERVING_CIRCUIT_ID,
|
||||
};
|
||||
use nssa_core::{
|
||||
InputAccountIdentity, PrivacyPreservingCircuitOutput,
|
||||
account::{Account, AccountId, AccountWithMetadata},
|
||||
program::ProgramId,
|
||||
};
|
||||
use risc0_zkvm::{InnerReceipt, Receipt, serde::to_vec};
|
||||
use risc0_zkvm::serde::to_vec;
|
||||
|
||||
use super::{PpeBenchResult, VerifyBenchResult};
|
||||
use crate::stats::Stats;
|
||||
use super::PpeBenchResult;
|
||||
|
||||
const AUTH_TRANSFER_ID: ProgramId = nssa::program_methods::AUTHENTICATED_TRANSFER_ID;
|
||||
const AUTH_TRANSFER_ELF: &[u8] = nssa::program_methods::AUTHENTICATED_TRANSFER_ELF;
|
||||
@ -50,7 +51,7 @@ pub fn run_auth_transfer_in_ppe() -> PpeBenchResult {
|
||||
}
|
||||
}
|
||||
|
||||
fn prove_auth_transfer_in_ppe() -> anyhow::Result<(PrivacyPreservingCircuitOutput, Proof)> {
|
||||
pub fn prove_auth_transfer_in_ppe() -> anyhow::Result<(PrivacyPreservingCircuitOutput, Proof)> {
|
||||
let program = Program::new(AUTH_TRANSFER_ELF.to_vec())?;
|
||||
let pwd = ProgramWithDependencies::from(program);
|
||||
|
||||
@ -73,8 +74,8 @@ fn prove_auth_transfer_in_ppe() -> anyhow::Result<(PrivacyPreservingCircuitOutpu
|
||||
};
|
||||
let pre_states = vec![sender, recipient];
|
||||
|
||||
let balance_to_move: u128 = 5_000;
|
||||
let instruction_data = to_vec(&balance_to_move)?;
|
||||
let instruction = authenticated_transfer_core::Instruction::Transfer { amount: 5_000 };
|
||||
let instruction_data = to_vec(&instruction)?;
|
||||
|
||||
let account_identities = vec![InputAccountIdentity::Public; pre_states.len()];
|
||||
|
||||
@ -156,39 +157,3 @@ fn prove_chain_caller(
|
||||
&pwd,
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn run_verify(iters: usize) -> anyhow::Result<VerifyBenchResult> {
|
||||
eprintln!("verify: generating PPE receipt for auth_transfer Transfer (~1 prove)");
|
||||
let (output, proof) = prove_auth_transfer_in_ppe()?;
|
||||
let journal = output.to_bytes();
|
||||
let journal_bytes = journal.len();
|
||||
let proof_bytes_vec = proof.into_inner();
|
||||
let proof_bytes = proof_bytes_vec.len();
|
||||
|
||||
let inner: InnerReceipt = borsh::from_slice(&proof_bytes_vec)
|
||||
.map_err(|e| anyhow::anyhow!("InnerReceipt deserialize: {e}"))?;
|
||||
let receipt = Receipt::new(inner, journal);
|
||||
|
||||
// Sanity-check before the timing loop so we don't measure 1000 failures.
|
||||
receipt
|
||||
.verify(PRIVACY_PRESERVING_CIRCUIT_ID)
|
||||
.map_err(|e| anyhow::anyhow!("verify sanity check failed: {e}"))?;
|
||||
|
||||
eprintln!("verify: timing {iters} iters of receipt.verify(...)");
|
||||
let mut samples = Vec::with_capacity(iters);
|
||||
for _ in 0..iters {
|
||||
let started = Instant::now();
|
||||
receipt
|
||||
.verify(PRIVACY_PRESERVING_CIRCUIT_ID)
|
||||
.map_err(|e| anyhow::anyhow!("verify failed mid-loop: {e}"))?;
|
||||
samples.push(started.elapsed().as_secs_f64() * 1_000.0);
|
||||
}
|
||||
let stats = Stats::from_samples(&samples);
|
||||
|
||||
Ok(VerifyBenchResult {
|
||||
label: "auth_transfer Transfer in PPE".to_owned(),
|
||||
stats,
|
||||
proof_bytes,
|
||||
journal_bytes,
|
||||
})
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user