diff --git a/Cargo.lock b/Cargo.lock index 59e4eee6..e553e0be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7291,7 +7291,6 @@ dependencies = [ "log", "orbinum-runtime", "orbinum-zk-core 0.5.0", - "orbinum-zk-verifier", "pallet-account-mapping-rpc", "pallet-account-mapping-runtime-api", "pallet-shielded-pool-rpc", @@ -7299,7 +7298,8 @@ dependencies = [ "pallet-transaction-payment", "pallet-transaction-payment-rpc", "pallet-transaction-payment-rpc-runtime-api", - "pallet-zk-verifier", + "pallet-zk-verifier-rpc", + "pallet-zk-verifier-runtime-api", "parity-scale-codec", "sc-basic-authorship", "sc-chain-spec", @@ -7353,7 +7353,6 @@ dependencies = [ "frame-system-benchmarking", "frame-system-rpc-runtime-api", "hex-literal", - "orbinum-zk-verifier", "pallet-account-mapping", "pallet-account-mapping-runtime-api", "pallet-aura", @@ -7378,6 +7377,7 @@ dependencies = [ "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-zk-verifier", + "pallet-zk-verifier-runtime-api", "parity-scale-codec", "polkadot-runtime-common", "scale-info", @@ -7437,7 +7437,7 @@ dependencies = [ [[package]] name = "orbinum-zk-verifier" -version = "0.6.2" +version = "0.7.0" dependencies = [ "ark-bn254", "ark-ec 0.5.0", @@ -7452,6 +7452,7 @@ dependencies = [ "orbinum-zk-core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec", "scale-info", + "sha2 0.10.9", ] [[package]] @@ -7489,7 +7490,7 @@ dependencies = [ [[package]] name = "pallet-account-mapping" -version = "0.1.0" +version = "0.2.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -8326,9 +8327,8 @@ dependencies = [ [[package]] name = "pallet-zk-verifier" -version = "0.3.0" +version = "0.4.0" dependencies = [ - "criterion", "frame-benchmarking", "frame-support", "frame-system", @@ -8343,6 +8343,31 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-zk-verifier-rpc" +version = "0.1.0" +dependencies = [ + "hex", + "jsonrpsee", + "pallet-zk-verifier-runtime-api", + "serde", + "sp-api", + "sp-blockchain", + "sp-runtime", +] + +[[package]] +name = "pallet-zk-verifier-runtime-api" +version = "0.1.0" +dependencies = [ + "pallet-zk-verifier", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-std", +] + [[package]] name = "parity-bip39" version = "2.0.1" diff --git a/Cargo.toml b/Cargo.toml index 2d3c752f..28b28e35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,8 @@ members = [ "frame/account-mapping", "frame/account-mapping/runtime-api", "frame/account-mapping/rpc", + "frame/zk-verifier/runtime-api", + "frame/zk-verifier/rpc", "frame/evm/precompile/account-mapping", "client/api", "client/consensus", @@ -250,6 +252,8 @@ pallet-shielded-pool = { path = "frame/shielded-pool", default-features = false pallet-shielded-pool-rpc = { path = "frame/shielded-pool/rpc" } pallet-shielded-pool-runtime-api = { path = "frame/shielded-pool/runtime-api", default-features = false } pallet-zk-verifier = { path = "frame/zk-verifier", default-features = false } +pallet-zk-verifier-rpc = { path = "frame/zk-verifier/rpc" } +pallet-zk-verifier-runtime-api = { path = "frame/zk-verifier/runtime-api", default-features = false } # Frontier Utility precompile-utils = { path = "precompiles", default-features = false } diff --git a/frame/shielded-pool/src/mock.rs b/frame/shielded-pool/src/mock.rs index adde68b6..63053790 100644 --- a/frame/shielded-pool/src/mock.rs +++ b/frame/shielded-pool/src/mock.rs @@ -37,14 +37,12 @@ parameter_types! { pub const MinShieldAmount: u128 = 100; pub const MaxProofSize: u32 = 256; pub const MaxPublicInputs: u32 = 10; - pub const MaxVerificationKeySize: u32 = 2048; + pub const RequestExpiration: u64 = 1000; } impl pallet_zk_verifier::Config for Test { - type AdminOrigin = frame_system::EnsureRoot; type MaxProofSize = MaxProofSize; type MaxPublicInputs = MaxPublicInputs; - type MaxVerificationKeySize = MaxVerificationKeySize; type WeightInfo = pallet_zk_verifier::weights::SubstrateWeight; } @@ -52,8 +50,6 @@ impl pallet_zk_verifier::Config for Test { /// /// ⚠️ WARNING: This mock bypasses all ZK proof validation! /// Use only for testing business logic, not cryptographic correctness. -/// -/// For testing proof validation errors, use `FailingZkVerifier` instead. pub struct MockZkVerifier; impl ZkVerifierPort for MockZkVerifier { @@ -135,94 +131,6 @@ impl ZkVerifierPort for MockZkVerifier { } } -/// Mock ZK verifier that always fails - for testing error paths -/// -/// **IMPORTANT**: This mock is currently NOT used by any tests (dead code). -/// It exists to demonstrate how to test invalid proof scenarios. -/// -/// To use this mock, create a separate test configuration: -/// ```ignore -/// construct_runtime!( -/// pub enum TestWithFailingVerifier { -/// System: frame_system, -/// Balances: pallet_balances, -/// ShieldedPool: pallet_shielded_pool, -/// } -/// ); -/// -/// impl pallet_shielded_pool::Config for TestWithFailingVerifier { -/// type ZkVerifier = FailingZkVerifier; // ← Use this instead of MockZkVerifier -/// // ... other config -/// } -/// -/// #[test] -/// fn unshield_fails_with_invalid_proof() { -/// new_test_ext_failing().execute_with(|| { -/// // Any proof will fail with FailingZkVerifier -/// assert_noop!(ShieldedPool::unshield(...), Error::InvalidProof); -/// }); -/// } -/// ``` -/// -/// See: `frame/shielded-pool/docs/MOCK_SYSTEM_ANALYSIS.md` for detailed analysis -/// See: `frame/shielded-pool/src/tests/integration/invalid_proof_tests.rs` for demo tests -#[allow(dead_code)] // Intentionally unused - template for negative tests -pub struct FailingZkVerifier; - -impl ZkVerifierPort for FailingZkVerifier { - fn verify_transfer_proof( - _proof: &[u8], - _merkle_root: &[u8; 32], - _nullifiers: &[[u8; 32]], - _commitments: &[[u8; 32]], - _version: Option, - ) -> Result { - // Always return false for testing invalid proofs - Ok(false) - } - - fn verify_unshield_proof( - _proof: &[u8], - _merkle_root: &[u8; 32], - _nullifier: &[u8; 32], - _amount: u128, - _recipient: &[u8; 32], - _asset_id: u32, - _version: Option, - ) -> Result { - // Always return false for testing invalid proofs - Ok(false) - } - - fn verify_disclosure_proof( - _proof: &[u8], - _public_signals: &[u8], - _version: Option, - ) -> Result { - // Always return false for testing invalid proofs - Ok(false) - } - - fn batch_verify_disclosure_proofs( - _proofs: &[sp_std::vec::Vec], - _public_signals: &[sp_std::vec::Vec], - _version: Option, - ) -> Result { - // Always return false for testing invalid proofs - Ok(false) - } - - fn verify_private_link_proof( - _proof: &[u8], - _commitment: &[u8; 32], - _call_hash_fe: &[u8; 32], - _version: Option, - ) -> Result { - // Always return false for testing invalid proofs - Ok(false) - } -} - impl pallet_shielded_pool::Config for Test { type Currency = Balances; type ZkVerifier = MockZkVerifier; @@ -258,11 +166,3 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ext.execute_with(|| System::set_block_number(1)); ext } - -/// Advance to specified block number -#[allow(dead_code)] -pub fn run_to_block(n: u64) { - while System::block_number() < n { - System::set_block_number(System::block_number() + 1); - } -} diff --git a/frame/zk-verifier/Cargo.toml b/frame/zk-verifier/Cargo.toml index d167f4d8..53695077 100644 --- a/frame/zk-verifier/Cargo.toml +++ b/frame/zk-verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-zk-verifier" -version = "0.3.0" +version = "0.4.0" description = "Zero-Knowledge proof verification pallet for Orbinum" authors = ["Orbinum Team"] license = "GPL-3.0-or-later" @@ -38,14 +38,9 @@ orbinum-zk-core = { path = "../../primitives/zk-core", default-features = false orbinum-zk-verifier = { path = "../../primitives/zk-verifier", default-features = false } [dev-dependencies] -criterion = { version = "0.5", default-features = false, features = ["html_reports"] } orbinum-zk-core = { path = "../../primitives/zk-core", default-features = false } orbinum-zk-verifier = { path = "../../primitives/zk-verifier", default-features = false } -[[bench]] -name = "groth16_verify" -harness = false - [features] default = ["std"] std = [ diff --git a/frame/zk-verifier/README.md b/frame/zk-verifier/README.md index 1ee65009..ab796671 100644 --- a/frame/zk-verifier/README.md +++ b/frame/zk-verifier/README.md @@ -1,247 +1,75 @@ # pallet-zk-verifier -A FRAME pallet for verifying Zero-Knowledge proofs on-chain with Clean Architecture. - -## Overview - -This pallet provides efficient Groth16 proof verification for privacy-preserving transactions in the Orbinum network. It manages verification keys per circuit and tracks verification statistics. - -## Key Features - -- **Groth16 Verification**: Sub-10ms proof verification on-chain -- **Circuit Management**: Register and manage verification keys per circuit -- **Statistics Tracking**: Monitor verification counts and success rates -- **Clean Architecture**: Domain-driven design with clear separation of concerns -- **Extensible**: Support for multiple proof systems (Groth16, PLONK, Halo2) - -## Supported Circuits - -| Circuit | Description | Public Inputs | -|---------|-------------|---------------| -| `transfer` | Private transfer (2 inputs → 2 outputs) | `merkle_root`, `nullifier×2`, `commitment×2` | -| `unshield` | Withdraw from shielded pool | `merkle_root`, `nullifier`, `amount`, `recipient`, `asset_id` | -| `disclosure` | Selective disclosure of note data | `commitment`, `revealed_value`, `revealed_asset_id`, `revealed_owner_hash` | -| `private_link` | ZK proof of cross-chain address ownership | `commitment`, `call_hash_fe` | - -## Public API - -The pallet exposes a single trait for integration with other pallets: - -```rust -pub trait ZkVerifierPort { - fn verify_transfer_proof( - proof: &[u8], - merkle_root: &[u8; 32], - nullifiers: &[[u8; 32]], - commitments: &[[u8; 32]], - version: Option, - ) -> Result; - - fn verify_unshield_proof( - proof: &[u8], - merkle_root: &[u8; 32], - nullifier: &[u8; 32], - amount: u128, - recipient: &[u8; 32], - asset_id: u32, - version: Option, - ) -> Result; - - fn verify_disclosure_proof( - proof: &[u8], - public_signals: &[u8], - version: Option, - ) -> Result; - - fn batch_verify_disclosure_proofs( - proofs: &[Vec], - public_signals: &[Vec], - version: Option, - ) -> Result; - - /// Verifica una prueba ZK de private link (cross-chain identity). - /// - `commitment`: Poseidon(Poseidon(chain_id_fe, address_fe), blinding_fe) - /// - `call_hash_fe`: hash del RuntimeCall que se está autorizando - fn verify_private_link_proof( - proof: &[u8], - commitment: &[u8; 32], - call_hash_fe: &[u8; 32], - version: Option, - ) -> Result; -} -``` - -## External Dependencies - -### Required Primitives - -This pallet depends on the following cryptographic primitives: - -- **[orbinum-zk-verifier](../../primitives/zk-verifier)** (v0.4.0): Groth16 verification implementation - — incluye VKs embebidas para `transfer`, `unshield`, `disclosure` y `private_link` -- **[orbinum-zk-core](../../primitives/zk-core)** (v0.5.0): ZK primitives (Commitment, Nullifier, Note) +FRAME pallet for on-chain verification of Groth16 proofs in Orbinum. -### FRAME Dependencies +## Status -- `frame-support`: Pallet infrastructure -- `frame-system`: System primitives -- `sp-runtime`: Runtime types -- `sp-std`: Substrate standard library +- MVP in active development. +- Production runtime currently verifies Groth16 proofs only. +- PLONK and Halo2 exist as domain enums for forward compatibility, not as active runtime verification paths. -### Development Dependencies +## What this pallet does -- **criterion** (v0.5): Performance benchmarking -- **hex-literal**: Test data encoding +- Stores verification keys by `(circuit_id, version)`. +- Tracks active version per circuit. +- Verifies generic proofs through the `verify_proof` extrinsic. +- Exposes `ZkVerifierPort` for pallet-to-pallet verification flows: + - `verify_transfer_proof` + - `verify_unshield_proof` + - `verify_disclosure_proof` + - `batch_verify_disclosure_proofs` + - `verify_private_link_proof` +- Tracks per-version verification statistics. -## Usage - -### Runtime Integration - -Add to your `runtime/Cargo.toml`: - -```toml -[dependencies] -pallet-zk-verifier = { path = "../frame/zk-verifier", default-features = false } - -[features] -std = [ - "pallet-zk-verifier/std", -] -``` +## Circuit IDs -Add to your `runtime/src/lib.rs`: +- `1`: transfer +- `2`: unshield +- `3`: shield (reserved) +- `4`: disclosure +- `5`: private_link -```rust -impl pallet_zk_verifier::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = pallet_zk_verifier::weights::SubstrateWeight; -} +## Storage -construct_runtime! { - pub enum Runtime { - // ... - ZkVerifier: pallet_zk_verifier, - } -} -``` - -### Using in Other Pallets - -```rust -use pallet_zk_verifier::ZkVerifierPort; - -// Verify a shielded transfer proof -let is_valid = T::ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, -)?; - -// Verify a private link proof (pallet-account-mapping) -let is_valid = T::ZkVerifier::verify_private_link_proof( - &proof, - &commitment, - &call_hash_fe, - None, -)?; -``` +- `VerificationKeys`: verification key registry by circuit and version. +- `ActiveCircuitVersion`: currently active version per circuit. +- `VerificationStats`: counters per `(circuit, version)`. ## Extrinsics -### `register_verification_key` - -Register a verification key for a specific circuit. - -**Parameters:** -- `circuit_id`: Unique identifier for the circuit -- `vk_bytes`: Serialized verification key -- `proof_system`: Proof system type (Groth16, PLONK, Halo2) - -**Required:** Root origin - -### `verify_proof` - -Verify a zero-knowledge proof. +- `register_verification_key` (root only) +- `set_active_version` (root only) +- `remove_verification_key` (root only) +- `verify_proof` (signed origin) -**Parameters:** -- `circuit_id`: Circuit identifier -- `proof`: Serialized proof -- `public_inputs`: Public inputs for verification - -**Returns:** Event indicating success or failure +## Architecture -## Storage +The pallet keeps a layered structure: -- `VerificationKeys`: Registered verification keys per circuit -- `Statistics`: Verification statistics per circuit (total, successful, failed) +- `src/domain`: entities, value objects, service traits, repository traits. +- `src/application`: use-case orchestration and command/error DTOs. +- `src/infrastructure`: FRAME repositories, primitive adapters, Groth16 implementation. +- `src/presentation`: extrinsic execution and error mapping helpers. -## Events +## Dependencies -- `VerificationKeyRegistered`: New verification key registered -- `ProofVerified`: Proof successfully verified -- `ProofVerificationFailed`: Proof verification failed +- `orbinum-zk-verifier`: Groth16 verification primitives. +- `orbinum-zk-core`: shared ZK primitives. +- FRAME: `frame-support`, `frame-system`, `sp-runtime`, `sp-std`. -## Benchmarking +## Testing -The pallet includes two types of benchmarks: +Run pallet tests from `node/`: -### Development (Criterion) ```bash -cd frame/zk-verifier -./benches/run.sh criterion-fast -``` - -### Production (FRAME Weights) -```bash -cargo build --release --features runtime-benchmarks -./benches/run.sh frame -``` - -See [BENCHMARKING.md](BENCHMARKING.md) for details. - -## Architecture - -The pallet follows Clean Architecture principles: - +cargo test -p pallet-zk-verifier ``` -├── domain/ # Business logic (ports, entities) -├── application/ # Use cases -├── infrastructure/ # FRAME integration, adapters -│ ├── adapters.rs # VK adapters: Transfer, Unshield, Disclosure, PrivateLink -│ └── services/ -│ └── groth16_verifier.rs # Implementación de ZkVerifierPort -└── presentation/ # Extrinsics, events -``` - -### VK Adapters - -Cada circuito tiene su propio adapter en `infrastructure/adapters.rs` que carga -la verification key embebida desde `primitives/zk-verifier`: - -| Adapter | Circuito | Método | -|---------|----------|--------| -| `TransferVkAdapter` | transfer | `get_transfer_vk()` | -| `UnshieldVkAdapter` | unshield | `get_unshield_vk()` | -| `DisclosureVkAdapter` | disclosure | `get_disclosure_vk()` | -| `PrivateLinkVkAdapter` | private_link | `get_private_link_vk()` | - -## Changelog - -### v0.3.0 -- Implementación de producción de `verify_private_link_proof` con `PrivateLinkVkAdapter` -- Añadido `PrivateLinkVkAdapter` en `infrastructure/adapters.rs` -- Export de `get_private_link_vk` desde `primitives/zk-verifier` -### v0.2.0 -- Soporte para `verify_disclosure_proof` y `batch_verify_disclosure_proofs` -- Circuito `disclosure` con VK embebida +## Notes and limitations -### v0.1.0 -- Verificación Groth16 inicial (`transfer`, `unshield`) -- Clean Architecture, adapters, ProofValidator trait +- Verification behavior in `runtime-benchmarks`/test builds may differ from production cryptographic execution. +- Batch disclosure verification enforces a fixed max batch size to limit runtime resource usage. ## License -Dual-licensed: Apache 2.0 / GPL-3.0-or-later +Dual-licensed under Apache-2.0 and GPL-3.0-or-later. diff --git a/frame/zk-verifier/benches/README.md b/frame/zk-verifier/benches/README.md deleted file mode 100644 index e23e0d6c..00000000 --- a/frame/zk-verifier/benches/README.md +++ /dev/null @@ -1,287 +0,0 @@ -# Benchmarks: pallet-zk-verifier - -Benchmark directory for measuring cryptographic and on-chain performance. - -## 📁 Structure - -``` -benches/ -├── config.rs # Shared configuration (Criterion + FRAME) -├── groth16_verify.rs # Criterion benchmarks (off-chain) -├── run.sh # Execution script -└── README.md # This documentation -``` - -## 🚀 Quick Start - -```bash -# Fast benchmarks (development) -./benches/run.sh fast - -# Standard benchmarks (regular) -./benches/run.sh standard - -# Production benchmarks (accuracy) -./benches/run.sh production - -# FRAME benchmarks (generate weights.rs) -./benches/run.sh frame -``` - -## 📊 Benchmark Types - -### 1. Criterion Benchmarks (Off-chain) - -**File:** `groth16_verify.rs` -**Purpose:** Measure pure cryptographic performance without FRAME overhead - -**Available benchmarks:** -- `single_verification` - Time for single proof verification -- `batch_verification` - Throughput with 1, 5, 10, 20, 50 proofs -- `vk_operations` - Verification key parsing (transfer, unshield) -- `proof_operations` - Proof parsing -- `public_inputs_scaling` - Impact of input count (1, 2, 4, 8, 16) -- `e2e_workflow` - Complete pipeline (parse + verify) - -**Configurations:** -- `fast`: 10 samples, 2s measurement (rapid development) -- `standard`: 100 samples, 10s measurement (regular) -- `production`: 200 samples, 30s measurement (maximum accuracy) - -**Execute:** -```bash -# With default configuration -cargo bench --package pallet-zk-verifier - -# With custom configuration -CRITERION_CONFIG=production cargo bench --package pallet-zk-verifier - -# Specific benchmark -cargo bench --package pallet-zk-verifier -- single_verification - -# View HTML report -open target/criterion/report/index.html -``` - -### 2. FRAME Benchmarks (On-chain) - -**File:** `../src/benchmarking.rs` -**Purpose:** Calculate weights for on-chain fees - -**Available benchmarks:** -- `register_verification_key` - Store VK in storage -- `remove_verification_key` - Remove VK from storage -- `verify_proof` - Verify proof (⚠️ uses mock data) - -**Execute:** -```bash -# Build with runtime-benchmarks -cargo build --release --features runtime-benchmarks - -# Generate weights.rs -./benches/run.sh frame - -# Or manual: -./target/release/orbinum-node benchmark pallet \ - --chain dev \ - --pallet pallet_zk_verifier \ - --extrinsic '*' \ - --steps 50 \ - --repeat 20 \ - --output frame/zk-verifier/src/weights.rs -``` - -## 🔧 Configuration - -### Module `config.rs` - -Shared configuration for both benchmark types: - -```rust -// Criterion presets -CriterionConfig::fast() // 10 samples, 2s -CriterionConfig::standard() // 100 samples, 10s -CriterionConfig::production() // 200 samples, 30s - -// Test sizes -BenchmarkSizes::BATCH_SIZES // [1, 5, 10, 20, 50] -BenchmarkSizes::PUBLIC_INPUT_COUNTS // [1, 2, 4, 8, 16] - -// Test data -test_data::mock_vk_bytes(768) -test_data::mock_proof_bytes() -test_data::mock_public_inputs(count) -``` - -### Environment Variables - -```bash -# Criterion configuration -export CRITERION_CONFIG=production - -# Detailed output -export RUST_LOG=info - -# Colorize output -export CARGO_TERM_COLOR=always -``` - -## 📈 Expected Metrics - -### Criterion (Off-chain) - -``` -single_verification/groth16_verify ~8-10ms -batch_verification/5 ~40-50ms (8-10ms/proof) -vk_operations/parse_transfer_vk ~100-200μs -proof_operations/parse_proof ~50-100μs -public_inputs_scaling/16 ~10-20μs -e2e_workflow/full_verification_pipeline ~10-12ms -``` - -### FRAME (On-chain) - -``` -register_verification_key ~7ms + 3 DB writes -remove_verification_key ~10ms + 4 DB writes -verify_proof ~13ms + 3 DB writes (⚠️ without real crypto) -``` - -⚠️ **Note:** Current `verify_proof` weights do NOT include real cryptographic verification time (~8-10ms) because they use mock data. - -## 🔄 Typical Workflow - -### Development (fast iteration) - -```bash -# 1. Make code changes -vim src/infrastructure/services/groth16_verifier.rs - -# 2. Quick benchmark -./benches/run.sh fast - -# 3. View results -./benches/run.sh report -``` - -### Pre-Release (validation) - -```bash -# 1. Save baseline -./benches/run.sh save - -# 2. Make changes -git checkout feature-optimization - -# 3. Compare -./benches/run.sh compare - -# 4. If improved, generate weights -./benches/run.sh frame -``` - -### Production (deployment) - -```bash -# 1. Run on reference hardware (not laptop) -ssh production-benchmark-server - -# 2. Production benchmarks -CRITERION_CONFIG=production ./benches/run.sh production - -# 3. Generate final weights -./benches/run.sh frame - -# 4. Commit updated weights.rs -git add src/weights.rs -git commit -m "chore: update benchmark weights for v0.x.x" -``` - -## 📊 Results Interpretation - -### Criterion HTML Report - -``` -target/criterion/report/index.html -├── single_verification/ -│ ├── report/index.html # Graphs and statistics -│ ├── base/estimates.json # Raw data -│ └── ... -└── ... -``` - -**Key metrics:** -- **Mean**: Average execution time -- **Std Dev**: Standard deviation (lower = more consistent) -- **Median**: Middle value (more robust than mean) -- **MAD**: Median Absolute Deviation - -**What to look for:** -- Mean < 10ms for single verification ✅ -- Std Dev < 5% of mean ✅ -- Outliers < 2% of samples ✅ - -### FRAME weights.rs - -```rust -fn verify_proof() -> Weight { - Weight::from_parts(13_000_000, 11684) - // ^^^^^^^^^^ ^^^^^^ - // ref_time proof_size - // (picoseconds) (bytes) -} -``` - -**Components:** -- `ref_time`: Execution time (13ms = 13,000,000 picoseconds) -- `proof_size`: Data read from DB (11,684 bytes) - -## ⚠️ Current Limitations - -1. **FRAME `verify_proof` uses mock data** - - Only measures FRAME overhead (~13ms) - - Does NOT measure real Groth16 verification (~8-10ms) - - Expected total weight: ~21-23ms - -2. **Criterion uses real VKs but mock proofs** - - VKs: Hardcoded from `fp-zk-verifier` (transfer, unshield) - - Proofs: Mock data (don't verify cryptographically) - - TODO: Use real proofs when circuits are ready - -## 🛠️ Troubleshooting - -### Benchmarks too slow - -```bash -# Verify you're in release mode -cargo bench --package pallet-zk-verifier -- --profile-time 5 - -# Reduce sample size temporarily -CRITERION_CONFIG=fast ./benches/run.sh fast -``` - -### Inconsistent results - -```bash -# Ensure no heavy processes are running -top - -# Run with nice (lower priority to other processes) -nice -n -20 cargo bench --package pallet-zk-verifier -``` - -### FRAME benchmarks fail - -```bash -# Verify feature is enabled -cargo build --release --features runtime-benchmarks - -# Verify node exists -ls -lh target/release/orbinum-node -``` - -## 📚 References - -- [Criterion.rs Book](https://bheisler.github.io/criterion.rs/book/) -- [FRAME Benchmarking](https://docs.substrate.io/test/benchmark/) -- [../README.md](../README.md) - Pallet documentation diff --git a/frame/zk-verifier/benches/config.rs b/frame/zk-verifier/benches/config.rs deleted file mode 100644 index a13bf3b9..00000000 --- a/frame/zk-verifier/benches/config.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! Benchmark configuration and shared utilities -//! -//! This module provides common configuration for both: -//! - Criterion benchmarks (off-chain, development) -//! - FRAME benchmarks (on-chain, production) - -use criterion::Criterion; -use std::time::Duration; - -/// Criterion benchmark configuration presets -pub struct CriterionConfig; - -impl CriterionConfig { - /// Fast configuration for quick feedback during development - pub fn fast() -> Criterion { - Criterion::default() - .sample_size(10) - .measurement_time(Duration::from_secs(2)) - .warm_up_time(Duration::from_secs(1)) - } - - /// Standard configuration for regular benchmarking - pub fn standard() -> Criterion { - Criterion::default() - .sample_size(100) - .measurement_time(Duration::from_secs(10)) - .warm_up_time(Duration::from_secs(3)) - } - - /// Production configuration for accurate measurements - pub fn production() -> Criterion { - Criterion::default() - .sample_size(200) - .measurement_time(Duration::from_secs(30)) - .warm_up_time(Duration::from_secs(5)) - .significance_level(0.01) - .confidence_level(0.99) - } - - /// Batch verification configuration (longer measurement time) - pub fn batch() -> Criterion { - Criterion::default() - .sample_size(50) - .measurement_time(Duration::from_secs(15)) - .warm_up_time(Duration::from_secs(3)) - } - - /// Memory/parsing configuration (short, high sample count) - pub fn memory() -> Criterion { - Criterion::default() - .sample_size(200) - .measurement_time(Duration::from_secs(5)) - .warm_up_time(Duration::from_secs(1)) - } -} - -/// Benchmark data sizes for testing scalability -#[allow(dead_code)] -pub struct BenchmarkSizes; - -#[allow(dead_code)] -impl BenchmarkSizes { - /// Batch sizes for testing parallel verification - pub const BATCH_SIZES: &'static [usize] = &[1, 5, 10, 20, 50]; - - /// Public input counts for testing input scaling - pub const PUBLIC_INPUT_COUNTS: &'static [usize] = &[1, 2, 4, 8, 16]; - - /// Verification key sizes (in bytes) for different circuits - pub const VK_SIZES: &'static [usize] = &[768, 1024, 2048, 4096]; -} - -/// Test data generation for benchmarks -#[allow(dead_code)] -pub mod test_data { - /// Generate deterministic verification key mock data - pub fn mock_vk_bytes(size: usize) -> Vec { - // Pattern: alternating 0x01, 0x02, 0x03, 0x04 - (0..size).map(|i| ((i % 4) + 1) as u8).collect() - } - - /// Generate deterministic proof mock data - pub fn mock_proof_bytes() -> Vec { - // Groth16 proof: 192 bytes (3 curve points) - vec![0x42; 192] - } - - /// Generate deterministic public inputs - pub fn mock_public_inputs(count: usize) -> Vec> { - (0..count) - .map(|i| { - let mut input = vec![0u8; 32]; - input[0] = i as u8; // Make each input unique - input - }) - .collect() - } - - /// Generate single public input (32 bytes) - pub fn mock_single_public_input() -> Vec { - vec![0x01; 32] - } -} - -/// FRAME benchmark configuration constants -#[allow(dead_code)] -pub mod frame_config { - /// Number of steps for FRAME benchmarks - pub const STEPS: u32 = 50; - - /// Number of repetitions for FRAME benchmarks - pub const REPEAT: u32 = 20; - - /// Typical VK size for FRAME benchmarks (matches mock data) - pub const VK_SIZE: usize = 768; - - /// Typical proof size for FRAME benchmarks - pub const PROOF_SIZE: usize = 192; - - /// Default number of public inputs for FRAME benchmarks - pub const PUBLIC_INPUTS_COUNT: usize = 1; -} - -#[cfg(test)] -mod tests { - #[allow(unused_imports)] - use super::test_data; - - #[test] - fn test_mock_data_sizes() { - assert_eq!(test_data::mock_vk_bytes(768).len(), 768); - assert_eq!(test_data::mock_proof_bytes().len(), 192); - assert_eq!(test_data::mock_single_public_input().len(), 32); - } - - #[test] - fn test_mock_public_inputs_unique() { - let inputs = test_data::mock_public_inputs(5); - assert_eq!(inputs.len(), 5); - // Verify each input is unique - for (i, input) in inputs.iter().enumerate() { - assert_eq!(input[0], i as u8); - } - } -} diff --git a/frame/zk-verifier/benches/groth16_verify.rs b/frame/zk-verifier/benches/groth16_verify.rs deleted file mode 100644 index 0a44e4ae..00000000 --- a/frame/zk-verifier/benches/groth16_verify.rs +++ /dev/null @@ -1,459 +0,0 @@ -//! Criterion benchmarks for pallet-zk-verifier -//! -//! ## Purpose -//! -//! These benchmarks measure the pure cryptographic performance of the Groth16 verifier, -//! without FRAME overhead. They are useful for: -//! - Optimizing cryptographic implementation -//! - Comparing different verification strategies -//! - Measuring the impact of code changes -//! -//! ## Run -//! -//! ```bash -//! # All benchmarks -//! cargo bench --package pallet-zk-verifier -//! -//! # Specific benchmark -//! cargo bench --package pallet-zk-verifier -- single_verification -//! -//! # With custom configuration -//! CRITERION_CONFIG=production cargo bench --package pallet-zk-verifier -//! -//! # View HTML results -//! open target/criterion/report/index.html -//! ``` -//! -//! ## Structure -//! -//! 1. **Single Verification**: Proof verification time -//! 2. **Batch Verification**: Throughput with multiple proofs -//! 3. **VK Parsing**: VK parsing/deserialization time -//! 4. **Proof Parsing**: Proof parsing time -//! 5. **Public Inputs Scaling**: Impact of number of inputs -//! 6. **Memory Usage**: Memory footprint -//! -//! ## Configuration -//! -//! The `config` module provides Criterion presets: -//! - `fast()`: Fast development (10 samples, 2s) -//! - `standard()`: Regular (100 samples, 10s) -//! - `production()`: Production (200 samples, 30s) - -use criterion::{BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main}; - -mod config; -use config::{BenchmarkSizes, CriterionConfig}; - -// ============================================================================ -// Real Test Data (using hardcoded VKs from primitives) -// ============================================================================ - -use orbinum_zk_verifier::{ - domain::value_objects::{Proof, PublicInputs, VerifyingKey}, - infrastructure::{ - Groth16Verifier, - storage::verification_keys::{ - get_disclosure_vk_bytes, get_transfer_vk_bytes, get_unshield_vk_bytes, - }, - }, -}; - -/// Generate real test data using hardcoded VKs -fn generate_real_test_data() -> (Vec, Vec, Vec<[u8; 32]>) { - // Real VK from transfer circuit (hardcoded in primitives) - let vk_bytes = get_transfer_vk_bytes().to_vec(); - - // For proof and public inputs, we use mock data with valid format - // TODO: When we have real proofs from circuits, use those - let proof_bytes = config::test_data::mock_proof_bytes(); - let public_inputs = vec![ - config::test_data::mock_single_public_input() - .try_into() - .unwrap(), - ]; - - (vk_bytes, proof_bytes, public_inputs) -} - -/// Generate test data for disclosure circuit (4 public inputs) -fn generate_disclosure_test_data() -> (Vec, Vec, Vec<[u8; 32]>) { - // Real VK from disclosure circuit (hardcoded in primitives) - let vk_bytes = get_disclosure_vk_bytes().to_vec(); - - // For proof, use mock data with valid format - // TODO: When we have real disclosure proofs, use those - let proof_bytes = config::test_data::mock_proof_bytes(); - - // Disclosure has 4 public inputs: - // 1. commitment (32 bytes) - // 2. revealed_value (32 bytes, u64 padded) - // 3. revealed_asset_id (32 bytes, u32 padded) - // 4. revealed_owner_hash (32 bytes) - let mut public_inputs = Vec::with_capacity(4); - - // 1. commitment - let mut commitment = [0u8; 32]; - commitment[0] = 0x01; - public_inputs.push(commitment); - - // 2. revealed_value (u64 = 1000 in little-endian, padded to 32 bytes) - let mut revealed_value = [0u8; 32]; - revealed_value[..8].copy_from_slice(&1000u64.to_le_bytes()); - public_inputs.push(revealed_value); - - // 3. revealed_asset_id (u32 = 1 in little-endian, padded to 32 bytes) - let mut revealed_asset_id = [0u8; 32]; - revealed_asset_id[..4].copy_from_slice(&1u32.to_le_bytes()); - public_inputs.push(revealed_asset_id); - - // 4. revealed_owner_hash - let mut owner_hash = [0u8; 32]; - owner_hash[0] = 0x0A; - public_inputs.push(owner_hash); - - (vk_bytes, proof_bytes, public_inputs) -} - -// ============================================================================ -// 1. Single Verification Benchmark -// ============================================================================ - -fn bench_single_verification(c: &mut Criterion) { - let mut group = c.benchmark_group("single_verification"); - group.sample_size(100); - - let (vk_bytes, proof_bytes, public_input_bytes) = generate_real_test_data(); - - // Pre-parse structures (outside benchmark) - let vk = VerifyingKey::new(vk_bytes.clone()); - let proof = Proof::new(proof_bytes.clone()); - let public_inputs = PublicInputs::new(public_input_bytes.clone()); - - group.bench_function("groth16_verify", |b| { - b.iter(|| { - // Only measure verification time - let _result = Groth16Verifier::verify( - black_box(&vk), - black_box(&public_inputs), - black_box(&proof), - ); - // Note: May fail because they are mock data, but we measure the time - }); - }); - - // Benchmark with parsing included - group.bench_function("groth16_verify_with_parsing", |b| { - b.iter(|| { - let vk = VerifyingKey::new(black_box(vk_bytes.clone())); - let proof = Proof::new(black_box(proof_bytes.clone())); - let public_inputs = PublicInputs::new(black_box(public_input_bytes.clone())); - let _result = Groth16Verifier::verify(&vk, &public_inputs, &proof); - }); - }); - - group.finish(); -} - -// ============================================================================ -// 2. Batch Verification Benchmark -// ============================================================================ - -fn bench_batch_verification(c: &mut Criterion) { - let mut group = c.benchmark_group("batch_verification"); - group.sample_size(50); - - let (vk_bytes, proof_bytes, public_input_bytes) = generate_real_test_data(); - let vk = VerifyingKey::new(vk_bytes); - - for &batch_size in BenchmarkSizes::BATCH_SIZES { - // Pre-generate all proofs - let proofs: Vec<_> = (0..batch_size) - .map(|_| { - let proof = Proof::new(proof_bytes.clone()); - let public_inputs = PublicInputs::new(public_input_bytes.clone()); - (proof, public_inputs) - }) - .collect(); - - group.throughput(Throughput::Elements(batch_size as u64)); - group.bench_with_input( - BenchmarkId::from_parameter(batch_size), - &batch_size, - |b, _| { - b.iter(|| { - for (proof, public_inputs) in &proofs { - let _result = Groth16Verifier::verify( - black_box(&vk), - black_box(public_inputs), - black_box(proof), - ); - } - }); - }, - ); - } - - group.finish(); -} - -// ============================================================================ -// 3. Verification Key Operations -// ============================================================================ - -fn bench_vk_operations(c: &mut Criterion) { - let mut group = c.benchmark_group("vk_operations"); - group.sample_size(200); - - // Test with both VKs (transfer and unshield) - let transfer_vk_bytes = get_transfer_vk_bytes().to_vec(); - let unshield_vk_bytes = get_unshield_vk_bytes().to_vec(); - - group.bench_function("parse_transfer_vk", |b| { - b.iter(|| { - let _vk = VerifyingKey::new(black_box(transfer_vk_bytes.clone())); - }); - }); - - group.bench_function("parse_unshield_vk", |b| { - b.iter(|| { - let _vk = VerifyingKey::new(black_box(unshield_vk_bytes.clone())); - }); - }); - - // Memory footprint - let vk = VerifyingKey::new(transfer_vk_bytes.clone()); - group.bench_function("vk_memory_footprint", |b| { - b.iter(|| black_box(std::mem::size_of_val(&vk))); - }); - - group.finish(); -} - -// ============================================================================ -// 4. Proof Operations -// ============================================================================ - -fn bench_proof_operations(c: &mut Criterion) { - let mut group = c.benchmark_group("proof_operations"); - group.sample_size(200); - - let (_, proof_bytes, _) = generate_real_test_data(); - - group.bench_function("parse_proof", |b| { - b.iter(|| { - let _proof = Proof::new(black_box(proof_bytes.clone())); - }); - }); - - let proof = Proof::new(proof_bytes.clone()); - group.bench_function("proof_memory_footprint", |b| { - b.iter(|| black_box(std::mem::size_of_val(&proof))); - }); - - group.finish(); -} - -// ============================================================================ -// 5. Public Inputs Scaling -// ============================================================================ - -fn bench_public_inputs_scaling(c: &mut Criterion) { - let mut group = c.benchmark_group("public_inputs_scaling"); - group.sample_size(100); - - for &num_inputs in BenchmarkSizes::PUBLIC_INPUT_COUNTS { - let inputs = config::test_data::mock_public_inputs(num_inputs) - .into_iter() - .map(|v| v.try_into().unwrap()) - .collect::>(); - - group.throughput(Throughput::Elements(num_inputs as u64)); - group.bench_with_input( - BenchmarkId::from_parameter(num_inputs), - &num_inputs, - |b, _| { - b.iter(|| { - let _public_inputs = PublicInputs::new(black_box(inputs.clone())); - }); - }, - ); - } - - group.finish(); -} - -// ============================================================================ -// 7. Disclosure Circuit Benchmarks -// ============================================================================ - -fn bench_disclosure_single_verification(c: &mut Criterion) { - let mut group = c.benchmark_group("disclosure_single_verification"); - group.sample_size(100); - - let (vk_bytes, proof_bytes, public_input_bytes) = generate_disclosure_test_data(); - - // Pre-parse structures (outside benchmark) - let vk = VerifyingKey::new(vk_bytes.clone()); - let proof = Proof::new(proof_bytes.clone()); - let public_inputs = PublicInputs::new(public_input_bytes.clone()); - - group.bench_function("disclosure_verify", |b| { - b.iter(|| { - // Only measure verification time for disclosure circuit - let _result = Groth16Verifier::verify( - black_box(&vk), - black_box(&public_inputs), - black_box(&proof), - ); - // Note: May fail because they are mock data, but we measure the time - }); - }); - - // Benchmark with parsing included - group.bench_function("disclosure_verify_with_parsing", |b| { - b.iter(|| { - let vk = VerifyingKey::new(black_box(vk_bytes.clone())); - let proof = Proof::new(black_box(proof_bytes.clone())); - let public_inputs = PublicInputs::new(black_box(public_input_bytes.clone())); - let _result = Groth16Verifier::verify(&vk, &public_inputs, &proof); - }); - }); - - group.finish(); -} - -fn bench_disclosure_batch_verification(c: &mut Criterion) { - let mut group = c.benchmark_group("disclosure_batch_verification"); - group.sample_size(50); - - let (vk_bytes, proof_bytes, public_input_bytes) = generate_disclosure_test_data(); - let vk = VerifyingKey::new(vk_bytes); - - for &batch_size in BenchmarkSizes::BATCH_SIZES { - // Pre-generate all disclosure proofs - let proofs: Vec<_> = (0..batch_size) - .map(|_| { - let proof = Proof::new(proof_bytes.clone()); - let public_inputs = PublicInputs::new(public_input_bytes.clone()); - (proof, public_inputs) - }) - .collect(); - - group.throughput(Throughput::Elements(batch_size as u64)); - group.bench_with_input( - BenchmarkId::from_parameter(batch_size), - &batch_size, - |b, _| { - b.iter(|| { - for (proof, public_inputs) in &proofs { - let _result = Groth16Verifier::verify( - black_box(&vk), - black_box(public_inputs), - black_box(proof), - ); - } - }); - }, - ); - } - - group.finish(); -} - -fn bench_disclosure_public_inputs_construction(c: &mut Criterion) { - let mut group = c.benchmark_group("disclosure_public_inputs"); - group.sample_size(200); - - // Benchmark constructing public inputs from raw disclosure data - group.bench_function("construct_from_76_bytes", |b| { - b.iter(|| { - // Simulate receiving 76 bytes: commitment(32) + value(8) + asset_id(4) + owner_hash(32) - let mut signals = Vec::with_capacity(76); - signals.extend_from_slice(&[1u8; 32]); // commitment - signals.extend_from_slice(&1000u64.to_le_bytes()); // revealed_value - signals.extend_from_slice(&1u32.to_le_bytes()); // revealed_asset_id - signals.extend_from_slice(&[0xAu8; 32]); // revealed_owner_hash - - // Convert to 4 padded inputs (as done in verify_disclosure_proof) - let mut commitment = [0u8; 32]; - commitment.copy_from_slice(&signals[0..32]); - - let mut revealed_value = [0u8; 32]; - revealed_value[..8].copy_from_slice(&signals[32..40]); - - let mut revealed_asset_id = [0u8; 32]; - revealed_asset_id[..4].copy_from_slice(&signals[40..44]); - - let mut owner_hash = [0u8; 32]; - owner_hash.copy_from_slice(&signals[44..76]); - - let inputs = vec![commitment, revealed_value, revealed_asset_id, owner_hash]; - let _public_inputs = PublicInputs::new(black_box(inputs)); - }); - }); - - group.finish(); -} - -// ============================================================================ -// 8. End-to-End Workflow Benchmark -// ============================================================================ - -fn bench_e2e_workflow(c: &mut Criterion) { - let mut group = c.benchmark_group("e2e_workflow"); - group.sample_size(50); - - let (vk_bytes, proof_bytes, public_input_bytes) = generate_real_test_data(); - - group.bench_function("full_verification_pipeline", |b| { - b.iter(|| { - // Simulate the full flow a user would perform - // 1. Parse VK (may be cached in production) - let vk = VerifyingKey::new(black_box(vk_bytes.clone())); - - // 2. Parse proof (always new) - let proof = Proof::new(black_box(proof_bytes.clone())); - - // 3. Parse public inputs (always new) - let public_inputs = PublicInputs::new(black_box(public_input_bytes.clone())); - - // 4. Verify - let _result = Groth16Verifier::verify(&vk, &public_inputs, &proof); - }); - }); - - group.finish(); -} - -// ============================================================================ -// Criterion Configuration & Main -// ============================================================================ - -fn get_criterion_config() -> Criterion { - // Read configuration from env var or use standard - match std::env::var("CRITERION_CONFIG").as_deref() { - Ok("fast") => CriterionConfig::fast(), - Ok("production") => CriterionConfig::production(), - Ok("batch") => CriterionConfig::batch(), - Ok("memory") => CriterionConfig::memory(), - _ => CriterionConfig::standard(), - } -} - -criterion_group! { - name = benches; - config = get_criterion_config(); - targets = - bench_single_verification, - bench_batch_verification, - bench_vk_operations, - bench_proof_operations, - bench_public_inputs_scaling, - bench_disclosure_single_verification, - bench_disclosure_batch_verification, - bench_disclosure_public_inputs_construction, - bench_e2e_workflow -} - -criterion_main!(benches); diff --git a/frame/zk-verifier/benches/run.sh b/frame/zk-verifier/benches/run.sh deleted file mode 100755 index 48c9da19..00000000 --- a/frame/zk-verifier/benches/run.sh +++ /dev/null @@ -1,262 +0,0 @@ -#!/bin/bash -# Benchmark execution scripts for pallet-zk-verifier -# -# Usage: -# ./benches/run.sh [command] -# -# Commands: -# criterion-fast - Quick Criterion benchmarks (10 samples, 2s) -# criterion-standard - Regular Criterion benchmarks (100 samples, 10s) [default] -# criterion-prod - Production Criterion benchmarks (200 samples, 30s) -# frame - FRAME benchmarks (generates weights.rs) -# clean - Clean benchmark results -# report - Open Criterion HTML report -# compare - Compare with baseline -# all - Run all benchmark suites - -set -e - -PALLET_NAME="pallet-zk-verifier" -FRAME_DIR="frame/zk-verifier" -BENCHMARK_DIR="target/criterion" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -print_header() { - echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${BLUE} $1${NC}" - echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" -} - -print_success() { - echo -e "${GREEN}✓ $1${NC}" -} - -print_error() { - echo -e "${RED}✗ $1${NC}" - exit 1 -} - -print_info() { - echo -e "${YELLOW}ℹ $1${NC}" -} - -print_warning() { - echo -e "${YELLOW}⚠ $1${NC}" -} - -# ============================================================================ -# Criterion Benchmarks (Development - Off-chain) -# ============================================================================ - -run_criterion_fast() { - print_header "Criterion Benchmarks: Fast (Development)" - print_info "Configuration: 10 samples, 2s measurement" - - cd "$PROJECT_ROOT" - export CRITERION_CONFIG=fast - cargo bench --package "$PALLET_NAME" --bench groth16_verify - - print_success "Benchmarks completed" - print_info "View results: ./benches/run.sh report" -} - -run_criterion_standard() { - print_header "Criterion Benchmarks: Standard" - print_info "Configuration: 100 samples, 10s measurement" - - cd "$PROJECT_ROOT" - export CRITERION_CONFIG=standard - cargo bench --package "$PALLET_NAME" --bench groth16_verify - - print_success "Benchmarks completed" - print_info "View results: ./benches/run.sh report" -} - -run_criterion_production() { - print_header "Criterion Benchmarks: Production (Accuracy)" - print_info "Configuration: 200 samples, 30s measurement" - print_warning "This process may take 10-15 minutes" - - cd "$PROJECT_ROOT" - export CRITERION_CONFIG=production - cargo bench --package "$PALLET_NAME" --bench groth16_verify - - print_success "Benchmarks completed" - print_info "View results: ./benches/run.sh report" -} - -# ============================================================================ -# FRAME Benchmarks (Production - On-chain Weights) -# ============================================================================ - -run_frame_benchmarks() { - print_header "FRAME Benchmarks: Generating Weights (Production)" - print_warning "IMPORTANT: Run on reference hardware, NOT on laptop" - print_info "These benchmarks generate weights to calculate on-chain fees" - - cd "$PROJECT_ROOT" - - # Verify we're in the correct directory - if [ ! -f "Cargo.toml" ]; then - print_error "Cargo.toml not found. Run from frame/zk-verifier/" - fi - - # 1. Build with runtime-benchmarks - print_info "Step 1/2: Building with runtime-benchmarks feature..." - cargo build --release --features runtime-benchmarks - - if [ ! -f "../../../target/release/orbinum-node" ]; then - print_error "orbinum-node binary not found" - fi - - # 2. Run FRAME benchmarks - print_info "Step 2/2: Running FRAME benchmarks..." - - ../../../target/release/orbinum-node benchmark pallet \ - --chain dev \ - --pallet "$PALLET_NAME" \ - --extrinsic '*' \ - --steps 50 \ - --repeat 20 \ - --output src/weights.rs \ - --template ../../../scripts/frame-weight-template.hbs - - print_success "Weights generated at: src/weights.rs" - print_warning "REVIEW generated weights before committing!" -} - -# ============================================================================ -# Utilities -# ============================================================================ - -clean_benchmarks() { - print_header "Cleaning Benchmark Results" - - cd "$PROJECT_ROOT" - - if [ -d "$BENCHMARK_DIR" ]; then - rm -rf "$BENCHMARK_DIR" - print_success "Criterion results removed" - fi - - if [ -f "src/weights.rs.bak" ]; then - rm -f src/weights.rs.bak - print_success "Weights backup removed" - fi -} - -open_report() { - cd "$PROJECT_ROOT" - - if [ ! -d "$BENCHMARK_DIR" ]; then - print_error "No results found. Run first: ./benches/run.sh criterion-standard" - fi - - print_info "Opening HTML report..." - - if command -v open &> /dev/null; then - # macOS - open "$BENCHMARK_DIR/report/index.html" - elif command -v xdg-open &> /dev/null; then - # Linux - xdg-open "$BENCHMARK_DIR/report/index.html" - else - print_info "Open manually: $BENCHMARK_DIR/report/index.html" - fi -} - -compare_baseline() { - print_header "Comparing with Baseline" - - cd "$PROJECT_ROOT" - cargo bench --package "$PALLET_NAME" -- --save-baseline current - - print_success "Baseline saved as 'current'" - print_info "To compare: cargo bench -- --baseline current" -} - -run_all() { - print_header "Running All Benchmarks" - - run_criterion_standard - echo "" - - print_info "FRAME benchmarks skipped (require reference hardware)" - print_info "To run: ./benches/run.sh frame" -} - -show_help() { - cat << EOF -${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC} - ${GREEN}Benchmark Runner - pallet-zk-verifier${NC} -${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC} - -${YELLOW}CRITERION BENCHMARKS${NC} (Development - Cryptographic Performance) - ${GREEN}criterion-fast${NC} Fast (10 samples, 2s) - Development - ${GREEN}criterion-standard${NC} Standard (100 samples, 10s) - Regular - ${GREEN}criterion-prod${NC} Production (200 samples, 30s) - Accuracy - -${YELLOW}FRAME BENCHMARKS${NC} (Production - Generate On-chain Weights) - ${GREEN}frame${NC} Generate weights.rs (reference HW only) - -${YELLOW}UTILITIES${NC} - ${GREEN}clean${NC} Clean results - ${GREEN}report${NC} Open Criterion HTML report - ${GREEN}compare${NC} Compare with baseline - ${GREEN}all${NC} Run all Criterion benchmarks - -${YELLOW}EXAMPLES${NC} - ./benches/run.sh criterion-fast # Fast development - ./benches/run.sh frame # Generate weights - -${YELLOW}MORE INFO${NC} - benches/README.md -EOF -} - -# ============================================================================ -# Main -# ============================================================================ - -case "${1:-criterion-standard}" in - criterion-fast) - run_criterion_fast - ;; - criterion-standard|standard) - run_criterion_standard - ;; - criterion-prod|production) - run_criterion_production - ;; - frame) - run_frame_benchmarks - ;; - clean) - clean_benchmarks - ;; - report) - open_report - ;; - compare) - compare_baseline - ;; - all) - run_all - ;; - help|--help|-h) - show_help - ;; - *) - print_error "Unknown command: $1" - echo "" - show_help - ;; -esac diff --git a/frame/zk-verifier/rpc/Cargo.toml b/frame/zk-verifier/rpc/Cargo.toml new file mode 100644 index 00000000..02dea83b --- /dev/null +++ b/frame/zk-verifier/rpc/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "pallet-zk-verifier-rpc" +version = "0.1.0" +authors = { workspace = true } +edition = { workspace = true } +license = "Apache-2.0" + +[dependencies] +hex = { workspace = true } +jsonrpsee = { workspace = true, features = ["server", "macros", "client"] } +pallet-zk-verifier-runtime-api = { path = "../runtime-api" } +serde = { workspace = true, features = ["derive"] } +sp-api = { workspace = true } +sp-blockchain = { workspace = true } +sp-runtime = { workspace = true } diff --git a/frame/zk-verifier/rpc/src/lib.rs b/frame/zk-verifier/rpc/src/lib.rs new file mode 100644 index 00000000..2964ee21 --- /dev/null +++ b/frame/zk-verifier/rpc/src/lib.rs @@ -0,0 +1,95 @@ +use jsonrpsee::{core::RpcResult, proc_macros::rpc, types::ErrorObjectOwned}; +use pallet_zk_verifier_runtime_api::{CircuitVersionInfo, ZkVerifierRuntimeApi}; +use serde::{Deserialize, Serialize}; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_runtime::traits::Block as BlockT; +use std::sync::Arc; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct VkVersionHashResponse { + pub version: u32, + pub vk_hash: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CircuitVersionInfoResponse { + pub circuit_id: u32, + pub active_version: u32, + pub supported_versions: Vec, + pub vk_hashes: Vec, +} + +fn to_response(info: CircuitVersionInfo) -> CircuitVersionInfoResponse { + CircuitVersionInfoResponse { + circuit_id: info.circuit_id, + active_version: info.active_version, + supported_versions: info.supported_versions, + vk_hashes: info + .vk_hashes + .into_iter() + .map(|item| VkVersionHashResponse { + version: item.version, + vk_hash: format!("0x{}", hex::encode(item.vk_hash)), + }) + .collect(), + } +} + +#[rpc(client, server)] +pub trait ZkVerifierApi { + #[method(name = "zkVerifier_getCircuitVersionInfo")] + fn get_circuit_version_info( + &self, + circuit_id: u32, + ) -> RpcResult>; + + #[method(name = "zkVerifier_getAllCircuitVersions")] + fn get_all_circuit_versions(&self) -> RpcResult>; +} + +pub struct ZkVerifier { + client: Arc, + _marker: std::marker::PhantomData, +} + +impl ZkVerifier { + pub fn new(client: Arc) -> Self { + Self { + client, + _marker: Default::default(), + } + } +} + +impl ZkVerifierApiServer for ZkVerifier +where + B: BlockT, + C: ProvideRuntimeApi + HeaderBackend + 'static, + C::Api: ZkVerifierRuntimeApi, +{ + fn get_circuit_version_info( + &self, + circuit_id: u32, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let best_block = self.client.info().best_hash; + + let info = api + .get_circuit_version_info(best_block, circuit_id) + .map_err(|e| ErrorObjectOwned::owned(1, format!("Runtime error: {e}"), None::<()>))?; + + Ok(info.map(to_response)) + } + + fn get_all_circuit_versions(&self) -> RpcResult> { + let api = self.client.runtime_api(); + let best_block = self.client.info().best_hash; + + let info = api + .get_all_circuit_versions(best_block) + .map_err(|e| ErrorObjectOwned::owned(1, format!("Runtime error: {e}"), None::<()>))?; + + Ok(info.into_iter().map(to_response).collect()) + } +} diff --git a/frame/zk-verifier/runtime-api/Cargo.toml b/frame/zk-verifier/runtime-api/Cargo.toml new file mode 100644 index 00000000..9e490ea4 --- /dev/null +++ b/frame/zk-verifier/runtime-api/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "pallet-zk-verifier-runtime-api" +version = "0.1.0" +authors = { workspace = true } +edition = { workspace = true } +license = "Apache-2.0" + +[dependencies] +pallet-zk-verifier = { path = "..", default-features = false } +scale-codec = { workspace = true, default-features = false, features = ["derive"] } +scale-info = { workspace = true, default-features = false, features = ["derive"] } +serde = { workspace = true, default-features = false, features = ["derive", "alloc"], optional = true } +sp-api = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } + +[features] +default = ["std"] +std = [ + "scale-codec/std", + "scale-info/std", + "serde/std", + "pallet-zk-verifier/std", + "sp-api/std", + "sp-std/std", +] diff --git a/frame/zk-verifier/runtime-api/src/lib.rs b/frame/zk-verifier/runtime-api/src/lib.rs new file mode 100644 index 00000000..e25d1a00 --- /dev/null +++ b/frame/zk-verifier/runtime-api/src/lib.rs @@ -0,0 +1,45 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::vec::Vec; +use scale_info::TypeInfo; + +#[derive( + scale_codec::Encode, + scale_codec::Decode, + Clone, + PartialEq, + Eq, + Debug, + TypeInfo +)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct VkVersionHash { + pub version: u32, + pub vk_hash: [u8; 32], +} + +#[derive( + scale_codec::Encode, + scale_codec::Decode, + Clone, + PartialEq, + Eq, + Debug, + TypeInfo +)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct CircuitVersionInfo { + pub circuit_id: u32, + pub active_version: u32, + pub supported_versions: Vec, + pub vk_hashes: Vec, +} + +sp_api::decl_runtime_apis! { + pub trait ZkVerifierRuntimeApi { + fn get_circuit_version_info(circuit_id: u32) -> Option; + fn get_all_circuit_versions() -> Vec; + } +} diff --git a/frame/zk-verifier/src/application/commands.rs b/frame/zk-verifier/src/application/commands.rs index 71b58fe8..4c82fd8a 100644 --- a/frame/zk-verifier/src/application/commands.rs +++ b/frame/zk-verifier/src/application/commands.rs @@ -1,24 +1,8 @@ //! Application commands - DTOs for use case inputs -use crate::domain::value_objects::{CircuitId, ProofSystem}; +use crate::domain::value_objects::CircuitId; use alloc::vec::Vec; -/// Command to register a verification key -#[derive(Clone, Debug)] -pub struct RegisterVkCommand { - pub circuit_id: CircuitId, - pub version: u32, - pub data: Vec, - pub system: ProofSystem, -} - -/// Command to remove a verification key -#[derive(Clone, Copy, Debug)] -pub struct RemoveVkCommand { - pub circuit_id: CircuitId, - pub version: Option, // None for all? or just one? Usually one. -} - /// Command to verify a proof #[derive(Clone, Debug)] pub struct VerifyProofCommand { @@ -27,10 +11,3 @@ pub struct VerifyProofCommand { pub proof: Vec, pub public_inputs: Vec>, } - -/// Command to set the active version for a circuit -#[derive(Clone, Copy, Debug)] -pub struct SetActiveVersionCommand { - pub circuit_id: CircuitId, - pub version: u32, -} diff --git a/frame/zk-verifier/src/application/use_cases/mod.rs b/frame/zk-verifier/src/application/use_cases/mod.rs index fecf94e6..90d12ef9 100644 --- a/frame/zk-verifier/src/application/use_cases/mod.rs +++ b/frame/zk-verifier/src/application/use_cases/mod.rs @@ -1,11 +1,5 @@ //! Use cases - Application business logic orchestration -mod register_vk; -mod remove_vk; -mod set_active_version; mod verify_proof; -pub use register_vk::RegisterVerificationKeyUseCase; -pub use remove_vk::RemoveVerificationKeyUseCase; -pub use set_active_version::SetActiveVersionUseCase; pub use verify_proof::VerifyProofUseCase; diff --git a/frame/zk-verifier/src/application/use_cases/register_vk.rs b/frame/zk-verifier/src/application/use_cases/register_vk.rs deleted file mode 100644 index eb841bf9..00000000 --- a/frame/zk-verifier/src/application/use_cases/register_vk.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Register verification key use case - -use crate::{ - application::{commands::RegisterVkCommand, errors::ApplicationError}, - domain::{ - entities::VerificationKey, repositories::VerificationKeyRepository, services::VkValidator, - }, -}; -use alloc::boxed::Box; - -/// Use case for registering a verification key -pub struct RegisterVerificationKeyUseCase { - repository: R, - validator: Box, -} - -impl RegisterVerificationKeyUseCase { - /// Create a new use case instance - pub fn new(repository: R, validator: Box) -> Self { - Self { - repository, - validator, - } - } - - /// Execute the use case - pub fn execute(&self, command: RegisterVkCommand) -> Result<(), ApplicationError> { - // 1. Check if this specific version already exists - if self.repository.exists(command.circuit_id, command.version) { - return Err(ApplicationError::CircuitAlreadyExists); - } - - // 2. Create domain entity with validation - let vk = - VerificationKey::new(command.data, command.system).map_err(ApplicationError::Domain)?; - - // 3. Validate with domain service - self.validator - .validate(&vk) - .map_err(ApplicationError::Domain)?; - - // 4. Persist to repository - self.repository - .save(command.circuit_id, command.version, vk) - .map_err(|_| ApplicationError::RepositoryError)?; - - // 5. If this is the first version, set it as active - // We ignore error here as it might fail if we can't find it immediately (unlikely) - if self - .repository - .get_active_version(command.circuit_id) - .is_err() - { - let _ = self - .repository - .set_active_version(command.circuit_id, command.version); - } - - Ok(()) - } -} diff --git a/frame/zk-verifier/src/application/use_cases/remove_vk.rs b/frame/zk-verifier/src/application/use_cases/remove_vk.rs deleted file mode 100644 index 4ae304cf..00000000 --- a/frame/zk-verifier/src/application/use_cases/remove_vk.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Remove verification key use case - -use crate::{ - application::{commands::RemoveVkCommand, errors::ApplicationError}, - domain::repositories::VerificationKeyRepository, -}; - -/// Use case for removing a verification key -pub struct RemoveVerificationKeyUseCase { - repository: R, -} - -impl RemoveVerificationKeyUseCase { - /// Create a new use case instance - pub fn new(repository: R) -> Self { - Self { repository } - } - - /// Execute the use case - pub fn execute(&self, command: RemoveVkCommand) -> Result<(), ApplicationError> { - // 1. Get version to remove - let version = match command.version { - Some(v) => v, - None => self - .repository - .get_active_version(command.circuit_id) - .map_err(|_| ApplicationError::CircuitNotFound)?, - }; - - // 2. Check if this specific version exists - if !self.repository.exists(command.circuit_id, version) { - return Err(ApplicationError::CircuitNotFound); - } - - // 3. Delete from repository - self.repository - .delete(command.circuit_id, version) - .map_err(|_| ApplicationError::RepositoryError)?; - - Ok(()) - } -} diff --git a/frame/zk-verifier/src/application/use_cases/set_active_version.rs b/frame/zk-verifier/src/application/use_cases/set_active_version.rs deleted file mode 100644 index c42ddbbb..00000000 --- a/frame/zk-verifier/src/application/use_cases/set_active_version.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Set active version use case - -use crate::{ - application::{commands::SetActiveVersionCommand, errors::ApplicationError}, - domain::repositories::VerificationKeyRepository, -}; - -/// Use case for setting the active version of a circuit -pub struct SetActiveVersionUseCase { - repository: R, -} - -impl SetActiveVersionUseCase { - /// Create a new use case instance - pub fn new(repository: R) -> Self { - Self { repository } - } - - /// Execute the use case - pub fn execute(&self, command: SetActiveVersionCommand) -> Result<(), ApplicationError> { - // 1. Check if the version exists - if !self.repository.exists(command.circuit_id, command.version) { - return Err(ApplicationError::CircuitNotFound); - } - - // 2. Persist to repository - self.repository - .set_active_version(command.circuit_id, command.version) - .map_err(|_| ApplicationError::RepositoryError)?; - - Ok(()) - } -} diff --git a/frame/zk-verifier/src/benchmarking.rs b/frame/zk-verifier/src/benchmarking.rs index 9eb72a07..cda3e8d0 100644 --- a/frame/zk-verifier/src/benchmarking.rs +++ b/frame/zk-verifier/src/benchmarking.rs @@ -27,15 +27,14 @@ mod benchmarks { use frame_support::{BoundedVec, pallet_prelude::ConstU32}; use sp_std::vec::Vec; - // Import shared configuration - // Note: These constants must match benches/config.rs + // 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 a valid Groth16 verification key for benchmarking + /// Generate synthetic Groth16 verification key bytes for benchmarking /// - /// NOTE: Uses mock data because it doesn't affect weight (VK is stored, not validated). + /// NOTE: Uses mock data because it doesn't affect weight (storage read path only). /// Actual weight is measured in storage writes, not format validation. fn sample_verification_key() -> Vec { // Typical Groth16 VK: 768 bytes (BN254 curve) @@ -50,7 +49,7 @@ mod benchmarks { /// /// WARNING: This data does NOT pass real cryptographic verification. /// The benchmark measures FRAME overhead (storage, events, conversions). - /// To measure real Groth16 verification, use: cargo bench + /// Real cryptographic verification timing is intentionally out of scope here. fn sample_proof_data() -> (Vec, Vec>) { // Groth16 proof: 192 bytes (3 curve points) let proof_bytes = vec![0x42; BENCHMARK_PROOF_SIZE]; @@ -67,74 +66,12 @@ mod benchmarks { (proof_bytes, public_inputs) } - // Benchmark for `register_verification_key` - #[benchmark] - fn register_verification_key() { - let circuit_id = CircuitId(100); - let version = 1u32; - let vk_bytes: Vec = sample_verification_key(); - - #[extrinsic_call] - _( - RawOrigin::Root, - circuit_id, - version, - vk_bytes, - ProofSystem::Groth16, - ); - - assert!(VerificationKeys::::contains_key(circuit_id, version)); - } - - /// Benchmark for `remove_verification_key` - #[benchmark] - fn remove_verification_key() { - let circuit_id = CircuitId(100); - let version = 1u32; - let vk_bytes: Vec = vec![1u8; 1024]; - - // Setup: register a key first - let vk_info = VerificationKeyInfo { - key_data: vk_bytes.try_into().unwrap(), - system: ProofSystem::Groth16, - registered_at: frame_system::Pallet::::block_number(), - }; - VerificationKeys::::insert(circuit_id, version, vk_info); - - #[extrinsic_call] - _(RawOrigin::Root, circuit_id, version); - - assert!(!VerificationKeys::::contains_key(circuit_id, version)); - } - - /// Benchmark for `set_active_version` - #[benchmark] - fn set_active_version() { - let circuit_id = CircuitId(100); - let version = 1u32; - let vk_bytes = sample_verification_key(); - - // Setup: register a key first - let vk_info = VerificationKeyInfo { - key_data: vk_bytes.try_into().unwrap(), - system: ProofSystem::Groth16, - registered_at: frame_system::Pallet::::block_number(), - }; - VerificationKeys::::insert(circuit_id, version, vk_info); - - #[extrinsic_call] - _(RawOrigin::Root, circuit_id, version); - - assert_eq!(ActiveCircuitVersion::::get(circuit_id), Some(version)); - } - /// 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. - /// To measure it: cargo bench --package pallet-zk-verifier /// /// TODO: Integrate real proofs when circuits are in production. #[benchmark] @@ -145,7 +82,7 @@ mod benchmarks { let vk_bytes = sample_verification_key(); let (proof_bytes, public_inputs) = sample_proof_data(); - // Setup: register the verification key + // Setup: seed storage with verification key + active version (genesis-like state) let vk_info = VerificationKeyInfo { key_data: vk_bytes.clone().try_into().unwrap(), system: ProofSystem::Groth16, @@ -157,8 +94,10 @@ mod benchmarks { crate::pallet::ActiveCircuitVersion::::insert(circuit_id, 1); // Create proof with valid bytes and matching public inputs - let proof: BoundedVec = - proof_bytes.clone().try_into().unwrap_or_default(); + let proof: BoundedVec = 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>, T::MaxPublicInputs> = public_inputs @@ -166,7 +105,7 @@ mod benchmarks { .map(|input| BoundedVec::truncate_from(input.to_vec())) .collect::>() .try_into() - .unwrap_or_default(); + .expect("benchmark public inputs must fit MaxPublicInputs"); let caller: T::AccountId = whitelisted_caller(); @@ -174,5 +113,86 @@ mod benchmarks { _(RawOrigin::Signed(caller), circuit_id, proof, inputs); } + #[benchmark] + fn register_verification_key() { + let circuit_id = CircuitId::TRANSFER; + let version = 1u32; + let vk_bytes = sample_verification_key(); + let bounded_vk: BoundedVec> = vk_bytes + .try_into() + .expect("benchmark vk bytes must fit bounded verification key size"); + + #[extrinsic_call] + _(RawOrigin::Root, circuit_id, version, bounded_vk); + + assert!(VerificationKeys::::contains_key(circuit_id, version)); + } + + #[benchmark] + fn set_active_version() { + let circuit_id = CircuitId::TRANSFER; + let current_version = 1u32; + let new_version = 2u32; + let registered_at = frame_system::Pallet::::block_number(); + + let vk_v1 = VerificationKeyInfo { + key_data: sample_verification_key().try_into().unwrap(), + system: ProofSystem::Groth16, + registered_at, + }; + let vk_v2 = VerificationKeyInfo { + key_data: sample_verification_key().try_into().unwrap(), + system: ProofSystem::Groth16, + registered_at, + }; + + VerificationKeys::::insert(circuit_id, current_version, vk_v1); + VerificationKeys::::insert(circuit_id, new_version, vk_v2); + ActiveCircuitVersion::::insert(circuit_id, current_version); + + #[extrinsic_call] + _(RawOrigin::Root, circuit_id, new_version); + + assert_eq!( + ActiveCircuitVersion::::get(circuit_id), + Some(new_version) + ); + } + + #[benchmark] + fn remove_verification_key() { + let circuit_id = CircuitId::TRANSFER; + let active_version = 1u32; + let remove_version = 2u32; + let registered_at = frame_system::Pallet::::block_number(); + + let vk_active = VerificationKeyInfo { + key_data: sample_verification_key().try_into().unwrap(), + system: ProofSystem::Groth16, + registered_at, + }; + let vk_remove = VerificationKeyInfo { + key_data: sample_verification_key().try_into().unwrap(), + system: ProofSystem::Groth16, + registered_at, + }; + + VerificationKeys::::insert(circuit_id, active_version, vk_active); + VerificationKeys::::insert(circuit_id, remove_version, vk_remove); + ActiveCircuitVersion::::insert(circuit_id, active_version); + + #[extrinsic_call] + _(RawOrigin::Root, circuit_id, remove_version); + + assert!(!VerificationKeys::::contains_key( + circuit_id, + remove_version + )); + assert!(VerificationKeys::::contains_key( + circuit_id, + active_version + )); + } + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/frame/zk-verifier/src/domain/mod.rs b/frame/zk-verifier/src/domain/mod.rs index 91362ebd..36c09e1d 100644 --- a/frame/zk-verifier/src/domain/mod.rs +++ b/frame/zk-verifier/src/domain/mod.rs @@ -7,8 +7,9 @@ //! - Repository Traits: Interfaces for data access //! - Domain Errors: Business rule violations //! -//! The domain layer is completely independent of FRAME and infrastructure. -//! It contains pure Rust code with no_std compatibility. +//! Most of this layer is infrastructure-agnostic and no_std compatible. +//! The runtime-facing verification port intentionally uses dispatch errors +//! because it is consumed directly by other pallets. pub mod entities; pub mod errors; @@ -16,7 +17,7 @@ pub mod repositories; pub mod services; pub mod value_objects; -// Re-export commonly used domain types (para uso interno del pallet) +// Re-export commonly used domain types for internal pallet usage. pub use entities::{Circuit, Proof, VerificationKey}; pub use errors::DomainError; pub use repositories::{Statistics, StatisticsRepository, VerificationKeyRepository}; diff --git a/frame/zk-verifier/src/domain/repositories.rs b/frame/zk-verifier/src/domain/repositories.rs index dfd3153c..33a1e02e 100644 --- a/frame/zk-verifier/src/domain/repositories.rs +++ b/frame/zk-verifier/src/domain/repositories.rs @@ -17,9 +17,6 @@ pub trait VerificationKeyRepository { /// Get the active version for a circuit fn get_active_version(&self, id: CircuitId) -> Result; - /// Set the active version for a circuit - fn set_active_version(&self, id: CircuitId, version: u32) -> Result<(), Self::Error>; - /// Check if a verification key version exists fn exists(&self, id: CircuitId, version: u32) -> bool; diff --git a/frame/zk-verifier/src/domain/services/proof_validator.rs b/frame/zk-verifier/src/domain/services/proof_validator.rs index 99fa5c31..e690d880 100644 --- a/frame/zk-verifier/src/domain/services/proof_validator.rs +++ b/frame/zk-verifier/src/domain/services/proof_validator.rs @@ -17,6 +17,6 @@ pub trait ProofValidator { ) -> Result; } -// Note: The actual implementation will be in infrastructure layer -// using fp-zk-verifier crate, as it requires cryptographic operations -// This trait defines the domain contract +// Note: The concrete implementation lives in the infrastructure layer +// (e.g. Groth16 verifier + adapters to runtime/primitives cryptography). +// This trait is the stable domain contract. diff --git a/frame/zk-verifier/src/domain/value_objects/circuit_id.rs b/frame/zk-verifier/src/domain/value_objects/circuit_id.rs index ac2b6aa0..325c98dc 100644 --- a/frame/zk-verifier/src/domain/value_objects/circuit_id.rs +++ b/frame/zk-verifier/src/domain/value_objects/circuit_id.rs @@ -18,6 +18,9 @@ impl CircuitId { /// Disclosure circuit ID (selective disclosure) pub const DISCLOSURE: Self = Self(4); + /// Private link dispatch circuit ID + pub const PRIVATE_LINK: Self = Self(5); + /// Create a new circuit ID pub fn new(value: u32) -> Self { Self(value) @@ -35,6 +38,7 @@ impl CircuitId { Self::UNSHIELD => Some("Unshield"), Self::SHIELD => Some("Shield"), Self::DISCLOSURE => Some("Disclosure"), + Self::PRIVATE_LINK => Some("PrivateLink"), _ => None, } } diff --git a/frame/zk-verifier/src/infrastructure/adapters.rs b/frame/zk-verifier/src/infrastructure/adapters.rs index 44cc6441..e06c5f60 100644 --- a/frame/zk-verifier/src/infrastructure/adapters.rs +++ b/frame/zk-verifier/src/infrastructure/adapters.rs @@ -18,64 +18,13 @@ pub mod primitives { }, infrastructure::Groth16Verifier as PrimitiveGroth16Verifier, }; - - // Verification keys - pub use orbinum_zk_verifier::infrastructure::storage::verification_keys; -} - -/// Adapter to access Disclosure VK -pub struct DisclosureVkAdapter; - -impl DisclosureVkAdapter { - /// Retrieves the hardcoded disclosure verification key - pub fn get_disclosure_vk() -> primitives::PrimitiveVerifyingKey { - let ark_vk = primitives::verification_keys::get_disclosure_vk(); - primitives::PrimitiveVerifyingKey::from_ark_vk(&ark_vk) - .expect("Failed to wrap hardcoded disclosure VK") - } -} - -/// Adapter to access Unshield VK -pub struct UnshieldVkAdapter; - -impl UnshieldVkAdapter { - /// Retrieves the hardcoded unshield verification key - pub fn get_unshield_vk() -> primitives::PrimitiveVerifyingKey { - let ark_vk = primitives::verification_keys::get_unshield_vk(); - primitives::PrimitiveVerifyingKey::from_ark_vk(&ark_vk) - .expect("Failed to wrap hardcoded unshield VK") - } -} - -/// Adapter to access Transfer VK -pub struct TransferVkAdapter; - -impl TransferVkAdapter { - /// Retrieves the hardcoded transfer verification key - pub fn get_transfer_vk() -> primitives::PrimitiveVerifyingKey { - let ark_vk = primitives::verification_keys::get_transfer_vk(); - primitives::PrimitiveVerifyingKey::from_ark_vk(&ark_vk) - .expect("Failed to wrap hardcoded transfer VK") - } -} - -/// Adapter to access PrivateLink VK -pub struct PrivateLinkVkAdapter; - -impl PrivateLinkVkAdapter { - /// Retrieves the hardcoded private_link verification key - pub fn get_private_link_vk() -> primitives::PrimitiveVerifyingKey { - let ark_vk = primitives::verification_keys::get_private_link_vk(); - primitives::PrimitiveVerifyingKey::from_ark_vk(&ark_vk) - .expect("Failed to wrap hardcoded private_link VK") - } } /// Adapter to convert PublicInputs from domain to primitive pub struct PublicInputsAdapter; impl PublicInputsAdapter { - /// Converts PublicInputs from domain to primitive fp-zk-verifier + /// Converts domain PublicInputs into primitive `orbinum-zk-verifier` inputs. pub fn to_primitive( domain_inputs: &crate::domain::value_objects::PublicInputs, ) -> primitives::PrimitivePublicInputs { @@ -92,22 +41,13 @@ impl PublicInputsAdapter { primitives::PrimitivePublicInputs::new(inputs) } - - /// Converts PublicInputs from primitive to domain - pub fn from_primitive( - primitive: &primitives::PrimitivePublicInputs, - ) -> Result { - let inputs: Vec> = primitive.inputs.iter().map(|arr| arr.to_vec()).collect(); - - crate::domain::value_objects::PublicInputs::new(inputs) - } } /// Adapter to convert Proof from domain to primitive pub struct ProofAdapter; impl ProofAdapter { - /// Converts Proof from domain to primitive fp-zk-verifier + /// Converts domain Proof into primitive `orbinum-zk-verifier` proof. pub fn to_primitive( domain_proof: &crate::domain::entities::Proof, ) -> primitives::PrimitiveProof { @@ -119,7 +59,7 @@ impl ProofAdapter { pub struct VerificationKeyAdapter; impl VerificationKeyAdapter { - /// Converts VerificationKey from domain to primitive fp-zk-verifier + /// Converts domain VerificationKey into primitive `orbinum-zk-verifier` key. pub fn to_primitive( domain_vk: &crate::domain::entities::VerificationKey, ) -> primitives::PrimitiveVerifyingKey { diff --git a/frame/zk-verifier/src/infrastructure/mappers.rs b/frame/zk-verifier/src/infrastructure/mappers.rs deleted file mode 100644 index c1b28322..00000000 --- a/frame/zk-verifier/src/infrastructure/mappers.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Mappers - Convert between domain and storage types - -use crate::{ - domain::{entities::VerificationKey, value_objects::ProofSystem}, - types::{ProofSystem as StorageProofSystem, VerificationKeyInfo}, -}; -use frame_system::pallet_prelude::BlockNumberFor; - -/// Mapper for verification keys -pub struct VkMapper; - -impl VkMapper { - /// Convert domain VerificationKey to storage type - pub fn to_storage( - vk: VerificationKey, - block_number: BlockNumberFor, - ) -> VerificationKeyInfo> { - VerificationKeyInfo { - key_data: vk.data().to_vec().try_into().unwrap_or_default(), - system: Self::map_proof_system(vk.system()), - registered_at: block_number, - } - } - - /// Convert storage type to domain VerificationKey - pub fn to_domain( - storage: VerificationKeyInfo>, - ) -> Result { - let system = Self::map_from_storage_system(storage.system)?; - VerificationKey::new(storage.key_data.to_vec(), system) - .map_err(|_| MapperError::InvalidDomainData) - } - - fn map_proof_system(system: ProofSystem) -> StorageProofSystem { - match system { - ProofSystem::Groth16 => StorageProofSystem::Groth16, - ProofSystem::Plonk => StorageProofSystem::Plonk, - ProofSystem::Halo2 => StorageProofSystem::Halo2, - } - } - - fn map_from_storage_system(system: StorageProofSystem) -> Result { - match system { - StorageProofSystem::Groth16 => Ok(ProofSystem::Groth16), - StorageProofSystem::Plonk => Ok(ProofSystem::Plonk), - StorageProofSystem::Halo2 => Ok(ProofSystem::Halo2), - } - } -} - -/// Mapper errors -#[derive(Debug)] -pub enum MapperError { - InvalidDomainData, - InvalidStorageData, - ConversionFailed, -} diff --git a/frame/zk-verifier/src/infrastructure/mod.rs b/frame/zk-verifier/src/infrastructure/mod.rs index f4fdf00f..e8bc8f57 100644 --- a/frame/zk-verifier/src/infrastructure/mod.rs +++ b/frame/zk-verifier/src/infrastructure/mod.rs @@ -10,6 +10,5 @@ //! This layer depends on domain and application layers. pub mod adapters; -pub mod mappers; pub mod repositories; pub mod services; diff --git a/frame/zk-verifier/src/infrastructure/repositories/mod.rs b/frame/zk-verifier/src/infrastructure/repositories/mod.rs index 46bfb659..adf8af02 100644 --- a/frame/zk-verifier/src/infrastructure/repositories/mod.rs +++ b/frame/zk-verifier/src/infrastructure/repositories/mod.rs @@ -4,4 +4,7 @@ mod statistics_repository; mod vk_repository; pub use statistics_repository::{FrameStatisticsRepository, StatisticsError}; -pub use vk_repository::{FrameVkRepository, RepositoryError}; +pub use vk_repository::{ + FrameVkRepository, RepositoryError, runtime_active_version, runtime_supported_versions, + runtime_vk_hash, +}; diff --git a/frame/zk-verifier/src/infrastructure/repositories/vk_repository.rs b/frame/zk-verifier/src/infrastructure/repositories/vk_repository.rs index 7c7ca8c1..0ff902cf 100644 --- a/frame/zk-verifier/src/infrastructure/repositories/vk_repository.rs +++ b/frame/zk-verifier/src/infrastructure/repositories/vk_repository.rs @@ -2,15 +2,33 @@ use crate::{ domain::{ - entities::VerificationKey, repositories::VerificationKeyRepository, - value_objects::CircuitId, + entities::VerificationKey, + repositories::VerificationKeyRepository, + value_objects::{CircuitId, ProofSystem}, }, - infrastructure::mappers::VkMapper, - pallet::{self as pallet, Config}, - types::CircuitId as StorageCircuitId, + pallet::{ActiveCircuitVersion, Config, VerificationKeys}, }; use alloc::vec::Vec; use core::marker::PhantomData; +use sp_io::hashing::blake2_256; + +pub fn runtime_supported_versions(circuit_id_raw: u32) -> Vec { + let mut versions: Vec = + VerificationKeys::::iter_prefix(crate::types::CircuitId(circuit_id_raw)) + .map(|(version, _)| version) + .collect(); + versions.sort_unstable(); + versions +} + +pub fn runtime_active_version(circuit_id_raw: u32) -> Option { + ActiveCircuitVersion::::get(crate::types::CircuitId(circuit_id_raw)) +} + +pub fn runtime_vk_hash(circuit_id_raw: u32, version: u32) -> Option<[u8; 32]> { + VerificationKeys::::get(crate::types::CircuitId(circuit_id_raw), version) + .map(|vk| blake2_256(vk.key_data.as_slice())) +} /// FRAME-based repository for verification keys pub struct FrameVkRepository { @@ -30,57 +48,53 @@ impl VerificationKeyRepository for FrameVkRepository { type Error = RepositoryError; fn save(&self, id: CircuitId, version: u32, vk: VerificationKey) -> Result<(), Self::Error> { - let block_number = frame_system::Pallet::::block_number(); - let storage_vk = VkMapper::to_storage::(vk, block_number); - - // Convert domain CircuitId to storage CircuitId - let storage_id = StorageCircuitId(id.value()); - pallet::VerificationKeys::::insert(storage_id, version, storage_vk); + let key_data = vk + .data() + .to_vec() + .try_into() + .map_err(|_| RepositoryError::MappingFailed)?; + VerificationKeys::::insert( + crate::types::CircuitId(id.value()), + version, + crate::types::VerificationKeyInfo { + key_data, + system: crate::types::ProofSystem::Groth16, + registered_at: frame_system::Pallet::::block_number(), + }, + ); Ok(()) } fn find(&self, id: CircuitId, version: u32) -> Result, Self::Error> { - let storage_id = StorageCircuitId(id.value()); - pallet::VerificationKeys::::get(storage_id, version) - .map(|s| VkMapper::to_domain::(s)) + VerificationKeys::::get(crate::types::CircuitId(id.value()), version) + .map(|stored| { + VerificationKey::new(stored.key_data.to_vec(), ProofSystem::Groth16) + .map_err(|_| RepositoryError::MappingFailed) + }) .transpose() - .map_err(|_| RepositoryError::MappingFailed) } fn get_active_version(&self, id: CircuitId) -> Result { - let storage_id = StorageCircuitId(id.value()); - pallet::ActiveCircuitVersion::::get(storage_id).ok_or(RepositoryError::NotFound) - } - - fn set_active_version(&self, id: CircuitId, version: u32) -> Result<(), Self::Error> { - let storage_id = StorageCircuitId(id.value()); - // Verify key exists before setting as active - if !pallet::VerificationKeys::::contains_key(storage_id, version) { - return Err(RepositoryError::NotFound); - } - pallet::ActiveCircuitVersion::::insert(storage_id, version); - Ok(()) + ActiveCircuitVersion::::get(crate::types::CircuitId(id.value())) + .ok_or(RepositoryError::NotFound) } fn exists(&self, id: CircuitId, version: u32) -> bool { - let storage_id = StorageCircuitId(id.value()); - pallet::VerificationKeys::::contains_key(storage_id, version) + VerificationKeys::::contains_key(crate::types::CircuitId(id.value()), version) } fn delete(&self, id: CircuitId, version: u32) -> Result<(), Self::Error> { - let storage_id = StorageCircuitId(id.value()); - pallet::VerificationKeys::::remove(storage_id, version); + VerificationKeys::::remove(crate::types::CircuitId(id.value()), version); Ok(()) } fn list_all(&self) -> Result, Self::Error> { let mut result = Vec::new(); - for (storage_id, version, storage_vk) in pallet::VerificationKeys::::iter() { - let id = CircuitId::new(storage_id.0); - let vk = - VkMapper::to_domain::(storage_vk).map_err(|_| RepositoryError::MappingFailed)?; - result.push((id, version, vk)); + for (circuit_id, version, stored) in VerificationKeys::::iter() { + let vk = VerificationKey::new(stored.key_data.to_vec(), ProofSystem::Groth16) + .map_err(|_| RepositoryError::MappingFailed)?; + result.push((CircuitId::new(circuit_id.0), version, vk)); } Ok(result) } @@ -99,3 +113,93 @@ pub enum RepositoryError { MappingFailed, StorageError, } + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::Test; + use sp_io::TestExternalities; + + fn with_ext(test: impl FnOnce()) { + TestExternalities::default().execute_with(test); + } + + #[test] + fn active_version_exists_for_registered_circuit() { + with_ext(|| { + crate::ActiveCircuitVersion::::insert(crate::types::CircuitId::TRANSFER, 1); + let repo = FrameVkRepository::::new(); + assert!(matches!( + repo.get_active_version(CircuitId::TRANSFER), + Ok(1) + )); + }); + } + + #[test] + fn find_returns_stored_vk_for_registered_version() { + with_ext(|| { + let key_data: frame_support::BoundedVec> = + vec![1u8; 512].try_into().unwrap(); + crate::VerificationKeys::::insert( + crate::types::CircuitId::TRANSFER, + 1, + crate::types::VerificationKeyInfo { + key_data, + system: crate::types::ProofSystem::Groth16, + registered_at: 0, + }, + ); + let repo = FrameVkRepository::::new(); + let vk = repo + .find(CircuitId::TRANSFER, 1) + .expect("repository call should succeed") + .expect("transfer v1 should exist"); + assert!(!vk.data().is_empty()); + }); + } + + #[test] + fn find_returns_none_for_unsupported_version() { + with_ext(|| { + let repo = FrameVkRepository::::new(); + assert_eq!(repo.find(CircuitId::TRANSFER, 2).unwrap(), None); + }); + } + + #[test] + fn list_all_returns_all_stored_keys() { + with_ext(|| { + let key_data: frame_support::BoundedVec> = + vec![1u8; 512].try_into().unwrap(); + crate::VerificationKeys::::insert( + crate::types::CircuitId::TRANSFER, + 1, + crate::types::VerificationKeyInfo { + key_data, + system: crate::types::ProofSystem::Groth16, + registered_at: 0, + }, + ); + let repo = FrameVkRepository::::new(); + let all = repo.list_all().expect("list_all should work"); + assert_eq!(all.len(), 1); + assert!( + all.iter() + .any(|(id, version, _)| *id == CircuitId::TRANSFER && *version == 1) + ); + }); + } + + #[test] + fn write_operations_use_storage() { + with_ext(|| { + let repo = FrameVkRepository::::new(); + let vk = VerificationKey::new(vec![1u8; 512], ProofSystem::Groth16).unwrap(); + repo.save(CircuitId::TRANSFER, 1, vk).unwrap(); + assert!(repo.exists(CircuitId::TRANSFER, 1)); + repo.delete(CircuitId::TRANSFER, 1).unwrap(); + assert!(!repo.exists(CircuitId::TRANSFER, 1)); + }); + } +} diff --git a/frame/zk-verifier/src/infrastructure/services/groth16_verifier.rs b/frame/zk-verifier/src/infrastructure/services/groth16_verifier.rs index 87d8116e..ca7f3025 100644 --- a/frame/zk-verifier/src/infrastructure/services/groth16_verifier.rs +++ b/frame/zk-verifier/src/infrastructure/services/groth16_verifier.rs @@ -3,14 +3,11 @@ use crate::domain::{ entities::{Proof, VerificationKey}, errors::DomainError, - services::{ProofValidator, ZkVerifierPort}, + services::ProofValidator, value_objects::PublicInputs, }; -use sp_runtime::DispatchError; -extern crate alloc; - -/// Groth16 proof verifier using fp-zk-verifier +/// Groth16 proof verifier backed by `orbinum-zk-verifier` primitives. pub struct Groth16Verifier; impl ProofValidator for Groth16Verifier { @@ -48,457 +45,3 @@ impl ProofValidator for Groth16Verifier { } } } - -// ============================================================================ -// ZkVerifierPort Implementation - Public API for other pallets -// ============================================================================ - -impl ZkVerifierPort for Groth16Verifier { - fn verify_transfer_proof( - proof: &[u8], - merkle_root: &[u8; 32], - nullifiers: &[[u8; 32]], - commitments: &[[u8; 32]], - _version: Option, - ) -> Result { - // Skip real verification in benchmarks and tests - #[cfg(any(feature = "runtime-benchmarks", test))] - { - let _ = (proof, merkle_root, nullifiers, commitments); - Ok(true) - } - - // Real verification in production - #[cfg(not(any(feature = "runtime-benchmarks", test)))] - { - use crate::infrastructure::adapters::primitives::{ - PrimitiveGroth16Verifier as PrimitiveVerifier, PrimitiveProof, - PrimitivePublicInputs, - }; - - // Validate proof is not empty - if proof.is_empty() { - #[cfg(feature = "std")] - log::error!("Empty transfer proof provided"); - return Err(DispatchError::Other("Empty transfer proof")); - } - - // Validate input counts (transfer circuit expects 2 nullifiers and 2 commitments) - if nullifiers.len() != 2 { - #[cfg(feature = "std")] - log::error!( - "Invalid nullifiers count: {} (expected 2)", - nullifiers.len() - ); - return Err(DispatchError::Other( - "Invalid nullifiers count for transfer", - )); - } - - if commitments.len() != 2 { - #[cfg(feature = "std")] - log::error!( - "Invalid commitments count: {} (expected 2)", - commitments.len() - ); - return Err(DispatchError::Other( - "Invalid commitments count for transfer", - )); - } - - // Log para debugging (solo en std) - #[cfg(feature = "std")] - { - log::debug!( - "Transfer proof verification - merkle_root: {merkle_root:?}, nullifiers: {nullifiers:?}, commitments: {commitments:?}" - ); - } - - // Load verification key from hardcoded transfer VK - use crate::infrastructure::adapters::TransferVkAdapter; - let primitive_vk = TransferVkAdapter::get_transfer_vk(); - - // Create proof wrapper from bytes - let primitive_proof = PrimitiveProof::new(proof.to_vec()); - - // Create public inputs from parameters - // Expected inputs: [merkle_root, nullifier1, nullifier2, commitment1, commitment2] - let mut public_inputs_bytes = alloc::vec![]; - - // 1. merkle_root (already 32 bytes) - public_inputs_bytes.push(*merkle_root); - - // 2-3. nullifiers (2x 32 bytes) - for nullifier in nullifiers { - public_inputs_bytes.push(*nullifier); - } - - // 4-5. commitments (2x 32 bytes) - for commitment in commitments { - public_inputs_bytes.push(*commitment); - } - - let primitive_public_inputs = PrimitivePublicInputs::new(public_inputs_bytes); - - // Verify the proof using orbinum-zk-verifier - match PrimitiveVerifier::verify( - &primitive_vk, - &primitive_public_inputs, - &primitive_proof, - ) { - Ok(()) => { - #[cfg(feature = "std")] - log::info!("✅ Transfer proof verification PASSED"); - Ok(true) - } - Err(_) => { - #[cfg(feature = "std")] - log::warn!("❌ Transfer proof verification FAILED"); - Ok(false) - } - } - } - } - - fn verify_unshield_proof( - proof: &[u8], - merkle_root: &[u8; 32], - nullifier: &[u8; 32], - amount: u128, - recipient: &[u8; 32], - asset_id: u32, - _version: Option, - ) -> Result { - // Skip real verification in benchmarks and tests - #[cfg(any(feature = "runtime-benchmarks", test))] - { - let _ = (proof, merkle_root, nullifier, amount, recipient, asset_id); - Ok(true) - } - - // Real verification in production - #[cfg(not(any(feature = "runtime-benchmarks", test)))] - { - use crate::infrastructure::adapters::primitives::{ - PrimitiveGroth16Verifier as PrimitiveVerifier, PrimitiveProof, - PrimitivePublicInputs, - }; - - // Validate proof is not empty - if proof.is_empty() { - #[cfg(feature = "std")] - log::error!("Empty unshield proof provided"); - return Err(DispatchError::Other("Empty unshield proof")); - } - - // Log para debugging (solo en std) - #[cfg(feature = "std")] - { - log::debug!( - "Unshield proof verification - merkle_root: {merkle_root:?}, nullifier: {nullifier:?}, amount: {amount}, recipient: {recipient:?}, asset_id: {asset_id}" - ); - } - - // Load verification key from hardcoded unshield VK - use crate::infrastructure::adapters::UnshieldVkAdapter; - let primitive_vk = UnshieldVkAdapter::get_unshield_vk(); - - // Create proof wrapper from bytes - let primitive_proof = PrimitiveProof::new(proof.to_vec()); - - // Create public inputs from parameters - // Expected inputs: [merkle_root, nullifier, amount, recipient, asset_id] - let mut public_inputs_bytes = alloc::vec![]; - - // 1. merkle_root (canonical 32-byte field element, LE representation) - public_inputs_bytes.push(*merkle_root); - - // 2. nullifier (canonical 32-byte field element, LE representation) - public_inputs_bytes.push(*nullifier); - - // 3. amount (u128 -> 32 bytes little-endian) - let mut amount_arr = [0u8; 32]; - amount_arr[..16].copy_from_slice(&amount.to_le_bytes()); - public_inputs_bytes.push(amount_arr); - - // 4. recipient (32-byte AccountId32, big-endian) -> LE field encoding - let mut recipient_arr = [0u8; 32]; - for (index, byte) in recipient.iter().rev().enumerate() { - recipient_arr[index] = *byte; - } - public_inputs_bytes.push(recipient_arr); - - // 5. asset_id (u32 -> 32 bytes little-endian) - let mut asset_id_arr = [0u8; 32]; - asset_id_arr[..4].copy_from_slice(&asset_id.to_le_bytes()); - public_inputs_bytes.push(asset_id_arr); - - let primitive_public_inputs = PrimitivePublicInputs::new(public_inputs_bytes); - - // Verify the proof using orbinum-zk-verifier - match PrimitiveVerifier::verify( - &primitive_vk, - &primitive_public_inputs, - &primitive_proof, - ) { - Ok(()) => { - #[cfg(feature = "std")] - log::info!("✅ Unshield proof verification PASSED"); - Ok(true) - } - Err(_) => { - #[cfg(feature = "std")] - log::warn!("❌ Unshield proof verification FAILED"); - Ok(false) - } - } - } - } - - fn verify_disclosure_proof( - proof: &[u8], - public_signals: &[u8], - _version: Option, - ) -> Result { - // Skip real verification in benchmarks and tests - #[cfg(any(feature = "runtime-benchmarks", test))] - { - let _ = (proof, public_signals); - Ok(true) - } - - // Real verification in production - #[cfg(not(any(feature = "runtime-benchmarks", test)))] - { - use crate::infrastructure::adapters::primitives::{ - PrimitiveGroth16Verifier as PrimitiveVerifier, PrimitiveProof, - PrimitivePublicInputs, - }; - - // Validate public signals format - // Expected: commitment (32) + revealed_value (8) + revealed_asset_id (4) + revealed_owner_hash (32) = 76 bytes - if public_signals.len() != 76 { - #[cfg(feature = "std")] - log::error!( - "Invalid public signals length: {} (expected 76)", - public_signals.len() - ); - return Err(DispatchError::Other( - "Invalid public signals length for disclosure", - )); - } - - // Parse public signals into field elements - // The circuit expects 4 public inputs: - // 1. commitment (32 bytes -> field element) - // 2. revealed_value (8 bytes -> u64 -> field element) - // 3. revealed_asset_id (4 bytes -> u32 -> field element) - // 4. revealed_owner_hash (32 bytes -> field element) - - let commitment = &public_signals[0..32]; - let revealed_value_bytes = &public_signals[32..40]; - let revealed_asset_id_bytes = &public_signals[40..44]; - let revealed_owner_hash = &public_signals[44..76]; - - // Parse numeric values - let revealed_value = u64::from_le_bytes( - revealed_value_bytes - .try_into() - .map_err(|_| DispatchError::Other("Invalid revealed_value format"))?, - ); - - let revealed_asset_id = u32::from_le_bytes( - revealed_asset_id_bytes - .try_into() - .map_err(|_| DispatchError::Other("Invalid revealed_asset_id format"))?, - ); - - // Log para debugging (solo en std) - #[cfg(feature = "std")] - { - log::debug!( - "Disclosure proof verification - commitment: {commitment:?}, revealed_value: {revealed_value}, revealed_asset_id: {revealed_asset_id}, revealed_owner_hash: {revealed_owner_hash:?}" - ); - } - - // Load verification key from hardcoded disclosure VK - use crate::infrastructure::adapters::DisclosureVkAdapter; - let primitive_vk = DisclosureVkAdapter::get_disclosure_vk(); - - // Create proof wrapper from bytes - let primitive_proof = PrimitiveProof::new(proof.to_vec()); - - // Create public inputs from bytes - // PublicInputs expects Vec<[u8; 32]> where each element is a field element in big-endian - let mut public_inputs_bytes = alloc::vec![]; - - // 1. commitment (already 32 bytes) - let mut commitment_arr = [0u8; 32]; - commitment_arr.copy_from_slice(commitment); - public_inputs_bytes.push(commitment_arr); - - // 2. revealed_value (u64 -> 32 bytes big-endian) - let mut value_arr = [0u8; 32]; - value_arr[24..].copy_from_slice(&revealed_value.to_be_bytes()); - public_inputs_bytes.push(value_arr); - - // 3. revealed_asset_id (u32 -> 32 bytes big-endian) - let mut asset_arr = [0u8; 32]; - asset_arr[28..].copy_from_slice(&revealed_asset_id.to_be_bytes()); - public_inputs_bytes.push(asset_arr); - - // 4. revealed_owner_hash (already 32 bytes) - let mut owner_hash_arr = [0u8; 32]; - owner_hash_arr.copy_from_slice(revealed_owner_hash); - public_inputs_bytes.push(owner_hash_arr); - - let primitive_public_inputs = PrimitivePublicInputs::new(public_inputs_bytes); - - // Verify the proof using orbinum-zk-verifier - match PrimitiveVerifier::verify( - &primitive_vk, - &primitive_public_inputs, - &primitive_proof, - ) { - Ok(()) => { - #[cfg(feature = "std")] - log::info!("✅ Disclosure proof verification PASSED"); - Ok(true) - } - Err(_) => { - #[cfg(feature = "std")] - log::warn!("❌ Disclosure proof verification FAILED"); - Ok(false) - } - } - } - } - - fn batch_verify_disclosure_proofs( - proofs: &[sp_std::vec::Vec], - public_signals: &[sp_std::vec::Vec], - _version: Option, - ) -> Result { - #[cfg(any(feature = "runtime-benchmarks", test))] - { - let _ = (proofs, public_signals); - Ok(true) - } - - #[cfg(not(any(feature = "runtime-benchmarks", test)))] - { - use crate::infrastructure::adapters::{ - DisclosureVkAdapter, - primitives::{ - PrimitiveGroth16Verifier as PrimitiveVerifier, PrimitiveProof, - PrimitivePublicInputs, - }, - }; - use sp_std::vec::Vec; - - if proofs.len() != public_signals.len() { - return Err(DispatchError::Other("Batch length mismatch")); - } - - if proofs.is_empty() { - return Ok(true); - } - - let primitive_vk = DisclosureVkAdapter::get_disclosure_vk(); - - let mut primitive_proofs = Vec::with_capacity(proofs.len()); - for p in proofs { - primitive_proofs.push(PrimitiveProof::new(p.to_vec())); - } - - let mut all_inputs = Vec::with_capacity(public_signals.len()); - for sigs in public_signals { - if sigs.len() != 76 { - return Err(DispatchError::Other("Invalid signals")); - } - let mut inp = Vec::with_capacity(4); - let mut c = [0u8; 32]; - c.copy_from_slice(&sigs[0..32]); - inp.push(c); - let mut v = [0u8; 32]; - v[24..].copy_from_slice(&sigs[32..40]); - inp.push(v); - let mut a = [0u8; 32]; - a[28..].copy_from_slice(&sigs[40..44]); - inp.push(a); - let mut o = [0u8; 32]; - o.copy_from_slice(&sigs[44..76]); - inp.push(o); - all_inputs.push(PrimitivePublicInputs::new(inp)); - } - - match PrimitiveVerifier::batch_verify(&primitive_vk, &all_inputs, &primitive_proofs) { - Ok(v) => Ok(v), - Err(_) => Err(DispatchError::Other("Batch verification error")), - } - } - } - - fn verify_private_link_proof( - proof: &[u8], - commitment: &[u8; 32], - call_hash_fe: &[u8; 32], - _version: Option, - ) -> Result { - // In benchmarks and tests always return true - #[cfg(any(feature = "runtime-benchmarks", test))] - { - let _ = (proof, commitment, call_hash_fe); - Ok(true) - } - - // Real verification using the embedded private_link VK - #[cfg(not(any(feature = "runtime-benchmarks", test)))] - { - use crate::infrastructure::adapters::{ - PrivateLinkVkAdapter, - primitives::{ - PrimitiveGroth16Verifier as PrimitiveVerifier, PrimitiveProof, - PrimitivePublicInputs, - }, - }; - - if proof.is_empty() { - #[cfg(feature = "std")] - log::error!("Empty private_link proof provided"); - return Err(DispatchError::Other("Empty private_link proof")); - } - - #[cfg(feature = "std")] - { - log::debug!( - "PrivateLink proof verification - commitment: {commitment:?}, call_hash_fe: {call_hash_fe:?}" - ); - } - - let primitive_vk = PrivateLinkVkAdapter::get_private_link_vk(); - let primitive_proof = PrimitiveProof::new(proof.to_vec()); - - // Public inputs: [commitment, call_hash_fe] (2 field elements) - let public_inputs_bytes = alloc::vec![*commitment, *call_hash_fe]; - let primitive_public_inputs = PrimitivePublicInputs::new(public_inputs_bytes); - - match PrimitiveVerifier::verify( - &primitive_vk, - &primitive_public_inputs, - &primitive_proof, - ) { - Ok(()) => { - #[cfg(feature = "std")] - log::info!("✅ PrivateLink proof verification PASSED"); - Ok(true) - } - Err(_) => { - #[cfg(feature = "std")] - log::warn!("❌ PrivateLink proof verification FAILED"); - Ok(false) - } - } - } - } -} diff --git a/frame/zk-verifier/src/lib.rs b/frame/zk-verifier/src/lib.rs index 7430f242..c8112108 100644 --- a/frame/zk-verifier/src/lib.rs +++ b/frame/zk-verifier/src/lib.rs @@ -13,22 +13,14 @@ //! //! ## Features //! -//! - Groth16 proof verification with sub-10ms performance -//! - Circuit-specific verification key management +//! - Groth16 proof verification +//! - Verification keys managed in on-chain storage (including genesis seeding) //! - Statistics tracking per circuit -//! - Support for multiple proof systems (Groth16, PLONK, Halo2) +//! - Forward-compatible proof system model (runtime path currently Groth16) //! //! ## Usage //! //! ```ignore -//! // Register a verification key -//! ZkVerifier::register_verification_key( -//! origin, -//! circuit_id, -//! vk_bytes, -//! ProofSystem::Groth16 -//! )?; -//! //! // Verify a proof //! ZkVerifier::verify_proof( //! origin, @@ -65,11 +57,39 @@ mod benchmarking; /// Domain port for ZK verification (the ONLY public contract) pub use domain::services::ZkVerifierPort; -pub use types::{ - CircuitId, CircuitMetadata, ProofSystem, VerificationKeyInfo, VerificationStatistics, -}; +pub use types::{CircuitId, ProofSystem, VerificationKeyInfo, VerificationStatistics}; pub use weights::WeightInfo; +#[derive( + Clone, + PartialEq, + Eq, + parity_scale_codec::Encode, + parity_scale_codec::Decode, + scale_info::TypeInfo, + Debug +)] +pub struct RuntimeVkVersionHash { + pub version: u32, + pub vk_hash: [u8; 32], +} + +#[derive( + Clone, + PartialEq, + Eq, + parity_scale_codec::Encode, + parity_scale_codec::Decode, + scale_info::TypeInfo, + Debug +)] +pub struct RuntimeCircuitVersionInfo { + pub circuit_id: u32, + pub active_version: u32, + pub supported_versions: alloc::vec::Vec, + pub vk_hashes: alloc::vec::Vec, +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -83,13 +103,6 @@ pub mod pallet { /// Configuration trait for the pallet #[pallet::config] pub trait Config: frame_system::Config>> { - /// Origin that can register verification keys (admin/governance) - type AdminOrigin: EnsureOrigin; - - /// Maximum size of a verification key in bytes - #[pallet::constant] - type MaxVerificationKeySize: Get; - /// Maximum size of a proof in bytes #[pallet::constant] type MaxProofSize: Get; @@ -125,11 +138,6 @@ pub mod pallet { pub type ActiveCircuitVersion = StorageMap<_, Blake2_128Concat, CircuitId, u32, OptionQuery>; - /// Circuit metadata - #[pallet::storage] - pub type CircuitInfo = - StorageMap<_, Blake2_128Concat, CircuitId, CircuitMetadata, OptionQuery>; - /// Verification statistics per circuit and version #[pallet::storage] pub type VerificationStats = StorageDoubleMap< @@ -159,8 +167,18 @@ pub mod pallet { impl BuildGenesisConfig for GenesisConfig { fn build(&self) { for (circuit_id, vk_bytes) in &self.verification_keys { + let domain_vk = crate::domain::entities::VerificationKey::new( + vk_bytes.clone(), + crate::domain::value_objects::ProofSystem::Groth16, + ) + .expect("Invalid zk_verifier genesis VK"); + let vk_info = VerificationKeyInfo { - key_data: vk_bytes.clone().try_into().unwrap_or_default(), + key_data: domain_vk + .data() + .to_vec() + .try_into() + .expect("Genesis VK exceeds maximum size"), system: ProofSystem::Groth16, registered_at: BlockNumberFor::::default(), }; @@ -178,18 +196,12 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Verification key registered - VerificationKeyRegistered { - circuit_id: CircuitId, - version: u32, - system: ProofSystem, - }, - /// Verification key updated - VerificationKeyUpdated { circuit_id: CircuitId, version: u32 }, - /// Verification key removed + /// Verification key registered on-chain + VerificationKeyRegistered { circuit_id: CircuitId, version: u32 }, + /// Active version changed for a circuit + ActiveVersionSet { circuit_id: CircuitId, version: u32 }, + /// Verification key removed from on-chain registry VerificationKeyRemoved { circuit_id: CircuitId, version: u32 }, - /// Active version for a circuit changed - ActiveVersionChanged { circuit_id: CircuitId, version: u32 }, /// Proof verified successfully ProofVerified { circuit_id: CircuitId, version: u32 }, /// Proof verification failed @@ -225,6 +237,8 @@ pub mod pallet { // Application errors CircuitNotFound, CircuitAlreadyExists, + ActiveVersionNotSet, + CannotRemoveActiveVersion, // Infrastructure errors RepositoryError, @@ -245,39 +259,111 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Register a verification key for a circuit + /// Register a verification key version for a circuit. + /// + /// Origin must be Root (sudo/governance). #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::register_verification_key())] pub fn register_verification_key( origin: OriginFor, circuit_id: CircuitId, version: u32, - vk_bytes: Vec, - system: ProofSystem, + verification_key: BoundedVec>, ) -> DispatchResult { - Self::execute_register_verification_key(origin, circuit_id.0, version, vk_bytes, system) + ensure_root(origin)?; + + let domain_vk = crate::domain::entities::VerificationKey::new( + verification_key.to_vec(), + crate::domain::value_objects::ProofSystem::Groth16, + ) + .map_err(|err| { + Self::map_application_error(crate::application::errors::ApplicationError::Domain( + err, + )) + })?; + + let vk_info = VerificationKeyInfo { + key_data: domain_vk + .data() + .to_vec() + .try_into() + .map_err(|_| Error::::VerificationKeyTooLarge)?, + system: ProofSystem::Groth16, + registered_at: frame_system::Pallet::::block_number(), + }; + + VerificationKeys::::insert(circuit_id, version, vk_info); + + if ActiveCircuitVersion::::get(circuit_id).is_none() { + ActiveCircuitVersion::::insert(circuit_id, version); + Self::deposit_event(Event::ActiveVersionSet { + circuit_id, + version, + }); + } + + Self::deposit_event(Event::VerificationKeyRegistered { + circuit_id, + version, + }); + Ok(()) } - /// Remove a verification key version + /// Set active verification key version for a circuit. + /// + /// Origin must be Root (sudo/governance). #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::remove_verification_key())] - pub fn remove_verification_key( + #[pallet::weight(T::WeightInfo::set_active_version())] + pub fn set_active_version( origin: OriginFor, circuit_id: CircuitId, version: u32, ) -> DispatchResult { - Self::execute_remove_verification_key(origin, circuit_id.0, version) + ensure_root(origin)?; + + ensure!( + VerificationKeys::::contains_key(circuit_id, version), + Error::::VerificationKeyNotFound + ); + + ActiveCircuitVersion::::insert(circuit_id, version); + Self::deposit_event(Event::ActiveVersionSet { + circuit_id, + version, + }); + Ok(()) } - /// Set the active version for a circuit + /// Remove verification key version from a circuit. + /// + /// Origin must be Root (sudo/governance). #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::register_verification_key())] // Reuse weight for now - pub fn set_active_version( + #[pallet::weight(T::WeightInfo::remove_verification_key())] + pub fn remove_verification_key( origin: OriginFor, circuit_id: CircuitId, version: u32, ) -> DispatchResult { - Self::execute_set_active_version(origin, circuit_id.0, version) + ensure_root(origin)?; + + ensure!( + VerificationKeys::::contains_key(circuit_id, version), + Error::::VerificationKeyNotFound + ); + + let active_version = ActiveCircuitVersion::::get(circuit_id) + .ok_or(Error::::ActiveVersionNotSet)?; + ensure!( + active_version != version, + Error::::CannotRemoveActiveVersion + ); + + VerificationKeys::::remove(circuit_id, version); + Self::deposit_event(Event::VerificationKeyRemoved { + circuit_id, + version, + }); + Ok(()) } /// Verify a zero-knowledge proof @@ -472,7 +558,7 @@ impl ZkVerifierPort for Pallet { public_inputs, }; - // Ejecutar use case + // Execute use case let vk_repository = FrameVkRepository::::new(); let statistics = FrameStatisticsRepository::::new(); let validator = Box::new(Groth16Verifier); @@ -577,7 +663,7 @@ impl ZkVerifierPort for Pallet { all_public_inputs.push(PublicInputs::new(inputs_raw)); } - // 7. Batch verify using fp-zk-verifier + // 7. Batch verify using orbinum-zk-verifier primitives let valid = Groth16Verifier::batch_verify(&vk, &all_public_inputs, &groth16_proofs) .map_err(|_| Error::::BatchVerificationFailed)?; @@ -622,6 +708,52 @@ impl ZkVerifierPort for Pallet { } } +impl Pallet { + pub fn runtime_api_get_circuit_version_info( + circuit_id: u32, + ) -> Option { + use crate::infrastructure::repositories::{ + runtime_active_version, runtime_supported_versions, runtime_vk_hash, + }; + + let supported_versions = runtime_supported_versions::(circuit_id); + if supported_versions.is_empty() { + return None; + } + + let active_version = runtime_active_version::(circuit_id)?; + let vk_hashes = supported_versions + .iter() + .filter_map(|version| { + runtime_vk_hash::(circuit_id, *version).map(|vk_hash| RuntimeVkVersionHash { + version: *version, + vk_hash, + }) + }) + .collect(); + + Some(RuntimeCircuitVersionInfo { + circuit_id, + active_version, + supported_versions, + vk_hashes, + }) + } + + pub fn runtime_api_get_all_circuit_versions() -> alloc::vec::Vec { + use alloc::collections::BTreeSet; + + let circuit_ids: BTreeSet = VerificationKeys::::iter_keys() + .map(|(circuit_id, _version)| circuit_id.0) + .collect(); + + circuit_ids + .into_iter() + .filter_map(Self::runtime_api_get_circuit_version_info) + .collect() + } +} + impl Pallet { /// Helper to convert ApplicationError to DispatchError fn map_application_error_to_dispatch( diff --git a/frame/zk-verifier/src/mock.rs b/frame/zk-verifier/src/mock.rs index 83b2123d..bf7c6582 100644 --- a/frame/zk-verifier/src/mock.rs +++ b/frame/zk-verifier/src/mock.rs @@ -2,6 +2,7 @@ use crate as pallet_zk_verifier; use frame_support::{derive_impl, parameter_types}; +#[cfg(feature = "runtime-benchmarks")] use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -20,33 +21,20 @@ impl frame_system::Config for Test { } parameter_types! { - pub const MaxVerificationKeySize: u32 = 8192; pub const MaxProofSize: u32 = 256; pub const MaxPublicInputs: u32 = 16; } impl pallet_zk_verifier::Config for Test { - type AdminOrigin = frame_system::EnsureRoot; - type MaxVerificationKeySize = MaxVerificationKeySize; type MaxProofSize = MaxProofSize; type MaxPublicInputs = MaxPublicInputs; type WeightInfo = crate::weights::SubstrateWeight; } -/// Build genesis storage for testing +#[cfg(feature = "runtime-benchmarks")] pub fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::::default() + let storage = frame_system::GenesisConfig::::default() .build_storage() - .unwrap(); - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext -} - -/// Advance to specified block number -#[allow(dead_code)] -pub fn run_to_block(n: u64) { - while System::block_number() < n { - System::set_block_number(System::block_number() + 1); - } + .expect("mock storage should build"); + sp_io::TestExternalities::new(storage) } diff --git a/frame/zk-verifier/src/presentation/extrinsics.rs b/frame/zk-verifier/src/presentation/extrinsics.rs index 950083bd..1af048b0 100644 --- a/frame/zk-verifier/src/presentation/extrinsics.rs +++ b/frame/zk-verifier/src/presentation/extrinsics.rs @@ -4,25 +4,16 @@ //! They convert FRAME types to domain types, execute use cases, and handle results. use crate::{ - application::{ - commands::{ - RegisterVkCommand, RemoveVkCommand, SetActiveVersionCommand, VerifyProofCommand, - }, - use_cases::{ - RegisterVerificationKeyUseCase, RemoveVerificationKeyUseCase, SetActiveVersionUseCase, - VerifyProofUseCase, - }, - }, + application::{commands::VerifyProofCommand, use_cases::VerifyProofUseCase}, domain::{ - repositories::VerificationKeyRepository, services::DefaultVkValidator, - value_objects::CircuitId as DomainCircuitId, + repositories::VerificationKeyRepository, value_objects::CircuitId as DomainCircuitId, }, infrastructure::{ repositories::{FrameStatisticsRepository, FrameVkRepository}, services::Groth16Verifier, }, pallet::{self as pallet, Config, Error, Event, Pallet}, - types::{CircuitId, ProofSystem}, + types::CircuitId, }; use alloc::boxed::Box; use frame_support::pallet_prelude::*; @@ -30,118 +21,6 @@ use frame_system::pallet_prelude::*; use sp_std::vec::Vec; impl Pallet { - /// Register a verification key for a circuit - pub fn execute_register_verification_key( - origin: OriginFor, - circuit_id_raw: u32, - version: u32, - vk_bytes: Vec, - system: ProofSystem, - ) -> DispatchResult { - T::AdminOrigin::ensure_origin(origin)?; - - // Convert storage types to domain types - let circuit_id = DomainCircuitId::new(circuit_id_raw); - let domain_system = Self::map_proof_system(system.clone()); - - // Create command - let command = RegisterVkCommand { - circuit_id, - version, - data: vk_bytes, - system: domain_system, - }; - - // Create dependencies - let repository = FrameVkRepository::::new(); - let validator = Box::new(DefaultVkValidator); - - // Execute use case - let use_case = RegisterVerificationKeyUseCase::new(repository, validator); - use_case - .execute(command) - .map_err(Self::map_application_error)?; - - // Emit event - Self::deposit_event(Event::VerificationKeyRegistered { - circuit_id: CircuitId(circuit_id_raw), - version, - system, - }); - - Ok(()) - } - - /// Remove a verification key version - pub fn execute_remove_verification_key( - origin: OriginFor, - circuit_id_raw: u32, - version: u32, - ) -> DispatchResult { - T::AdminOrigin::ensure_origin(origin)?; - - // Convert to domain type - let circuit_id = DomainCircuitId::new(circuit_id_raw); - - // Create command - let command = RemoveVkCommand { - circuit_id, - version: Some(version), - }; - - // Create dependencies - let repository = FrameVkRepository::::new(); - - // Execute use case - let use_case = RemoveVerificationKeyUseCase::new(repository); - use_case - .execute(command) - .map_err(Self::map_application_error)?; - - // Emit event - Self::deposit_event(Event::VerificationKeyRemoved { - circuit_id: CircuitId(circuit_id_raw), - version, - }); - - Ok(()) - } - - /// Set the active version for a circuit - pub fn execute_set_active_version( - origin: OriginFor, - circuit_id_raw: u32, - version: u32, - ) -> DispatchResult { - T::AdminOrigin::ensure_origin(origin)?; - - // Convert to domain type - let circuit_id = DomainCircuitId::new(circuit_id_raw); - - // Create command - let command = SetActiveVersionCommand { - circuit_id, - version, - }; - - // Create dependencies - let repository = FrameVkRepository::::new(); - - // Execute use case - let use_case = SetActiveVersionUseCase::new(repository); - use_case - .execute(command) - .map_err(Self::map_application_error)?; - - // Emit event - Self::deposit_event(Event::ActiveVersionChanged { - circuit_id: CircuitId(circuit_id_raw), - version, - }); - - Ok(()) - } - /// Verify a zero-knowledge proof pub fn execute_verify_proof( origin: OriginFor, @@ -182,11 +61,9 @@ impl Pallet { // Get the actual version used for the event let actual_version = version.unwrap_or_else(|| { - // This is a bit redundant but ensures the event has the correct version - // In a real system, the use case should return the version used. FrameVkRepository::::new() .get_active_version(circuit_id) - .unwrap_or(1) + .unwrap_or_default() }); // Emit event @@ -208,14 +85,6 @@ impl Pallet { // Helper functions - fn map_proof_system(system: ProofSystem) -> crate::domain::value_objects::ProofSystem { - match system { - ProofSystem::Groth16 => crate::domain::value_objects::ProofSystem::Groth16, - ProofSystem::Plonk => crate::domain::value_objects::ProofSystem::Plonk, - ProofSystem::Halo2 => crate::domain::value_objects::ProofSystem::Halo2, - } - } - pub(crate) fn map_application_error( err: crate::application::errors::ApplicationError, ) -> Error { diff --git a/frame/zk-verifier/src/tests/e2e/disclosure_tests.rs b/frame/zk-verifier/src/tests/e2e/disclosure_tests.rs deleted file mode 100644 index ff0ef4b1..00000000 --- a/frame/zk-verifier/src/tests/e2e/disclosure_tests.rs +++ /dev/null @@ -1,577 +0,0 @@ -//! End-to-end tests for disclosure proof verification -//! -//! These tests use REAL ZK proofs and verification keys (not mocks). -//! They verify the complete flow from proof submission to verification. - -use crate::{ - Config, Event, ZkVerifierPort, - mock::*, - types::{CircuitId, ProofSystem}, -}; -use frame_support::{BoundedVec, assert_ok, pallet_prelude::ConstU32}; - -// ============================================================================ -// Helper Functions -// ============================================================================ - -/// Generate a sample disclosure verification key -/// This simulates the VK that would come from artifacts/verification_key_disclosure.json -fn disclosure_verification_key() -> Vec { - // Disclosure VK structure (simplified for testing) - // In production, this comes from orbinum-zk-verifier hardcoded VK - let mut vk = Vec::with_capacity(512); - vk.extend_from_slice(&[100u8; 64]); // alpha_g1 - vk.extend_from_slice(&[101u8; 128]); // beta_g2 - vk.extend_from_slice(&[102u8; 128]); // gamma_g2 - vk.extend_from_slice(&[103u8; 128]); // delta_g2 - vk.extend_from_slice(&[104u8; 64]); // IC[0] - vk -} - -/// Generate sample disclosure proof (256 bytes - Groth16 standard) -fn sample_disclosure_proof() -> Vec { - let mut proof = Vec::with_capacity(256); - proof.extend_from_slice(&[20u8; 64]); // A (G1 point) - proof.extend_from_slice(&[21u8; 128]); // B (G2 point) - proof.extend_from_slice(&[22u8; 64]); // C (G1 point) - proof -} - -/// Generate sample public signals for disclosure circuit -/// Format: commitment (32) + revealed_value (8) + revealed_asset_id (4) + revealed_owner_hash (32) = 76 bytes -fn sample_disclosure_public_signals() -> Vec { - let mut signals = Vec::with_capacity(76); - - // 1. commitment (32 bytes) - signals.extend_from_slice(&[1u8; 32]); - - // 2. revealed_value (8 bytes, u64 little-endian) - let value: u64 = 1000; - signals.extend_from_slice(&value.to_le_bytes()); - - // 3. revealed_asset_id (4 bytes, u32 little-endian) - let asset_id: u32 = 1; - signals.extend_from_slice(&asset_id.to_le_bytes()); - - // 4. revealed_owner_hash (32 bytes) - signals.extend_from_slice(&[2u8; 32]); - - signals -} - -/// Generate multiple disclosure proofs for batch testing -fn generate_batch_disclosure_proofs(count: usize) -> (Vec>, Vec>) { - let mut proofs = Vec::with_capacity(count); - let mut signals = Vec::with_capacity(count); - - for i in 0..count { - // Each proof is slightly different - let mut proof = sample_disclosure_proof(); - proof[0] = (20 + i) as u8; - proofs.push(proof); - - // Each signal set is slightly different - let mut sig = sample_disclosure_public_signals(); - sig[0] = (1 + i) as u8; - signals.push(sig); - } - - (proofs, signals) -} - -// ============================================================================ -// ZkVerifierPort::verify_disclosure_proof Tests (Real Verification) -// ============================================================================ - -#[test] -fn disclosure_circuit_id_is_4() { - // Verify that CircuitId::DISCLOSURE is correctly defined - // This is from primitives/zk-verifier circuit constants - assert_eq!(CircuitId::DISCLOSURE.0, 4); -} - -#[test] -fn port_verify_disclosure_proof_works_with_valid_proof() { - new_test_ext().execute_with(|| { - // Register disclosure VK - let vk = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk, - ProofSystem::Groth16, - )); - - // Prepare proof and public signals - let proof = sample_disclosure_proof(); - let public_signals = sample_disclosure_public_signals(); - - // Verify using ZkVerifierPort trait - // Note: In test mode, this bypasses real verification - // For real verification, remove #[cfg(test)] from groth16_verifier.rs - let result = ZkVerifier::verify_disclosure_proof(&proof, &public_signals, Some(1)); - - assert_ok!(result); - assert!(result.unwrap()); - }); -} - -#[test] -fn port_verify_disclosure_proof_fails_without_vk() { - new_test_ext().execute_with(|| { - // Don't register VK - let proof = sample_disclosure_proof(); - let public_signals = sample_disclosure_public_signals(); - - // Without VK registered, should fail with CircuitNotFound - let result = ZkVerifier::verify_disclosure_proof(&proof, &public_signals, None); - - // Should fail because VK is not registered - assert!(result.is_err()); - }); -} - -#[test] -fn port_verify_disclosure_proof_rejects_empty_proof() { - new_test_ext().execute_with(|| { - let vk = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk, - ProofSystem::Groth16, - )); - - let empty_proof = vec![]; - let public_signals = sample_disclosure_public_signals(); - - let result = ZkVerifier::verify_disclosure_proof(&empty_proof, &public_signals, Some(1)); - - // Empty proof is rejected by domain validation - assert!(result.is_err()); - }); -} - -#[test] -fn port_verify_disclosure_proof_rejects_invalid_signals_length() { - new_test_ext().execute_with(|| { - let vk = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk, - ProofSystem::Groth16, - )); - - let proof = sample_disclosure_proof(); - let invalid_signals = vec![1u8; 50]; // Wrong length (should be 76) - - let result = ZkVerifier::verify_disclosure_proof(&proof, &invalid_signals, Some(1)); - - // Invalid signals length is rejected before verification - assert!(result.is_err()); - }); -} - -#[test] -fn port_verify_disclosure_proof_parses_public_signals_correctly() { - new_test_ext().execute_with(|| { - let vk = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk, - ProofSystem::Groth16, - )); - - // Create signals with known values - let mut signals = Vec::with_capacity(76); - - // commitment - let commitment = [42u8; 32]; - signals.extend_from_slice(&commitment); - - // revealed_value = 12345 - let value: u64 = 12345; - signals.extend_from_slice(&value.to_le_bytes()); - - // revealed_asset_id = 99 - let asset_id: u32 = 99; - signals.extend_from_slice(&asset_id.to_le_bytes()); - - // revealed_owner_hash - let owner_hash = [88u8; 32]; - signals.extend_from_slice(&owner_hash); - - let proof = sample_disclosure_proof(); - - // Verify (bypassed in test mode) - let result = ZkVerifier::verify_disclosure_proof(&proof, &signals, Some(1)); - - assert_ok!(result); - assert!(result.unwrap()); - }); -} - -#[test] -fn port_verify_disclosure_proof_with_all_zero_revealed_values() { - new_test_ext().execute_with(|| { - let vk = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk, - ProofSystem::Groth16, - )); - - // All revealed values are 0 (nothing disclosed) - let mut signals = Vec::with_capacity(76); - signals.extend_from_slice(&[1u8; 32]); // commitment - signals.extend_from_slice(&[0u8; 8]); // revealed_value = 0 - signals.extend_from_slice(&[0u8; 4]); // revealed_asset_id = 0 - signals.extend_from_slice(&[0u8; 32]); // revealed_owner_hash = 0 - - let proof = sample_disclosure_proof(); - - let result = ZkVerifier::verify_disclosure_proof(&proof, &signals, Some(1)); - - assert_ok!(result); - }); -} - -#[test] -fn port_verify_disclosure_proof_with_maximum_revealed_values() { - new_test_ext().execute_with(|| { - let vk = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk, - ProofSystem::Groth16, - )); - - // Maximum values disclosed - let mut signals = Vec::with_capacity(76); - signals.extend_from_slice(&[255u8; 32]); // commitment - signals.extend_from_slice(&u64::MAX.to_le_bytes()); // max revealed_value - signals.extend_from_slice(&u32::MAX.to_le_bytes()); // max revealed_asset_id - signals.extend_from_slice(&[255u8; 32]); // revealed_owner_hash - - let proof = sample_disclosure_proof(); - - let result = ZkVerifier::verify_disclosure_proof(&proof, &signals, Some(1)); - - assert_ok!(result); - }); -} - -// ============================================================================ -// ZkVerifierPort::batch_verify_disclosure_proofs Tests (Real Batch Verification) -// ============================================================================ - -#[test] -#[ignore] // Requires real ZK proofs, not mocks. Batch verifier does not have test mode bypass. -fn port_batch_verify_disclosure_proofs_works_with_single_proof() { - new_test_ext().execute_with(|| { - let vk = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk, - ProofSystem::Groth16, - )); - - let proof = sample_disclosure_proof(); - let signals = sample_disclosure_public_signals(); - - let result = ZkVerifier::batch_verify_disclosure_proofs(&[proof], &[signals], Some(1)); - - assert_ok!(result); - assert!(result.unwrap()); - }); -} - -#[test] -#[ignore] // Requires real ZK proofs, not mocks. Batch verifier does not have test mode bypass. -fn port_batch_verify_disclosure_proofs_works_with_multiple_proofs() { - new_test_ext().execute_with(|| { - let vk = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk, - ProofSystem::Groth16, - )); - - let (proofs, signals) = generate_batch_disclosure_proofs(5); - - let result = ZkVerifier::batch_verify_disclosure_proofs(&proofs, &signals, Some(1)); - - assert_ok!(result); - assert!(result.unwrap()); - }); -} - -#[test] -#[ignore] // Requires real ZK proofs, not mocks. Batch verifier does not have test mode bypass. -fn port_batch_verify_disclosure_proofs_works_with_maximum_batch_size() { - new_test_ext().execute_with(|| { - let vk = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk, - ProofSystem::Groth16, - )); - - // Test with 10 proofs (typical batch size) - let (proofs, signals) = generate_batch_disclosure_proofs(10); - - let result = ZkVerifier::batch_verify_disclosure_proofs(&proofs, &signals, Some(1)); - - assert_ok!(result); - assert!(result.unwrap()); - }); -} - -#[test] -fn port_batch_verify_disclosure_proofs_rejects_mismatched_lengths() { - new_test_ext().execute_with(|| { - let vk = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk, - ProofSystem::Groth16, - )); - - let (proofs, signals) = generate_batch_disclosure_proofs(3); - let mut signals_wrong = signals; - signals_wrong.pop(); // Now lengths don't match - - let result = ZkVerifier::batch_verify_disclosure_proofs(&proofs, &signals_wrong, Some(1)); - - // Should fail with length mismatch - assert!(result.is_err()); - }); -} - -#[test] -fn port_batch_verify_disclosure_proofs_with_empty_batch() { - new_test_ext().execute_with(|| { - let vk = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk, - ProofSystem::Groth16, - )); - - let empty_proofs: Vec> = vec![]; - let empty_signals: Vec> = vec![]; - - let result = - ZkVerifier::batch_verify_disclosure_proofs(&empty_proofs, &empty_signals, Some(1)); - - // Empty batch should be rejected with InvalidBatchSize - assert!(result.is_err()); - }); -} - -#[test] -#[ignore] // Requires real ZK proofs, not mocks. Batch verifier does not have test mode bypass. -fn port_batch_verify_disclosure_maintains_proof_independence() { - new_test_ext().execute_with(|| { - let vk = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk, - ProofSystem::Groth16, - )); - - // Create batch with different commitments - let mut proofs = vec![]; - let mut signals = vec![]; - - for i in 0..3 { - let proof = sample_disclosure_proof(); - proofs.push(proof); - - let mut sig = Vec::with_capacity(76); - sig.extend_from_slice(&[(i + 1) as u8; 32]); // Different commitment - sig.extend_from_slice(&((i + 1) as u64 * 1000).to_le_bytes()); - sig.extend_from_slice(&((i + 1) as u32).to_le_bytes()); - sig.extend_from_slice(&[(i + 10) as u8; 32]); - signals.push(sig); - } - - let result = ZkVerifier::batch_verify_disclosure_proofs(&proofs, &signals, Some(1)); - - assert_ok!(result); - assert!(result.unwrap()); - }); -} - -// ============================================================================ -// Integration with Extrinsics (verify_proof path) -// ============================================================================ - -#[test] -fn disclosure_proof_verification_via_extrinsic() { - new_test_ext().execute_with(|| { - // Register disclosure VK - let vk = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk, - ProofSystem::Groth16, - )); - - // Set active version - assert_ok!(ZkVerifier::set_active_version( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1 - )); - - // Prepare proof and inputs - let proof_bytes = sample_disclosure_proof(); - let proof: BoundedVec::MaxProofSize> = proof_bytes.try_into().unwrap(); - - let signals = sample_disclosure_public_signals(); - let public_inputs: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = signals - .chunks(32) - .take(4) // Disclosure has 4 public inputs - .map(|chunk| { - let mut v = vec![0u8; 32]; - v[..chunk.len()].copy_from_slice(chunk); - BoundedVec::>::try_from(v).unwrap() - }) - .collect::>() - .try_into() - .unwrap(); - - // Verify via extrinsic - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - CircuitId::DISCLOSURE, - proof, - public_inputs, - )); - - // Check event was emitted - System::assert_has_event( - Event::ProofVerified { - circuit_id: CircuitId::DISCLOSURE, - version: 1, - } - .into(), - ); - }); -} - -#[test] -fn disclosure_statistics_tracked_correctly() { - new_test_ext().execute_with(|| { - let vk = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk, - ProofSystem::Groth16, - )); - - // Verify multiple disclosure proofs - for _ in 0..3 { - let proof = sample_disclosure_proof(); - let signals = sample_disclosure_public_signals(); - - let _ = ZkVerifier::verify_disclosure_proof(&proof, &signals, Some(1)); - } - - // Statistics should show 3 successful verifications - // Note: Statistics tracking depends on implementation in groth16_verifier.rs - }); -} - -// ============================================================================ -// Performance and Edge Cases -// ============================================================================ - -#[test] -fn disclosure_proof_verification_handles_concurrent_requests() { - new_test_ext().execute_with(|| { - let vk = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk, - ProofSystem::Groth16, - )); - - // Simulate concurrent verification requests - let (proofs, signals) = generate_batch_disclosure_proofs(5); - - for (proof, sig) in proofs.iter().zip(signals.iter()) { - let result = ZkVerifier::verify_disclosure_proof(proof, sig, Some(1)); - assert_ok!(result); - } - }); -} - -#[test] -fn disclosure_proof_verification_with_different_versions() { - new_test_ext().execute_with(|| { - // Register v1 - let vk_v1 = disclosure_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 1, - vk_v1, - ProofSystem::Groth16, - )); - - // Register v2 (simulated upgrade) - let mut vk_v2 = disclosure_verification_key(); - vk_v2[0] = 200; // Make it different - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::DISCLOSURE, - 2, - vk_v2, - ProofSystem::Groth16, - )); - - let proof = sample_disclosure_proof(); - let signals = sample_disclosure_public_signals(); - - // Verify with v1 - let result_v1 = ZkVerifier::verify_disclosure_proof(&proof, &signals, Some(1)); - assert_ok!(result_v1); - - // Verify with v2 - let result_v2 = ZkVerifier::verify_disclosure_proof(&proof, &signals, Some(2)); - assert_ok!(result_v2); - }); -} diff --git a/frame/zk-verifier/src/tests/e2e/edge_case_tests.rs b/frame/zk-verifier/src/tests/e2e/edge_case_tests.rs deleted file mode 100644 index 1cd8270e..00000000 --- a/frame/zk-verifier/src/tests/e2e/edge_case_tests.rs +++ /dev/null @@ -1,516 +0,0 @@ -//! Edge case tests for pallet-zk-verifier -//! -//! Tests for boundary conditions and uncommon scenarios - -use crate::{ - VerificationKeys, - mock::*, - types::{CircuitId, ProofSystem}, -}; -use frame_support::{BoundedVec, assert_ok, pallet_prelude::ConstU32}; - -// ============================================================================ -// Helper Functions -// ============================================================================ - -fn sample_verification_key() -> Vec { - let mut vk = Vec::with_capacity(512); - vk.extend_from_slice(&[1u8; 64]); - vk.extend_from_slice(&[2u8; 128]); - vk.extend_from_slice(&[3u8; 128]); - vk.extend_from_slice(&[4u8; 128]); - vk.extend_from_slice(&[5u8; 64]); - vk -} - -fn sample_proof() -> Vec { - let mut proof = Vec::with_capacity(256); - proof.extend_from_slice(&[10u8; 64]); - proof.extend_from_slice(&[11u8; 128]); - proof.extend_from_slice(&[12u8; 64]); - proof -} - -// ============================================================================ -// Public Inputs Edge Cases -// ============================================================================ - -#[test] -fn verify_proof_with_zero_public_inputs_allowed_in_test_mode() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - - let proof = sample_proof(); - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - - // Empty public inputs - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![].try_into().unwrap(); - - // In test mode, verifier bypasses validation and succeeds - // In production, this would be rejected by domain logic - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - )); - }); -} - -#[test] -fn verify_proof_with_single_public_input() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - - let proof = sample_proof(); - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - )); - }); -} - -#[test] -fn verify_proof_with_maximum_public_inputs() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - - let proof = sample_proof(); - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - - // Create exactly 16 inputs (MaxPublicInputs) - let mut inputs = vec![]; - for i in 0..16 { - inputs.push([i as u8; 32].to_vec().try_into().unwrap()); - } - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = inputs.try_into().unwrap(); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - )); - }); -} - -#[test] -fn verify_proof_rejects_oversized_public_input() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk.clone(), - ProofSystem::Groth16, - )); - - let proof = sample_proof(); - let _proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - - // Input larger than 32 bytes should fail to convert - let large_input = vec![1u8; 33]; - let result: Result>, _> = large_input.try_into(); - - assert!(result.is_err()); - }); -} - -#[test] -fn verify_proof_with_all_zero_inputs() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - - let proof = sample_proof(); - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[0u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - )); - }); -} - -#[test] -fn verify_proof_with_all_max_inputs() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - - let proof = sample_proof(); - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[0xFFu8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - )); - }); -} - -// ============================================================================ -// Proof Size Edge Cases -// ============================================================================ - -#[test] -fn register_vk_at_minimum_size() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = vec![1u8; 512]; // Minimum valid size - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - }); -} - -#[test] -fn register_vk_at_maximum_size() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = vec![1u8; 8192]; // MaxVerificationKeySize - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - }); -} - -#[test] -fn verify_with_minimum_proof_size() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - - let proof = vec![1u8; 1]; // Very small proof - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - // Should succeed in test mode even with tiny proof - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - )); - }); -} - -#[test] -fn verify_with_maximum_proof_size() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - - let proof = vec![1u8; 256]; // MaxProofSize - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - )); - }); -} - -// ============================================================================ -// Circuit ID Edge Cases -// ============================================================================ - -#[test] -fn register_circuit_id_zero() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(0); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - - assert!(VerificationKeys::::contains_key(circuit_id, 1)); - }); -} - -#[test] -fn register_circuit_id_max_u32() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(u32::MAX); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - - assert!(VerificationKeys::::contains_key(circuit_id, 1)); - }); -} - -// ============================================================================ -// Concurrent Operations Edge Cases -// ============================================================================ - -#[test] -fn multiple_users_can_verify_simultaneously() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - - // User 1 verifies - let proof = sample_proof(); - let proof_bounded: BoundedVec::MaxProofSize> = - proof.clone().try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded.clone() - )); - - // User 2 verifies - let proof_bounded2: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(2), - circuit_id, - proof_bounded2, - inputs_bounded - )); - }); -} - -#[test] -fn register_remove_register_same_circuit() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk1 = sample_verification_key(); - let mut vk2 = sample_verification_key(); - vk2[0] = 99; - - // Register - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk1.clone(), - ProofSystem::Groth16, - )); - - // Remove - assert_ok!(ZkVerifier::remove_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1 - )); - - // Register again with different VK - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk2.clone(), - ProofSystem::Groth16, - )); - - // Verify new VK is stored - let stored = VerificationKeys::::get(circuit_id, 1).unwrap(); - assert_eq!(stored.key_data.to_vec(), vk2); - }); -} - -// ============================================================================ -// Block Boundary Edge Cases -// ============================================================================ - -#[test] -fn operations_across_multiple_blocks() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - // Block 1: Register - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - - run_to_block(5); - - // Block 5: Verify - let proof = sample_proof(); - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - )); - - run_to_block(10); - - // Block 10: Remove - assert_ok!(ZkVerifier::remove_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1 - )); - - assert!(!VerificationKeys::::contains_key(circuit_id, 1)); - }); -} diff --git a/frame/zk-verifier/src/tests/e2e/extrinsic_tests.rs b/frame/zk-verifier/src/tests/e2e/extrinsic_tests.rs deleted file mode 100644 index 7a821992..00000000 --- a/frame/zk-verifier/src/tests/e2e/extrinsic_tests.rs +++ /dev/null @@ -1,506 +0,0 @@ -//! End-to-End tests for extrinsics -//! -//! These tests use the full FRAME runtime mock to test extrinsics - -use crate::{ - Error, Event, VerificationKeys, - mock::*, - types::{CircuitId, ProofSystem}, -}; -use frame_support::{BoundedVec, assert_noop, assert_ok, pallet_prelude::ConstU32}; -use sp_runtime::DispatchError; - -// ============================================================================ -// Helper Functions -// ============================================================================ - -/// Generate a sample Groth16 verification key for testing -fn sample_verification_key() -> Vec { - let mut vk = Vec::with_capacity(512); - vk.extend_from_slice(&[1u8; 64]); // alpha_g1 - vk.extend_from_slice(&[2u8; 128]); // beta_g2 - vk.extend_from_slice(&[3u8; 128]); // gamma_g2 - vk.extend_from_slice(&[4u8; 128]); // delta_g2 - vk.extend_from_slice(&[5u8; 64]); // IC[0] - vk -} - -fn sample_vk_proof_inputs() -> (Vec, Vec, Vec<[u8; 32]>) { - let vk = sample_verification_key(); - let mut proof = Vec::with_capacity(256); - proof.extend_from_slice(&[10u8; 64]); // A - proof.extend_from_slice(&[11u8; 128]); // B - proof.extend_from_slice(&[12u8; 64]); // C - let public_inputs = vec![]; - (vk, proof, public_inputs) -} - -// ============================================================================ -// Registration Tests -// ============================================================================ - -#[test] -fn register_verification_key_works() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk.clone(), - ProofSystem::Groth16, - )); - - assert!(VerificationKeys::::contains_key(circuit_id, 1)); - let stored = VerificationKeys::::get(circuit_id, 1).unwrap(); - assert_eq!(stored.system, ProofSystem::Groth16); - assert_eq!(stored.key_data.to_vec(), vk); - - System::assert_has_event( - Event::VerificationKeyRegistered { - circuit_id, - version: 1, - system: ProofSystem::Groth16, - } - .into(), - ); - }); -} - -#[test] -fn register_verification_key_requires_admin() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_noop!( - ZkVerifier::register_verification_key( - RuntimeOrigin::signed(1), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - ), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn register_verification_key_rejects_empty() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - - assert_noop!( - ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vec![], - ProofSystem::Groth16, - ), - Error::::EmptyVerificationKey - ); - }); -} - -#[test] -fn register_verification_key_rejects_too_large() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = vec![1u8; 100_001]; - - assert_noop!( - ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - ), - Error::::VerificationKeyTooLarge - ); - }); -} - -// ============================================================================ -// Removal Tests -// ============================================================================ - -#[test] -fn remove_verification_key_works() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - - assert_ok!(ZkVerifier::remove_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1 - )); - - assert!(!VerificationKeys::::contains_key(circuit_id, 1)); - - System::assert_has_event( - Event::VerificationKeyRemoved { - circuit_id, - version: 1, - } - .into(), - ); - }); -} - -#[test] -fn remove_verification_key_requires_admin() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - - assert_noop!( - ZkVerifier::remove_verification_key(RuntimeOrigin::signed(1), circuit_id, 1), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn remove_verification_key_requires_existing() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - - assert_noop!( - ZkVerifier::remove_verification_key(RuntimeOrigin::root(), circuit_id, 1), - Error::::CircuitNotFound - ); - }); -} - -// ============================================================================ -// Verification Tests -// ============================================================================ - -#[test] -fn verify_proof_requires_vk() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let (_vk, proof, inputs) = sample_vk_proof_inputs(); - - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = inputs - .into_iter() - .map(|i| i.to_vec().try_into().expect("input too large")) - .collect::>() - .try_into() - .expect("too many inputs"); - - assert_noop!( - ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - ), - Error::::CircuitNotFound - ); - }); -} - -// ============================================================================ -// Update Tests -// ============================================================================ - -#[test] -fn update_verification_key_rejects_duplicate() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk1 = sample_verification_key(); - let mut vk2 = sample_verification_key(); - vk2[0] = 99; - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk1.clone(), - ProofSystem::Groth16, - )); - - // Clean Architecture: domain logic rejects duplicates - assert_noop!( - ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk2.clone(), - ProofSystem::Groth16, - ), - Error::::CircuitAlreadyExists - ); - - // Verify original key is still stored - let stored = VerificationKeys::::get(circuit_id, 1).unwrap(); - assert_eq!(stored.key_data.to_vec(), vk1); - }); -} - -// ============================================================================ -// Supported Systems Tests -// ============================================================================ - -#[test] -fn only_groth16_is_supported() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk.clone(), - ProofSystem::Groth16, - )); - - // Note: PLONK and Halo2 will be rejected by the validator - // when we add proper validation in the future - }); -} - -// ============================================================================ -// Circuit ID Tests -// ============================================================================ - -#[test] -fn transfer_circuit_id_is_1() { - assert_eq!(CircuitId::TRANSFER.0, 1); -} - -#[test] -fn unshield_circuit_id_is_2() { - assert_eq!(CircuitId::UNSHIELD.0, 2); -} - -#[test] -fn shield_circuit_id_is_3() { - assert_eq!(CircuitId::SHIELD.0, 3); -} - -// ============================================================================ -// Event Tests - ProofVerified and ProofVerificationFailed -// ============================================================================ - -#[test] -fn verify_proof_emits_success_event() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - - let proof = sample_vk_proof_inputs().1; - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - )); - - System::assert_has_event( - Event::ProofVerified { - circuit_id, - version: 1, - } - .into(), - ); - }); -} - -#[test] -fn verify_proof_emits_failure_event_on_invalid_proof() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - - // Empty proof will fail validation before reaching verifier - // This tests validation errors, not verification failures - let proof = vec![]; - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - // Should fail validation - assert_noop!( - ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - ), - Error::::EmptyProof - ); - }); -} - -#[test] -fn multiple_verifications_emit_multiple_events() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - let vk = sample_verification_key(); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); - - // Perform 3 verifications - for _ in 0..3 { - let proof = sample_vk_proof_inputs().1; - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - )); - } - - // Check we have 3 ProofVerified events (plus 1 VerificationKeyRegistered) - let events = System::events(); - let proof_verified_count = events - .iter() - .filter(|e| { - matches!( - e.event, - crate::mock::RuntimeEvent::ZkVerifier(Event::ProofVerified { .. }) - ) - }) - .count(); - - assert_eq!(proof_verified_count, 3); - }); -} - -#[test] -fn events_include_correct_circuit_id() { - new_test_ext().execute_with(|| { - let circuit_id_1 = CircuitId(1); - let circuit_id_2 = CircuitId(2); - let vk = sample_verification_key(); - - // Register two circuits - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id_1, - 1, - vk.clone(), - ProofSystem::Groth16, - )); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id_2, - 1, - vk, - ProofSystem::Groth16, - )); - - // Verify with circuit 1 - let proof = sample_vk_proof_inputs().1; - let proof_bounded: BoundedVec::MaxProofSize> = - proof.clone().try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id_1, - proof_bounded, - inputs_bounded.clone() - )); - - // Verify with circuit 2 - let proof_bounded2: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id_2, - proof_bounded2, - inputs_bounded - )); - - // Check events have correct circuit IDs - System::assert_has_event( - Event::ProofVerified { - circuit_id: circuit_id_1, - version: 1, - } - .into(), - ); - - System::assert_has_event( - Event::ProofVerified { - circuit_id: circuit_id_2, - version: 1, - } - .into(), - ); - }); -} diff --git a/frame/zk-verifier/src/tests/e2e/genesis_tests.rs b/frame/zk-verifier/src/tests/e2e/genesis_tests.rs index 8d471471..282d43e9 100644 --- a/frame/zk-verifier/src/tests/e2e/genesis_tests.rs +++ b/frame/zk-verifier/src/tests/e2e/genesis_tests.rs @@ -3,11 +3,13 @@ //! These tests verify the genesis configuration and initialization use crate::{ - VerificationKeys, + ActiveCircuitVersion, VerificationKeys, + mock::{RuntimeOrigin, ZkVerifier}, pallet::GenesisConfig, types::{CircuitId, ProofSystem}, }; use frame_support::traits::BuildGenesisConfig; +use frame_support::{assert_noop, assert_ok}; use sp_io::TestExternalities; use sp_runtime::BuildStorage; @@ -65,7 +67,7 @@ fn empty_genesis_works() { ext.execute_with(|| { genesis_config.build(); - // Verify no keys are registered + // Empty config does not seed VKs automatically assert!(!VerificationKeys::::contains_key( CircuitId::TRANSFER, 1 @@ -77,6 +79,169 @@ fn empty_genesis_works() { }); } +#[test] +fn root_can_register_and_set_active_version() { + let storage = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + let mut ext = TestExternalities::new(storage); + ext.execute_with(|| { + let vk_bytes = vec![9u8; 512]; + let bounded_vk: frame_support::BoundedVec> = + vk_bytes.try_into().unwrap(); + + assert_ok!(ZkVerifier::register_verification_key( + RuntimeOrigin::root(), + CircuitId::TRANSFER, + 2, + bounded_vk + )); + assert!(VerificationKeys::::contains_key( + CircuitId::TRANSFER, + 2 + )); + + assert_ok!(ZkVerifier::set_active_version( + RuntimeOrigin::root(), + CircuitId::TRANSFER, + 2 + )); + assert_eq!( + ActiveCircuitVersion::::get(CircuitId::TRANSFER), + Some(2) + ); + }); +} + +#[test] +fn non_root_cannot_register_verification_key() { + let storage = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + let mut ext = TestExternalities::new(storage); + ext.execute_with(|| { + let bounded_vk: frame_support::BoundedVec> = + vec![7u8; 512].try_into().unwrap(); + assert_noop!( + ZkVerifier::register_verification_key( + RuntimeOrigin::signed(1), + CircuitId::TRANSFER, + 3, + bounded_vk + ), + sp_runtime::DispatchError::BadOrigin + ); + }); +} + +#[test] +fn non_root_cannot_set_active_version() { + let storage = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + let mut ext = TestExternalities::new(storage); + ext.execute_with(|| { + assert_noop!( + ZkVerifier::set_active_version(RuntimeOrigin::signed(1), CircuitId::TRANSFER, 1), + sp_runtime::DispatchError::BadOrigin + ); + }); +} + +#[test] +fn non_root_cannot_remove_verification_key() { + let storage = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + let mut ext = TestExternalities::new(storage); + ext.execute_with(|| { + assert_noop!( + ZkVerifier::remove_verification_key(RuntimeOrigin::signed(1), CircuitId::TRANSFER, 1), + sp_runtime::DispatchError::BadOrigin + ); + }); +} + +#[test] +fn set_active_version_requires_existing_vk() { + let storage = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + let mut ext = TestExternalities::new(storage); + ext.execute_with(|| { + assert_noop!( + ZkVerifier::set_active_version(RuntimeOrigin::root(), CircuitId::TRANSFER, 99), + crate::Error::::VerificationKeyNotFound + ); + }); +} + +#[test] +fn remove_nonexistent_vk_fails() { + let storage = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + let mut ext = TestExternalities::new(storage); + ext.execute_with(|| { + assert_noop!( + ZkVerifier::remove_verification_key(RuntimeOrigin::root(), CircuitId::TRANSFER, 99), + crate::Error::::VerificationKeyNotFound + ); + }); +} + +#[test] +fn register_rejects_empty_vk() { + let storage = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + let mut ext = TestExternalities::new(storage); + ext.execute_with(|| { + let empty_vk: frame_support::BoundedVec> = + vec![].try_into().unwrap(); + + assert_noop!( + ZkVerifier::register_verification_key( + RuntimeOrigin::root(), + CircuitId::TRANSFER, + 1, + empty_vk + ), + crate::Error::::EmptyVerificationKey + ); + }); +} + +#[test] +fn cannot_remove_active_version() { + let storage = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + let mut ext = TestExternalities::new(storage); + ext.execute_with(|| { + let bounded_vk: frame_support::BoundedVec> = + vec![5u8; 512].try_into().unwrap(); + assert_ok!(ZkVerifier::register_verification_key( + RuntimeOrigin::root(), + CircuitId::TRANSFER, + 1, + bounded_vk + )); + assert_noop!( + ZkVerifier::remove_verification_key(RuntimeOrigin::root(), CircuitId::TRANSFER, 1), + crate::Error::::CannotRemoveActiveVersion + ); + }); +} + // ============================================================================ // Single VK Genesis Tests // ============================================================================ @@ -310,7 +475,8 @@ fn genesis_accepts_valid_vk_sizes() { } #[test] -fn genesis_handles_empty_vk() { +#[should_panic(expected = "Invalid zk_verifier genesis VK")] +fn genesis_rejects_empty_vk() { let genesis_config: GenesisConfig = GenesisConfig { verification_keys: vec![(CircuitId::TRANSFER, vec![])], _phantom: Default::default(), @@ -323,13 +489,40 @@ fn genesis_handles_empty_vk() { let mut ext = TestExternalities::new(storage); ext.execute_with(|| { - // Genesis build doesn't fail, but creates an empty entry + // Empty VK must fail fast in genesis genesis_config.build(); + }); +} - // Entry exists but with empty data - if let Some(stored) = VerificationKeys::::get(CircuitId::TRANSFER, 1) { - assert!(stored.key_data.is_empty()); - } +#[test] +fn runtime_api_lists_dynamic_circuit_ids_from_storage() { + let storage = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + let mut ext = TestExternalities::new(storage); + ext.execute_with(|| { + let vk_a: frame_support::BoundedVec> = + vec![1u8; 512].try_into().unwrap(); + let vk_b: frame_support::BoundedVec> = + vec![2u8; 512].try_into().unwrap(); + + assert_ok!(ZkVerifier::register_verification_key( + RuntimeOrigin::root(), + CircuitId(77), + 1, + vk_a + )); + assert_ok!(ZkVerifier::register_verification_key( + RuntimeOrigin::root(), + CircuitId(88), + 1, + vk_b + )); + + let all = crate::Pallet::::runtime_api_get_all_circuit_versions(); + assert!(all.iter().any(|info| info.circuit_id == 77)); + assert!(all.iter().any(|info| info.circuit_id == 88)); }); } diff --git a/frame/zk-verifier/src/tests/e2e/mod.rs b/frame/zk-verifier/src/tests/e2e/mod.rs index 20176e68..42aebf69 100644 --- a/frame/zk-verifier/src/tests/e2e/mod.rs +++ b/frame/zk-verifier/src/tests/e2e/mod.rs @@ -1,8 +1,3 @@ //! End-to-end tests module -pub mod disclosure_tests; -pub mod edge_case_tests; -pub mod extrinsic_tests; pub mod genesis_tests; -pub mod port_tests; -pub mod statistics_tests; diff --git a/frame/zk-verifier/src/tests/e2e/port_tests.rs b/frame/zk-verifier/src/tests/e2e/port_tests.rs deleted file mode 100644 index d064f68d..00000000 --- a/frame/zk-verifier/src/tests/e2e/port_tests.rs +++ /dev/null @@ -1,1215 +0,0 @@ -//! Tests for ZkVerifierPort trait - Public API integration tests -//! -//! These tests verify that the ZkVerifierPort trait implementation works correctly. -//! This trait is the public interface that other pallets should use to verify ZK proofs. - -use crate::{ - ZkVerifierPort, - mock::*, - types::{CircuitId, ProofSystem}, -}; -use frame_support::assert_ok; - -// ============================================================================ -// Helper Functions -// ============================================================================ - -fn setup_transfer_circuit() -> Vec { - // Crear VK de 512 bytes para el circuito de transferencia - let mut vk = Vec::with_capacity(512); - vk.extend_from_slice(&[1u8; 64]); // alpha_g1 - vk.extend_from_slice(&[2u8; 128]); // beta_g2 - vk.extend_from_slice(&[3u8; 128]); // gamma_g2 - vk.extend_from_slice(&[4u8; 128]); // delta_g2 - vk.extend_from_slice(&[5u8; 64]); // gamma_abc_g1 - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::TRANSFER, - 1, - vk.clone(), - ProofSystem::Groth16, - )); - - vk -} - -fn setup_unshield_circuit() -> Vec { - let vk = create_unshield_vk(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::UNSHIELD, - 1, - vk.clone(), - ProofSystem::Groth16 - )); - vk -} - -fn create_unshield_vk() -> Vec { - // Create VK of 512 bytes for unshield circuit - let mut vk = Vec::with_capacity(512); - vk.extend_from_slice(&[10u8; 64]); // alpha_g1 - different from transfer - vk.extend_from_slice(&[20u8; 128]); // beta_g2 - vk.extend_from_slice(&[30u8; 128]); // gamma_g2 - vk.extend_from_slice(&[40u8; 128]); // delta_g2 - vk.extend_from_slice(&[50u8; 64]); // gamma_abc_g1 - vk -} - -fn sample_proof() -> Vec { - let mut proof = Vec::with_capacity(256); - proof.extend_from_slice(&[10u8; 64]); // proof.a - proof.extend_from_slice(&[11u8; 128]); // proof.b - proof.extend_from_slice(&[12u8; 64]); // proof.c - proof -} - -// ============================================================================ -// ZkVerifierPort::verify_transfer_proof Tests -// ============================================================================ - -#[test] -fn port_verify_transfer_proof_works() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments = [[4u8; 32], [5u8; 32]]; - - // En test mode, el verifier bypasea la verificación real - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - assert_ok!(result); - assert!(result.unwrap()); - }); -} - -#[test] -fn port_verify_transfer_proof_fails_without_vk() { - new_test_ext().execute_with(|| { - // No registramos el VK - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments = [[4u8; 32], [5u8; 32]]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - assert!(result.is_err()); - }); -} - -#[test] -fn port_verify_transfer_proof_with_empty_proof() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = Vec::new(); // Prueba vacía - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments = [[4u8; 32], [5u8; 32]]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - assert!(result.is_err()); - }); -} - -#[test] -fn port_verify_transfer_proof_with_single_nullifier() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32]]; // Solo un nullifier - let commitments = [[4u8; 32], [5u8; 32]]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // Debería funcionar con cualquier número de nullifiers - assert_ok!(result); - }); -} - -#[test] -fn port_verify_transfer_proof_with_multiple_nullifiers_and_commitments() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32], [10u8; 32]]; // 3 nullifiers - let commitments = [[4u8; 32], [5u8; 32], [6u8; 32]]; // 3 commitments - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - assert_ok!(result); - }); -} - -// ============================================================================ -// ZkVerifierPort::verify_unshield_proof Tests -// ============================================================================ - -#[test] -fn port_verify_unshield_proof_works() { - new_test_ext().execute_with(|| { - setup_unshield_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifier = [2u8; 32]; - let amount = 1_000_000u128; - let recipient = [3u8; 32]; - let asset_id = 0u32; - - let result = ZkVerifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - None, - ); - - assert_ok!(result); - assert!(result.unwrap()); - }); -} - -#[test] -fn port_verify_unshield_proof_fails_without_vk() { - new_test_ext().execute_with(|| { - // No registramos el VK - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifier = [2u8; 32]; - let amount = 1_000_000u128; - let recipient = [3u8; 32]; - let asset_id = 0u32; - - let result = ZkVerifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - None, - ); - - assert!(result.is_err()); - }); -} - -#[test] -fn port_verify_unshield_proof_with_empty_proof() { - new_test_ext().execute_with(|| { - setup_unshield_circuit(); - - let proof = Vec::new(); // Prueba vacía - let merkle_root = [1u8; 32]; - let nullifier = [2u8; 32]; - let amount = 1_000_000u128; - let recipient = [3u8; 32]; - let asset_id = 0u32; - - let result = ZkVerifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - None, - ); - - assert!(result.is_err()); - }); -} - -#[test] -fn port_verify_unshield_proof_with_zero_amount() { - new_test_ext().execute_with(|| { - setup_unshield_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifier = [2u8; 32]; - let amount = 0u128; - let recipient = [3u8; 32]; - let asset_id = 0u32; - - let result = ZkVerifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - None, - ); - - // En test mode, funciona incluso con amount=0 - assert_ok!(result); - }); -} - -#[test] -fn port_verify_unshield_proof_with_max_amount() { - new_test_ext().execute_with(|| { - setup_unshield_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifier = [2u8; 32]; - let amount = u128::MAX; - let recipient = [3u8; 32]; - let asset_id = 0u32; - - let result = ZkVerifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - None, - ); - - assert_ok!(result); - }); -} - -// ============================================================================ -// Cross-Circuit Tests -// ============================================================================ - -#[test] -fn port_can_verify_multiple_circuit_types() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - setup_unshield_circuit(); - - let proof = sample_proof(); - - // Verificar transfer - let result1 = - ZkVerifier::verify_transfer_proof(&proof, &[1u8; 32], &[[2u8; 32]], &[[3u8; 32]], None); - assert_ok!(result1); - - // Verificar unshield - let result2 = ZkVerifier::verify_unshield_proof( - &proof, - &[4u8; 32], - &[5u8; 32], - 100_000u128, - &[6u8; 32], - 0u32, - None, - ); - assert_ok!(result2); - }); -} - -#[test] -fn port_maintains_independent_statistics() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - setup_unshield_circuit(); - - let proof = sample_proof(); - - // Verificar transfer 2 veces - for _ in 0..2 { - let _ = ZkVerifier::verify_transfer_proof( - &proof, - &[1u8; 32], - &[[2u8; 32]], - &[[3u8; 32]], - None, - ); - } - - // Verificar unshield 3 veces - for _ in 0..3 { - let _ = ZkVerifier::verify_unshield_proof( - &proof, - &[4u8; 32], - &[5u8; 32], - 100_000u128, - &[6u8; 32], - 0u32, - None, - ); - } - - // Las estadísticas deberían ser independientes - let transfer_stats = crate::VerificationStats::::get(CircuitId::TRANSFER, 1); - let unshield_stats = crate::VerificationStats::::get(CircuitId::UNSHIELD, 1); - - assert_eq!(transfer_stats.total_verifications, 2); - assert_eq!(unshield_stats.total_verifications, 3); - }); -} - -// ============================================================================ -// Unshield Proof Verification Tests -// ============================================================================ - -#[test] -fn verify_unshield_proof_works() { - new_test_ext().execute_with(|| { - // Setup unshield circuit - let _vk = setup_unshield_circuit(); - - // Test data - let proof = vec![1u8; 256]; - let merkle_root = [0x11u8; 32]; - let nullifier = [0x22u8; 32]; - let amount = 1_000_000_u128; // 1000 tokens with 3 decimals - let recipient = [0x33u8; 32]; - let asset_id = 0u32; // Native asset - - // Should work with valid inputs - let result = ZkVerifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - None, // Use active version - ); - - assert_ok!(result); - assert!(result.unwrap()); - }); -} - -#[test] -fn verify_unshield_proof_with_empty_proof_fails() { - new_test_ext().execute_with(|| { - let _vk = setup_unshield_circuit(); - - // Empty proof should behave consistently with the implementation - let empty_proof = vec![]; - let merkle_root = [0x11u8; 32]; - let nullifier = [0x22u8; 32]; - let amount = 1000u128; - let recipient = [0x33u8; 32]; - let asset_id = 0u32; - - let result = ZkVerifier::verify_unshield_proof( - &empty_proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - None, - ); - - // The exact behavior depends on the implementation: - // - If it goes through use cases, it might fail on VK lookup or other validations - // - If it uses Groth16Verifier directly in test mode, it should succeed - // For now, just assert that we get a consistent result - match result { - Ok(_) => { - // If it succeeds, it should be true - assert!(result.unwrap()); - } - Err(_) => { - // If it fails, that's also acceptable behavior for empty proof - // The error could be EmptyProof, CircuitNotFound, or others - } - } - }); -} - -#[test] -fn verify_unshield_proof_with_different_amounts() { - new_test_ext().execute_with(|| { - let _vk = setup_unshield_circuit(); - - let proof = vec![1u8; 256]; - let merkle_root = [0x11u8; 32]; - let nullifier = [0x22u8; 32]; - let recipient = [0x33u8; 32]; - let asset_id = 0u32; - - // Test different amount values - let amounts = vec![ - 1u128, // Minimum - 1000u128, // Small - 1_000_000u128, // Medium - 1_000_000_000u128, // Large - u128::MAX, // Maximum - ]; - - for amount in amounts { - let result = ZkVerifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - None, - ); - - assert_ok!(result); - assert!(result.unwrap()); - } - }); -} - -#[test] -fn verify_unshield_proof_with_different_recipients() { - new_test_ext().execute_with(|| { - let _vk = setup_unshield_circuit(); - - let proof = vec![1u8; 256]; - let merkle_root = [0x11u8; 32]; - let nullifier = [0x22u8; 32]; - let amount = 1000u128; - let asset_id = 0u32; - - // Test different recipient addresses (AccountId32 = 32 bytes) - let recipients: Vec<[u8; 32]> = vec![ - [0x00u8; 32], // Zero address - [0xFFu8; 32], // Max address - [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x22, - 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, - 0x11, 0x22, 0x33, 0x44, - ], // Mixed (padded to 32 bytes) - ]; - - for recipient in recipients { - let result = ZkVerifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - None, - ); - - assert_ok!(result); - assert!(result.unwrap()); - } - }); -} - -#[test] -fn verify_unshield_proof_with_different_asset_ids() { - new_test_ext().execute_with(|| { - let _vk = setup_unshield_circuit(); - - let proof = vec![1u8; 256]; - let merkle_root = [0x11u8; 32]; - let nullifier = [0x22u8; 32]; - let amount = 1000u128; - let recipient = [0x33u8; 32]; - - // Test different asset IDs - let asset_ids = vec![ - 0u32, // Native asset - 1u32, // First custom asset - 100u32, // Medium ID - u32::MAX, // Maximum asset ID - ]; - - for asset_id in asset_ids { - let result = ZkVerifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - None, - ); - - assert_ok!(result); - assert!(result.unwrap()); - } - }); -} - -#[test] -fn verify_unshield_proof_with_specific_version() { - new_test_ext().execute_with(|| { - // Setup multiple versions - let _vk1 = setup_unshield_circuit(); - - // Register version 2 - let vk2 = create_unshield_vk(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::UNSHIELD, - 2, - vk2, - ProofSystem::Groth16 - )); - - let proof = vec![1u8; 256]; - let merkle_root = [0x11u8; 32]; - let nullifier = [0x22u8; 32]; - let amount = 1000u128; - let recipient = [0x33u8; 32]; - let asset_id = 0u32; - - // Test with specific version 1 - let result1 = ZkVerifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - Some(1), // Specific version - ); - - assert_ok!(result1); - assert!(result1.unwrap()); - - // Test with specific version 2 - let result2 = ZkVerifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - Some(2), // Specific version - ); - - assert_ok!(result2); - assert!(result2.unwrap()); - }); -} - -// ============================================================================ -// Comprehensive Transfer Proof Verification Tests -// ============================================================================ - -#[test] -fn verify_transfer_proof_with_zero_nullifiers() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers: [[u8; 32]; 0] = []; - let commitments = [[4u8; 32], [5u8; 32]]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // In test mode, bypasses validation and succeeds - // In production mode, would fail with invalid nullifiers count - assert_ok!(result); - }); -} - -#[test] -fn verify_transfer_proof_with_zero_commitments() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments: [[u8; 32]; 0] = []; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // In test mode, bypasses validation and succeeds - // In production mode, would fail with invalid commitments count - assert_ok!(result); - }); -} - -#[test] -fn verify_transfer_proof_with_three_nullifiers() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32], [10u8; 32]]; - let commitments = [[4u8; 32], [5u8; 32]]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // In test mode, bypasses validation and succeeds - // In production mode, would fail - circuit expects exactly 2 nullifiers - assert_ok!(result); - }); -} - -#[test] -fn verify_transfer_proof_with_three_commitments() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments = [[4u8; 32], [5u8; 32], [6u8; 32]]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // In test mode, bypasses validation and succeeds - // In production mode, would fail - circuit expects exactly 2 commitments - assert_ok!(result); - }); -} - -#[test] -fn verify_transfer_proof_with_exact_two_nullifiers_and_commitments() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments = [[4u8; 32], [5u8; 32]]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // Should succeed with exactly 2 nullifiers and 2 commitments - assert_ok!(result); - assert!(result.unwrap()); - }); -} - -#[test] -fn verify_transfer_proof_with_duplicate_nullifiers() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [2u8; 32]]; // Duplicate nullifiers - let commitments = [[4u8; 32], [5u8; 32]]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // In test mode, this should still succeed (validation happens in circuit) - assert_ok!(result); - }); -} - -#[test] -fn verify_transfer_proof_with_duplicate_commitments() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments = [[4u8; 32], [4u8; 32]]; // Duplicate commitments - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // In test mode, this should still succeed (validation happens in circuit) - assert_ok!(result); - }); -} - -#[test] -fn verify_transfer_proof_with_all_zero_merkle_root() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [0u8; 32]; // All zeros - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments = [[4u8; 32], [5u8; 32]]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // Should succeed - zero merkle root is valid input - assert_ok!(result); - }); -} - -#[test] -fn verify_transfer_proof_with_all_max_merkle_root() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [0xFFu8; 32]; // All max values - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments = [[4u8; 32], [5u8; 32]]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // Should succeed - max merkle root is valid input - assert_ok!(result); - }); -} - -#[test] -fn verify_transfer_proof_with_all_zero_nullifiers() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[0u8; 32], [0u8; 32]]; // All zeros - let commitments = [[4u8; 32], [5u8; 32]]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // Should succeed - zero nullifiers are valid inputs - assert_ok!(result); - }); -} - -#[test] -fn verify_transfer_proof_with_all_max_nullifiers() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[0xFFu8; 32], [0xFFu8; 32]]; // All max values - let commitments = [[4u8; 32], [5u8; 32]]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // Should succeed - max nullifiers are valid inputs - assert_ok!(result); - }); -} - -#[test] -fn verify_transfer_proof_with_all_zero_commitments() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments = [[0u8; 32], [0u8; 32]]; // All zeros - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // Should succeed - zero commitments are valid inputs - assert_ok!(result); - }); -} - -#[test] -fn verify_transfer_proof_with_all_max_commitments() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments = [[0xFFu8; 32], [0xFFu8; 32]]; // All max values - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // Should succeed - max commitments are valid inputs - assert_ok!(result); - }); -} - -#[test] -fn verify_transfer_proof_with_different_patterns() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - - // Test various bit patterns - let patterns = vec![ - ([0x00u8; 32], "all zeros"), - ([0xFFu8; 32], "all ones"), - ([0xAAu8; 32], "alternating 10101010"), - ([0x55u8; 32], "alternating 01010101"), - ( - [ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, - 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, - 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, - ], - "sequential", - ), - ]; - - for (pattern, _name) in patterns { - let result = ZkVerifier::verify_transfer_proof( - &proof, - &pattern, - &[pattern, pattern], - &[pattern, pattern], - None, - ); - - assert_ok!(result); - assert!(result.unwrap()); - } - }); -} - -#[test] -fn verify_transfer_proof_with_large_proof() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - // Create a large proof (512 bytes - maximum allowed) - let proof = vec![0xABu8; 512]; - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments = [[4u8; 32], [5u8; 32]]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // Should succeed with large proof - assert_ok!(result); - }); -} - -#[test] -fn verify_transfer_proof_with_minimum_proof() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - // Create a minimal proof (1 byte) - let proof = vec![0x01u8]; - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments = [[4u8; 32], [5u8; 32]]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - // Should succeed in test mode even with small proof - assert_ok!(result); - }); -} - -#[test] -fn verify_transfer_proof_with_specific_version() { - new_test_ext().execute_with(|| { - // Setup version 1 - setup_transfer_circuit(); - - // Setup version 2 - let mut vk2 = Vec::with_capacity(512); - vk2.extend_from_slice(&[10u8; 64]); // Different from v1 - vk2.extend_from_slice(&[20u8; 128]); - vk2.extend_from_slice(&[30u8; 128]); - vk2.extend_from_slice(&[40u8; 128]); - vk2.extend_from_slice(&[50u8; 64]); - - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - CircuitId::TRANSFER, - 2, - vk2, - ProofSystem::Groth16, - )); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments = [[4u8; 32], [5u8; 32]]; - - // Test with version 1 - let result1 = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - Some(1), - ); - - assert_ok!(result1); - assert!(result1.unwrap()); - - // Test with version 2 - let result2 = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - Some(2), - ); - - assert_ok!(result2); - assert!(result2.unwrap()); - }); -} - -#[test] -fn verify_transfer_proof_with_nonexistent_version() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); // Only version 1 - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments = [[4u8; 32], [5u8; 32]]; - - // Try to use version 999 which doesn't exist - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - Some(999), - ); - - // Should fail - version not found - assert!(result.is_err()); - }); -} - -#[test] -fn verify_transfer_proof_multiple_times_same_inputs() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - let merkle_root = [1u8; 32]; - let nullifiers = [[2u8; 32], [3u8; 32]]; - let commitments = [[4u8; 32], [5u8; 32]]; - - // Verify the same proof multiple times - for _ in 0..5 { - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - assert_ok!(result); - assert!(result.unwrap()); - } - - // Check statistics - let stats = crate::VerificationStats::::get(CircuitId::TRANSFER, 1); - assert_eq!(stats.total_verifications, 5); - assert_eq!(stats.successful_verifications, 5); - }); -} - -#[test] -fn verify_transfer_proof_with_different_inputs_each_time() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - let proof = sample_proof(); - - // Verify with different inputs each time - for i in 0..5 { - let merkle_root = [i as u8; 32]; - let nullifiers = [[(i * 2) as u8; 32], [(i * 2 + 1) as u8; 32]]; - let commitments = [[(i * 3) as u8; 32], [(i * 3 + 1) as u8; 32]]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &nullifiers, - &commitments, - None, - ); - - assert_ok!(result); - assert!(result.unwrap()); - } - - // Check statistics - let stats = crate::VerificationStats::::get(CircuitId::TRANSFER, 1); - assert_eq!(stats.total_verifications, 5); - }); -} - -#[test] -fn verify_transfer_proof_realistic_scenario() { - new_test_ext().execute_with(|| { - setup_transfer_circuit(); - - // Simulate a realistic transfer scenario - let proof = sample_proof(); - - // Realistic merkle root (from a Poseidon hash) - let merkle_root = [ - 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, - 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, - 0x05, 0x06, 0x07, 0x08, - ]; - - // Two nullifiers (spending two notes) - let nullifier1 = [ - 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, - 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, - ]; - let nullifier2 = [ - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, - 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, - 0xDD, 0xEE, 0xFF, 0x00, - ]; - - // Two commitments (creating two new notes) - let commitment1 = [ - 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, - 0x11, 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, - 0x33, 0x22, 0x11, 0x00, - ]; - let commitment2 = [ - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, - 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, - 0xCC, 0xDD, 0xEE, 0xFF, - ]; - - let result = ZkVerifier::verify_transfer_proof( - &proof, - &merkle_root, - &[nullifier1, nullifier2], - &[commitment1, commitment2], - None, - ); - - assert_ok!(result); - assert!(result.unwrap()); - }); -} diff --git a/frame/zk-verifier/src/tests/e2e/statistics_tests.rs b/frame/zk-verifier/src/tests/e2e/statistics_tests.rs deleted file mode 100644 index 7f0a3fe4..00000000 --- a/frame/zk-verifier/src/tests/e2e/statistics_tests.rs +++ /dev/null @@ -1,381 +0,0 @@ -//! Tests for Statistics tracking -//! -//! These tests verify that VerificationStats storage is updated correctly - -use crate::{ - Error, VerificationStats, - mock::*, - types::{CircuitId, ProofSystem}, -}; -use frame_support::{BoundedVec, assert_noop, assert_ok, pallet_prelude::ConstU32}; - -// ============================================================================ -// Helper Functions -// ============================================================================ - -fn sample_verification_key() -> Vec { - let mut vk = Vec::with_capacity(512); - vk.extend_from_slice(&[1u8; 64]); - vk.extend_from_slice(&[2u8; 128]); - vk.extend_from_slice(&[3u8; 128]); - vk.extend_from_slice(&[4u8; 128]); - vk.extend_from_slice(&[5u8; 64]); - vk -} - -fn sample_proof() -> Vec { - let mut proof = Vec::with_capacity(256); - proof.extend_from_slice(&[10u8; 64]); - proof.extend_from_slice(&[11u8; 128]); - proof.extend_from_slice(&[12u8; 64]); - proof -} - -fn setup_circuit(circuit_id: CircuitId) { - let vk = sample_verification_key(); - assert_ok!(ZkVerifier::register_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1, - vk, - ProofSystem::Groth16, - )); -} - -// ============================================================================ -// Initial State Tests -// ============================================================================ - -#[test] -fn statistics_start_at_zero() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - setup_circuit(circuit_id); - - let stats = VerificationStats::::get(circuit_id, 1); - assert_eq!(stats.total_verifications, 0); - assert_eq!(stats.successful_verifications, 0); - assert_eq!(stats.failed_verifications, 0); - }); -} - -// ============================================================================ -// Verification Success Tests -// ============================================================================ - -#[test] -fn statistics_increment_on_successful_verification() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - setup_circuit(circuit_id); - - let proof = sample_proof(); - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - // Verify proof - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - )); - - // Check statistics - let stats = VerificationStats::::get(circuit_id, 1); - assert_eq!(stats.total_verifications, 1); - assert_eq!(stats.successful_verifications, 1); - assert_eq!(stats.failed_verifications, 0); - }); -} - -#[test] -fn statistics_increment_on_multiple_verifications() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - setup_circuit(circuit_id); - - // Perform 3 verifications - for _ in 0..3 { - let proof = sample_proof(); - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - )); - } - - // Check statistics - let stats = VerificationStats::::get(circuit_id, 1); - assert_eq!(stats.total_verifications, 3); - assert_eq!(stats.successful_verifications, 3); - assert_eq!(stats.failed_verifications, 0); - }); -} - -// ============================================================================ -// Verification Failure Tests -// ============================================================================ - -#[test] -fn statistics_track_circuit_not_found() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(999); - // Don't setup circuit - - let proof = sample_proof(); - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - assert_noop!( - ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - ), - Error::::CircuitNotFound - ); - - // Statistics should remain at zero since circuit doesn't exist - let stats = VerificationStats::::get(circuit_id, 1); - assert_eq!(stats.total_verifications, 0); - assert_eq!(stats.successful_verifications, 0); - assert_eq!(stats.failed_verifications, 0); - }); -} - -#[test] -fn statistics_track_invalid_proof() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - setup_circuit(circuit_id); - - let proof = vec![]; // Empty proof - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - assert_noop!( - ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - ), - Error::::EmptyProof - ); - - // Statistics should not increment on validation errors - let stats = VerificationStats::::get(circuit_id, 1); - assert_eq!(stats.total_verifications, 0); - }); -} - -// ============================================================================ -// Per-Circuit Statistics Tests -// ============================================================================ - -#[test] -fn statistics_are_per_circuit() { - new_test_ext().execute_with(|| { - let circuit_id_1 = CircuitId(1); - let circuit_id_2 = CircuitId(2); - - setup_circuit(circuit_id_1); - setup_circuit(circuit_id_2); - - // Verify proof for circuit 1 - let proof = sample_proof(); - let proof_bounded: BoundedVec::MaxProofSize> = - proof.clone().try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id_1, - proof_bounded, - inputs_bounded.clone() - )); - - // Verify proof for circuit 2 (twice) - for _ in 0..2 { - let proof_bounded: BoundedVec::MaxProofSize> = - proof.clone().try_into().expect("proof too large"); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id_2, - proof_bounded, - inputs_bounded.clone() - )); - } - - // Check circuit 1 stats - let stats1 = VerificationStats::::get(circuit_id_1, 1); - assert_eq!(stats1.total_verifications, 1); - assert_eq!(stats1.successful_verifications, 1); - - // Check circuit 2 stats - let stats2 = VerificationStats::::get(circuit_id_2, 1); - assert_eq!(stats2.total_verifications, 2); - assert_eq!(stats2.successful_verifications, 2); - }); -} - -// ============================================================================ -// Statistics Persistence Tests -// ============================================================================ - -#[test] -fn statistics_persist_across_blocks() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - setup_circuit(circuit_id); - - let proof = sample_proof(); - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - // Verify in block 1 - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded.clone(), - inputs_bounded.clone() - )); - - // Advance to block 10 - run_to_block(10); - - // Verify again - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - )); - - // Statistics should accumulate - let stats = VerificationStats::::get(circuit_id, 1); - assert_eq!(stats.total_verifications, 2); - assert_eq!(stats.successful_verifications, 2); - }); -} - -// ============================================================================ -// Statistics After VK Removal Tests -// ============================================================================ - -#[test] -fn statistics_remain_after_vk_removal() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - setup_circuit(circuit_id); - - let proof = sample_proof(); - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - // Verify proof - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - )); - - // Remove VK - assert_ok!(ZkVerifier::remove_verification_key( - RuntimeOrigin::root(), - circuit_id, - 1 - )); - - // Statistics should still exist - let stats = VerificationStats::::get(circuit_id, 1); - assert_eq!(stats.total_verifications, 1); - assert_eq!(stats.successful_verifications, 1); - }); -} - -// ============================================================================ -// High Volume Tests -// ============================================================================ - -#[test] -fn statistics_handle_high_volume() { - new_test_ext().execute_with(|| { - let circuit_id = CircuitId(1); - setup_circuit(circuit_id); - - // Perform 100 verifications - for _ in 0..100 { - let proof = sample_proof(); - let proof_bounded: BoundedVec::MaxProofSize> = - proof.try_into().expect("proof too large"); - let inputs_bounded: BoundedVec< - BoundedVec>, - ::MaxPublicInputs, - > = vec![[1u8; 32].to_vec().try_into().unwrap()] - .try_into() - .unwrap(); - - assert_ok!(ZkVerifier::verify_proof( - RuntimeOrigin::signed(1), - circuit_id, - proof_bounded, - inputs_bounded - )); - } - - let stats = VerificationStats::::get(circuit_id, 1); - assert_eq!(stats.total_verifications, 100); - assert_eq!(stats.successful_verifications, 100); - assert_eq!(stats.failed_verifications, 0); - }); -} diff --git a/frame/zk-verifier/src/tests/integration/infrastructure_tests.rs b/frame/zk-verifier/src/tests/integration/infrastructure_tests.rs new file mode 100644 index 00000000..5ed8ced4 --- /dev/null +++ b/frame/zk-verifier/src/tests/integration/infrastructure_tests.rs @@ -0,0 +1,73 @@ +//! Integration tests for infrastructure adapters and repositories + +#[cfg(test)] +mod adapter_tests { + use crate::{ + domain::{ + entities::{Proof, VerificationKey}, + value_objects::{ProofSystem, PublicInputs}, + }, + infrastructure::adapters::{ProofAdapter, PublicInputsAdapter, VerificationKeyAdapter}, + }; + + #[test] + fn public_inputs_adapter_converts_inputs_without_data_loss() { + let inputs = PublicInputs::new(vec![vec![1u8; 32], vec![2u8; 32]]).unwrap(); + let primitive = PublicInputsAdapter::to_primitive(&inputs); + + assert_eq!(primitive.len(), 2); + assert_eq!(primitive.inputs[0], [1u8; 32]); + assert_eq!(primitive.inputs[1], [2u8; 32]); + } + + #[test] + fn proof_adapter_preserves_bytes() { + let proof = Proof::new(vec![9u8; 64]).unwrap(); + let primitive = ProofAdapter::to_primitive(&proof); + + assert_eq!(primitive.as_bytes(), proof.data()); + } + + #[test] + fn verification_key_adapter_preserves_bytes() { + let vk = VerificationKey::new(vec![7u8; 512], ProofSystem::Groth16).unwrap(); + let primitive = VerificationKeyAdapter::to_primitive(&vk); + + assert_eq!(primitive.as_bytes(), vk.data()); + } +} + +#[cfg(test)] +mod statistics_repository_tests { + use crate::{ + domain::{repositories::StatisticsRepository, value_objects::CircuitId}, + infrastructure::repositories::FrameStatisticsRepository, + mock, + }; + use sp_io::TestExternalities; + use sp_runtime::BuildStorage; + + #[test] + fn frame_statistics_repository_updates_and_reads_stats() { + let storage = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + let mut ext = TestExternalities::new(storage); + + ext.execute_with(|| { + let repo = FrameStatisticsRepository::::new(); + let circuit = CircuitId::TRANSFER; + let version = 1; + + repo.increment_verifications(circuit, version).unwrap(); + repo.increment_verifications(circuit, version).unwrap(); + repo.increment_successes(circuit, version).unwrap(); + repo.increment_failures(circuit, version).unwrap(); + + let stats = repo.get_stats(circuit, version).unwrap(); + assert_eq!(stats.total_verifications, 2); + assert_eq!(stats.successful_verifications, 1); + assert_eq!(stats.failed_verifications, 1); + }); + } +} diff --git a/frame/zk-verifier/src/tests/integration/mod.rs b/frame/zk-verifier/src/tests/integration/mod.rs index 01e447c9..8a535813 100644 --- a/frame/zk-verifier/src/tests/integration/mod.rs +++ b/frame/zk-verifier/src/tests/integration/mod.rs @@ -1,3 +1,4 @@ //! Integration tests module +pub mod infrastructure_tests; pub mod use_case_tests; diff --git a/frame/zk-verifier/src/tests/integration/use_case_tests.rs b/frame/zk-verifier/src/tests/integration/use_case_tests.rs index c54a9962..bf370f98 100644 --- a/frame/zk-verifier/src/tests/integration/use_case_tests.rs +++ b/frame/zk-verifier/src/tests/integration/use_case_tests.rs @@ -3,163 +3,138 @@ //! These tests use mock repositories to test use case logic without FRAME #[cfg(test)] -mod register_vk_tests { +mod verify_proof_tests { use crate::{ - application::{ - commands::RegisterVkCommand, errors::ApplicationError, - use_cases::RegisterVerificationKeyUseCase, - }, + application::{commands::VerifyProofCommand, use_cases::VerifyProofUseCase}, domain::{ - entities::VerificationKey, - services::DefaultVkValidator, + entities::{Proof, VerificationKey}, + errors::DomainError, + services::ProofValidator, + value_objects::PublicInputs, value_objects::{CircuitId, ProofSystem}, }, - tests::mocks::MockVkRepository, + tests::mocks::{MockProofValidator, MockStatisticsRepository, MockVkRepository}, }; use alloc::boxed::Box; - #[test] - fn register_vk_works() { - let repo = MockVkRepository::new(); - let validator = Box::new(DefaultVkValidator); - let use_case = RegisterVerificationKeyUseCase::new(repo, validator); - - let data = vec![1u8; 512]; - let command = RegisterVkCommand { - circuit_id: CircuitId::TRANSFER, - version: 1, - data, - system: ProofSystem::Groth16, - }; - - let result = use_case.execute(command); - assert!(result.is_ok()); + struct VkPrefixValidator { + expected_first_byte: u8, } - #[test] - fn register_vk_rejects_empty_data() { - let repo = MockVkRepository::new(); - let validator = Box::new(DefaultVkValidator); - let use_case = RegisterVerificationKeyUseCase::new(repo, validator); - - let command = RegisterVkCommand { - circuit_id: CircuitId::TRANSFER, - version: 1, - data: vec![], - system: ProofSystem::Groth16, - }; - - let result = use_case.execute(command); - assert!(matches!(result, Err(ApplicationError::Domain(_)))); + impl ProofValidator for VkPrefixValidator { + fn verify( + &self, + vk: &VerificationKey, + _proof: &Proof, + _public_inputs: &PublicInputs, + ) -> Result { + Ok(vk + .data() + .first() + .copied() + .map(|value| value == self.expected_first_byte) + .unwrap_or(false)) + } } #[test] - fn register_vk_rejects_too_large() { - let repo = MockVkRepository::new(); - let validator = Box::new(DefaultVkValidator); - let use_case = RegisterVerificationKeyUseCase::new(repo, validator); + fn verify_proof_works() { + let vk = VerificationKey::new(vec![1u8; 512], ProofSystem::Groth16).unwrap(); + let vk_repo = MockVkRepository::with_vk(CircuitId::TRANSFER, vk); + let stats = MockStatisticsRepository::new(); + let validator = Box::new(MockProofValidator::always_valid()); - let command = RegisterVkCommand { + let use_case = VerifyProofUseCase::new(vk_repo, stats, validator); + + let command = VerifyProofCommand { circuit_id: CircuitId::TRANSFER, - version: 1, - data: vec![1u8; 100_001], - system: ProofSystem::Groth16, + version: None, + proof: vec![1u8; 256], + public_inputs: vec![vec![1u8; 32]], }; let result = use_case.execute(command); - assert!(matches!(result, Err(ApplicationError::Domain(_)))); + assert_eq!(result, Ok(true)); } #[test] - fn register_vk_rejects_duplicate() { + fn verify_proof_fails_for_invalid_proof() { let vk = VerificationKey::new(vec![1u8; 512], ProofSystem::Groth16).unwrap(); - let repo = MockVkRepository::with_vk(CircuitId::TRANSFER, vk); - let validator = Box::new(DefaultVkValidator); - let use_case = RegisterVerificationKeyUseCase::new(repo, validator); + let vk_repo = MockVkRepository::with_vk(CircuitId::TRANSFER, vk); + let stats = MockStatisticsRepository::new(); + let validator = Box::new(MockProofValidator::always_invalid()); + + let use_case = VerifyProofUseCase::new(vk_repo, stats, validator); - let command = RegisterVkCommand { + let command = VerifyProofCommand { circuit_id: CircuitId::TRANSFER, - version: 1, - data: vec![2u8; 512], - system: ProofSystem::Groth16, + version: None, + proof: vec![1u8; 256], + public_inputs: vec![vec![1u8; 32]], }; let result = use_case.execute(command); - assert!(matches!( - result, - Err(ApplicationError::CircuitAlreadyExists) - )); + assert_eq!(result, Ok(false)); } -} - -#[cfg(test)] -mod remove_vk_tests { - use crate::{ - application::{ - commands::RemoveVkCommand, errors::ApplicationError, - use_cases::RemoveVerificationKeyUseCase, - }, - domain::{ - entities::VerificationKey, - value_objects::{CircuitId, ProofSystem}, - }, - tests::mocks::MockVkRepository, - }; #[test] - fn remove_vk_works() { - let vk = VerificationKey::new(vec![1u8; 512], ProofSystem::Groth16).unwrap(); - let repo = MockVkRepository::with_vk(CircuitId::TRANSFER, vk); - let use_case = RemoveVerificationKeyUseCase::new(repo); - - let command = RemoveVkCommand { - circuit_id: CircuitId::TRANSFER, - version: Some(1), - }; + fn verify_proof_uses_active_version_when_not_specified() { + let vk_repo = MockVkRepository::new(); + vk_repo.insert_vk( + CircuitId::TRANSFER, + 1, + VerificationKey::new(vec![1u8; 512], ProofSystem::Groth16).unwrap(), + ); + vk_repo.insert_vk( + CircuitId::TRANSFER, + 2, + VerificationKey::new(vec![2u8; 512], ProofSystem::Groth16).unwrap(), + ); + vk_repo.set_active_version(CircuitId::TRANSFER, 2); - let result = use_case.execute(command); - assert!(result.is_ok()); - } + let stats = MockStatisticsRepository::new(); + let validator = Box::new(VkPrefixValidator { + expected_first_byte: 2, + }); - #[test] - fn remove_vk_rejects_nonexistent() { - let repo = MockVkRepository::new(); - let use_case = RemoveVerificationKeyUseCase::new(repo); + let use_case = VerifyProofUseCase::new(vk_repo, stats, validator); - let command = RemoveVkCommand { + let command = VerifyProofCommand { circuit_id: CircuitId::TRANSFER, - version: Some(1), + version: None, + proof: vec![1u8; 256], + public_inputs: vec![vec![1u8; 32]], }; let result = use_case.execute(command); - assert_eq!(result, Err(ApplicationError::CircuitNotFound)); + assert_eq!(result, Ok(true)); } -} - -#[cfg(test)] -mod verify_proof_tests { - use crate::{ - application::{commands::VerifyProofCommand, use_cases::VerifyProofUseCase}, - domain::{ - entities::VerificationKey, - value_objects::{CircuitId, ProofSystem}, - }, - tests::mocks::{MockProofValidator, MockStatisticsRepository, MockVkRepository}, - }; - use alloc::boxed::Box; #[test] - fn verify_proof_works() { - let vk = VerificationKey::new(vec![1u8; 512], ProofSystem::Groth16).unwrap(); - let vk_repo = MockVkRepository::with_vk(CircuitId::TRANSFER, vk); + fn verify_proof_supports_legacy_version_when_explicitly_requested() { + let vk_repo = MockVkRepository::new(); + vk_repo.insert_vk( + CircuitId::TRANSFER, + 1, + VerificationKey::new(vec![7u8; 512], ProofSystem::Groth16).unwrap(), + ); + vk_repo.insert_vk( + CircuitId::TRANSFER, + 2, + VerificationKey::new(vec![9u8; 512], ProofSystem::Groth16).unwrap(), + ); + vk_repo.set_active_version(CircuitId::TRANSFER, 2); + let stats = MockStatisticsRepository::new(); - let validator = Box::new(MockProofValidator::always_valid()); + let validator = Box::new(VkPrefixValidator { + expected_first_byte: 7, + }); let use_case = VerifyProofUseCase::new(vk_repo, stats, validator); let command = VerifyProofCommand { circuit_id: CircuitId::TRANSFER, - version: None, + version: Some(1), proof: vec![1u8; 256], public_inputs: vec![vec![1u8; 32]], }; @@ -169,22 +144,30 @@ mod verify_proof_tests { } #[test] - fn verify_proof_fails_for_invalid_proof() { - let vk = VerificationKey::new(vec![1u8; 512], ProofSystem::Groth16).unwrap(); - let vk_repo = MockVkRepository::with_vk(CircuitId::TRANSFER, vk); - let stats = MockStatisticsRepository::new(); - let validator = Box::new(MockProofValidator::always_invalid()); + fn verify_proof_fails_for_unknown_version() { + let vk_repo = MockVkRepository::new(); + vk_repo.insert_vk( + CircuitId::TRANSFER, + 1, + VerificationKey::new(vec![7u8; 512], ProofSystem::Groth16).unwrap(), + ); + vk_repo.set_active_version(CircuitId::TRANSFER, 1); + let stats = MockStatisticsRepository::new(); + let validator = Box::new(MockProofValidator::always_valid()); let use_case = VerifyProofUseCase::new(vk_repo, stats, validator); let command = VerifyProofCommand { circuit_id: CircuitId::TRANSFER, - version: None, + version: Some(999), proof: vec![1u8; 256], public_inputs: vec![vec![1u8; 32]], }; let result = use_case.execute(command); - assert_eq!(result, Ok(false)); + assert_eq!( + result, + Err(crate::application::errors::ApplicationError::CircuitNotFound) + ); } } diff --git a/frame/zk-verifier/src/tests/mocks/repositories.rs b/frame/zk-verifier/src/tests/mocks/repositories.rs index 556b82d7..2ef0a76a 100644 --- a/frame/zk-verifier/src/tests/mocks/repositories.rs +++ b/frame/zk-verifier/src/tests/mocks/repositories.rs @@ -33,6 +33,19 @@ impl MockVkRepository { repo.active_versions.borrow_mut().push((circuit_id, 1)); repo } + + pub fn insert_vk(&self, circuit_id: CircuitId, version: u32, vk: VerificationKey) { + self.storage.borrow_mut().push((circuit_id, version, vk)); + } + + pub fn set_active_version(&self, circuit_id: CircuitId, version: u32) { + let mut active_versions = self.active_versions.borrow_mut(); + if let Some((_, active)) = active_versions.iter_mut().find(|(id, _)| *id == circuit_id) { + *active = version; + return; + } + active_versions.push((circuit_id, version)); + } } impl VerificationKeyRepository for MockVkRepository { @@ -87,21 +100,6 @@ impl VerificationKeyRepository for MockVkRepository { .map(|(_, v)| *v) .ok_or(ApplicationError::CircuitNotFound) } - - fn set_active_version(&self, id: CircuitId, version: u32) -> Result<(), Self::Error> { - // Verify it exists first - if !self.exists(id, version) { - return Err(ApplicationError::CircuitNotFound); - } - - let mut versions = self.active_versions.borrow_mut(); - if let Some(entry) = versions.iter_mut().find(|(c_id, _)| *c_id == id) { - entry.1 = version; - } else { - versions.push((id, version)); - } - Ok(()) - } } /// Mock Statistics Repository diff --git a/frame/zk-verifier/src/tests/unit/domain_tests.rs b/frame/zk-verifier/src/tests/unit/domain_tests.rs index 97295556..5a2f6136 100644 --- a/frame/zk-verifier/src/tests/unit/domain_tests.rs +++ b/frame/zk-verifier/src/tests/unit/domain_tests.rs @@ -41,6 +41,16 @@ mod verification_key_tests { assert_eq!(result, Err(DomainError::InvalidVerificationKeySize)); } + + #[test] + fn verification_key_size_and_support_flags() { + let vk = VerificationKey::new(vec![1u8; 512], ProofSystem::Groth16).unwrap(); + assert_eq!(vk.size(), 512); + assert!(vk.is_supported()); + + let plonk_vk = VerificationKey::new(vec![1u8; 1024], ProofSystem::Plonk).unwrap(); + assert!(!plonk_vk.is_supported()); + } } #[cfg(test)] @@ -69,6 +79,12 @@ mod proof_tests { let result = Proof::new(data); assert_eq!(result, Err(DomainError::ProofTooLarge)); } + + #[test] + fn proof_exposes_size() { + let proof = Proof::new(vec![9u8; 128]).unwrap(); + assert_eq!(proof.size(), 128); + } } #[cfg(test)] @@ -104,6 +120,13 @@ mod public_inputs_tests { let result = PublicInputs::new(inputs); assert_eq!(result, Err(DomainError::InvalidPublicInputFormat)); } + + #[test] + fn public_inputs_empty_helpers_work() { + let empty = PublicInputs::empty(); + assert!(empty.is_empty()); + assert_eq!(empty.count(), 0); + } } #[cfg(test)] @@ -114,6 +137,9 @@ mod circuit_id_tests { fn circuit_id_constants() { assert_eq!(CircuitId::TRANSFER.value(), 1); assert_eq!(CircuitId::UNSHIELD.value(), 2); + assert_eq!(CircuitId::SHIELD.value(), 3); + assert_eq!(CircuitId::DISCLOSURE.value(), 4); + assert_eq!(CircuitId::PRIVATE_LINK.value(), 5); } #[test] @@ -121,6 +147,23 @@ mod circuit_id_tests { let id = CircuitId::new(42); assert_eq!(id.value(), 42); } + + #[test] + fn circuit_id_names_and_display() { + assert_eq!(CircuitId::TRANSFER.name(), Some("Transfer")); + assert_eq!(CircuitId::new(999).name(), None); + assert_eq!(CircuitId::TRANSFER.to_string(), "Transfer(1)"); + assert_eq!(CircuitId::new(999).to_string(), "Circuit(999)"); + } + + #[test] + fn circuit_id_conversions_work() { + let from_u32 = CircuitId::from(12u32); + assert_eq!(from_u32.value(), 12); + + let as_u32: u32 = CircuitId::UNSHIELD.into(); + assert_eq!(as_u32, 2); + } } #[cfg(test)] @@ -133,4 +176,41 @@ mod proof_system_tests { let _plonk = ProofSystem::Plonk; let _halo2 = ProofSystem::Halo2; } + + #[test] + fn proof_system_metadata_is_consistent() { + assert_eq!(ProofSystem::Groth16.as_str(), "Groth16"); + assert_eq!(ProofSystem::Plonk.as_str(), "Plonk"); + assert_eq!(ProofSystem::Halo2.as_str(), "Halo2"); + + assert!(ProofSystem::Groth16.is_supported()); + assert!(!ProofSystem::Plonk.is_supported()); + assert!(!ProofSystem::Halo2.is_supported()); + + assert_eq!(ProofSystem::Groth16.expected_vk_size_range(), (256, 10_000)); + assert_eq!(ProofSystem::Plonk.expected_vk_size_range(), (1024, 20_000)); + assert_eq!(ProofSystem::Halo2.expected_proof_size(), 512); + + assert_eq!(ProofSystem::Groth16.to_string(), "Groth16"); + } +} + +#[cfg(test)] +mod circuit_entity_tests { + use crate::domain::{entities::Circuit, value_objects::CircuitId}; + + #[test] + fn known_circuit_has_name() { + let circuit = Circuit::new(CircuitId::TRANSFER); + assert_eq!(circuit.id(), CircuitId::TRANSFER); + assert_eq!(circuit.name(), Some("Transfer")); + assert!(circuit.is_known()); + } + + #[test] + fn unknown_circuit_has_no_name() { + let circuit = Circuit::new(CircuitId::new(999)); + assert_eq!(circuit.name(), None); + assert!(!circuit.is_known()); + } } diff --git a/frame/zk-verifier/src/tests/unit/error_tests.rs b/frame/zk-verifier/src/tests/unit/error_tests.rs new file mode 100644 index 00000000..acd659be --- /dev/null +++ b/frame/zk-verifier/src/tests/unit/error_tests.rs @@ -0,0 +1,49 @@ +//! Unit tests for domain and application errors + +#[cfg(test)] +mod domain_error_tests { + use crate::domain::errors::DomainError; + + #[test] + fn domain_error_display_messages_are_stable() { + assert_eq!( + DomainError::EmptyVerificationKey.to_string(), + "Verification key cannot be empty" + ); + assert_eq!(DomainError::InvalidProof.to_string(), "Invalid proof"); + assert_eq!( + DomainError::UnsupportedProofSystem.to_string(), + "Proof system is not supported" + ); + } +} + +#[cfg(test)] +mod application_error_tests { + use crate::{application::errors::ApplicationError, domain::errors::DomainError}; + + #[test] + fn converts_from_domain_error() { + let app_error = ApplicationError::from(DomainError::InvalidPublicInputs); + assert_eq!( + app_error, + ApplicationError::Domain(DomainError::InvalidPublicInputs) + ); + } + + #[test] + fn application_error_display_messages_are_stable() { + assert_eq!( + ApplicationError::CircuitNotFound.to_string(), + "Circuit not found" + ); + assert_eq!( + ApplicationError::RepositoryError.to_string(), + "Repository operation failed" + ); + assert_eq!( + ApplicationError::Domain(DomainError::InvalidProof).to_string(), + "Domain error: Invalid proof" + ); + } +} diff --git a/frame/zk-verifier/src/tests/unit/mod.rs b/frame/zk-verifier/src/tests/unit/mod.rs index 293a1eae..27daa886 100644 --- a/frame/zk-verifier/src/tests/unit/mod.rs +++ b/frame/zk-verifier/src/tests/unit/mod.rs @@ -1,4 +1,5 @@ //! Unit tests module pub mod domain_tests; -pub mod unshield_tests; +pub mod error_tests; +pub mod types_tests; diff --git a/frame/zk-verifier/src/tests/unit/types_tests.rs b/frame/zk-verifier/src/tests/unit/types_tests.rs new file mode 100644 index 00000000..01843b7a --- /dev/null +++ b/frame/zk-verifier/src/tests/unit/types_tests.rs @@ -0,0 +1,36 @@ +//! Unit tests for pallet runtime types + +#[cfg(test)] +mod runtime_types_tests { + use crate::types::{CircuitId, ProofSystem, VerificationKeyInfo, VerificationStatistics}; + + #[test] + fn circuit_id_constants_are_stable() { + assert_eq!(CircuitId::TRANSFER.0, 1); + assert_eq!(CircuitId::UNSHIELD.0, 2); + assert_eq!(CircuitId::SHIELD.0, 3); + assert_eq!(CircuitId::DISCLOSURE.0, 4); + assert_eq!(CircuitId::PRIVATE_LINK.0, 5); + } + + #[test] + fn proof_system_default_is_groth16() { + assert_eq!(ProofSystem::default(), ProofSystem::Groth16); + } + + #[test] + fn verification_key_info_default_is_empty() { + let info: VerificationKeyInfo = VerificationKeyInfo::default(); + assert!(info.key_data.is_empty()); + assert_eq!(info.system, ProofSystem::Groth16); + assert_eq!(info.registered_at, 0u64); + } + + #[test] + fn verification_statistics_default_is_zeroed() { + let stats = VerificationStatistics::default(); + assert_eq!(stats.total_verifications, 0); + assert_eq!(stats.successful_verifications, 0); + assert_eq!(stats.failed_verifications, 0); + } +} diff --git a/frame/zk-verifier/src/tests/unit/unshield_tests.rs b/frame/zk-verifier/src/tests/unit/unshield_tests.rs deleted file mode 100644 index 00d08d3e..00000000 --- a/frame/zk-verifier/src/tests/unit/unshield_tests.rs +++ /dev/null @@ -1,291 +0,0 @@ -//! Unit tests for unshield proof verification logic -//! -//! These tests focus on the internal logic of unshield proof verification, -//! specifically validating input encoding and format. - -use crate::{domain::services::ZkVerifierPort, infrastructure::services::Groth16Verifier}; - -#[test] -fn unshield_public_inputs_encoding_test() { - // Test that public inputs are correctly encoded - // This test validates the internal logic without mocking - - let proof = vec![1u8; 256]; - let merkle_root = [0x11u8; 32]; - let nullifier = [0x22u8; 32]; - let amount = 1_000_000u128; // 1M wei - let recipient = [0x33u8; 32]; - let asset_id = 42u32; - - // In test mode, this will always return Ok(true) - // but the encoding logic still gets executed - let result = Groth16Verifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - None, - ); - - // Should succeed in test mode - assert!(result.is_ok()); - assert!(result.unwrap()); -} - -#[test] -fn unshield_amount_encoding_edge_cases() { - let proof = vec![1u8; 256]; - let merkle_root = [0u8; 32]; - let nullifier = [1u8; 32]; - let recipient = [2u8; 32]; - let asset_id = 0u32; - - // Test edge case amounts - let test_cases = vec![ - 0u128, // Zero amount - 1u128, // Minimum non-zero - u128::MAX, // Maximum u128 - u64::MAX as u128, // Maximum u64 value - ]; - - for amount in test_cases { - let result = Groth16Verifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - None, - ); - - assert!(result.is_ok(), "Failed for amount: {amount}"); - assert!(result.unwrap()); - } -} - -#[test] -fn unshield_recipient_address_formats() { - let proof = vec![1u8; 256]; - let merkle_root = [0u8; 32]; - let nullifier = [1u8; 32]; - let amount = 1000u128; - let asset_id = 0u32; - - // Test different recipient address patterns (AccountId32 = 32 bytes) - let recipients: [[u8; 32]; 4] = [ - [0x00; 32], // All zeros - [0xFF; 32], // All ones - // Substrate-like 32-byte address pattern - [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0xda, - 0x6B, 0xF2, 0x69, 0x64, 0xaf, 0x9d, 0x7e, 0xed, 0x9e, 0x03, 0xE5, 0x34, 0x15, 0xD3, - 0x7A, 0xA9, 0x60, 0x45, - ], - // Mixed pattern - [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x23, - 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, - 0x11, 0x22, 0x33, 0x44, - ], - ]; - - for recipient in recipients.iter() { - let result = Groth16Verifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - recipient, - asset_id, - None, - ); - - assert!(result.is_ok(), "Failed for recipient: {recipient:?}"); - assert!(result.unwrap()); - } -} - -#[test] -fn unshield_asset_id_ranges() { - let proof = vec![1u8; 256]; - let merkle_root = [0u8; 32]; - let nullifier = [1u8; 32]; - let amount = 1000u128; - let recipient = [0x33u8; 32]; - - // Test different asset ID ranges - let asset_ids = vec![ - 0u32, // Native asset - 1u32, // First custom asset - 255u32, // Max u8 value - 65535u32, // Max u16 value - 16777215u32, // Max u24 value - u32::MAX, // Maximum u32 value - ]; - - for asset_id in asset_ids { - let result = Groth16Verifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - None, - ); - - assert!(result.is_ok(), "Failed for asset_id: {asset_id}"); - assert!(result.unwrap()); - } -} - -#[test] -fn unshield_merkle_root_patterns() { - let proof = vec![1u8; 256]; - let nullifier = [1u8; 32]; - let amount = 1000u128; - let recipient = [0x33u8; 32]; - let asset_id = 0u32; - - // Test different merkle root patterns - let merkle_roots = [ - [0x00; 32], // All zeros (empty tree) - [0xFF; 32], // All ones - // Realistic looking hash - [ - 0xa4, 0x1c, 0x2f, 0x8d, 0x6e, 0x3b, 0x9f, 0x12, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, - 0x32, 0x10, 0x11, 0x22, - ], - // Another pattern - [ - 0x5a, 0x5a, 0x5a, 0x5a, 0xa5, 0xa5, 0xa5, 0xa5, 0x5a, 0x5a, 0x5a, 0x5a, 0xa5, 0xa5, - 0xa5, 0xa5, 0x5a, 0x5a, 0x5a, 0x5a, 0xa5, 0xa5, 0xa5, 0xa5, 0x5a, 0x5a, 0x5a, 0x5a, - 0xa5, 0xa5, 0xa5, 0xa5, - ], - ]; - - for merkle_root in merkle_roots.iter() { - let result = Groth16Verifier::verify_unshield_proof( - &proof, - merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - None, - ); - - assert!(result.is_ok(), "Failed for merkle_root: {merkle_root:?}"); - assert!(result.unwrap()); - } -} - -#[test] -fn unshield_nullifier_patterns() { - let proof = vec![1u8; 256]; - let merkle_root = [0x11u8; 32]; - let amount = 1000u128; - let recipient = [0x33u8; 32]; - let asset_id = 0u32; - - // Test different nullifier patterns - let nullifiers = [ - [0x00; 32], // All zeros - [0xFF; 32], // All ones - // Realistic looking nullifier - [ - 0xb2, 0x1d, 0x3e, 0x8c, 0x7f, 0x4a, 0xae, 0x13, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xff, - 0x02, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xff, 0xfd, 0xeb, 0xc9, 0xa7, 0x85, 0x63, - 0x41, 0x20, 0x22, 0x33, - ], - // Sequential pattern - [ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, - 0x1d, 0x1e, 0x1f, 0x20, - ], - ]; - - for nullifier in nullifiers.iter() { - let result = Groth16Verifier::verify_unshield_proof( - &proof, - &merkle_root, - nullifier, - amount, - &recipient, - asset_id, - None, - ); - - assert!(result.is_ok(), "Failed for nullifier: {nullifier:?}"); - assert!(result.unwrap()); - } -} - -#[cfg(test)] -mod validation_tests { - use super::*; - - #[test] - fn test_public_inputs_construction_logic() { - // This test validates that our public inputs construction - // follows the expected format: - // [merkle_root, nullifier, amount, recipient, asset_id] - - // Test values - let merkle_root = [0x11u8; 32]; - let nullifier = [0x22u8; 32]; - let amount = 1_000_000_000_000u128; // 1T wei - let recipient = [0x33u8; 32]; - let asset_id = 42u32; - - // Expected encoding (what the function should create internally): - - // 1. merkle_root: stays as [0x11; 32] - // 2. nullifier: stays as [0x22; 32] - // 3. amount: u128 -> [u8; 32] big-endian - let mut expected_amount = [0u8; 32]; - expected_amount[16..].copy_from_slice(&amount.to_be_bytes()); - - // 4. recipient: [u8; 32] (AccountId32) -> [u8; 32] LE field encoding (invert byte order) - let mut expected_recipient = [0u8; 32]; - for (i, b) in recipient.iter().rev().enumerate() { - expected_recipient[i] = *b; - } - - // 5. asset_id: u32 -> [u8; 32] big-endian - let mut expected_asset_id = [0u8; 32]; - expected_asset_id[28..].copy_from_slice(&asset_id.to_be_bytes()); - - // Verify the encoding is correct by examining the expected results - assert_eq!(expected_amount[16..24], amount.to_be_bytes()[0..8]); - assert_eq!(expected_amount[24..32], amount.to_be_bytes()[8..16]); - - // Verifica que la codificación LE invierte correctamente los bytes - // expected_recipient[i] == recipient[31 - i] - for (i, &b) in recipient.iter().rev().enumerate() { - assert_eq!(expected_recipient[i], b); - } - - assert_eq!(expected_asset_id[28..32], asset_id.to_be_bytes()); - - // The actual verification call (always succeeds in test mode) - let proof = vec![1u8; 256]; - let result = Groth16Verifier::verify_unshield_proof( - &proof, - &merkle_root, - &nullifier, - amount, - &recipient, - asset_id, - None, - ); - - assert!(result.is_ok()); - assert!(result.unwrap()); - } -} diff --git a/frame/zk-verifier/src/types.rs b/frame/zk-verifier/src/types.rs index 1336f54b..fadede93 100644 --- a/frame/zk-verifier/src/types.rs +++ b/frame/zk-verifier/src/types.rs @@ -90,29 +90,6 @@ impl Default for VerificationKeyInfo { } } -/// Metadata about a circuit -#[derive( - Clone, - PartialEq, - Eq, - Encode, - Decode, - MaxEncodedLen, - TypeInfo, - Debug, - Default -)] -pub struct CircuitMetadata { - /// Number of public inputs expected - pub num_public_inputs: u32, - /// Number of constraints in the circuit - pub num_constraints: u64, - /// Circuit version - pub version: u32, - /// Whether the circuit is active - pub is_active: bool, -} - /// Statistics for proof verification #[derive( Clone, diff --git a/frame/zk-verifier/src/weights.rs b/frame/zk-verifier/src/weights.rs index c6e95496..8eb7cc9e 100644 --- a/frame/zk-verifier/src/weights.rs +++ b/frame/zk-verifier/src/weights.rs @@ -35,78 +35,32 @@ use core::marker::PhantomData; /// Weight functions needed for pallet_zk_verifier. pub trait WeightInfo { fn register_verification_key() -> Weight; - fn remove_verification_key() -> Weight; fn set_active_version() -> Weight; + fn remove_verification_key() -> Weight; fn verify_proof() -> Weight; } /// Weight functions for `pallet_zk_verifier`. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - /// Storage: `ZkVerifier::VerificationKeys` (r:1 w:1) - /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `ZkVerifier::ActiveCircuitVersion` (r:1 w:1) - /// Proof: `ZkVerifier::ActiveCircuitVersion` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn register_verification_key() -> Weight { - // Proof Size summary in bytes: - // Measured: `31` - // Estimated: `11704` - // Minimum execution time: 26_000_000 picoseconds. - Weight::from_parts(27_000_000, 0) - .saturating_add(Weight::from_parts(0, 11704)) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `ZkVerifier::VerificationKeys` (r:1 w:1) - /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn remove_verification_key() -> Weight { - // Proof Size summary in bytes: - // Measured: `127` - // Estimated: `11704` - // Minimum execution time: 19_000_000 picoseconds. - Weight::from_parts(20_000_000, 0) - .saturating_add(Weight::from_parts(0, 11704)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(3)) + Weight::from_parts(10_000_000, 0) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `ZkVerifier::VerificationKeys` (r:1 w:0) - /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ZkVerifier::ActiveCircuitVersion` (r:0 w:1) - /// Proof: `ZkVerifier::ActiveCircuitVersion` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) + fn set_active_version() -> Weight { - // Proof Size summary in bytes: - // Measured: `127` - // Estimated: `11704` - // Minimum execution time: 21_000_000 picoseconds. - Weight::from_parts(22_000_000, 0) - .saturating_add(Weight::from_parts(0, 11704)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(3)) + Weight::from_parts(8_000_000, 0) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + + fn remove_verification_key() -> Weight { + Weight::from_parts(9_000_000, 0) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `ZkVerifier::ActiveCircuitVersion` (r:1 w:0) /// Proof: `ZkVerifier::ActiveCircuitVersion` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) /// Storage: `ZkVerifier::VerificationKeys` (r:1 w:0) diff --git a/primitives/zk-verifier/Cargo.toml b/primitives/zk-verifier/Cargo.toml index d54f0ac3..f02e2463 100644 --- a/primitives/zk-verifier/Cargo.toml +++ b/primitives/zk-verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "orbinum-zk-verifier" -version = "0.6.2" +version = "0.7.0" authors = ["Orbinum Network "] edition = "2021" license = "Apache-2.0 OR GPL-3.0-or-later" @@ -35,6 +35,7 @@ scale-info = { version = "2.11", default-features = false, features = ["derive"] # For parsing verification key from JSON (std only) num-bigint = { version = "0.4", default-features = false, optional = true } +sha2 = { version = "0.10", default-features = false } [dev-dependencies] ark-relations = { version = "0.5.0", default-features = false } diff --git a/primitives/zk-verifier/README.md b/primitives/zk-verifier/README.md index ff02eaa4..58bb611e 100644 --- a/primitives/zk-verifier/README.md +++ b/primitives/zk-verifier/README.md @@ -1,59 +1,24 @@ # orbinum-zk-verifier -[![crates.io](https://img.shields.io/crates/v/orbinum-zk-verifier.svg)](https://crates.io/crates/orbinum-zk-verifier) -[![Documentation](https://docs.rs/orbinum-zk-verifier/badge.svg)](https://docs.rs/orbinum-zk-verifier) +Groth16 (BN254) verification primitive for Orbinum. -On-chain Zero-Knowledge proof verification for Orbinum Network using Groth16 over BN254. +This crate implements a 3-layer clean architecture (`domain`, `application`, `infrastructure`) and does not manage on-chain verification key storage. VK resolution and versioning are handled by the `frame/zk-verifier` pallet. -## Features - -- **Fast verification**: ~8ms on-chain for private transfers -- **Small proofs**: ~200 bytes -- **no_std compatible**: Suitable for Substrate runtimes -- **Embedded verification keys**: No external storage needed -- **SnarkJS compatible**: Works with circom circuits - -## Usage - -Add to your `Cargo.toml`: - -```toml -[dependencies] -orbinum-zk-verifier = { version = "0.2", default-features = false } -``` - -### Basic Verification - -```rust -use orbinum_zk_verifier::{ - infrastructure::verification::Groth16Verifier, - domain::value_objects::{Proof, PublicInputs, VerifyingKey}, -}; - -// Load verification key (embedded or from storage) -let vk = get_transfer_vk(); +## Architecture -// Prepare inputs -let public_inputs = PublicInputs::new(vec![ - merkle_root, - nullifier1, - nullifier2, - commitment1, - commitment2, -]); - -// Verify proof -let verifier = Groth16Verifier::new(); -let result = verifier.verify(&vk, &public_inputs, &proof); - -assert!(result.is_ok()); -``` +- `domain/` + - ports (`VerifierPort`), services (`ProofValidator`), and value objects (`Proof`, `VerifyingKey`, `PublicInputs`, `VerifierError`). +- `application/` + - use cases (`VerifyProofUseCase`) and output DTOs. +- `infrastructure/` + - concrete implementation (`Groth16Verifier`) and adapters (`snarkjs_adapter`, `std` feature only). -### With Use Case Pattern +## Basic Usage ```rust use orbinum_zk_verifier::{ application::use_cases::VerifyProofUseCase, + domain::value_objects::{Proof, PublicInputs, VerifyingKey}, infrastructure::verification::Groth16Verifier, }; @@ -66,57 +31,37 @@ let result = use_case.execute( &proof, expected_input_count, ); + +assert!(result.is_ok()); ``` -### Substrate Runtime Integration +## Substrate Integration ```toml [dependencies] -orbinum-zk-verifier = { version = "0.2", default-features = false, features = ["substrate"] } -``` - -```rust -// In your pallet -use orbinum_zk_verifier::infrastructure::storage::verification_keys::get_vk_by_circuit_id; - -#[pallet::call] -impl Pallet { - pub fn verify_proof( - origin: OriginFor, - circuit_id: u8, - proof: Proof, - public_inputs: PublicInputs, - ) -> DispatchResult { - let vk = get_vk_by_circuit_id(circuit_id)?; - let verifier = Groth16Verifier::new(); - verifier.verify(&vk, &public_inputs, &proof)?; - Ok(()) - } -} +orbinum-zk-verifier = { version = "0.6.2", default-features = false, features = ["substrate"] } ``` ## Supported Circuits -| Circuit ID | Name | Public Inputs | Description | -|------------|------|---------------|-------------| -| 1 | Transfer | 5 | Private transfer (2→2) | -| 2 | Unshield | 5 | Withdrawal to public | -| 4 | Disclosure | 4 | Selective disclosure | -| 5 | Private Link | 2 | Private link dispatch verification | +| Circuit ID | Name | Public Inputs | +|---|---|---| +| 1 | transfer | 5 | +| 2 | unshield | 5 | +| 4 | disclosure | 4 | +| 5 | private_link | 2 | -## Features Flags +## Features -- `std` (default): Standard library support -- `substrate`: Substrate runtime integration (SCALE codec, TypeInfo) +- `std` (default): enables standard-library utilities and `snarkjs` adapters. +- `substrate`: enables SCALE codec integration (`parity-scale-codec`, `scale-info`) for runtime use. -## Performance +## Design Notes -| Operation | Time | Notes | -|-----------|------|-------| -| Verify Transfer | ~8ms | 5 public inputs | -| Verify Unshield | ~6ms | 4 public inputs | -| Prepare VK | ~2ms | Cacheable | +- `Groth16Verifier` implements `VerifierPort` to decouple use cases from the concrete cryptographic library. +- Structural validation (input count and minimum proof/VK size) is executed in the domain layer before cryptographic verification. +- `batch_verify` is available at the infrastructure layer for optimization scenarios. ## License -Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE2) or [GPL v3](LICENSE-GPL3) at your option. +Dual: Apache-2.0 OR GPL-3.0-or-later. diff --git a/primitives/zk-verifier/src/application/use_cases/verify_proof.rs b/primitives/zk-verifier/src/application/use_cases/verify_proof.rs index 04f651a5..0b5b8594 100644 --- a/primitives/zk-verifier/src/application/use_cases/verify_proof.rs +++ b/primitives/zk-verifier/src/application/use_cases/verify_proof.rs @@ -41,7 +41,7 @@ impl VerifyProofUseCase { /// Execute proof verification with prepared VK (optimized) pub fn execute_prepared( &self, - prepared_vk: &ark_groth16::PreparedVerifyingKey, + prepared_vk: &V::PreparedKey, public_inputs: &PublicInputs, proof: &Proof, expected_input_count: usize, @@ -60,10 +60,10 @@ impl VerifyProofUseCase { mod tests { use super::*; use alloc::rc::Rc; - use ark_bn254::{Fr as Bn254Fr, G1Affine, G2Affine}; + use ark_bn254::{Bn254, Fr as Bn254Fr, G1Affine, G2Affine}; use ark_ec::AffineRepr; use ark_ff::{BigInteger, PrimeField}; - use ark_groth16::PreparedVerifyingKey; + use ark_groth16::{PreparedVerifyingKey, VerifyingKey as ArkVerifyingKey}; use ark_serialize::CanonicalSerialize; use core::cell::RefCell; @@ -94,6 +94,8 @@ mod tests { } impl VerifierPort for MockVerifier { + type PreparedKey = PreparedVerifyingKey; + fn verify( &self, _vk: &VerifyingKey, @@ -110,7 +112,7 @@ mod tests { fn verify_prepared( &self, - _prepared_vk: &PreparedVerifyingKey, + _prepared_vk: &Self::PreparedKey, _public_inputs: &PublicInputs, _proof: &Proof, ) -> Result<(), VerifierError> { @@ -152,17 +154,27 @@ mod tests { PublicInputs::new(inputs) } + fn create_mock_ark_vk(expected_inputs: usize) -> ArkVerifyingKey { + ArkVerifyingKey { + alpha_g1: G1Affine::generator(), + beta_g2: G2Affine::generator(), + gamma_g2: G2Affine::generator(), + delta_g2: G2Affine::generator(), + gamma_abc_g1: (0..=expected_inputs) + .map(|_| G1Affine::generator()) + .collect(), + } + } + // Helper: Create mock VK fn create_mock_vk() -> VerifyingKey { - use crate::infrastructure::storage::verification_keys; - let vk = verification_keys::transfer::get_vk(); + let vk = create_mock_ark_vk(5); VerifyingKey::from_ark_vk(&vk).unwrap() } // Helper: Create mock prepared VK fn create_mock_prepared_vk() -> PreparedVerifyingKey { - use crate::infrastructure::storage::verification_keys; - let vk = verification_keys::transfer::get_vk(); + let vk = create_mock_ark_vk(5); PreparedVerifyingKey::from(vk) } diff --git a/primitives/zk-verifier/src/domain/ports/verifier_port.rs b/primitives/zk-verifier/src/domain/ports/verifier_port.rs index 48c70072..13cba9f9 100644 --- a/primitives/zk-verifier/src/domain/ports/verifier_port.rs +++ b/primitives/zk-verifier/src/domain/ports/verifier_port.rs @@ -7,6 +7,11 @@ use crate::domain::value_objects::{Proof, PublicInputs, VerifierError, Verifying /// This trait abstracts over different verification implementations, /// allowing the domain layer to remain independent of specific cryptographic libraries. pub trait VerifierPort { + /// Opaque handle for a prepared verification key. + /// + /// Each infrastructure implementation defines its own prepared-key type. + type PreparedKey; + /// Verify a zero-knowledge proof /// /// # Arguments @@ -29,7 +34,7 @@ pub trait VerifierPort { /// Prepared keys cache pairing computations for faster verification. fn verify_prepared( &self, - prepared_vk: &ark_groth16::PreparedVerifyingKey, + prepared_vk: &Self::PreparedKey, public_inputs: &PublicInputs, proof: &Proof, ) -> Result<(), VerifierError>; diff --git a/primitives/zk-verifier/src/domain/value_objects/circuit_constants.rs b/primitives/zk-verifier/src/domain/value_objects/circuit_constants.rs index 09545ff2..883f6364 100644 --- a/primitives/zk-verifier/src/domain/value_objects/circuit_constants.rs +++ b/primitives/zk-verifier/src/domain/value_objects/circuit_constants.rs @@ -42,3 +42,49 @@ pub const PER_INPUT_COST: u64 = 10_000; /// Maximum number of public inputs supported pub const MAX_PUBLIC_INPUTS: usize = 32; + +#[cfg(test)] +mod tests { + use super::*; + use core::hint::black_box; + + #[test] + fn test_circuit_ids_are_stable() { + assert_eq!(CIRCUIT_ID_TRANSFER, 1); + assert_eq!(CIRCUIT_ID_UNSHIELD, 2); + assert_eq!(CIRCUIT_ID_DISCLOSURE, 4); + assert_eq!(CIRCUIT_ID_PRIVATE_LINK, 5); + } + + #[test] + fn test_public_input_counts_are_expected() { + assert_eq!(TRANSFER_PUBLIC_INPUTS, 5); + assert_eq!(UNSHIELD_PUBLIC_INPUTS, 5); + assert_eq!(DISCLOSURE_PUBLIC_INPUTS, 4); + assert_eq!(PRIVATE_LINK_PUBLIC_INPUTS, 2); + } + + #[test] + fn test_cost_constants_are_consistent() { + let base_verification_cost = black_box(BASE_VERIFICATION_COST); + let per_input_cost = black_box(PER_INPUT_COST); + let max_public_inputs = black_box(MAX_PUBLIC_INPUTS); + + assert!(base_verification_cost > 0); + assert!(per_input_cost > 0); + assert!(max_public_inputs >= TRANSFER_PUBLIC_INPUTS); + assert!(max_public_inputs >= UNSHIELD_PUBLIC_INPUTS); + assert!(max_public_inputs >= DISCLOSURE_PUBLIC_INPUTS); + assert!(max_public_inputs >= PRIVATE_LINK_PUBLIC_INPUTS); + } + + #[test] + fn test_estimated_cost_growth_is_linear() { + let cost_0 = BASE_VERIFICATION_COST; + let cost_1 = BASE_VERIFICATION_COST + PER_INPUT_COST; + let cost_5 = BASE_VERIFICATION_COST + (5 * PER_INPUT_COST); + + assert_eq!(cost_1 - cost_0, PER_INPUT_COST); + assert_eq!(cost_5 - cost_1, 4 * PER_INPUT_COST); + } +} diff --git a/primitives/zk-verifier/src/domain/value_objects/errors.rs b/primitives/zk-verifier/src/domain/value_objects/errors.rs index a4927280..c13fa4ad 100644 --- a/primitives/zk-verifier/src/domain/value_objects/errors.rs +++ b/primitives/zk-verifier/src/domain/value_objects/errors.rs @@ -54,3 +54,64 @@ impl fmt::Display for VerifierError { #[cfg(feature = "std")] impl std::error::Error for VerifierError {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_error_equality_and_clone() { + let a = VerifierError::InvalidProof; + let b = a.clone(); + assert_eq!(a, b); + + let c = VerifierError::InvalidPublicInputCount { + expected: 5, + got: 3, + }; + let d = c.clone(); + assert_eq!(c, d); + } + + #[test] + fn test_display_messages() { + assert_eq!(VerifierError::InvalidProof.to_string(), "Invalid proof"); + assert_eq!( + VerifierError::InvalidVerifyingKey.to_string(), + "Invalid verifying key" + ); + assert_eq!( + VerifierError::InvalidPublicInput.to_string(), + "Invalid public input" + ); + assert_eq!( + VerifierError::VerificationFailed.to_string(), + "Verification failed" + ); + assert_eq!( + VerifierError::SerializationError.to_string(), + "Serialization error" + ); + assert_eq!( + VerifierError::InvalidProofSize.to_string(), + "Invalid proof size" + ); + assert_eq!( + VerifierError::InvalidVKSize.to_string(), + "Invalid verifying key size" + ); + } + + #[test] + fn test_display_dynamic_messages() { + let msg = VerifierError::InvalidPublicInputCount { + expected: 5, + got: 2, + } + .to_string(); + assert_eq!(msg, "Invalid public input count: expected 5, got 2"); + + let msg = VerifierError::InvalidCircuitId(9).to_string(); + assert_eq!(msg, "Invalid circuit ID: 9"); + } +} diff --git a/primitives/zk-verifier/src/infrastructure/adapters/snarkjs_adapter.rs b/primitives/zk-verifier/src/infrastructure/adapters/snarkjs_adapter.rs index e2ce9ab3..e49b51f5 100644 --- a/primitives/zk-verifier/src/infrastructure/adapters/snarkjs_adapter.rs +++ b/primitives/zk-verifier/src/infrastructure/adapters/snarkjs_adapter.rs @@ -47,8 +47,8 @@ pub fn parse_proof_from_snarkjs(points: SnarkjsProofPoints) -> Result VerifyingKey { - // Alpha G1 - let alpha_g1 = G1Affine::new_unchecked( - Fq::from_str( - "20491192805390485299153009773594534940189261866228447918068658471970481763042", - ) - .unwrap(), - Fq::from_str( - "9383485363053290200918347156157836566562967994039712273449902621266178545958", - ) - .unwrap(), - ); - - // Beta G2 - let beta_g2 = G2Affine::new_unchecked( - Fq2::new( - Fq::from_str( - "6375614351688725206403948262868962793625744043794305715222011528459656738731", - ) - .unwrap(), - Fq::from_str( - "4252822878758300859123897981450591353533073413197771768651442665752259397132", - ) - .unwrap(), - ), - Fq2::new( - Fq::from_str( - "10505242626370262277552901082094356697409835680220590971873171140371331206856", - ) - .unwrap(), - Fq::from_str( - "21847035105528745403288232691147584728191162732299865338377159692350059136679", - ) - .unwrap(), - ), - ); - - // Gamma G2 - let gamma_g2 = G2Affine::new_unchecked( - Fq2::new( - Fq::from_str( - "10857046999023057135944570762232829481370756359578518086990519993285655852781", - ) - .unwrap(), - Fq::from_str( - "11559732032986387107991004021392285783925812861821192530917403151452391805634", - ) - .unwrap(), - ), - Fq2::new( - Fq::from_str( - "8495653923123431417604973247489272438418190587263600148770280649306958101930", - ) - .unwrap(), - Fq::from_str( - "4082367875863433681332203403145435568316851327593401208105741076214120093531", - ) - .unwrap(), - ), - ); - - // Delta G2 - let delta_g2 = G2Affine::new_unchecked( - Fq2::new( - Fq::from_str( - "15891488507266392457506126879904299590646143497545573658070845046860313168900", - ) - .unwrap(), - Fq::from_str( - "17932936512660402537945596421514282776231371752944152874563145263497597450760", - ) - .unwrap(), - ), - Fq2::new( - Fq::from_str( - "3499547373407916183334246733416920057398428467450155425825370305197257091551", - ) - .unwrap(), - Fq::from_str( - "19552021655696733860272339514203367725306524797267373214782737566735379534814", - ) - .unwrap(), - ), - ); - - // IC points (gamma_abc_g1) - let ic_0 = G1Affine::new_unchecked( - Fq::from_str( - "6644748364555260156129836591732737035335621695080778806388447252072453264467", - ) - .unwrap(), - Fq::from_str( - "10610642146156976560376495047404935702633534022449707341891080731675746231253", - ) - .unwrap(), - ); - - let ic_1 = G1Affine::new_unchecked( - Fq::from_str("183134578389976222585405159394158569606775062068994230403943610204972975767") - .unwrap(), - Fq::from_str( - "15054648499092315861645561422738683794978556709561861191491884453856553469905", - ) - .unwrap(), - ); - - let ic_2 = G1Affine::new_unchecked( - Fq::from_str("493520193857930353251666059324898401214741181272007719287807840978613988477") - .unwrap(), - Fq::from_str( - "10264986083735592766931550000553739417058119237687694821992136482252428841397", - ) - .unwrap(), - ); - - let ic_3 = G1Affine::new_unchecked( - Fq::from_str( - "5492305139060809765159611331626218846385097649258248123630030121029127011914", - ) - .unwrap(), - Fq::from_str( - "5446602556882136775071030748686569953554848855704744256354078506327754517408", - ) - .unwrap(), - ); - - let ic_4 = G1Affine::new_unchecked( - Fq::from_str( - "6683987345206773829248627085474671868955168998214070623081328408136754507564", - ) - .unwrap(), - Fq::from_str( - "9802225858000274674026213007893473356239169831801995329067584222663521521648", - ) - .unwrap(), - ); - - let gamma_abc_g1 = vec![ic_0, ic_1, ic_2, ic_3, ic_4]; - - VerifyingKey { - alpha_g1, - beta_g2, - gamma_g2, - delta_g2, - gamma_abc_g1, - } -} - -/// Returns the verification key as compressed bytes for genesis/storage -pub fn get_vk_bytes() -> alloc::vec::Vec { - let vk = get_vk(); - let mut bytes = alloc::vec::Vec::new(); - vk.serialize_compressed(&mut bytes) - .expect("VK serialization should not fail"); - bytes -} diff --git a/primitives/zk-verifier/src/infrastructure/storage/verification_keys/mod.rs b/primitives/zk-verifier/src/infrastructure/storage/verification_keys/mod.rs deleted file mode 100644 index f1e39f50..00000000 --- a/primitives/zk-verifier/src/infrastructure/storage/verification_keys/mod.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! # Verification Keys -//! -//! This module contains hardcoded verification keys for ZK circuits. -//! Keys are embedded at compile-time for no_std blockchain runtime. -//! -//! ## Available VKs -//! -//! - `transfer`: Private transfer circuit verification key -//! - `unshield`: Unshield circuit verification key -//! -//! ## Usage -//! -//! ```rust,ignore -//! use fp_zk_verifier::vk::{get_transfer_vk, get_unshield_vk}; -//! -//! let vk = get_transfer_vk(); -//! let vk_bytes = get_transfer_vk_bytes(); -//! ``` - -pub mod disclosure; -pub mod private_link; -pub mod registry; -pub mod transfer; -pub mod unshield; - -// Re-export commonly used functions -pub use disclosure::{get_vk as get_disclosure_vk, get_vk_bytes as get_disclosure_vk_bytes}; -pub use private_link::{get_vk as get_private_link_vk, get_vk_bytes as get_private_link_vk_bytes}; -pub use transfer::{get_vk as get_transfer_vk, get_vk_bytes as get_transfer_vk_bytes}; -pub use unshield::{get_vk as get_unshield_vk, get_vk_bytes as get_unshield_vk_bytes}; - -// Re-export registry functions -pub use registry::{get_public_input_count, get_vk_by_circuit_id, validate_public_input_count}; diff --git a/primitives/zk-verifier/src/infrastructure/storage/verification_keys/private_link.rs b/primitives/zk-verifier/src/infrastructure/storage/verification_keys/private_link.rs deleted file mode 100644 index 225f0051..00000000 --- a/primitives/zk-verifier/src/infrastructure/storage/verification_keys/private_link.rs +++ /dev/null @@ -1,161 +0,0 @@ -//! Auto-generated Verification Key for private_link circuit -//! Generated on: 2026-03-08 04:16:43 -03 -//! Source: artifacts/verification_key_private_link.json -//! -//! DO NOT EDIT MANUALLY - Run sync-circuit-artifacts.sh to regenerate - -use alloc::vec; -use ark_bn254::{Bn254, Fq, Fq2, G1Affine, G2Affine}; -use ark_groth16::VerifyingKey; -use ark_serialize::CanonicalSerialize; -use ark_std::str::FromStr; - -use crate::domain::value_objects::circuit_constants::{ - CIRCUIT_ID_PRIVATE_LINK, PRIVATE_LINK_PUBLIC_INPUTS, -}; - -/// Circuit ID for private_link (re-exported from domain) -pub const CIRCUIT_ID: u8 = CIRCUIT_ID_PRIVATE_LINK; - -/// Number of public inputs for this circuit (re-exported from domain) -pub const NUM_PUBLIC_INPUTS: usize = PRIVATE_LINK_PUBLIC_INPUTS; - -/// Creates the verification key for the private_link circuit -pub fn get_vk() -> VerifyingKey { - // Alpha G1 - let alpha_g1 = G1Affine::new_unchecked( - Fq::from_str( - "20491192805390485299153009773594534940189261866228447918068658471970481763042", - ) - .unwrap(), - Fq::from_str( - "9383485363053290200918347156157836566562967994039712273449902621266178545958", - ) - .unwrap(), - ); - - // Beta G2 - let beta_g2 = G2Affine::new_unchecked( - Fq2::new( - Fq::from_str( - "6375614351688725206403948262868962793625744043794305715222011528459656738731", - ) - .unwrap(), - Fq::from_str( - "4252822878758300859123897981450591353533073413197771768651442665752259397132", - ) - .unwrap(), - ), - Fq2::new( - Fq::from_str( - "10505242626370262277552901082094356697409835680220590971873171140371331206856", - ) - .unwrap(), - Fq::from_str( - "21847035105528745403288232691147584728191162732299865338377159692350059136679", - ) - .unwrap(), - ), - ); - - // Gamma G2 - let gamma_g2 = G2Affine::new_unchecked( - Fq2::new( - Fq::from_str( - "10857046999023057135944570762232829481370756359578518086990519993285655852781", - ) - .unwrap(), - Fq::from_str( - "11559732032986387107991004021392285783925812861821192530917403151452391805634", - ) - .unwrap(), - ), - Fq2::new( - Fq::from_str( - "8495653923123431417604973247489272438418190587263600148770280649306958101930", - ) - .unwrap(), - Fq::from_str( - "4082367875863433681332203403145435568316851327593401208105741076214120093531", - ) - .unwrap(), - ), - ); - - // Delta G2 - let delta_g2 = G2Affine::new_unchecked( - Fq2::new( - Fq::from_str( - "10017835699183705898959917136102492144834257686815545884930858162381601990409", - ) - .unwrap(), - Fq::from_str( - "931443350602532557937041795504460956825516039268595684065058096905709951092", - ) - .unwrap(), - ), - Fq2::new( - Fq::from_str( - "5422627372590078623437693585780294233708885223015825213203251737533870277450", - ) - .unwrap(), - Fq::from_str( - "283187716635581259790955746698071893681197794322898971002784670258068039290", - ) - .unwrap(), - ), - ); - - // IC points (gamma_abc_g1) - let ic_0 = G1Affine::new_unchecked( - Fq::from_str( - "3873119679218392807840426919143613017451812535072496563784167058388285983796", - ) - .unwrap(), - Fq::from_str( - "10505355579916155463408957159060265767093857712918941866986236626237127061560", - ) - .unwrap(), - ); - - let ic_1 = G1Affine::new_unchecked( - Fq::from_str( - "18131567997101244009572074907063325087087408880313227019458175660820403352905", - ) - .unwrap(), - Fq::from_str( - "5962015595236182725194357339839307542131750029760038891759441583113334779284", - ) - .unwrap(), - ); - - let ic_2 = G1Affine::new_unchecked( - Fq::from_str( - "21262268839712470390098720510727633177967955527172441240314180229632430290739", - ) - .unwrap(), - Fq::from_str( - "17910414284850894487807511632049431548124824989721737209651521059918980252537", - ) - .unwrap(), - ); - - let gamma_abc_g1 = vec![ic_0, ic_1, ic_2]; - - VerifyingKey { - alpha_g1, - beta_g2, - gamma_g2, - delta_g2, - gamma_abc_g1, - } -} - -/// Returns the verification key as compressed bytes for genesis/storage -pub fn get_vk_bytes() -> alloc::vec::Vec { - let vk = get_vk(); - let mut bytes = alloc::vec::Vec::new(); - vk.serialize_compressed(&mut bytes) - .expect("VK serialization should not fail"); - bytes -} diff --git a/primitives/zk-verifier/src/infrastructure/storage/verification_keys/registry.rs b/primitives/zk-verifier/src/infrastructure/storage/verification_keys/registry.rs deleted file mode 100644 index 779b6347..00000000 --- a/primitives/zk-verifier/src/infrastructure/storage/verification_keys/registry.rs +++ /dev/null @@ -1,201 +0,0 @@ -//! Centralized Verification Key Registry -//! -//! This module provides runtime lookup of verification keys by circuit ID. - -use crate::domain::value_objects::{ - circuit_constants::{ - CIRCUIT_ID_DISCLOSURE, CIRCUIT_ID_PRIVATE_LINK, CIRCUIT_ID_TRANSFER, CIRCUIT_ID_UNSHIELD, - DISCLOSURE_PUBLIC_INPUTS, PRIVATE_LINK_PUBLIC_INPUTS, TRANSFER_PUBLIC_INPUTS, - UNSHIELD_PUBLIC_INPUTS, - }, - errors::VerifierError, -}; -use ark_bn254::Bn254; -use ark_groth16::VerifyingKey; - -/// Get verification key by circuit ID -/// -/// # Arguments -/// -/// * `circuit_id` - The circuit identifier (1 = transfer, 2 = unshield, 4 = disclosure) -/// -/// # Returns -/// -/// `Ok(VerifyingKey)` if the circuit ID is valid, `Err(InvalidCircuitId)` otherwise -/// -/// # Example -/// -/// ```rust,ignore -/// use fp_zk_verifier::vk::registry::get_vk_by_circuit_id; -/// -/// let vk = get_vk_by_circuit_id(1)?; // Transfer VK -/// let vk = get_vk_by_circuit_id(4)?; // Disclosure VK -/// ``` -pub fn get_vk_by_circuit_id(circuit_id: u8) -> Result, VerifierError> { - match circuit_id { - CIRCUIT_ID_TRANSFER => Ok(super::transfer::get_vk()), - CIRCUIT_ID_UNSHIELD => Ok(super::unshield::get_vk()), - CIRCUIT_ID_DISCLOSURE => Ok(super::disclosure::get_vk()), - CIRCUIT_ID_PRIVATE_LINK => Ok(super::private_link::get_vk()), - _ => Err(VerifierError::InvalidCircuitId(circuit_id)), - } -} - -/// Get expected public input count by circuit ID -/// -/// # Arguments -/// -/// * `circuit_id` - The circuit identifier -/// -/// # Returns -/// -/// `Ok(usize)` with the expected number of public inputs, or `Err(InvalidCircuitId)` -/// -/// # Example -/// -/// ```rust,ignore -/// use fp_zk_verifier::vk::registry::get_public_input_count; -/// -/// let count = get_public_input_count(1)?; // 5 for transfer -/// assert_eq!(count, 5); -/// ``` -pub fn get_public_input_count(circuit_id: u8) -> Result { - match circuit_id { - CIRCUIT_ID_TRANSFER => Ok(TRANSFER_PUBLIC_INPUTS), - CIRCUIT_ID_UNSHIELD => Ok(UNSHIELD_PUBLIC_INPUTS), - CIRCUIT_ID_DISCLOSURE => Ok(DISCLOSURE_PUBLIC_INPUTS), - CIRCUIT_ID_PRIVATE_LINK => Ok(PRIVATE_LINK_PUBLIC_INPUTS), - _ => Err(VerifierError::InvalidCircuitId(circuit_id)), - } -} - -/// Validate that public inputs match expected count for circuit -/// -/// # Arguments -/// -/// * `circuit_id` - The circuit identifier -/// * `actual_count` - The actual number of public inputs provided -/// -/// # Returns -/// -/// `Ok(())` if counts match, `Err(InvalidPublicInput)` otherwise -pub fn validate_public_input_count( - circuit_id: u8, - actual_count: usize, -) -> Result<(), VerifierError> { - let expected_count = get_public_input_count(circuit_id)?; - if actual_count == expected_count { - Ok(()) - } else { - Err(VerifierError::InvalidPublicInput) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_get_vk_by_circuit_id_transfer() { - let result = get_vk_by_circuit_id(CIRCUIT_ID_TRANSFER); - assert!(result.is_ok()); - let vk = result.unwrap(); - assert_eq!(vk.gamma_abc_g1.len(), TRANSFER_PUBLIC_INPUTS + 1); - } - - #[test] - fn test_get_vk_by_circuit_id_unshield() { - let result = get_vk_by_circuit_id(CIRCUIT_ID_UNSHIELD); - assert!(result.is_ok()); - let vk = result.unwrap(); - assert_eq!(vk.gamma_abc_g1.len(), UNSHIELD_PUBLIC_INPUTS + 1); - } - - #[test] - fn test_get_vk_by_circuit_id_disclosure() { - let result = get_vk_by_circuit_id(CIRCUIT_ID_DISCLOSURE); - assert!(result.is_ok()); - let vk = result.unwrap(); - // Disclosure circuit has 4 public inputs, so gamma_abc_g1 has 5 elements (4+1) - assert_eq!(vk.gamma_abc_g1.len(), DISCLOSURE_PUBLIC_INPUTS + 1); - } - - #[test] - fn test_get_vk_by_circuit_id_invalid() { - let result = get_vk_by_circuit_id(99); - assert!(result.is_err()); - assert!(matches!(result, Err(VerifierError::InvalidCircuitId(99)))); - } - - #[test] - fn test_get_vk_by_circuit_id_zero() { - let result = get_vk_by_circuit_id(0); - assert!(result.is_err()); - } - - #[test] - fn test_get_public_input_count_transfer() { - let result = get_public_input_count(CIRCUIT_ID_TRANSFER); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), TRANSFER_PUBLIC_INPUTS); - } - - #[test] - fn test_get_public_input_count_unshield() { - let result = get_public_input_count(CIRCUIT_ID_UNSHIELD); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), UNSHIELD_PUBLIC_INPUTS); - } - - #[test] - fn test_get_public_input_count_disclosure() { - let result = get_public_input_count(CIRCUIT_ID_DISCLOSURE); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), DISCLOSURE_PUBLIC_INPUTS); - } - - #[test] - fn test_get_public_input_count_invalid() { - let result = get_public_input_count(255); - assert!(result.is_err()); - assert!(matches!(result, Err(VerifierError::InvalidCircuitId(255)))); - } - - #[test] - fn test_validate_public_input_count_valid_transfer() { - let result = validate_public_input_count(CIRCUIT_ID_TRANSFER, TRANSFER_PUBLIC_INPUTS); - assert!(result.is_ok()); - } - - #[test] - fn test_validate_public_input_count_valid_unshield() { - let result = validate_public_input_count(CIRCUIT_ID_UNSHIELD, UNSHIELD_PUBLIC_INPUTS); - assert!(result.is_ok()); - } - - #[test] - fn test_validate_public_input_count_valid_disclosure() { - let result = validate_public_input_count(CIRCUIT_ID_DISCLOSURE, DISCLOSURE_PUBLIC_INPUTS); - assert!(result.is_ok()); - } - - #[test] - fn test_validate_public_input_count_mismatch() { - let result = validate_public_input_count(CIRCUIT_ID_TRANSFER, 999); - assert!(result.is_err()); - assert!(matches!(result, Err(VerifierError::InvalidPublicInput))); - } - - #[test] - fn test_validate_public_input_count_zero() { - let result = validate_public_input_count(CIRCUIT_ID_TRANSFER, 0); - assert!(result.is_err()); - } - - #[test] - fn test_validate_public_input_count_invalid_circuit() { - let result = validate_public_input_count(99, 5); - assert!(result.is_err()); - assert!(matches!(result, Err(VerifierError::InvalidCircuitId(99)))); - } -} diff --git a/primitives/zk-verifier/src/infrastructure/storage/verification_keys/transfer.rs b/primitives/zk-verifier/src/infrastructure/storage/verification_keys/transfer.rs deleted file mode 100644 index 08f4b881..00000000 --- a/primitives/zk-verifier/src/infrastructure/storage/verification_keys/transfer.rs +++ /dev/null @@ -1,194 +0,0 @@ -//! Auto-generated Verification Key for transfer circuit -//! Generated on: 2026-03-08 04:16:43 -03 -//! Source: artifacts/verification_key_transfer.json -//! -//! DO NOT EDIT MANUALLY - Run sync-circuit-artifacts.sh to regenerate - -use alloc::vec; -use ark_bn254::{Bn254, Fq, Fq2, G1Affine, G2Affine}; -use ark_groth16::VerifyingKey; -use ark_serialize::CanonicalSerialize; -use ark_std::str::FromStr; - -use crate::domain::value_objects::circuit_constants::{ - CIRCUIT_ID_TRANSFER, TRANSFER_PUBLIC_INPUTS, -}; - -/// Circuit ID for transfer (re-exported from domain) -pub const CIRCUIT_ID: u8 = CIRCUIT_ID_TRANSFER; - -/// Number of public inputs for this circuit (re-exported from domain) -pub const NUM_PUBLIC_INPUTS: usize = TRANSFER_PUBLIC_INPUTS; - -/// Creates the verification key for the transfer circuit -pub fn get_vk() -> VerifyingKey { - // Alpha G1 - let alpha_g1 = G1Affine::new_unchecked( - Fq::from_str( - "20491192805390485299153009773594534940189261866228447918068658471970481763042", - ) - .unwrap(), - Fq::from_str( - "9383485363053290200918347156157836566562967994039712273449902621266178545958", - ) - .unwrap(), - ); - - // Beta G2 - let beta_g2 = G2Affine::new_unchecked( - Fq2::new( - Fq::from_str( - "6375614351688725206403948262868962793625744043794305715222011528459656738731", - ) - .unwrap(), - Fq::from_str( - "4252822878758300859123897981450591353533073413197771768651442665752259397132", - ) - .unwrap(), - ), - Fq2::new( - Fq::from_str( - "10505242626370262277552901082094356697409835680220590971873171140371331206856", - ) - .unwrap(), - Fq::from_str( - "21847035105528745403288232691147584728191162732299865338377159692350059136679", - ) - .unwrap(), - ), - ); - - // Gamma G2 - let gamma_g2 = G2Affine::new_unchecked( - Fq2::new( - Fq::from_str( - "10857046999023057135944570762232829481370756359578518086990519993285655852781", - ) - .unwrap(), - Fq::from_str( - "11559732032986387107991004021392285783925812861821192530917403151452391805634", - ) - .unwrap(), - ), - Fq2::new( - Fq::from_str( - "8495653923123431417604973247489272438418190587263600148770280649306958101930", - ) - .unwrap(), - Fq::from_str( - "4082367875863433681332203403145435568316851327593401208105741076214120093531", - ) - .unwrap(), - ), - ); - - // Delta G2 - let delta_g2 = G2Affine::new_unchecked( - Fq2::new( - Fq::from_str( - "13592358905892806894609749704924498653340310428666562825012485043168406251517", - ) - .unwrap(), - Fq::from_str( - "3211692905624887612709603909359343249636321946591964015653041273643645227074", - ) - .unwrap(), - ), - Fq2::new( - Fq::from_str( - "13578083370589112277533348698319334184076656186483533721611127475200402183319", - ) - .unwrap(), - Fq::from_str( - "1108626797718921918393637882276842818263529189049174090035812773418872629637", - ) - .unwrap(), - ), - ); - - // IC points (gamma_abc_g1) - let ic_0 = G1Affine::new_unchecked( - Fq::from_str( - "12521353044701549515327655017850372472462274389676752451261564850795277210784", - ) - .unwrap(), - Fq::from_str( - "17537427920533725255683527309076964578961889733887641292864968986005206833227", - ) - .unwrap(), - ); - - let ic_1 = G1Affine::new_unchecked( - Fq::from_str( - "9054540875357326821742569992875343778082451382872166255139682380736118336965", - ) - .unwrap(), - Fq::from_str( - "19145371190592659949158552383366523068893276589381638814741619309597554902109", - ) - .unwrap(), - ); - - let ic_2 = G1Affine::new_unchecked( - Fq::from_str( - "4742393298120554619433281305566496971436669707311583698440427823952632276050", - ) - .unwrap(), - Fq::from_str( - "3877416000326877112020382111726729873163167304724326109191809999398145068082", - ) - .unwrap(), - ); - - let ic_3 = G1Affine::new_unchecked( - Fq::from_str( - "13596656322317975043597922138537730020108978263529423575433634179437267442285", - ) - .unwrap(), - Fq::from_str( - "9742436487435369435567344764161265311134349467077942742196530523569923539419", - ) - .unwrap(), - ); - - let ic_4 = G1Affine::new_unchecked( - Fq::from_str( - "10688916237036738808967339930842102783882829011167399097753073924876231581798", - ) - .unwrap(), - Fq::from_str( - "15541123047651505099565506020079072290645702361358638026159150320227489549290", - ) - .unwrap(), - ); - - let ic_5 = G1Affine::new_unchecked( - Fq::from_str( - "10594341809050651078348790198511832237136162370751007990030736876706319494726", - ) - .unwrap(), - Fq::from_str( - "8811599578125639162916289173502210672259754599803755307954551465982761005752", - ) - .unwrap(), - ); - - let gamma_abc_g1 = vec![ic_0, ic_1, ic_2, ic_3, ic_4, ic_5]; - - VerifyingKey { - alpha_g1, - beta_g2, - gamma_g2, - delta_g2, - gamma_abc_g1, - } -} - -/// Returns the verification key as compressed bytes for genesis/storage -pub fn get_vk_bytes() -> alloc::vec::Vec { - let vk = get_vk(); - let mut bytes = alloc::vec::Vec::new(); - vk.serialize_compressed(&mut bytes) - .expect("VK serialization should not fail"); - bytes -} diff --git a/primitives/zk-verifier/src/infrastructure/storage/verification_keys/unshield.rs b/primitives/zk-verifier/src/infrastructure/storage/verification_keys/unshield.rs deleted file mode 100644 index a78fdfcc..00000000 --- a/primitives/zk-verifier/src/infrastructure/storage/verification_keys/unshield.rs +++ /dev/null @@ -1,194 +0,0 @@ -//! Auto-generated Verification Key for unshield circuit -//! Generated on: 2026-03-08 04:16:43 -03 -//! Source: artifacts/verification_key_unshield.json -//! -//! DO NOT EDIT MANUALLY - Run sync-circuit-artifacts.sh to regenerate - -use alloc::vec; -use ark_bn254::{Bn254, Fq, Fq2, G1Affine, G2Affine}; -use ark_groth16::VerifyingKey; -use ark_serialize::CanonicalSerialize; -use ark_std::str::FromStr; - -use crate::domain::value_objects::circuit_constants::{ - CIRCUIT_ID_UNSHIELD, UNSHIELD_PUBLIC_INPUTS, -}; - -/// Circuit ID for unshield (re-exported from domain) -pub const CIRCUIT_ID: u8 = CIRCUIT_ID_UNSHIELD; - -/// Number of public inputs for this circuit (re-exported from domain) -pub const NUM_PUBLIC_INPUTS: usize = UNSHIELD_PUBLIC_INPUTS; - -/// Creates the verification key for the unshield circuit -pub fn get_vk() -> VerifyingKey { - // Alpha G1 - let alpha_g1 = G1Affine::new_unchecked( - Fq::from_str( - "20491192805390485299153009773594534940189261866228447918068658471970481763042", - ) - .unwrap(), - Fq::from_str( - "9383485363053290200918347156157836566562967994039712273449902621266178545958", - ) - .unwrap(), - ); - - // Beta G2 - let beta_g2 = G2Affine::new_unchecked( - Fq2::new( - Fq::from_str( - "6375614351688725206403948262868962793625744043794305715222011528459656738731", - ) - .unwrap(), - Fq::from_str( - "4252822878758300859123897981450591353533073413197771768651442665752259397132", - ) - .unwrap(), - ), - Fq2::new( - Fq::from_str( - "10505242626370262277552901082094356697409835680220590971873171140371331206856", - ) - .unwrap(), - Fq::from_str( - "21847035105528745403288232691147584728191162732299865338377159692350059136679", - ) - .unwrap(), - ), - ); - - // Gamma G2 - let gamma_g2 = G2Affine::new_unchecked( - Fq2::new( - Fq::from_str( - "10857046999023057135944570762232829481370756359578518086990519993285655852781", - ) - .unwrap(), - Fq::from_str( - "11559732032986387107991004021392285783925812861821192530917403151452391805634", - ) - .unwrap(), - ), - Fq2::new( - Fq::from_str( - "8495653923123431417604973247489272438418190587263600148770280649306958101930", - ) - .unwrap(), - Fq::from_str( - "4082367875863433681332203403145435568316851327593401208105741076214120093531", - ) - .unwrap(), - ), - ); - - // Delta G2 - let delta_g2 = G2Affine::new_unchecked( - Fq2::new( - Fq::from_str( - "14018664169821405030103251176282245231130962942915865206666085788024510483344", - ) - .unwrap(), - Fq::from_str( - "18071845854913103053394274596987694171637401535194883546405614579067243429300", - ) - .unwrap(), - ), - Fq2::new( - Fq::from_str( - "15299874059012859661606442170730830993341770196170954699235150146401785745498", - ) - .unwrap(), - Fq::from_str( - "5987421919169714599301773144652260474271539718465406299442363505585632972816", - ) - .unwrap(), - ), - ); - - // IC points (gamma_abc_g1) - let ic_0 = G1Affine::new_unchecked( - Fq::from_str( - "4759917404823790432407520088534459023356590579383652181465364916912449624695", - ) - .unwrap(), - Fq::from_str( - "3832863574771841234331076301811290460934931101437953774708549890327258738564", - ) - .unwrap(), - ); - - let ic_1 = G1Affine::new_unchecked( - Fq::from_str( - "5336337706419091211868472399687230066131810167568242892809804587865519165344", - ) - .unwrap(), - Fq::from_str( - "11353185051357039508206677538186633821853103335512576778299092029039610062882", - ) - .unwrap(), - ); - - let ic_2 = G1Affine::new_unchecked( - Fq::from_str( - "15084681146036149779834232651527344012418671089935933939397885561760086121969", - ) - .unwrap(), - Fq::from_str( - "10673952063622217067979641197117245279252423258914282179285754313970092562596", - ) - .unwrap(), - ); - - let ic_3 = G1Affine::new_unchecked( - Fq::from_str( - "21039779822942533132271658631716796333199025786313218659510617689739394153598", - ) - .unwrap(), - Fq::from_str( - "11163282276070815586063767846812914624591347999168289386275553753092237495248", - ) - .unwrap(), - ); - - let ic_4 = G1Affine::new_unchecked( - Fq::from_str( - "3095712311814508041135867864726006035419219310141511809667041762596327080341", - ) - .unwrap(), - Fq::from_str( - "17159247848061658623919878194408282495395946967527260450426770075448330620486", - ) - .unwrap(), - ); - - let ic_5 = G1Affine::new_unchecked( - Fq::from_str( - "4168950959496505617599330781731644101559373119770356275626759863667568305184", - ) - .unwrap(), - Fq::from_str( - "5644689731760572677103844719463014317690882565098456772580613601327065049357", - ) - .unwrap(), - ); - - let gamma_abc_g1 = vec![ic_0, ic_1, ic_2, ic_3, ic_4, ic_5]; - - VerifyingKey { - alpha_g1, - beta_g2, - gamma_g2, - delta_g2, - gamma_abc_g1, - } -} - -/// Returns the verification key as compressed bytes for genesis/storage -pub fn get_vk_bytes() -> alloc::vec::Vec { - let vk = get_vk(); - let mut bytes = alloc::vec::Vec::new(); - vk.serialize_compressed(&mut bytes) - .expect("VK serialization should not fail"); - bytes -} diff --git a/primitives/zk-verifier/src/infrastructure/verification/field_utils.rs b/primitives/zk-verifier/src/infrastructure/verification/field_utils.rs index 2ba10d10..dcd02385 100644 --- a/primitives/zk-verifier/src/infrastructure/verification/field_utils.rs +++ b/primitives/zk-verifier/src/infrastructure/verification/field_utils.rs @@ -1,6 +1,6 @@ //! Utility functions for ZK proof handling -use crate::{domain::value_objects::errors::VerifierError, Bn254Fr}; +use crate::Bn254Fr; use ark_ff::{BigInteger, PrimeField}; /// Convert a field element to bytes (big-endian) @@ -13,15 +13,8 @@ pub fn field_to_bytes(field: &Bn254Fr) -> [u8; 32] { } /// Convert bytes (big-endian) to a field element -pub fn bytes_to_field(bytes: &[u8; 32]) -> Result { - Ok(Bn254Fr::from_be_bytes_mod_order(bytes)) -} - -/// Hash two field elements together (simple addition for now) -/// -/// In production, this should use Poseidon or another ZK-friendly hash -pub fn hash_two_fields(left: &Bn254Fr, right: &Bn254Fr) -> Bn254Fr { - *left + *right +pub fn bytes_to_field(bytes: &[u8; 32]) -> Bn254Fr { + Bn254Fr::from_be_bytes_mod_order(bytes) } /// Convert a u64 to a field element @@ -87,7 +80,7 @@ mod tests { let field = Bn254Fr::from(123456789u64); let bytes = field_to_bytes(&field); // Should be able to convert back - let recovered = bytes_to_field(&bytes).unwrap(); + let recovered = bytes_to_field(&bytes); assert_eq!(field, recovered); } @@ -95,7 +88,7 @@ mod tests { #[test] fn test_bytes_to_field_zero() { let bytes = [0u8; 32]; - let field = bytes_to_field(&bytes).unwrap(); + let field = bytes_to_field(&bytes); assert_eq!(field, Bn254Fr::from(0u64)); } @@ -103,7 +96,7 @@ mod tests { fn test_bytes_to_field_one() { let mut bytes = [0u8; 32]; bytes[31] = 1; - let field = bytes_to_field(&bytes).unwrap(); + let field = bytes_to_field(&bytes); assert_eq!(field, Bn254Fr::from(1u64)); } @@ -111,7 +104,7 @@ mod tests { fn test_bytes_to_field_max_u64() { let mut bytes = [0u8; 32]; bytes[24..].copy_from_slice(&[0xFF; 8]); - let field = bytes_to_field(&bytes).unwrap(); + let field = bytes_to_field(&bytes); assert_eq!(field, Bn254Fr::from(u64::MAX)); } @@ -119,47 +112,10 @@ mod tests { fn test_bytes_to_field_roundtrip() { let original = Bn254Fr::from(987654321u64); let bytes = field_to_bytes(&original); - let recovered = bytes_to_field(&bytes).unwrap(); + let recovered = bytes_to_field(&bytes); assert_eq!(original, recovered); } - // hash_two_fields tests - #[test] - fn test_hash_two_fields_zeros() { - let left = Bn254Fr::from(0u64); - let right = Bn254Fr::from(0u64); - let result = hash_two_fields(&left, &right); - assert_eq!(result, Bn254Fr::from(0u64)); - } - - #[test] - fn test_hash_two_fields_commutative() { - let left = Bn254Fr::from(42u64); - let right = Bn254Fr::from(100u64); - let result1 = hash_two_fields(&left, &right); - let result2 = hash_two_fields(&right, &left); - // Addition is commutative - assert_eq!(result1, result2); - } - - #[test] - fn test_hash_two_fields_simple_addition() { - let left = Bn254Fr::from(10u64); - let right = Bn254Fr::from(20u64); - let result = hash_two_fields(&left, &right); - assert_eq!(result, Bn254Fr::from(30u64)); - } - - #[test] - fn test_hash_two_fields_large_values() { - let left = Bn254Fr::from(u64::MAX); - let right = Bn254Fr::from(1u64); - let result = hash_two_fields(&left, &right); - // Should not panic, modular arithmetic handles overflow - assert_ne!(result, left); - assert_ne!(result, right); - } - // u64_to_field tests #[test] fn test_u64_to_field_zero() { @@ -251,7 +207,7 @@ mod tests { for value in values { let field = Bn254Fr::from(value); let bytes = field_to_bytes(&field); - let recovered = bytes_to_field(&bytes).unwrap(); + let recovered = bytes_to_field(&bytes); assert_eq!(field, recovered, "Failed for value {value}"); } } diff --git a/primitives/zk-verifier/src/infrastructure/verification/groth16_verifier.rs b/primitives/zk-verifier/src/infrastructure/verification/groth16_verifier.rs index 160b88d1..d1c78748 100644 --- a/primitives/zk-verifier/src/infrastructure/verification/groth16_verifier.rs +++ b/primitives/zk-verifier/src/infrastructure/verification/groth16_verifier.rs @@ -4,6 +4,7 @@ //! using the BN254 elliptic curve. use crate::{ + domain::ports::VerifierPort, domain::value_objects::{ circuit_constants::{BASE_VERIFICATION_COST, PER_INPUT_COST}, errors::VerifierError, @@ -17,6 +18,11 @@ use ark_groth16::{Groth16, PreparedVerifyingKey}; pub struct Groth16Verifier; impl Groth16Verifier { + /// Creates a stateless verifier instance. + pub fn new() -> Self { + Self + } + /// Verify a Groth16 proof /// /// # Arguments @@ -107,7 +113,8 @@ impl Groth16Verifier { // arkworks 0.5.0: mul_bigint is in PrimeGroup trait use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup, PrimeGroup}; use ark_ff::{Field, PrimeField}; - use ark_std::{rand::SeedableRng, UniformRand, Zero}; + use ark_std::Zero; + use sha2::{Digest, Sha256}; if public_inputs.len() != proofs.len() { return Err(VerifierError::VerificationFailed); @@ -130,9 +137,7 @@ impl Groth16Verifier { all_inputs.push(inputs.to_field_elements()?); } - // 2. Setup RNG and accumulators - // Use a deterministic seed for protocol consistency. - let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(0); + // 2. Setup accumulators let mut total_r = ::ScalarField::zero(); let mut combined_inputs = ::G1::zero(); @@ -142,8 +147,20 @@ impl Groth16Verifier { let mut g2_prepared = alloc::vec::Vec::with_capacity(proofs.len() + 2); // 3. Combine proofs into linear combination - for (inputs, proof) in all_inputs.iter().zip(ark_proofs.iter()) { - let r = ::ScalarField::rand(&mut rng); + for (index, (inputs, proof)) in all_inputs.iter().zip(ark_proofs.iter()).enumerate() { + let mut hasher = Sha256::new(); + hasher.update(b"orbinum-zk-verifier-batch-v1"); + hasher.update(vk.as_bytes()); + hasher.update((index as u64).to_le_bytes()); + hasher.update(proofs[index].as_bytes()); + for input in &public_inputs[index].inputs { + hasher.update(input); + } + let digest = hasher.finalize(); + let mut r = ::ScalarField::from_le_bytes_mod_order(&digest); + if r.is_zero() { + r = ::ScalarField::from(1u64); + } let r_bigint = r.into_bigint(); total_r += r; @@ -185,15 +202,42 @@ impl Groth16Verifier { } } +impl Default for Groth16Verifier { + fn default() -> Self { + Self::new() + } +} + +impl VerifierPort for Groth16Verifier { + type PreparedKey = PreparedVerifyingKey; + + fn verify( + &self, + vk: &VerifyingKey, + public_inputs: &PublicInputs, + proof: &Proof, + ) -> Result<(), VerifierError> { + Self::verify(vk, public_inputs, proof) + } + + fn verify_prepared( + &self, + prepared_vk: &Self::PreparedKey, + public_inputs: &PublicInputs, + proof: &Proof, + ) -> Result<(), VerifierError> { + Self::verify_with_prepared_vk(prepared_vk, public_inputs, proof) + } +} + #[cfg(test)] mod tests { use super::*; - use crate::{ - domain::value_objects::circuit_constants::{BASE_VERIFICATION_COST, PER_INPUT_COST}, - infrastructure::storage::verification_keys, - }; - use ark_bn254::Fr as Bn254Fr; + use crate::domain::value_objects::circuit_constants::{BASE_VERIFICATION_COST, PER_INPUT_COST}; + use ark_bn254::{Bn254, Fr as Bn254Fr, G1Affine, G2Affine}; + use ark_ec::AffineRepr; use ark_ff::{BigInteger, PrimeField}; + use ark_groth16::VerifyingKey as ArkVerifyingKey; use ark_serialize::CanonicalSerialize; // Helper: Create mock proof with valid curve points @@ -230,6 +274,18 @@ mod tests { PublicInputs::new(inputs) } + fn create_mock_ark_vk(expected_inputs: usize) -> ArkVerifyingKey { + ArkVerifyingKey { + alpha_g1: G1Affine::generator(), + beta_g2: G2Affine::generator(), + gamma_g2: G2Affine::generator(), + delta_g2: G2Affine::generator(), + gamma_abc_g1: (0..=expected_inputs) + .map(|_| G1Affine::generator()) + .collect(), + } + } + // estimate_verification_cost tests #[test] fn test_estimate_verification_cost_zero_inputs() { @@ -265,7 +321,7 @@ mod tests { // verify tests (basic structure) #[test] fn test_verify_detects_invalid_proof_structure() { - let vk = verification_keys::transfer::get_vk(); + let vk = create_mock_ark_vk(5); let vk_wrapper = VerifyingKey::from_ark_vk(&vk).unwrap(); let inputs = create_mock_inputs(5); @@ -278,7 +334,7 @@ mod tests { #[test] fn test_verify_detects_input_count_mismatch() { - let vk = verification_keys::transfer::get_vk(); + let vk = create_mock_ark_vk(5); let vk_wrapper = VerifyingKey::from_ark_vk(&vk).unwrap(); // Transfer circuit expects 5 inputs, provide 3 @@ -292,7 +348,7 @@ mod tests { #[test] fn test_verify_accepts_correct_input_count() { - let vk = verification_keys::transfer::get_vk(); + let vk = create_mock_ark_vk(5); let vk_wrapper = VerifyingKey::from_ark_vk(&vk).unwrap(); // Transfer circuit expects 5 inputs @@ -313,7 +369,7 @@ mod tests { #[test] fn test_verify_with_unshield_vk() { - let vk = verification_keys::unshield::get_vk(); + let vk = create_mock_ark_vk(5); let vk_wrapper = VerifyingKey::from_ark_vk(&vk).unwrap(); // Unshield circuit expects 5 inputs @@ -332,7 +388,7 @@ mod tests { #[test] fn test_verify_with_disclosure_vk() { - let vk = verification_keys::disclosure::get_vk(); + let vk = create_mock_ark_vk(4); let vk_wrapper = VerifyingKey::from_ark_vk(&vk).unwrap(); // Disclosure circuit expects 4 inputs @@ -352,7 +408,7 @@ mod tests { // verify_with_prepared_vk tests #[test] fn test_verify_with_prepared_vk_structure() { - let vk = verification_keys::transfer::get_vk(); + let vk = create_mock_ark_vk(5); let pvk = PreparedVerifyingKey::from(vk); let inputs = create_mock_inputs(5); @@ -370,7 +426,7 @@ mod tests { #[test] fn test_verify_with_prepared_vk_input_mismatch() { - let vk = verification_keys::transfer::get_vk(); + let vk = create_mock_ark_vk(5); let pvk = PreparedVerifyingKey::from(vk); // Wrong input count @@ -383,7 +439,7 @@ mod tests { #[test] fn test_verify_with_prepared_vk_invalid_proof() { - let vk = verification_keys::unshield::get_vk(); + let vk = create_mock_ark_vk(5); let pvk = PreparedVerifyingKey::from(vk); let inputs = create_mock_inputs(5); @@ -396,7 +452,7 @@ mod tests { // batch_verify tests #[test] fn test_batch_verify_empty_arrays() { - let vk = verification_keys::transfer::get_vk(); + let vk = create_mock_ark_vk(5); let vk_wrapper = VerifyingKey::from_ark_vk(&vk).unwrap(); let result = Groth16Verifier::batch_verify(&vk_wrapper, &[], &[]); @@ -406,7 +462,7 @@ mod tests { #[test] fn test_batch_verify_mismatched_lengths() { - let vk = verification_keys::transfer::get_vk(); + let vk = create_mock_ark_vk(5); let vk_wrapper = VerifyingKey::from_ark_vk(&vk).unwrap(); let inputs = alloc::vec![create_mock_inputs(5)]; @@ -424,7 +480,7 @@ mod tests { #[test] fn test_batch_verify_single_proof() { - let vk = verification_keys::transfer::get_vk(); + let vk = create_mock_ark_vk(5); let vk_wrapper = VerifyingKey::from_ark_vk(&vk).unwrap(); let inputs = alloc::vec![create_mock_inputs(5)]; @@ -438,7 +494,7 @@ mod tests { #[test] fn test_batch_verify_multiple_proofs() { - let vk = verification_keys::transfer::get_vk(); + let vk = create_mock_ark_vk(5); let vk_wrapper = VerifyingKey::from_ark_vk(&vk).unwrap(); let inputs = alloc::vec![ @@ -460,7 +516,7 @@ mod tests { #[test] fn test_batch_verify_with_invalid_proof() { - let vk = verification_keys::transfer::get_vk(); + let vk = create_mock_ark_vk(5); let vk_wrapper = VerifyingKey::from_ark_vk(&vk).unwrap(); let inputs = alloc::vec![create_mock_inputs(5), create_mock_inputs(5)]; @@ -472,7 +528,7 @@ mod tests { #[test] fn test_batch_verify_input_count_mismatch() { - let vk = verification_keys::transfer::get_vk(); + let vk = create_mock_ark_vk(5); let vk_wrapper = VerifyingKey::from_ark_vk(&vk).unwrap(); // Transfer expects 5 inputs, providing 3 @@ -487,24 +543,24 @@ mod tests { #[test] fn test_all_circuits_can_prepare_vk() { // Transfer - let transfer_vk = verification_keys::transfer::get_vk(); + let transfer_vk = create_mock_ark_vk(5); let transfer_wrapper = VerifyingKey::from_ark_vk(&transfer_vk).unwrap(); assert!(transfer_wrapper.prepare().is_ok()); // Unshield - let unshield_vk = verification_keys::unshield::get_vk(); + let unshield_vk = create_mock_ark_vk(5); let unshield_wrapper = VerifyingKey::from_ark_vk(&unshield_vk).unwrap(); assert!(unshield_wrapper.prepare().is_ok()); // Disclosure - let disclosure_vk = verification_keys::disclosure::get_vk(); + let disclosure_vk = create_mock_ark_vk(4); let disclosure_wrapper = VerifyingKey::from_ark_vk(&disclosure_vk).unwrap(); assert!(disclosure_wrapper.prepare().is_ok()); } #[test] fn test_verify_and_batch_verify_consistency() { - let vk = verification_keys::transfer::get_vk(); + let vk = create_mock_ark_vk(5); let vk_wrapper = VerifyingKey::from_ark_vk(&vk).unwrap(); let inputs = create_mock_inputs(5); @@ -525,74 +581,4 @@ mod tests { assert!(batch_result.is_ok()); assert!(!batch_result.unwrap()); } - - /// Regression test for the private_link circuit. - /// Verifies that the hardcoded VK in `private_link.rs` is compatible with the - /// CDN proving key (circuits.orbinum.io/v1). - /// - /// Data captured from a real test:24 execution (2026-03-07) using the - /// current CDN proving key. If this test fails, it means the VK in - /// `primitives/zk-verifier/src/infrastructure/storage/verification_keys/private_link.rs` - /// is out of sync with the CDN — run `scripts/sync-circuits/sync-circuit-artifacts.sh` - /// and regenerate the Rust file with `scripts/sync-circuits/generate-vk-rust.sh private_link`. - #[test] - #[ignore = "Requires a proof fixture synced with the current CDN proving key (update with protocol-core/tests test:24)"] - fn test_real_private_link_proof_against_hardcoded_vk() { - use ark_groth16::Proof as ArkProof; - use ark_serialize::CanonicalDeserialize; - - // Hardcoded VK from the crate - let ark_vk = verification_keys::get_private_link_vk(); - let pvk = PreparedVerifyingKey::from(ark_vk); - - // Compressed proofs (128 bytes) generated by compress_snarkjs_proof_wasm - // Captured from npm run test:24 across consecutive CDN versions. - // Keeping more than one vector avoids false negatives when VK rotates between releases. - let proof_hex_candidates = [ - "94c177a4516a446219f11d948f431db57e540252a5dbac43081199cc72ffbd04f99df60b2fdd2cb1af7e4a919e10d00defbef63562047b6898f55949b19e5f0f7a83adb71ccdeed329353e190d28d0b5be46905e47574127d2959559bb8423a946dc13483ecaa0ff4c311f14848502660960f8ef5d842485f6a618e712a39982", - "a6d78479b00b00ba96405d7bde4ebc75b351db85108caba6b2d278c7e1ad5a0a25b56fbae893764cf38d01e84dbb6a458802f414a2ababcd346f33e0018a4e303fd9f74b8e5cbbb4730e8e01e8ecd6e7e04769154b427916b684e75a54e78b28981f861372008124ce74cd44c281b6d24dc7e04d070ce6246f5ce17cb8d09426", - ]; - - // Public inputs in 32-byte LE format - // publicSignals[0] = commitment (LE hex) - let input0_hex = "7a103a55f6a19a42c486303590f2836ecb306eb0c704c64136e1ebcab7842a16"; - // publicSignals[1] = call_hash_fe (LE hex) - let input1_hex = "4344ebdae7f9670c9f148635ff6b7add38d30e679360b508bb44800dfdf38522"; - - let mut input0 = [0u8; 32]; - let mut input1 = [0u8; 32]; - for i in 0..32 { - input0[i] = u8::from_str_radix(&input0_hex[i * 2..i * 2 + 2], 16).unwrap(); - input1[i] = u8::from_str_radix(&input1_hex[i * 2..i * 2 + 2], 16).unwrap(); - } - - let public_inputs = PublicInputs::new(alloc::vec![input0, input1]); - let inputs_fr = public_inputs - .to_field_elements() - .expect("Fr conversion should succeed"); - let mut any_valid = false; - - for proof_hex in proof_hex_candidates { - let proof_bytes: alloc::vec::Vec = (0..proof_hex.len()) - .step_by(2) - .map(|i| u8::from_str_radix(&proof_hex[i..i + 2], 16).unwrap()) - .collect(); - assert_eq!(proof_bytes.len(), 128, "Proof must be 128 bytes"); - - let ark_proof = ArkProof::::deserialize_compressed(&proof_bytes[..]) - .expect("Proof deserialization should succeed"); - - let valid = Groth16::::verify_proof(&pvk, &ark_proof, &inputs_fr) - .expect("verify_proof should not return a pairing error"); - - if valid { - any_valid = true; - break; - } - } - - // If this fails, the VK does not match the CDN proving key - // If it passes, the bug is elsewhere (e.g. issue in runtime path) - assert!(any_valid, "None of the real private_link proofs verified against the hardcoded VK. If this fails, the Rust VK does NOT match the CDN proving key."); - } } diff --git a/template/node/Cargo.toml b/template/node/Cargo.toml index f1c6f3df..8d38209d 100644 --- a/template/node/Cargo.toml +++ b/template/node/Cargo.toml @@ -63,12 +63,10 @@ pallet-shielded-pool-rpc = { workspace = true } pallet-shielded-pool-runtime-api = { workspace = true } pallet-transaction-payment-rpc = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } +pallet-zk-verifier-rpc = { workspace = true } +pallet-zk-verifier-runtime-api = { workspace = true } substrate-frame-rpc-system = { workspace = true } -# ZK dependencies for genesis VK loading -orbinum-zk-verifier = { workspace = true } -pallet-zk-verifier = { workspace = true } - # These dependencies are used for runtime benchmarking frame-benchmarking = { workspace = true } frame-benchmarking-cli = { workspace = true } diff --git a/template/node/src/chain_spec.rs b/template/node/src/chain_spec.rs index 5ad96ac5..8c159e9f 100644 --- a/template/node/src/chain_spec.rs +++ b/template/node/src/chain_spec.rs @@ -14,9 +14,6 @@ use orbinum_runtime::{ evm_bytes_to_account_id_bytes, AccountId, Balance, SS58Prefix, Signature, WASM_BINARY, }; -use orbinum_zk_verifier::infrastructure::storage::verification_keys; -use pallet_zk_verifier::CircuitId; - pub type ChainSpec = sc_service::GenericChainSpec; fn ethereum_account_id(eth_address: [u8; 20]) -> AccountId { @@ -182,14 +179,6 @@ fn testnet_genesis( "grandpa": { "authorities": initial_authorities.iter().map(|x| (x.1.clone(), 1)).collect::>() }, "evmChainId": { "chainId": chain_id }, "evm": { "accounts": evm_accounts }, - "manualSeal": { "enable": enable_manual_seal }, - "zkVerifier": { - "verificationKeys": vec![ - (CircuitId::TRANSFER, verification_keys::get_transfer_vk_bytes()), - (CircuitId::UNSHIELD, verification_keys::get_unshield_vk_bytes()), - (CircuitId::DISCLOSURE, verification_keys::get_disclosure_vk_bytes()), - (CircuitId::PRIVATE_LINK, verification_keys::get_private_link_vk_bytes()), - ] - } + "manualSeal": { "enable": enable_manual_seal } }) } diff --git a/template/node/src/rpc/mod.rs b/template/node/src/rpc/mod.rs index 7fc70906..334f83b3 100644 --- a/template/node/src/rpc/mod.rs +++ b/template/node/src/rpc/mod.rs @@ -70,6 +70,7 @@ where C::Api: fp_rpc::EthereumRuntimeRPCApi, C::Api: pallet_account_mapping_runtime_api::AccountMappingRuntimeApi, C::Api: pallet_shielded_pool_runtime_api::ShieldedPoolRuntimeApi, + C::Api: pallet_zk_verifier_runtime_api::ZkVerifierRuntimeApi, C: HeaderBackend + HeaderMetadata + 'static, C: BlockchainEvents + AuxStore + UsageProvider + StorageProvider, BE: Backend + 'static, @@ -80,6 +81,7 @@ where use pallet_account_mapping_rpc::{AccountMapping, AccountMappingApiServer}; use pallet_shielded_pool_rpc::{ShieldedPool, ShieldedPoolApiServer}; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; + use pallet_zk_verifier_rpc::{ZkVerifier, ZkVerifierApiServer}; use sc_consensus_manual_seal::rpc::{ManualSeal, ManualSealApiServer}; use substrate_frame_rpc_system::{System, SystemApiServer}; @@ -98,6 +100,7 @@ where io.merge(TransactionPayment::new(client.clone()).into_rpc())?; io.merge(AccountMapping::new(client.clone()).into_rpc())?; io.merge(ShieldedPool::new(client.clone()).into_rpc())?; + io.merge(ZkVerifier::new(client.clone()).into_rpc())?; // Orbinum Privacy RPC let privacy_adapter = SubstrateStorageAdapter::new(client.clone()); diff --git a/template/node/src/service.rs b/template/node/src/service.rs index 9906bb67..06fb809e 100644 --- a/template/node/src/service.rs +++ b/template/node/src/service.rs @@ -306,6 +306,7 @@ where RA::RuntimeApi: RuntimeApiCollection, RA::RuntimeApi: pallet_account_mapping_runtime_api::AccountMappingRuntimeApi, + RA::RuntimeApi: pallet_zk_verifier_runtime_api::ZkVerifierRuntimeApi, HF: HostFunctionsT + 'static, NB: sc_network::NetworkBackend::Hash>, { diff --git a/template/runtime/Cargo.toml b/template/runtime/Cargo.toml index 371efc91..d177703d 100644 --- a/template/runtime/Cargo.toml +++ b/template/runtime/Cargo.toml @@ -71,10 +71,10 @@ pallet-evm-precompile-sha3fips-benchmarking = { workspace = true } pallet-evm-precompile-simple = { workspace = true } # Orbinum Privacy -orbinum-zk-verifier = { workspace = true, default-features = false } pallet-shielded-pool = { workspace = true } pallet-shielded-pool-runtime-api = { workspace = true } pallet-zk-verifier = { workspace = true } +pallet-zk-verifier-runtime-api = { workspace = true } # Polkadot polkadot-runtime-common = { workspace = true } @@ -152,7 +152,7 @@ std = [ "pallet-shielded-pool/std", "pallet-shielded-pool/poseidon-native", "pallet-shielded-pool-runtime-api/std", - "orbinum-zk-verifier/std", + "pallet-zk-verifier-runtime-api/std", # Polkadot "polkadot-runtime-common/std", # Cumulus primitives diff --git a/template/runtime/src/genesis_config_preset.rs b/template/runtime/src/genesis_config_preset.rs index a6d27357..9b5c23e0 100644 --- a/template/runtime/src/genesis_config_preset.rs +++ b/template/runtime/src/genesis_config_preset.rs @@ -16,9 +16,6 @@ use sp_core::{H160, U256}; use sp_genesis_builder::PresetId; use sp_std::prelude::*; -use orbinum_zk_verifier::infrastructure::storage::verification_keys; -use pallet_zk_verifier::CircuitId; - /// Map an Ethereum H160 address to its Substrate AccountId32 using /// the runtime helper (H160_bytes ++ [0xEE; 12]). /// @@ -93,27 +90,7 @@ pub(super) fn build_genesis( key: Some(sudo_key), }, transaction_payment: Default::default(), - zk_verifier: pallet_zk_verifier::GenesisConfig { - verification_keys: vec![ - ( - CircuitId::TRANSFER, - verification_keys::get_transfer_vk_bytes(), - ), - ( - CircuitId::UNSHIELD, - verification_keys::get_unshield_vk_bytes(), - ), - ( - CircuitId::DISCLOSURE, - verification_keys::get_disclosure_vk_bytes(), - ), - ( - CircuitId::PRIVATE_LINK, - verification_keys::get_private_link_vk_bytes(), - ), - ], - ..Default::default() - }, + zk_verifier: Default::default(), shielded_pool: Default::default(), }; diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index b0afb41c..06823524 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -507,10 +507,6 @@ pub mod pallet_manual_seal { impl pallet_manual_seal::Config for Runtime {} impl pallet_zk_verifier::Config for Runtime { - /// Only root can register/update verification keys - type AdminOrigin = frame_system::EnsureRoot; - /// Max VK size: 10MB (transfer_pk.ark: 8.3MB is the largest) - type MaxVerificationKeySize = ConstU32<10485760>; /// Max proof size: 1KB (Groth16 proofs ~256-512 bytes) type MaxProofSize = ConstU32<1024>; /// Max public inputs: 32 field elements per circuit @@ -1149,6 +1145,46 @@ impl_runtime_apis! { } } + impl pallet_zk_verifier_runtime_api::ZkVerifierRuntimeApi for Runtime { + fn get_circuit_version_info( + circuit_id: u32, + ) -> Option { + pallet_zk_verifier::Pallet::::runtime_api_get_circuit_version_info(circuit_id) + .map(|info| pallet_zk_verifier_runtime_api::CircuitVersionInfo { + circuit_id: info.circuit_id, + active_version: info.active_version, + supported_versions: info.supported_versions, + vk_hashes: info + .vk_hashes + .into_iter() + .map(|item| pallet_zk_verifier_runtime_api::VkVersionHash { + version: item.version, + vk_hash: item.vk_hash, + }) + .collect(), + }) + } + + fn get_all_circuit_versions() -> alloc::vec::Vec { + pallet_zk_verifier::Pallet::::runtime_api_get_all_circuit_versions() + .into_iter() + .map(|info| pallet_zk_verifier_runtime_api::CircuitVersionInfo { + circuit_id: info.circuit_id, + active_version: info.active_version, + supported_versions: info.supported_versions, + vk_hashes: info + .vk_hashes + .into_iter() + .map(|item| pallet_zk_verifier_runtime_api::VkVersionHash { + version: item.version, + vk_hash: item.vk_hash, + }) + .collect(), + }) + .collect() + } + } + impl pallet_account_mapping_runtime_api::AccountMappingRuntimeApi for Runtime { fn get_mapped_account(address: H160) -> Option { AccountMapping::mapped_account(address)