From 5ca994d88973928241103568ab63b08c92ad19bd Mon Sep 17 00:00:00 2001 From: nol4lej Date: Wed, 8 Jul 2026 19:16:01 -0400 Subject: [PATCH 1/9] feat(zk-verifier): per-note circuit versioning with version retirement --- .../evm/precompile/shielded-pool/CHANGELOG.md | 19 ++ .../src/calls/claim_shielded_fees.rs | 46 ++- .../src/calls/private_transfer.rs | 18 +- .../shielded-pool/src/calls/unshield.rs | 35 ++- frame/evm/precompile/shielded-pool/src/lib.rs | 12 +- .../evm/precompile/shielded-pool/src/mock.rs | 4 + .../evm/precompile/shielded-pool/src/tests.rs | 36 ++- frame/shielded-pool/CHANGELOG.md | 16 + frame/shielded-pool/src/benchmarking.rs | 3 + frame/shielded-pool/src/lib.rs | 17 +- frame/shielded-pool/src/mock.rs | 4 + frame/shielded-pool/src/operations/fees.rs | 25 +- .../src/operations/private_transfer.rs | 27 +- .../shielded-pool/src/operations/unshield.rs | 33 +- frame/shielded-pool/src/validate_unsigned.rs | 114 ++++++- frame/zk-verifier/CHANGELOG.md | 46 +++ frame/zk-verifier/src/benchmarking.rs | 59 +++- frame/zk-verifier/src/lib.rs | 288 ++++++++++++++++-- frame/zk-verifier/src/port.rs | 86 +++++- frame/zk-verifier/src/runtime_api.rs | 11 +- frame/zk-verifier/src/verifier.rs | 19 +- frame/zk-verifier/src/weights.rs | 26 ++ template/runtime/src/lib.rs | 4 +- 23 files changed, 858 insertions(+), 90 deletions(-) diff --git a/frame/evm/precompile/shielded-pool/CHANGELOG.md b/frame/evm/precompile/shielded-pool/CHANGELOG.md index 24bd7ed1..1d2bc849 100644 --- a/frame/evm/precompile/shielded-pool/CHANGELOG.md +++ b/frame/evm/precompile/shielded-pool/CHANGELOG.md @@ -2,6 +2,25 @@ All notable changes to `pallet-evm-precompile-shielded-pool` will be documented in this file. +## [Unreleased] + +### Changed +- **ABI: added a trailing `uint32 circuitVersion` to `privateTransfer`, `unshield` + and `claimShieldedFees`** — the circuit version the spent note was created under, + forwarded to the pallet so the proof is verified against that version's VK. This + changes each function's selector (**breaking**): `privateTransfer` `0x8c0f5d24` → + `0x66ed2cd4`, `unshield` `0xcc1a3b38` → `0x4e505348`, `claimShieldedFees` + `0x42e1e74c` → `0x88d9deba`. Callers (SDK / app) must send the new calldata. +- Fixed the stale `unshield` selector/signature in the router doc table + (`0x47fc44a2` → the actual on-chain value). + +### Security +- **`unshield` change-memo decode fails loudly for a partial unshield.** A + malformed `change_encrypted_memo` offset pointer previously fell back to an + empty memo (`unwrap_or_default`), silently turning a partial unshield into one + whose change note is unrecoverable. It now errors unless the unshield is total + (`change_commitment == [0; 32]`), where an absent memo is legitimate. + ## [0.2.0] - 2026-05-08 ### Changed diff --git a/frame/evm/precompile/shielded-pool/src/calls/claim_shielded_fees.rs b/frame/evm/precompile/shielded-pool/src/calls/claim_shielded_fees.rs index d0161422..52e6882e 100644 --- a/frame/evm/precompile/shielded-pool/src/calls/claim_shielded_fees.rs +++ b/frame/evm/precompile/shielded-pool/src/calls/claim_shielded_fees.rs @@ -1,9 +1,9 @@ //! ABI decoding and call construction for -//! `claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes)`. +//! `claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes,uint32)`. //! //! ## Selector -//! `keccak256("claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes)")[0..4]` -//! = `0x42e1e74c` +//! `keccak256("claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes,uint32)")[0..4]` +//! = `0x88d9deba` //! //! ## ABI layout (`input[4..]`) //! | Slot (bytes) | Type | Field | @@ -14,6 +14,7 @@ //! | 96..128 | `uint256` | offset → `memo` | //! | 128..160 | `uint256` | offset → `proof` | //! | 160..192 | `uint256` | offset → `public_signals` | +//! | 192..224 | `uint32` | `circuit_version` | //! //! The **validator** origin is derived from `handle.context().caller` //! (the EVM address that sent the transaction), mapped to an `AccountId` @@ -30,8 +31,10 @@ use sp_core::U256; use crate::abi; -/// `keccak256("claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes)")[0..4]` -pub const SELECTOR: [u8; 4] = [0x42, 0xe1, 0xe7, 0x4c]; +/// `keccak256("claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes,uint32)")[0..4]` +/// The trailing `uint32` is `circuitVersion` — the circuit version the spent +/// notes were created under, so the proof is verified against that version's VK. +pub const SELECTOR: [u8; 4] = [0x88, 0xd9, 0xde, 0xba]; /// Maximum byte length of a serialised Groth16 proof accepted by the pallet. const MAX_PROOF_LEN: u32 = 512; @@ -95,6 +98,13 @@ where .try_into() .map_err(|_| err("claimShieldedFees: public_signals too long"))?; + if params.len() < 224 { + return Err(err( + "claimShieldedFees: input too short (missing circuitVersion)", + )); + } + let circuit_version = abi::decode_u32(¶ms[192..224])?; + Ok(pallet_shielded_pool::Call::::claim_shielded_fees { commitment, amount, @@ -102,6 +112,7 @@ where memo, proof, public_signals, + circuit_version, }) } @@ -171,8 +182,8 @@ mod tests { /// Encodes a full `claimShieldedFees` ABI call (selector + params). /// - /// ABI head (192 bytes after selector): - /// `[commitment(32) | amount(32) | asset_id(32) | off_memo(32) | off_proof(32) | off_ps(32)]` + /// ABI head (224 bytes after selector): + /// `[commitment(32) | amount(32) | asset_id(32) | off_memo(32) | off_proof(32) | off_ps(32) | circuit_version(32)]` fn encode_claim_shielded_fees( commitment: [u8; 32], amount: u128, @@ -180,13 +191,14 @@ mod tests { memo: &[u8], proof: &[u8], public_signals: &[u8], + circuit_version: u32, ) -> Vec { let memo_enc = encode_bytes(memo); let proof_enc = encode_bytes(proof); let ps_enc = encode_bytes(public_signals); - // 6 fixed head slots × 32 bytes = 192 - let head_size = 192usize; + // 7 fixed head slots × 32 bytes = 224 (added trailing uint32 circuitVersion) + let head_size = 224usize; let memo_off = head_size; let proof_off = head_size + memo_enc.len(); let ps_off = head_size + memo_enc.len() + proof_enc.len(); @@ -198,6 +210,7 @@ mod tests { head[96..128].copy_from_slice(&u256_word(memo_off)); head[128..160].copy_from_slice(&u256_word(proof_off)); head[160..192].copy_from_slice(&u256_word(ps_off)); + head[220..224].copy_from_slice(&circuit_version.to_be_bytes()); let mut input = SELECTOR.to_vec(); input.extend_from_slice(&head); @@ -243,6 +256,7 @@ mod tests { &valid_memo(), PROOF, &valid_public_signals(), + 1, ) } @@ -278,6 +292,7 @@ mod tests { &valid_memo(), PROOF, &valid_public_signals(), + 1, ); let h = MockHandle::new(input.clone()); let result = super::decode::(&h, &input); @@ -295,6 +310,7 @@ mod tests { &valid_memo(), PROOF, &bad_signals, + 1, ); let h = MockHandle::new(input.clone()); let result = super::decode::(&h, &input); @@ -316,6 +332,7 @@ mod tests { &valid_memo(), &oversized_proof, &valid_public_signals(), + 1, ); let h = MockHandle::new(input.clone()); let result = super::decode::(&h, &input); @@ -334,6 +351,7 @@ mod tests { &valid_memo(), &[], // ← empty proof &valid_public_signals(), + 1, ); let h = MockHandle::new(input.clone()); let result = super::decode::(&h, &input); @@ -361,6 +379,7 @@ mod tests { &valid_memo(), PROOF, make_public_signals(commitment, AMOUNT as u64, ASSET_ID).as_ref(), + 1, ); let h = MockHandle::new(input.clone()); match super::decode::(&h, &input).unwrap() { @@ -382,6 +401,7 @@ mod tests { &valid_memo(), PROOF, ps.as_ref(), + 1, ); let h = MockHandle::new(input.clone()); match super::decode::(&h, &input).unwrap() { @@ -402,6 +422,7 @@ mod tests { &valid_memo(), PROOF, make_public_signals(COMMITMENT, AMOUNT as u64, 0).as_ref(), + 1, ); let h = MockHandle::new(input.clone()); match super::decode::(&h, &input).unwrap() { @@ -455,6 +476,7 @@ mod tests { &valid_memo(), PROOF, &valid_public_signals(), + 1, ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -471,6 +493,7 @@ mod tests { &valid_memo(), &[], &valid_public_signals(), + 1, ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -489,6 +512,7 @@ mod tests { &valid_memo(), PROOF, &bad_ps, + 1, ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -508,6 +532,7 @@ mod tests { &valid_memo(), PROOF, mismatched.as_ref(), + 1, ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -527,6 +552,7 @@ mod tests { &valid_memo(), PROOF, mismatched.as_ref(), + 1, ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -546,6 +572,7 @@ mod tests { &valid_memo(), PROOF, mismatched.as_ref(), + 1, ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -637,6 +664,7 @@ mod tests { &valid_memo(), PROOF, ps.as_ref(), + 1, ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); diff --git a/frame/evm/precompile/shielded-pool/src/calls/private_transfer.rs b/frame/evm/precompile/shielded-pool/src/calls/private_transfer.rs index 837b314b..5a1862fd 100644 --- a/frame/evm/precompile/shielded-pool/src/calls/private_transfer.rs +++ b/frame/evm/precompile/shielded-pool/src/calls/private_transfer.rs @@ -1,9 +1,9 @@ //! ABI decoding and call construction for -//! `privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256)`. +//! `privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256,uint32)`. //! //! ## Selector -//! `keccak256("privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256)")[0..4]` -//! = `0x8c0f5d24` +//! `keccak256("privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256,uint32)")[0..4]` +//! = `0x66ed2cd4` //! //! ## ABI layout (`input[4..]`) — standard head/tail encoding //! | Slot (bytes) | Type | Field | @@ -15,6 +15,7 @@ //! | 128..160 | `uint256` | offset → memos | //! | 160..192 | `uint32` | `asset_id` | //! | 192..224 | `uint256` | `fee` | +//! | 224..256 | `uint32` | `circuit_version` | //! //! `relayer` is derived from `handle.context().caller` — not part of the ABI. @@ -24,8 +25,10 @@ use sp_core::U256; use crate::abi; -/// `keccak256("privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256)")[0..4]` -pub const SELECTOR: [u8; 4] = [0x8c, 0x0f, 0x5d, 0x24]; +/// `keccak256("privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256,uint32)")[0..4]` +/// The trailing `uint32` is `circuitVersion` — the circuit version the spent +/// notes were created under, so the proof is verified against that version's VK. +pub const SELECTOR: [u8; 4] = [0x66, 0xed, 0x2c, 0xd4]; /// Maximum byte length of a serialised Groth16 proof accepted by the pallet. const MAX_PROOF_LEN: u32 = 512; @@ -46,7 +49,7 @@ where pallet_shielded_pool::BalanceOf: TryFrom, { let params = &input[4..]; - if params.len() < 224 { + if params.len() < 256 { return Err(err("privateTransfer: input too short")); } @@ -120,6 +123,8 @@ where let relayer = Some(handle.context().caller); + let circuit_version = abi::decode_u32(¶ms[224..256])?; + Ok(pallet_shielded_pool::Call::::private_transfer { proof, merkle_root, @@ -129,6 +134,7 @@ where asset_id, fee, relayer, + circuit_version, }) } diff --git a/frame/evm/precompile/shielded-pool/src/calls/unshield.rs b/frame/evm/precompile/shielded-pool/src/calls/unshield.rs index b2784ad2..ddd0fc46 100644 --- a/frame/evm/precompile/shielded-pool/src/calls/unshield.rs +++ b/frame/evm/precompile/shielded-pool/src/calls/unshield.rs @@ -1,9 +1,9 @@ //! ABI decoding and call construction for -//! `unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32,bytes)`. +//! `unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32,bytes,uint32)`. //! //! ## Selector -//! `keccak256("unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32,bytes)")[0..4]` -//! = `0xcc1a3b38` +//! `keccak256("unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32,bytes,uint32)")[0..4]` +//! = `0x4e505348` //! //! ## ABI layout (`input[4..]`) //! | Slot (bytes) | Type | Field | @@ -17,6 +17,7 @@ //! | 192..224 | `uint256` | `fee` | //! | 224..256 | `bytes32` | `change_commitment` | //! | 256..288 | `uint256` | offset → `change_encrypted_memo` | +//! | 288..320 | `uint32` | `circuit_version` | //! //! `recipient` is an `AccountId32` encoded as a 32-byte ABI `bytes32` slot. //! This can be a Substrate-native account or the `AccountId32` derived from @@ -38,8 +39,10 @@ use sp_core::U256; use crate::abi; -/// `keccak256("unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32,bytes)")[0..4]` -pub const SELECTOR: [u8; 4] = [0xcc, 0x1a, 0x3b, 0x38]; +/// `keccak256("unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32,bytes,uint32)")[0..4]` +/// The trailing `uint32` is `circuitVersion` — the circuit version the spent +/// notes were created under, so the proof is verified against that version's VK. +pub const SELECTOR: [u8; 4] = [0x4e, 0x50, 0x53, 0x48]; /// Maximum byte length of a serialised Groth16 proof accepted by the pallet. const MAX_PROOF_LEN: u32 = 512; @@ -107,11 +110,21 @@ where }; let change_commitment: pallet_shielded_pool::Hash = abi::read_bytes32(params, 224)?; + let is_total_unshield = change_commitment == [0u8; 32]; - // Decode change_encrypted_memo as a dynamic bytes field. - // Offset pointer lives at slot 256 (params[256..288]). + // Decode change_encrypted_memo as a dynamic bytes field (offset pointer at + // slot 256). For a partial unshield (non-zero change_commitment) a malformed + // offset must fail loudly — silently defaulting to an empty memo would make + // the change note unrecoverable. A total unshield legitimately has no memo. let change_encrypted_memo_bytes = if params.len() >= 288 { - abi::decode_bytes_at_slot(params, 256).unwrap_or_default() + match abi::decode_bytes_at_slot(params, 256) { + Ok(bytes) => bytes, + Err(e) if is_total_unshield => { + let _ = e; + Vec::new() + } + Err(_) => return Err(err("unshield: malformed change_encrypted_memo offset")), + } } else { Vec::new() }; @@ -130,6 +143,11 @@ where let relayer = Some(handle.context().caller); + if params.len() < 320 { + return Err(err("unshield: input too short (missing circuitVersion)")); + } + let circuit_version = abi::decode_u32(¶ms[288..320])?; + Ok(pallet_shielded_pool::Call::::unshield { proof, merkle_root, @@ -141,6 +159,7 @@ where change_commitment, change_encrypted_memo, relayer, + circuit_version, }) } diff --git a/frame/evm/precompile/shielded-pool/src/lib.rs b/frame/evm/precompile/shielded-pool/src/lib.rs index 6a11a5a5..570872b3 100644 --- a/frame/evm/precompile/shielded-pool/src/lib.rs +++ b/frame/evm/precompile/shielded-pool/src/lib.rs @@ -16,12 +16,12 @@ use sp_runtime::traits::Dispatchable; /// /// Four functions are exposed, each identified by a 4-byte ABI selector: /// -/// | Selector | Solidity signature | -/// |-------------|---------------------------------------------------------------------------------------| -/// | `0x9feb22ea` | `shield(uint32,bytes32,bytes)` — payable, amount = `msg.value` | -/// | `0x8c0f5d24` | `privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256)` | -/// | `0x47fc44a2` | `unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256)` | -/// | `0x42e1e74c` | `claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes)` — signed (validators) | +/// | Selector | Solidity signature | +/// |-------------|-----------------------------------------------------------------------------------------------| +/// | `0x9feb22ea` | `shield(uint32,bytes32,bytes)` — payable, amount = `msg.value` | +/// | `0x66ed2cd4` | `privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256,uint32)` | +/// | `0x4e505348` | `unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32,bytes,uint32)` | +/// | `0x88d9deba` | `claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes,uint32)` — signed (validators) | /// /// Selector computation: `bytes4(keccak256("functionName(argTypes)"))`. /// Verify with: `node -e "const {ethers}=require('ethers'); console.log(ethers.id('sig').slice(0,10))"` diff --git a/frame/evm/precompile/shielded-pool/src/mock.rs b/frame/evm/precompile/shielded-pool/src/mock.rs index c95a212c..a3c7170e 100644 --- a/frame/evm/precompile/shielded-pool/src/mock.rs +++ b/frame/evm/precompile/shielded-pool/src/mock.rs @@ -185,6 +185,10 @@ impl ZkVerifierPort for MockZkVerifier { } Ok(true) } + + fn is_supported_version(_circuit_id: u32, version: u32) -> bool { + version != 0 + } } pub struct MockBlockAuthor; diff --git a/frame/evm/precompile/shielded-pool/src/tests.rs b/frame/evm/precompile/shielded-pool/src/tests.rs index ae8f5bf7..2fc4b18e 100644 --- a/frame/evm/precompile/shielded-pool/src/tests.rs +++ b/frame/evm/precompile/shielded-pool/src/tests.rs @@ -113,20 +113,21 @@ fn encode_private_transfer( memos: &[Vec], asset_id: u32, fee: u128, + circuit_version: u32, ) -> Vec { let proof_enc = encode_bytes(proof); let nullifiers_enc = encode_bytes32_array(nullifiers); let commitments_enc = encode_bytes32_array(commitments); let memos_enc = encode_bytes_array(memos); - // head: 7 slots × 32 = 224 bytes - let head_size = 224usize; + // head: 8 slots × 32 = 256 bytes (added trailing uint32 circuitVersion) + let head_size = 256usize; let off_proof = head_size; let off_nullifiers = off_proof + proof_enc.len(); let off_commitments = off_nullifiers + nullifiers_enc.len(); let off_memos = off_commitments + commitments_enc.len(); - let mut input = vec![0x8c, 0x0f, 0x5d, 0x24]; + let mut input = vec![0x66, 0xed, 0x2c, 0xd4]; let mut head = vec![0u8; head_size]; head[0..32].copy_from_slice(&u256_word(off_proof)); head[32..64].copy_from_slice(&merkle_root); @@ -135,6 +136,7 @@ fn encode_private_transfer( head[128..160].copy_from_slice(&u256_word(off_memos)); head[188..192].copy_from_slice(&asset_id.to_be_bytes()); head[192..224].copy_from_slice(&u256_word_u128(fee)); + head[252..256].copy_from_slice(&circuit_version.to_be_bytes()); input.extend_from_slice(&head); input.extend_from_slice(&proof_enc); @@ -144,7 +146,7 @@ fn encode_private_transfer( input } -/// `unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32,bytes)` selector `0xcc1a3b38` +/// `unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32,bytes,uint32)` selector `0x4e505348` #[allow(clippy::too_many_arguments)] fn encode_unshield( proof: &[u8], @@ -156,11 +158,13 @@ fn encode_unshield( fee: u128, change_commitment: [u8; 32], change_encrypted_memo: &[u8], + circuit_version: u32, ) -> Vec { - // head: 9 slots × 32 = 288 bytes; tails (proof, memo) appended after - let mut input = vec![0xcc, 0x1a, 0x3b, 0x38]; - let mut head = vec![0u8; 288]; - let proof_offset = 288usize; + // head: 10 slots × 32 = 320 bytes (added trailing uint32 circuitVersion); + // tails (proof, memo) appended after. + let mut input = vec![0x4e, 0x50, 0x53, 0x48]; + let mut head = vec![0u8; 320]; + let proof_offset = 320usize; let memo_offset = proof_offset + encode_bytes(proof).len(); head[0..32].copy_from_slice(&u256_word(proof_offset)); @@ -172,6 +176,7 @@ fn encode_unshield( head[192..224].copy_from_slice(&u256_word_u128(fee)); head[224..256].copy_from_slice(&change_commitment); head[256..288].copy_from_slice(&u256_word(memo_offset)); + head[316..320].copy_from_slice(&circuit_version.to_be_bytes()); input.extend_from_slice(&head); input.extend_from_slice(&encode_bytes(proof)); input.extend_from_slice(&encode_bytes(change_encrypted_memo)); @@ -459,6 +464,7 @@ fn private_transfer_rejects_empty_proof() { &[vec![0xAA; 176], vec![0xBB; 176]], 0, 0, + 1, ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -480,6 +486,7 @@ fn private_transfer_rejects_zero_nullifiers() { &[], // 0 memos 0, 0, + 1, ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -500,6 +507,7 @@ fn private_transfer_rejects_mismatched_nullifier_commitment_count() { &[vec![0xAA; 176]], // 1 memo 0, 0, + 1, ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -520,6 +528,7 @@ fn private_transfer_rejects_mismatched_commitment_memo_count() { &[vec![0xAA; 176]], // 1 memo — mismatch 0, 0, + 1, ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -544,6 +553,7 @@ fn private_transfer_happy_path() { &[vec![0xAA; 176], vec![0xBB; 176]], 0, 0, + 1, ); let mut h = MockHandle::new(input); assert_success(ShieldedPoolPrecompile::::execute(&mut h)); @@ -586,6 +596,7 @@ fn private_transfer_rejects_double_spend() { &[vec![0xAA; 176], vec![0xBB; 176]], 0, 0, + 1, ); let mut h = MockHandle::new(input.clone()); assert_success(ShieldedPoolPrecompile::::execute(&mut h)); @@ -610,6 +621,7 @@ fn private_transfer_root_updates_after_outputs() { &[vec![0xAA; 176], vec![0xBB; 176]], 0, 0, + 1, ); let mut h = MockHandle::new(input); assert_success(ShieldedPoolPrecompile::::execute(&mut h)); @@ -649,6 +661,7 @@ fn unshield_rejects_empty_proof() { 0, [0u8; 32], &[], + 1, ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -672,6 +685,7 @@ fn unshield_happy_path() { 0, [0u8; 32], &[], + 1, ); let mut h = MockHandle::new(input); assert_success(ShieldedPoolPrecompile::::execute(&mut h)); @@ -706,6 +720,7 @@ fn unshield_rejects_double_spend() { 0, [0u8; 32], &[], + 1, ); let mut h = MockHandle::new(input.clone()); assert_success(ShieldedPoolPrecompile::::execute(&mut h)); @@ -730,6 +745,7 @@ fn unshield_full_balance() { 0, [0u8; 32], &[], + 1, ); let mut h = MockHandle::new(input); assert_success(ShieldedPoolPrecompile::::execute(&mut h)); @@ -754,6 +770,7 @@ fn unshield_rejects_zero_recipient() { 0, [0u8; 32], &[], + 1, ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -777,6 +794,7 @@ fn unshield_rejects_zero_amount() { 0, [0u8; 32], &[], + 1, ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -809,6 +827,7 @@ fn full_lifecycle_shield_transfer_unshield() { &[vec![0xAA; 176], vec![0xBB; 176]], 0, 0, + 1, ); let mut h_pt = MockHandle::new(pt_input); assert_success(ShieldedPoolPrecompile::::execute(&mut h_pt)); @@ -827,6 +846,7 @@ fn full_lifecycle_shield_transfer_unshield() { 0, [0u8; 32], &[], + 1, ); let mut h_us = MockHandle::new(unshield_input); assert_success(ShieldedPoolPrecompile::::execute(&mut h_us)); diff --git a/frame/shielded-pool/CHANGELOG.md b/frame/shielded-pool/CHANGELOG.md index 60bb041a..1918c5ea 100644 --- a/frame/shielded-pool/CHANGELOG.md +++ b/frame/shielded-pool/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to `pallet-shielded-pool` will be documented in this file. ## [Unreleased] +### Changed + +- **`private_transfer`, `unshield` and `claim_shielded_fees` now take a required + `circuit_version: u32`** (last param). The proof is verified against that + circuit version's VK instead of always the active version, so a note created + under an older circuit stays spendable after a VK rotation. The three operation + functions pass `Some(circuit_version)` to the verifier (no more hardcoded + `None`). **Consensus-affecting**: the dispatch signatures change → runtime + `spec_version` 3→4 and `transaction_version` 1→2. The EVM precompile calldata + gains a trailing `uint32 circuitVersion` (new selectors — see the precompile + changelog). +- **`validate_unsigned` rejects an unsupported circuit version early** + (`InvalidTransaction::Custom(10)`) via the new `ZkVerifier::is_supported_version` + port method, so the tx-pool is not flooded with proofs for versions that have + no registered VK. + ### Security - `private_transfer` now rejects an unregistered or unverified asset before any diff --git a/frame/shielded-pool/src/benchmarking.rs b/frame/shielded-pool/src/benchmarking.rs index 3a89d672..b176704d 100644 --- a/frame/shielded-pool/src/benchmarking.rs +++ b/frame/shielded-pool/src/benchmarking.rs @@ -142,6 +142,7 @@ mod benchmarks { asset_id, fee, Some(relayer), + 1u32, ); } @@ -181,6 +182,7 @@ mod benchmarks { Hash::default(), // change_commitment: [0u8; 32] for total unshield Default::default(), // change_encrypted_memo: empty for total unshield Some(relayer), // relayer resolves the fee recipient + 1u32, // circuit_version ); } @@ -247,6 +249,7 @@ mod benchmarks { memo, proof.try_into().expect("proof fits bound"), public_signals.try_into().expect("signals fit bound"), + 1u32, ); } diff --git a/frame/shielded-pool/src/lib.rs b/frame/shielded-pool/src/lib.rs index 1cde708d..ecae68e3 100644 --- a/frame/shielded-pool/src/lib.rs +++ b/frame/shielded-pool/src/lib.rs @@ -599,8 +599,10 @@ pub mod pallet { encrypted_memos: BoundedVec>, asset_id: u32, fee: BalanceOf, - // EVM address of the relay node that signed the tx (from precompile caller); None for direct Substrate. relayer: Option, + // Circuit version the spent notes were created under; the proof is + // verified against this version's VK (not merely the active one). + circuit_version: u32, ) -> DispatchResult { ensure_none(origin)?; @@ -614,6 +616,7 @@ pub mod pallet { asset_id, fee, relayer, + circuit_version, ) } @@ -663,6 +666,9 @@ pub mod pallet { change_encrypted_memo: FrameEncryptedMemo, // EVM address of the relay node that signed the tx (from precompile caller); None for direct Substrate. relayer: Option, + // Circuit version the spent notes were created under; the proof is + // verified against this version's VK (not merely the active one). + circuit_version: u32, ) -> DispatchResult { ensure_none(origin)?; @@ -678,6 +684,7 @@ pub mod pallet { change_commitment, change_encrypted_memo, relayer, + circuit_version, ) } @@ -795,6 +802,9 @@ pub mod pallet { memo: FrameEncryptedMemo, proof: BoundedVec>, public_signals: BoundedVec>, + // Circuit version the spent notes were created under; the proof is + // verified against this version's VK (not merely the active one). + circuit_version: u32, ) -> DispatchResult { let validator = ensure_signed(origin)?; crate::operations::fees::FeeOperation::claim_shielded::( @@ -805,6 +815,7 @@ pub mod pallet { memo, proof.into_inner(), public_signals.into_inner(), + circuit_version, ) } } @@ -835,12 +846,14 @@ pub mod pallet { nullifiers, fee, relayer, + circuit_version, .. } => crate::validate_unsigned::validate_private_transfer::( merkle_root, nullifiers, fee, relayer, + *circuit_version, ), Call::unshield { @@ -850,6 +863,7 @@ pub mod pallet { amount, fee, relayer, + circuit_version, .. } => crate::validate_unsigned::validate_unshield::( merkle_root, @@ -858,6 +872,7 @@ pub mod pallet { amount, fee, relayer, + *circuit_version, ), _ => InvalidTransaction::Call.into(), diff --git a/frame/shielded-pool/src/mock.rs b/frame/shielded-pool/src/mock.rs index b78228ad..b1c3da0f 100644 --- a/frame/shielded-pool/src/mock.rs +++ b/frame/shielded-pool/src/mock.rs @@ -129,6 +129,10 @@ impl ZkVerifierPort for MockZkVerifier { // Always return true for testing (bypass ZK verification) Ok(true) } + + fn is_supported_version(_circuit_id: u32, version: u32) -> bool { + version != 0 + } } /// Block-author provider for the real `pallet-relayer` in tests. diff --git a/frame/shielded-pool/src/operations/fees.rs b/frame/shielded-pool/src/operations/fees.rs index 8b045cd4..b6eb72bd 100644 --- a/frame/shielded-pool/src/operations/fees.rs +++ b/frame/shielded-pool/src/operations/fees.rs @@ -26,6 +26,7 @@ impl FeeOperation { /// /// # public_signals layout (76 bytes) /// `commitment[0..32] | value[32..40] | asset_id[40..44] | owner_hash[44..76]` + #[allow(clippy::too_many_arguments)] pub fn claim_shielded( validator: T::AccountId, commitment: Commitment, @@ -34,6 +35,7 @@ impl FeeOperation { memo: EncryptedMemo, proof: sp_std::vec::Vec, public_signals: sp_std::vec::Vec, + circuit_version: u32, ) -> DispatchResult { ensure!( Assets::::contains_key(asset_id), @@ -53,9 +55,12 @@ impl FeeOperation { #[cfg(not(feature = "skip-proof-verification"))] { - let is_valid = T::ZkVerifier::verify_value_proof(&proof, &public_signals, None)?; + let is_valid = + T::ZkVerifier::verify_value_proof(&proof, &public_signals, Some(circuit_version))?; ensure!(is_valid, Error::::InvalidProof); } + #[cfg(feature = "skip-proof-verification")] + let _ = circuit_version; // signals[0..32]: commitment must match the extrinsic argument ensure!( @@ -160,6 +165,7 @@ mod tests { make_memo(), make_proof(), make_signals(&commitment, amount, asset_id), + 1, )); }); } @@ -177,6 +183,7 @@ mod tests { make_memo(), make_proof(), make_signals(&commitment, 100u128, 99u32), + 1, ), crate::pallet::Error::::InvalidAssetId ); @@ -201,6 +208,7 @@ mod tests { make_memo(), make_proof(), make_signals(&commitment, amount, asset_id), + 1, ), crate::pallet::Error::::InsufficientPendingFees ); @@ -226,6 +234,7 @@ mod tests { make_memo(), make_proof(), make_signals(&commitment, amount, asset_id), + 1, )); assert!(CommitmentRepository::exists::(&commitment)); @@ -250,6 +259,7 @@ mod tests { memo.clone(), make_proof(), make_signals(&commitment, amount, asset_id), + 1, )); let stored = CommitmentRepository::get_memo::(&commitment); @@ -275,6 +285,7 @@ mod tests { make_memo(), make_proof(), make_signals(&commitment, claim, asset_id), + 1, )); let remaining = mock_pending_fees_get(validator, asset_id); @@ -299,6 +310,7 @@ mod tests { make_memo(), make_proof(), make_signals(&commitment, amount, asset_id), + 1, )); let events = frame_system::Pallet::::events(); @@ -337,6 +349,7 @@ mod tests { make_memo(), make_proof(), make_signals(&commitment, 1u128, asset_id), + 1, ), crate::pallet::Error::::InsufficientPendingFees ); @@ -363,6 +376,7 @@ mod tests { make_memo(), make_proof(), make_signals(&commitment, 0u128, asset_id), + 1, ), crate::pallet::Error::::InvalidAmount ); @@ -407,6 +421,7 @@ mod tests { make_memo(), make_rejected_proof(), // Ok(false) from verifier make_signals(&commitment, amount, asset_id), + 1, ), crate::pallet::Error::::InvalidProof ); @@ -432,6 +447,7 @@ mod tests { make_memo(), make_rejected_proof(), make_signals(&commitment, amount, asset_id), + 1, ); // Commitment must NOT be in the tree @@ -459,6 +475,7 @@ mod tests { make_memo(), vec![], // empty proof make_signals(&commitment, amount, asset_id), + 1, ), crate::pallet::Error::::InvalidProof ); @@ -483,6 +500,7 @@ mod tests { make_memo(), vec![0x01u8; 64], // wrong length (not 128) make_signals(&commitment, amount, asset_id), + 1, ), crate::pallet::Error::::InvalidProof ); @@ -507,6 +525,7 @@ mod tests { make_memo(), make_proof(), vec![0u8; 32], // wrong length (not 76) + 1, ), crate::pallet::Error::::InvalidPublicSignals ); @@ -533,6 +552,7 @@ mod tests { make_memo(), make_proof(), make_signals(&other_commitment, amount, asset_id), + 1, ), crate::pallet::Error::::InvalidPublicSignals ); @@ -558,6 +578,7 @@ mod tests { make_memo(), make_proof(), make_signals(&commitment, 999u128, asset_id), + 1, ), crate::pallet::Error::::InvalidPublicSignals ); @@ -583,6 +604,7 @@ mod tests { make_memo(), make_proof(), make_signals(&commitment, amount, 999u32), + 1, ), crate::pallet::Error::::InvalidPublicSignals ); @@ -621,6 +643,7 @@ mod tests { make_memo(), make_proof(), make_signals(&commitment, amount, asset_id), + 1, )); // The claim swaps a pending number for a note; both are backed by the diff --git a/frame/shielded-pool/src/operations/private_transfer.rs b/frame/shielded-pool/src/operations/private_transfer.rs index fe57c722..aa50c1b2 100644 --- a/frame/shielded-pool/src/operations/private_transfer.rs +++ b/frame/shielded-pool/src/operations/private_transfer.rs @@ -23,6 +23,7 @@ impl PrivateTransferOperation { asset_id: u32, fee: <::Currency as Currency<::AccountId>>::Balance, relayer_evm: Option, + circuit_version: u32, ) -> DispatchResult { let asset = AssetRepository::get_asset::(asset_id).ok_or(Error::::InvalidAssetId)?; ensure!(asset.is_verified, Error::::AssetNotVerified); @@ -82,11 +83,13 @@ impl PrivateTransferOperation { &commitment_arrays, asset_id, fee_u128, - None, + Some(circuit_version), )?; ensure!(valid, Error::::ProofVerificationFailed); } + #[cfg(feature = "skip-proof-verification")] + let _ = circuit_version; #[cfg(feature = "skip-proof-verification")] { @@ -219,6 +222,7 @@ mod tests { 0u32, 0u128, None, + 1, )); }); } @@ -237,6 +241,7 @@ mod tests { 0u32, 0u128, None, + 1, )); }); } @@ -255,6 +260,7 @@ mod tests { 0u32, 0u128, None, + 1, ), crate::pallet::Error::::UnknownMerkleRoot ); @@ -278,6 +284,7 @@ mod tests { 0u32, 0u128, None, + 1, ), crate::pallet::Error::::NullifierAlreadyUsed ); @@ -300,6 +307,7 @@ mod tests { 0u32, 0u128, None, + 1, ), crate::pallet::Error::::MemoCommitmentMismatch ); @@ -324,6 +332,7 @@ mod tests { 0u32, 0u128, None, + 1, ), crate::pallet::Error::::InvalidMemoSize ); @@ -349,6 +358,7 @@ mod tests { 0u32, 0u128, None, + 1, )); assert!(PrivateTransferOperation::is_nullifier_used::(&n1)); @@ -373,6 +383,7 @@ mod tests { 0u32, 0u128, None, + 1, )); assert!(CommitmentRepository::exists::(&c)); @@ -396,6 +407,7 @@ mod tests { 0u32, 0u128, None, + 1, )); let events = frame_system::Pallet::::events(); @@ -437,6 +449,7 @@ mod tests { 0u32, fee, None, + 1, )); // MockRelayer block_author = Some(1) @@ -459,6 +472,7 @@ mod tests { 0u32, 0u128, None, + 1, )); let pending = crate::mock::mock_pending_fees_get(acc(1), 0u32); @@ -514,6 +528,7 @@ mod tests { 0u32, 0u128, None, + 1, )); // Real nullifier must be marked used @@ -547,6 +562,7 @@ mod tests { 0u32, 0u128, None, + 1, )); // Second tx with a different real nullifier but same dummy — must succeed @@ -559,6 +575,7 @@ mod tests { 0u32, 0u128, None, + 1, )); }); } @@ -584,6 +601,7 @@ mod tests { 0u32, 0u128, None, + 1, ), Error::::InvalidAmount ); @@ -608,6 +626,7 @@ mod tests { 0u32, 0u128, None, + 1, ), Error::::TooManyInputsOrOutputs ); @@ -649,6 +668,7 @@ mod tests { asset_id, fee, None, + 1, )); assert_eq!( @@ -689,6 +709,7 @@ mod tests { 0u32, 30u128, Some(sp_core::H160::from([0xAA; 20])), + 1, )); assert_eq!(crate::mock::mock_pending_fees_get(relayer_acct, 0u32), 30); @@ -702,6 +723,7 @@ mod tests { 0u32, 20u128, Some(sp_core::H160::from([0xBB; 20])), + 1, )); assert_eq!(crate::mock::mock_pending_fees_get(acc(1), 0u32), 20); }); @@ -724,6 +746,7 @@ mod tests { 0u32, 25u128, None, + 1, ), Error::::FeeRecipientUnavailable ); @@ -750,6 +773,7 @@ mod tests { 0u32, 0u128, None, + 1, ), Error::::AssetNotVerified ); @@ -772,6 +796,7 @@ mod tests { 999u32, 0u128, None, + 1, ), Error::::InvalidAssetId ); diff --git a/frame/shielded-pool/src/operations/unshield.rs b/frame/shielded-pool/src/operations/unshield.rs index 9a12e2e2..2b66aa10 100644 --- a/frame/shielded-pool/src/operations/unshield.rs +++ b/frame/shielded-pool/src/operations/unshield.rs @@ -45,6 +45,7 @@ impl UnshieldOperation { change_commitment: [u8; 32], change_encrypted_memo: FrameEncryptedMemo, relayer_evm: Option, + circuit_version: u32, ) -> DispatchResult { let asset = AssetRepository::get_asset::(asset_id).ok_or(Error::::InvalidAssetId)?; ensure!(asset.is_verified, Error::::AssetNotVerified); @@ -109,11 +110,13 @@ impl UnshieldOperation { asset_id, fee_u128, &change_commitment, - None, + Some(circuit_version), )?; ensure!(valid, Error::::ProofVerificationFailed); } + #[cfg(feature = "skip-proof-verification")] + let _ = circuit_version; #[cfg(feature = "skip-proof-verification")] { @@ -265,6 +268,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, )); }); } @@ -285,6 +289,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, ), crate::pallet::Error::::InvalidAssetId ); @@ -312,6 +317,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, ), crate::pallet::Error::::AssetNotVerified ); @@ -340,6 +346,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, ), crate::pallet::Error::::InvalidAmount ); @@ -366,6 +373,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, ), crate::pallet::Error::::InvalidRecipient ); @@ -391,6 +399,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, ), crate::pallet::Error::::UnknownMerkleRoot ); @@ -419,6 +428,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, ), crate::pallet::Error::::NullifierAlreadyUsed ); @@ -445,6 +455,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, ), crate::pallet::Error::::InsufficientPoolBalance ); @@ -471,6 +482,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, )); assert!(UnshieldOperation::is_nullifier_used::(&n)); }); @@ -495,6 +507,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, )); let remaining = PoolBalanceRepository::get_asset_balance::(asset_id); @@ -525,6 +538,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, )); let after = @@ -552,6 +566,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, )); let events = frame_system::Pallet::::events(); @@ -592,6 +607,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, )); // MockRelayer block_author returns Some(1); fee should be accumulated there @@ -624,6 +640,7 @@ mod tests { change_comm_bytes, FrameEncryptedMemo::default(), None, + 1, )); // The change commitment must now exist as a leaf in the Merkle tree. @@ -680,6 +697,7 @@ mod tests { [0u8; 32], // zero change_commitment = total unshield FrameEncryptedMemo::default(), None, + 1, )); // Pool balance must be zero. @@ -737,6 +755,7 @@ mod tests { change_comm_bytes, FrameEncryptedMemo::default(), None, + 1, ), Error::::CommitmentAlreadyExists ); @@ -801,6 +820,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, )); // Only `amount` left the pool; `fee` stays as backing for pending fees. @@ -834,6 +854,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, ), crate::pallet::Error::::InsufficientPoolBalance ); @@ -851,6 +872,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, )); }); } @@ -894,6 +916,7 @@ mod tests { [0u8; 32], memo.clone(), None, + 1, )); assert_eq!(tracked(asset_id), 300); assert_eq!(tracked(asset_id), pool_physical()); @@ -913,6 +936,7 @@ mod tests { crate::types::EncryptedMemo::from_bytes(&[0u8; 176]).unwrap(), vec![0x01u8; 128], signals, + 1, )); assert_eq!(tracked(asset_id), 300); assert_eq!(tracked(asset_id), pool_physical()); @@ -930,6 +954,7 @@ mod tests { [0u8; 32], memo, None, + 1, )); assert_eq!(tracked(asset_id), 250); assert_eq!(tracked(asset_id), pool_physical()); @@ -972,6 +997,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), Some(evm(0xAA)), + 1, )); assert_eq!( @@ -1007,6 +1033,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), Some(evm(0xBB)), + 1, )); assert_eq!( @@ -1036,6 +1063,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, )); assert_eq!( @@ -1071,6 +1099,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, ), crate::pallet::Error::::FeeRecipientUnavailable ); @@ -1098,6 +1127,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, )); }); } @@ -1126,6 +1156,7 @@ mod tests { [0u8; 32], FrameEncryptedMemo::default(), None, + 1, ), crate::pallet::Error::::AssetNotVerified ); diff --git a/frame/shielded-pool/src/validate_unsigned.rs b/frame/shielded-pool/src/validate_unsigned.rs index 934985c5..06dc5653 100644 --- a/frame/shielded-pool/src/validate_unsigned.rs +++ b/frame/shielded-pool/src/validate_unsigned.rs @@ -13,12 +13,18 @@ use crate::{ }; use frame_support::pallet_prelude::*; use pallet_relayer::RelayerInterface as _; +use pallet_zk_verifier::ZkVerifierPort as _; use parity_scale_codec::Encode; use sp_runtime::{ SaturatedConversion, transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, }; +/// On-chain circuit ids used for the version guard (mirror the zk-verifier's +/// `CircuitId` constants: TRANSFER = 1, UNSHIELD = 2). +const CIRCUIT_TRANSFER: u32 = 1; +const CIRCUIT_UNSHIELD: u32 = 2; + /// How long an unsigned transaction stays valid in the pool, in blocks. Bounded /// so a transaction that never gets included does not linger indefinitely. const TX_LONGEVITY: u64 = 64; @@ -29,7 +35,13 @@ pub fn validate_private_transfer( nullifiers: &BoundedVec>, fee: &BalanceOf, relayer: &Option, + circuit_version: u32, ) -> TransactionValidity { + // Anti-spam: reject an unsupported circuit version before pool admission. + if !T::ZkVerifier::is_supported_version(CIRCUIT_TRANSFER, circuit_version) { + return InvalidTransaction::Custom(10).into(); + } + // Anti-spam: fee must meet minimum relay fee let min_fee: BalanceOf = T::Relayer::min_relay_fee().saturated_into(); if *fee < min_fee { @@ -85,7 +97,13 @@ pub fn validate_unshield( amount: &BalanceOf, fee: &BalanceOf, relayer: &Option, + circuit_version: u32, ) -> TransactionValidity { + // Anti-spam: reject an unsupported circuit version before pool admission. + if !T::ZkVerifier::is_supported_version(CIRCUIT_UNSHIELD, circuit_version) { + return InvalidTransaction::Custom(10).into(); + } + // Anti-spam: fee must meet minimum relay fee let min_fee: BalanceOf = T::Relayer::min_relay_fee().saturated_into(); if *fee < min_fee { @@ -156,6 +174,7 @@ mod tests { &nullifiers_of(&[0x01]), &0u128, &None, + 1, ); assert!(result.is_ok()); }); @@ -169,6 +188,7 @@ mod tests { &nullifiers_of(&[0x01]), &0u128, &None, + 1, ); assert!(result.is_err()); }); @@ -185,6 +205,7 @@ mod tests { &nullifiers_of(&[0x05]), &0u128, &None, + 1, ); assert!(result.is_err()); }); @@ -202,6 +223,7 @@ mod tests { &nullifiers_of(&[0x10, 0x11]), &0u128, &None, + 1, ); assert!(result.is_err()); }); @@ -216,6 +238,7 @@ mod tests { &nullifiers_of(&[0xA1, 0xA2]), &100u128, // non-zero fee, &None, + 1, ); assert!(result.is_ok()); }); @@ -233,7 +256,8 @@ mod tests { let mut nullifiers: BoundedVec> = BoundedVec::new(); nullifiers.try_push(make_nullifier(0x01)).ok(); nullifiers.try_push(Nullifier::new([0u8; 32])).ok(); // dummy - let result = validate_private_transfer::(&KNOWN_ROOT, &nullifiers, &0u128, &None); + let result = + validate_private_transfer::(&KNOWN_ROOT, &nullifiers, &0u128, &None, 1); assert!( result.is_ok(), "dummy nullifier should not cause Stale rejection" @@ -252,7 +276,8 @@ mod tests { let mut nullifiers: BoundedVec> = BoundedVec::new(); nullifiers.try_push(real).ok(); nullifiers.try_push(Nullifier::new([0u8; 32])).ok(); // dummy - let result = validate_private_transfer::(&KNOWN_ROOT, &nullifiers, &0u128, &None); + let result = + validate_private_transfer::(&KNOWN_ROOT, &nullifiers, &0u128, &None, 1); assert!( result.is_err(), "used real nullifier must still be rejected" @@ -269,7 +294,8 @@ mod tests { let mut nullifiers: BoundedVec> = BoundedVec::new(); nullifiers.try_push(Nullifier::new([0u8; 32])).ok(); nullifiers.try_push(Nullifier::new([0u8; 32])).ok(); - let result = validate_private_transfer::(&KNOWN_ROOT, &nullifiers, &0u128, &None); + let result = + validate_private_transfer::(&KNOWN_ROOT, &nullifiers, &0u128, &None, 1); assert!(result.is_err(), "all-dummy-nullifier tx must be rejected"); }); } @@ -288,6 +314,7 @@ mod tests { &500u128, &0u128, &None, + 1, ); assert!(result.is_ok()); }); @@ -304,6 +331,7 @@ mod tests { &500u128, &0u128, &None, + 1, ); assert!(result.is_err()); }); @@ -316,7 +344,8 @@ mod tests { PoolBalanceRepository::set_asset_balance::(0, 1_000u128); let n = make_nullifier(0x20); NullifierRepository::mark_as_used::(n, 1u64); - let result = validate_unshield::(&KNOWN_ROOT, &n, &0u32, &500u128, &0u128, &None); + let result = + validate_unshield::(&KNOWN_ROOT, &n, &0u32, &500u128, &0u128, &None, 1); assert!(result.is_err()); }); } @@ -333,6 +362,7 @@ mod tests { &100u128, // 100 > 50 &0u128, &None, + 1, ); assert!(result.is_err()); }); @@ -351,6 +381,7 @@ mod tests { &100u128, &60u128, &None, + 1, ); assert!(result.is_err()); }); @@ -369,6 +400,7 @@ mod tests { &100u128, &50u128, &None, + 1, ); assert!(result.is_ok()); }); @@ -392,6 +424,7 @@ mod tests { &nullifiers_of(&[0x01]), &50u128, &None, + 1, ); assert!(result.is_err(), "fee below minimum must be rejected"); assert_eq!( @@ -414,6 +447,7 @@ mod tests { &nullifiers_of(&[0x01]), &100u128, &None, + 1, ); assert!(result.is_ok(), "fee equal to minimum must be accepted"); }); @@ -433,6 +467,7 @@ mod tests { &100u128, &50u128, &None, + 1, ); assert!(result.is_err(), "fee below minimum must be rejected"); assert_eq!( @@ -458,6 +493,7 @@ mod tests { &100u128, &200u128, &None, + 1, ); assert!(result.is_ok(), "fee equal to minimum must be accepted"); }); @@ -486,6 +522,7 @@ mod tests { &100u128, &10u128, &Some(evm(0xAA)), + 1, ) .unwrap(); let b = validate_unshield::( @@ -495,10 +532,12 @@ mod tests { &100u128, &10u128, &Some(evm(0xBB)), + 1, ) .unwrap(); - let none = validate_unshield::(&KNOWN_ROOT, &n, &0u32, &100u128, &10u128, &None) - .unwrap(); + let none = + validate_unshield::(&KNOWN_ROOT, &n, &0u32, &100u128, &10u128, &None, 1) + .unwrap(); assert_ne!(a.provides, b.provides, "different relayer → different tags"); assert_ne!(a.provides, none.provides, "Some vs None → different tags"); @@ -523,6 +562,7 @@ mod tests { &100u128, &10u128, &Some(evm(0xAA)), + 1, ) .unwrap(); let b = validate_unshield::( @@ -532,6 +572,7 @@ mod tests { &100u128, &10u128, &Some(evm(0xAA)), + 1, ) .unwrap(); assert_eq!(a.provides, b.provides); @@ -545,10 +586,12 @@ mod tests { MerkleRepository::add_historic_poseidon_root::(KNOWN_ROOT); let ns = nullifiers_of(&[0x63]); - let a = validate_private_transfer::(&KNOWN_ROOT, &ns, &10u128, &Some(evm(0xAA))) - .unwrap(); - let b = validate_private_transfer::(&KNOWN_ROOT, &ns, &10u128, &Some(evm(0xBB))) - .unwrap(); + let a = + validate_private_transfer::(&KNOWN_ROOT, &ns, &10u128, &Some(evm(0xAA)), 1) + .unwrap(); + let b = + validate_private_transfer::(&KNOWN_ROOT, &ns, &10u128, &Some(evm(0xBB)), 1) + .unwrap(); assert_ne!(a.provides, b.provides); }); } @@ -566,6 +609,7 @@ mod tests { &nullifiers_of(&[0x90]), &10u128, &None, + 1, ) .unwrap(); assert_eq!(t.longevity, TX_LONGEVITY); @@ -578,9 +622,59 @@ mod tests { &100u128, &10u128, &None, + 1, ) .unwrap(); assert_eq!(u.longevity, TX_LONGEVITY); }); } + + // ── circuit-version guard (anti-spam) ───────────────────────────────────── + // + // The mock's `is_supported_version` treats version 0 as unsupported; a + // supported version passes the guard, an unsupported one is rejected before + // any other check. + + #[test] + fn private_transfer_unsupported_version_rejected() { + new_test_ext().execute_with(|| { + MerkleRepository::add_historic_poseidon_root::(KNOWN_ROOT); + let result = validate_private_transfer::( + &KNOWN_ROOT, + &nullifiers_of(&[0x01]), + &0u128, + &None, + 0, + ); + assert_eq!( + result.unwrap_err(), + sp_runtime::transaction_validity::TransactionValidityError::Invalid( + sp_runtime::transaction_validity::InvalidTransaction::Custom(10) + ), + ); + }); + } + + #[test] + fn unshield_unsupported_version_rejected() { + new_test_ext().execute_with(|| { + MerkleRepository::add_historic_poseidon_root::(KNOWN_ROOT); + PoolBalanceRepository::set_asset_balance::(0, 1_000u128); + let result = validate_unshield::( + &KNOWN_ROOT, + &make_nullifier(0x10), + &0u32, + &500u128, + &0u128, + &None, + 0, + ); + assert_eq!( + result.unwrap_err(), + sp_runtime::transaction_validity::TransactionValidityError::Invalid( + sp_runtime::transaction_validity::InvalidTransaction::Custom(10) + ), + ); + }); + } } diff --git a/frame/zk-verifier/CHANGELOG.md b/frame/zk-verifier/CHANGELOG.md index 9b60356d..5f0642aa 100644 --- a/frame/zk-verifier/CHANGELOG.md +++ b/frame/zk-verifier/CHANGELOG.md @@ -4,6 +4,52 @@ All notable changes to this pallet are documented here. --- +## [Unreleased] + +### Added + +- **`Error::UnsupportedCircuitVersion`** — `verifier::verify` now distinguishes an + explicit version request with no registered VK (→ `UnsupportedCircuitVersion`) + from a `None`/active-unset resolution (→ `CircuitNotFound` / + `VerificationKeyNotFound`), giving callers a clear "that version is not + supported" signal instead of a generic key-not-found. +- **`ZkVerifierPort::is_supported_version(circuit_id, version) -> bool`** — returns + whether a VK is registered for `(circuit, version)` AND not retired, so callers + (e.g. the shielded-pool `validate_unsigned`) can reject an unsupported version early. +- **Dedicated benchmarks + `WeightInfo` for `retire_version` / `unretire_version`** + so both weigh their own storage cost instead of borrowing + `remove_verification_key`'s. + +### Security + +- **Version retirement (`retire_version` / `unretire_version`, Root only)** — a + `RetiredVersions` set lets governance refuse proofs for a `(circuit, version)` + whose VK is compromised/weak, WITHOUT deleting the VK (audit + stats preserved). + `verify` and `is_supported_version` reject a retired version fail-closed. This + closes the downgrade-to-weak-key path: superseding an active version with + `set_active_version` does not disable the old VK, so a caller could still request + the old version explicitly; retiring it makes notes minted under it unspendable + (the nuclear option for a bad VK). The active version cannot be retired. +- **`MAX_VERSIONS_PER_CIRCUIT = 64` cap** on registered versions per circuit + (`store_vk`), bounding the versions DoubleMap and the runtime-API iteration. + Registration is Root-only, so this is operator-discipline, not an attacker limit. +- **Stored VK hash (`VkHashes`)** — `blake2_256(key_data)` is computed once at + registration and read by the runtime API, instead of re-hashing every VK (up to + 8 KB) on each RPC call. Falls back to recompute for keys registered earlier. +- **Documented the circuit-version security invariant** on `register_verification_key`: + a new version of an existing circuit id must be a key rotation of a semantically + identical circuit; a semantic change must use a NEW circuit id. The note commitment + does not bind the version (shielded-pool Limitation 1), so this is a governance- + enforced rule that `ensure_vk_arity` (arity only) cannot check. + +### Fixed + +- **VK registration no longer rejects valid transfer keys.** `ensure_vk_arity` + compares a key's arity against `expected_public_inputs`, which returned 5 for + transfer while the circuit (and every published VK) has arity 7. Registering a + transfer VK failed with `InvalidVerificationKey`. Fixed upstream in + `orbinum-zk-verifier` (`TRANSFER_PUBLIC_INPUTS` 5 → 7). + ## [0.8.0] - 2026-07-04 ### Security diff --git a/frame/zk-verifier/src/benchmarking.rs b/frame/zk-verifier/src/benchmarking.rs index 2020e3b0..25733935 100644 --- a/frame/zk-verifier/src/benchmarking.rs +++ b/frame/zk-verifier/src/benchmarking.rs @@ -57,7 +57,7 @@ mod benchmarks { .bytes } - /// VK for the TRANSFER circuit (arity 5) — used by the storage benchmarks, + /// VK for the TRANSFER circuit (arity 7) — used by the storage benchmarks, /// which validate that a registered VK deserializes and matches circuit arity. fn sample_verification_key() -> Vec { synthetic_vk(orbinum_zk_verifier::TRANSFER_PUBLIC_INPUTS) @@ -186,6 +186,63 @@ mod benchmarks { )); } + #[benchmark] + fn retire_version() { + let circuit_id = CircuitId::TRANSFER; + let active = 1u32; + let target = 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_target = VerificationKeyInfo { + key_data: sample_verification_key().try_into().unwrap(), + system: ProofSystem::Groth16, + registered_at, + }; + + VerificationKeys::::insert(circuit_id, active, vk_active); + VerificationKeys::::insert(circuit_id, target, vk_target); + ActiveCircuitVersion::::insert(circuit_id, active); + + #[extrinsic_call] + _(RawOrigin::Root, circuit_id, target); + + assert!(RetiredVersions::::contains_key(circuit_id, target)); + } + + #[benchmark] + fn unretire_version() { + let circuit_id = CircuitId::TRANSFER; + let active_version = 1u32; + let retired = 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_retired = 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, retired, vk_retired); + ActiveCircuitVersion::::insert(circuit_id, active_version); + RetiredVersions::::insert(circuit_id, retired, ()); + + #[extrinsic_call] + _(RawOrigin::Root, circuit_id, retired); + + assert!(!RetiredVersions::::contains_key(circuit_id, retired)); + } + #[benchmark] fn batch_register_verification_keys(n: Linear<1, 10>) { let vk_bytes = sample_verification_key(); diff --git a/frame/zk-verifier/src/lib.rs b/frame/zk-verifier/src/lib.rs index e5e83571..9521c114 100644 --- a/frame/zk-verifier/src/lib.rs +++ b/frame/zk-verifier/src/lib.rs @@ -85,6 +85,23 @@ pub mod pallet { pub type ActiveCircuitVersion = StorageMap<_, Blake2_128Concat, CircuitId, u32, OptionQuery>; + /// Retired `(circuit_id, version)` pairs rejected during verification. + #[pallet::storage] + pub type RetiredVersions = + StorageDoubleMap<_, Blake2_128Concat, CircuitId, Blake2_128Concat, u32, (), OptionQuery>; + + /// Cached `blake2_256(key_data)` hash for each registered verification key. + #[pallet::storage] + pub type VkHashes = StorageDoubleMap< + _, + Blake2_128Concat, + CircuitId, + Blake2_128Concat, + u32, + [u8; 32], + OptionQuery, + >; + /// Verification statistics per (circuit_id, version). #[pallet::storage] pub type VerificationStats = StorageDoubleMap< @@ -122,6 +139,7 @@ pub mod pallet { .try_into() .expect("Genesis VK exceeds maximum size (8 KB)"); + let hash = sp_io::hashing::blake2_256(key_data.as_slice()); VerificationKeys::::insert( circuit_id, 1u32, @@ -131,6 +149,7 @@ pub mod pallet { registered_at: BlockNumberFor::::default(), }, ); + VkHashes::::insert(circuit_id, 1u32, hash); ActiveCircuitVersion::::insert(circuit_id, 1u32); } } @@ -162,6 +181,8 @@ pub mod pallet { VerificationKeyRegistered { circuit_id: CircuitId, version: u32 }, ActiveVersionSet { circuit_id: CircuitId, version: u32 }, VerificationKeyRemoved { circuit_id: CircuitId, version: u32 }, + VersionRetired { circuit_id: CircuitId, version: u32 }, + VersionUnretired { circuit_id: CircuitId, version: u32 }, ProofVerified { circuit_id: CircuitId, version: u32 }, ProofVerificationFailed { circuit_id: CircuitId, version: u32 }, BatchVerificationKeysRegistered { count: u32 }, @@ -192,6 +213,11 @@ pub mod pallet { CircuitAlreadyExists, ActiveVersionNotSet, CannotRemoveActiveVersion, + UnsupportedCircuitVersion, + CannotRetireActiveVersion, + VersionAlreadyRetired, + VersionNotRetired, + TooManyVersions, // Infrastructure RepositoryError, DeserializationError, @@ -206,6 +232,18 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Register a verification key version for a circuit (Root only). + /// + /// SECURITY INVARIANT: a new version of an EXISTING circuit id must only be + /// a key rotation of the *same* circuit (identical R1CS / public-signal + /// semantics — e.g. a fresh trusted setup). A note's circuit version is NOT + /// bound into its commitment (see shielded-pool Limitation 1), so the + /// submitter picks the version freely; verification is safe only because a + /// proof verifies solely under the exact VK of the circuit that produced it. + /// Registering a version whose circuit keeps the same public-input arity but + /// changes a constraint's *meaning* would let a note be spent under weaker + /// rules. A semantic change MUST use a NEW circuit id, never a new version. + /// `ensure_vk_arity` enforces arity, NOT semantics — that is a governance + /// responsibility. Retire a superseded weak version with `retire_version`. #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::register_verification_key())] pub fn register_verification_key( @@ -223,25 +261,13 @@ pub mod pallet { verification_key.len() >= 256, Error::::InvalidVerificationKey ); - // Reject a malformed or wrong-arity VK at registration, not at proof time. Self::ensure_vk_arity(circuit_id, &verification_key)?; - // Prevent silent overwrite of an existing VK. Replacing a key that is - // already referenced by recorded stats would desync the stats from the - // actual key in use. Use set_active_version to switch active versions. ensure!( !VerificationKeys::::contains_key(circuit_id, version), Error::::CircuitAlreadyExists ); - VerificationKeys::::insert( - circuit_id, - version, - VerificationKeyInfo { - key_data: verification_key, - system: ProofSystem::Groth16, - registered_at: frame_system::Pallet::::block_number(), - }, - ); + Self::store_vk(circuit_id, version, verification_key)?; if ActiveCircuitVersion::::get(circuit_id).is_none() { ActiveCircuitVersion::::insert(circuit_id, version); @@ -299,6 +325,7 @@ pub mod pallet { ensure!(active != version, Error::::CannotRemoveActiveVersion); VerificationKeys::::remove(circuit_id, version); + RetiredVersions::::remove(circuit_id, version); Self::deposit_event(Event::VerificationKeyRemoved { circuit_id, version, @@ -373,15 +400,11 @@ pub mod pallet { Error::::CircuitAlreadyExists ); - VerificationKeys::::insert( + Self::store_vk( entry.circuit_id, entry.version, - VerificationKeyInfo { - key_data: entry.verification_key.clone(), - system: ProofSystem::Groth16, - registered_at: frame_system::Pallet::::block_number(), - }, - ); + entry.verification_key.clone(), + )?; let auto_activate = ActiveCircuitVersion::::get(entry.circuit_id).is_none(); if entry.set_active || auto_activate { @@ -403,6 +426,56 @@ pub mod pallet { }); Ok(()) } + + /// Retires a verification key version while preserving its data. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::retire_version())] + pub fn retire_version( + origin: OriginFor, + circuit_id: CircuitId, + version: u32, + ) -> DispatchResult { + ensure_root(origin)?; + ensure!( + VerificationKeys::::contains_key(circuit_id, version), + Error::::VerificationKeyNotFound + ); + ensure!( + !RetiredVersions::::contains_key(circuit_id, version), + Error::::VersionAlreadyRetired + ); + let active = ActiveCircuitVersion::::get(circuit_id) + .ok_or(Error::::ActiveVersionNotSet)?; + ensure!(active != version, Error::::CannotRetireActiveVersion); + + RetiredVersions::::insert(circuit_id, version, ()); + Self::deposit_event(Event::VersionRetired { + circuit_id, + version, + }); + Ok(()) + } + + /// Reverse `retire_version`: allow proofs for `(circuit_id, version)` again. + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::unretire_version())] + pub fn unretire_version( + origin: OriginFor, + circuit_id: CircuitId, + version: u32, + ) -> DispatchResult { + ensure_root(origin)?; + ensure!( + RetiredVersions::::contains_key(circuit_id, version), + Error::::VersionNotRetired + ); + RetiredVersions::::remove(circuit_id, version); + Self::deposit_event(Event::VersionUnretired { + circuit_id, + version, + }); + Ok(()) + } } impl Pallet { @@ -422,6 +495,39 @@ pub mod pallet { } Ok(()) } + + /// Upper bound on stored versions per circuit — keeps the versions DoubleMap + /// and the runtime-API iteration provably bounded. Registration is Root-only, + /// so this is operator-discipline, not an attacker limit. + pub(crate) const MAX_VERSIONS_PER_CIRCUIT: u32 = 64; + + /// Insert a validated VK for `(circuit_id, version)` and store its hash. + /// Enforces the per-circuit version cap. Callers must have already run + /// `ensure_vk_arity` + the silent-overwrite check. + fn store_vk( + circuit_id: CircuitId, + version: u32, + key_data: BoundedVec>, + ) -> DispatchResult { + let count = VerificationKeys::::iter_key_prefix(circuit_id).count() as u32; + ensure!( + count < Self::MAX_VERSIONS_PER_CIRCUIT, + Error::::TooManyVersions + ); + + let hash = sp_io::hashing::blake2_256(key_data.as_slice()); + VerificationKeys::::insert( + circuit_id, + version, + VerificationKeyInfo { + key_data, + system: ProofSystem::Groth16, + registered_at: frame_system::Pallet::::block_number(), + }, + ); + VkHashes::::insert(circuit_id, version, hash); + Ok(()) + } } } @@ -432,7 +538,7 @@ mod tests { use super::*; use crate::{ mock::{MaxProofSize, MaxPublicInputs, RuntimeEvent, Test, ZkVerifier}, - pallet::{ActiveCircuitVersion, Event, VerificationKeys}, + pallet::{ActiveCircuitVersion, Event, RetiredVersions, VerificationKeys}, types::{ProofSystem, VkEntry}, }; use frame_support::{BoundedVec, assert_err, assert_noop, assert_ok}; @@ -1362,6 +1468,146 @@ mod tests { }); } + // ── retire_version / unretire_version ───────────────────────────────────── + + #[test] + fn retire_version_requires_root() { + new_test_ext().execute_with(|| { + insert_vk(CircuitId::TRANSFER, 1); + insert_vk(CircuitId::TRANSFER, 2); + activate(CircuitId::TRANSFER, 1); + assert_noop!( + ZkVerifier::retire_version(signed().into(), CircuitId::TRANSFER, 2), + sp_runtime::DispatchError::BadOrigin + ); + }); + } + + #[test] + fn retire_version_rejects_active_and_unknown() { + new_test_ext().execute_with(|| { + insert_vk(CircuitId::TRANSFER, 1); + activate(CircuitId::TRANSFER, 1); + // active version cannot be retired + assert_noop!( + ZkVerifier::retire_version(root().into(), CircuitId::TRANSFER, 1), + Error::::CannotRetireActiveVersion + ); + // unknown version cannot be retired + assert_noop!( + ZkVerifier::retire_version(root().into(), CircuitId::TRANSFER, 9), + Error::::VerificationKeyNotFound + ); + }); + } + + #[test] + fn retire_then_unretire_toggles_the_flag() { + new_test_ext().execute_with(|| { + insert_vk(CircuitId::TRANSFER, 1); + insert_vk(CircuitId::TRANSFER, 2); + activate(CircuitId::TRANSFER, 1); + + assert_ok!(ZkVerifier::retire_version( + root().into(), + CircuitId::TRANSFER, + 2 + )); + assert!(RetiredVersions::::contains_key( + CircuitId::TRANSFER, + 2 + )); + assert!(has_event(Event::VersionRetired { + circuit_id: CircuitId::TRANSFER, + version: 2 + })); + // double-retire rejected + assert_noop!( + ZkVerifier::retire_version(root().into(), CircuitId::TRANSFER, 2), + Error::::VersionAlreadyRetired + ); + + assert_ok!(ZkVerifier::unretire_version( + root().into(), + CircuitId::TRANSFER, + 2 + )); + assert!(!RetiredVersions::::contains_key( + CircuitId::TRANSFER, + 2 + )); + // unretire of a non-retired version rejected + assert_noop!( + ZkVerifier::unretire_version(root().into(), CircuitId::TRANSFER, 2), + Error::::VersionNotRetired + ); + }); + } + + #[test] + fn removing_a_vk_clears_its_retirement_flag() { + new_test_ext().execute_with(|| { + insert_vk(CircuitId::TRANSFER, 1); + insert_vk(CircuitId::TRANSFER, 2); + activate(CircuitId::TRANSFER, 1); + assert_ok!(ZkVerifier::retire_version( + root().into(), + CircuitId::TRANSFER, + 2 + )); + assert_ok!(ZkVerifier::remove_verification_key( + root().into(), + CircuitId::TRANSFER, + 2 + )); + assert!(!RetiredVersions::::contains_key( + CircuitId::TRANSFER, + 2 + )); + }); + } + + // ── version cap + stored vk_hash ────────────────────────────────────────── + + #[test] + fn register_stores_the_vk_hash() { + new_test_ext().execute_with(|| { + let vk = real_vk(TRANSFER_PUBLIC_INPUTS); + assert_ok!(ZkVerifier::register_verification_key( + root().into(), + CircuitId::TRANSFER, + 1, + vk.clone() + )); + let expected = sp_io::hashing::blake2_256(vk.as_slice()); + assert_eq!( + crate::pallet::VkHashes::::get(CircuitId::TRANSFER, 1), + Some(expected) + ); + }); + } + + #[test] + fn register_rejects_beyond_max_versions_per_circuit() { + new_test_ext().execute_with(|| { + // Fill the circuit up to the cap with direct inserts (versions 1..=MAX). + let max = Pallet::::MAX_VERSIONS_PER_CIRCUIT; + for v in 1..=max { + insert_vk(CircuitId::TRANSFER, v); + } + // One more via the real path must be rejected by the cap. + assert_noop!( + ZkVerifier::register_verification_key( + root().into(), + CircuitId::TRANSFER, + max + 1, + real_vk(TRANSFER_PUBLIC_INPUTS) + ), + Error::::TooManyVersions + ); + }); + } + // The integrity_test must abort when verification is compiled out WITHOUT the // benchmark feature — i.e. a would-be release runtime with no verification. Only // compiles in that exact combination (skip on, benchmarks off). diff --git a/frame/zk-verifier/src/port.rs b/frame/zk-verifier/src/port.rs index f6ae0db0..4065c975 100644 --- a/frame/zk-verifier/src/port.rs +++ b/frame/zk-verifier/src/port.rs @@ -57,6 +57,9 @@ pub trait ZkVerifierPort { call_hash_fe: &[u8; 32], version: Option, ) -> Result; + + /// Whether a verification key is registered for `(circuit_id, version)`. + fn is_supported_version(circuit_id: u32, version: u32) -> bool; } // ─── impl ───────────────────────────────────────────────────────────────────── @@ -132,6 +135,12 @@ impl ZkVerifierPort for Pallet { let raw = encoding::encode_private_link(commitment, call_hash_fe); verifier::verify::(CircuitId::PRIVATE_LINK, version, proof, raw).map(|(ok, _)| ok) } + + fn is_supported_version(circuit_id: u32, version: u32) -> bool { + let cid = CircuitId(circuit_id); + crate::pallet::VerificationKeys::::contains_key(cid, version) + && !crate::pallet::RetiredVersions::::contains_key(cid, version) + } } // ─── Tests ──────────────────────────────────────────────────────────────────── @@ -142,7 +151,7 @@ mod tests { use crate::{ Error, mock::Test, - pallet::{ActiveCircuitVersion, VerificationKeys}, + pallet::{ActiveCircuitVersion, RetiredVersions, VerificationKeys}, types::{ProofSystem, VerificationKeyInfo}, }; use frame_support::{BoundedVec, assert_err}; @@ -234,7 +243,7 @@ mod tests { } #[test] - fn transfer_missing_vk_returns_not_found() { + fn transfer_explicit_unsupported_version_rejected() { new_test_ext().execute_with(|| { assert_err!( as ZkVerifierPort>::verify_transfer_proof( @@ -246,7 +255,7 @@ mod tests { 0, Some(99), ), - Error::::VerificationKeyNotFound + Error::::UnsupportedCircuitVersion ); }); } @@ -399,7 +408,7 @@ mod tests { } #[test] - fn unshield_missing_vk_returns_not_found() { + fn unshield_explicit_unsupported_version_rejected() { new_test_ext().execute_with(|| { assert_err!( as ZkVerifierPort>::verify_unshield_proof( @@ -413,7 +422,7 @@ mod tests { &[0u8; 32], Some(99), ), - Error::::VerificationKeyNotFound + Error::::UnsupportedCircuitVersion ); }); } @@ -519,7 +528,7 @@ mod tests { } #[test] - fn private_link_missing_vk_returns_not_found() { + fn private_link_explicit_unsupported_version_rejected() { new_test_ext().execute_with(|| { assert_err!( as ZkVerifierPort>::verify_private_link_proof( @@ -528,7 +537,7 @@ mod tests { &[0u8; 32], Some(99), ), - Error::::VerificationKeyNotFound + Error::::UnsupportedCircuitVersion ); }); } @@ -565,4 +574,67 @@ mod tests { assert!(ok); }); } + + // ── is_supported_version ─────────────────────────────────────────────────── + + #[test] + fn is_supported_version_reflects_registered_vks() { + new_test_ext().execute_with(|| { + // No VK yet → unsupported. + assert!(! as ZkVerifierPort>::is_supported_version( + CircuitId::TRANSFER.0, + 1 + )); + insert_vk(CircuitId::TRANSFER, 1); + // Registered → supported; other versions/circuits stay unsupported. + assert!( as ZkVerifierPort>::is_supported_version( + CircuitId::TRANSFER.0, + 1 + )); + assert!(! as ZkVerifierPort>::is_supported_version( + CircuitId::TRANSFER.0, + 2 + )); + assert!(! as ZkVerifierPort>::is_supported_version( + CircuitId::UNSHIELD.0, + 1 + )); + }); + } + + #[test] + fn retired_version_is_not_supported_and_verify_rejects_it() { + new_test_ext().execute_with(|| { + // v1 active, v2 registered and retired. + insert_vk(CircuitId::TRANSFER, 1); + insert_vk(CircuitId::TRANSFER, 2); + activate(CircuitId::TRANSFER, 1); + RetiredVersions::::insert(CircuitId::TRANSFER, 2, ()); + + // A retired version drops out of is_supported_version even though its VK exists. + assert!(VerificationKeys::::contains_key( + CircuitId::TRANSFER, + 2 + )); + assert!(! as ZkVerifierPort>::is_supported_version( + CircuitId::TRANSFER.0, + 2 + )); + + // verify against the retired version fails closed (UnsupportedCircuitVersion), + // not by falling through to the active version. + assert_err!( + as ZkVerifierPort>::verify_transfer_proof( + &proof(), + &merkle_root(), + &[[0u8; 32]], + &[[0u8; 32]], + 0, + 0, + Some(2), + ), + Error::::UnsupportedCircuitVersion + ); + }); + } } diff --git a/frame/zk-verifier/src/runtime_api.rs b/frame/zk-verifier/src/runtime_api.rs index 642ca84a..a1801fd7 100644 --- a/frame/zk-verifier/src/runtime_api.rs +++ b/frame/zk-verifier/src/runtime_api.rs @@ -7,7 +7,7 @@ extern crate alloc; use crate::{ CircuitVersionInfo, VkVersionHash, - pallet::{ActiveCircuitVersion, Config, VerificationKeys}, + pallet::{ActiveCircuitVersion, Config, RetiredVersions, VerificationKeys, VkHashes}, types::CircuitId, }; @@ -18,6 +18,7 @@ impl crate::Pallet { let mut supported: alloc::vec::Vec = VerificationKeys::::iter_prefix(cid) .map(|(v, _)| v) + .filter(|v| !RetiredVersions::::contains_key(cid, v)) .collect(); if supported.is_empty() { return None; @@ -29,9 +30,13 @@ impl crate::Pallet { let vk_hashes = supported .iter() .filter_map(|v| { - VerificationKeys::::get(cid, v).map(|vk| VkVersionHash { + let vk_hash = VkHashes::::get(cid, v).or_else(|| { + VerificationKeys::::get(cid, v) + .map(|vk| sp_io::hashing::blake2_256(vk.key_data.as_slice())) + })?; + Some(VkVersionHash { version: *v, - vk_hash: sp_io::hashing::blake2_256(vk.key_data.as_slice()), + vk_hash, }) }) .collect(); diff --git a/frame/zk-verifier/src/verifier.rs b/frame/zk-verifier/src/verifier.rs index 45f88c21..f3e73fd7 100644 --- a/frame/zk-verifier/src/verifier.rs +++ b/frame/zk-verifier/src/verifier.rs @@ -6,7 +6,7 @@ use crate::{ Error, - pallet::{ActiveCircuitVersion, Config, VerificationKeys, VerificationStats}, + pallet::{ActiveCircuitVersion, Config, RetiredVersions, VerificationKeys, VerificationStats}, types::CircuitId, }; use alloc::vec::Vec; @@ -29,12 +29,21 @@ pub fn verify( frame_support::ensure!(!proof_bytes.is_empty(), Error::::EmptyProof); frame_support::ensure!(!raw_inputs.is_empty(), Error::::EmptyPublicInputs); + let explicit = version.is_some(); let resolved = version .or_else(|| ActiveCircuitVersion::::get(circuit_id)) .ok_or(Error::::CircuitNotFound)?; - let vk_info = VerificationKeys::::get(circuit_id, resolved) - .ok_or(Error::::VerificationKeyNotFound)?; + frame_support::ensure!( + !RetiredVersions::::contains_key(circuit_id, resolved), + Error::::UnsupportedCircuitVersion + ); + + let vk_info = VerificationKeys::::get(circuit_id, resolved).ok_or(if explicit { + Error::::UnsupportedCircuitVersion + } else { + Error::::VerificationKeyNotFound + })?; let result = do_verify(vk_info.key_data.as_slice(), proof_bytes, raw_inputs); record_stats::(circuit_id, resolved, result); @@ -164,11 +173,11 @@ mod tests { } #[test] - fn explicit_version_without_vk_returns_not_found() { + fn explicit_version_without_vk_is_unsupported() { new_test_ext().execute_with(|| { assert_err!( verify::(CircuitId::TRANSFER, Some(99), &proof_bytes(), inputs()), - crate::Error::::VerificationKeyNotFound + crate::Error::::UnsupportedCircuitVersion ); }); } diff --git a/frame/zk-verifier/src/weights.rs b/frame/zk-verifier/src/weights.rs index 76a5792d..6d01451d 100644 --- a/frame/zk-verifier/src/weights.rs +++ b/frame/zk-verifier/src/weights.rs @@ -43,6 +43,8 @@ pub trait WeightInfo { fn register_verification_key() -> Weight; fn set_active_version() -> Weight; fn remove_verification_key() -> Weight; + fn retire_version() -> Weight; + fn unretire_version() -> Weight; fn batch_register_verification_keys(n: u32, ) -> Weight; } @@ -139,6 +141,18 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } + /// Placeholder until regenerated — retire toggles one flag (1 read + 1 write). + fn retire_version() -> Weight { + Weight::from_parts(21_470_000, 11704) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Placeholder until regenerated — unretire clears one flag (1 read + 1 write). + fn unretire_version() -> Weight { + Weight::from_parts(21_470_000, 11704) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } /// Storage: `ZkVerifier::VerificationKeys` (r:10 w:10) /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) /// Storage: `System::Number` (r:1 w:0) @@ -260,6 +274,18 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } + /// Placeholder until regenerated — retire toggles one flag (1 read + 1 write). + fn retire_version() -> Weight { + Weight::from_parts(21_470_000, 11704) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Placeholder until regenerated — unretire clears one flag (1 read + 1 write). + fn unretire_version() -> Weight { + Weight::from_parts(21_470_000, 11704) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } /// Storage: `ZkVerifier::VerificationKeys` (r:10 w:10) /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) /// Storage: `System::Number` (r:1 w:0) diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index 2dc939b7..95d8fc0a 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -197,10 +197,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: Cow::Borrowed("orbinum"), impl_name: Cow::Borrowed("orbinum"), authoring_version: 1, - spec_version: 3, + spec_version: 4, impl_version: 1, apis: RUNTIME_API_VERSIONS, - transaction_version: 1, + transaction_version: 2, system_version: 1, }; From c9996fbf86a95631c4adb4e3b93d2be85578caf6 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Thu, 9 Jul 2026 10:53:02 -0400 Subject: [PATCH 2/9] fix(zk-verifier): correct transfer public-input arity to 7 --- primitives/zk-verifier/CHANGELOG.md | 15 +++++++++++++++ primitives/zk-verifier/src/types.rs | 8 ++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/primitives/zk-verifier/CHANGELOG.md b/primitives/zk-verifier/CHANGELOG.md index 9626a74f..85b1177f 100644 --- a/primitives/zk-verifier/CHANGELOG.md +++ b/primitives/zk-verifier/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to this crate are documented here. --- +## [Unreleased] + +### Fixed + +- **`TRANSFER_PUBLIC_INPUTS` corrected from 5 to 7.** The constant counted the + transfer circuit's declared public-signal names, but `nullifiers[2]` and + `commitments[2]` are arrays, so the true arity is 7 (`merkle_root` + 2 + nullifiers + 2 commitments + `asset_id` + `fee`), matching every published + VK's `nPublic`. The value was dead until `expected_public_inputs` began + gating VK registration; the check then rejected every valid transfer VK. + Proof verification was unaffected — it reads arity from the deserialized VK, + never from this constant. + +--- + ## [1.2.0] - 2026-07-04 ### Added diff --git a/primitives/zk-verifier/src/types.rs b/primitives/zk-verifier/src/types.rs index 16d47098..fab3750a 100644 --- a/primitives/zk-verifier/src/types.rs +++ b/primitives/zk-verifier/src/types.rs @@ -24,8 +24,8 @@ pub const CIRCUIT_ID_VALUE_PROOF: u8 = 6; pub const CIRCUIT_ID_PRIVATE_LINK: u8 = 5; /// Number of public inputs for the transfer circuit. -/// Public inputs: [merkle_root, nullifier1, nullifier2, commitment1, commitment2] -pub const TRANSFER_PUBLIC_INPUTS: usize = 5; +/// Public inputs: [merkle_root, nullifier1, nullifier2, commitment1, commitment2, asset_id, fee] +pub const TRANSFER_PUBLIC_INPUTS: usize = 7; /// Number of public inputs for the unshield circuit. /// Public inputs: [merkle_root, nullifier, amount, recipient, asset_id, fee, change_commitment] pub const UNSHIELD_PUBLIC_INPUTS: usize = 7; @@ -310,7 +310,7 @@ mod tests { #[test] fn test_public_input_counts_are_expected() { - assert_eq!(TRANSFER_PUBLIC_INPUTS, 5); + assert_eq!(TRANSFER_PUBLIC_INPUTS, 7); assert_eq!(UNSHIELD_PUBLIC_INPUTS, 7); assert_eq!(VALUE_PROOF_PUBLIC_INPUTS, 4); assert_eq!(PRIVATE_LINK_PUBLIC_INPUTS, 2); @@ -318,7 +318,7 @@ mod tests { #[test] fn expected_public_inputs_maps_known_circuits() { - assert_eq!(expected_public_inputs(CIRCUIT_ID_TRANSFER), Some(5)); + assert_eq!(expected_public_inputs(CIRCUIT_ID_TRANSFER), Some(7)); assert_eq!(expected_public_inputs(CIRCUIT_ID_UNSHIELD), Some(7)); assert_eq!(expected_public_inputs(CIRCUIT_ID_PRIVATE_LINK), Some(2)); assert_eq!(expected_public_inputs(CIRCUIT_ID_VALUE_PROOF), Some(4)); From 1d616cdd6885729affc82802421cd46d689c58c9 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Thu, 9 Jul 2026 16:00:10 +0000 Subject: [PATCH 3/9] chore(benchmarks): regenerate pallet weights from benchmark VPS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Real measured weights for account-mapping, relayer, shielded-pool and zk-verifier. zk-verifier gains retire_version / unretire_version (retire ~20.96ms, unretire ~15.41ms — unretire only clears a flag); shielded-pool covers the circuit_version-carrying private_transfer / unshield / claim_shielded_fees. --- frame/account-mapping/src/weights.rs | 130 ++++++++--------- frame/relayer/src/weights.rs | 42 +++--- frame/shielded-pool/src/weights.rs | 82 +++++------ frame/zk-verifier/src/weights.rs | 210 ++++++++++++++++++--------- 4 files changed, 268 insertions(+), 196 deletions(-) diff --git a/frame/account-mapping/src/weights.rs b/frame/account-mapping/src/weights.rs index d9225b7b..e448c2c5 100644 --- a/frame/account-mapping/src/weights.rs +++ b/frame/account-mapping/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for pallet_account_mapping //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 53.0.0 -//! DATE: 2026-07-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-07-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `ubuntu-32gb-fsn1-1`, CPU: `AMD EPYC-Genoa Processor` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -72,8 +72,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `19` // Estimated: `1504` - // Minimum execution time: 8_450_000 picoseconds. - Weight::from_parts(8_900_000, 1504) + // Minimum execution time: 7_860_000 picoseconds. + Weight::from_parts(8_241_000, 1504) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -93,8 +93,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `189` // Estimated: `3533` - // Minimum execution time: 16_630_000 picoseconds. - Weight::from_parts(17_570_000, 3533) + // Minimum execution time: 15_642_000 picoseconds. + Weight::from_parts(16_022_000, 3533) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -114,8 +114,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `61` // Estimated: `5728` - // Minimum execution time: 37_300_000 picoseconds. - Weight::from_parts(38_810_000, 5728) + // Minimum execution time: 34_708_000 picoseconds. + Weight::from_parts(35_921_000, 5728) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -139,8 +139,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `257` // Estimated: `5728` - // Minimum execution time: 41_600_000 picoseconds. - Weight::from_parts(42_370_000, 5728) + // Minimum execution time: 38_843_000 picoseconds. + Weight::from_parts(39_824_000, 5728) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -164,8 +164,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `360` // Estimated: `6102` - // Minimum execution time: 67_349_000 picoseconds. - Weight::from_parts(68_879_000, 6102) + // Minimum execution time: 63_378_000 picoseconds. + Weight::from_parts(65_651_000, 6102) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -185,8 +185,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `186` // Estimated: `4172` - // Minimum execution time: 20_320_000 picoseconds. - Weight::from_parts(20_940_000, 4172) + // Minimum execution time: 18_706_000 picoseconds. + Weight::from_parts(19_388_000, 4172) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -206,8 +206,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `256` // Estimated: `4172` - // Minimum execution time: 21_720_000 picoseconds. - Weight::from_parts(22_510_000, 4172) + // Minimum execution time: 19_767_000 picoseconds. + Weight::from_parts(20_389_000, 4172) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -233,8 +233,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `546` // Estimated: `5728` - // Minimum execution time: 119_829_000 picoseconds. - Weight::from_parts(123_109_000, 5728) + // Minimum execution time: 112_476_000 picoseconds. + Weight::from_parts(114_447_000, 5728) .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -258,8 +258,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `299` // Estimated: `5728` - // Minimum execution time: 79_380_000 picoseconds. - Weight::from_parts(82_719_000, 5728) + // Minimum execution time: 65_270_000 picoseconds. + Weight::from_parts(66_702_000, 5728) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -281,8 +281,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `286` // Estimated: `5728` - // Minimum execution time: 24_779_000 picoseconds. - Weight::from_parts(25_740_000, 5728) + // Minimum execution time: 22_371_000 picoseconds. + Weight::from_parts(23_353_000, 5728) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -302,8 +302,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `186` // Estimated: `3546` - // Minimum execution time: 18_130_000 picoseconds. - Weight::from_parts(18_920_000, 3546) + // Minimum execution time: 16_694_000 picoseconds. + Weight::from_parts(17_394_000, 3546) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -323,8 +323,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248` // Estimated: `3647` - // Minimum execution time: 71_810_000 picoseconds. - Weight::from_parts(73_830_000, 3647) + // Minimum execution time: 59_002_000 picoseconds. + Weight::from_parts(62_737_000, 3647) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -344,8 +344,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `237` // Estimated: `4091` - // Minimum execution time: 22_110_000 picoseconds. - Weight::from_parts(23_971_000, 4091) + // Minimum execution time: 20_379_000 picoseconds. + Weight::from_parts(20_639_000, 4091) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -365,8 +365,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `281` // Estimated: `4091` - // Minimum execution time: 23_269_000 picoseconds. - Weight::from_parts(24_750_000, 4091) + // Minimum execution time: 21_210_000 picoseconds. + Weight::from_parts(22_140_000, 4091) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -392,8 +392,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `393` // Estimated: `5728` - // Minimum execution time: 158_099_000 picoseconds. - Weight::from_parts(165_799_000, 5728) + // Minimum execution time: 129_108_000 picoseconds. + Weight::from_parts(135_598_000, 5728) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -413,8 +413,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `281` // Estimated: `4091` - // Minimum execution time: 24_210_000 picoseconds. - Weight::from_parts(25_259_000, 4091) + // Minimum execution time: 21_930_000 picoseconds. + Weight::from_parts(22_662_000, 4091) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -434,8 +434,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `19` // Estimated: `1504` - // Minimum execution time: 8_450_000 picoseconds. - Weight::from_parts(8_900_000, 1504) + // Minimum execution time: 7_860_000 picoseconds. + Weight::from_parts(8_241_000, 1504) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -455,8 +455,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `189` // Estimated: `3533` - // Minimum execution time: 16_630_000 picoseconds. - Weight::from_parts(17_570_000, 3533) + // Minimum execution time: 15_642_000 picoseconds. + Weight::from_parts(16_022_000, 3533) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -476,8 +476,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `61` // Estimated: `5728` - // Minimum execution time: 37_300_000 picoseconds. - Weight::from_parts(38_810_000, 5728) + // Minimum execution time: 34_708_000 picoseconds. + Weight::from_parts(35_921_000, 5728) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -501,8 +501,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `257` // Estimated: `5728` - // Minimum execution time: 41_600_000 picoseconds. - Weight::from_parts(42_370_000, 5728) + // Minimum execution time: 38_843_000 picoseconds. + Weight::from_parts(39_824_000, 5728) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -526,8 +526,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `360` // Estimated: `6102` - // Minimum execution time: 67_349_000 picoseconds. - Weight::from_parts(68_879_000, 6102) + // Minimum execution time: 63_378_000 picoseconds. + Weight::from_parts(65_651_000, 6102) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -547,8 +547,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `186` // Estimated: `4172` - // Minimum execution time: 20_320_000 picoseconds. - Weight::from_parts(20_940_000, 4172) + // Minimum execution time: 18_706_000 picoseconds. + Weight::from_parts(19_388_000, 4172) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -568,8 +568,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `256` // Estimated: `4172` - // Minimum execution time: 21_720_000 picoseconds. - Weight::from_parts(22_510_000, 4172) + // Minimum execution time: 19_767_000 picoseconds. + Weight::from_parts(20_389_000, 4172) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -595,8 +595,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `546` // Estimated: `5728` - // Minimum execution time: 119_829_000 picoseconds. - Weight::from_parts(123_109_000, 5728) + // Minimum execution time: 112_476_000 picoseconds. + Weight::from_parts(114_447_000, 5728) .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -620,8 +620,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `299` // Estimated: `5728` - // Minimum execution time: 79_380_000 picoseconds. - Weight::from_parts(82_719_000, 5728) + // Minimum execution time: 65_270_000 picoseconds. + Weight::from_parts(66_702_000, 5728) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -643,8 +643,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `286` // Estimated: `5728` - // Minimum execution time: 24_779_000 picoseconds. - Weight::from_parts(25_740_000, 5728) + // Minimum execution time: 22_371_000 picoseconds. + Weight::from_parts(23_353_000, 5728) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -664,8 +664,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `186` // Estimated: `3546` - // Minimum execution time: 18_130_000 picoseconds. - Weight::from_parts(18_920_000, 3546) + // Minimum execution time: 16_694_000 picoseconds. + Weight::from_parts(17_394_000, 3546) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -685,8 +685,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248` // Estimated: `3647` - // Minimum execution time: 71_810_000 picoseconds. - Weight::from_parts(73_830_000, 3647) + // Minimum execution time: 59_002_000 picoseconds. + Weight::from_parts(62_737_000, 3647) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -706,8 +706,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `237` // Estimated: `4091` - // Minimum execution time: 22_110_000 picoseconds. - Weight::from_parts(23_971_000, 4091) + // Minimum execution time: 20_379_000 picoseconds. + Weight::from_parts(20_639_000, 4091) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -727,8 +727,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `281` // Estimated: `4091` - // Minimum execution time: 23_269_000 picoseconds. - Weight::from_parts(24_750_000, 4091) + // Minimum execution time: 21_210_000 picoseconds. + Weight::from_parts(22_140_000, 4091) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -754,8 +754,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `393` // Estimated: `5728` - // Minimum execution time: 158_099_000 picoseconds. - Weight::from_parts(165_799_000, 5728) + // Minimum execution time: 129_108_000 picoseconds. + Weight::from_parts(135_598_000, 5728) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -775,8 +775,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `281` // Estimated: `4091` - // Minimum execution time: 24_210_000 picoseconds. - Weight::from_parts(25_259_000, 4091) + // Minimum execution time: 21_930_000 picoseconds. + Weight::from_parts(22_662_000, 4091) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } diff --git a/frame/relayer/src/weights.rs b/frame/relayer/src/weights.rs index 9ce7b980..03ac4d8b 100644 --- a/frame/relayer/src/weights.rs +++ b/frame/relayer/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for pallet_relayer //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 53.0.0 -//! DATE: 2026-07-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-07-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `ubuntu-32gb-fsn1-1`, CPU: `AMD EPYC-Genoa Processor` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -62,8 +62,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `19` // Estimated: `1504` - // Minimum execution time: 8_070_000 picoseconds. - Weight::from_parts(8_500_000, 1504) + // Minimum execution time: 7_499_000 picoseconds. + Weight::from_parts(7_800_000, 1504) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -78,14 +78,12 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Relayer::AllowedSelectors` (r:0 w:1) /// Proof: `Relayer::AllowedSelectors` (`max_values`: Some(1), `max_size`: Some(65), added: 560, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 16]`. - fn set_allowed_selectors(n: u32, ) -> Weight { + fn set_allowed_selectors(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `19` // Estimated: `1504` - // Minimum execution time: 8_260_000 picoseconds. - Weight::from_parts(9_079_107, 1504) - // Standard Error: 2_952 - .saturating_add(Weight::from_parts(13_029, 0).saturating_mul(n.into())) + // Minimum execution time: 7_421_000 picoseconds. + Weight::from_parts(8_145_006, 1504) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -105,8 +103,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `61` // Estimated: `3533` - // Minimum execution time: 18_179_000 picoseconds. - Weight::from_parts(18_800_000, 3533) + // Minimum execution time: 16_172_000 picoseconds. + Weight::from_parts(16_442_000, 3533) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -126,8 +124,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `189` // Estimated: `3533` - // Minimum execution time: 18_140_000 picoseconds. - Weight::from_parts(18_790_000, 3533) + // Minimum execution time: 16_343_000 picoseconds. + Weight::from_parts(16_783_000, 3533) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -149,8 +147,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `19` // Estimated: `1504` - // Minimum execution time: 8_070_000 picoseconds. - Weight::from_parts(8_500_000, 1504) + // Minimum execution time: 7_499_000 picoseconds. + Weight::from_parts(7_800_000, 1504) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -165,14 +163,12 @@ impl WeightInfo for () { /// Storage: `Relayer::AllowedSelectors` (r:0 w:1) /// Proof: `Relayer::AllowedSelectors` (`max_values`: Some(1), `max_size`: Some(65), added: 560, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 16]`. - fn set_allowed_selectors(n: u32, ) -> Weight { + fn set_allowed_selectors(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `19` // Estimated: `1504` - // Minimum execution time: 8_260_000 picoseconds. - Weight::from_parts(9_079_107, 1504) - // Standard Error: 2_952 - .saturating_add(Weight::from_parts(13_029, 0).saturating_mul(n.into())) + // Minimum execution time: 7_421_000 picoseconds. + Weight::from_parts(8_145_006, 1504) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -192,8 +188,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `61` // Estimated: `3533` - // Minimum execution time: 18_179_000 picoseconds. - Weight::from_parts(18_800_000, 3533) + // Minimum execution time: 16_172_000 picoseconds. + Weight::from_parts(16_442_000, 3533) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -213,8 +209,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `189` // Estimated: `3533` - // Minimum execution time: 18_140_000 picoseconds. - Weight::from_parts(18_790_000, 3533) + // Minimum execution time: 16_343_000 picoseconds. + Weight::from_parts(16_783_000, 3533) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } diff --git a/frame/shielded-pool/src/weights.rs b/frame/shielded-pool/src/weights.rs index 3a82a0b7..5c8e783e 100644 --- a/frame/shielded-pool/src/weights.rs +++ b/frame/shielded-pool/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for pallet_shielded_pool //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 53.0.0 -//! DATE: 2026-07-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-07-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `ubuntu-32gb-fsn1-1`, CPU: `AMD EPYC-Genoa Processor` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -90,8 +90,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1127` // Estimated: `4687` - // Minimum execution time: 892_444_000 picoseconds. - Weight::from_parts(908_654_000, 4687) + // Minimum execution time: 773_840_000 picoseconds. + Weight::from_parts(785_138_000, 4687) .saturating_add(T::DbWeight::get().reads(14_u64)) .saturating_add(T::DbWeight::get().writes(13_u64)) } @@ -134,10 +134,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1127` // Estimated: `4687 + n * (2701 ±0)` - // Minimum execution time: 888_364_000 picoseconds. - Weight::from_parts(99_550_075, 4687) - // Standard Error: 709_747 - .saturating_add(Weight::from_parts(869_647_941, 0).saturating_mul(n.into())) + // Minimum execution time: 761_244_000 picoseconds. + Weight::from_parts(776_987_000, 4687) + // Standard Error: 1_644_747 + .saturating_add(Weight::from_parts(701_980_415, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(9_u64)) @@ -187,10 +187,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1182` // Estimated: `4687 + n * (2701 ±0)` - // Minimum execution time: 845_115_000 picoseconds. - Weight::from_parts(83_928_940, 4687) - // Standard Error: 2_278_134 - .saturating_add(Weight::from_parts(798_570_579, 0).saturating_mul(n.into())) + // Minimum execution time: 702_703_000 picoseconds. + Weight::from_parts(733_295_000, 4687) + // Standard Error: 4_803_642 + .saturating_add(Weight::from_parts(43_395_069, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(15_u64)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(9_u64)) @@ -229,8 +229,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `903` // Estimated: `6196` - // Minimum execution time: 111_590_000 picoseconds. - Weight::from_parts(114_739_000, 6196) + // Minimum execution time: 105_837_000 picoseconds. + Weight::from_parts(123_090_000, 6196) .saturating_add(T::DbWeight::get().reads(15_u64)) .saturating_add(T::DbWeight::get().writes(8_u64)) } @@ -250,8 +250,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `239` // Estimated: `3631` - // Minimum execution time: 17_960_000 picoseconds. - Weight::from_parts(18_710_000, 3631) + // Minimum execution time: 16_512_000 picoseconds. + Weight::from_parts(17_735_000, 3631) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -269,8 +269,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `328` // Estimated: `3631` - // Minimum execution time: 17_370_000 picoseconds. - Weight::from_parts(18_150_000, 3631) + // Minimum execution time: 15_602_000 picoseconds. + Weight::from_parts(16_614_000, 3631) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -288,8 +288,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `328` // Estimated: `3631` - // Minimum execution time: 17_540_000 picoseconds. - Weight::from_parts(18_319_000, 3631) + // Minimum execution time: 16_192_000 picoseconds. + Weight::from_parts(17_184_000, 3631) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -327,8 +327,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1085` // Estimated: `4687` - // Minimum execution time: 833_605_000 picoseconds. - Weight::from_parts(852_403_000, 4687) + // Minimum execution time: 714_679_000 picoseconds. + Weight::from_parts(915_558_000, 4687) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(12_u64)) } @@ -374,8 +374,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1127` // Estimated: `4687` - // Minimum execution time: 892_444_000 picoseconds. - Weight::from_parts(908_654_000, 4687) + // Minimum execution time: 773_840_000 picoseconds. + Weight::from_parts(785_138_000, 4687) .saturating_add(RocksDbWeight::get().reads(14_u64)) .saturating_add(RocksDbWeight::get().writes(13_u64)) } @@ -418,10 +418,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1127` // Estimated: `4687 + n * (2701 ±0)` - // Minimum execution time: 888_364_000 picoseconds. - Weight::from_parts(99_550_075, 4687) - // Standard Error: 709_747 - .saturating_add(Weight::from_parts(869_647_941, 0).saturating_mul(n.into())) + // Minimum execution time: 761_244_000 picoseconds. + Weight::from_parts(776_987_000, 4687) + // Standard Error: 1_644_747 + .saturating_add(Weight::from_parts(701_980_415, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(9_u64)) @@ -471,10 +471,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1182` // Estimated: `4687 + n * (2701 ±0)` - // Minimum execution time: 845_115_000 picoseconds. - Weight::from_parts(83_928_940, 4687) - // Standard Error: 2_278_134 - .saturating_add(Weight::from_parts(798_570_579, 0).saturating_mul(n.into())) + // Minimum execution time: 702_703_000 picoseconds. + Weight::from_parts(733_295_000, 4687) + // Standard Error: 4_803_642 + .saturating_add(Weight::from_parts(43_395_069, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(15_u64)) .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(9_u64)) @@ -513,8 +513,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `903` // Estimated: `6196` - // Minimum execution time: 111_590_000 picoseconds. - Weight::from_parts(114_739_000, 6196) + // Minimum execution time: 105_837_000 picoseconds. + Weight::from_parts(123_090_000, 6196) .saturating_add(RocksDbWeight::get().reads(15_u64)) .saturating_add(RocksDbWeight::get().writes(8_u64)) } @@ -534,8 +534,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `239` // Estimated: `3631` - // Minimum execution time: 17_960_000 picoseconds. - Weight::from_parts(18_710_000, 3631) + // Minimum execution time: 16_512_000 picoseconds. + Weight::from_parts(17_735_000, 3631) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -553,8 +553,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `328` // Estimated: `3631` - // Minimum execution time: 17_370_000 picoseconds. - Weight::from_parts(18_150_000, 3631) + // Minimum execution time: 15_602_000 picoseconds. + Weight::from_parts(16_614_000, 3631) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -572,8 +572,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `328` // Estimated: `3631` - // Minimum execution time: 17_540_000 picoseconds. - Weight::from_parts(18_319_000, 3631) + // Minimum execution time: 16_192_000 picoseconds. + Weight::from_parts(17_184_000, 3631) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -611,8 +611,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1085` // Estimated: `4687` - // Minimum execution time: 833_605_000 picoseconds. - Weight::from_parts(852_403_000, 4687) + // Minimum execution time: 714_679_000 picoseconds. + Weight::from_parts(915_558_000, 4687) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(12_u64)) } diff --git a/frame/zk-verifier/src/weights.rs b/frame/zk-verifier/src/weights.rs index 6d01451d..676bdd55 100644 --- a/frame/zk-verifier/src/weights.rs +++ b/frame/zk-verifier/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for pallet_zk_verifier //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 53.0.0 -//! DATE: 2026-07-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-07-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `ubuntu-32gb-fsn1-1`, CPU: `AMD EPYC-Genoa Processor` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -53,6 +53,8 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { /// Storage: `ZkVerifier::ActiveCircuitVersion` (r:1 w:0) /// Proof: `ZkVerifier::ActiveCircuitVersion` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::RetiredVersions` (r:1 w:0) + /// Proof: `ZkVerifier::RetiredVersions` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `ZkVerifier::VerificationKeys` (r:1 w:0) /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) /// Storage: `ZkVerifier::VerificationStats` (r:1 w:1) @@ -70,15 +72,15 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `445 + n * (32 ±0)` // Estimated: `11704 + n * (32 ±0)` - // Minimum execution time: 11_597_520_000 picoseconds. - Weight::from_parts(11_846_175_078, 11704) - // Standard Error: 996_773 - .saturating_add(Weight::from_parts(25_332_234, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(7_u64)) + // Minimum execution time: 10_356_729_000 picoseconds. + Weight::from_parts(10_616_358_737, 11704) + // Standard Error: 3_826_694 + .saturating_add(Weight::from_parts(84_861_894, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) .saturating_add(Weight::from_parts(0, 32).saturating_mul(n.into())) } - /// Storage: `ZkVerifier::VerificationKeys` (r:1 w:1) + /// Storage: `ZkVerifier::VerificationKeys` (r:2 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`) @@ -90,14 +92,16 @@ impl WeightInfo for SubstrateWeight { /// 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::VkHashes` (r:0 w:1) + /// Proof: `ZkVerifier::VkHashes` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) fn register_verification_key() -> Weight { // Proof Size summary in bytes: - // Measured: `61` - // Estimated: `11704` - // Minimum execution time: 2_567_035_000 picoseconds. - Weight::from_parts(2_598_264_000, 11704) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + // Measured: `65` + // Estimated: `22418` + // Minimum execution time: 2_404_770_000 picoseconds. + Weight::from_parts(2_456_662_000, 22418) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `ZkVerifier::VerificationKeys` (r:1 w:0) /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) @@ -115,8 +119,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `231` // Estimated: `11704` - // Minimum execution time: 16_670_000 picoseconds. - Weight::from_parts(17_320_000, 11704) + // Minimum execution time: 14_791_000 picoseconds. + Weight::from_parts(15_462_000, 11704) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -132,28 +136,60 @@ impl WeightInfo for SubstrateWeight { /// 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::RetiredVersions` (r:0 w:1) + /// Proof: `ZkVerifier::RetiredVersions` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) fn remove_verification_key() -> Weight { // Proof Size summary in bytes: // Measured: `242` // Estimated: `11704` - // Minimum execution time: 20_820_000 picoseconds. - Weight::from_parts(21_470_000, 11704) + // Minimum execution time: 20_969_000 picoseconds. + Weight::from_parts(21_570_000, 11704) .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } - /// Placeholder until regenerated — retire toggles one flag (1 read + 1 write). + /// Storage: `ZkVerifier::VerificationKeys` (r:1 w:0) + /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::RetiredVersions` (r:1 w:1) + /// Proof: `ZkVerifier::RetiredVersions` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::ActiveCircuitVersion` (r:1 w:0) + /// Proof: `ZkVerifier::ActiveCircuitVersion` (`max_values`: None, `max_size`: Some(24), added: 2499, 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 retire_version() -> Weight { - Weight::from_parts(21_470_000, 11704) - .saturating_add(T::DbWeight::get().reads(6_u64)) + // Proof Size summary in bytes: + // Measured: `242` + // Estimated: `11704` + // Minimum execution time: 20_960_000 picoseconds. + Weight::from_parts(21_369_000, 11704) + .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } - /// Placeholder until regenerated — unretire clears one flag (1 read + 1 write). + /// Storage: `ZkVerifier::RetiredVersions` (r:1 w:1) + /// Proof: `ZkVerifier::RetiredVersions` (`max_values`: None, `max_size`: Some(40), added: 2515, 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 unretire_version() -> Weight { - Weight::from_parts(21_470_000, 11704) - .saturating_add(T::DbWeight::get().reads(6_u64)) + // Proof Size summary in bytes: + // Measured: `196` + // Estimated: `3505` + // Minimum execution time: 15_412_000 picoseconds. + Weight::from_parts(15_871_000, 3505) + .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } - /// Storage: `ZkVerifier::VerificationKeys` (r:10 w:10) + /// Storage: `ZkVerifier::VerificationKeys` (r:20 w:10) /// 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`) @@ -165,20 +201,22 @@ impl WeightInfo for SubstrateWeight { /// 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::VkHashes` (r:0 w:10) + /// Proof: `ZkVerifier::VkHashes` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// The range of component `n` is `[1, 10]`. fn batch_register_verification_keys(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `61` - // Estimated: `1546 + n * (10714 ±0)` - // Minimum execution time: 2_535_745_000 picoseconds. - Weight::from_parts(51_613_689, 1546) - // Standard Error: 1_581_789 - .saturating_add(Weight::from_parts(2_558_352_793, 0).saturating_mul(n.into())) + // Measured: `65` + // Estimated: `1550 + n * (21428 ±0)` + // Minimum execution time: 2_372_285_000 picoseconds. + Weight::from_parts(2_424_847_000, 1550) + // Standard Error: 9_236_844 + .saturating_add(Weight::from_parts(2_068_662_849, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 10714).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 21428).saturating_mul(n.into())) } } @@ -186,6 +224,8 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { /// Storage: `ZkVerifier::ActiveCircuitVersion` (r:1 w:0) /// Proof: `ZkVerifier::ActiveCircuitVersion` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::RetiredVersions` (r:1 w:0) + /// Proof: `ZkVerifier::RetiredVersions` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `ZkVerifier::VerificationKeys` (r:1 w:0) /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) /// Storage: `ZkVerifier::VerificationStats` (r:1 w:1) @@ -203,15 +243,15 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `445 + n * (32 ±0)` // Estimated: `11704 + n * (32 ±0)` - // Minimum execution time: 11_597_520_000 picoseconds. - Weight::from_parts(11_846_175_078, 11704) - // Standard Error: 996_773 - .saturating_add(Weight::from_parts(25_332_234, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(7_u64)) + // Minimum execution time: 10_356_729_000 picoseconds. + Weight::from_parts(10_616_358_737, 11704) + // Standard Error: 3_826_694 + .saturating_add(Weight::from_parts(84_861_894, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) .saturating_add(Weight::from_parts(0, 32).saturating_mul(n.into())) } - /// Storage: `ZkVerifier::VerificationKeys` (r:1 w:1) + /// Storage: `ZkVerifier::VerificationKeys` (r:2 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`) @@ -223,14 +263,16 @@ impl WeightInfo for () { /// 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::VkHashes` (r:0 w:1) + /// Proof: `ZkVerifier::VkHashes` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) fn register_verification_key() -> Weight { // Proof Size summary in bytes: - // Measured: `61` - // Estimated: `11704` - // Minimum execution time: 2_567_035_000 picoseconds. - Weight::from_parts(2_598_264_000, 11704) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + // Measured: `65` + // Estimated: `22418` + // Minimum execution time: 2_404_770_000 picoseconds. + Weight::from_parts(2_456_662_000, 22418) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `ZkVerifier::VerificationKeys` (r:1 w:0) /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) @@ -248,8 +290,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `231` // Estimated: `11704` - // Minimum execution time: 16_670_000 picoseconds. - Weight::from_parts(17_320_000, 11704) + // Minimum execution time: 14_791_000 picoseconds. + Weight::from_parts(15_462_000, 11704) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -265,28 +307,60 @@ impl WeightInfo for () { /// 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::RetiredVersions` (r:0 w:1) + /// Proof: `ZkVerifier::RetiredVersions` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) fn remove_verification_key() -> Weight { // Proof Size summary in bytes: // Measured: `242` // Estimated: `11704` - // Minimum execution time: 20_820_000 picoseconds. - Weight::from_parts(21_470_000, 11704) + // Minimum execution time: 20_969_000 picoseconds. + Weight::from_parts(21_570_000, 11704) .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } - /// Placeholder until regenerated — retire toggles one flag (1 read + 1 write). + /// Storage: `ZkVerifier::VerificationKeys` (r:1 w:0) + /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::RetiredVersions` (r:1 w:1) + /// Proof: `ZkVerifier::RetiredVersions` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::ActiveCircuitVersion` (r:1 w:0) + /// Proof: `ZkVerifier::ActiveCircuitVersion` (`max_values`: None, `max_size`: Some(24), added: 2499, 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 retire_version() -> Weight { - Weight::from_parts(21_470_000, 11704) - .saturating_add(RocksDbWeight::get().reads(6_u64)) + // Proof Size summary in bytes: + // Measured: `242` + // Estimated: `11704` + // Minimum execution time: 20_960_000 picoseconds. + Weight::from_parts(21_369_000, 11704) + .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } - /// Placeholder until regenerated — unretire clears one flag (1 read + 1 write). + /// Storage: `ZkVerifier::RetiredVersions` (r:1 w:1) + /// Proof: `ZkVerifier::RetiredVersions` (`max_values`: None, `max_size`: Some(40), added: 2515, 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 unretire_version() -> Weight { - Weight::from_parts(21_470_000, 11704) - .saturating_add(RocksDbWeight::get().reads(6_u64)) + // Proof Size summary in bytes: + // Measured: `196` + // Estimated: `3505` + // Minimum execution time: 15_412_000 picoseconds. + Weight::from_parts(15_871_000, 3505) + .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } - /// Storage: `ZkVerifier::VerificationKeys` (r:10 w:10) + /// Storage: `ZkVerifier::VerificationKeys` (r:20 w:10) /// 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`) @@ -298,19 +372,21 @@ impl WeightInfo for () { /// 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::VkHashes` (r:0 w:10) + /// Proof: `ZkVerifier::VkHashes` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// The range of component `n` is `[1, 10]`. fn batch_register_verification_keys(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `61` - // Estimated: `1546 + n * (10714 ±0)` - // Minimum execution time: 2_535_745_000 picoseconds. - Weight::from_parts(51_613_689, 1546) - // Standard Error: 1_581_789 - .saturating_add(Weight::from_parts(2_558_352_793, 0).saturating_mul(n.into())) + // Measured: `65` + // Estimated: `1550 + n * (21428 ±0)` + // Minimum execution time: 2_372_285_000 picoseconds. + Weight::from_parts(2_424_847_000, 1550) + // Standard Error: 9_236_844 + .saturating_add(Weight::from_parts(2_068_662_849, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) - .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) - .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 10714).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 21428).saturating_mul(n.into())) } } From 66d5aa0065973e93c08a490f4ec3e53bef8cd1da Mon Sep 17 00:00:00 2001 From: nol4lej Date: Thu, 9 Jul 2026 12:28:17 -0400 Subject: [PATCH 4/9] chore(release): bump crate versions and update changelogs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cut dated releases for the per-note circuit-version work: - orbinum-zk-verifier 1.2.0 → 1.3.0 (transfer arity fix) - pallet-zk-verifier 0.8.0 → 0.9.0 (version retirement, circuit-version verify, regenerated weights) - pallet-shielded-pool 0.8.3 → 0.9.0 (circuit_version params, regenerated weights) - pallet-evm-precompile-shielded-pool 0.2.0 → 0.3.0 (circuitVersion calldata) - pallet-account-mapping 0.3.1 → 0.3.2 (weights regenerated) - pallet-relayer 0.3.0 → 0.3.1 (weights regenerated) --- Cargo.lock | 12 ++++++------ frame/account-mapping/CHANGELOG.md | 7 +++++++ frame/account-mapping/Cargo.toml | 2 +- frame/evm/precompile/shielded-pool/CHANGELOG.md | 2 +- frame/evm/precompile/shielded-pool/Cargo.toml | 2 +- frame/relayer/CHANGELOG.md | 9 +++++++++ frame/relayer/Cargo.toml | 2 +- frame/shielded-pool/CHANGELOG.md | 5 ++++- frame/shielded-pool/Cargo.toml | 2 +- frame/zk-verifier/CHANGELOG.md | 7 ++++++- frame/zk-verifier/Cargo.toml | 2 +- primitives/zk-verifier/CHANGELOG.md | 2 +- primitives/zk-verifier/Cargo.toml | 2 +- 13 files changed, 40 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 461bab7a..2b3beedc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7374,7 +7374,7 @@ dependencies = [ [[package]] name = "orbinum-zk-verifier" -version = "1.2.0" +version = "1.3.0" dependencies = [ "ark-bn254", "ark-ec 0.5.0", @@ -7426,7 +7426,7 @@ dependencies = [ [[package]] name = "pallet-account-mapping" -version = "0.3.1" +version = "0.3.2" dependencies = [ "frame-benchmarking", "frame-support", @@ -7934,7 +7934,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-shielded-pool" -version = "0.2.0" +version = "0.3.0" dependencies = [ "fp-evm", "frame-support", @@ -8080,7 +8080,7 @@ dependencies = [ [[package]] name = "pallet-relayer" -version = "0.3.0" +version = "0.3.1" dependencies = [ "frame-benchmarking", "frame-support", @@ -8143,7 +8143,7 @@ dependencies = [ [[package]] name = "pallet-shielded-pool" -version = "0.8.3" +version = "0.9.0" dependencies = [ "ark-bn254", "ark-ff 0.5.0", @@ -8368,7 +8368,7 @@ dependencies = [ [[package]] name = "pallet-zk-verifier" -version = "0.8.0" +version = "0.9.0" dependencies = [ "ark-bn254", "ark-ec 0.5.0", diff --git a/frame/account-mapping/CHANGELOG.md b/frame/account-mapping/CHANGELOG.md index 63510516..2058bb32 100644 --- a/frame/account-mapping/CHANGELOG.md +++ b/frame/account-mapping/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to `pallet-account-mapping` will be documented in this file. +## [0.3.2] - 2026-07-09 + +### Changed + +- Weights regenerated on the benchmark host (AMD EPYC-Genoa, 32 GB). No logic + change — refreshed alongside the per-note circuit-version release. + ## [0.3.1] - 2026-06-01 ### Changed diff --git a/frame/account-mapping/Cargo.toml b/frame/account-mapping/Cargo.toml index 98af57d9..828c2401 100644 --- a/frame/account-mapping/Cargo.toml +++ b/frame/account-mapping/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-account-mapping" -version = "0.3.1" +version = "0.3.2" license = "Apache-2.0" description = "Stateful mapping between Substrate AccountId32 and EVM H160 addresses, with alias registration and multichain identity support." authors = { workspace = true } diff --git a/frame/evm/precompile/shielded-pool/CHANGELOG.md b/frame/evm/precompile/shielded-pool/CHANGELOG.md index 1d2bc849..825979ab 100644 --- a/frame/evm/precompile/shielded-pool/CHANGELOG.md +++ b/frame/evm/precompile/shielded-pool/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to `pallet-evm-precompile-shielded-pool` will be documented in this file. -## [Unreleased] +## [0.3.0] - 2026-07-09 ### Changed - **ABI: added a trailing `uint32 circuitVersion` to `privateTransfer`, `unshield` diff --git a/frame/evm/precompile/shielded-pool/Cargo.toml b/frame/evm/precompile/shielded-pool/Cargo.toml index b57559dc..4854f10d 100644 --- a/frame/evm/precompile/shielded-pool/Cargo.toml +++ b/frame/evm/precompile/shielded-pool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-evm-precompile-shielded-pool" -version = "0.2.0" +version = "0.3.0" authors = { workspace = true } edition = "2021" description = "EVM Precompile for Orbinum Shielded Pool Pallet." diff --git a/frame/relayer/CHANGELOG.md b/frame/relayer/CHANGELOG.md index 3ef62f1a..e93a77fa 100644 --- a/frame/relayer/CHANGELOG.md +++ b/frame/relayer/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to `pallet-relayer` will be documented in this file. +## [0.3.1] - 2026-07-09 + +### Changed + +- Weights regenerated on the benchmark host (AMD EPYC-Genoa, 32 GB). No logic + change — refreshed alongside the per-note circuit-version release. + `set_allowed_selectors` no longer scales with its component (`n` → `_n`); the + measured cost did not vary with the selector count. + ## [0.3.0] - 2026-06-03 ### Changed diff --git a/frame/relayer/Cargo.toml b/frame/relayer/Cargo.toml index 00760450..bf85cb8b 100644 --- a/frame/relayer/Cargo.toml +++ b/frame/relayer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-relayer" -version = "0.3.0" +version = "0.3.1" description = "On-chain relay configuration, relayer registry and fee accounting." authors = { workspace = true } license = "GPL-3.0-or-later" diff --git a/frame/shielded-pool/CHANGELOG.md b/frame/shielded-pool/CHANGELOG.md index 1918c5ea..89e5810a 100644 --- a/frame/shielded-pool/CHANGELOG.md +++ b/frame/shielded-pool/CHANGELOG.md @@ -2,10 +2,13 @@ All notable changes to `pallet-shielded-pool` will be documented in this file. -## [Unreleased] +## [0.9.0] - 2026-07-09 ### Changed +- **Weights regenerated** on the benchmark host for the `circuit_version`-carrying + `private_transfer` / `unshield` / `claim_shielded_fees` and the rest of the pallet. + - **`private_transfer`, `unshield` and `claim_shielded_fees` now take a required `circuit_version: u32`** (last param). The proof is verified against that circuit version's VK instead of always the active version, so a note created diff --git a/frame/shielded-pool/Cargo.toml b/frame/shielded-pool/Cargo.toml index e2041d9a..47d2261a 100644 --- a/frame/shielded-pool/Cargo.toml +++ b/frame/shielded-pool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-shielded-pool" -version = "0.8.3" +version = "0.9.0" description = "Shielded pool pallet for private transactions using ZK proofs" authors = ["Orbinum Team"] license = "GPL-3.0-or-later" diff --git a/frame/zk-verifier/CHANGELOG.md b/frame/zk-verifier/CHANGELOG.md index 5f0642aa..e1c4e0c5 100644 --- a/frame/zk-verifier/CHANGELOG.md +++ b/frame/zk-verifier/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this pallet are documented here. --- -## [Unreleased] +## [0.9.0] - 2026-07-09 ### Added @@ -42,6 +42,11 @@ All notable changes to this pallet are documented here. does not bind the version (shielded-pool Limitation 1), so this is a governance- enforced rule that `ensure_vk_arity` (arity only) cannot check. +### Changed + +- **Weights regenerated** on the benchmark host, including real measured weights + for `retire_version` (~20.96ms) and `unretire_version` (~15.41ms). + ### Fixed - **VK registration no longer rejects valid transfer keys.** `ensure_vk_arity` diff --git a/frame/zk-verifier/Cargo.toml b/frame/zk-verifier/Cargo.toml index 71880fa9..fb9432ad 100644 --- a/frame/zk-verifier/Cargo.toml +++ b/frame/zk-verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-zk-verifier" -version = "0.8.0" +version = "0.9.0" description = "Zero-Knowledge proof verification pallet for Orbinum" authors = ["Orbinum Team"] license = "GPL-3.0-or-later" diff --git a/primitives/zk-verifier/CHANGELOG.md b/primitives/zk-verifier/CHANGELOG.md index 85b1177f..ec13154c 100644 --- a/primitives/zk-verifier/CHANGELOG.md +++ b/primitives/zk-verifier/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this crate are documented here. --- -## [Unreleased] +## [1.3.0] - 2026-07-09 ### Fixed diff --git a/primitives/zk-verifier/Cargo.toml b/primitives/zk-verifier/Cargo.toml index 9689da98..26af8ad8 100644 --- a/primitives/zk-verifier/Cargo.toml +++ b/primitives/zk-verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "orbinum-zk-verifier" -version = "1.2.0" +version = "1.3.0" authors = ["Orbinum Network "] edition = "2021" publish = false From eb12cf2fd542b65621be60d0898de48ce880e2a3 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Thu, 9 Jul 2026 15:59:24 -0400 Subject: [PATCH 5/9] chore(runtime): bump spec_version to 5, transaction_version to 3 --- template/runtime/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index 95d8fc0a..8d171e78 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -197,10 +197,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: Cow::Borrowed("orbinum"), impl_name: Cow::Borrowed("orbinum"), authoring_version: 1, - spec_version: 4, + spec_version: 5, impl_version: 1, apis: RUNTIME_API_VERSIONS, - transaction_version: 2, + transaction_version: 3, system_version: 1, }; From 827f181cdbd7a1eea008fb94e8fcae33abd821b3 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Thu, 9 Jul 2026 16:01:08 -0400 Subject: [PATCH 6/9] chore(vk): fix stale circuit-id references in README and rotate-dev.sh --- scripts/vk/README.md | 2 +- scripts/vk/workflows/rotate-dev.sh | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/vk/README.md b/scripts/vk/README.md index 0871ec86..13b3f19a 100644 --- a/scripts/vk/README.md +++ b/scripts/vk/README.md @@ -10,7 +10,7 @@ This folder centralizes all Verification Key (VK) operations for development and - `set-active` - `remove` - `workflows/setup-dev.sh` - - DEV bootstrap for fixed circuits `1,2,4,5`. + - DEV bootstrap for fixed circuits `1,2,6,5`. - `workflows/rotate-dev.sh` - Version rotation `old -> new` with RPC validation and optional `--remove-old`. - `policy/verify-window-dev.sh` diff --git a/scripts/vk/workflows/rotate-dev.sh b/scripts/vk/workflows/rotate-dev.sh index e5c93c76..991a57a0 100755 --- a/scripts/vk/workflows/rotate-dev.sh +++ b/scripts/vk/workflows/rotate-dev.sh @@ -6,10 +6,10 @@ set -euo pipefail # DEV on-chain VK rotation for the 4 supported circuits. # # Flow: -# 1) register NEW_VERSION on 1,2,4,5 -# 2) set-active NEW_VERSION on 1,2,4,5 +# 1) register NEW_VERSION on 1,2,6,5 +# 2) set-active NEW_VERSION on 1,2,6,5 # 3) RPC validation per circuit -# 4) optional: remove OLD_VERSION on 1,2,4,5 (if --remove-old) +# 4) optional: remove OLD_VERSION on 1,2,6,5 (if --remove-old) # # USAGE: # bash scripts/vk/workflows/rotate-dev.sh [rpc_ws] [sudo_seed] [old_version] From 1edff4451204358046de07ff9e940a74fdb434d1 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Thu, 9 Jul 2026 16:25:34 -0400 Subject: [PATCH 7/9] chore(deps): patch RUSTSEC-2026-0204 and a yanked crate --- Cargo.lock | 44 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b3beedc..9281155c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1077,20 +1077,41 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2d6094e2a1ba3c93b5a596fe5a10d1a10c3c6e06785cde89f693a044c01aa40" +dependencies = [ + "bitcoin-internals", +] + +[[package]] +name = "bitcoin-internals" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a30a22d1f112dde8e16be7b45c63645dc165cef254f835b3e1e9553e485cfa64" +dependencies = [ + "hex-conservative 0.3.2", +] + [[package]] name = "bitcoin-io" -version = "0.1.100" +version = "0.1.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11301df0b06f22dea7bb1916403fdd88a371031e495c49b8f96931b28189e175" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] [[package]] name = "bitcoin_hashes" -version = "0.14.100" +version = "0.14.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9901a56e133a1fc86eeb1113e2591f45f4682451ca893bff494d2f88918e3f" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" dependencies = [ "bitcoin-io", - "hex-conservative", + "hex-conservative 0.2.2", ] [[package]] @@ -1973,9 +1994,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] @@ -4619,6 +4640,15 @@ dependencies = [ "arrayvec 0.7.6", ] +[[package]] +name = "hex-conservative" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830e599c2904b08f0834ee6337d8fe8f0ed4a63b5d9e7a7f49c0ffa06d08d360" +dependencies = [ + "arrayvec 0.7.6", +] + [[package]] name = "hex-literal" version = "0.4.1" From 1ee1b47fdc3a1d431a81791c55054c641400a46a Mon Sep 17 00:00:00 2001 From: nol4lej Date: Thu, 9 Jul 2026 17:23:32 -0400 Subject: [PATCH 8/9] fix(precompile): silence too_many_arguments on private_transfer test helper --- frame/evm/precompile/shielded-pool/src/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/evm/precompile/shielded-pool/src/tests.rs b/frame/evm/precompile/shielded-pool/src/tests.rs index 2fc4b18e..871f7586 100644 --- a/frame/evm/precompile/shielded-pool/src/tests.rs +++ b/frame/evm/precompile/shielded-pool/src/tests.rs @@ -105,6 +105,7 @@ fn encode_shield(asset_id: u32, commitment: [u8; 32], memo: &[u8]) -> Vec { } /// `privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256)` selector `0x8c0f5d24` +#[allow(clippy::too_many_arguments)] fn encode_private_transfer( proof: &[u8], merkle_root: [u8; 32], From 04995c28c4f72b0c21b4af26a4b879fa80eee419 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Thu, 9 Jul 2026 18:25:26 -0400 Subject: [PATCH 9/9] fix(zk-verifier): update wrong-arity test after transfer 5->7 fix --- frame/zk-verifier/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/zk-verifier/src/lib.rs b/frame/zk-verifier/src/lib.rs index 9521c114..1ebedd01 100644 --- a/frame/zk-verifier/src/lib.rs +++ b/frame/zk-verifier/src/lib.rs @@ -702,13 +702,13 @@ mod tests { #[test] fn register_vk_rejects_wrong_arity() { new_test_ext().execute_with(|| { - // TRANSFER expects 5 public inputs; a VK built for 7 must be rejected. + // TRANSFER expects TRANSFER_PUBLIC_INPUTS; a VK of a different arity is rejected. assert_noop!( ZkVerifier::register_verification_key( root().into(), CircuitId::TRANSFER, 1, - real_vk(UNSHIELD_PUBLIC_INPUTS) + real_vk(TRANSFER_PUBLIC_INPUTS + 1) ), Error::::InvalidVerificationKey );