Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Cargo.lock

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

19 changes: 19 additions & 0 deletions frame/zk-verifier/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ All notable changes to this pallet are documented here.

---

## [0.7.0] — 2026-05-22

### Fixed

- `do_verify`: removed `feature = "runtime-benchmarks"` from the `#[cfg]` bypass — the short-circuit `return true` now applies **only** in `#[cfg(test)]` builds. Previously the benchmark was measuring only FRAME overhead (~µs) instead of real Groth16 BN254 pairing cost (~8-10 ms), causing a severe weight underestimation and a potential DoS vector.

### Added

- `src/bench_fixtures/vk_transfer.bin` (488 B) — arkworks-compressed VerifyingKey for the transfer circuit, embedded via `include_bytes!`.
- `src/bench_fixtures/proof_transfer.bin` (128 B) — real Groth16 BN254 proof (A: G1, B: G2, C: G1, compressed).
- `src/bench_fixtures/public_inputs_transfer.bin` (224 B) — 7 × 32-byte LE field elements (merkle_root, nullifier×2, commitment×2, asset_id, fee).
- `scripts/generate-bench-fixtures.mjs` — Node.js script that regenerates the three fixture files using snarkjs + the transfer circuit WASM/zkey and the groth16-proofs WASM pkg. Run after any circuit change.

### Changed

- `benchmarking.rs` `verify_proof`: replaced mock 192-byte proof + 1 dummy input with real fixtures loaded via `include_bytes!`. The benchmark now exercises the full Groth16 pairing computation on BN254 so that generated weights reflect true on-chain cost.

---

## [0.6.0] — 2026-05-14

### Changed
Expand Down
2 changes: 1 addition & 1 deletion frame/zk-verifier/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pallet-zk-verifier"
version = "0.6.0"
version = "0.7.0"
description = "Zero-Knowledge proof verification pallet for Orbinum"
authors = ["Orbinum Team"]
license = "GPL-3.0-or-later"
Expand Down
1 change: 1 addition & 0 deletions frame/zk-verifier/src/bench_fixtures/proof_transfer.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Gix²ZàYƒNRq°çù_‹¾ýòµóvñ™‘‘)qJ©í·UG†OËl{ë,ÄàÁOªÿtföëT£=–à=˜c©8‚’ÝÉÙQGpϳBf€-àt/VŸîOo•» o@ªm=ïŸÍ)Ñ@*’1ñ–ž¾X<¡F$”
Binary file not shown.
Binary file added frame/zk-verifier/src/bench_fixtures/vk_transfer.bin
Binary file not shown.
83 changes: 31 additions & 52 deletions frame/zk-verifier/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,19 @@ mod benchmarks {

// Benchmark configuration constants for FRAME weight generation
const BENCHMARK_VK_SIZE: usize = 768;
const BENCHMARK_PROOF_SIZE: usize = 192;
const BENCHMARK_PUBLIC_INPUTS_COUNT: usize = 1;

/// Generate synthetic Groth16 verification key bytes for benchmarking
/// Real Groth16 fixtures for `verify_proof` (transfer circuit, BN254).
///
/// NOTE: Uses mock data because it doesn't affect weight (storage read path only).
/// Actual weight is measured in storage writes, not format validation.
/// Generated by `scripts/generate-bench-fixtures.mjs`.
/// Regenerate after any circuit change: `node scripts/generate-bench-fixtures.mjs`
const BENCH_VK: &[u8] = include_bytes!("bench_fixtures/vk_transfer.bin");
const BENCH_PROOF: &[u8] = include_bytes!("bench_fixtures/proof_transfer.bin");
const BENCH_PUBLIC_INPUTS: &[u8] = include_bytes!("bench_fixtures/public_inputs_transfer.bin");

/// Generate synthetic Groth16 verification key bytes for storage benchmarks.
///
/// Used by `register_verification_key`, `set_active_version`, `remove_verification_key`
/// and `batch_register_verification_keys` — benchmarks that measure storage cost, not crypto.
fn sample_verification_key() -> Vec<u8> {
// Typical Groth16 VK: 768 bytes (BN254 curve)
// Structure: alpha_g1 (48) + beta_g2 (96) + gamma_g2 (96) + delta_g2 (96) + ic (variable)
Expand All @@ -45,67 +51,40 @@ mod benchmarks {
.collect()
}

/// Generate mock proof data for benchmarking
///
/// WARNING: This data does NOT pass real cryptographic verification.
/// The benchmark measures FRAME overhead (storage, events, conversions).
/// Real cryptographic verification timing is intentionally out of scope here.
fn sample_proof_data() -> (Vec<u8>, Vec<Vec<u8>>) {
// Groth16 proof: 192 bytes (3 curve points)
let proof_bytes = vec![0x42; BENCHMARK_PROOF_SIZE];

// Public inputs with deterministic pattern
let public_inputs = (0..BENCHMARK_PUBLIC_INPUTS_COUNT)
.map(|i| {
let mut input = vec![0u8; 32];
input[0] = i as u8;
input
})
.collect();

(proof_bytes, public_inputs)
}

/// Benchmark for `verify_proof`
///
/// ⚠️ LIMITATION: This benchmark uses mock data that does NOT pass cryptographic verification.
/// It only measures FRAME overhead (storage reads, mappers, events).
///
/// Real Groth16 verification time (~8-10ms) is NOT included here.
///
/// TODO: Integrate real proofs when circuits are in production.
/// Uses a real Groth16 proof over BN254 generated from the transfer circuit so that
/// the full pairing computation (~8-10 ms) is included in the measured weight.
/// Fixtures are embedded via `include_bytes!` and can be regenerated with:
/// `node scripts/generate-bench-fixtures.mjs`
#[benchmark]
fn verify_proof() {
let circuit_id = CircuitId::TRANSFER;

// Get mock VK and proof data (do NOT verify cryptographically)
let vk_bytes = sample_verification_key();
let (proof_bytes, public_inputs) = sample_proof_data();

// Setup: seed storage with verification key + active version (genesis-like state)
// Seed storage with the real VK so `do_verify` runs the actual Groth16 pairing.
let vk_info = VerificationKeyInfo {
key_data: vk_bytes.clone().try_into().unwrap(),
key_data: BENCH_VK.to_vec().try_into().unwrap(),
system: ProofSystem::Groth16,
registered_at: frame_system::Pallet::<T>::block_number(),
};
VerificationKeys::<T>::insert(circuit_id, 1, vk_info);

// Set active version for verify_proof
crate::pallet::ActiveCircuitVersion::<T>::insert(circuit_id, 1);

// Create proof with valid bytes and matching public inputs
let proof: BoundedVec<u8, T::MaxProofSize> = proof_bytes
.clone()
.try_into()
.expect("benchmark proof bytes must fit MaxProofSize");

// Use T::MaxPublicInputs for the outer BoundedVec to avoid truncation issues
let inputs: BoundedVec<BoundedVec<u8, ConstU32<32>>, T::MaxPublicInputs> = public_inputs
.iter()
.map(|input| BoundedVec::truncate_from(input.to_vec()))
.collect::<Vec<_>>()
// 128-byte compressed Groth16 proof (A: G1, B: G2, C: G1 on BN254)
let proof: BoundedVec<u8, T::MaxProofSize> = BENCH_PROOF
.to_vec()
.try_into()
.expect("benchmark public inputs must fit MaxPublicInputs");
.expect("bench proof must fit MaxProofSize");

// 7 public inputs (merkle_root, nullifier×2, commitment×2, asset_id, fee)
// each encoded as 32-byte little-endian BN254 scalar
let inputs: BoundedVec<BoundedVec<u8, ConstU32<32>>, T::MaxPublicInputs> =
BENCH_PUBLIC_INPUTS
.chunks_exact(32)
.map(|c| BoundedVec::truncate_from(c.to_vec()))
.collect::<Vec<_>>()
.try_into()
.expect("bench public inputs must fit MaxPublicInputs");

let caller: T::AccountId = whitelisted_caller();

Expand Down
8 changes: 5 additions & 3 deletions frame/zk-verifier/src/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,17 @@ pub fn verify<T: Config>(

/// Actual cryptographic verification.
///
/// Always returns `true` in benchmarking and test builds (no real VK available).
/// Returns `true` unconditionally in **test** builds (no real VK/proof available in unit tests).
/// In **benchmarking** builds the full Groth16 pairing computation runs so that
/// `verify_proof` weights reflect real on-chain cost.
fn do_verify(vk_bytes: &[u8], proof_bytes: &[u8], raw_inputs: Vec<[u8; 32]>) -> bool {
#[cfg(any(feature = "runtime-benchmarks", test))]
#[cfg(test)]
{
let _ = (vk_bytes, proof_bytes, raw_inputs);
true
}

#[cfg(not(any(feature = "runtime-benchmarks", test)))]
#[cfg(not(test))]
{
use orbinum_zk_verifier::{Groth16Verifier, Proof, PublicInputs, VerifyingKey};

Expand Down
Loading