From 930620af713bffb37e4dbe39aa5367d4359e0406 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 13:26:50 -0400 Subject: [PATCH 01/18] feat: update documentation for poseidon_hash_1 to reflect value_proof circuit usage --- primitives/zk-core/CHANGELOG.md | 9 +++++++++ primitives/zk-core/Cargo.toml | 2 +- primitives/zk-core/README.md | 8 ++++---- primitives/zk-core/src/hash.rs | 6 +++--- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/primitives/zk-core/CHANGELOG.md b/primitives/zk-core/CHANGELOG.md index aa86a49f..a5dace79 100644 --- a/primitives/zk-core/CHANGELOG.md +++ b/primitives/zk-core/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this crate are documented here. --- +## [1.0.1] — 2026-05-14 + +### Changed +- `poseidon_hash_1` doc comment updated: use case renamed from *disclosure circuit / viewing key* + to *value_proof circuit / owner hash* (`owner_hash = Poseidon(owner_pubkey)`). + README example variable renamed from `viewing_key` to `owner_hash` accordingly. + +--- + ## [1.0.0] — 2026-04-14 - `PoseidonHasher` trait with `hash_2`, `hash_4`, `hash_5` arities diff --git a/primitives/zk-core/Cargo.toml b/primitives/zk-core/Cargo.toml index 6c5689ce..2f83208c 100644 --- a/primitives/zk-core/Cargo.toml +++ b/primitives/zk-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "orbinum-zk-core" -version = "1.0.0" +version = "1.0.1" authors = ["Orbinum Network "] edition = "2021" license = "Apache-2.0 OR GPL-3.0-or-later" diff --git a/primitives/zk-core/README.md b/primitives/zk-core/README.md index 4acc2a98..0e891227 100644 --- a/primitives/zk-core/README.md +++ b/primitives/zk-core/README.md @@ -61,13 +61,13 @@ let commitment = note.commitment(&hasher); let nullifier = note.nullifier(&hasher, spending_key); ``` -### Single-element hash (disclosure circuit) +### Single-element hash (value_proof circuit) ```rust use orbinum_zk_core::{poseidon_hash_1, FieldElement}; -// viewing_key = Poseidon(owner_pubkey) -let viewing_key = poseidon_hash_1(FieldElement::from_u64(owner_pk)); +// owner_hash = Poseidon(owner_pubkey) — public signal of the value_proof circuit +let owner_hash = poseidon_hash_1(FieldElement::from_u64(owner_pk)); ``` ### Custom hasher (testing) @@ -90,7 +90,7 @@ commitment = Poseidon(value, asset_id, owner_pubkey, blinding) // has nullifier = Poseidon(commitment, spending_key) // hash_2 merkle_node = Poseidon(left, right) // hash_2 eddsa_h = Poseidon(R8x, R8y, Ax, Ay, msg) // hash_5 -viewing_key = Poseidon(owner_pubkey) // hash_1 (disclosure) +owner_hash = Poseidon(owner_pubkey) // hash_1 (value_proof) ``` All hashes use circomlib-compatible Poseidon (BN254, iden3 parameters). diff --git a/primitives/zk-core/src/hash.rs b/primitives/zk-core/src/hash.rs index 88b4d944..c0b15b8e 100644 --- a/primitives/zk-core/src/hash.rs +++ b/primitives/zk-core/src/hash.rs @@ -125,11 +125,11 @@ fn bytes_to_field(bytes: &[u8]) -> FieldElement { /// Poseidon hash of a single field element. /// -/// Used in the disclosure circuit to derive a viewing key: -/// `viewing_key = Poseidon(owner_pubkey)`. +/// Used in the `value_proof` circuit to derive the owner key hash: +/// `owner_hash = Poseidon(owner_pubkey)`. /// /// Single-input Poseidon is intentionally not part of [`PoseidonHasher`] because -/// it is only needed for the disclosure circuit. +/// it is only needed for the value_proof circuit. pub fn poseidon_hash_1(input: FieldElement) -> FieldElement { let result = Poseidon::::new_circom(1) .expect("Poseidon init (1 input) failed") From 90adc1e1aba90859be9bec65ec65942cb5259db3 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 13:28:46 -0400 Subject: [PATCH 02/18] feat: update circuit identifiers and public inputs for value proof operations --- primitives/zk-verifier/CHANGELOG.md | 14 ++++++++++++++ primitives/zk-verifier/Cargo.toml | 2 +- primitives/zk-verifier/README.md | 2 +- primitives/zk-verifier/src/lib.rs | 6 +++--- primitives/zk-verifier/src/types.rs | 14 +++++++------- primitives/zk-verifier/src/verifier.rs | 2 +- 6 files changed, 27 insertions(+), 13 deletions(-) diff --git a/primitives/zk-verifier/CHANGELOG.md b/primitives/zk-verifier/CHANGELOG.md index 2fd5447f..3d87546c 100644 --- a/primitives/zk-verifier/CHANGELOG.md +++ b/primitives/zk-verifier/CHANGELOG.md @@ -4,6 +4,20 @@ All notable changes to this crate are documented here. --- +## [1.1.0] — 2026-05-14 + +### Changed + +- Renamed `CIRCUIT_ID_DISCLOSURE` (4) to `CIRCUIT_ID_VALUE_PROOF` (6) — aligns with `CircuitId::VALUE_PROOF` in `pallet-zk-verifier` +- Renamed `DISCLOSURE_PUBLIC_INPUTS` (8) to `VALUE_PROOF_PUBLIC_INPUTS` (4) — reflects the 4 public signals of the value proof circuit (commitment, value, asset_id, owner_hash) + +### Removed + +- `CIRCUIT_ID_DISCLOSURE` — use `CIRCUIT_ID_VALUE_PROOF` instead +- `DISCLOSURE_PUBLIC_INPUTS` — use `VALUE_PROOF_PUBLIC_INPUTS` instead + +--- + ## [1.0.0] — 2026-04-14 - `Groth16Verifier` — static `verify`, `verify_with_prepared_vk`, `batch_verify` diff --git a/primitives/zk-verifier/Cargo.toml b/primitives/zk-verifier/Cargo.toml index 17412c45..fcbed683 100644 --- a/primitives/zk-verifier/Cargo.toml +++ b/primitives/zk-verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "orbinum-zk-verifier" -version = "1.0.0" +version = "1.1.0" authors = ["Orbinum Network "] edition = "2021" publish = false diff --git a/primitives/zk-verifier/README.md b/primitives/zk-verifier/README.md index a1353b29..4a40f29c 100644 --- a/primitives/zk-verifier/README.md +++ b/primitives/zk-verifier/README.md @@ -71,7 +71,7 @@ let inputs = parse_public_inputs_from_snarkjs(&["12345", "67890"])?; |---|---|---| | 1 | `transfer` | 7 | | 2 | `unshield` | 6 | -| 4 | `disclosure` | 4 | +| 6 | `value_proof` | 4 | | 5 | `private_link` | 2 | ## Weight estimation diff --git a/primitives/zk-verifier/src/lib.rs b/primitives/zk-verifier/src/lib.rs index ec723c80..673cf442 100644 --- a/primitives/zk-verifier/src/lib.rs +++ b/primitives/zk-verifier/src/lib.rs @@ -32,8 +32,8 @@ pub use snarkjs::SnarkjsProofPoints; pub use snarkjs::{parse_proof_from_snarkjs, parse_public_inputs_from_snarkjs}; pub use types::{ Proof, PublicInputs, VerifierError, VerifyingKey, BASE_VERIFICATION_COST, - CIRCUIT_ID_DISCLOSURE, CIRCUIT_ID_PRIVATE_LINK, CIRCUIT_ID_TRANSFER, CIRCUIT_ID_UNSHIELD, - DISCLOSURE_PUBLIC_INPUTS, MAX_PUBLIC_INPUTS, PER_INPUT_COST, PRIVATE_LINK_PUBLIC_INPUTS, - TRANSFER_PUBLIC_INPUTS, UNSHIELD_PUBLIC_INPUTS, + CIRCUIT_ID_PRIVATE_LINK, CIRCUIT_ID_TRANSFER, CIRCUIT_ID_UNSHIELD, CIRCUIT_ID_VALUE_PROOF, + MAX_PUBLIC_INPUTS, PER_INPUT_COST, PRIVATE_LINK_PUBLIC_INPUTS, TRANSFER_PUBLIC_INPUTS, + UNSHIELD_PUBLIC_INPUTS, VALUE_PROOF_PUBLIC_INPUTS, }; pub use verifier::Groth16Verifier; diff --git a/primitives/zk-verifier/src/types.rs b/primitives/zk-verifier/src/types.rs index ca11246b..cab7885f 100644 --- a/primitives/zk-verifier/src/types.rs +++ b/primitives/zk-verifier/src/types.rs @@ -18,8 +18,8 @@ use scale_info::TypeInfo; pub const CIRCUIT_ID_TRANSFER: u8 = 1; /// Circuit identifier for unshield (withdraw) operations. pub const CIRCUIT_ID_UNSHIELD: u8 = 2; -/// Circuit identifier for disclosure (selective disclosure) operations. -pub const CIRCUIT_ID_DISCLOSURE: u8 = 4; +/// Circuit identifier for value proof operations (proves note commitment encodes declared value). +pub const CIRCUIT_ID_VALUE_PROOF: u8 = 6; /// Circuit identifier for private link dispatch operations. pub const CIRCUIT_ID_PRIVATE_LINK: u8 = 5; @@ -29,9 +29,9 @@ pub const TRANSFER_PUBLIC_INPUTS: usize = 5; /// 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; -/// Number of public inputs for the disclosure circuit. -/// Public inputs: [commitment, revealed_value, revealed_asset_id, revealed_owner_hash] -pub const DISCLOSURE_PUBLIC_INPUTS: usize = 4; +/// Number of public signals for the value proof circuit. +/// Signals: [commitment(32B), value(8B), asset_id(4B), owner_hash(32B)] = 4 field elements. +pub const VALUE_PROOF_PUBLIC_INPUTS: usize = 4; /// Number of public inputs for the private link circuit. /// Public inputs: [commitment(32B LE field element), call_hash_fe(32B LE field element)] pub const PRIVATE_LINK_PUBLIC_INPUTS: usize = 2; @@ -277,7 +277,7 @@ mod tests { fn test_circuit_ids_are_stable() { assert_eq!(CIRCUIT_ID_TRANSFER, 1); assert_eq!(CIRCUIT_ID_UNSHIELD, 2); - assert_eq!(CIRCUIT_ID_DISCLOSURE, 4); + assert_eq!(CIRCUIT_ID_VALUE_PROOF, 6); assert_eq!(CIRCUIT_ID_PRIVATE_LINK, 5); } @@ -285,7 +285,7 @@ mod tests { fn test_public_input_counts_are_expected() { assert_eq!(TRANSFER_PUBLIC_INPUTS, 5); assert_eq!(UNSHIELD_PUBLIC_INPUTS, 7); - assert_eq!(DISCLOSURE_PUBLIC_INPUTS, 4); + assert_eq!(VALUE_PROOF_PUBLIC_INPUTS, 4); assert_eq!(PRIVATE_LINK_PUBLIC_INPUTS, 2); } diff --git a/primitives/zk-verifier/src/verifier.rs b/primitives/zk-verifier/src/verifier.rs index 546604d7..99662609 100644 --- a/primitives/zk-verifier/src/verifier.rs +++ b/primitives/zk-verifier/src/verifier.rs @@ -269,7 +269,7 @@ mod tests { } #[test] - fn test_verify_with_disclosure_vk() { + fn test_verify_with_value_proof_vk() { let vk_wrapper = VerifyingKey::from_ark_vk(&create_mock_ark_vk(4)).unwrap(); let result = Groth16Verifier::verify(&vk_wrapper, &create_mock_inputs(4), &create_mock_proof()); From 764b06875fb0d54f234eaefb0243af9848b72c59 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 13:29:21 -0400 Subject: [PATCH 03/18] feat: implement value proof circuit with public signals and proof structure --- primitives/encrypted-memo/CHANGELOG.md | 20 + primitives/encrypted-memo/Cargo.lock | 2 +- primitives/encrypted-memo/Cargo.toml | 2 +- primitives/encrypted-memo/README.md | 60 +- primitives/encrypted-memo/src/disclosure.rs | 604 ------------------- primitives/encrypted-memo/src/lib.rs | 8 +- primitives/encrypted-memo/src/memo.rs | 11 +- primitives/encrypted-memo/src/value_proof.rs | 162 +++++ 8 files changed, 213 insertions(+), 656 deletions(-) delete mode 100644 primitives/encrypted-memo/src/disclosure.rs create mode 100644 primitives/encrypted-memo/src/value_proof.rs diff --git a/primitives/encrypted-memo/CHANGELOG.md b/primitives/encrypted-memo/CHANGELOG.md index dc4dd9c2..ec33cd39 100644 --- a/primitives/encrypted-memo/CHANGELOG.md +++ b/primitives/encrypted-memo/CHANGELOG.md @@ -2,6 +2,26 @@ All notable changes to `orbinum-encrypted-memo` will be documented in this file. +## [0.6.0] - 2026-05-14 + +### Added +- **`ValueProofPublicSignals`** — public signals for the `value_proof` circuit (CircuitId 6). + Fixed 76-byte layout: `commitment(32) | value(8) | asset_id(4) | owner_hash(32)`. + Proves `commitment = Poseidon(value, asset_id, owner_pubkey, blinding)` without + revealing the blinding factor or raw owner key. +- **`ValueProof`** — Groth16 proof bundle (proof 128B + public signals 76B) with + `to_bytes` / `from_bytes` / `validate()` helpers. + +### Removed +- **`DisclosureMask`**, **`DisclosurePublicSignals`**, **`DisclosureProof`**, **`PartialMemoData`** — + selective disclosure circuit has been removed from Orbinum. All disclosure types deleted. +- **`MemoError::InvalidDisclosureMask`** and **`MemoError::InvalidDisclosureData`** variants removed. +- `disclosure.rs` module emptied; `mod disclosure` removed from public API. + +### Changed +- `MemoError::InvalidProof` display message updated from "Invalid disclosure proof" to "Invalid value proof". +- README rewritten: removed selective disclosure sections; added value proof usage example and public signals table. + ## [0.5.0] - 2026-05-08 ### Added diff --git a/primitives/encrypted-memo/Cargo.lock b/primitives/encrypted-memo/Cargo.lock index 56706413..22966035 100644 --- a/primitives/encrypted-memo/Cargo.lock +++ b/primitives/encrypted-memo/Cargo.lock @@ -280,7 +280,7 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "orbinum-encrypted-memo" -version = "0.4.0" +version = "0.6.0" dependencies = [ "chacha20poly1305", "getrandom", diff --git a/primitives/encrypted-memo/Cargo.toml b/primitives/encrypted-memo/Cargo.toml index 83790994..ed86612d 100644 --- a/primitives/encrypted-memo/Cargo.toml +++ b/primitives/encrypted-memo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "orbinum-encrypted-memo" -version = "0.5.0" +version = "0.6.0" authors = ["Orbinum Network "] edition = "2021" license = "Apache-2.0 OR GPL-3.0-or-later" diff --git a/primitives/encrypted-memo/README.md b/primitives/encrypted-memo/README.md index c3960157..bff2c1df 100644 --- a/primitives/encrypted-memo/README.md +++ b/primitives/encrypted-memo/README.md @@ -9,7 +9,7 @@ Encrypted memo primitives for private transaction metadata in Orbinum Network. - **ChaCha20Poly1305 AEAD**: Authenticated encryption for memo data - **Viewing key encryption**: Only recipient can decrypt note details -- **Selective disclosure**: ZK proof structures for partial data revelation +- **Value proofs**: Public signal types for the `value_proof` circuit (CircuitId 6) - **Key derivation**: Deterministic keys from spending key via SHA-256 - **no_std compatible**: WASM runtime support @@ -82,26 +82,22 @@ for (commitment, encrypted_memo) in blockchain_notes { } ``` -### Selective Disclosure +### Value Proof Types ```rust -use orbinum_encrypted_memo::{DisclosureMask, DisclosureProof, PartialMemoData}; +use orbinum_encrypted_memo::{ValueProofPublicSignals, ValueProof}; -// Build mask (reveal only value; blinding is always hidden) -let mask = DisclosureMask::only_value(); +// Build public signals from on-chain bytes (76 bytes, CircuitId 6) +let signals = ValueProofPublicSignals::from_bytes(&raw_76_bytes)?; +assert_eq!(signals.commitment, expected_commitment); -// PartialMemoData shows only disclosed fields -let partial = PartialMemoData::from_disclosure(&memo, &mask); -assert_eq!(partial.value, Some(1000)); -assert!(partial.owner_pk.is_none()); +// Bundle proof + signals for on-chain submission +let vp = ValueProof::new(groth16_128_bytes, signals); +vp.validate()?; +let serialized = vp.to_bytes(); -// Serialize proof bundle for on-chain submission -let proof = DisclosureProof::new(groth16_proof_bytes, public_signals, mask); -proof.validate()?; -let bytes = proof.to_bytes(); - -// Deserialize on the other side -let proof = DisclosureProof::from_bytes(&bytes)?; +// Deserialize on the verifier side +let vp = ValueProof::from_bytes(&serialized)?; ``` ## Encryption Scheme @@ -177,29 +173,19 @@ This ensures change note commitments are **unlinkable** — they cannot be assoc Use `MemoData::new_without_counterparty(value, owner_pk, blinding, asset_id)` for shield, unshield, and change notes. -## Selective Disclosure Masks - -| Constructor | Reveals | Use Case | -|---|---|---| -| `DisclosureMask::all()` | value, owner, asset_id | Full compliance disclosure | -| `DisclosureMask::only_value()` | value | Prove amount without revealing identity | -| `DisclosureMask::value_and_asset()` | value + asset_id | Asset-specific compliance | -| `DisclosureMask::none()` | nothing | Custom mask starting point | - -`disclose_blinding` is always rejected by `validate()` — exposing the blinding factor breaks commitment privacy. +## Value Proof Public Signals -## Circuit Artifacts +The `value_proof` circuit (CircuitId 6) always reveals exactly 4 signals (76 bytes): -This crate provides **data types only** — it does not generate ZK proofs. Proof generation -is done client-side using the Circom disclosure circuit. Artifacts are bundled in the -node repository under `/artifacts/`: +| Field | Size | Description | +|-------|------|-------------| +| `commitment` | 32 bytes | On-chain note commitment | +| `value` | 8 bytes | Token amount (LE u64) | +| `asset_id` | 4 bytes | Asset identifier (LE u32) | +| `owner_hash` | 32 bytes | Poseidon hash of owner public key | -``` -artifacts/ - disclosure.wasm # Witness generator (client) - disclosure.zkey # Proving key (client) - verification_key_disclosure.json # Verification key (runtime) -``` +Used by `pallet-shielded-pool::claim_shielded_fees` to prove that a note commitment +encodes the exact amount and asset being claimed. ## Security Properties @@ -207,7 +193,7 @@ artifacts/ - **Authenticity**: AEAD MAC prevents tampering - **Unlinkability**: Unique encryption key per note (commitment-bound) - **Forward Secrecy**: Subkey compromise does not expose spending key -- **Zero-Knowledge**: Selective disclosure without revealing hidden fields +- **Zero-Knowledge**: Value proof reveals only commitment/value/asset_id/owner_hash ## License diff --git a/primitives/encrypted-memo/src/disclosure.rs b/primitives/encrypted-memo/src/disclosure.rs deleted file mode 100644 index d2236315..00000000 --- a/primitives/encrypted-memo/src/disclosure.rs +++ /dev/null @@ -1,604 +0,0 @@ -//! Selective disclosure types for Orbinum ZK proofs. -//! -//! These types model the output of the disclosure circuit: -//! a Groth16 proof that selectively reveals fields from an encrypted note -//! without exposing the full plaintext. -//! -//! # Types -//! -//! - [`DisclosureMask`] — which fields to reveal -//! - [`DisclosurePublicSignals`] — on-chain verified disclosure output (76 bytes) -//! - [`DisclosureProof`] — proof + signals + mask bundled for submission -//! - [`PartialMemoData`] — client-side partially revealed memo fields - -use alloc::vec::Vec; - -use crate::memo::MemoError; - -#[cfg(all(feature = "parity-scale-codec", feature = "scale-info"))] -use parity_scale_codec::{Decode, Encode}; -#[cfg(all(feature = "parity-scale-codec", feature = "scale-info"))] -use scale_info::TypeInfo; - -// ─── DisclosureMask ─────────────────────────────────────────────────────────── - -/// Controls which note fields are revealed in a selective disclosure proof. -/// -/// `disclose_blinding` MUST always be `false` to preserve commitment privacy. -/// -/// # Memo v2 (future) -/// `counterparty_pk` disclosure is planned for a future circuit update. When the -/// disclosure circuit is recompiled with the new field, add `disclose_counterparty: bool` -/// here and update `to_bitmap()` / `from_bitmap()` to expose bit 4. -// TODO(memo-v2): add disclose_counterparty field when disclosure circuit is updated -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr( - all(feature = "parity-scale-codec", feature = "scale-info"), - derive(Encode, Decode, TypeInfo) -)] -pub struct DisclosureMask { - /// Reveal the token amount. - pub disclose_value: bool, - /// Reveal the owner public key (or its hash). - pub disclose_owner: bool, - /// Reveal the blinding factor — MUST ALWAYS BE FALSE. - pub disclose_blinding: bool, - /// Reveal the asset ID. - pub disclose_asset_id: bool, - // TODO(memo-v2): add `pub disclose_counterparty: bool` when disclosure circuit is updated -} - -impl DisclosureMask { - /// Reveals all fields except `blinding`. - pub fn all() -> Self { - Self { - disclose_value: true, - disclose_owner: true, - disclose_blinding: false, - disclose_asset_id: true, - } - } - - /// Reveals only the token amount. - pub fn only_value() -> Self { - Self { - disclose_value: true, - disclose_owner: false, - disclose_blinding: false, - disclose_asset_id: false, - } - } - - /// Reveals the token amount and asset ID. - pub fn value_and_asset() -> Self { - Self { - disclose_value: true, - disclose_owner: false, - disclose_blinding: false, - disclose_asset_id: true, - } - } - - /// Reveals nothing (invalid for proof generation — useful for custom mask construction). - pub fn none() -> Self { - Self { - disclose_value: false, - disclose_owner: false, - disclose_blinding: false, - disclose_asset_id: false, - } - } - - /// Converts to a 4-bit bitmap. Bit layout (LSB first): `[value | owner | blinding | asset_id]` - pub fn to_bitmap(&self) -> u8 { - (self.disclose_value as u8) - | (self.disclose_owner as u8) << 1 - | (self.disclose_blinding as u8) << 2 - | (self.disclose_asset_id as u8) << 3 - } - - /// Creates from a 4-bit bitmap (inverse of [`to_bitmap`]). - pub fn from_bitmap(bits: u8) -> Self { - Self { - disclose_value: (bits & 0b0001) != 0, - disclose_owner: (bits & 0b0010) != 0, - disclose_blinding: (bits & 0b0100) != 0, - disclose_asset_id: (bits & 0b1000) != 0, - } - } - - /// Validates the mask. Fails if blinding is set or no field is selected. - pub fn validate(&self) -> Result<(), MemoError> { - if self.disclose_blinding { - return Err(MemoError::InvalidDisclosureMask( - "Cannot disclose blinding factor — compromises commitment privacy", - )); - } - if !self.disclose_value && !self.disclose_owner && !self.disclose_asset_id { - return Err(MemoError::InvalidDisclosureMask( - "Must disclose at least one field (value, owner, or asset_id)", - )); - } - Ok(()) - } - - /// Returns the number of fields that will be revealed. - pub fn disclosed_field_count(&self) -> usize { - [ - self.disclose_value, - self.disclose_owner, - self.disclose_blinding, - self.disclose_asset_id, - ] - .iter() - .filter(|&&v| v) - .count() - } -} - -// ─── DisclosurePublicSignals ────────────────────────────────────────────────── - -/// Public signals produced by the disclosure circuit and verified on-chain. -/// -/// Fixed serialized size: 76 bytes -/// `commitment(32) | value(8) | asset_id(4) | owner_hash(32)` -/// -/// # Memo v2 (future) -/// When the disclosure circuit is updated to support `counterparty_pk`, this struct will -/// gain a `revealed_counterparty_hash: [u8; 32]` field and the serialized size will grow -/// to 108 bytes: `commitment(32) | value(8) | asset_id(4) | owner_hash(32) | counterparty_hash(32)`. -// TODO(memo-v2): add revealed_counterparty_hash field and update to_bytes/from_bytes to 108 bytes -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr( - all(feature = "parity-scale-codec", feature = "scale-info"), - derive(Encode, Decode, TypeInfo) -)] -pub struct DisclosurePublicSignals { - /// Commitment binding the proof to a specific note. - pub commitment: [u8; 32], - /// Revealed token amount (0 when not disclosed). - pub revealed_value: u64, - /// Revealed asset ID (0 when not disclosed). - pub revealed_asset_id: u32, - /// Hash of owner public key (zero when owner not disclosed). - pub revealed_owner_hash: [u8; 32], -} - -impl DisclosurePublicSignals { - /// Creates new public signals. - pub fn new( - commitment: [u8; 32], - revealed_value: u64, - revealed_asset_id: u32, - revealed_owner_hash: [u8; 32], - ) -> Self { - Self { - commitment, - revealed_value, - revealed_asset_id, - revealed_owner_hash, - } - } - - /// Serializes to exactly 76 bytes. - pub fn to_bytes(&self) -> Vec { - let mut bytes = Vec::with_capacity(76); - bytes.extend_from_slice(&self.commitment); - bytes.extend_from_slice(&self.revealed_value.to_le_bytes()); - bytes.extend_from_slice(&self.revealed_asset_id.to_le_bytes()); - bytes.extend_from_slice(&self.revealed_owner_hash); - bytes - } - - /// Deserializes from exactly 76 bytes. - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != 76 { - return Err(MemoError::InvalidProof("Invalid public signals length")); - } - let mut commitment = [0u8; 32]; - commitment.copy_from_slice(&bytes[0..32]); - let revealed_value = u64::from_le_bytes( - bytes[32..40] - .try_into() - .map_err(|_| MemoError::InvalidProof("Invalid revealed_value"))?, - ); - let revealed_asset_id = u32::from_le_bytes( - bytes[40..44] - .try_into() - .map_err(|_| MemoError::InvalidProof("Invalid revealed_asset_id"))?, - ); - let mut revealed_owner_hash = [0u8; 32]; - revealed_owner_hash.copy_from_slice(&bytes[44..76]); - Ok(Self { - commitment, - revealed_value, - revealed_asset_id, - revealed_owner_hash, - }) - } - - /// Validates consistency against the disclosure mask. - /// - /// If the owner is NOT disclosed, `revealed_owner_hash` must be zero. - pub fn validate(&self, mask: &DisclosureMask) -> Result<(), MemoError> { - if !mask.disclose_owner && self.revealed_owner_hash != [0u8; 32] { - return Err(MemoError::InvalidProof( - "revealed_owner_hash must be zero when owner is not disclosed", - )); - } - Ok(()) - } -} - -// ─── DisclosureProof ────────────────────────────────────────────────────────── - -/// Selective disclosure proof ready for on-chain verification. -/// -/// Serialized layout: `proof_len(2) || proof(n) || public_signals(76) || mask_bitmap(1)` -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr( - all(feature = "parity-scale-codec", feature = "scale-info"), - derive(Encode, Decode, TypeInfo) -)] -pub struct DisclosureProof { - /// Raw Groth16 proof bytes (BN254: up to 192 bytes compressed). - pub proof: Vec, - /// Public signals verified on-chain. - pub public_signals: DisclosurePublicSignals, - /// Disclosure mask used when generating this proof. - pub mask: DisclosureMask, -} - -impl DisclosureProof { - /// Creates a new disclosure proof. - pub fn new( - proof: Vec, - public_signals: DisclosurePublicSignals, - mask: DisclosureMask, - ) -> Self { - Self { - proof, - public_signals, - mask, - } - } - - /// Serializes for on-chain storage: `proof_len(2) || proof(n) || signals(76) || mask_bitmap(1)` - pub fn to_bytes(&self) -> Vec { - let mut bytes = Vec::new(); - bytes.extend_from_slice(&(self.proof.len() as u16).to_le_bytes()); - bytes.extend_from_slice(&self.proof); - bytes.extend_from_slice(&self.public_signals.to_bytes()); - bytes.push(self.mask.to_bitmap()); - bytes - } - - /// Deserializes from bytes. - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() < 2 { - return Err(MemoError::InvalidProof("Proof too short")); - } - let mut off = 0; - let proof_len = u16::from_le_bytes( - bytes[off..off + 2] - .try_into() - .map_err(|_| MemoError::InvalidProof("Invalid proof length"))?, - ) as usize; - off += 2; - if bytes.len() < off + proof_len { - return Err(MemoError::InvalidProof("Proof bytes truncated")); - } - let proof = bytes[off..off + proof_len].to_vec(); - off += proof_len; - if bytes.len() < off + 76 { - return Err(MemoError::InvalidProof("Public signals truncated")); - } - let public_signals = DisclosurePublicSignals::from_bytes(&bytes[off..off + 76])?; - off += 76; - if bytes.len() < off + 1 { - return Err(MemoError::InvalidProof("Mask bitmap missing")); - } - let mask = DisclosureMask::from_bitmap(bytes[off]); - Ok(Self { - proof, - public_signals, - mask, - }) - } - - /// Validates proof consistency before on-chain submission. - pub fn validate(&self) -> Result<(), MemoError> { - self.mask.validate()?; - if self.proof.is_empty() { - return Err(MemoError::InvalidProof("Proof is empty")); - } - self.public_signals.validate(&self.mask)?; - Ok(()) - } -} - -// ─── PartialMemoData ────────────────────────────────────────────────────────── - -/// Partially revealed memo data. Only disclosed fields are `Some`. -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr( - all(feature = "parity-scale-codec", feature = "scale-info"), - derive(Encode, Decode, TypeInfo) -)] -pub struct PartialMemoData { - /// Revealed token amount (`None` when not disclosed). - pub value: Option, - /// Revealed owner public key (`None` when not disclosed). - pub owner_pk: Option<[u8; 32]>, - /// Revealed blinding factor — should always be `None`. - pub blinding: Option<[u8; 32]>, - /// Revealed asset ID (`None` when not disclosed). - pub asset_id: Option, -} - -impl PartialMemoData { - /// Creates empty partial memo (nothing revealed). - pub fn empty() -> Self { - Self { - value: None, - owner_pk: None, - blinding: None, - asset_id: None, - } - } - - /// Applies a disclosure mask to full memo data. - pub fn from_disclosure(memo: &crate::memo::MemoData, mask: &DisclosureMask) -> Self { - Self { - value: mask.disclose_value.then_some(memo.value), - owner_pk: mask.disclose_owner.then_some(memo.owner_pk), - blinding: mask.disclose_blinding.then_some(memo.blinding), - asset_id: mask.disclose_asset_id.then_some(memo.asset_id), - } - } - - /// Returns `true` when no fields are revealed. - pub fn is_empty(&self) -> bool { - self.value.is_none() - && self.owner_pk.is_none() - && self.blinding.is_none() - && self.asset_id.is_none() - } - - /// Serializes: `flags(1) || [optional fields...]` - pub fn to_bytes(&self) -> Vec { - let mut bytes = Vec::new(); - let flags = (self.value.is_some() as u8) - | (self.owner_pk.is_some() as u8) << 1 - | (self.blinding.is_some() as u8) << 2 - | (self.asset_id.is_some() as u8) << 3; - bytes.push(flags); - if let Some(v) = self.value { - bytes.extend_from_slice(&v.to_le_bytes()); - } - if let Some(pk) = self.owner_pk { - bytes.extend_from_slice(&pk); - } - if let Some(b) = self.blinding { - bytes.extend_from_slice(&b); - } - if let Some(id) = self.asset_id { - bytes.extend_from_slice(&id.to_le_bytes()); - } - bytes - } - - /// Deserializes from bytes produced by [`to_bytes`]. - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.is_empty() { - return Err(MemoError::InvalidDisclosureData); - } - let flags = bytes[0]; - let mut off = 1; - - let value = if (flags & 0b0001) != 0 { - if bytes.len() < off + 16 { - return Err(MemoError::InvalidDisclosureData); - } - let v = u128::from_le_bytes( - bytes[off..off + 16] - .try_into() - .map_err(|_| MemoError::InvalidDisclosureData)?, - ); - off += 16; - Some(v) - } else { - None - }; - - let owner_pk = if (flags & 0b0010) != 0 { - if bytes.len() < off + 32 { - return Err(MemoError::InvalidDisclosureData); - } - let mut pk = [0u8; 32]; - pk.copy_from_slice(&bytes[off..off + 32]); - off += 32; - Some(pk) - } else { - None - }; - - let blinding = if (flags & 0b0100) != 0 { - if bytes.len() < off + 32 { - return Err(MemoError::InvalidDisclosureData); - } - let mut b = [0u8; 32]; - b.copy_from_slice(&bytes[off..off + 32]); - off += 32; - Some(b) - } else { - None - }; - - let asset_id = if (flags & 0b1000) != 0 { - if bytes.len() < off + 4 { - return Err(MemoError::InvalidDisclosureData); - } - let id = u32::from_le_bytes( - bytes[off..off + 4] - .try_into() - .map_err(|_| MemoError::InvalidDisclosureData)?, - ); - Some(id) - } else { - None - }; - - Ok(Self { - value, - owner_pk, - blinding, - asset_id, - }) - } -} - -// ─── Tests ──────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - use crate::memo::MemoData; - - fn sample_signals() -> DisclosurePublicSignals { - DisclosurePublicSignals::new([1u8; 32], 500, 3, [2u8; 32]) - } - - fn valid_mask() -> DisclosureMask { - DisclosureMask::all() - } - - // ── DisclosureMask ──────────────────────────────────────────────────────── - - #[test] - fn mask_bitmap_roundtrip() { - for bits in 0u8..=15 { - assert_eq!(DisclosureMask::from_bitmap(bits).to_bitmap(), bits); - } - } - - #[test] - fn mask_all_excludes_blinding() { - let m = DisclosureMask::all(); - assert!(m.disclose_value && m.disclose_owner && m.disclose_asset_id); - assert!(!m.disclose_blinding); - } - - #[test] - fn mask_validate_rejects_blinding() { - let m = DisclosureMask { - disclose_value: true, - disclose_owner: false, - disclose_blinding: true, - disclose_asset_id: false, - }; - assert!(m.validate().is_err()); - } - - #[test] - fn mask_validate_rejects_all_false() { - assert!(DisclosureMask::none().validate().is_err()); - } - - #[test] - fn mask_validate_ok_with_one_field() { - assert!(DisclosureMask::only_value().validate().is_ok()); - } - - #[test] - fn mask_disclosed_field_count() { - assert_eq!(DisclosureMask::all().disclosed_field_count(), 3); - assert_eq!(DisclosureMask::only_value().disclosed_field_count(), 1); - assert_eq!(DisclosureMask::none().disclosed_field_count(), 0); - } - - // ── DisclosurePublicSignals ─────────────────────────────────────────────── - - #[test] - fn signals_roundtrip() { - let s = sample_signals(); - assert_eq!( - DisclosurePublicSignals::from_bytes(&s.to_bytes()).unwrap(), - s - ); - } - - #[test] - fn signals_wrong_length() { - assert!(DisclosurePublicSignals::from_bytes(&[0u8; 75]).is_err()); - assert!(DisclosurePublicSignals::from_bytes(&[0u8; 77]).is_err()); - } - - #[test] - fn signals_validate_owner_hash_must_be_zero_when_not_disclosed() { - let s = DisclosurePublicSignals::new([0u8; 32], 0, 0, [1u8; 32]); - let mask = DisclosureMask::only_value(); - assert!(s.validate(&mask).is_err()); - - let s_ok = DisclosurePublicSignals::new([0u8; 32], 0, 0, [0u8; 32]); - assert!(s_ok.validate(&mask).is_ok()); - } - - // ── DisclosureProof ─────────────────────────────────────────────────────── - - #[test] - fn proof_roundtrip() { - let proof = DisclosureProof::new(vec![0xABu8; 128], sample_signals(), valid_mask()); - assert_eq!( - DisclosureProof::from_bytes(&proof.to_bytes()).unwrap(), - proof - ); - } - - #[test] - fn proof_too_short() { - assert!(DisclosureProof::from_bytes(&[0u8; 1]).is_err()); - } - - #[test] - fn proof_validate_rejects_empty_proof_bytes() { - let p = DisclosureProof::new(vec![], sample_signals(), valid_mask()); - assert!(p.validate().is_err()); - } - - #[test] - fn proof_validate_ok() { - let p = DisclosureProof::new(vec![1u8; 64], sample_signals(), valid_mask()); - assert!(p.validate().is_ok()); - } - - // ── PartialMemoData ─────────────────────────────────────────────────────── - - #[test] - fn partial_empty_is_empty() { - assert!(PartialMemoData::empty().is_empty()); - } - - #[test] - fn partial_from_disclosure_respects_mask() { - let memo = MemoData::new(100, [1u8; 32], [2u8; 32], 5, [0u8; 32]); - let mask = DisclosureMask::only_value(); - let partial = PartialMemoData::from_disclosure(&memo, &mask); - assert_eq!(partial.value, Some(100)); - assert!(partial.owner_pk.is_none()); - assert!(partial.blinding.is_none()); - assert!(partial.asset_id.is_none()); - } - - #[test] - fn partial_roundtrip() { - let memo = MemoData::new(42, [3u8; 32], [4u8; 32], 7, [0u8; 32]); - let p = PartialMemoData::from_disclosure(&memo, &DisclosureMask::all()); - assert_eq!(PartialMemoData::from_bytes(&p.to_bytes()).unwrap(), p); - } - - #[test] - fn partial_from_bytes_empty_input() { - assert!(PartialMemoData::from_bytes(&[]).is_err()); - } -} diff --git a/primitives/encrypted-memo/src/lib.rs b/primitives/encrypted-memo/src/lib.rs index 32cd2ff5..aa46c094 100644 --- a/primitives/encrypted-memo/src/lib.rs +++ b/primitives/encrypted-memo/src/lib.rs @@ -4,7 +4,7 @@ //! //! - [`keys`] — Key types (`ViewingKey`, `NullifierKey`, `EdDSAKey`), `KeySet`, derivation fns //! - [`memo`] — `MemoData`, `MemoError`, encrypt/decrypt functions, size constants -//! - [`disclosure`] — `DisclosureMask`, `DisclosurePublicSignals`, `DisclosureProof`, `PartialMemoData` +//! - [`value_proof`] — `ValueProofPublicSignals`, `ValueProof` (CircuitId 6) //! //! # Example //! @@ -27,9 +27,9 @@ extern crate alloc; -mod disclosure; mod keys; mod memo; +mod value_proof; // Keys pub use keys::{ @@ -47,5 +47,5 @@ pub use memo::{ #[cfg(feature = "encrypt")] pub use memo::encrypt_memo_random; -// Disclosure -pub use disclosure::{DisclosureMask, DisclosureProof, DisclosurePublicSignals, PartialMemoData}; +// Value proof +pub use value_proof::{ValueProof, ValueProofPublicSignals}; diff --git a/primitives/encrypted-memo/src/memo.rs b/primitives/encrypted-memo/src/memo.rs index eb74b3b4..aa3d7bfa 100644 --- a/primitives/encrypted-memo/src/memo.rs +++ b/primitives/encrypted-memo/src/memo.rs @@ -54,11 +54,7 @@ pub enum MemoError { InvalidNoteData, /// Commitment mismatch detected after decryption. CommitmentMismatch, - /// Invalid disclosure mask configuration. - InvalidDisclosureMask(&'static str), - /// Invalid disclosed data format. - InvalidDisclosureData, - /// Invalid or inconsistent disclosure proof. + /// Invalid or inconsistent value proof. InvalidProof(&'static str), } @@ -71,9 +67,7 @@ impl core::fmt::Display for MemoError { Self::EncryptionFailed => write!(f, "Encryption operation failed"), Self::InvalidNoteData => write!(f, "Invalid note data format"), Self::CommitmentMismatch => write!(f, "Commitment mismatch after decryption"), - Self::InvalidDisclosureMask(msg) => write!(f, "Invalid disclosure mask: {msg}"), - Self::InvalidDisclosureData => write!(f, "Invalid disclosed data format"), - Self::InvalidProof(msg) => write!(f, "Invalid disclosure proof: {msg}"), + Self::InvalidProof(msg) => write!(f, "Invalid value proof: {msg}"), } } } @@ -329,7 +323,6 @@ mod tests { assert!(format!("{}", MemoError::DecryptionFailed) .to_lowercase() .contains("decryption")); - assert!(format!("{}", MemoError::InvalidDisclosureMask("x")).contains("x")); assert!(format!("{}", MemoError::InvalidProof("y")).contains("y")); } diff --git a/primitives/encrypted-memo/src/value_proof.rs b/primitives/encrypted-memo/src/value_proof.rs new file mode 100644 index 00000000..ef4f3b26 --- /dev/null +++ b/primitives/encrypted-memo/src/value_proof.rs @@ -0,0 +1,162 @@ +//! Value proof types for Orbinum ZK note verification. +//! +//! These types model the public signals of the `value_proof` circuit (CircuitId 6): +//! a Groth16 proof that proves a note commitment encodes a specific value and asset, +//! without revealing the blinding factor or the raw owner public key. +//! +//! # Public signals layout (76 bytes) +//! +//! ```text +//! commitment(32) | value(8) | asset_id(4) | owner_hash(32) +//! ``` +//! +//! Proves: `commitment = Poseidon(value, asset_id, owner_pubkey, blinding)` +//! +//! # Types +//! +//! - [`ValueProofPublicSignals`] — on-chain verified public signals (76 bytes) +//! - [`ValueProof`] — proof + signals bundled for submission + +use alloc::vec::Vec; + +use crate::memo::MemoError; + +#[cfg(all(feature = "parity-scale-codec", feature = "scale-info"))] +use parity_scale_codec::{Decode, Encode}; +#[cfg(all(feature = "parity-scale-codec", feature = "scale-info"))] +use scale_info::TypeInfo; + +// ─── ValueProofPublicSignals ───────────────────────────────────────────────── + +/// Public signals produced by the `value_proof` circuit (CircuitId 6) and verified on-chain. +/// +/// Fixed serialized size: **76 bytes** +/// Layout: `commitment(32) | value(8) | asset_id(4) | owner_hash(32)` +/// +/// All four fields are always present — the value_proof circuit unconditionally +/// reveals commitment, value, asset_id and the owner public key hash. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + all(feature = "parity-scale-codec", feature = "scale-info"), + derive(Encode, Decode, TypeInfo) +)] +pub struct ValueProofPublicSignals { + /// Commitment binding the proof to a specific on-chain note. + pub commitment: [u8; 32], + /// Token amount encoded in the commitment (LE u64). + pub value: u64, + /// Asset identifier encoded in the commitment. + pub asset_id: u32, + /// Poseidon hash of the owner public key (not the raw key). + pub owner_hash: [u8; 32], +} + +impl ValueProofPublicSignals { + /// Creates new public signals. + pub fn new(commitment: [u8; 32], value: u64, asset_id: u32, owner_hash: [u8; 32]) -> Self { + Self { commitment, value, asset_id, owner_hash } + } + + /// Serializes to exactly 76 bytes. + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::with_capacity(76); + bytes.extend_from_slice(&self.commitment); + bytes.extend_from_slice(&self.value.to_le_bytes()); + bytes.extend_from_slice(&self.asset_id.to_le_bytes()); + bytes.extend_from_slice(&self.owner_hash); + bytes + } + + /// Deserializes from exactly 76 bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != 76 { + return Err(MemoError::InvalidProof( + "Invalid public signals length (expected 76 bytes)", + )); + } + let mut commitment = [0u8; 32]; + commitment.copy_from_slice(&bytes[0..32]); + let value = u64::from_le_bytes( + bytes[32..40] + .try_into() + .map_err(|_| MemoError::InvalidProof("Invalid value bytes"))?, + ); + let asset_id = u32::from_le_bytes( + bytes[40..44] + .try_into() + .map_err(|_| MemoError::InvalidProof("Invalid asset_id bytes"))?, + ); + let mut owner_hash = [0u8; 32]; + owner_hash.copy_from_slice(&bytes[44..76]); + Ok(Self { commitment, value, asset_id, owner_hash }) + } +} + +// ─── ValueProof ────────────────────────────────────────────────────────────── + +/// Value proof ready for on-chain verification (CircuitId 6). +/// +/// Serialized layout: `proof_len(2) || proof(n) || public_signals(76)` +/// +/// The Groth16 proof for BN254 is always 128 bytes. `validate()` enforces this. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + all(feature = "parity-scale-codec", feature = "scale-info"), + derive(Encode, Decode, TypeInfo) +)] +pub struct ValueProof { + /// Raw Groth16 proof bytes (BN254: 128 bytes). + pub proof: Vec, + /// Public signals verified on-chain. + pub public_signals: ValueProofPublicSignals, +} + +impl ValueProof { + /// Creates a new value proof. + pub fn new(proof: Vec, public_signals: ValueProofPublicSignals) -> Self { + Self { proof, public_signals } + } + + /// Serializes: `proof_len(2) || proof(n) || signals(76)` + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + bytes.extend_from_slice(&(self.proof.len() as u16).to_le_bytes()); + bytes.extend_from_slice(&self.proof); + bytes.extend_from_slice(&self.public_signals.to_bytes()); + bytes + } + + /// Deserializes from bytes produced by [`to_bytes`]. + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < 2 { + return Err(MemoError::InvalidProof("Value proof too short")); + } + let proof_len = u16::from_le_bytes( + bytes[0..2] + .try_into() + .map_err(|_| MemoError::InvalidProof("Invalid proof length field"))?, + ) as usize; + if bytes.len() < 2 + proof_len + 76 { + return Err(MemoError::InvalidProof("Value proof bytes truncated")); + } + let proof = bytes[2..2 + proof_len].to_vec(); + let public_signals = + ValueProofPublicSignals::from_bytes(&bytes[2 + proof_len..2 + proof_len + 76])?; + Ok(Self { proof, public_signals }) + } + + /// Validates before on-chain submission. + /// + /// A valid BN254 Groth16 proof is exactly 128 bytes. + pub fn validate(&self) -> Result<(), MemoError> { + if self.proof.is_empty() { + return Err(MemoError::InvalidProof("Proof is empty")); + } + if self.proof.len() != 128 { + return Err(MemoError::InvalidProof( + "Groth16 BN254 proof must be exactly 128 bytes", + )); + } + Ok(()) + } +} From f468920553725c7c73c0eed6eff62268e09478ab Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 13:30:42 -0400 Subject: [PATCH 04/18] feat: update ZkVerifierPort to replace disclosure proof with value proof and adjust related documentation --- frame/zk-verifier/CHANGELOG.md | 30 +++ frame/zk-verifier/Cargo.toml | 2 +- frame/zk-verifier/README.md | 14 +- frame/zk-verifier/src/encoding.rs | 74 +++--- frame/zk-verifier/src/lib.rs | 7 +- frame/zk-verifier/src/port.rs | 407 +----------------------------- frame/zk-verifier/src/types.rs | 4 +- frame/zk-verifier/src/verifier.rs | 1 - template/runtime/src/lib.rs | 2 - 9 files changed, 95 insertions(+), 446 deletions(-) create mode 100644 frame/zk-verifier/CHANGELOG.md diff --git a/frame/zk-verifier/CHANGELOG.md b/frame/zk-verifier/CHANGELOG.md new file mode 100644 index 00000000..020935b9 --- /dev/null +++ b/frame/zk-verifier/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog — pallet-zk-verifier + +All notable changes to this pallet are documented here. + +--- + +## [0.6.0] — 2026-05-14 + +### Changed + +- `ZkVerifierPort`: replaced `verify_disclosure_proof` and `batch_verify_disclosure_proofs` with `verify_value_proof` +- `CircuitId::VALUE_PROOF = 6` replaces the removed disclosure circuit (ID 4) +- README: updated Circuit IDs table and `ZkVerifierPort` documentation to reflect new interface + +### Removed + +- `ZkVerifierPort::verify_disclosure_proof` — use `verify_value_proof` instead +- `ZkVerifierPort::batch_verify_disclosure_proofs` — batch disclosure is no longer supported +- Circuit ID `4` (`disclosure`) — replaced by `6` (`value_proof`) + +--- + +## [0.5.1] — 2026-04-14 + +- Initial tracked release +- `Groth16Verifier` integration via `orbinum-zk-verifier` +- `CircuitId` type with constants: `TRANSFER(1)`, `UNSHIELD(2)`, `DISCLOSURE(4)`, `PRIVATE_LINK(5)` +- `ZkVerifierPort` trait: `verify_transfer_proof`, `verify_unshield_proof`, `verify_disclosure_proof`, `batch_verify_disclosure_proofs`, `verify_private_link_proof` +- Per-version VK storage and `VerificationStats` +- `verify_proof` extrinsic (Signed origin) diff --git a/frame/zk-verifier/Cargo.toml b/frame/zk-verifier/Cargo.toml index 02ddd769..27457e00 100644 --- a/frame/zk-verifier/Cargo.toml +++ b/frame/zk-verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-zk-verifier" -version = "0.5.1" +version = "0.6.0" description = "Zero-Knowledge proof verification pallet for Orbinum" authors = ["Orbinum Team"] license = "GPL-3.0-or-later" diff --git a/frame/zk-verifier/README.md b/frame/zk-verifier/README.md index 06a4d01b..1569ce77 100644 --- a/frame/zk-verifier/README.md +++ b/frame/zk-verifier/README.md @@ -21,8 +21,8 @@ MVP in active development. Production runtime verifies Groth16 proofs on BN254. | `1` | transfer (2-in / 2-out UTXO) | | `2` | unshield (pool withdrawal) | | `3` | shield (reserved) | -| `4` | disclosure (selective disclosure) | | `5` | private_link | +| `6` | value_proof (relay fee claiming) | ## Storage @@ -70,17 +70,12 @@ pub trait ZkVerifierPort { version: Option, ) -> Result; - fn verify_disclosure_proof( + fn verify_value_proof( proof: &[u8], - public_signals: &[u8], // exactly 76 bytes + public_signals: &[u8], // exactly 76 bytes: commitment[0..32] | value[32..40] | asset_id[40..44] | owner_hash[44..76] version: Option, ) -> Result; - fn batch_verify_disclosure_proofs( - proofs_and_signals: &[(&[u8], &[u8])], // max 10, signals = 76 bytes each - version: Option, - ) -> Result, DispatchError>; - fn verify_private_link_proof( proof: &[u8], commitment: &[u8; 32], @@ -137,8 +132,7 @@ The `recipient` is passed **without byte-reversal**. `PublicInputs::to_field_ele ## Notes and limitations - Verification behavior differs between `runtime-benchmarks`/test builds (mock VK) and production (real Groth16). -- Batch disclosure verification is limited to 10 proofs per call. -- `public_signals` for disclosure must be exactly 76 bytes: `commitment[0..32] | value[32..40] | asset_id[40..44] | owner_hash[44..76]`. +- `public_signals` for `verify_value_proof` must be exactly 76 bytes: `commitment[0..32] | value[32..40] | asset_id[40..44] | owner_hash[44..76]`. ## License diff --git a/frame/zk-verifier/src/encoding.rs b/frame/zk-verifier/src/encoding.rs index 13e94ec9..a9270ab0 100644 --- a/frame/zk-verifier/src/encoding.rs +++ b/frame/zk-verifier/src/encoding.rs @@ -76,31 +76,24 @@ pub fn encode_private_link(commitment: &[u8; 32], call_hash_fe: &[u8; 32]) -> Ve alloc::vec![*commitment, *call_hash_fe] } -/// Decode 76-byte selective disclosure public signals into 4 field elements. +/// Encode value proof public inputs (CircuitId 6). /// -/// Layout: `commitment[0..32] | value[32..40] | asset_id[40..44] | owner_hash[44..76]` -pub fn decode_disclosure_signals( - signals: &[u8], -) -> Result, sp_runtime::DispatchError> { - if signals.len() != 76 { - return Err(sp_runtime::DispatchError::Other( - "Invalid disclosure signals length (expected 76 bytes)", - )); - } - +/// Expands the compact 76-byte on-chain layout into 4 BN254 field elements: +/// `commitment(32) | value_u64_le(8→32) | asset_id_u32_le(4→32) | owner_hash(32)` +pub fn encode_value_proof(public_signals: &[u8; 76]) -> Vec<[u8; 32]> { let mut commitment = [0u8; 32]; - commitment.copy_from_slice(&signals[0..32]); + commitment.copy_from_slice(&public_signals[0..32]); let mut value = [0u8; 32]; - value[..8].copy_from_slice(&signals[32..40]); + value[..8].copy_from_slice(&public_signals[32..40]); let mut asset_id = [0u8; 32]; - asset_id[..4].copy_from_slice(&signals[40..44]); + asset_id[..4].copy_from_slice(&public_signals[40..44]); let mut owner_hash = [0u8; 32]; - owner_hash.copy_from_slice(&signals[44..76]); + owner_hash.copy_from_slice(&public_signals[44..76]); - Ok(alloc::vec![commitment, value, asset_id, owner_hash]) + alloc::vec![commitment, value, asset_id, owner_hash] } // ─── Tests ──────────────────────────────────────────────────────────────────── @@ -157,24 +150,43 @@ mod tests { } #[test] - fn decode_disclosure_signals_valid() { + fn encode_value_proof_correct_length() { + let signals = [0u8; 76]; + let raw = encode_value_proof(&signals); + assert_eq!(raw.len(), 4); + } + + #[test] + fn encode_value_proof_commitment_field() { + let mut signals = [0u8; 76]; + signals[0..32].copy_from_slice(&[0xAAu8; 32]); + let raw = encode_value_proof(&signals); + assert_eq!(raw[0], [0xAAu8; 32]); + } + + #[test] + fn encode_value_proof_value_field_zero_padded() { let mut signals = [0u8; 76]; - signals[0] = 0xAA; - signals[32] = 0xBB; - signals[40] = 0xCC; - signals[44] = 0xDD; - let result = decode_disclosure_signals(&signals).unwrap(); - assert_eq!(result.len(), 4); - assert_eq!(result[0][0], 0xAA); - assert_eq!(result[1][0], 0xBB); - assert_eq!(result[2][0], 0xCC); - assert_eq!(result[3][0], 0xDD); + signals[32..40].copy_from_slice(&100u64.to_le_bytes()); + let raw = encode_value_proof(&signals); + assert_eq!(&raw[1][..8], &100u64.to_le_bytes()); + assert_eq!(&raw[1][8..], &[0u8; 24]); } #[test] - fn decode_disclosure_signals_wrong_length() { - assert!(decode_disclosure_signals(&[0u8; 75]).is_err()); - assert!(decode_disclosure_signals(&[0u8; 77]).is_err()); - assert!(decode_disclosure_signals(&[]).is_err()); + fn encode_value_proof_asset_id_field_zero_padded() { + let mut signals = [0u8; 76]; + signals[40..44].copy_from_slice(&42u32.to_le_bytes()); + let raw = encode_value_proof(&signals); + assert_eq!(&raw[2][..4], &42u32.to_le_bytes()); + assert_eq!(&raw[2][4..], &[0u8; 28]); + } + + #[test] + fn encode_value_proof_owner_hash_field() { + let mut signals = [0u8; 76]; + signals[44..76].copy_from_slice(&[0xBBu8; 32]); + let raw = encode_value_proof(&signals); + assert_eq!(raw[3], [0xBBu8; 32]); } } diff --git a/frame/zk-verifier/src/lib.rs b/frame/zk-verifier/src/lib.rs index 1de81d4b..8ba33f6e 100644 --- a/frame/zk-verifier/src/lib.rs +++ b/frame/zk-verifier/src/lib.rs @@ -981,7 +981,6 @@ mod tests { let entries: BoundedVec> = vec![ make_vk_entry(CircuitId::TRANSFER, 1, false), make_vk_entry(CircuitId::UNSHIELD, 1, false), - make_vk_entry(CircuitId::DISCLOSURE, 1, false), ] .try_into() .unwrap(); @@ -997,12 +996,8 @@ mod tests { CircuitId::UNSHIELD, 1u32 )); - assert!(VerificationKeys::::contains_key( - CircuitId::DISCLOSURE, - 1u32 - )); assert!(has_event(Event::BatchVerificationKeysRegistered { - count: 3 + count: 2 })); }); } diff --git a/frame/zk-verifier/src/port.rs b/frame/zk-verifier/src/port.rs index a163b4c1..f6ae0db0 100644 --- a/frame/zk-verifier/src/port.rs +++ b/frame/zk-verifier/src/port.rs @@ -8,7 +8,7 @@ use crate::{ Pallet, encoding, - pallet::{ActiveCircuitVersion, Config, Error, VerificationKeys, VerificationStats}, + pallet::{Config, Error}, types::CircuitId, verifier, }; @@ -42,26 +42,14 @@ pub trait ZkVerifierPort { version: Option, ) -> Result; - /// Verify a selective disclosure proof. - /// - /// `public_signals` must be exactly 76 bytes: - /// `commitment[0..32] | value[32..40] | asset_id[40..44] | owner_hash[44..76]` - fn verify_disclosure_proof( + /// Verify a value proof (76-byte layout: commitment | value | asset_id | owner_hash). + /// Used for gasless fee claiming. + fn verify_value_proof( proof: &[u8], public_signals: &[u8], version: Option, ) -> Result; - /// Batch-verify multiple disclosure proofs (optimised path via `ark-groth16`). - /// - /// All `public_signals` slices must be exactly 76 bytes each. - /// Batch size is limited to 10. - fn batch_verify_disclosure_proofs( - proofs: &[sp_std::vec::Vec], - public_signals: &[sp_std::vec::Vec], - version: Option, - ) -> Result; - /// Verify a private-link dispatch proof. fn verify_private_link_proof( proof: &[u8], @@ -118,68 +106,21 @@ impl ZkVerifierPort for Pallet { verifier::verify::(CircuitId::UNSHIELD, version, proof, raw).map(|(ok, _)| ok) } - fn verify_disclosure_proof( + fn verify_value_proof( proof: &[u8], public_signals: &[u8], version: Option, ) -> Result { - let raw = encoding::decode_disclosure_signals(public_signals)?; - verifier::verify::(CircuitId::DISCLOSURE, version, proof, raw).map(|(ok, _)| ok) - } - - fn batch_verify_disclosure_proofs( - proofs: &[sp_std::vec::Vec], - public_signals: &[sp_std::vec::Vec], - version: Option, - ) -> Result { - use orbinum_zk_verifier::{Groth16Verifier, Proof, PublicInputs, VerifyingKey}; - - const MAX_BATCH: usize = 10; - - frame_support::ensure!( - !proofs.is_empty() && proofs.len() <= MAX_BATCH, - Error::::InvalidBatchSize - ); - frame_support::ensure!( - proofs.len() == public_signals.len(), - Error::::BatchLengthMismatch - ); - - let resolved = version - .or_else(|| ActiveCircuitVersion::::get(CircuitId::DISCLOSURE)) - .ok_or(Error::::CircuitNotFound)?; - - let vk_info = VerificationKeys::::get(CircuitId::DISCLOSURE, resolved) - .ok_or(Error::::VerificationKeyNotFound)?; - - let vk = VerifyingKey::new(vk_info.key_data.to_vec()); - - let batch_proofs: sp_std::vec::Vec = - proofs.iter().map(|p| Proof::new(p.clone())).collect(); - - let all_inputs = public_signals - .iter() - .map(|s| encoding::decode_disclosure_signals(s).map(PublicInputs::new)) - .collect::, _>>()?; - - let count = proofs.len() as u64; - - match Groth16Verifier::batch_verify(&vk, &all_inputs, &batch_proofs) { - Ok(_) => { - VerificationStats::::mutate(CircuitId::DISCLOSURE, resolved, |s| { - s.total_verifications = s.total_verifications.saturating_add(count); - s.successful_verifications = s.successful_verifications.saturating_add(count); - }); - Ok(true) - } - Err(_) => { - VerificationStats::::mutate(CircuitId::DISCLOSURE, resolved, |s| { - s.total_verifications = s.total_verifications.saturating_add(count); - s.failed_verifications = s.failed_verifications.saturating_add(count); - }); - Err(Error::::BatchVerificationFailed.into()) - } + if public_signals.len() != 76 { + return Err(sp_runtime::DispatchError::Other( + "Invalid value proof signals length (expected 76 bytes)", + )); } + let signals: &[u8; 76] = public_signals + .try_into() + .map_err(|_| sp_runtime::DispatchError::Other("value proof signals slice error"))?; + let raw = encoding::encode_value_proof(signals); + verifier::verify::(CircuitId::VALUE_PROOF, version, proof, raw).map(|(ok, _)| ok) } fn verify_private_link_proof( @@ -237,11 +178,6 @@ mod tests { [0x02u8; 32] } - /// 76-byte buffer accepted by `decode_disclosure_signals`. - fn valid_signals() -> alloc::vec::Vec { - vec![0xAAu8; 76] - } - fn insert_vk(circuit_id: CircuitId, version: u32) { VerificationKeys::::insert( circuit_id, @@ -258,64 +194,6 @@ mod tests { ActiveCircuitVersion::::insert(circuit_id, version); } - // ── decode_disclosure_signals (via verify_disclosure_proof) ─────────────── - // - // The helper is private, so we exercise it through the public trait method. - // decode_disclosure_signals is called BEFORE verifier::verify, so VK setup - // is not required to hit the length check. - - #[test] - fn signals_too_short_returns_dispatch_error_other() { - new_test_ext().execute_with(|| { - let err = as ZkVerifierPort>::verify_disclosure_proof( - &proof(), - &[0u8; 75], - None, - ) - .unwrap_err(); - assert!(matches!(err, sp_runtime::DispatchError::Other(_))); - }); - } - - #[test] - fn signals_too_long_returns_dispatch_error_other() { - new_test_ext().execute_with(|| { - let err = as ZkVerifierPort>::verify_disclosure_proof( - &proof(), - &[0u8; 77], - None, - ) - .unwrap_err(); - assert!(matches!(err, sp_runtime::DispatchError::Other(_))); - }); - } - - #[test] - fn signals_empty_returns_dispatch_error_other() { - new_test_ext().execute_with(|| { - let err = - as ZkVerifierPort>::verify_disclosure_proof(&proof(), &[], None) - .unwrap_err(); - assert!(matches!(err, sp_runtime::DispatchError::Other(_))); - }); - } - - #[test] - fn signals_exactly_76_bytes_passes_decode() { - // Confirms decode succeeds. CircuitNotFound fires next (no VK), not a - // length error. - new_test_ext().execute_with(|| { - assert_err!( - as ZkVerifierPort>::verify_disclosure_proof( - &proof(), - &valid_signals(), - None, - ), - Error::::CircuitNotFound - ); - }); - } - // ── verify_transfer_proof ────────────────────────────────────────────────── #[test] @@ -608,263 +486,6 @@ mod tests { }); } - // ── verify_disclosure_proof ──────────────────────────────────────────────── - - #[test] - fn disclosure_empty_proof_is_rejected() { - // EmptyProof fires after successful signals decode, before VK lookup. - new_test_ext().execute_with(|| { - assert_err!( - as ZkVerifierPort>::verify_disclosure_proof( - &[], - &valid_signals(), - Some(1), - ), - Error::::EmptyProof - ); - }); - } - - #[test] - fn disclosure_no_active_version_returns_circuit_not_found() { - new_test_ext().execute_with(|| { - assert_err!( - as ZkVerifierPort>::verify_disclosure_proof( - &proof(), - &valid_signals(), - None, - ), - Error::::CircuitNotFound - ); - }); - } - - #[test] - fn disclosure_missing_vk_returns_not_found() { - new_test_ext().execute_with(|| { - assert_err!( - as ZkVerifierPort>::verify_disclosure_proof( - &proof(), - &valid_signals(), - Some(99), - ), - Error::::VerificationKeyNotFound - ); - }); - } - - #[test] - fn disclosure_happy_path_returns_true() { - new_test_ext().execute_with(|| { - insert_vk(CircuitId::DISCLOSURE, 1); - activate(CircuitId::DISCLOSURE, 1); - let ok = as ZkVerifierPort>::verify_disclosure_proof( - &proof(), - &valid_signals(), - None, - ) - .unwrap(); - assert!(ok); - }); - } - - #[test] - fn disclosure_explicit_version_overrides_active() { - new_test_ext().execute_with(|| { - insert_vk(CircuitId::DISCLOSURE, 1); - insert_vk(CircuitId::DISCLOSURE, 2); - activate(CircuitId::DISCLOSURE, 1); - let ok = as ZkVerifierPort>::verify_disclosure_proof( - &proof(), - &valid_signals(), - Some(2), - ) - .unwrap(); - assert!(ok); - }); - } - - // ── batch_verify_disclosure_proofs ───────────────────────────────────────── - - #[test] - fn batch_empty_proofs_returns_invalid_batch_size() { - new_test_ext().execute_with(|| { - assert_err!( - as ZkVerifierPort>::batch_verify_disclosure_proofs(&[], &[], None,), - Error::::InvalidBatchSize - ); - }); - } - - #[test] - fn batch_over_limit_returns_invalid_batch_size() { - new_test_ext().execute_with(|| { - let proofs: alloc::vec::Vec<_> = (0..11).map(|_| proof()).collect(); - let signals: alloc::vec::Vec<_> = (0..11).map(|_| valid_signals()).collect(); - assert_err!( - as ZkVerifierPort>::batch_verify_disclosure_proofs( - &proofs, &signals, None, - ), - Error::::InvalidBatchSize - ); - }); - } - - #[test] - fn batch_length_mismatch_returns_error() { - new_test_ext().execute_with(|| { - let proofs = alloc::vec![proof(), proof()]; - let signals = alloc::vec![valid_signals()]; // 2 vs 1 - assert_err!( - as ZkVerifierPort>::batch_verify_disclosure_proofs( - &proofs, &signals, None, - ), - Error::::BatchLengthMismatch - ); - }); - } - - #[test] - fn batch_no_active_version_returns_circuit_not_found() { - new_test_ext().execute_with(|| { - assert_err!( - as ZkVerifierPort>::batch_verify_disclosure_proofs( - &[proof()], - &[valid_signals()], - None, - ), - Error::::CircuitNotFound - ); - }); - } - - #[test] - fn batch_missing_vk_returns_not_found() { - new_test_ext().execute_with(|| { - assert_err!( - as ZkVerifierPort>::batch_verify_disclosure_proofs( - &[proof()], - &[valid_signals()], - Some(99), - ), - Error::::VerificationKeyNotFound - ); - }); - } - - #[test] - fn batch_invalid_signals_length_returns_dispatch_error_other() { - // decode_disclosure_signals runs during input collection, after VK is resolved. - new_test_ext().execute_with(|| { - insert_vk(CircuitId::DISCLOSURE, 1); - activate(CircuitId::DISCLOSURE, 1); - let err = as ZkVerifierPort>::batch_verify_disclosure_proofs( - &[proof()], - &[vec![0u8; 50]], - Some(1), - ) - .unwrap_err(); - assert!(matches!(err, sp_runtime::DispatchError::Other(_))); - }); - } - - #[test] - fn batch_at_exact_limit_of_ten_is_accepted() { - // Validates upper bound is inclusive (10 items is still valid). - // batch_verify calls real Groth16 crypto (not mocked in port.rs), - // so with bogus VK/proof data it returns BatchVerificationFailed — - // confirming all guard conditions were passed. - new_test_ext().execute_with(|| { - insert_vk(CircuitId::DISCLOSURE, 1); - activate(CircuitId::DISCLOSURE, 1); - let proofs: alloc::vec::Vec<_> = (0..10).map(|_| proof()).collect(); - let signals: alloc::vec::Vec<_> = (0..10).map(|_| valid_signals()).collect(); - assert_err!( - as ZkVerifierPort>::batch_verify_disclosure_proofs( - &proofs, - &signals, - Some(1), - ), - Error::::BatchVerificationFailed - ); - }); - } - - #[test] - fn batch_with_valid_setup_reaches_crypto_and_fails_on_bogus_data() { - // All storage guards pass; Groth16Verifier rejects the bogus VK/proof bytes. - new_test_ext().execute_with(|| { - insert_vk(CircuitId::DISCLOSURE, 1); - activate(CircuitId::DISCLOSURE, 1); - assert_err!( - as ZkVerifierPort>::batch_verify_disclosure_proofs( - &[proof()], - &[valid_signals()], - Some(1), - ), - Error::::BatchVerificationFailed - ); - }); - } - - // ── batch stats tracking ─────────────────────────────────────────────────── - - #[test] - fn batch_failure_increments_failed_and_total_stats() { - // Groth16Verifier rejects bogus data → stats must record the failure. - new_test_ext().execute_with(|| { - use crate::pallet::VerificationStats; - - insert_vk(CircuitId::DISCLOSURE, 1); - activate(CircuitId::DISCLOSURE, 1); - - let _ = as ZkVerifierPort>::batch_verify_disclosure_proofs( - &[proof(), proof()], - &[valid_signals(), valid_signals()], - Some(1), - ); - - let stats = VerificationStats::::get(CircuitId::DISCLOSURE, 1u32); - assert_eq!( - stats.total_verifications, 2, - "total should count batch size" - ); - assert_eq!(stats.failed_verifications, 2, "both proofs failed"); - assert_eq!(stats.successful_verifications, 0); - }); - } - - #[test] - fn batch_stats_are_isolated_from_single_verify_stats() { - // batch and single-proof stats accumulate independently into the same counter. - new_test_ext().execute_with(|| { - use crate::pallet::VerificationStats; - - insert_vk(CircuitId::DISCLOSURE, 1); - activate(CircuitId::DISCLOSURE, 1); - - // Single verify (succeeds in test build via do_verify mock). - let _ = as ZkVerifierPort>::verify_disclosure_proof( - &proof(), - &valid_signals(), - Some(1), - ); - - // Batch verify (fails with bogus data — stats still update). - let _ = as ZkVerifierPort>::batch_verify_disclosure_proofs( - &[proof()], - &[valid_signals()], - Some(1), - ); - - let stats = VerificationStats::::get(CircuitId::DISCLOSURE, 1u32); - // 1 from single (success) + 1 from batch (failure) - assert_eq!(stats.total_verifications, 2); - assert_eq!(stats.successful_verifications, 1); - assert_eq!(stats.failed_verifications, 1); - }); - } - // ── verify_private_link_proof ────────────────────────────────────────────── #[test] diff --git a/frame/zk-verifier/src/types.rs b/frame/zk-verifier/src/types.rs index 897f6465..23b4b2f8 100644 --- a/frame/zk-verifier/src/types.rs +++ b/frame/zk-verifier/src/types.rs @@ -31,10 +31,10 @@ impl CircuitId { pub const UNSHIELD: Self = Self(2); /// Shield circuit ID pub const SHIELD: Self = Self(3); - /// Disclosure circuit ID - pub const DISCLOSURE: Self = Self(4); /// Private link dispatch circuit ID pub const PRIVATE_LINK: Self = Self(5); + /// Value proof circuit ID — proves commitment encodes (value, asset_id) before fee insertion + pub const VALUE_PROOF: Self = Self(6); } /// Supported proof systems diff --git a/frame/zk-verifier/src/verifier.rs b/frame/zk-verifier/src/verifier.rs index 70114376..3329a765 100644 --- a/frame/zk-verifier/src/verifier.rs +++ b/frame/zk-verifier/src/verifier.rs @@ -213,7 +213,6 @@ mod tests { CircuitId::TRANSFER, CircuitId::UNSHIELD, CircuitId::SHIELD, - CircuitId::DISCLOSURE, CircuitId::PRIVATE_LINK, ] { insert_vk(cid, 1); diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index 7ce330af..8e66ac26 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -599,8 +599,6 @@ impl pallet_shielded_pool::Config for Runtime { /// Minimum shield amount: prevents spam, 1 ORB = 1e18 wei type MinShieldAmount = ConstU128<1_000_000_000_000_000_000>; type WeightInfo = pallet_shielded_pool::weights::SubstrateWeight; - /// Disclosure requests expire after 14400 blocks (~1 day at 6s/block) - type RequestExpiration = ConstU32<14400>; } // Create the runtime by composing the FRAME pallets that were previously configured. From a531f41dd86f2c307bdbba5adc6d6a91eeef580d Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 13:32:12 -0400 Subject: [PATCH 05/18] Refactor shielded pool operations: remove audit and disclosure functionality --- frame/shielded-pool/CHANGELOG.md | 38 ++ frame/shielded-pool/Cargo.toml | 2 +- frame/shielded-pool/README.md | 24 +- frame/shielded-pool/src/benchmarking.rs | 215 +------ frame/shielded-pool/src/helpers.rs | 42 +- frame/shielded-pool/src/lib.rs | 439 +-------------- frame/shielded-pool/src/mock.rs | 31 +- .../src/operations/disclosure/batch_submit.rs | 410 -------------- .../src/operations/disclosure/mod.rs | 60 -- .../src/operations/disclosure/policy.rs | 204 ------- .../src/operations/disclosure/record.rs | 172 ------ .../src/operations/disclosure/request.rs | 269 --------- .../src/operations/disclosure/submit.rs | 427 -------------- .../src/operations/disclosure/validation.rs | 524 ------------------ frame/shielded-pool/src/operations/fees.rs | 332 +---------- frame/shielded-pool/src/operations/mod.rs | 1 - frame/shielded-pool/src/storage.rs | 358 +----------- frame/shielded-pool/src/types.rs | 114 ---- frame/shielded-pool/src/weights.rs | 63 --- 19 files changed, 82 insertions(+), 3643 deletions(-) delete mode 100644 frame/shielded-pool/src/operations/disclosure/batch_submit.rs delete mode 100644 frame/shielded-pool/src/operations/disclosure/mod.rs delete mode 100644 frame/shielded-pool/src/operations/disclosure/policy.rs delete mode 100644 frame/shielded-pool/src/operations/disclosure/record.rs delete mode 100644 frame/shielded-pool/src/operations/disclosure/request.rs delete mode 100644 frame/shielded-pool/src/operations/disclosure/submit.rs delete mode 100644 frame/shielded-pool/src/operations/disclosure/validation.rs diff --git a/frame/shielded-pool/CHANGELOG.md b/frame/shielded-pool/CHANGELOG.md index 50aa374a..cf910650 100644 --- a/frame/shielded-pool/CHANGELOG.md +++ b/frame/shielded-pool/CHANGELOG.md @@ -2,6 +2,44 @@ All notable changes to `pallet-shielded-pool` will be documented in this file. +## [0.8.0] - 2026-05-14 + +### Added +- **`claim_shielded_fees` extrinsic** — validators claim accrued relay fees as a private shielded note. + Requires a Groth16 `value_proof` (CircuitId 6) proving that the supplied commitment encodes exactly + `(amount, asset_id, owner_pubkey, blinding)` via Poseidon4. Prevents fee inflation attacks where a + relayer could craft a commitment encoding a larger value and later drain the pool via `unshield`. + Public signals layout (76 bytes): `commitment(32) | value(8) | asset_id(4) | owner_hash(32)`. +- **`claim_relay_fees_to_evm` extrinsic** — signed extrinsic that transfers accrued relay fees to the + relayer's registered H160 EVM address. +- **`verify_asset` / `unverify_asset` extrinsics** (Root origin) — mark or unmark a registered asset as + verified, enabling or disabling shielding for that asset. +- **`operations/` module structure** — business logic split into dedicated sub-modules: + `shield.rs`, `private_transfer.rs`, `unshield.rs`, `fees.rs`, `assets.rs`. +- **`runtime_api_impl.rs`** — Runtime API implementations (Merkle proofs, tree info) extracted from `lib.rs`. +- **`pallet-relayer` integration** — relay fee accounting via `RelayerInterface` trait + (`accumulate_relay_fee`, `consume_relay_fee`). +- **`verify_value_proof` mock** in `mock.rs` for unit testing `claim_shielded_fees` without on-chain VK. +- **`claim_shielded_fees` weight** added to `WeightInfo` trait and both substrate/rocks implementations. + +### Removed +- **Selective disclosure subsystem** — all disclosure extrinsics, storage, and types removed: + - Extrinsics: `set_audit_policy`, `request_disclosure`, `disclose`, `reject_disclosure`, + `batch_submit_disclosure_proofs`, `prune_expired_request`, `revoke_disclosure_record`. + - Storage: `AuditPolicies`, `DisclosureRequests`, `DisclosureRecords`, `AuditTrailStorage`, + `NextAuditTrailId`, `LastDisclosureTimestamp`, `DisclosureCounters`. + - Types: `AuditTrail`, `DisclosureRecord`, `DisclosureRequest`, `Auditor`, `DisclosureCondition`. + - Weight entries: all disclosure-related benchmark weights removed. + - `operations/disclosure/` module directory entirely removed. + +### Changed +- **README**: updated extrinsic table — `disclose` row replaced by `claim_shielded_fees` and + `claim_relay_fees_to_evm`; removed disclosure storage rows; updated status note. +- **README**: `operations.rs` monolith replaced by `operations/` module directory listing. +- **`helpers.rs`**: storage imports cleaned up — `DisclosureRecords`, `DisclosureRequests` and + other disclosure-related storage removed from `mock.rs` and helpers. +- **`benchmarking.rs`**: disclosure benchmarks removed; `claim_shielded_fees` benchmark retained. + ## [0.7.0] - 2026-05-08 ### Added diff --git a/frame/shielded-pool/Cargo.toml b/frame/shielded-pool/Cargo.toml index 3aa24b41..53b59da8 100644 --- a/frame/shielded-pool/Cargo.toml +++ b/frame/shielded-pool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-shielded-pool" -version = "0.7.0" +version = "0.8.0" description = "Shielded pool pallet for private transactions using ZK proofs" authors = ["Orbinum Team"] license = "GPL-3.0-or-later" diff --git a/frame/shielded-pool/README.md b/frame/shielded-pool/README.md index a1c0d654..abac7e3f 100644 --- a/frame/shielded-pool/README.md +++ b/frame/shielded-pool/README.md @@ -4,7 +4,7 @@ FRAME pallet for privacy-preserving transactions in Orbinum using ZK-SNARKs. ## Status -MVP in active development. Core shield / transfer / unshield flows are functional, including partial unshield with automatic change note handling. Audit and disclosure features are present but under active review. +MVP in active development. Core shield / transfer / unshield flows are functional, including partial unshield with automatic change note handling. Relay fee claiming uses the `value_proof` circuit. ## What this pallet does @@ -35,8 +35,11 @@ This ensures change note commitments are **not linkable** to other notes belongi | `shield_batch` | Signed | Deposit and insert multiple commitments in one call | | `private_transfer` | Unsigned | ZK-proven private transfer between notes | | `unshield` | Unsigned | ZK-proven withdrawal to a public account. Accepts a `change_commitment` and `change_encrypted_memo` for partial unshield with stealth change notes | -| `disclose` | Signed | Selective disclosure of a note to an auditor | +| `claim_shielded_fees` | Unsigned | Claim accrued relay fees as a private ZK note (value_proof circuit) | +| `claim_relay_fees_to_evm` | Signed | Transfer accrued relay fees to the relayer's H160 EVM address | | `register_asset` | Signed | Register a new asset for multi-asset support | +| `verify_asset` | Root | Mark a registered asset as verified (enables shielding) | +| `unverify_asset` | Root | Remove verified status from an asset | ## Storage @@ -49,16 +52,9 @@ This ensures change note commitments are **not linkable** to other notes belongi | `HistoricPoseidonRoots` | Past roots (accepted for proofs) | | `HistoricRootsOrder` | Bounded ordered list of historic roots | | `CommitmentMemos` | Encrypted memos per commitment | -| `AuditPolicies` | Per-account audit policies | -| `DisclosureRequests` | Pending disclosure requests by `(target, auditor)` | -| `DisclosureRecords` | Completed disclosures by `(who, commitment)` | -| `AuditTrailStorage` | Full audit trail entries | -| `NextAuditTrailId` | Auto-increment for audit trail entries | | `Assets` | Registered asset metadata | | `NextAssetId` | Auto-increment for asset IDs | | `PoolBalancePerAsset` | Total shielded balance per asset | -| `LastDisclosureTimestamp` | Rate-limiting per `(who, auditor)` | -| `DisclosureCounters` | Disclosure count per `(who, auditor)` | ## Module layout @@ -67,11 +63,18 @@ src/ lib.rs — Config, Storage, Events, Errors, extrinsics types.rs — Commitment, Nullifier, Hash, EncryptedMemo and aliases merkle.rs — Poseidon Merkle tree insertion and root update - operations.rs — Proof verification dispatch and business logic helpers + operations/ + mod.rs — module declarations + shield.rs — shield / shield_batch logic + private_transfer.rs — private transfer proof dispatch + unshield.rs — unshield logic (partial + full) + fees.rs — relay fee claiming (claim_shielded_fees, claim_relay_fees_to_evm) + assets.rs — register / verify / unverify asset logic storage.rs — Storage helper functions (nullifier checks, root lookups) helpers.rs — Miscellaneous internal helpers genesis.rs — GenesisConfig and BuildGenesisConfig impl validate_unsigned.rs — ValidateUnsigned impl for unsigned extrinsics + runtime_api_impl.rs — Runtime API implementations (Merkle proofs, tree info) benchmarking.rs — FRAME benchmarks weights.rs — WeightInfo trait and generated weights ``` @@ -97,6 +100,7 @@ These are design properties of the current MVP. No formal security audit has bee ## Dependencies - `pallet-zk-verifier`: proof verification via `ZkVerifierPort`. +- `pallet-relayer`: relay fee accounting via `RelayerInterface`. - `orbinum-zk-core`: Poseidon hash, commitment and nullifier types. - FRAME: `frame-support`, `frame-system`, `sp-runtime`. diff --git a/frame/shielded-pool/src/benchmarking.rs b/frame/shielded-pool/src/benchmarking.rs index 07dec7b1..26212f03 100644 --- a/frame/shielded-pool/src/benchmarking.rs +++ b/frame/shielded-pool/src/benchmarking.rs @@ -22,12 +22,10 @@ use alloc::vec; )] mod benchmarks { use super::*; + use crate::FrameEncryptedMemo; use crate::pallet::{ - Assets, CommitmentMemos, DisclosureRecords, DisclosureRequests, HistoricPoseidonRoots, - NextAssetId, PoolBalancePerAsset, + Assets, CommitmentMemos, HistoricPoseidonRoots, NextAssetId, PoolBalancePerAsset, }; - use crate::{Auditor, DisclosureCondition}; - use crate::{DisclosureRequest, FrameEncryptedMemo}; use sp_std::vec::Vec; fn setup_benchmark_env() -> (T::AccountId, u32) { @@ -169,215 +167,6 @@ mod benchmarks { ); } - #[benchmark] - fn set_audit_policy() { - let caller: T::AccountId = whitelisted_caller(); - let auditor: T::AccountId = account("auditor", 0, 0); - let auditors = vec![Auditor::Account(auditor)].try_into().unwrap(); - let conditions = vec![DisclosureCondition::AmountThreshold { - min_amount: 1000u32.into(), - }] - .try_into() - .unwrap(); - - #[extrinsic_call] - set_audit_policy( - RawOrigin::Signed(caller), - auditors, - conditions, - Some(100u32.into()), - None, - ); - } - - #[benchmark] - fn request_disclosure() { - let target: T::AccountId = account("target", 0, 0); - let auditor: T::AccountId = whitelisted_caller(); - let reason = vec![1u8; 100].try_into().unwrap(); - - // Setup: Create audit policy for the auditor - let auditors = vec![Auditor::Account(auditor.clone())].try_into().unwrap(); - let conditions = vec![DisclosureCondition::AmountThreshold { - min_amount: 1000u32.into(), - }] - .try_into() - .unwrap(); - let _ = Pallet::::set_audit_policy( - RawOrigin::Signed(target.clone()).into(), - auditors, - conditions, - Some(100u32.into()), - None, - ); - - #[extrinsic_call] - request_disclosure(RawOrigin::Signed(auditor), target, reason); - } - - #[benchmark] - fn disclose() { - let target: T::AccountId = whitelisted_caller(); - let auditor: T::AccountId = account("auditor", 0, 0); - let commitment = Commitment([42u8; 32]); - - // Insert commitment memo (required by CommitmentNotFound check) - let memo_bytes = vec![0u8; MAX_ENCRYPTED_MEMO_SIZE as usize]; - CommitmentMemos::::insert( - commitment, - FrameEncryptedMemo(memo_bytes.try_into().unwrap()), - ); - - // Set up audit policy with Always condition (worst case: full policy validation) - let auditors = vec![Auditor::Account(auditor.clone())].try_into().unwrap(); - let conditions = vec![DisclosureCondition::Always].try_into().unwrap(); - let _ = Pallet::::set_audit_policy( - RawOrigin::Signed(target.clone()).into(), - auditors, - conditions, - Some(100u32.into()), - None, - ); - - // Insert a non-expired DisclosureRequest - DisclosureRequests::::insert( - &target, - &auditor, - DisclosureRequest { - target: target.clone(), - auditor: auditor.clone(), - reason: vec![1u8; 32].try_into().unwrap(), - requested_at: 0u32.into(), - expires_at: frame_system::Pallet::::block_number() + T::RequestExpiration::get(), - }, - ); - - // Groth16 BN254 compressed proof = 128 bytes - let proof_bytes: BoundedVec> = vec![1u8; 128].try_into().unwrap(); - // public_signals (76 bytes): commitment(32) + revealed_value(8) + - // revealed_asset_id(4) + revealed_owner_hash(32) - let mut signals = vec![0u8; 76]; - signals[0..32].copy_from_slice(&commitment.0); - let public_signals: BoundedVec> = signals.try_into().unwrap(); - - #[extrinsic_call] - disclose( - RawOrigin::Signed(target), - commitment, - proof_bytes, - public_signals, - Some(auditor), - ); - } - - #[benchmark] - fn reject_disclosure() { - let target: T::AccountId = whitelisted_caller(); - let auditor: T::AccountId = account("auditor", 0, 0); - let reason = vec![1u8; 100].try_into().unwrap(); - - // Setup request in storage - crate::pallet::DisclosureRequests::::insert( - &target, - &auditor, - DisclosureRequest { - target: target.clone(), - auditor: auditor.clone(), - reason: vec![1u8; 32].try_into().unwrap(), - requested_at: frame_system::Pallet::::block_number(), - expires_at: frame_system::Pallet::::block_number() + T::RequestExpiration::get(), - }, - ); - - #[extrinsic_call] - reject_disclosure(RawOrigin::Signed(target), auditor, reason); - } - - #[benchmark] - fn batch_submit_disclosure_proofs(n: Linear<1, 10>) { - let caller: T::AccountId = whitelisted_caller(); - - // No AuditPolicy → auditor must be None (self-disclosure path) - let mut submissions = Vec::new(); - for i in 0..n { - // Use i+1 so commitment bytes are never all-zero - let commitment = Commitment([i as u8 + 1; 32]); - - // Insert commitment memo (required by CommitmentNotFound check) - CommitmentMemos::::insert( - commitment, - FrameEncryptedMemo( - vec![0u8; MAX_ENCRYPTED_MEMO_SIZE as usize] - .try_into() - .unwrap(), - ), - ); - - // public_signals (76 bytes): first 32 must match commitment - let mut signals = vec![0u8; 76]; - signals[0..32].copy_from_slice(&commitment.0); - - submissions.push(crate::BatchDisclosureSubmission { - commitment, - // 128-byte proof satisfies MockZkVerifier (non-empty check) - proof: vec![1u8; 128].try_into().unwrap(), - public_signals: signals.try_into().unwrap(), - auditor: None, - }); - } - let submissions_vec: BoundedVec<_, ConstU32<10>> = submissions.try_into().unwrap(); - - #[extrinsic_call] - batch_submit_disclosure_proofs(RawOrigin::Signed(caller), submissions_vec); - } - - #[benchmark] - fn prune_expired_request() { - let pruner: T::AccountId = whitelisted_caller(); - let target: T::AccountId = account("target", 0, 0); - let auditor: T::AccountId = account("auditor", 0, 0); - - // Insert a request that is already expired (expires_at = block 1, current = 10) - crate::pallet::DisclosureRequests::::insert( - &target, - &auditor, - DisclosureRequest { - target: target.clone(), - auditor: auditor.clone(), - reason: vec![1u8; 32].try_into().unwrap(), - requested_at: 0u32.into(), - expires_at: 1u32.into(), - }, - ); - // Advance block so current_block > expires_at - frame_system::Pallet::::set_block_number(10u32.into()); - - #[extrinsic_call] - prune_expired_request(RawOrigin::Signed(pruner), target, auditor); - } - - #[benchmark] - fn revoke_disclosure_record() { - let caller: T::AccountId = whitelisted_caller(); - let commitment = Commitment([99u8; 32]); - - // Insert a self-disclosure record so the caller can revoke it - DisclosureRecords::::insert( - commitment, - &caller, - crate::DisclosureRecord { - revealed_value: None, - revealed_asset_id: None, - revealed_owner_hash: None, - requester: caller.clone(), - timestamp: frame_system::Pallet::::block_number(), - }, - ); - - #[extrinsic_call] - revoke_disclosure_record(RawOrigin::Signed(caller), commitment); - } - #[benchmark] fn register_asset() { let name: BoundedVec> = vec![1u8; 32].try_into().unwrap(); diff --git a/frame/shielded-pool/src/helpers.rs b/frame/shielded-pool/src/helpers.rs index 4d713dc2..1b2d8fea 100644 --- a/frame/shielded-pool/src/helpers.rs +++ b/frame/shielded-pool/src/helpers.rs @@ -7,14 +7,10 @@ use crate::{ Pallet, merkle::MerkleTreeService, - operations::disclosure::validation::DisclosureValidationService, pallet::Config, types::{Commitment, DefaultMerklePath, Hash}, }; -use frame_support::{ - pallet_prelude::{DispatchError, DispatchResult}, - traits::Get, -}; +use frame_support::{pallet_prelude::DispatchError, traits::Get}; use sp_runtime::traits::AccountIdConversion; impl Pallet { @@ -44,42 +40,6 @@ impl Pallet { pub fn get_leaf_index(commitment: &Commitment) -> Option { MerkleTreeService::find_leaf_index::(commitment) } - - // ── Disclosure proof verification ──────────────────────────────────────── - - /// Verify a raw Groth16 disclosure proof (128-byte proof, 76-byte signals). - pub fn verify_disclosure_proof_internal( - proof_bytes: &[u8], - public_signals: &[u8], - ) -> DispatchResult { - DisclosureValidationService::verify_proof_internal::(proof_bytes, public_signals) - } - - /// Validate public signals consistency against an on-chain commitment. - pub fn validate_public_signals( - commitment: &Commitment, - public_signals: &[u8], - ) -> DispatchResult { - DisclosureValidationService::validate_public_signals::(commitment, public_signals) - } - - /// Validate disclosure access control and rate limiting for `who`. - pub fn validate_disclosure_access( - who: &T::AccountId, - commitment: &Commitment, - auditor: Option<&T::AccountId>, - ) -> DispatchResult { - DisclosureValidationService::validate_disclosure_access::(who, commitment, auditor) - } - - /// Full cryptographic verification of a disclosure ZK proof with context. - pub fn verify_disclosure_proof( - proof: &[u8], - public_signals: &[u8], - commitment: &Commitment, - ) -> Result<(), DispatchError> { - DisclosureValidationService::verify_disclosure_proof::(proof, public_signals, commitment) - } } #[cfg(test)] diff --git a/frame/shielded-pool/src/lib.rs b/frame/shielded-pool/src/lib.rs index 3fbeb869..ba1dbbbb 100644 --- a/frame/shielded-pool/src/lib.rs +++ b/frame/shielded-pool/src/lib.rs @@ -80,8 +80,7 @@ mod runtime_api_impl; // Re-export types for external use pub use types::{ - AssetId, AssetMetadata, AuditPolicy, AuditTrail, Auditor, Commitment, DEFAULT_TREE_DEPTH, - DefaultMerklePath, DisclosureCondition, DisclosureRecord, DisclosureRequest, + AssetId, AssetMetadata, Commitment, DEFAULT_TREE_DEPTH, DefaultMerklePath, EncryptedMemo as FrameEncryptedMemo, Hash, MAX_ENCRYPTED_MEMO_SIZE, MAX_TREE_DEPTH, MerklePath, Note, Nullifier, }; @@ -97,7 +96,6 @@ pub mod pallet { }; use frame_system::pallet_prelude::*; use pallet_zk_verifier::ZkVerifierPort; - use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen}; /// The balance type for this pallet pub type BalanceOf = @@ -106,29 +104,6 @@ pub mod pallet { #[pallet::pallet] pub struct Pallet(_); - /// Input data for a batch disclosure submission - #[derive( - Clone, - Encode, - Decode, - DecodeWithMemTracking, - TypeInfo, - PartialEq, - RuntimeDebug, - MaxEncodedLen - )] - pub struct BatchDisclosureSubmission { - /// The commitment being disclosed - pub commitment: Commitment, - /// ZK proof (Groth16, max 256 bytes) - pub proof: BoundedVec>, - /// Public signals (76 bytes): [commitment(32)][value(8)][asset_id(4)][owner_hash(32)] - pub public_signals: BoundedVec>, - /// Optional auditor. `Some` → Flujo B (requires active DisclosureRequest). - /// `None` → Flujo A (self-disclosure). - pub auditor: Option, - } - /// Configuration trait for the pallet #[pallet::config] pub trait Config: frame_system::Config>> { @@ -161,10 +136,6 @@ pub mod pallet { type MinShieldAmount: Get>; /// Weight information for extrinsics in this pallet type WeightInfo: WeightInfo; - - /// Number of blocks after which a disclosure request expires - #[pallet::constant] - type RequestExpiration: Get>; } // ======================================================================== @@ -221,78 +192,12 @@ pub mod pallet { /// Encrypted memos for commitments /// /// Maps each commitment to its associated encrypted memo. - /// Memos enable: - /// - Note recovery by scanning blockchain - /// - Selective disclosure to authorized auditors - /// - FATF Travel Rule compliance - /// + /// Memos enable note recovery by scanning the blockchain. /// Only the note owner (with the correct decryption key) can decrypt the memo. #[pallet::storage] pub type CommitmentMemos = StorageMap<_, Blake2_128Concat, Commitment, FrameEncryptedMemo, OptionQuery>; - // ======================================================================== - // Audit Policies Storage - // ======================================================================== - - /// Audit policies defined by users - /// - /// Maps account to their audit policy defining disclosure rules - #[pallet::storage] - pub type AuditPolicies = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - AuditPolicy, BlockNumberFor>, - OptionQuery, - >; - - /// Pending disclosure requests - /// - /// Maps (target_account, auditor, request_id) to disclosure request - #[pallet::storage] - pub type DisclosureRequests = StorageDoubleMap< - _, - Blake2_128Concat, - T::AccountId, // target account - Blake2_128Concat, - T::AccountId, // auditor - DisclosureRequest>, - OptionQuery, - >; - - /// Disclosure records (parsed results of verified ZK disclosures) - /// - /// Double-map: (commitment, key) → DisclosureRecord - /// - Flujo A (self): key = note owner - /// - Flujo B (audited): key = auditor, record.requester = target - #[pallet::storage] - pub type DisclosureRecords = StorageDoubleMap< - _, - Blake2_128Concat, - Commitment, - Blake2_128Concat, - T::AccountId, - DisclosureRecord>, - OptionQuery, - >; - - /// Audit trail for compliance - /// - /// Stores all disclosure events for regulatory compliance - #[pallet::storage] - pub type AuditTrailStorage = StorageMap< - _, - Blake2_128Concat, - Hash, // audit trail hash - AuditTrail>, - OptionQuery, - >; - - /// Next audit trail ID for generating unique hashes - #[pallet::storage] - pub type NextAuditTrailId = StorageValue<_, u64, ValueQuery>; - // ======================================================================== // Multi-Asset Support Storage // ======================================================================== @@ -326,37 +231,6 @@ pub mod pallet { ValueQuery, >; - /// Last disclosure timestamp for rate limiting - /// - /// Maps (account, commitment) to block number of last disclosure - /// Used to enforce max_frequency from AuditPolicy - #[pallet::storage] - pub type LastDisclosureTimestamp = StorageDoubleMap< - _, - Blake2_128Concat, - T::AccountId, // account owner - Blake2_128Concat, - Commitment, - BlockNumberFor, - OptionQuery, - >; - - /// Disclosure counters for O(1) rate limiting - /// - /// Maps (target_account, auditor) to the total number of completed disclosures. - /// Incremented on approve_disclosure and submit_disclosure (with auditor). - /// Replaces the O(n) AuditTrailStorage::iter() scan in request_disclosure. - #[pallet::storage] - pub type DisclosureCounters = StorageDoubleMap< - _, - Blake2_128Concat, - T::AccountId, // target account - Blake2_128Concat, - T::AccountId, // auditor - u32, - ValueQuery, - >; - /// Total number of commitments ever inserted into the Merkle tree. /// /// Monotonically increasing counter. Incremented once per successful @@ -459,60 +333,6 @@ pub mod pallet { tree_size: u32, }, - /// Audit policy was set or updated - AuditPolicySet { - /// Account that set the policy - account: T::AccountId, - /// Policy version - version: u32, - }, - - /// A selective disclosure was successfully verified and recorded - Disclosed { - /// Account that submitted the disclosure (note owner) - who: T::AccountId, - /// Commitment whose note was disclosed - commitment: Commitment, - /// Auditor when Flujo B; `None` for self-disclosure (Flujo A) - auditor: Option, - }, - - /// Disclosure request was submitted by auditor - DisclosureRequested { - /// Target account to audit - target: T::AccountId, - /// Auditor making the request - auditor: T::AccountId, - /// Request reason - reason: BoundedVec>, - }, - - /// Disclosure request was rejected - DisclosureRejected { - /// Target account - target: T::AccountId, - /// Auditor - auditor: T::AccountId, - /// Reason for rejection - reason: BoundedVec>, - }, - - /// An expired disclosure request was pruned - DisclosureRequestExpired { - /// Target account (the account that received the request) - target: T::AccountId, - /// Auditor that submitted the request - auditor: T::AccountId, - }, - - /// A disclosure record was revoked by its creator (Flujo A only) - DisclosureRecordRevoked { - /// Account that revoked the record - who: T::AccountId, - /// Commitment whose record was revoked - commitment: Commitment, - }, - /// Asset was registered in the registry AssetRegistered { /// The asset ID @@ -544,18 +364,6 @@ pub mod pallet { /// Leaf index of the new note leaf_index: u32, }, - - /// Relay fees were claimed and sent directly to the relayer's EVM account - RelayFeesClaimedToEvm { - /// The validator substrate account that claimed - validator: T::AccountId, - /// The EVM address that received the funds - evm_address: sp_core::H160, - /// Asset ID of the fees claimed - asset_id: u32, - /// Amount transferred to the EVM mirror account - amount: BalanceOf, - }, } // ======================================================================== @@ -588,12 +396,6 @@ pub mod pallet { InvalidMemoSize, /// Mismatch between number of memos and commitments MemoCommitmentMismatch, - /// Audit policy not found - AuditPolicyNotFound, - /// Auditor not authorized - AuditorNotAuthorized, - /// Disclosure conditions not met - DisclosureConditionsNotMet, /// Asset ID does not exist in the registry InvalidAssetId, /// Asset is not verified for use @@ -604,50 +406,12 @@ pub mod pallet { InvalidRecipient, /// Gasless fee is below the required minimum FeeTooLow, - /// Disclosure request already exists - DisclosureRequestAlreadyExists, - /// Disclosure request not found - DisclosureRequestNotFound, - /// Invalid disclosure record (ZK proof failed or data inconsistent) - InvalidDisclosureRecord, - /// Audit policy version mismatch - AuditPolicyVersionMismatch, /// Invalid public signals (length or consistency) InvalidPublicSignals, - /// Invalid disclosure mask (blinding revealed or no fields disclosed) - InvalidDisclosureMask, /// Commitment not found on-chain CommitmentNotFound, - /// Auditor not authorized in policy - UnauthorizedAuditor, - /// Disclosure frequency limit exceeded - DisclosureFrequencyExceeded, - /// Too many auditors in policy - TooManyAuditors, - /// Too many conditions in policy - TooManyConditions, - /// Too many disclosure requests - TooManyDisclosureRequests, - /// No auditors provided (at least one required) - NoAuditorsProvided, - /// Disclosure record already exists for this (commitment, key) pair - DisclosureRecordAlreadyExists, - /// Duplicate auditor entry in policy (each auditor must appear at most once) - DuplicateAuditor, - /// Disclosure request has expired and can no longer be fulfilled - DisclosureRequestExpired, - /// Disclosure request has not yet expired and cannot be pruned - DisclosureRequestNotExpired, - /// Audit policy has expired (valid_until block has passed) - AuditPolicyExpired, - /// Disclosure record not found for the given commitment - DisclosureRecordNotFound, - /// Caller is not the owner of the disclosure record - NotRecordOwner, /// Pending validator fees are less than the requested claim amount InsufficientPendingFees, - /// Caller has no EVM address registered in the relayer registry - RelayerNotRegistered, } // ======================================================================== @@ -660,7 +424,7 @@ pub mod pallet { /// /// This converts public tokens into a private note represented by a commitment. /// The commitment is added to the Merkle tree, and an encrypted memo is stored - /// for note recovery and selective disclosure. + /// for note recovery. /// /// # Arguments /// * `origin` - The account depositing tokens @@ -861,111 +625,6 @@ pub mod pallet { ) } - /// Set or update audit policy for selective disclosure - #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::set_audit_policy())] - pub fn set_audit_policy( - origin: OriginFor, - auditors: BoundedVec, ConstU32<10>>, - conditions: BoundedVec< - DisclosureCondition, BlockNumberFor>, - ConstU32<10>, - >, - max_frequency: Option>, - valid_until: Option>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - crate::operations::disclosure::DisclosureOperation::set_audit_policy::( - &who, - auditors, - conditions, - max_frequency, - valid_until, - ) - } - - /// Request disclosure from a target account - #[pallet::call_index(5)] - #[pallet::weight(T::WeightInfo::request_disclosure())] - pub fn request_disclosure( - origin: OriginFor, - target: T::AccountId, - reason: BoundedVec>, - ) -> DispatchResult { - let auditor = ensure_signed(origin)?; - - crate::operations::disclosure::DisclosureOperation::request_disclosure::( - &auditor, &target, reason, - ) - } - - /// Submit a selective disclosure proof for a specific commitment. - /// - /// Unifies Flujo A (self-disclosure) and Flujo B (audited) into a single - /// extrinsic. The `auditor` parameter selects the flow: - /// - /// - `None` → **Flujo A**: self-disclosure. Optional AuditPolicy is checked. - /// Record stored at `(commitment, who)` and revocable by the owner. - /// - `Some(auditor)` → **Flujo B**: audited disclosure. Requires an active - /// `DisclosureRequest` and matching `AuditPolicy`. Record stored at - /// `(commitment, auditor)` and permanent (not revocable). - /// - /// # SAFETY - /// Commitment ownership is enforced by the ZK proof: the Groth16 circuit - /// reconstructs `commitment` from private inputs, so a passing proof can only - /// have been generated by the note owner. - #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::disclose())] - pub fn disclose( - origin: OriginFor, - commitment: Commitment, - proof_bytes: BoundedVec>, - // public_signals (76 bytes): [commitment(32)][value(8)][asset_id(4)][owner_hash(32)] - public_signals: BoundedVec>, - auditor: Option, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - crate::operations::disclosure::DisclosureOperation::disclose::( - &who, - commitment, - proof_bytes, - public_signals, - auditor.as_ref(), - ) - } - - /// Reject disclosure request - #[pallet::call_index(7)] - #[pallet::weight(T::WeightInfo::reject_disclosure())] - pub fn reject_disclosure( - origin: OriginFor, - auditor: T::AccountId, - reason: BoundedVec>, - ) -> DispatchResult { - let target = ensure_signed(origin)?; - - crate::operations::disclosure::DisclosureOperation::reject_disclosure::( - &target, &auditor, reason, - ) - } - - #[pallet::call_index(13)] - #[pallet::weight(T::WeightInfo::batch_submit_disclosure_proofs(submissions.len() as u32))] - pub fn batch_submit_disclosure_proofs( - origin: OriginFor, - submissions: BoundedVec, ConstU32<10>>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - crate::operations::disclosure::DisclosureOperation::batch_submit_proofs::( - &who, - submissions, - ) - } - - /// Register a new asset for use in the shielded pool /// /// Allows governance to register new assets that can be privately transferred. /// Assets must be verified before they can be used in shield/unshield operations. @@ -1049,67 +708,11 @@ pub mod pallet { crate::operations::assets::AssetOperation::unverify::(asset_id) } - /// Remove an expired disclosure request from storage. - /// - /// Permissionless cleanup: any account can prune a request whose `expires_at` - /// block has passed. This prevents storage bloat without requiring an - /// O(n) on_initialize hook. - /// - /// # Errors - /// * `DisclosureRequestNotFound` - No request for (target, auditor) - /// * `DisclosureRequestNotExpired` - `expires_at` has not passed yet - #[pallet::call_index(14)] - #[pallet::weight(T::WeightInfo::prune_expired_request())] - pub fn prune_expired_request( - origin: OriginFor, - target: T::AccountId, - auditor: T::AccountId, - ) -> DispatchResult { - let _ = ensure_signed(origin)?; - - let request = DisclosureRequests::::get(&target, &auditor) - .ok_or(Error::::DisclosureRequestNotFound)?; - let current_block = frame_system::Pallet::::block_number(); - ensure!( - current_block > request.expires_at, - Error::::DisclosureRequestNotExpired - ); - DisclosureRequests::::remove(&target, &auditor); - Self::deposit_event(Event::DisclosureRequestExpired { target, auditor }); - Ok(()) - } - - /// Revoke a Flujo A (self-disclosure) record created by the caller. - /// - /// Removes the `DisclosureRecord` stored at `(commitment, caller)`. Only - /// applies to self-disclosures — Flujo B records (stored under the auditor - /// key) are permanent once approved. - /// - /// # Errors - /// * `DisclosureRecordNotFound` - No self-disclosure record for `commitment` - #[pallet::call_index(15)] - #[pallet::weight(T::WeightInfo::revoke_disclosure_record())] - pub fn revoke_disclosure_record( - origin: OriginFor, - commitment: Commitment, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - ensure!( - DisclosureRecords::::contains_key(commitment, &who), - Error::::DisclosureRecordNotFound - ); - DisclosureRecords::::remove(commitment, &who); - Self::deposit_event(Event::DisclosureRecordRevoked { who, commitment }); - Ok(()) - } - /// Claim accumulated validator fees as a private shielded note. /// /// Converts pending relay fee credits into a Merkle tree commitment. - /// A selective-disclosure ZK proof (disclosure circuit) must be supplied - /// proving that `commitment` encodes exactly `amount` and `asset_id`. - /// This closes the KNOWN LIMITATION that existed in the previous design. + /// A value_proof ZK proof must be supplied, proving that `commitment` + /// encodes exactly `amount` and `asset_id`. /// /// # Errors /// * `InvalidProof` - ZK proof verification failed or wrong length (expected 128 bytes) @@ -1140,38 +743,6 @@ pub mod pallet { public_signals, ) } - - /// Claim accumulated relay fees and transfer them directly to the - /// relayer's EVM account. - /// - /// Converts the pending relay-fee balance for (`origin`, `asset_id`) into - /// real tokens sent to the H160 EVM mirror account - /// (`H160[0..20] ++ [0x00; 12]`). The EVM sees the balance immediately — - /// no ZK proof or `unshield` step required. - /// - /// This is the preferred path for gasless relayers: the H160 that pays - /// EVM gas fees is automatically refunded without any manual token bridging. - /// - /// # Arguments - /// * `origin` - Signed validator/relayer (sr25519 / Aura key) - /// * `asset_id` - Asset whose pending fees to claim - /// * `amount` - Amount to transfer (must be ≤ pending balance) - /// - /// # Errors - /// * `InvalidAssetId` - Asset is not registered - /// * `RelayerNotRegistered` - Caller has no H160 in the relayer registry - /// * `InsufficientPendingFees` - Pending balance < `amount` - /// * `InsufficientPoolBalance` - Pool lacks tokens (invariant violation) - #[pallet::call_index(17)] - #[pallet::weight(T::WeightInfo::claim_shielded_fees())] - pub fn claim_relay_fees_to_evm( - origin: OriginFor, - asset_id: u32, - amount: BalanceOf, - ) -> DispatchResult { - let validator = ensure_signed(origin)?; - crate::operations::fees::FeeOperation::claim_to_evm::(validator, asset_id, amount) - } } // ======================================================================== diff --git a/frame/shielded-pool/src/mock.rs b/frame/shielded-pool/src/mock.rs index 794d0c1e..f2149cfc 100644 --- a/frame/shielded-pool/src/mock.rs +++ b/frame/shielded-pool/src/mock.rs @@ -37,7 +37,6 @@ parameter_types! { pub const MinShieldAmount: u128 = 100; pub const MaxProofSize: u32 = 256; pub const MaxPublicInputs: u32 = 10; - pub const RequestExpiration: u64 = 1000; } impl pallet_zk_verifier::Config for Test { @@ -90,45 +89,20 @@ impl ZkVerifierPort for MockZkVerifier { Ok(true) } - fn verify_disclosure_proof( + fn verify_value_proof( proof: &[u8], - public_signals: &[u8], + _public_signals: &[u8], _version: Option, ) -> Result { - // Validate basic format if proof.is_empty() { return Err(sp_runtime::DispatchError::Other("Empty proof")); } - if public_signals.len() != 76 { - return Err(sp_runtime::DispatchError::Other( - "Invalid public signals length", - )); - } - // Sentinel: a proof whose first byte is 0x00 is treated as cryptographically - // rejected (simulates Groth16 returning false). All other non-empty proofs pass. if proof[0] == 0x00 { return Ok(false); } Ok(true) } - fn batch_verify_disclosure_proofs( - proofs: &[sp_std::vec::Vec], - public_signals: &[sp_std::vec::Vec], - _version: Option, - ) -> Result { - // Validate basic format - if proofs.len() != public_signals.len() { - return Err(sp_runtime::DispatchError::Other("Mismatched array lengths")); - } - // Sentinel: any proof in the batch starting with 0x00 causes the whole batch - // to return Ok(false), simulating a failed cryptographic batch verification. - if proofs.iter().any(|p| p.first() == Some(&0x00)) { - return Ok(false); - } - Ok(true) - } - fn verify_private_link_proof( proof: &[u8], _commitment: &[u8; 32], @@ -152,7 +126,6 @@ impl pallet_shielded_pool::Config for Test { type MaxHistoricRoots = MaxHistoricRoots; type MinShieldAmount = MinShieldAmount; type WeightInfo = (); - type RequestExpiration = RequestExpiration; type Relayer = MockRelayer; } diff --git a/frame/shielded-pool/src/operations/disclosure/batch_submit.rs b/frame/shielded-pool/src/operations/disclosure/batch_submit.rs deleted file mode 100644 index 830a284c..00000000 --- a/frame/shielded-pool/src/operations/disclosure/batch_submit.rs +++ /dev/null @@ -1,410 +0,0 @@ -use crate::{ - operations::disclosure::record::ParsedDisclosureSignals, - operations::disclosure::validation::DisclosureValidationService, - pallet::{BatchDisclosureSubmission, Config, Error, Event, Pallet}, - storage::{AuditRepository, CommitmentRepository}, -}; -use frame_support::{BoundedVec, ensure, pallet_prelude::*}; -use pallet_zk_verifier::ZkVerifierPort; - -pub fn batch_submit_proofs( - who: &T::AccountId, - submissions: BoundedVec, ConstU32<10>>, -) -> DispatchResult { - if submissions.is_empty() { - return Ok(()); - } - - let mut proofs_raw = alloc::vec::Vec::with_capacity(submissions.len()); - let mut signals_raw = alloc::vec::Vec::with_capacity(submissions.len()); - - for sub in submissions.iter() { - ensure!( - CommitmentRepository::exists::(&sub.commitment), - Error::::CommitmentNotFound - ); - DisclosureValidationService::validate_disclosure_access::( - who, - &sub.commitment, - sub.auditor.as_ref(), - )?; - DisclosureValidationService::validate_public_signals::( - &sub.commitment, - &sub.public_signals, - )?; - - proofs_raw.push(sub.proof.to_vec()); - signals_raw.push(sub.public_signals.to_vec()); - } - - let all_valid = T::ZkVerifier::batch_verify_disclosure_proofs(&proofs_raw, &signals_raw, None)?; - ensure!(all_valid, Error::::InvalidDisclosureRecord); - - let current_block = frame_system::Pallet::::block_number(); - - for sub in submissions { - let record = ParsedDisclosureSignals::from_public_signals(&sub.public_signals) - .into_record(who.clone(), current_block); - - // Preserve current semantics: batch records are stored under the caller key. - AuditRepository::store_disclosure_record::(sub.commitment, who, record)?; - AuditRepository::update_disclosure_timestamp::(who, sub.commitment, current_block); - - let effective_auditor = sub.auditor.as_ref().unwrap_or(who); - let disclosure_type = if sub.auditor.is_some() { - b"batch_audited_disclosure" as &[u8] - } else { - b"batch_disclosure" as &[u8] - }; - let _ = AuditRepository::create_audit_trail::( - who, - effective_auditor, - sub.commitment, - disclosure_type, - )?; - - if let Some(ref auditor_account) = sub.auditor { - AuditRepository::increment_disclosure_counter::(who, auditor_account); - AuditRepository::remove_disclosure_request::(who, auditor_account); - } - - Pallet::::deposit_event(Event::Disclosed { - who: who.clone(), - commitment: sub.commitment, - auditor: sub.auditor, - }); - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - mock::{Test, new_test_ext}, - operations::disclosure::policy, - pallet::{BatchDisclosureSubmission, CommitmentMemos, Event as PalletEvent}, - storage::AuditRepository, - types::{Auditor, Commitment, DisclosureCondition, DisclosureRequest, EncryptedMemo}, - }; - use frame_support::{assert_noop, assert_ok}; - - // ── helpers ────────────────────────────────────────────────────────────── - - fn commitment(seed: u8) -> Commitment { - Commitment::new([seed; 32]) - } - - fn register_commitment(c: Commitment) { - CommitmentMemos::::insert(c, EncryptedMemo::default()); - } - - fn proof_vec() -> BoundedVec> { - BoundedVec::try_from([0x01u8; 128].to_vec()).unwrap() - } - - fn signals_vec(c: &Commitment) -> BoundedVec> { - let mut buf = [0u8; 76]; - buf[0..32].copy_from_slice(&c.0); - buf[32..40].copy_from_slice(&100u64.to_le_bytes()); - buf[40..44].copy_from_slice(&1u32.to_le_bytes()); - BoundedVec::try_from(buf.to_vec()).unwrap() - } - - fn submission(c: Commitment) -> BatchDisclosureSubmission { - BatchDisclosureSubmission { - commitment: c, - proof: proof_vec(), - public_signals: signals_vec(&c), - auditor: None, - } - } - - fn submission_with_auditor(c: Commitment, auditor: u64) -> BatchDisclosureSubmission { - BatchDisclosureSubmission { - commitment: c, - proof: proof_vec(), - public_signals: signals_vec(&c), - auditor: Some(auditor), - } - } - - fn set_policy_simple(owner: u64, auditor: u64) { - let auditors = BoundedVec::try_from(vec![Auditor::Account(auditor)]).unwrap(); - let conditions: BoundedVec, ConstU32<10>> = - BoundedVec::default(); - policy::set_audit_policy::(&owner, auditors, conditions, None, None).unwrap(); - } - - fn make_request(target: u64, auditor: u64) { - let req = DisclosureRequest { - auditor, - target, - requested_at: 1u64, - expires_at: 9999u64, - reason: BoundedVec::default(), - }; - crate::pallet::DisclosureRequests::::insert(target, auditor, req); - } - - // ── batch_submit_proofs ─────────────────────────────────────────────────── - - #[test] - fn empty_batch_returns_ok() { - new_test_ext().execute_with(|| { - let submissions: BoundedVec, ConstU32<10>> = - BoundedVec::default(); - assert_ok!(batch_submit_proofs::(&1u64, submissions)); - }); - } - - #[test] - fn single_submission_works() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let c = commitment(0x20); - register_commitment(c); - - let submissions = BoundedVec::try_from(vec![submission(c)]).unwrap(); - assert_ok!(batch_submit_proofs::(&owner, submissions)); - - assert!(AuditRepository::has_disclosure_record::(c, &owner)); - }); - } - - #[test] - fn multiple_submissions_work() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let c1 = commitment(0x21); - let c2 = commitment(0x22); - let c3 = commitment(0x23); - register_commitment(c1); - register_commitment(c2); - register_commitment(c3); - - let submissions = - BoundedVec::try_from(vec![submission(c1), submission(c2), submission(c3)]).unwrap(); - assert_ok!(batch_submit_proofs::(&owner, submissions)); - - assert!(AuditRepository::has_disclosure_record::(c1, &owner)); - assert!(AuditRepository::has_disclosure_record::(c2, &owner)); - assert!(AuditRepository::has_disclosure_record::(c3, &owner)); - }); - } - - #[test] - fn commitment_not_found_fails() { - new_test_ext().execute_with(|| { - let c = commitment(0x30); - // Not registered - let submissions = BoundedVec::try_from(vec![submission(c)]).unwrap(); - assert_noop!( - batch_submit_proofs::(&1u64, submissions), - crate::pallet::Error::::CommitmentNotFound - ); - }); - } - - #[test] - fn second_submission_commitment_not_found_fails() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let c1 = commitment(0x31); - let c2 = commitment(0x32); // not registered - register_commitment(c1); - - let submissions = BoundedVec::try_from(vec![submission(c1), submission(c2)]).unwrap(); - assert_noop!( - batch_submit_proofs::(&owner, submissions), - crate::pallet::Error::::CommitmentNotFound - ); - }); - } - - #[test] - fn batch_emits_disclosed_events() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let c1 = commitment(0x40); - let c2 = commitment(0x41); - register_commitment(c1); - register_commitment(c2); - - let submissions = BoundedVec::try_from(vec![submission(c1), submission(c2)]).unwrap(); - assert_ok!(batch_submit_proofs::(&owner, submissions)); - - let events = frame_system::Pallet::::events(); - let disclosed_events: Vec<_> = events - .iter() - .filter(|r| { - matches!( - r.event, - crate::mock::RuntimeEvent::ShieldedPool(PalletEvent::Disclosed { - who, - auditor: None, - .. - }) if who == owner - ) - }) - .collect(); - assert_eq!(disclosed_events.len(), 2, "Expected 2 Disclosed events"); - }); - } - - #[test] - fn batch_with_auditor_submission_works() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let auditor: u64 = 2; - let c = commitment(0x50); - register_commitment(c); - set_policy_simple(owner, auditor); - make_request(owner, auditor); - - let submissions = - BoundedVec::try_from(vec![submission_with_auditor(c, auditor)]).unwrap(); - assert_ok!(batch_submit_proofs::(&owner, submissions)); - - // Record stored under caller (owner) key per current semantics - assert!(AuditRepository::has_disclosure_record::(c, &owner)); - }); - } - - #[test] - fn batch_updates_disclosure_timestamp() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let c = commitment(0x60); - register_commitment(c); - - let submissions = BoundedVec::try_from(vec![submission(c)]).unwrap(); - assert_ok!(batch_submit_proofs::(&owner, submissions)); - - let ts = crate::pallet::LastDisclosureTimestamp::::get(owner, c); - assert_eq!(ts, Some(1u64)); - }); - } - - // ── Flujo A + Flujo B mixed batch ───────────────────────────────────────── - // - // Mixing self-disclosure (auditor: None) and audited disclosure - // (auditor: Some(...)) in the same batch is intentional: each submission is - // processed independently inside `batch_submit_proofs`. Mixing is valid as - // long as every individual entry satisfies its own access policy. - // These tests document and pin that behaviour. - - #[test] - fn mixed_batch_flujo_a_and_flujo_b_both_succeed() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let auditor: u64 = 2; - - // Flujo A commitment — no auditor required - let c_a = commitment(0x70); - register_commitment(c_a); - - // Flujo B commitment — requires policy + pending request - let c_b = commitment(0x71); - register_commitment(c_b); - set_policy_simple(owner, auditor); - make_request(owner, auditor); - - let submissions = BoundedVec::try_from(vec![ - submission(c_a), // Flujo A - submission_with_auditor(c_b, auditor), // Flujo B - ]) - .unwrap(); - - assert_ok!(batch_submit_proofs::(&owner, submissions)); - - // Both records stored under the owner key - assert!(AuditRepository::has_disclosure_record::(c_a, &owner)); - assert!(AuditRepository::has_disclosure_record::(c_b, &owner)); - }); - } - - #[test] - fn mixed_batch_emits_correct_events_per_entry() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let auditor: u64 = 2; - - let c_a = commitment(0x72); - register_commitment(c_a); - - let c_b = commitment(0x73); - register_commitment(c_b); - set_policy_simple(owner, auditor); - make_request(owner, auditor); - - let submissions = - BoundedVec::try_from(vec![submission(c_a), submission_with_auditor(c_b, auditor)]) - .unwrap(); - assert_ok!(batch_submit_proofs::(&owner, submissions)); - - let events = frame_system::Pallet::::events(); - let disclosed: Vec<_> = events - .iter() - .filter_map(|r| { - if let crate::mock::RuntimeEvent::ShieldedPool(PalletEvent::Disclosed { - who, - commitment, - auditor: aud, - }) = &r.event - { - Some((*who, *commitment, *aud)) - } else { - None - } - }) - .collect(); - - assert_eq!(disclosed.len(), 2); - - // Flujo A: auditor field must be None - assert!( - disclosed - .iter() - .any(|(who, c, aud)| *who == owner && *c == c_a && aud.is_none()), - "Expected Disclosed event with auditor=None for Flujo A" - ); - // Flujo B: auditor field must be Some(auditor) - assert!( - disclosed - .iter() - .any(|(who, c, aud)| *who == owner && *c == c_b && *aud == Some(auditor)), - "Expected Disclosed event with auditor=Some({auditor}) for Flujo B" - ); - }); - } - - #[test] - fn mixed_batch_fails_when_flujo_b_entry_has_no_policy() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let auditor: u64 = 3; // no policy set for this auditor - - let c_a = commitment(0x74); - register_commitment(c_a); - let c_b = commitment(0x75); - register_commitment(c_b); - // Deliberately NOT calling set_policy_simple / make_request - - let submissions = - BoundedVec::try_from(vec![submission(c_a), submission_with_auditor(c_b, auditor)]) - .unwrap(); - - // The whole batch must be rejected — atomicity guarantees no partial state. - assert_noop!( - batch_submit_proofs::(&owner, submissions), - crate::pallet::Error::::UnauthorizedAuditor - ); - - // Neither record should have been stored - assert!(!AuditRepository::has_disclosure_record::(c_a, &owner)); - assert!(!AuditRepository::has_disclosure_record::(c_b, &owner)); - }); - } -} diff --git a/frame/shielded-pool/src/operations/disclosure/mod.rs b/frame/shielded-pool/src/operations/disclosure/mod.rs deleted file mode 100644 index bbd7eafe..00000000 --- a/frame/shielded-pool/src/operations/disclosure/mod.rs +++ /dev/null @@ -1,60 +0,0 @@ -mod batch_submit; -mod policy; -mod record; -mod request; -mod submit; -pub mod validation; - -use crate::{ - pallet::{BalanceOf, BatchDisclosureSubmission, Config}, - types::{Auditor, Commitment, DisclosureCondition}, -}; -use frame_support::{BoundedVec, pallet_prelude::*}; -use frame_system::pallet_prelude::BlockNumberFor; - -pub struct DisclosureOperation; - -impl DisclosureOperation { - pub fn set_audit_policy( - who: &T::AccountId, - auditors: BoundedVec, ConstU32<10>>, - conditions: BoundedVec, BlockNumberFor>, ConstU32<10>>, - max_frequency: Option>, - valid_until: Option>, - ) -> DispatchResult { - policy::set_audit_policy::(who, auditors, conditions, max_frequency, valid_until) - } - - pub fn request_disclosure( - auditor: &T::AccountId, - target: &T::AccountId, - reason: BoundedVec>, - ) -> DispatchResult { - request::request_disclosure::(auditor, target, reason) - } - - pub fn reject_disclosure( - target: &T::AccountId, - auditor: &T::AccountId, - reason: BoundedVec>, - ) -> DispatchResult { - request::reject_disclosure::(target, auditor, reason) - } - - pub fn disclose( - who: &T::AccountId, - commitment: Commitment, - proof_bytes: BoundedVec>, - public_signals: BoundedVec>, - auditor: Option<&T::AccountId>, - ) -> DispatchResult { - submit::disclose::(who, commitment, proof_bytes, public_signals, auditor) - } - - pub fn batch_submit_proofs( - who: &T::AccountId, - submissions: BoundedVec, ConstU32<10>>, - ) -> DispatchResult { - batch_submit::batch_submit_proofs::(who, submissions) - } -} diff --git a/frame/shielded-pool/src/operations/disclosure/policy.rs b/frame/shielded-pool/src/operations/disclosure/policy.rs deleted file mode 100644 index 0c7e4364..00000000 --- a/frame/shielded-pool/src/operations/disclosure/policy.rs +++ /dev/null @@ -1,204 +0,0 @@ -use crate::{ - pallet::{BalanceOf, Config, Error, Event, Pallet}, - storage::AuditRepository, - types::{AuditPolicy, Auditor, DisclosureCondition}, -}; -use frame_support::{BoundedVec, ensure, pallet_prelude::*}; -use frame_system::pallet_prelude::BlockNumberFor; - -pub fn set_audit_policy( - who: &T::AccountId, - auditors: BoundedVec, ConstU32<10>>, - conditions: BoundedVec, BlockNumberFor>, ConstU32<10>>, - max_frequency: Option>, - valid_until: Option>, -) -> DispatchResult { - ensure!(!auditors.is_empty(), Error::::NoAuditorsProvided); - ensure!(auditors.len() <= 10, Error::::TooManyAuditors); - ensure!(conditions.len() <= 10, Error::::TooManyConditions); - - for i in 0..auditors.len() { - ensure!( - !auditors[i + 1..].contains(&auditors[i]), - Error::::DuplicateAuditor - ); - } - - let current_version = AuditRepository::get_policy::(who) - .map(|policy| policy.version) - .unwrap_or(0); - - let policy = AuditPolicy { - auditors, - conditions, - max_frequency, - valid_until, - version: current_version.saturating_add(1), - }; - - AuditRepository::store_policy::(who, policy.clone()); - Pallet::::deposit_event(Event::AuditPolicySet { - account: who.clone(), - version: policy.version, - }); - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - mock::{Test, new_test_ext}, - pallet::Event as PalletEvent, - storage::AuditRepository, - types::{Auditor, DisclosureCondition}, - }; - use frame_support::pallet_prelude::{BoundedVec, ConstU32}; - use frame_support::{assert_noop, assert_ok}; - - // ── helpers ────────────────────────────────────────────────────────────── - - fn auditors_one(account: u64) -> BoundedVec, ConstU32<10>> { - BoundedVec::try_from(vec![Auditor::Account(account)]).unwrap() - } - - fn no_conditions() -> BoundedVec, ConstU32<10>> { - BoundedVec::default() - } - - // ── set_audit_policy ───────────────────────────────────────────────────── - - #[test] - fn set_audit_policy_works() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let auditor: u64 = 2; - assert_ok!(set_audit_policy::( - &owner, - auditors_one(auditor), - no_conditions(), - None, - None, - )); - - let policy = AuditRepository::get_policy::(&owner).unwrap(); - assert_eq!(policy.version, 1); - assert_eq!(policy.auditors.len(), 1); - assert_eq!(policy.auditors[0], Auditor::Account(auditor)); - }); - } - - #[test] - fn set_audit_policy_empty_auditors_fails() { - new_test_ext().execute_with(|| { - let empty: BoundedVec, ConstU32<10>> = BoundedVec::default(); - assert_noop!( - set_audit_policy::(&1u64, empty, no_conditions(), None, None), - crate::pallet::Error::::NoAuditorsProvided - ); - }); - } - - #[test] - fn set_audit_policy_duplicate_auditor_fails() { - new_test_ext().execute_with(|| { - let auditors = - BoundedVec::try_from(vec![Auditor::Account(2u64), Auditor::Account(2u64)]).unwrap(); - assert_noop!( - set_audit_policy::(&1u64, auditors, no_conditions(), None, None), - crate::pallet::Error::::DuplicateAuditor - ); - }); - } - - #[test] - fn set_audit_policy_increments_version_on_update() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - assert_ok!(set_audit_policy::( - &owner, - auditors_one(2), - no_conditions(), - None, - None, - )); - assert_ok!(set_audit_policy::( - &owner, - auditors_one(3), - no_conditions(), - None, - None, - )); - - let policy = AuditRepository::get_policy::(&owner).unwrap(); - assert_eq!(policy.version, 2); - }); - } - - #[test] - fn set_audit_policy_with_conditions_works() { - new_test_ext().execute_with(|| { - let conditions: BoundedVec, ConstU32<10>> = - BoundedVec::try_from(vec![DisclosureCondition::Always]).unwrap(); - assert_ok!(set_audit_policy::( - &1u64, - auditors_one(2), - conditions.clone(), - Some(100u64), - Some(9999u64), - )); - - let policy = AuditRepository::get_policy::(&1u64).unwrap(); - assert_eq!(policy.conditions.len(), 1); - assert_eq!(policy.max_frequency, Some(100u64)); - assert_eq!(policy.valid_until, Some(9999u64)); - }); - } - - #[test] - fn set_audit_policy_emits_event() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - assert_ok!(set_audit_policy::( - &owner, - auditors_one(2), - no_conditions(), - None, - None, - )); - - let events = frame_system::Pallet::::events(); - let found = events.iter().any(|record| { - matches!( - record.event, - crate::mock::RuntimeEvent::ShieldedPool(PalletEvent::AuditPolicySet { - account, - version: 1, - }) if account == owner - ) - }); - assert!(found, "AuditPolicySet event not emitted"); - }); - } - - #[test] - fn set_audit_policy_multiple_auditors_works() { - new_test_ext().execute_with(|| { - let auditors = BoundedVec::try_from(vec![ - Auditor::Account(2u64), - Auditor::Account(3u64), - Auditor::Account(4u64), - ]) - .unwrap(); - assert_ok!(set_audit_policy::( - &1u64, - auditors, - no_conditions(), - None, - None, - )); - let policy = AuditRepository::get_policy::(&1u64).unwrap(); - assert_eq!(policy.auditors.len(), 3); - }); - } -} diff --git a/frame/shielded-pool/src/operations/disclosure/record.rs b/frame/shielded-pool/src/operations/disclosure/record.rs deleted file mode 100644 index 7967fb9b..00000000 --- a/frame/shielded-pool/src/operations/disclosure/record.rs +++ /dev/null @@ -1,172 +0,0 @@ -use crate::types::DisclosureRecord; - -pub struct ParsedDisclosureSignals { - pub revealed_value: Option, - pub revealed_asset_id: Option, - pub revealed_owner_hash: Option<[u8; 32]>, -} - -impl ParsedDisclosureSignals { - pub fn from_public_signals(public_signals: &[u8]) -> Self { - let revealed_value = if public_signals.len() >= 40 { - let value = u64::from_le_bytes(public_signals[32..40].try_into().unwrap_or([0u8; 8])); - (value != 0).then_some(value) - } else { - None - }; - - let revealed_asset_id = if public_signals.len() >= 44 { - let asset_id = - u32::from_le_bytes(public_signals[40..44].try_into().unwrap_or([0u8; 4])); - (asset_id != 0).then_some(asset_id) - } else { - None - }; - - let revealed_owner_hash = if public_signals.len() >= 76 { - let owner_hash: [u8; 32] = public_signals[44..76].try_into().unwrap_or([0u8; 32]); - (owner_hash != [0u8; 32]).then_some(owner_hash) - } else { - None - }; - - Self { - revealed_value, - revealed_asset_id, - revealed_owner_hash, - } - } - - pub fn into_record( - self, - requester: AccountId, - timestamp: BlockNumber, - ) -> DisclosureRecord { - DisclosureRecord { - revealed_value: self.revealed_value, - revealed_asset_id: self.revealed_asset_id, - revealed_owner_hash: self.revealed_owner_hash, - requester, - timestamp, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - // ── helpers ────────────────────────────────────────────────────────────── - - /// 76-byte signals: commitment(32) | value_le(8) | asset_id_le(4) | owner_hash(32) - fn signals(value: u64, asset_id: u32, owner_hash: [u8; 32]) -> [u8; 76] { - let mut buf = [0u8; 76]; - buf[32..40].copy_from_slice(&value.to_le_bytes()); - buf[40..44].copy_from_slice(&asset_id.to_le_bytes()); - buf[44..76].copy_from_slice(&owner_hash); - buf - } - - // ── from_public_signals ────────────────────────────────────────────────── - - #[test] - fn all_fields_present_when_nonzero() { - let owner_hash = [0xABu8; 32]; - let s = signals(1000, 7, owner_hash); - let parsed = ParsedDisclosureSignals::from_public_signals(&s); - - assert_eq!(parsed.revealed_value, Some(1000u64)); - assert_eq!(parsed.revealed_asset_id, Some(7u32)); - assert_eq!(parsed.revealed_owner_hash, Some(owner_hash)); - } - - #[test] - fn zero_value_returns_none() { - let s = signals(0, 5, [0xBBu8; 32]); - let parsed = ParsedDisclosureSignals::from_public_signals(&s); - assert_eq!(parsed.revealed_value, None); - assert_eq!(parsed.revealed_asset_id, Some(5u32)); - } - - #[test] - fn zero_asset_id_returns_none() { - let s = signals(42, 0, [0xCCu8; 32]); - let parsed = ParsedDisclosureSignals::from_public_signals(&s); - assert_eq!(parsed.revealed_asset_id, None); - assert_eq!(parsed.revealed_value, Some(42u64)); - } - - #[test] - fn zero_owner_hash_returns_none() { - let s = signals(1, 1, [0u8; 32]); - let parsed = ParsedDisclosureSignals::from_public_signals(&s); - assert_eq!(parsed.revealed_owner_hash, None); - } - - #[test] - fn all_fields_none_when_all_zero() { - let s = signals(0, 0, [0u8; 32]); - let parsed = ParsedDisclosureSignals::from_public_signals(&s); - assert_eq!(parsed.revealed_value, None); - assert_eq!(parsed.revealed_asset_id, None); - assert_eq!(parsed.revealed_owner_hash, None); - } - - #[test] - fn short_input_returns_none_for_missing_fields() { - // Only 33 bytes: not enough for value (needs 40), asset_id (needs 44), owner_hash (needs 76) - let s = [0u8; 33]; - let parsed = ParsedDisclosureSignals::from_public_signals(&s); - assert_eq!(parsed.revealed_value, None); - assert_eq!(parsed.revealed_asset_id, None); - assert_eq!(parsed.revealed_owner_hash, None); - } - - #[test] - fn exactly_40_bytes_gives_value_but_no_asset_or_owner() { - let mut s = [0u8; 40]; - s[32..40].copy_from_slice(&500u64.to_le_bytes()); - let parsed = ParsedDisclosureSignals::from_public_signals(&s); - assert_eq!(parsed.revealed_value, Some(500u64)); - assert_eq!(parsed.revealed_asset_id, None); - assert_eq!(parsed.revealed_owner_hash, None); - } - - #[test] - fn exactly_44_bytes_gives_value_and_asset_but_no_owner() { - let mut s = [0u8; 44]; - s[32..40].copy_from_slice(&9u64.to_le_bytes()); - s[40..44].copy_from_slice(&3u32.to_le_bytes()); - let parsed = ParsedDisclosureSignals::from_public_signals(&s); - assert_eq!(parsed.revealed_value, Some(9u64)); - assert_eq!(parsed.revealed_asset_id, Some(3u32)); - assert_eq!(parsed.revealed_owner_hash, None); - } - - // ── into_record ────────────────────────────────────────────────────────── - - #[test] - fn into_record_maps_fields_correctly() { - let owner_hash = [0x01u8; 32]; - let s = signals(500, 2, owner_hash); - let parsed = ParsedDisclosureSignals::from_public_signals(&s); - let record = parsed.into_record(42u64, 10u64); - - assert_eq!(record.revealed_value, Some(500u64)); - assert_eq!(record.revealed_asset_id, Some(2u32)); - assert_eq!(record.revealed_owner_hash, Some(owner_hash)); - assert_eq!(record.requester, 42u64); - assert_eq!(record.timestamp, 10u64); - } - - #[test] - fn into_record_with_none_fields() { - let s = signals(0, 0, [0u8; 32]); - let parsed = ParsedDisclosureSignals::from_public_signals(&s); - let record = parsed.into_record(1u64, 5u64); - - assert_eq!(record.revealed_value, None); - assert_eq!(record.revealed_asset_id, None); - assert_eq!(record.revealed_owner_hash, None); - } -} diff --git a/frame/shielded-pool/src/operations/disclosure/request.rs b/frame/shielded-pool/src/operations/disclosure/request.rs deleted file mode 100644 index 4cfeb560..00000000 --- a/frame/shielded-pool/src/operations/disclosure/request.rs +++ /dev/null @@ -1,269 +0,0 @@ -use crate::{ - pallet::{Config, Error, Event, Pallet}, - storage::AuditRepository, - types::{Auditor, DisclosureRequest}, -}; -use frame_support::{BoundedVec, ensure, pallet_prelude::*}; - -pub fn request_disclosure( - auditor: &T::AccountId, - target: &T::AccountId, - reason: BoundedVec>, -) -> DispatchResult { - ensure!( - !AuditRepository::has_disclosure_request::(target, auditor), - Error::::DisclosureRequestAlreadyExists - ); - - let policy = AuditRepository::get_policy::(target).ok_or(Error::::AuditPolicyNotFound)?; - - let is_authorized = policy - .auditors - .iter() - .any(|auditor_type| matches!(auditor_type, Auditor::Account(acc) if acc == auditor)); - ensure!(is_authorized, Error::::AuditorNotAuthorized); - - if let Some(max_freq) = policy.max_frequency { - let counter = AuditRepository::get_disclosure_counter::(target, auditor); - let max_freq_u32: u32 = TryInto::::try_into(max_freq).unwrap_or(u32::MAX); - ensure!( - counter < max_freq_u32, - Error::::TooManyDisclosureRequests - ); - } - - let reason_clone = reason.clone(); - let current_block = frame_system::Pallet::::block_number(); - let request = DisclosureRequest { - auditor: auditor.clone(), - target: target.clone(), - requested_at: current_block, - expires_at: current_block + T::RequestExpiration::get(), - reason, - }; - - AuditRepository::store_disclosure_request::(target.clone(), auditor.clone(), request); - Pallet::::deposit_event(Event::DisclosureRequested { - target: target.clone(), - auditor: auditor.clone(), - reason: reason_clone, - }); - Ok(()) -} - -pub fn reject_disclosure( - target: &T::AccountId, - auditor: &T::AccountId, - reason: BoundedVec>, -) -> DispatchResult { - ensure!( - AuditRepository::has_disclosure_request::(target, auditor), - Error::::DisclosureRequestNotFound - ); - - AuditRepository::remove_disclosure_request::(target, auditor); - Pallet::::deposit_event(Event::DisclosureRejected { - target: target.clone(), - auditor: auditor.clone(), - reason, - }); - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - mock::{Test, new_test_ext}, - operations::disclosure::policy, - pallet::Event as PalletEvent, - storage::AuditRepository, - types::{Auditor, DisclosureCondition}, - }; - use frame_support::{assert_noop, assert_ok}; - - // ── helpers ────────────────────────────────────────────────────────────── - - fn set_policy_with_auditor(owner: u64, auditor: u64) { - let auditors = BoundedVec::try_from(vec![Auditor::Account(auditor)]).unwrap(); - let conditions: BoundedVec, ConstU32<10>> = - BoundedVec::default(); - policy::set_audit_policy::(&owner, auditors, conditions, None, None).unwrap(); - } - - fn reason(text: &[u8]) -> BoundedVec> { - BoundedVec::try_from(text.to_vec()).unwrap() - } - - // ── request_disclosure ─────────────────────────────────────────────────── - - #[test] - fn request_disclosure_works() { - new_test_ext().execute_with(|| { - let target: u64 = 1; - let auditor: u64 = 2; - set_policy_with_auditor(target, auditor); - - assert_ok!(request_disclosure::( - &auditor, - &target, - reason(b"audit required"), - )); - - assert!(AuditRepository::has_disclosure_request::( - &target, &auditor - )); - }); - } - - #[test] - fn request_disclosure_no_policy_fails() { - new_test_ext().execute_with(|| { - assert_noop!( - request_disclosure::(&2u64, &1u64, reason(b"no policy")), - crate::pallet::Error::::AuditPolicyNotFound - ); - }); - } - - #[test] - fn request_disclosure_auditor_not_in_policy_fails() { - new_test_ext().execute_with(|| { - let target: u64 = 1; - // Policy authorizes auditor 2, not 3 - set_policy_with_auditor(target, 2u64); - - assert_noop!( - request_disclosure::(&3u64, &target, reason(b"unauthorized")), - crate::pallet::Error::::AuditorNotAuthorized - ); - }); - } - - #[test] - fn request_disclosure_already_exists_fails() { - new_test_ext().execute_with(|| { - let target: u64 = 1; - let auditor: u64 = 2; - set_policy_with_auditor(target, auditor); - - assert_ok!(request_disclosure::( - &auditor, - &target, - reason(b"first") - )); - assert_noop!( - request_disclosure::(&auditor, &target, reason(b"second")), - crate::pallet::Error::::DisclosureRequestAlreadyExists - ); - }); - } - - #[test] - fn request_disclosure_stores_request_fields() { - new_test_ext().execute_with(|| { - let target: u64 = 1; - let auditor: u64 = 2; - set_policy_with_auditor(target, auditor); - - assert_ok!(request_disclosure::( - &auditor, - &target, - reason(b"check") - )); - - let req = AuditRepository::get_disclosure_request::(&target, &auditor).unwrap(); - assert_eq!(req.auditor, auditor); - assert_eq!(req.target, target); - // requested_at == current block (1) - assert_eq!(req.requested_at, 1u64); - // expires_at == requested_at + RequestExpiration (1000 in mock) - assert_eq!(req.expires_at, 1001u64); - }); - } - - #[test] - fn request_disclosure_emits_event() { - new_test_ext().execute_with(|| { - let target: u64 = 1; - let auditor: u64 = 2; - set_policy_with_auditor(target, auditor); - - assert_ok!(request_disclosure::( - &auditor, - &target, - reason(b"audit") - )); - - let events = frame_system::Pallet::::events(); - let found = events.iter().any(|r| { - matches!( - r.event, - crate::mock::RuntimeEvent::ShieldedPool( - PalletEvent::DisclosureRequested { target: t, auditor: a, .. } - ) if t == target && a == auditor - ) - }); - assert!(found, "DisclosureRequested event not emitted"); - }); - } - - // ── reject_disclosure ──────────────────────────────────────────────────── - - #[test] - fn reject_disclosure_works() { - new_test_ext().execute_with(|| { - let target: u64 = 1; - let auditor: u64 = 2; - set_policy_with_auditor(target, auditor); - assert_ok!(request_disclosure::( - &auditor, - &target, - reason(b"audit") - )); - - assert_ok!(reject_disclosure::(&target, &auditor, reason(b"no"))); - - assert!(!AuditRepository::has_disclosure_request::( - &target, &auditor - )); - }); - } - - #[test] - fn reject_disclosure_not_found_fails() { - new_test_ext().execute_with(|| { - assert_noop!( - reject_disclosure::(&1u64, &2u64, reason(b"nope")), - crate::pallet::Error::::DisclosureRequestNotFound - ); - }); - } - - #[test] - fn reject_disclosure_emits_event() { - new_test_ext().execute_with(|| { - let target: u64 = 1; - let auditor: u64 = 2; - set_policy_with_auditor(target, auditor); - assert_ok!(request_disclosure::(&auditor, &target, reason(b"r"))); - - assert_ok!(reject_disclosure::( - &target, - &auditor, - reason(b"denied") - )); - - let events = frame_system::Pallet::::events(); - let found = events.iter().any(|r| { - matches!( - r.event, - crate::mock::RuntimeEvent::ShieldedPool( - PalletEvent::DisclosureRejected { target: t, auditor: a, .. } - ) if t == target && a == auditor - ) - }); - assert!(found, "DisclosureRejected event not emitted"); - }); - } -} diff --git a/frame/shielded-pool/src/operations/disclosure/submit.rs b/frame/shielded-pool/src/operations/disclosure/submit.rs deleted file mode 100644 index d6f2afa9..00000000 --- a/frame/shielded-pool/src/operations/disclosure/submit.rs +++ /dev/null @@ -1,427 +0,0 @@ -use crate::{ - operations::disclosure::record::ParsedDisclosureSignals, - operations::disclosure::validation::DisclosureValidationService, - pallet::{Config, Error, Event, Pallet}, - storage::{AuditRepository, CommitmentRepository}, - types::{Commitment, DisclosureCondition}, -}; -use frame_support::{BoundedVec, ensure, pallet_prelude::*}; -use sp_runtime::traits::SaturatedConversion; - -pub fn disclose( - who: &T::AccountId, - commitment: Commitment, - proof_bytes: BoundedVec>, - public_signals: BoundedVec>, - auditor: Option<&T::AccountId>, -) -> DispatchResult { - ensure!( - CommitmentRepository::exists::(&commitment), - Error::::CommitmentNotFound - ); - - let current_block = frame_system::Pallet::::block_number(); - - if let Some(auditor_account) = auditor { - disclose_with_auditor::( - who, - auditor_account, - commitment, - proof_bytes.as_slice(), - public_signals.as_slice(), - current_block, - )?; - } else { - disclose_self::( - who, - commitment, - proof_bytes.as_slice(), - public_signals.as_slice(), - current_block, - )?; - } - - Pallet::::deposit_event(Event::Disclosed { - who: who.clone(), - commitment, - auditor: auditor.cloned(), - }); - - Ok(()) -} - -fn disclose_self( - who: &T::AccountId, - commitment: Commitment, - proof_bytes: &[u8], - public_signals: &[u8], - current_block: frame_system::pallet_prelude::BlockNumberFor, -) -> DispatchResult { - DisclosureValidationService::validate_disclosure_access::(who, &commitment, None)?; - DisclosureValidationService::verify_proof_internal::(proof_bytes, public_signals)?; - DisclosureValidationService::validate_public_signals::(&commitment, public_signals)?; - - let record = ParsedDisclosureSignals::from_public_signals(public_signals) - .into_record(who.clone(), current_block); - AuditRepository::store_disclosure_record::(commitment, who, record)?; - AuditRepository::update_disclosure_timestamp::(who, commitment, current_block); - Ok(()) -} - -fn disclose_with_auditor( - who: &T::AccountId, - auditor: &T::AccountId, - commitment: Commitment, - proof_bytes: &[u8], - public_signals: &[u8], - current_block: frame_system::pallet_prelude::BlockNumberFor, -) -> DispatchResult { - let request = AuditRepository::get_disclosure_request::(who, auditor) - .ok_or(Error::::DisclosureRequestNotFound)?; - ensure!( - current_block <= request.expires_at, - Error::::DisclosureRequestExpired - ); - - let policy = AuditRepository::get_policy::(who).ok_or(Error::::AuditPolicyNotFound)?; - if let Some(valid_until) = policy.valid_until { - ensure!(current_block <= valid_until, Error::::AuditPolicyExpired); - } - - let revealed_value_u64 = if public_signals.len() >= 40 { - u64::from_le_bytes(public_signals[32..40].try_into().unwrap_or([0u8; 8])) - } else { - 0u64 - }; - - let conditions_met = policy.conditions.iter().any(|cond| match cond { - DisclosureCondition::Always => true, - DisclosureCondition::TimeDelay { after_block } => current_block >= *after_block, - DisclosureCondition::AmountThreshold { min_amount } => { - let min_u64: u64 = (*min_amount).saturated_into::(); - revealed_value_u64 >= min_u64 - } - }); - ensure!(conditions_met, Error::::DisclosureConditionsNotMet); - - DisclosureValidationService::verify_disclosure_proof::( - proof_bytes, - public_signals, - &commitment, - )?; - - let record = ParsedDisclosureSignals::from_public_signals(public_signals) - .into_record(who.clone(), current_block); - AuditRepository::store_disclosure_record::(commitment, auditor, record)?; - AuditRepository::update_disclosure_timestamp::(who, commitment, current_block); - AuditRepository::increment_disclosure_counter::(who, auditor); - AuditRepository::remove_disclosure_request::(who, auditor); - let _ = AuditRepository::create_audit_trail::( - who, - auditor, - commitment, - b"selective_disclosure", - )?; - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - mock::{Test, new_test_ext}, - operations::disclosure::policy, - pallet::{CommitmentMemos, Event as PalletEvent}, - storage::AuditRepository, - types::{Auditor, Commitment, DisclosureCondition, DisclosureRequest, EncryptedMemo}, - }; - use frame_support::{BoundedVec, assert_noop, assert_ok, pallet_prelude::ConstU32}; - - // ── helpers ────────────────────────────────────────────────────────────── - - fn commitment(seed: u8) -> Commitment { - Commitment::new([seed; 32]) - } - - fn register_commitment(c: Commitment) { - CommitmentMemos::::insert(c, EncryptedMemo::default()); - } - - fn proof() -> BoundedVec> { - BoundedVec::try_from([0x01u8; 128].to_vec()).unwrap() - } - - /// 76-byte signals: commitment (32) | value_le (8) | asset_id_le (4) | owner_hash (32) - fn signals_for(c: &Commitment) -> BoundedVec> { - let mut buf = [0u8; 76]; - buf[0..32].copy_from_slice(&c.0); - buf[32..40].copy_from_slice(&100u64.to_le_bytes()); - buf[40..44].copy_from_slice(&1u32.to_le_bytes()); - BoundedVec::try_from(buf.to_vec()).unwrap() - } - - fn set_policy_always(owner: u64, auditor: u64) { - let auditors = BoundedVec::try_from(vec![Auditor::Account(auditor)]).unwrap(); - let conditions: BoundedVec, ConstU32<10>> = - BoundedVec::try_from(vec![DisclosureCondition::Always]).unwrap(); - policy::set_audit_policy::(&owner, auditors, conditions, None, None).unwrap(); - } - - fn make_request(target: u64, auditor: u64, expires_at: u64) { - use frame_support::BoundedVec; - let req = DisclosureRequest { - auditor, - target, - requested_at: 1u64, - expires_at, - reason: BoundedVec::default(), - }; - crate::pallet::DisclosureRequests::::insert(target, auditor, req); - } - - // ── disclose (self) ─────────────────────────────────────────────────────── - - #[test] - fn disclose_self_works() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let c = commitment(0x01); - register_commitment(c); - - assert_ok!(disclose::(&owner, c, proof(), signals_for(&c), None)); - - // Record stored under caller key - assert!(AuditRepository::has_disclosure_record::(c, &owner)); - }); - } - - #[test] - fn disclose_commitment_not_found_fails() { - new_test_ext().execute_with(|| { - let c = commitment(0x02); - // Not registered - assert_noop!( - disclose::(&1u64, c, proof(), signals_for(&c), None), - crate::pallet::Error::::CommitmentNotFound - ); - }); - } - - #[test] - fn disclose_self_emits_event() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let c = commitment(0x03); - register_commitment(c); - - assert_ok!(disclose::(&owner, c, proof(), signals_for(&c), None)); - - let events = frame_system::Pallet::::events(); - let found = events.iter().any(|r| { - matches!( - r.event, - crate::mock::RuntimeEvent::ShieldedPool(PalletEvent::Disclosed { - who, - commitment: ec, - auditor: None, - }) if who == owner && ec == c - ) - }); - assert!(found, "Disclosed event not emitted"); - }); - } - - #[test] - fn disclose_self_updates_timestamp() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let c = commitment(0x04); - register_commitment(c); - - assert_ok!(disclose::(&owner, c, proof(), signals_for(&c), None)); - - let ts = crate::pallet::LastDisclosureTimestamp::::get(owner, c); - assert_eq!(ts, Some(1u64)); - }); - } - - #[test] - fn disclose_self_twice_fails_duplicate_record() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let c = commitment(0x05); - register_commitment(c); - - assert_ok!(disclose::(&owner, c, proof(), signals_for(&c), None)); - assert_noop!( - disclose::(&owner, c, proof(), signals_for(&c), None), - crate::pallet::Error::::DisclosureRecordAlreadyExists - ); - }); - } - - // ── disclose (with auditor) ─────────────────────────────────────────────── - - #[test] - fn disclose_with_auditor_works() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let auditor: u64 = 2; - let c = commitment(0x10); - register_commitment(c); - set_policy_always(owner, auditor); - make_request(owner, auditor, 9999u64); - - assert_ok!(disclose::( - &owner, - c, - proof(), - signals_for(&c), - Some(&auditor), - )); - - // Record stored under auditor key - assert!(AuditRepository::has_disclosure_record::(c, &auditor)); - }); - } - - #[test] - fn disclose_with_auditor_request_not_found_fails() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let auditor: u64 = 2; - let c = commitment(0x11); - register_commitment(c); - // Policy but no request - set_policy_always(owner, auditor); - - assert_noop!( - disclose::(&owner, c, proof(), signals_for(&c), Some(&auditor)), - crate::pallet::Error::::DisclosureRequestNotFound - ); - }); - } - - #[test] - fn disclose_with_auditor_request_expired_fails() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let auditor: u64 = 2; - let c = commitment(0x12); - register_commitment(c); - set_policy_always(owner, auditor); - make_request(owner, auditor, 0u64); // expires at block 0, current = 1 - - assert_noop!( - disclose::(&owner, c, proof(), signals_for(&c), Some(&auditor)), - crate::pallet::Error::::DisclosureRequestExpired - ); - }); - } - - #[test] - fn disclose_with_auditor_policy_expired_fails() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let auditor: u64 = 2; - let c = commitment(0x13); - register_commitment(c); - - // Policy expired (valid_until = 0) - let auditors = BoundedVec::try_from(vec![Auditor::Account(auditor)]).unwrap(); - let conditions: BoundedVec, ConstU32<10>> = - BoundedVec::try_from(vec![DisclosureCondition::Always]).unwrap(); - policy::set_audit_policy::(&owner, auditors, conditions, None, Some(0u64)) - .unwrap(); - make_request(owner, auditor, 9999u64); - - assert_noop!( - disclose::(&owner, c, proof(), signals_for(&c), Some(&auditor)), - crate::pallet::Error::::AuditPolicyExpired - ); - }); - } - - #[test] - fn disclose_with_auditor_conditions_not_met_fails() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let auditor: u64 = 2; - let c = commitment(0x14); - register_commitment(c); - - // Condition: AmountThreshold(min=99999) but signals encode value=100 - let auditors = BoundedVec::try_from(vec![Auditor::Account(auditor)]).unwrap(); - let conditions: BoundedVec, ConstU32<10>> = - BoundedVec::try_from(vec![DisclosureCondition::AmountThreshold { - min_amount: 99_999u128, - }]) - .unwrap(); - policy::set_audit_policy::(&owner, auditors, conditions, None, None).unwrap(); - make_request(owner, auditor, 9999u64); - - assert_noop!( - disclose::(&owner, c, proof(), signals_for(&c), Some(&auditor)), - crate::pallet::Error::::DisclosureConditionsNotMet - ); - }); - } - - #[test] - fn disclose_with_auditor_removes_request_after_success() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let auditor: u64 = 2; - let c = commitment(0x15); - register_commitment(c); - set_policy_always(owner, auditor); - make_request(owner, auditor, 9999u64); - - assert_ok!(disclose::( - &owner, - c, - proof(), - signals_for(&c), - Some(&auditor), - )); - - assert!( - !AuditRepository::has_disclosure_request::(&owner, &auditor), - "Request should be removed after successful disclosure" - ); - }); - } - - #[test] - fn disclose_with_auditor_emits_event() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let auditor: u64 = 2; - let c = commitment(0x16); - register_commitment(c); - set_policy_always(owner, auditor); - make_request(owner, auditor, 9999u64); - - assert_ok!(disclose::( - &owner, - c, - proof(), - signals_for(&c), - Some(&auditor), - )); - - let events = frame_system::Pallet::::events(); - let found = events.iter().any(|r| { - matches!( - r.event, - crate::mock::RuntimeEvent::ShieldedPool(PalletEvent::Disclosed { - who, - commitment: ec, - auditor: Some(a), - }) if who == owner && ec == c && a == auditor - ) - }); - assert!(found, "Disclosed (with auditor) event not emitted"); - }); - } -} diff --git a/frame/shielded-pool/src/operations/disclosure/validation.rs b/frame/shielded-pool/src/operations/disclosure/validation.rs deleted file mode 100644 index f12d5e77..00000000 --- a/frame/shielded-pool/src/operations/disclosure/validation.rs +++ /dev/null @@ -1,524 +0,0 @@ -//! Disclosure Validation Service -//! -//! Validation logic for selective disclosure proofs: ZK proof verification, -//! public signals validation, access control and rate limiting. - -use crate::{ - pallet::{ - AuditPolicies, CommitmentMemos, Config, DisclosureRequests, Error, LastDisclosureTimestamp, - }, - types::Auditor, - types::Commitment, -}; -use frame_support::{ensure, pallet_prelude::*}; -use frame_system; -use pallet_zk_verifier::ZkVerifierPort; -use sp_runtime::traits::Saturating; - -pub struct DisclosureValidationService; - -impl DisclosureValidationService { - /// Verify disclosure proof using ZK verifier (internal validation) - pub fn verify_proof_internal( - proof_bytes: &[u8], - public_signals: &[u8], - ) -> DispatchResult { - ensure!(proof_bytes.len() == 128, Error::::InvalidProof); - ensure!(public_signals.len() == 76, Error::::InvalidPublicSignals); - - let is_valid = T::ZkVerifier::verify_disclosure_proof(proof_bytes, public_signals, None)?; - ensure!(is_valid, Error::::InvalidProof); - - Ok(()) - } - - /// Validate public signals consistency - pub fn validate_public_signals( - commitment: &Commitment, - public_signals: &[u8], - ) -> DispatchResult { - ensure!(public_signals.len() == 76, Error::::InvalidPublicSignals); - - let commitment_from_signals = &public_signals[0..32]; - let revealed_value_bytes = &public_signals[32..40]; - let revealed_asset_id_bytes = &public_signals[40..44]; - let _revealed_owner_hash = &public_signals[44..76]; - - ensure!( - commitment_from_signals == commitment.0, - Error::::InvalidPublicSignals - ); - - let _revealed_value = u64::from_le_bytes( - revealed_value_bytes - .try_into() - .map_err(|_| Error::::InvalidPublicSignals)?, - ); - - let _revealed_asset_id = u32::from_le_bytes( - revealed_asset_id_bytes - .try_into() - .map_err(|_| Error::::InvalidPublicSignals)?, - ); - - Ok(()) - } - - /// Validate disclosure access control and rate limiting - /// - /// # SAFETY - /// Commitment ownership is implicitly enforced by the ZK proof verified in - /// `verify_disclosure_proof`: the Groth16 circuit binds the proof to the - /// commitment and the caller's viewing key. - pub fn validate_disclosure_access( - who: &::AccountId, - commitment: &Commitment, - auditor: Option<&::AccountId>, - ) -> DispatchResult { - if let Some(policy) = AuditPolicies::::get(who) { - if let Some(valid_until) = policy.valid_until { - let current_block = frame_system::Pallet::::block_number(); - ensure!(current_block <= valid_until, Error::::AuditPolicyExpired); - } - - if let Some(auditor_id) = auditor { - let is_authorized = policy - .auditors - .iter() - .any(|a| matches!(a, Auditor::Account(acc) if acc == auditor_id)); - ensure!(is_authorized, Error::::UnauthorizedAuditor); - - let request = DisclosureRequests::::get(who, auditor_id) - .ok_or(Error::::DisclosureRequestNotFound)?; - let current_block = frame_system::Pallet::::block_number(); - ensure!( - current_block <= request.expires_at, - Error::::DisclosureRequestExpired - ); - } - - if let Some(max_frequency) = policy.max_frequency { - let current_block = frame_system::Pallet::::block_number(); - - if let Some(last_disclosure) = LastDisclosureTimestamp::::get(who, commitment) { - let blocks_since_last = current_block.saturating_sub(last_disclosure); - ensure!( - blocks_since_last >= max_frequency, - Error::::DisclosureFrequencyExceeded - ); - } - } - } else { - ensure!(auditor.is_none(), Error::::UnauthorizedAuditor); - } - - Ok(()) - } - - /// Verify disclosure proof (cryptographic verification with full context) - pub fn verify_disclosure_proof( - proof: &[u8], - public_signals: &[u8], - commitment: &Commitment, - ) -> Result<(), DispatchError> { - ensure!(proof.len() == 128, Error::::InvalidDisclosureRecord); - ensure!( - public_signals.len() == 76, - Error::::InvalidDisclosureRecord - ); - - ensure!( - CommitmentMemos::::contains_key(commitment), - Error::::CommitmentNotFound - ); - - ensure!( - public_signals[0..32] == commitment.0, - Error::::InvalidDisclosureRecord - ); - - let is_valid = T::ZkVerifier::verify_disclosure_proof(proof, public_signals, None)?; - - ensure!(is_valid, Error::::InvalidDisclosureRecord); - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - mock::{Test, new_test_ext}, - operations::disclosure::policy, - pallet::{DisclosureRequests, LastDisclosureTimestamp}, - storage::CommitmentRepository, - types::{Auditor, Commitment, DisclosureCondition, DisclosureRequest}, - }; - use frame_support::{assert_noop, assert_ok}; - - // ── helpers ────────────────────────────────────────────────────────────── - - fn valid_proof() -> [u8; 128] { - [0x01u8; 128] - } - - /// 76-byte public signals where signals[0..32] == commitment bytes. - fn valid_signals(commitment: &Commitment) -> [u8; 76] { - let mut buf = [0u8; 76]; - buf[0..32].copy_from_slice(&commitment.0); - // non-zero value so parse gives Some - buf[32..40].copy_from_slice(&100u64.to_le_bytes()); - buf[40..44].copy_from_slice(&1u32.to_le_bytes()); - buf - } - - fn commitment(seed: u8) -> Commitment { - Commitment::new([seed; 32]) - } - - fn set_policy_simple(owner: u64, auditor: u64) { - let auditors = BoundedVec::try_from(vec![Auditor::Account(auditor)]).unwrap(); - let conditions: BoundedVec, ConstU32<10>> = - BoundedVec::default(); - policy::set_audit_policy::(&owner, auditors, conditions, None, None).unwrap(); - } - - fn store_request(target: u64, auditor: u64, expires_at: u64) { - use frame_support::BoundedVec; - let req = DisclosureRequest { - auditor, - target, - requested_at: 1u64, - expires_at, - reason: BoundedVec::default(), - }; - DisclosureRequests::::insert(target, auditor, req); - } - - // ── verify_proof_internal ──────────────────────────────────────────────── - - #[test] - fn verify_proof_internal_valid_works() { - new_test_ext().execute_with(|| { - let c = commitment(1); - assert_ok!(DisclosureValidationService::verify_proof_internal::( - &valid_proof(), - &valid_signals(&c), - )); - }); - } - - #[test] - fn verify_proof_internal_wrong_proof_len_fails() { - new_test_ext().execute_with(|| { - let c = commitment(1); - assert_noop!( - DisclosureValidationService::verify_proof_internal::( - &[0x01u8; 64], - &valid_signals(&c), - ), - crate::pallet::Error::::InvalidProof - ); - }); - } - - #[test] - fn verify_proof_internal_wrong_signals_len_fails() { - new_test_ext().execute_with(|| { - assert_noop!( - DisclosureValidationService::verify_proof_internal::( - &valid_proof(), - &[0x01u8; 32], - ), - crate::pallet::Error::::InvalidPublicSignals - ); - }); - } - - #[test] - fn verify_proof_internal_empty_proof_fails() { - new_test_ext().execute_with(|| { - let c = commitment(1); - // MockZkVerifier returns Err for empty proof - assert!( - DisclosureValidationService::verify_proof_internal::( - &[], - &valid_signals(&c), - ) - .is_err() - ); - }); - } - - // ── validate_public_signals ────────────────────────────────────────────── - - #[test] - fn validate_public_signals_valid_works() { - new_test_ext().execute_with(|| { - let c = commitment(0x42); - assert_ok!( - DisclosureValidationService::validate_public_signals::( - &c, - &valid_signals(&c), - ) - ); - }); - } - - #[test] - fn validate_public_signals_wrong_len_fails() { - new_test_ext().execute_with(|| { - let c = commitment(1); - assert_noop!( - DisclosureValidationService::validate_public_signals::(&c, &[0u8; 40]), - crate::pallet::Error::::InvalidPublicSignals - ); - }); - } - - #[test] - fn validate_public_signals_commitment_mismatch_fails() { - new_test_ext().execute_with(|| { - let c = commitment(0xAA); - let signals = valid_signals(&commitment(0xBB)); // different seed - assert_noop!( - DisclosureValidationService::validate_public_signals::(&c, &signals), - crate::pallet::Error::::InvalidPublicSignals - ); - }); - } - - // ── validate_disclosure_access ─────────────────────────────────────────── - - #[test] - fn access_no_policy_no_auditor_works() { - new_test_ext().execute_with(|| { - let c = commitment(1); - // No policy set → auditor must be None (self-disclosure is allowed) - assert_ok!(DisclosureValidationService::validate_disclosure_access::< - Test, - >(&1u64, &c, None,)); - }); - } - - #[test] - fn access_no_policy_with_auditor_fails() { - new_test_ext().execute_with(|| { - let c = commitment(1); - assert_noop!( - DisclosureValidationService::validate_disclosure_access::( - &1u64, - &c, - Some(&2u64), - ), - crate::pallet::Error::::UnauthorizedAuditor - ); - }); - } - - #[test] - fn access_policy_expired_fails() { - new_test_ext().execute_with(|| { - // Policy with valid_until = 0 (already past block 1) - let owner: u64 = 1; - let auditors = BoundedVec::try_from(vec![Auditor::Account(2u64)]).unwrap(); - let conditions: BoundedVec, ConstU32<10>> = - BoundedVec::default(); - policy::set_audit_policy::(&owner, auditors, conditions, None, Some(0u64)) - .unwrap(); - - let c = commitment(1); - assert_noop!( - DisclosureValidationService::validate_disclosure_access::(&owner, &c, None,), - crate::pallet::Error::::AuditPolicyExpired - ); - }); - } - - #[test] - fn access_unauthorized_auditor_fails() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - // Policy only authorizes auditor 2 - set_policy_simple(owner, 2u64); - let c = commitment(1); - - assert_noop!( - DisclosureValidationService::validate_disclosure_access::( - &owner, - &c, - Some(&3u64), // auditor 3 not in policy - ), - crate::pallet::Error::::UnauthorizedAuditor - ); - }); - } - - #[test] - fn access_request_not_found_fails() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - set_policy_simple(owner, 2u64); - let c = commitment(1); - // No DisclosureRequest stored → DisclosureRequestNotFound - assert_noop!( - DisclosureValidationService::validate_disclosure_access::( - &owner, - &c, - Some(&2u64), - ), - crate::pallet::Error::::DisclosureRequestNotFound - ); - }); - } - - #[test] - fn access_request_expired_fails() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - set_policy_simple(owner, 2u64); - // Request expired at block 0 (current block is 1) - store_request(owner, 2u64, 0u64); - let c = commitment(1); - - assert_noop!( - DisclosureValidationService::validate_disclosure_access::( - &owner, - &c, - Some(&2u64), - ), - crate::pallet::Error::::DisclosureRequestExpired - ); - }); - } - - #[test] - fn access_valid_request_with_auditor_works() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - set_policy_simple(owner, 2u64); - store_request(owner, 2u64, 9999u64); - let c = commitment(1); - - assert_ok!(DisclosureValidationService::validate_disclosure_access::< - Test, - >(&owner, &c, Some(&2u64),)); - }); - } - - #[test] - fn access_frequency_exceeded_fails() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let auditors = BoundedVec::try_from(vec![Auditor::Account(2u64)]).unwrap(); - let conditions: BoundedVec, ConstU32<10>> = - BoundedVec::default(); - // max_frequency = 100 blocks - policy::set_audit_policy::(&owner, auditors, conditions, Some(100u64), None) - .unwrap(); - - let c = commitment(1); - // Simulate last disclosure at block 1 (same as current) - LastDisclosureTimestamp::::insert(owner, c, 1u64); - - // blocks_since_last = 1 - 1 = 0 < 100 → should fail - assert_noop!( - DisclosureValidationService::validate_disclosure_access::(&owner, &c, None,), - crate::pallet::Error::::DisclosureFrequencyExceeded - ); - }); - } - - #[test] - fn access_frequency_ok_after_enough_blocks() { - new_test_ext().execute_with(|| { - let owner: u64 = 1; - let auditors = BoundedVec::try_from(vec![Auditor::Account(2u64)]).unwrap(); - let conditions: BoundedVec, ConstU32<10>> = - BoundedVec::default(); - policy::set_audit_policy::(&owner, auditors, conditions, Some(5u64), None) - .unwrap(); - - let c = commitment(1); - // Last disclosure 10 blocks ago — 10 >= 5 → allowed - LastDisclosureTimestamp::::insert(owner, c, 0u64); - frame_system::Pallet::::set_block_number(10); - - assert_ok!(DisclosureValidationService::validate_disclosure_access::< - Test, - >(&owner, &c, None,)); - }); - } - - // ── verify_disclosure_proof ────────────────────────────────────────────── - - #[test] - fn verify_disclosure_proof_works() { - new_test_ext().execute_with(|| { - let c = commitment(0xCC); - // Register commitment memo so the lookup passes - CommitmentRepository::store_memo::(c, crate::types::EncryptedMemo::default()); - - assert_ok!( - DisclosureValidationService::verify_disclosure_proof::( - &valid_proof(), - &valid_signals(&c), - &c, - ) - ); - }); - } - - #[test] - fn verify_disclosure_proof_commitment_not_found_fails() { - new_test_ext().execute_with(|| { - let c = commitment(0xDD); - // No memo stored → CommitmentNotFound - assert_noop!( - DisclosureValidationService::verify_disclosure_proof::( - &valid_proof(), - &valid_signals(&c), - &c, - ), - crate::pallet::Error::::CommitmentNotFound - ); - }); - } - - #[test] - fn verify_disclosure_proof_wrong_commitment_in_signals_fails() { - new_test_ext().execute_with(|| { - let c = commitment(0xEE); - CommitmentRepository::store_memo::(c, crate::types::EncryptedMemo::default()); - - // Signals encode a different commitment - let signals = valid_signals(&commitment(0xFF)); - assert_noop!( - DisclosureValidationService::verify_disclosure_proof::( - &valid_proof(), - &signals, - &c, - ), - crate::pallet::Error::::InvalidDisclosureRecord - ); - }); - } - - #[test] - fn verify_disclosure_proof_wrong_proof_len_fails() { - new_test_ext().execute_with(|| { - let c = commitment(1); - CommitmentRepository::store_memo::(c, crate::types::EncryptedMemo::default()); - - assert_noop!( - DisclosureValidationService::verify_disclosure_proof::( - &[0x01u8; 64], - &valid_signals(&c), - &c, - ), - crate::pallet::Error::::InvalidDisclosureRecord - ); - }); - } -} diff --git a/frame/shielded-pool/src/operations/fees.rs b/frame/shielded-pool/src/operations/fees.rs index 55e54d5c..5e7f22c2 100644 --- a/frame/shielded-pool/src/operations/fees.rs +++ b/frame/shielded-pool/src/operations/fees.rs @@ -2,17 +2,11 @@ use crate::{ pallet::{Assets, CommitmentMemos, Config, Error, Event, Pallet}, - storage::PoolBalanceRepository, types::{Commitment, EncryptedMemo}, }; -use frame_support::{ - ensure, - pallet_prelude::*, - traits::{Currency, ExistenceRequirement}, -}; +use frame_support::{ensure, pallet_prelude::*}; use pallet_relayer::RelayerInterface as _; use pallet_zk_verifier::ZkVerifierPort; -use parity_scale_codec::Decode; use sp_runtime::SaturatedConversion; pub type BalanceOf = <::Currency as frame_support::traits::Currency< @@ -24,10 +18,10 @@ pub struct FeeOperation; impl FeeOperation { /// Claim accumulated relay fees as a private shielded commitment. /// - /// Verifies a selective-disclosure ZK proof (disclosure circuit) proving - /// that `commitment` encodes exactly `amount` and `asset_id`. The proof - /// prevents validators from inserting inflated commitments while debiting - /// only a small pending-fee balance. + /// Requires a `value_proof` (Groth16 over `value_proof.circom`) proving that + /// the supplied `commitment` encodes exactly `(amount, asset_id, owner_pubkey, + /// blinding)` via `Poseidon4`. Without this proof a relayer could craft a + /// commitment encoding an inflated amount and drain the pool on `unshield`. /// /// # public_signals layout (76 bytes) /// `commitment[0..32] | value[32..40] | asset_id[40..44] | owner_hash[44..76]` @@ -52,11 +46,11 @@ impl FeeOperation { // amount must fit in u64 (circuit signal size) ensure!(amount_u128 <= u64::MAX as u128, Error::::InvalidAmount); - // Verify ZK disclosure proof (proves commitment = Poseidon4(amount, assetId, pk, r)) + // Verify ZK value_proof (proves commitment = Poseidon4(amount, assetId, pk, r)) ensure!(proof.len() == 128, Error::::InvalidProof); ensure!(public_signals.len() == 76, Error::::InvalidPublicSignals); - let is_valid = T::ZkVerifier::verify_disclosure_proof(&proof, &public_signals, None)?; + let is_valid = T::ZkVerifier::verify_value_proof(&proof, &public_signals, None)?; ensure!(is_valid, Error::::InvalidProof); // signals[0..32]: commitment must match the extrinsic argument @@ -98,89 +92,19 @@ impl FeeOperation { Ok(()) } - - /// Claim accumulated relay fees and transfer them directly to the relayer's - /// EVM mirror account (H160[0..20] ++ [0x00; 12]). - /// - /// This is the gasless-relayer convenience path: the relayer's H160 EVM - /// account is automatically funded with public ORB, so it can pay gas for - /// future relay transactions without any manual token bridging. - /// - /// Unlike `claim_shielded`, no ZK proof is required — the transfer is - /// public. Security comes from the `PendingRelayerFees` ledger: the - /// caller can only claim what the pool has already credited to them. - /// - /// # Errors - /// * `InvalidAssetId` - Asset is not registered - /// * `RelayerNotRegistered` - Caller has no H160 in the relayer registry - /// * `InsufficientPendingFees` - Pending balance < `amount` - /// * `InsufficientPoolBalance` - Pool lacks tokens (should never happen) - pub fn claim_to_evm( - validator: T::AccountId, - asset_id: u32, - amount: BalanceOf, - ) -> DispatchResult { - ensure!( - Assets::::contains_key(asset_id), - Error::::InvalidAssetId - ); - - let amount_u128: u128 = amount.saturated_into(); - - ensure!(!amount.is_zero(), Error::::InvalidAmount); - - // Resolve the registered H160 for this validator. - let evm_address = T::Relayer::registered_evm_address(&validator) - .ok_or(Error::::RelayerNotRegistered)?; - - // Derive the EVM mirror AccountId: H160[0..20] ++ [0x00; 12]. - let mut mirror_bytes = [0u8; 32]; - mirror_bytes[..20].copy_from_slice(evm_address.as_bytes()); - let mirror_account = T::AccountId::decode(&mut &mirror_bytes[..]) - .map_err(|_| Error::::InvalidRecipient)?; - - ensure!( - T::Relayer::pending_relay_fees(&validator, asset_id) >= amount_u128, - Error::::InsufficientPendingFees - ); - T::Relayer::consume_relay_fee(&validator, asset_id, amount_u128)?; - - // Transfer tokens from the shielded pool to the EVM mirror account. - T::Currency::transfer( - &Pallet::::pool_account_id(), - &mirror_account, - amount, - ExistenceRequirement::AllowDeath, - )?; - - // Keep the pool balance tracking consistent. - PoolBalanceRepository::decrease_balance::(asset_id, amount); - - Pallet::::deposit_event(Event::RelayFeesClaimedToEvm { - validator, - evm_address, - asset_id, - amount, - }); - - Ok(()) - } } #[cfg(test)] mod tests { use super::*; use crate::{ - mock::{ - Test, mock_evm_address_set, mock_pending_fees_get, mock_pending_fees_set, new_test_ext, - }, + mock::{Test, mock_pending_fees_get, mock_pending_fees_set, new_test_ext}, operations::assets::AssetOperation, pallet::Event as PalletEvent, - storage::{CommitmentRepository, PoolBalanceRepository}, + storage::CommitmentRepository, types::{Commitment, EncryptedMemo, MAX_ENCRYPTED_MEMO_SIZE}, }; - use frame_support::{assert_noop, assert_ok, traits::Currency}; - use sp_core::H160; + use frame_support::{assert_noop, assert_ok}; // ── helpers ─────────────────────────────────────────────────────────────── @@ -417,7 +341,7 @@ mod tests { #[test] fn claim_shielded_zero_amount_fails_with_invalid_amount() { - // amount == 0 must be rejected before the ZK check. A disclosure proof that + // amount == 0 must be rejected before the ZK check. A value_proof that // encodes value=0 is cryptographically valid but inserts a worthless leaf into // the Merkle tree — cheap spam that wastes tree capacity. new_test_ext().execute_with(|| { @@ -657,238 +581,4 @@ mod tests { ); }); } - - // ── helpers for claim_to_evm ────────────────────────────────────────────── - - /// EVM address used across claim_to_evm tests (H160::from_low_u64_be(0xA11CE)). - fn alice_evm() -> H160 { - H160::from_low_u64_be(0xA11CE) - } - - /// Deposit tokens into the pool (both Currency and PoolBalancePerAsset). - fn fund_pool(asset_id: u32, total: u128) { - let pool = crate::Pallet::::pool_account_id(); - let _ = as Currency>::deposit_creating(&pool, total); - PoolBalanceRepository::set_asset_balance::(asset_id, total); - } - - /// Derive the mirror AccountId for an H160 (H160[0..20] ++ [0x00; 12]). - fn mirror_account(evm: H160) -> u64 { - use parity_scale_codec::Decode; - let mut bytes = [0u8; 32]; - bytes[..20].copy_from_slice(evm.as_bytes()); - u64::decode(&mut &bytes[..]).unwrap() - } - - // ── claim_to_evm ───────────────────────────────────────────────────────── - - #[test] - fn claim_to_evm_works() { - new_test_ext().execute_with(|| { - let validator: u64 = 1; - let asset_id = setup_asset(); - let amount = 500u128; - - mock_evm_address_set(validator, alice_evm()); - mock_pending_fees_set(validator, asset_id, amount); - fund_pool(asset_id, amount); - - assert_ok!(FeeOperation::claim_to_evm::( - validator, asset_id, amount - )); - }); - } - - #[test] - fn claim_to_evm_transfers_tokens_to_mirror_account() { - new_test_ext().execute_with(|| { - let validator: u64 = 1; - let asset_id = setup_asset(); - let amount = 400u128; - - mock_evm_address_set(validator, alice_evm()); - mock_pending_fees_set(validator, asset_id, amount); - fund_pool(asset_id, amount); - - assert_ok!(FeeOperation::claim_to_evm::( - validator, asset_id, amount - )); - - let mirror = mirror_account(alice_evm()); - assert_eq!( - pallet_balances::Pallet::::free_balance(mirror), - amount, - "mirror account should hold the claimed amount" - ); - }); - } - - #[test] - fn claim_to_evm_deducts_pending_fees() { - new_test_ext().execute_with(|| { - let validator: u64 = 1; - let asset_id = setup_asset(); - let initial = 1000u128; - let claim = 300u128; - - mock_evm_address_set(validator, alice_evm()); - mock_pending_fees_set(validator, asset_id, initial); - fund_pool(asset_id, initial); - - assert_ok!(FeeOperation::claim_to_evm::( - validator, asset_id, claim - )); - - assert_eq!( - mock_pending_fees_get(validator, asset_id), - initial - claim, - "pending fees must decrease by the claimed amount" - ); - }); - } - - #[test] - fn claim_to_evm_decreases_pool_balance_tracker() { - new_test_ext().execute_with(|| { - let validator: u64 = 1; - let asset_id = setup_asset(); - let amount = 250u128; - - mock_evm_address_set(validator, alice_evm()); - mock_pending_fees_set(validator, asset_id, amount); - fund_pool(asset_id, amount); - - assert_ok!(FeeOperation::claim_to_evm::( - validator, asset_id, amount - )); - - assert_eq!( - PoolBalanceRepository::get_asset_balance::(asset_id), - 0u128, - "PoolBalancePerAsset must reflect the withdrawal" - ); - }); - } - - #[test] - fn claim_to_evm_emits_event() { - new_test_ext().execute_with(|| { - let validator: u64 = 1; - let asset_id = setup_asset(); - let amount = 150u128; - - mock_evm_address_set(validator, alice_evm()); - mock_pending_fees_set(validator, asset_id, amount); - fund_pool(asset_id, amount); - - assert_ok!(FeeOperation::claim_to_evm::( - validator, asset_id, amount - )); - - let events = frame_system::Pallet::::events(); - let found = events.iter().any(|r| { - matches!( - &r.event, - crate::mock::RuntimeEvent::ShieldedPool(PalletEvent::RelayFeesClaimedToEvm { - validator: ev, - evm_address: ea, - asset_id: eid, - amount: eamt, - }) if *ev == validator && *ea == alice_evm() && *eid == asset_id && *eamt == amount - ) - }); - assert!(found, "RelayFeesClaimedToEvm event not emitted"); - }); - } - - #[test] - fn claim_to_evm_fails_if_invalid_asset() { - new_test_ext().execute_with(|| { - let validator: u64 = 1; - mock_evm_address_set(validator, alice_evm()); - - assert_noop!( - FeeOperation::claim_to_evm::(validator, 9999u32, 100u128), - crate::pallet::Error::::InvalidAssetId - ); - }); - } - - #[test] - fn claim_to_evm_fails_if_not_registered() { - new_test_ext().execute_with(|| { - let validator: u64 = 1; - let asset_id = setup_asset(); - // No mock_evm_address_set → registered_evm_address returns None - - mock_pending_fees_set(validator, asset_id, 500u128); - - assert_noop!( - FeeOperation::claim_to_evm::(validator, asset_id, 100u128), - crate::pallet::Error::::RelayerNotRegistered - ); - }); - } - - #[test] - fn claim_to_evm_fails_if_insufficient_pending_fees() { - new_test_ext().execute_with(|| { - let validator: u64 = 1; - let asset_id = setup_asset(); - - mock_evm_address_set(validator, alice_evm()); - mock_pending_fees_set(validator, asset_id, 50u128); // only 50 available - fund_pool(asset_id, 1_000u128); - - assert_noop!( - FeeOperation::claim_to_evm::(validator, asset_id, 100u128), - crate::pallet::Error::::InsufficientPendingFees - ); - }); - } - - #[test] - fn claim_to_evm_partial_claim_leaves_remaining_in_pool() { - new_test_ext().execute_with(|| { - let validator: u64 = 1; - let asset_id = setup_asset(); - let total = 800u128; - let claim = 200u128; - - mock_evm_address_set(validator, alice_evm()); - mock_pending_fees_set(validator, asset_id, total); - fund_pool(asset_id, total); - - assert_ok!(FeeOperation::claim_to_evm::( - validator, asset_id, claim - )); - - // Pool balance tracker must reflect only the claimed portion removed - assert_eq!( - PoolBalanceRepository::get_asset_balance::(asset_id), - total - claim - ); - // Pending fees also decreased - assert_eq!(mock_pending_fees_get(validator, asset_id), total - claim); - }); - } - - #[test] - fn claim_to_evm_zero_amount_fails() { - // amount == 0 must be rejected: a zero-value EVM claim is a no-op that wastes - // block space and emits a misleading event without moving any funds. - new_test_ext().execute_with(|| { - let validator: u64 = 1; - let asset_id = setup_asset(); - - mock_evm_address_set(validator, alice_evm()); - mock_pending_fees_set(validator, asset_id, 500u128); - fund_pool(asset_id, 500u128); - - assert_noop!( - FeeOperation::claim_to_evm::(validator, asset_id, 0u128), - crate::pallet::Error::::InvalidAmount - ); - }); - } } diff --git a/frame/shielded-pool/src/operations/mod.rs b/frame/shielded-pool/src/operations/mod.rs index 5f733543..dec72558 100644 --- a/frame/shielded-pool/src/operations/mod.rs +++ b/frame/shielded-pool/src/operations/mod.rs @@ -5,7 +5,6 @@ //! and infrastructure services. pub mod assets; -pub mod disclosure; pub mod fees; pub mod private_transfer; pub mod shield; diff --git a/frame/shielded-pool/src/storage.rs b/frame/shielded-pool/src/storage.rs index 96c6389d..c1a9d41a 100644 --- a/frame/shielded-pool/src/storage.rs +++ b/frame/shielded-pool/src/storage.rs @@ -5,20 +5,15 @@ use crate::{ pallet::{ - Assets, AuditPolicies, AuditTrailStorage, BalanceOf, CommitmentMemos, - CommitmentToLeafIndex, Config, DisclosureCounters, DisclosureRecords, DisclosureRequests, - Error, HistoricPoseidonRoots, HistoricRootsOrder, LastDisclosureTimestamp, MerkleLeaves, - MerkleTreeFrontier, MerkleTreeSize, NextAssetId, NextAuditTrailId, NullifierSet, - PoolBalancePerAsset, PoseidonRoot, TotalCommitmentsInserted, TotalNullifiersSpent, - }, - types::{ - AssetMetadata, AuditPolicy, AuditTrail, Commitment, DisclosureRecord, DisclosureRequest, - EncryptedMemo, Hash, + Assets, BalanceOf, CommitmentMemos, CommitmentToLeafIndex, Config, HistoricPoseidonRoots, + HistoricRootsOrder, MerkleLeaves, MerkleTreeFrontier, MerkleTreeSize, NextAssetId, + NullifierSet, PoolBalancePerAsset, PoseidonRoot, TotalCommitmentsInserted, + TotalNullifiersSpent, }, + types::{AssetMetadata, Commitment, EncryptedMemo, Hash}, }; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::BlockNumberFor; -use parity_scale_codec::Encode; use sp_runtime::traits::Saturating; // ════════════════════════════════════════════════════════════════════════════ @@ -213,182 +208,16 @@ impl PoolBalanceRepository { } } -// ════════════════════════════════════════════════════════════════════════════ -// AuditRepository // ════════════════════════════════════════════════════════════════════════════ -pub struct AuditRepository; - -impl AuditRepository { - // ── Policies ──────────────────────────────────────────────────────── - pub fn get_policy( - account: &T::AccountId, - ) -> Option, BlockNumberFor>> { - AuditPolicies::::get(account) - } - pub fn store_policy( - account: &T::AccountId, - policy: AuditPolicy, BlockNumberFor>, - ) { - AuditPolicies::::insert(account, policy); - } - pub fn get_audit_policy( - account: &T::AccountId, - ) -> Option, BlockNumberFor>> { - AuditPolicies::::get(account) - } - pub fn set_audit_policy( - account: T::AccountId, - policy: AuditPolicy, BlockNumberFor>, - ) { - AuditPolicies::::insert(account, policy); - } - pub fn remove_audit_policy(account: &T::AccountId) { - AuditPolicies::::remove(account); - } - - // ── Disclosure Requests ───────────────────────────────────────────── - pub fn get_disclosure_request( - target: &T::AccountId, - auditor: &T::AccountId, - ) -> Option>> { - DisclosureRequests::::get(target, auditor) - } - pub fn store_disclosure_request( - target: T::AccountId, - auditor: T::AccountId, - request: DisclosureRequest>, - ) { - DisclosureRequests::::insert(target, auditor, request); - } - pub fn remove_disclosure_request(target: &T::AccountId, auditor: &T::AccountId) { - DisclosureRequests::::remove(target, auditor); - } - pub fn has_disclosure_request( - target: &T::AccountId, - auditor: &T::AccountId, - ) -> bool { - DisclosureRequests::::contains_key(target, auditor) - } - - // ── Disclosure Records ────────────────────────────────────────────── - pub fn get_disclosure_record( - commitment: Commitment, - key: &T::AccountId, - ) -> Option>> { - DisclosureRecords::::get(commitment, key) - } - pub fn store_disclosure_record( - commitment: Commitment, - key: &T::AccountId, - record: DisclosureRecord>, - ) -> sp_runtime::DispatchResult { - frame_support::ensure!( - !DisclosureRecords::::contains_key(commitment, key), - Error::::DisclosureRecordAlreadyExists - ); - DisclosureRecords::::insert(commitment, key, record); - Ok(()) - } - pub fn has_disclosure_record(commitment: Commitment, key: &T::AccountId) -> bool { - DisclosureRecords::::contains_key(commitment, key) - } - - // ── Audit Trail ───────────────────────────────────────────────────── - pub fn get_audit_trail( - trail_hash: &Hash, - ) -> Option>> { - AuditTrailStorage::::get(trail_hash) - } - pub fn store_audit_trail( - trail_hash: Hash, - trail: AuditTrail>, - ) { - AuditTrailStorage::::insert(trail_hash, trail); - } - pub fn get_next_audit_trail_id() -> u64 { - NextAuditTrailId::::get() - } - pub fn increment_audit_trail_id() -> u64 { - let current = Self::get_next_audit_trail_id::(); - NextAuditTrailId::::put(current.saturating_add(1)); - current - } - - // ── Rate Limiting ─────────────────────────────────────────────────── - pub fn get_last_disclosure_timestamp( - account: &T::AccountId, - commitment: Commitment, - ) -> Option> { - LastDisclosureTimestamp::::get(account, commitment) - } - pub fn update_disclosure_timestamp( - account: &T::AccountId, - commitment: Commitment, - block: BlockNumberFor, - ) { - LastDisclosureTimestamp::::insert(account, commitment, block); - } - pub fn set_last_disclosure_timestamp( - account: T::AccountId, - commitment: Commitment, - block: BlockNumberFor, - ) { - LastDisclosureTimestamp::::insert(account, commitment, block); - } - - // ── Commitment Memos ──────────────────────────────────────────────── - pub fn has_commitment_memo(commitment: Commitment) -> bool { - CommitmentMemos::::contains_key(commitment) - } - - // ── Audit Trail Creation ──────────────────────────────────────────── - pub fn create_audit_trail( - account: &T::AccountId, - auditor: &T::AccountId, - commitment: Commitment, - disclosure_type: &[u8], - ) -> Result<[u8; 32], sp_runtime::DispatchError> { - let trail_id = Self::increment_audit_trail_id::(); - let trail_hash = { - let mut data = trail_id.to_le_bytes().to_vec(); - data.extend_from_slice(&account.encode()); - data.extend_from_slice(&auditor.encode()); - data.extend_from_slice(&commitment.0); - sp_io::hashing::blake2_256(&data) - }; - let current_block = frame_system::Pallet::::block_number(); - let trail = AuditTrail { - account: account.clone(), - auditor: auditor.clone(), - timestamp: current_block, - disclosure_type: disclosure_type.to_vec().try_into().unwrap_or_default(), - trail_hash, - }; - Self::store_audit_trail::(trail_hash, trail); - Ok(trail_hash) - } - - // ── Disclosure Counters ───────────────────────────────────────────── - pub fn get_disclosure_counter(target: &T::AccountId, auditor: &T::AccountId) -> u32 { - DisclosureCounters::::get(target, auditor) - } - pub fn increment_disclosure_counter(target: &T::AccountId, auditor: &T::AccountId) { - DisclosureCounters::::mutate(target, auditor, |c| *c = c.saturating_add(1)); - } -} - #[cfg(test)] mod tests { use super::*; use crate::{ mock::{Test, new_test_ext}, - types::{ - AssetMetadata, AuditPolicy, Commitment, DisclosureCondition, DisclosureRecord, - DisclosureRequest, EncryptedMemo, MAX_ENCRYPTED_MEMO_SIZE, Nullifier, - }, + types::{AssetMetadata, Commitment, EncryptedMemo, MAX_ENCRYPTED_MEMO_SIZE, Nullifier}, }; - use frame_support::{BoundedVec, assert_ok, pallet_prelude::ConstU32}; + use frame_support::{BoundedVec, pallet_prelude::ConstU32}; // ── helpers ─────────────────────────────────────────────────────────────── @@ -741,176 +570,5 @@ mod tests { }); } - // ── AuditRepository ────────────────────────────────────────────────────── - - #[test] - fn audit_repo_policy_store_and_get() { - new_test_ext().execute_with(|| { - let policy: AuditPolicy = AuditPolicy { - auditors: BoundedVec::new(), - conditions: BoundedVec::new(), - max_frequency: None, - valid_until: None, - version: 1, - }; - AuditRepository::store_policy::(&1u64, policy); - let got = AuditRepository::get_policy::(&1u64).unwrap(); - assert_eq!(got.version, 1); - }); - } - - #[test] - fn audit_repo_policy_remove() { - new_test_ext().execute_with(|| { - let policy: AuditPolicy = AuditPolicy { - auditors: BoundedVec::new(), - conditions: BoundedVec::new(), - max_frequency: None, - valid_until: None, - version: 2, - }; - AuditRepository::store_policy::(&2u64, policy); - AuditRepository::remove_audit_policy::(&2u64); - assert!(AuditRepository::get_policy::(&2u64).is_none()); - }); - } - - #[test] - fn audit_repo_disclosure_request_store_and_get() { - new_test_ext().execute_with(|| { - let req = DisclosureRequest { - auditor: 10u64, - target: 20u64, - requested_at: 1u64, - expires_at: 1000u64, - reason: BoundedVec::try_from(b"audit".to_vec()).unwrap(), - }; - AuditRepository::store_disclosure_request::(20u64, 10u64, req); - assert!(AuditRepository::has_disclosure_request::( - &20u64, &10u64 - )); - assert!(AuditRepository::get_disclosure_request::(&20u64, &10u64).is_some()); - }); - } - - #[test] - fn audit_repo_disclosure_request_remove() { - new_test_ext().execute_with(|| { - let req = DisclosureRequest { - auditor: 11u64, - target: 21u64, - requested_at: 2u64, - expires_at: 2000u64, - reason: BoundedVec::try_from(b"test".to_vec()).unwrap(), - }; - AuditRepository::store_disclosure_request::(21u64, 11u64, req); - AuditRepository::remove_disclosure_request::(&21u64, &11u64); - assert!(!AuditRepository::has_disclosure_request::( - &21u64, &11u64 - )); - }); - } - - #[test] - fn audit_repo_disclosure_record_store_and_has() { - new_test_ext().execute_with(|| { - let c = test_commitment(0x30); - let record = DisclosureRecord { - revealed_value: Some(100u64), - revealed_asset_id: Some(0), - revealed_owner_hash: None, - requester: 5u64, - timestamp: 1u64, - }; - assert_ok!(AuditRepository::store_disclosure_record::( - c, &5u64, record - )); - assert!(AuditRepository::has_disclosure_record::(c, &5u64)); - }); - } - - #[test] - fn audit_repo_disclosure_record_duplicate_fails() { - new_test_ext().execute_with(|| { - let c = test_commitment(0x31); - let record = DisclosureRecord { - revealed_value: None, - revealed_asset_id: None, - revealed_owner_hash: None, - requester: 6u64, - timestamp: 1u64, - }; - AuditRepository::store_disclosure_record::(c, &6u64, record.clone()).unwrap(); - assert!(AuditRepository::store_disclosure_record::(c, &6u64, record).is_err()); - }); - } - - #[test] - fn audit_repo_audit_trail_id_increments() { - new_test_ext().execute_with(|| { - let id0 = AuditRepository::get_next_audit_trail_id::(); - let returned = AuditRepository::increment_audit_trail_id::(); - let id1 = AuditRepository::get_next_audit_trail_id::(); - assert_eq!(returned, id0); - assert_eq!(id1, id0 + 1); - }); - } - - #[test] - fn audit_repo_create_audit_trail_stores_entry() { - new_test_ext().execute_with(|| { - let c = test_commitment(0x40); - let hash = - AuditRepository::create_audit_trail::(&1u64, &2u64, c, b"full_disclosure") - .unwrap(); - let trail = AuditRepository::get_audit_trail::(&hash).unwrap(); - assert_eq!(trail.account, 1u64); - assert_eq!(trail.auditor, 2u64); - }); - } - - #[test] - fn audit_repo_disclosure_counter_starts_at_zero_and_increments() { - new_test_ext().execute_with(|| { - assert_eq!( - AuditRepository::get_disclosure_counter::(&1u64, &2u64), - 0 - ); - AuditRepository::increment_disclosure_counter::(&1u64, &2u64); - assert_eq!( - AuditRepository::get_disclosure_counter::(&1u64, &2u64), - 1 - ); - AuditRepository::increment_disclosure_counter::(&1u64, &2u64); - assert_eq!( - AuditRepository::get_disclosure_counter::(&1u64, &2u64), - 2 - ); - }); - } - - #[test] - fn audit_repo_rate_limit_timestamp_store_and_retrieve() { - new_test_ext().execute_with(|| { - let c = test_commitment(0x50); - assert!(AuditRepository::get_last_disclosure_timestamp::(&1u64, c).is_none()); - AuditRepository::update_disclosure_timestamp::(&1u64, c, 999u64); - assert_eq!( - AuditRepository::get_last_disclosure_timestamp::(&1u64, c), - Some(999u64) - ); - }); - } - - #[test] - fn audit_repo_has_commitment_memo_returns_correct() { - new_test_ext().execute_with(|| { - let c = test_commitment(0x60); - assert!(!AuditRepository::has_commitment_memo::(c)); - CommitmentRepository::store_memo::(c, test_memo()); - assert!(AuditRepository::has_commitment_memo::(c)); - }); - } - - fn _suppress_unused(_: DisclosureCondition, _: ConstU32<10>) {} + fn _suppress_unused(_: ConstU32<10>) {} } diff --git a/frame/shielded-pool/src/types.rs b/frame/shielded-pool/src/types.rs index 199ec198..be3f8535 100644 --- a/frame/shielded-pool/src/types.rs +++ b/frame/shielded-pool/src/types.rs @@ -454,120 +454,6 @@ impl AssetMetadata { } } -// ════════════════════════════════════════════════════════════════════════════ -// Audit / Disclosure types -// ════════════════════════════════════════════════════════════════════════════ - -/// Who can request disclosure of encrypted notes. -#[derive( - Clone, - PartialEq, - Eq, - Encode, - Decode, - DecodeWithMemTracking, - TypeInfo, - RuntimeDebug, - MaxEncodedLen -)] -pub enum Auditor { - Account(AccountId), -} - -/// Conditions that must be met for disclosure. -#[derive( - Clone, - PartialEq, - Eq, - Encode, - Decode, - DecodeWithMemTracking, - TypeInfo, - RuntimeDebug, - MaxEncodedLen -)] -pub enum DisclosureCondition { - Always, - TimeDelay { after_block: BlockNumber }, - AmountThreshold { min_amount: Balance }, -} - -/// Audit policy defining disclosure rules. -#[derive( - Clone, - PartialEq, - Eq, - Encode, - Decode, - TypeInfo, - RuntimeDebug, - MaxEncodedLen -)] -pub struct AuditPolicy { - pub auditors: BoundedVec, ConstU32<10>>, - pub conditions: BoundedVec, ConstU32<10>>, - pub max_frequency: Option, - pub valid_until: Option, - pub version: u32, -} - -/// Audit trail entry (immutable). -#[derive( - Clone, - PartialEq, - Eq, - Encode, - Decode, - TypeInfo, - RuntimeDebug, - MaxEncodedLen -)] -pub struct AuditTrail { - pub account: AccountId, - pub auditor: AccountId, - pub timestamp: BlockNumber, - pub disclosure_type: BoundedVec>, - pub trail_hash: Hash, -} - -/// A selective disclosure record stored on-chain. -#[derive( - Clone, - PartialEq, - Eq, - Encode, - Decode, - TypeInfo, - RuntimeDebug, - MaxEncodedLen -)] -pub struct DisclosureRecord { - pub revealed_value: Option, - pub revealed_asset_id: Option, - pub revealed_owner_hash: Option<[u8; 32]>, - pub requester: AccountId, - pub timestamp: BlockNumber, -} - -/// Disclosure request from an auditor. -#[derive( - Clone, - PartialEq, - Eq, - Encode, - Decode, - TypeInfo, - RuntimeDebug, - MaxEncodedLen -)] -pub struct DisclosureRequest { - pub auditor: AccountId, - pub target: AccountId, - pub requested_at: BlockNumber, - pub expires_at: BlockNumber, - pub reason: BoundedVec>, -} - #[cfg(test)] mod tests { use super::*; diff --git a/frame/shielded-pool/src/weights.rs b/frame/shielded-pool/src/weights.rs index 01cdf197..0a23cfc5 100644 --- a/frame/shielded-pool/src/weights.rs +++ b/frame/shielded-pool/src/weights.rs @@ -32,17 +32,10 @@ pub trait WeightInfo { fn shield() -> Weight; fn private_transfer() -> Weight; fn unshield() -> Weight; - fn set_audit_policy() -> Weight; - fn request_disclosure() -> Weight; - fn disclose() -> Weight; - fn reject_disclosure() -> Weight; - fn batch_submit_disclosure_proofs(n: u32) -> Weight; fn shield_batch(n: u32) -> Weight; fn register_asset() -> Weight; fn verify_asset() -> Weight; fn unverify_asset() -> Weight; - fn prune_expired_request() -> Weight; - fn revoke_disclosure_record() -> Weight; fn claim_shielded_fees() -> Weight; } @@ -145,28 +138,6 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(12_u64)) } - fn set_audit_policy() -> Weight { - Weight::from_parts(100_000, 0).saturating_add(T::DbWeight::get().writes(1)) - } - fn request_disclosure() -> Weight { - Weight::from_parts(50_000, 0).saturating_add(T::DbWeight::get().reads_writes(2, 1)) - } - fn disclose() -> Weight { - Weight::from_parts(500_000, 0).saturating_add(T::DbWeight::get().reads_writes(4, 3)) - } - fn reject_disclosure() -> Weight { - Weight::from_parts(30_000, 0).saturating_add(T::DbWeight::get().writes(1)) - } - fn batch_submit_disclosure_proofs(n: u32) -> Weight { - // Base cost for 1 proof is ~500k. - // With pairing batching, subsequent proofs are much cheaper. - // Formula: base_verified + (n-1) * optimized_verified + per_submission_storage - let base_weight = 500_000; - let optimized_weight = 150_000; // ~70% reduction for subsequent pairings - Weight::from_parts(base_weight, 0) - .saturating_add(Weight::from_parts(optimized_weight, 0).saturating_mul((n.saturating_sub(1)) as u64)) - .saturating_add(T::DbWeight::get().reads_writes(n as u64 * 3, n as u64 * 2)) - } fn shield_batch(n: u32) -> Weight { Weight::from_parts(61_000_000, 3581) .saturating_mul(n as u64) @@ -183,15 +154,6 @@ impl WeightInfo for SubstrateWeight { fn unverify_asset() -> Weight { Weight::from_parts(50_000, 0).saturating_add(T::DbWeight::get().reads_writes(1, 1)) } - /// Storage: `ShieldedPool::DisclosureRequests` (r:1 w:1) - /// Storage: `System::Number` (r:1 w:0) - fn prune_expired_request() -> Weight { - Weight::from_parts(30_000, 0).saturating_add(T::DbWeight::get().reads_writes(2, 1)) - } - /// Storage: `ShieldedPool::DisclosureRecords` (r:1 w:1) - fn revoke_disclosure_record() -> Weight { - Weight::from_parts(40_000, 0).saturating_add(T::DbWeight::get().reads_writes(1, 1)) - } fn claim_shielded_fees() -> Weight { // 1 read (PendingValidatorFees) + Merkle insert (reads/writes) + 1 write (CommitmentMemos) Weight::from_parts(70_000_000, 3581) @@ -298,25 +260,6 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().writes(12_u64)) } - fn set_audit_policy() -> Weight { - Weight::from_parts(100_000, 0).saturating_add(RocksDbWeight::get().writes(1)) - } - fn request_disclosure() -> Weight { - Weight::from_parts(50_000, 0).saturating_add(RocksDbWeight::get().reads_writes(2, 1)) - } - fn disclose() -> Weight { - Weight::from_parts(500_000, 0).saturating_add(RocksDbWeight::get().reads_writes(4, 3)) - } - fn reject_disclosure() -> Weight { - Weight::from_parts(30_000, 0).saturating_add(RocksDbWeight::get().writes(1)) - } - fn batch_submit_disclosure_proofs(n: u32) -> Weight { - let base_weight = 500_000; - let optimized_weight = 150_000; - Weight::from_parts(base_weight, 0) - .saturating_add(Weight::from_parts(optimized_weight, 0).saturating_mul((n.saturating_sub(1)) as u64)) - .saturating_add(RocksDbWeight::get().reads_writes(n as u64 * 3, n as u64 * 2)) - } fn shield_batch(n: u32) -> Weight { Weight::from_parts(61_000_000, 3581) .saturating_mul(n as u64) @@ -333,12 +276,6 @@ impl WeightInfo for () { fn unverify_asset() -> Weight { Weight::from_parts(50_000, 0).saturating_add(RocksDbWeight::get().reads_writes(1, 1)) } - fn prune_expired_request() -> Weight { - Weight::from_parts(30_000, 0).saturating_add(RocksDbWeight::get().reads_writes(2, 1)) - } - fn revoke_disclosure_record() -> Weight { - Weight::from_parts(40_000, 0).saturating_add(RocksDbWeight::get().reads_writes(1, 1)) - } fn claim_shielded_fees() -> Weight { Weight::from_parts(70_000_000, 3581) .saturating_add(RocksDbWeight::get().reads(3_u64)) From 6e82578a2cc529dd59f4a04c56a4badc8a85fd77 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 13:33:12 -0400 Subject: [PATCH 06/18] feat: update changelog, README, and runtime API for value proof integration; remove claim_relay_fees extrinsic and related tests --- frame/relayer/CHANGELOG.md | 38 ++++++++++++ frame/relayer/Cargo.toml | 2 +- frame/relayer/README.md | 6 +- frame/relayer/runtime-api/src/lib.rs | 12 ++++ frame/relayer/src/benchmarking.rs | 19 ------ frame/relayer/src/lib.rs | 27 --------- frame/relayer/src/tests/fees_tests.rs | 87 --------------------------- frame/relayer/src/tests/mod.rs | 2 +- frame/relayer/src/weights.rs | 39 ------------ 9 files changed, 55 insertions(+), 177 deletions(-) create mode 100644 frame/relayer/CHANGELOG.md diff --git a/frame/relayer/CHANGELOG.md b/frame/relayer/CHANGELOG.md new file mode 100644 index 00000000..53e19475 --- /dev/null +++ b/frame/relayer/CHANGELOG.md @@ -0,0 +1,38 @@ +# Changelog + +All notable changes to `pallet-relayer` will be documented in this file. + +## [0.2.0] - 2026-05-14 + +### Added +- **`get_active_relayers()`** — new Runtime API method returning all `(evm_address, substrate_account)` + pairs in `RelayerRegistry`. Allows clients and the RPC relay server to discover which validator + nodes have relay active (Capa 2). +- **`is_relayer_evm(evm_address)`** — new Runtime API method returning `bool`. Used by + `orbinum_relayerStatus` to surface on-chain registration state to relay operators (Capa 2). + +### Removed +- **`claim_relay_fees` extrinsic** (call_index 4) — removed. Validators must use + `pallet-shielded-pool::claim_shielded_fees` instead, which inserts a private note into the + Merkle tree and requires a `value_proof` ZK proof. +- Corresponding benchmark (`fn claim_relay_fees`) and weight entry removed. +- Unit tests `claim_relay_fees_works`, `claim_relay_fees_emits_event`, + `claim_relay_fees_insufficient_fails` removed. + +### Changed +- README: updated `claim_shielded_fees` description — ZK proof requirement changed from + *disclosure circuit* to *value_proof circuit* (CircuitId 6). +- README: comparison table updated (`ZK proof required: Yes (value_proof circuit)`). + +## [0.1.0] - 2026-04-? + +### Added +- Initial release: `pallet-relayer` with full relay infrastructure. +- **Storage**: `PendingRelayerFees` (`(AccountId, asset_id) → u128`) and + `RelayerRegistry` (`H160 → AccountId32`). +- **Events**: `RelayFeeAccumulated`, `RelayFeeConsumed`, `RelayerRegistered`, `RelayerUnregistered`. +- **Errors**: `InsufficientPendingFees`, `AlreadyRegistered`, `NotRegistered`. +- **Extrinsics**: `register_relayer`, `unregister_relayer`. +- **`RelayerInterface` trait**: `accumulate_relay_fee`, `pending_relay_fees`, `consume_relay_fee`. +- Runtime API `RelayerRuntimeApi` with initial methods. +- Full benchmark and weight coverage. diff --git a/frame/relayer/Cargo.toml b/frame/relayer/Cargo.toml index 0dee567c..49f8f9f5 100644 --- a/frame/relayer/Cargo.toml +++ b/frame/relayer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-relayer" -version = "0.1.0" +version = "0.2.0" description = "On-chain relay configuration, relayer registry and fee accounting." authors = { workspace = true } license = "GPL-3.0-or-later" diff --git a/frame/relayer/README.md b/frame/relayer/README.md index 01853c70..b0bce9f4 100644 --- a/frame/relayer/README.md +++ b/frame/relayer/README.md @@ -80,7 +80,7 @@ Decrements `amount` from the caller's pending fee balance (accounting only — n Emits: `RelayFeesConsumed { relayer, asset_id, amount }`. > **Note:** This extrinsic only updates the `PendingRelayerFees` counter. To receive real tokens, the validator must call one of the two claim paths in `pallet-shielded-pool`: -> - `claim_shielded_fees` — receives the fee as a private ZK note (requires a disclosure proof) +> - `claim_shielded_fees` — receives the fee as a private ZK note (requires a value_proof ZK proof) > - `claim_relay_fees_to_evm` — transfers public ORB directly to the H160 mirror AccountId (no proof required; ideal for refilling the relayer's EVM gas wallet) --- @@ -109,7 +109,7 @@ Both are linked at startup via `register_relayer(evm_address)`, which writes bot └─ PendingRelayerFees[AccountId][0] += 100 3a. claim_shielded_fees(commitment, amount=100, ...) [pallet-shielded-pool, call_index 16] - ├─ Verifies ZK disclosure proof + ├─ Verifies ZK value proof ├─ consume_relay_fee(validator, 0, 100) │ └─ PendingRelayerFees[validator][0] -= 100 └─ MerkleTree ← commitment(100) @@ -131,7 +131,7 @@ Both are linked at startup via `register_relayer(evm_address)`, which writes bot |---|---|---| | Result | Private note in Merkle tree | Public ORB on H160 EVM | | Privacy | Full (ZK UTXO) | Public (visible transfer) | -| ZK proof required | Yes (disclosure circuit) | No | +| ZK proof required | Yes (value_proof circuit) | No | | Typical use | Accumulate private funds | Refill relayer gas wallet | --- diff --git a/frame/relayer/runtime-api/src/lib.rs b/frame/relayer/runtime-api/src/lib.rs index 69447dcc..2d835777 100644 --- a/frame/relayer/runtime-api/src/lib.rs +++ b/frame/relayer/runtime-api/src/lib.rs @@ -13,6 +13,18 @@ sp_api::decl_runtime_apis! { /// Returns the registered EVM address for the relayer, if any. fn registered_evm_address(account: sp_runtime::AccountId32) -> Option<[u8; 20]>; + + /// Returns all (evm_address, substrate_account) pairs in the relay registry. + /// + /// Used by clients to discover which validator nodes have relay active. + /// Capa 2 of the relay architecture. + fn get_active_relayers() -> alloc::vec::Vec<([u8; 20], sp_runtime::AccountId32)>; + + /// Returns true if the given EVM address is registered as a relayer on-chain. + /// + /// Used by `orbinum_relayerStatus` to surface registration state to operators. + /// Capa 2 of the relay architecture. + fn is_relayer_evm(evm_address: [u8; 20]) -> bool; } } diff --git a/frame/relayer/src/benchmarking.rs b/frame/relayer/src/benchmarking.rs index 5445c90a..05e13717 100644 --- a/frame/relayer/src/benchmarking.rs +++ b/frame/relayer/src/benchmarking.rs @@ -99,25 +99,6 @@ mod benchmarks { assert!(!RelayerByAccount::::contains_key(&caller)); } - // ── claim_relay_fees ───────────────────────────────────────────────────── - - /// Worst case: one double-map read + mutate + event. - /// Pre-condition: caller has sufficient pending fees. - #[benchmark] - fn claim_relay_fees() { - let caller: T::AccountId = whitelisted_caller(); - let asset_id = 0u32; - let balance = 1_000_000_000_000_000u128; - - // Seed pending fees directly into storage. - PendingRelayerFees::::insert(&caller, asset_id, balance); - - #[extrinsic_call] - claim_relay_fees(RawOrigin::Signed(caller.clone()), asset_id, balance / 2); - - assert_eq!(PendingRelayerFees::::get(&caller, asset_id), balance / 2); - } - // ── Benchmark test suite ───────────────────────────────────────────────── impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test,); diff --git a/frame/relayer/src/lib.rs b/frame/relayer/src/lib.rs index 21239c41..9f83aff7 100644 --- a/frame/relayer/src/lib.rs +++ b/frame/relayer/src/lib.rs @@ -269,33 +269,6 @@ pub mod pallet { }); Ok(()) } - - /// Standalone claim for accrued relay fees (planck accounting only). - /// - /// Decrements `PendingRelayerFees` without performing the actual token - /// transfer. Validators should prefer - /// `pallet-shielded-pool::claim_shielded_fees`, which inserts a private - /// note into the Merkle tree. - #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::claim_relay_fees())] - pub fn claim_relay_fees( - origin: OriginFor, - asset_id: u32, - amount: u128, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - let pending = PendingRelayerFees::::get(&who, asset_id); - ensure!(pending >= amount, Error::::InsufficientPendingFees); - PendingRelayerFees::::mutate(&who, asset_id, |b| { - *b = b.saturating_sub(amount); - }); - Self::deposit_event(Event::RelayFeesConsumed { - relayer: who, - asset_id, - amount, - }); - Ok(()) - } } // ── RelayerInterface implementation ─────────────────────────────────────── diff --git a/frame/relayer/src/tests/fees_tests.rs b/frame/relayer/src/tests/fees_tests.rs index 9d2cc8a8..256c6f22 100644 --- a/frame/relayer/src/tests/fees_tests.rs +++ b/frame/relayer/src/tests/fees_tests.rs @@ -4,7 +4,6 @@ //! - `accumulate_relay_fee` — storage update, saturation, event //! - `pending_relay_fees` — zero for unknown, correct after accumulation //! - `consume_relay_fee` — happy path, partial, insufficient, event -//! - `claim_relay_fees` — extrinsic path, signed guard, insufficient guard use crate::traits::RelayerInterface; use crate::{Error, Event, PendingRelayerFees, mock::*}; @@ -141,85 +140,8 @@ fn consume_relay_fee_fails_for_unknown_account() { }); } -// ─── claim_relay_fees (extrinsic) ──────────────────────────────────────────── - -#[test] -fn claim_relay_fees_works() { - new_test_ext().execute_with(|| { - crate::Pallet::::accumulate_relay_fee(&1u64, 0, 800); - assert_ok!(Relayer::claim_relay_fees(RuntimeOrigin::signed(1), 0, 300)); - assert_eq!(PendingRelayerFees::::get(1u64, 0), 500u128); - }); -} - -#[test] -fn claim_relay_fees_emits_event() { - new_test_ext().execute_with(|| { - crate::Pallet::::accumulate_relay_fee(&1u64, 0, 500); - assert_ok!(Relayer::claim_relay_fees(RuntimeOrigin::signed(1), 0, 200)); - System::assert_last_event( - Event::RelayFeesConsumed { - relayer: 1u64, - asset_id: 0, - amount: 200, - } - .into(), - ); - }); -} - -#[test] -fn claim_relay_fees_drains_fully() { - new_test_ext().execute_with(|| { - crate::Pallet::::accumulate_relay_fee(&1u64, 0, 100); - assert_ok!(Relayer::claim_relay_fees(RuntimeOrigin::signed(1), 0, 100)); - assert_eq!(PendingRelayerFees::::get(1u64, 0), 0u128); - }); -} - -#[test] -fn claim_relay_fees_fails_if_insufficient() { - new_test_ext().execute_with(|| { - crate::Pallet::::accumulate_relay_fee(&1u64, 0, 50); - assert_noop!( - Relayer::claim_relay_fees(RuntimeOrigin::signed(1), 0, 51), - Error::::InsufficientPendingFees, - ); - }); -} - -#[test] -fn claim_relay_fees_requires_signed() { - new_test_ext().execute_with(|| { - assert_noop!( - Relayer::claim_relay_fees(RuntimeOrigin::none(), 0, 1), - frame_support::error::BadOrigin, - ); - }); -} - // ─── Cross-account isolation ────────────────────────────────────────────────── -/// Account 2 cannot drain fees that were accrued for account 1. -/// The storage map is keyed by the signed origin — there is no parameter that -/// lets a caller specify a different beneficiary. -#[test] -fn claim_relay_fees_cannot_drain_another_accounts_fees() { - new_test_ext().execute_with(|| { - // Only account 1 has fees - crate::Pallet::::accumulate_relay_fee(&1u64, 0, 1000); - - // Account 2 tries to claim — its own pending balance is 0 → InsufficientPendingFees - assert_noop!( - Relayer::claim_relay_fees(RuntimeOrigin::signed(2), 0, 1), - Error::::InsufficientPendingFees, - ); - - // Account 1's fees are untouched - assert_eq!(PendingRelayerFees::::get(1u64, 0), 1000u128); - }); -} - /// `consume_relay_fee` (called internally by claim_shielded_fees) also isolates /// by AccountId — passing account 1's fees to account 2 is impossible. #[test] @@ -237,12 +159,3 @@ fn consume_relay_fee_cannot_drain_another_accounts_fees() { assert_eq!(PendingRelayerFees::::get(1u64, 0), 500u128); }); } - -#[test] -fn claim_relay_fees_zero_amount_succeeds() { - new_test_ext().execute_with(|| { - // Claiming 0 is valid (no-op debit, event still emitted) - assert_ok!(Relayer::claim_relay_fees(RuntimeOrigin::signed(1), 0, 0)); - assert_eq!(PendingRelayerFees::::get(1u64, 0), 0u128); - }); -} diff --git a/frame/relayer/src/tests/mod.rs b/frame/relayer/src/tests/mod.rs index 830e9e20..16bc4a62 100644 --- a/frame/relayer/src/tests/mod.rs +++ b/frame/relayer/src/tests/mod.rs @@ -3,7 +3,7 @@ //! Modules: //! - `config_tests` — MinRelayFee and AllowedSelectors governance //! - `registry_tests` — EVM address ↔ AccountId binding -//! - `fees_tests` — relay fee accumulation, querying and consumption +//! - `fees_tests` — relay fee accumulation, querying, and consumption via RelayerInterface #[cfg(test)] pub mod config_tests; diff --git a/frame/relayer/src/weights.rs b/frame/relayer/src/weights.rs index 4deb74ee..a883650f 100644 --- a/frame/relayer/src/weights.rs +++ b/frame/relayer/src/weights.rs @@ -33,7 +33,6 @@ pub trait WeightInfo { fn set_allowed_selectors(n: u32, ) -> Weight; fn register_relayer() -> Weight; fn unregister_relayer() -> Weight; - fn claim_relay_fees() -> Weight; } /// Weights for pallet_relayer using the Substrate node and recommended hardware. @@ -120,25 +119,6 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } - /// Storage: `Relayer::PendingRelayerFees` (r:1 w:1) - /// Proof: `Relayer::PendingRelayerFees` (`max_values`: None, `max_size`: Some(84), added: 2559, 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 claim_relay_fees() -> Weight { - // Proof Size summary in bytes: - // Measured: `171` - // Estimated: `3549` - // Minimum execution time: 11_000_000 picoseconds. - Weight::from_parts(12_000_000, 3549) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) - } } // For backwards compatibility and tests @@ -224,23 +204,4 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } - /// Storage: `Relayer::PendingRelayerFees` (r:1 w:1) - /// Proof: `Relayer::PendingRelayerFees` (`max_values`: None, `max_size`: Some(84), added: 2559, 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 claim_relay_fees() -> Weight { - // Proof Size summary in bytes: - // Measured: `171` - // Estimated: `3549` - // Minimum execution time: 11_000_000 picoseconds. - Weight::from_parts(12_000_000, 3549) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) - } } From 42bad8a02dc227c75bcf324b67bb1c3b712d6a65 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 13:33:39 -0400 Subject: [PATCH 07/18] feat: implement claim_shielded_fees precompile with ABI decoding and dispatch logic --- frame/account-mapping/src/lib.rs | 2 +- .../src/calls/claim_shielded_fees.rs | 642 ++++++++++++++++++ .../precompile/shielded-pool/src/calls/mod.rs | 1 + .../precompile/shielded-pool/src/dispatch.rs | 35 + frame/evm/precompile/shielded-pool/src/lib.rs | 17 +- .../evm/precompile/shielded-pool/src/mock.rs | 30 +- 6 files changed, 705 insertions(+), 22 deletions(-) create mode 100644 frame/evm/precompile/shielded-pool/src/calls/claim_shielded_fees.rs diff --git a/frame/account-mapping/src/lib.rs b/frame/account-mapping/src/lib.rs index 07e401f8..33c76dea 100644 --- a/frame/account-mapping/src/lib.rs +++ b/frame/account-mapping/src/lib.rs @@ -1040,7 +1040,7 @@ pub mod pallet { /// /// **This operation is irreversible.** Once revealed, the link is public. /// - /// Use case: selective disclosure — proving wallet ownership to an auditor, + /// Use case: controlled revelation — proving wallet ownership to an auditor, /// regulator, DAO, or grant committee without having been public from the start. #[pallet::call_index(16)] #[pallet::weight(T::WeightInfo::reveal_private_link())] 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 new file mode 100644 index 00000000..02312c21 --- /dev/null +++ b/frame/evm/precompile/shielded-pool/src/calls/claim_shielded_fees.rs @@ -0,0 +1,642 @@ +//! ABI decoding and call construction for +//! `claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes)`. +//! +//! ## Selector +//! `keccak256("claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes)")[0..4]` +//! = `0x42e1e74c` +//! +//! ## ABI layout (`input[4..]`) +//! | Slot (bytes) | Type | Field | +//! |-------------|-----------|--------------------| +//! | 0..32 | `bytes32` | `commitment` | +//! | 32..64 | `uint256` | `amount` | +//! | 64..96 | `uint32` | `asset_id` | +//! | 96..128 | `uint256` | offset → `memo` | +//! | 128..160 | `uint256` | offset → `proof` | +//! | 160..192 | `uint256` | offset → `public_signals` | +//! +//! The **validator** origin is derived from `handle.context().caller` +//! (the EVM address that sent the transaction), mapped to an `AccountId` +//! via `AddressMapping`. It must match the address registered in +//! `pallet-relayer` that has accumulated pending fees. +//! +//! ## public_signals layout (76 bytes, off-chain encoded) +//! `commitment[0..32] | value[32..40] | asset_id[40..44] | owner_hash[44..76]` + +use alloc::vec::Vec; + +use fp_evm::{ExitError, PrecompileFailure, PrecompileHandle}; +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]; + +/// Maximum byte length of a serialised Groth16 proof accepted by the pallet. +const MAX_PROOF_LEN: u32 = 512; + +/// Decodes the ABI-encoded `input` and returns a ready-to-dispatch +/// `claim_shielded_fees` call. +/// +/// The validator `AccountId` is NOT part of the ABI — it is derived from +/// `handle.context().caller` so the pallet can look up the correct pending +/// fee balance in `pallet-relayer`. +pub fn decode( + _handle: &impl PrecompileHandle, + input: &[u8], +) -> Result, PrecompileFailure> +where + T: pallet_shielded_pool::Config, + pallet_shielded_pool::BalanceOf: TryFrom, +{ + // Minimum head section: 6 fixed slots × 32 bytes = 192 bytes. + let params = &input[4..]; + if params.len() < 192 { + return Err(err("claimShieldedFees: input too short")); + } + + let commitment = pallet_shielded_pool::Commitment::from(abi::read_bytes32(params, 0)?); + + // Reject zero-amount calls early. + let amount_u256 = U256::from_big_endian(¶ms[32..64]); + if amount_u256.is_zero() { + return Err(err("claimShieldedFees: amount must be non-zero")); + } + let amount: pallet_shielded_pool::BalanceOf = { + let raw: u128 = amount_u256 + .try_into() + .map_err(|_| err("claimShieldedFees: amount overflow"))?; + raw.try_into() + .map_err(|_| err("claimShieldedFees: amount conversion failed"))? + }; + + let asset_id = abi::decode_u32(¶ms[64..96])?; + + let memo_bytes: Vec = abi::decode_bytes_at_slot(params, 96)?; + let memo = pallet_shielded_pool::FrameEncryptedMemo::new(memo_bytes) + .map_err(|_| err("claimShieldedFees: memo too long or wrong size"))?; + + let proof: frame_support::BoundedVec> = + abi::decode_bytes_at_slot(params, 128)? + .try_into() + .map_err(|_| err("claimShieldedFees: proof too long"))?; + + if proof.is_empty() { + return Err(err("claimShieldedFees: proof must be non-empty")); + } + + let public_signals: Vec = abi::decode_bytes_at_slot(params, 160)?; + + if public_signals.len() != 76 { + return Err(err("claimShieldedFees: public_signals must be 76 bytes")); + } + + Ok(pallet_shielded_pool::Call::::claim_shielded_fees { + commitment, + amount, + asset_id, + memo, + proof: proof.into(), + public_signals, + }) +} + +// ───────────────────────────────────────────────────────────────────────────── + +fn err(msg: &'static str) -> PrecompileFailure { + PrecompileFailure::Error { + exit_status: ExitError::Other(msg.into()), + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// Tests +// ───────────────────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use fp_evm::{ExitError, ExitSucceed, Precompile, PrecompileFailure}; + use sp_core::U256; + + use crate::{ + mock::{new_test_ext, set_pending_relay_fees, MockHandle, Test}, + ShieldedPoolPrecompile, + }; + + use super::SELECTOR; + + // ── Assertion helpers ──────────────────────────────────────────────────── + + fn expect_error(result: Result) { + assert!( + matches!( + result, + Err(PrecompileFailure::Error { + exit_status: ExitError::Other(_) + }) + ), + "expected PrecompileFailure::Error(ExitError::Other(_)), got: {result:?}" + ); + } + + fn assert_success(result: Result) { + match result { + Ok(out) => assert_eq!(out.exit_status, ExitSucceed::Stopped), + Err(e) => panic!("expected successful dispatch, got error: {e:?}"), + } + } + + // ── ABI encoder ────────────────────────────────────────────────────────── + + fn u256_word(v: usize) -> [u8; 32] { + U256::from(v).to_big_endian() + } + + fn u256_word_u128(v: u128) -> [u8; 32] { + U256::from(v).to_big_endian() + } + + /// Encodes a single `bytes` tail: `uint256(length) ++ data ++ zero-padding`. + fn encode_bytes(data: &[u8]) -> Vec { + let padded = (data.len() + 31) & !31; + let mut out = vec![0u8; 32 + padded]; + out[..32].copy_from_slice(&u256_word(data.len())); + out[32..32 + data.len()].copy_from_slice(data); + out + } + + /// 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)]` + fn encode_claim_shielded_fees( + commitment: [u8; 32], + amount: u128, + asset_id: u32, + memo: &[u8], + proof: &[u8], + public_signals: &[u8], + ) -> 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; + let memo_off = head_size; + let proof_off = head_size + memo_enc.len(); + let ps_off = head_size + memo_enc.len() + proof_enc.len(); + + let mut head = vec![0u8; head_size]; + head[0..32].copy_from_slice(&commitment); + head[32..64].copy_from_slice(&u256_word_u128(amount)); + head[92..96].copy_from_slice(&asset_id.to_be_bytes()); // right-aligned uint32 + 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)); + + let mut input = SELECTOR.to_vec(); + input.extend_from_slice(&head); + input.extend_from_slice(&memo_enc); + input.extend_from_slice(&proof_enc); + input.extend_from_slice(&ps_enc); + input + } + + /// Builds a valid 76-byte `public_signals` blob consistent with + /// `(commitment, amount, asset_id)`. + /// + /// Layout: `commitment[0..32] | value_le[32..40] | asset_id_le[40..44] | owner_hash[44..76]` + fn make_public_signals(commitment: [u8; 32], amount: u64, asset_id: u32) -> [u8; 76] { + let mut ps = [0u8; 76]; + ps[0..32].copy_from_slice(&commitment); + ps[32..40].copy_from_slice(&amount.to_le_bytes()); + ps[40..44].copy_from_slice(&asset_id.to_le_bytes()); + // owner_hash[44..76] can be arbitrary — left as zeros + ps + } + + // ── Minimal valid parameters ───────────────────────────────────────────── + + const COMMITMENT: [u8; 32] = [0x11; 32]; + const AMOUNT: u128 = 500; + const ASSET_ID: u32 = 0; + const PROOF: &[u8] = &[0x01u8; 128]; // 128 bytes → passes pallet check + + fn valid_memo() -> Vec { + vec![0xAB; 176] + } + + fn valid_public_signals() -> Vec { + make_public_signals(COMMITMENT, AMOUNT as u64, ASSET_ID).to_vec() + } + + fn valid_input() -> Vec { + encode_claim_shielded_fees( + COMMITMENT, + AMOUNT, + ASSET_ID, + &valid_memo(), + PROOF, + &valid_public_signals(), + ) + } + + // ───────────────────────────────────────────────────────────────────────── + // Tests: ABI decoder (pure unit tests, no pallet state) + // ───────────────────────────────────────────────────────────────────────── + + #[test] + fn decode_rejects_truncated_input() { + // Input has only selector, no params at all. + let input = SELECTOR.to_vec(); + let h = MockHandle::new(input.clone()); + let result = super::decode::(&h, &input); + assert!(result.is_err(), "expected Err for truncated input"); + } + + #[test] + fn decode_rejects_input_shorter_than_head() { + // 4-byte selector + 100-byte body (< 192 required). + let mut input = SELECTOR.to_vec(); + input.extend_from_slice(&[0u8; 100]); + let h = MockHandle::new(input.clone()); + let result = super::decode::(&h, &input); + assert!(result.is_err()); + } + + #[test] + fn decode_rejects_zero_amount() { + let input = encode_claim_shielded_fees( + COMMITMENT, + 0, // ← zero amount + ASSET_ID, + &valid_memo(), + PROOF, + &valid_public_signals(), + ); + let h = MockHandle::new(input.clone()); + let result = super::decode::(&h, &input); + assert!(result.is_err(), "zero amount must be rejected"); + } + + #[test] + fn decode_rejects_public_signals_not_76_bytes() { + for bad_len in [0usize, 32, 75, 77, 128] { + let bad_signals = vec![0xFFu8; bad_len]; + let input = encode_claim_shielded_fees( + COMMITMENT, + AMOUNT, + ASSET_ID, + &valid_memo(), + PROOF, + &bad_signals, + ); + let h = MockHandle::new(input.clone()); + let result = super::decode::(&h, &input); + assert!( + result.is_err(), + "public_signals of len {bad_len} must be rejected" + ); + } + } + + #[test] + fn decode_rejects_proof_exceeding_max_len() { + // MAX_PROOF_LEN = 512; send 513 bytes. + let oversized_proof = vec![0x01u8; 513]; + let input = encode_claim_shielded_fees( + COMMITMENT, + AMOUNT, + ASSET_ID, + &valid_memo(), + &oversized_proof, + &valid_public_signals(), + ); + let h = MockHandle::new(input.clone()); + let result = super::decode::(&h, &input); + assert!( + result.is_err(), + "proof exceeding MAX_PROOF_LEN must be rejected" + ); + } + + #[test] + fn decode_rejects_empty_proof() { + let input = encode_claim_shielded_fees( + COMMITMENT, + AMOUNT, + ASSET_ID, + &valid_memo(), + &[], // ← empty proof + &valid_public_signals(), + ); + let h = MockHandle::new(input.clone()); + let result = super::decode::(&h, &input); + assert!(result.is_err(), "empty proof must be rejected"); + } + + #[test] + fn decode_accepts_valid_input() { + let input = valid_input(); + let h = MockHandle::new(input.clone()); + let result = super::decode::(&h, &input); + assert!( + result.is_ok(), + "valid input must be accepted, got: {result:?}" + ); + } + + #[test] + fn decode_round_trips_commitment() { + let commitment = [0xABu8; 32]; + let input = encode_claim_shielded_fees( + commitment, + AMOUNT, + ASSET_ID, + &valid_memo(), + PROOF, + &make_public_signals(commitment, AMOUNT as u64, ASSET_ID).to_vec(), + ); + let h = MockHandle::new(input.clone()); + match super::decode::(&h, &input).unwrap() { + pallet_shielded_pool::Call::claim_shielded_fees { commitment: c, .. } => { + assert_eq!(c, pallet_shielded_pool::Commitment::from(commitment)); + } + _ => panic!("unexpected call variant"), + } + } + + #[test] + fn decode_round_trips_amount() { + let amount: u128 = 999_999; + let ps = make_public_signals(COMMITMENT, amount as u64, ASSET_ID); + let input = encode_claim_shielded_fees( + COMMITMENT, + amount, + ASSET_ID, + &valid_memo(), + PROOF, + &ps.to_vec(), + ); + let h = MockHandle::new(input.clone()); + match super::decode::(&h, &input).unwrap() { + pallet_shielded_pool::Call::claim_shielded_fees { amount: a, .. } => { + assert_eq!(Into::::into(a), amount); + } + _ => panic!("unexpected call variant"), + } + } + + #[test] + fn decode_round_trips_asset_id() { + // asset_id = 0 (the only registered asset in the mock genesis). + let input = encode_claim_shielded_fees( + COMMITMENT, + AMOUNT, + 0, + &valid_memo(), + PROOF, + &make_public_signals(COMMITMENT, AMOUNT as u64, 0).to_vec(), + ); + let h = MockHandle::new(input.clone()); + match super::decode::(&h, &input).unwrap() { + pallet_shielded_pool::Call::claim_shielded_fees { asset_id: id, .. } => { + assert_eq!(id, 0u32); + } + _ => panic!("unexpected call variant"), + } + } + + // ───────────────────────────────────────────────────────────────────────── + // Tests: router + dispatch (full precompile execution) + // ───────────────────────────────────────────────────────────────────────── + + #[test] + fn router_recognises_claim_shielded_fees_selector() { + // Sending only the selector (no valid body) must reach the decoder and + // produce a decode error, NOT "unknown selector". + new_test_ext().execute_with(|| { + let mut h = MockHandle::new(SELECTOR.to_vec()); + match ShieldedPoolPrecompile::::execute(&mut h) { + Err(PrecompileFailure::Error { + exit_status: ExitError::Other(msg), + }) => { + let s = msg.to_string(); + assert!( + !s.contains("unknown selector"), + "selector must be recognised, got: {s}" + ); + } + other => panic!("expected a precompile error, got: {other:?}"), + } + }); + } + + #[test] + fn dispatch_rejects_truncated_input() { + new_test_ext().execute_with(|| { + let mut h = MockHandle::new(SELECTOR.to_vec()); + expect_error(ShieldedPoolPrecompile::::execute(&mut h)); + }); + } + + #[test] + fn dispatch_rejects_zero_amount() { + new_test_ext().execute_with(|| { + let input = encode_claim_shielded_fees( + COMMITMENT, + 0, + ASSET_ID, + &valid_memo(), + PROOF, + &valid_public_signals(), + ); + let mut h = MockHandle::new(input); + expect_error(ShieldedPoolPrecompile::::execute(&mut h)); + }); + } + + #[test] + fn dispatch_rejects_empty_proof() { + new_test_ext().execute_with(|| { + let input = encode_claim_shielded_fees( + COMMITMENT, + AMOUNT, + ASSET_ID, + &valid_memo(), + &[], + &valid_public_signals(), + ); + let mut h = MockHandle::new(input); + expect_error(ShieldedPoolPrecompile::::execute(&mut h)); + }); + } + + #[test] + fn dispatch_rejects_wrong_public_signals_length() { + new_test_ext().execute_with(|| { + // 75 bytes — one byte short + let bad_ps = vec![0xFFu8; 75]; + let input = encode_claim_shielded_fees( + COMMITMENT, + AMOUNT, + ASSET_ID, + &valid_memo(), + PROOF, + &bad_ps, + ); + let mut h = MockHandle::new(input); + expect_error(ShieldedPoolPrecompile::::execute(&mut h)); + }); + } + + #[test] + fn dispatch_rejects_commitment_mismatch_in_signals() { + // signals[0..32] encodes a different commitment than the param. + new_test_ext().execute_with(|| { + set_pending_relay_fees(AMOUNT); + let mismatched = make_public_signals([0xFFu8; 32], AMOUNT as u64, ASSET_ID); + let input = encode_claim_shielded_fees( + COMMITMENT, // param commitment ≠ signals commitment + AMOUNT, + ASSET_ID, + &valid_memo(), + PROOF, + &mismatched.to_vec(), + ); + let mut h = MockHandle::new(input); + expect_error(ShieldedPoolPrecompile::::execute(&mut h)); + }); + } + + #[test] + fn dispatch_rejects_value_mismatch_in_signals() { + // signals[32..40] encodes a different amount than the param. + new_test_ext().execute_with(|| { + set_pending_relay_fees(AMOUNT); + let mismatched = make_public_signals(COMMITMENT, (AMOUNT + 1) as u64, ASSET_ID); + let input = encode_claim_shielded_fees( + COMMITMENT, + AMOUNT, // param amount ≠ signals value + ASSET_ID, + &valid_memo(), + PROOF, + &mismatched.to_vec(), + ); + let mut h = MockHandle::new(input); + expect_error(ShieldedPoolPrecompile::::execute(&mut h)); + }); + } + + #[test] + fn dispatch_rejects_asset_id_mismatch_in_signals() { + // signals[40..44] encodes a different asset_id than the param. + new_test_ext().execute_with(|| { + set_pending_relay_fees(AMOUNT); + let mismatched = make_public_signals(COMMITMENT, AMOUNT as u64, 99); + let input = encode_claim_shielded_fees( + COMMITMENT, + AMOUNT, + ASSET_ID, // param asset_id 0 ≠ signals asset_id 99 + &valid_memo(), + PROOF, + &mismatched.to_vec(), + ); + let mut h = MockHandle::new(input); + expect_error(ShieldedPoolPrecompile::::execute(&mut h)); + }); + } + + #[test] + fn dispatch_rejects_insufficient_pending_fees() { + // MockRelayer returns AMOUNT - 1 < AMOUNT → InsufficientPendingFees. + new_test_ext().execute_with(|| { + set_pending_relay_fees(AMOUNT - 1); + let mut h = MockHandle::new(valid_input()); + expect_error(ShieldedPoolPrecompile::::execute(&mut h)); + }); + } + + #[test] + fn dispatch_happy_path_inserts_commitment_and_emits_event() { + new_test_ext().execute_with(|| { + set_pending_relay_fees(AMOUNT); + + let tree_size_before = pallet_shielded_pool::MerkleTreeSize::::get(); + + let mut h = MockHandle::new(valid_input()); + assert_success(ShieldedPoolPrecompile::::execute(&mut h)); + + // Commitment must have been inserted into the Merkle tree. + let tree_size_after = pallet_shielded_pool::MerkleTreeSize::::get(); + assert_eq!( + tree_size_after, + tree_size_before + 1, + "one leaf must be added" + ); + let leaf = pallet_shielded_pool::MerkleLeaves::::get(tree_size_before).unwrap(); + assert_eq!( + leaf, + pallet_shielded_pool::Commitment::from(COMMITMENT), + "leaf must match the submitted commitment" + ); + }); + } + + #[test] + fn dispatch_happy_path_updates_merkle_root() { + new_test_ext().execute_with(|| { + set_pending_relay_fees(AMOUNT); + let root_before = pallet_shielded_pool::Pallet::::poseidon_root(); + + let mut h = MockHandle::new(valid_input()); + assert_success(ShieldedPoolPrecompile::::execute(&mut h)); + + let root_after = pallet_shielded_pool::Pallet::::poseidon_root(); + assert_ne!( + root_before, root_after, + "Merkle root must change after fee claim" + ); + }); + } + + #[test] + fn dispatch_happy_path_stores_encrypted_memo() { + new_test_ext().execute_with(|| { + set_pending_relay_fees(AMOUNT); + + let mut h = MockHandle::new(valid_input()); + assert_success(ShieldedPoolPrecompile::::execute(&mut h)); + + // The memo must be retrievable by the commitment key. + let stored = pallet_shielded_pool::CommitmentMemos::::get( + pallet_shielded_pool::Commitment::from(COMMITMENT), + ); + assert!( + stored.is_some(), + "encrypted memo must be stored after successful claim" + ); + }); + } + + #[test] + fn dispatch_rejects_unregistered_asset() { + // asset_id 99 is not registered at genesis → InvalidAssetId. + new_test_ext().execute_with(|| { + set_pending_relay_fees(AMOUNT); + let ps = make_public_signals(COMMITMENT, AMOUNT as u64, 99); + let input = encode_claim_shielded_fees( + COMMITMENT, + AMOUNT, + 99, // unregistered + &valid_memo(), + PROOF, + &ps.to_vec(), + ); + let mut h = MockHandle::new(input); + expect_error(ShieldedPoolPrecompile::::execute(&mut h)); + }); + } +} diff --git a/frame/evm/precompile/shielded-pool/src/calls/mod.rs b/frame/evm/precompile/shielded-pool/src/calls/mod.rs index d0f4e00f..4cc153aa 100644 --- a/frame/evm/precompile/shielded-pool/src/calls/mod.rs +++ b/frame/evm/precompile/shielded-pool/src/calls/mod.rs @@ -8,6 +8,7 @@ //! The precompile router in `lib.rs` only needs to match on `SELECTOR`s and //! forward to the appropriate `decode`, then hand the call to `dispatch`. +pub mod claim_shielded_fees; pub mod private_transfer; pub mod shield; pub mod unshield; diff --git a/frame/evm/precompile/shielded-pool/src/dispatch.rs b/frame/evm/precompile/shielded-pool/src/dispatch.rs index 275175ef..326c7785 100644 --- a/frame/evm/precompile/shielded-pool/src/dispatch.rs +++ b/frame/evm/precompile/shielded-pool/src/dispatch.rs @@ -53,6 +53,41 @@ where dispatch(runtime_call, origin) } +/// Dispatches `call` with the **EVM caller** as signed origin. +/// +/// Used for `claim_shielded_fees`: the validator calls the precompile from their +/// EVM address; their `H160` is mapped to an `AccountId` via `AddressMapping` and +/// used as the signed origin so `ensure_signed` succeeds in the pallet. +pub fn from_caller( + handle: &mut impl PrecompileHandle, + call: pallet_shielded_pool::Call, +) -> PrecompileResult +where + T: pallet_evm::Config + pallet_shielded_pool::Config, + ::RuntimeCall: Dispatchable + + GetDispatchInfo + + From>, + <::RuntimeCall as Dispatchable>::RuntimeOrigin: + From::AccountId>>, + <::RuntimeCall as Dispatchable>::PostInfo: core::fmt::Debug, + pallet_evm::AccountIdOf: Into<::AccountId>, +{ + let runtime_call = <::RuntimeCall as From< + pallet_shielded_pool::Call, + >>::from(call); + let gas_cost = + T::GasWeightMapping::weight_to_gas(runtime_call.get_dispatch_info().total_weight()); + handle.record_cost(gas_cost)?; + + let caller_account: ::AccountId = + T::AddressMapping::into_account_id(handle.context().caller).into(); + let origin = <::RuntimeCall as Dispatchable>::RuntimeOrigin::from( + Some(caller_account), + ); + + dispatch(runtime_call, origin) +} + /// Dispatches `call` with `None` origin (`ensure_none`). /// /// Gas cost is derived from the call's dispatch weight via [`GasWeightMapping`]. diff --git a/frame/evm/precompile/shielded-pool/src/lib.rs b/frame/evm/precompile/shielded-pool/src/lib.rs index 556583a4..6a11a5a5 100644 --- a/frame/evm/precompile/shielded-pool/src/lib.rs +++ b/frame/evm/precompile/shielded-pool/src/lib.rs @@ -14,13 +14,14 @@ use sp_runtime::traits::Dispatchable; /// EVM precompile that bridges Solidity calls into `pallet_shielded_pool` extrinsics. /// -/// Three functions are exposed, each identified by a 4-byte ABI selector: +/// 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)` | +/// | 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 computation: `bytes4(keccak256("functionName(argTypes)"))`. /// Verify with: `node -e "const {ethers}=require('ethers'); console.log(ethers.id('sig').slice(0,10))"` @@ -63,6 +64,10 @@ where let call = calls::unshield::decode::(handle, &input)?; dispatch::unsigned::(handle, call) } + calls::claim_shielded_fees::SELECTOR => { + let call = calls::claim_shielded_fees::decode::(handle, &input)?; + dispatch::from_caller::(handle, call) + } _ => Err(PrecompileFailure::Error { exit_status: ExitError::Other("unknown selector".into()), }), diff --git a/frame/evm/precompile/shielded-pool/src/mock.rs b/frame/evm/precompile/shielded-pool/src/mock.rs index 8bf90412..8ea7dfee 100644 --- a/frame/evm/precompile/shielded-pool/src/mock.rs +++ b/frame/evm/precompile/shielded-pool/src/mock.rs @@ -121,7 +121,6 @@ parameter_types! { pub const MaxTreeDepth: u32 = 32; pub const MaxHistoricRoots: u32 = 100; pub const MinShieldAmount: u128 = 100; - pub const RequestExpiration: u64 = 1000; } pub struct MockZkVerifier; @@ -159,7 +158,7 @@ impl ZkVerifierPort for MockZkVerifier { Ok(true) } - fn verify_disclosure_proof( + fn verify_value_proof( proof: &[u8], public_signals: &[u8], _version: Option, @@ -175,17 +174,6 @@ impl ZkVerifierPort for MockZkVerifier { Ok(true) } - fn batch_verify_disclosure_proofs( - proofs: &[sp_std::vec::Vec], - public_signals: &[sp_std::vec::Vec], - _version: Option, - ) -> Result { - if proofs.len() != public_signals.len() { - return Err(sp_runtime::DispatchError::Other("Mismatched array lengths")); - } - Ok(true) - } - fn verify_private_link_proof( proof: &[u8], _commitment: &[u8; 32], @@ -207,6 +195,19 @@ impl frame_support::traits::Get> for MockBlockAuthor { } pub struct MockRelayer; + +std::thread_local! { + /// Controls the value returned by `MockRelayer::pending_relay_fees` during tests. + /// Default is 0 (no pending fees). Set with `set_pending_relay_fees` before the + /// test that exercises a happy-path `claim_shielded_fees` dispatch. + static PENDING_RELAY_FEES: std::cell::Cell = const { std::cell::Cell::new(0) }; +} + +/// Override the mock's pending relay-fee balance for the current test. +pub fn set_pending_relay_fees(amount: u128) { + PENDING_RELAY_FEES.with(|c| c.set(amount)); +} + impl pallet_relayer::RelayerInterface for MockRelayer { type AccountId = AccountId32; @@ -229,7 +230,7 @@ impl pallet_relayer::RelayerInterface for MockRelayer { fn accumulate_relay_fee(_author: &AccountId32, _asset_id: u32, _amount: u128) {} fn pending_relay_fees(_who: &AccountId32, _asset_id: u32) -> u128 { - 0 + PENDING_RELAY_FEES.with(|c| c.get()) } fn consume_relay_fee( @@ -253,7 +254,6 @@ impl pallet_shielded_pool::Config for Test { type MaxHistoricRoots = MaxHistoricRoots; type MinShieldAmount = MinShieldAmount; type WeightInfo = (); - type RequestExpiration = RequestExpiration; type Relayer = MockRelayer; } From 7f6c9a35b09bb8b520e41bdeda21af283a481c33 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 13:34:52 -0400 Subject: [PATCH 08/18] feat: add Chain RPC for general chain state queries and integrate with existing API --- client/rpc-v2/src/README.md | 63 ++++++++++++---- client/rpc-v2/src/chain.rs | 140 ++++++++++++++++++++++++++++++++++++ client/rpc-v2/src/lib.rs | 2 + 3 files changed, 192 insertions(+), 13 deletions(-) create mode 100644 client/rpc-v2/src/chain.rs diff --git a/client/rpc-v2/src/README.md b/client/rpc-v2/src/README.md index 7c8dfcf8..d44722fc 100644 --- a/client/rpc-v2/src/README.md +++ b/client/rpc-v2/src/README.md @@ -1,28 +1,26 @@ -# fc-rpc-v2 — Orbinum Privacy RPC +# fc-rpc-v2 — Orbinum Custom RPC -Read-only JSON-RPC layer for the Orbinum shielded pool. Exposes five endpoints that query `pallet-shielded-pool` storage directly, without going through the Runtime API. +Read-only JSON-RPC layer for Orbinum-specific chain state. Endpoints query pallet storage directly, without going through the Runtime API — no runtime upgrade required to extend this layer. ## Crate layout ``` src/ -├── lib.rs — crate root, re-exports public types -└── privacy.rs — all five endpoints, response types, and tests +├── lib.rs — crate root, re-exports public types +├── chain.rs — general chain state endpoints (`chain_*`) +└── privacy.rs — shielded-pool endpoints (`privacy_*`) ``` ## Public API -### Server struct +All server structs share the same generic signature: ```rust -pub struct PrivacyRpc { ... } - -impl PrivacyRpc { - pub fn new(client: Arc) -> Self -} +pub struct PrivacyRpc { pub fn new(client: Arc) -> Self } +pub struct ChainRpc { pub fn new(client: Arc) -> Self } ``` -Bounds required on the generic parameters: +Required bounds on generic parameters: | Parameter | Required traits | |-----------|----------------| @@ -33,15 +31,46 @@ Bounds required on the generic parameters: ### Registering with the node ```rust -use fc_rpc_v2::{PrivacyApiServer, PrivacyRpc}; +use fc_rpc_v2::{ChainApiServer, ChainRpc, PrivacyApiServer, PrivacyRpc}; io.merge(PrivacyRpc::new(client.clone()).into_rpc())?; +io.merge(ChainRpc::new(client.clone()).into_rpc())?; ``` --- ## Endpoints +### `chain_isValidator` + +Returns `true` if the given SS58-encoded account is an active Aura block author. + +Reads `pallet_aura::Authorities` directly from storage — no runtime API call is made. Intended for UIs that need to gate actions (e.g. relayer registration) on validator eligibility without waiting for an extrinsic to fail. + +**Parameters** + +| Name | Type | Description | +|------|------|-------------| +| `account` | `String` | SS58-encoded account address | + +**Result:** `bool` + +```json +// request +{ "jsonrpc": "2.0", "method": "chain_isValidator", "params": ["5GrwvaEF..."], "id": 1 } + +// response +{ "jsonrpc": "2.0", "result": true, "id": 1 } +``` + +**Storage read:** `Twox128("Aura") ++ Twox128("Authorities")` → SCALE `Vec<[u8; 32]>` + +**Errors** +- `InvalidParams` — invalid SS58 address +- `InternalError` — storage decode failure + +--- + ### `privacy_getMerkleRoot` Returns the current Merkle tree root. @@ -206,11 +235,19 @@ pub struct PoolStatsResponse { ## Storage access -All endpoints query `pallet-shielded-pool` storage directly using `sc_client_api::StorageProvider`. No Runtime API call is made, which means: +All endpoints query pallet storage directly using `sc_client_api::StorageProvider`. No Runtime API call is made, which means: - No runtime upgrade required to add or change these endpoints. - Keys are built with the standard Substrate hasher layout: +### `chain.rs` — `pallet_aura` + +| Storage item | Key layout | +|---|---| +| `Authorities` | `Twox128("Aura") ++ Twox128("Authorities")` | + +### `privacy.rs` — `pallet_shielded_pool` + | Storage item | Key layout | Hasher | |---|---|---| | `PoseidonRoot` | `Twox128("ShieldedPool") ++ Twox128("PoseidonRoot")` | — | diff --git a/client/rpc-v2/src/chain.rs b/client/rpc-v2/src/chain.rs new file mode 100644 index 00000000..6ba9decd --- /dev/null +++ b/client/rpc-v2/src/chain.rs @@ -0,0 +1,140 @@ +//! Chain RPC — general chain state queries. +//! +//! Endpoints: +//! - `chain_isValidator` — returns `true` if the given SS58 account is an active Aura validator. + +use jsonrpsee::{ + core::RpcResult, + proc_macros::rpc, + types::error::{ErrorCode, ErrorObject}, +}; +use sc_client_api::StorageProvider as ScStorageProvider; +use scale_codec::Decode; +use sp_blockchain::HeaderBackend; +use sp_core::{crypto::Ss58Codec, hashing::twox_128, storage::StorageKey}; +use sp_runtime::{traits::Block as BlockT, AccountId32}; +use std::{marker::PhantomData, sync::Arc}; + +// ============================================================================ +// Storage key helpers +// ============================================================================ + +/// `twox_128("Aura") ++ twox_128("Authorities")` — `StorageValue` key for `pallet_aura`. +fn aura_authorities_key() -> Vec { + [twox_128(b"Aura"), twox_128(b"Authorities")].concat() +} + +fn read_storage, BE: sc_client_api::Backend>( + client: &C, + hash: B::Hash, + key: Vec, +) -> RpcResult>> { + ScStorageProvider::storage(client, hash, &StorageKey(key)) + .map_err(internal_error) + .map(|opt| opt.map(|data| data.0)) +} + +// ============================================================================ +// RPC trait +// ============================================================================ + +#[rpc(server)] +pub trait ChainApi { + /// Returns `true` if the given SS58-encoded account is an active Aura validator. + /// + /// Reads `pallet_aura::Authorities` directly from storage at the best known block. + /// No runtime API call is performed. + #[method(name = "chain_isValidator")] + fn is_validator(&self, account: String) -> RpcResult; +} + +// ============================================================================ +// RPC server struct +// ============================================================================ + +/// Chain RPC server for general Orbinum chain state queries. +pub struct ChainRpc { + client: Arc, + _ph: PhantomData<(B, BE)>, +} + +impl ChainRpc { + pub fn new(client: Arc) -> Self { + Self { + client, + _ph: PhantomData, + } + } +} + +// ============================================================================ +// Error helpers +// ============================================================================ + +fn internal_error(msg: impl std::fmt::Display) -> ErrorObject<'static> { + ErrorObject::owned( + ErrorCode::InternalError.code(), + format!("Internal error: {msg}"), + None::<()>, + ) +} + +fn invalid_params(msg: impl std::fmt::Display) -> ErrorObject<'static> { + ErrorObject::owned( + ErrorCode::InvalidParams.code(), + format!("Invalid params: {msg}"), + None::<()>, + ) +} + +// ============================================================================ +// Implementation +// ============================================================================ + +impl ChainApiServer for ChainRpc +where + C: HeaderBackend + ScStorageProvider + Send + Sync + 'static, + B: BlockT, + BE: sc_client_api::Backend + Send + Sync + 'static, +{ + fn is_validator(&self, account: String) -> RpcResult { + // Parse SS58 address → 32-byte AccountId32. + let account_id = AccountId32::from_ss58check(&account) + .map_err(|e| invalid_params(format!("invalid SS58 address: {e:?}")))?; + let target: [u8; 32] = *account_id.as_ref(); + + // Read `Aura::Authorities` from storage at the best known block. + let best = self.client.info().best_hash; + let raw = read_storage(&*self.client, best, aura_authorities_key())?; + + let Some(bytes) = raw else { + // Storage item absent → chain has no configured validators. + return Ok(false); + }; + + // SCALE-decode as `Vec<[u8; 32]>`. + // `AuraId = sr25519::Public`, which serialises as a plain 32-byte array. + let authorities = Vec::<[u8; 32]>::decode(&mut bytes.as_slice()) + .map_err(|e| internal_error(format!("failed to decode Aura::Authorities: {e}")))?; + + Ok(authorities.iter().any(|auth| auth == &target)) + } +} + +#[cfg(test)] +mod tests { + use super::aura_authorities_key; + use sp_core::hashing::twox_128; + + #[test] + fn aura_authorities_key_is_32_bytes() { + assert_eq!(aura_authorities_key().len(), 32); + } + + #[test] + fn aura_authorities_key_uses_correct_hashes() { + let key = aura_authorities_key(); + assert_eq!(&key[..16], &twox_128(b"Aura")); + assert_eq!(&key[16..], &twox_128(b"Authorities")); + } +} diff --git a/client/rpc-v2/src/lib.rs b/client/rpc-v2/src/lib.rs index 7415563e..77f1013e 100644 --- a/client/rpc-v2/src/lib.rs +++ b/client/rpc-v2/src/lib.rs @@ -22,8 +22,10 @@ // Orbinum Privacy RPC // ============================================================================ +pub mod chain; pub mod privacy; +pub use chain::{ChainApiServer, ChainRpc}; pub use privacy::{ AssetBalanceResponse, MerkleProofResponse, NullifierStatusResponse, PoolStatsResponse, PrivacyApiServer, PrivacyRpc, From e5045ecc201bdcaec3421158b462827a143088c2 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 13:35:57 -0400 Subject: [PATCH 09/18] feat: add Orbinum relay functionality with shielded fees support - Introduced new relay module with operations for unshielding and private transfers. - Implemented RPC API for relaying shielded pool calls. - Added validation logic for calldata and fee requirements. - Created operations for handling unshield and private transfer requests. - Established types and structures for relay status and API responses. - Ensured all new components are unit tested for reliability. --- client/rpc/Cargo.toml | 1 + client/rpc/src/relay/mod.rs | 300 +++++++++++++++++ client/rpc/src/relay/operations.rs | 97 ++++++ client/rpc/src/relay/types.rs | 41 +++ client/rpc/src/relay/validation.rs | 515 +++++++++++++++++++++++++++++ 5 files changed, 954 insertions(+) create mode 100644 client/rpc/src/relay/mod.rs create mode 100644 client/rpc/src/relay/operations.rs create mode 100644 client/rpc/src/relay/types.rs create mode 100644 client/rpc/src/relay/validation.rs diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index 222dde7f..5c9f93b6 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -61,6 +61,7 @@ fp-evm = { workspace = true, features = ["default"] } fp-rpc = { workspace = true, features = ["default"] } fp-storage = { workspace = true, features = ["default"] } pallet-evm = { workspace = true, features = ["default"] } +pallet-relayer-runtime-api = { workspace = true, features = ["std"] } pallet-shielded-pool-runtime-api = { workspace = true, features = ["std"] } [dev-dependencies] diff --git a/client/rpc/src/relay/mod.rs b/client/rpc/src/relay/mod.rs new file mode 100644 index 00000000..28d174cf --- /dev/null +++ b/client/rpc/src/relay/mod.rs @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +//! Node-native EVM relay RPC. +//! +//! When the node is started with `--evm-relayer-key `, it exposes: +//! +//! - `orbinum_relayShieldedCall(calldata: "0x...")` → `txHash` +//! - `orbinum_relayerStatus()` → `{ address, minFee, balanceWei, enabled, isRegistered }` +//! +//! The relay only accepts calls to the ShieldedPool precompile +//! (`0x0000000000000000000000000000000000000801`) with selector +//! `0x47fc44a2` (unshield) or `0x8c0f5d24` (privateTransfer). +//! It checks the fee embedded in ABI slot 6 is ≥ the current `min_relay_fee` from +//! `pallet-relayer` (queried dynamically via Runtime API so forkless upgrades take effect immediately). +//! +//! # Module layout +//! +//! | Sub-module | Responsibility | +//! |-----------------|-------------------------------------------------------------| +//! | [`operations`] | `RelayableOperation` trait + `UnshieldOp` + `PrivateTransferOp` (Capa 3) | +//! | [`types`] | `OrbinumRelayApi` RPC trait + `RelayerStatus` response type | +//! | [`validation`] | Pure calldata validation + fee-floor computation + tests | + +pub mod operations; +pub mod types; +pub mod validation; + +pub use types::{OrbinumRelayApiServer, RelayerStatus}; + +use std::sync::Arc; + +use ethereum::TransactionAction; +use ethereum_types::{H160, H256, U256}; +use jsonrpsee::core::RpcResult; +// Substrate +use sc_client_api::backend::{Backend, StorageProvider}; +use sc_transaction_pool_api::{TransactionPool, TransactionSource}; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_runtime::traits::Block as BlockT; +// Frontier +use fc_rpc_core::types::{Bytes, TransactionMessage}; +use fp_rpc::{ConvertTransactionRuntimeApi, EthereumRuntimeRPCApi}; +// Orbinum +use pallet_relayer_runtime_api::RelayerRuntimeApi; +use pallet_shielded_pool_runtime_api::ShieldedPoolRuntimeApi; + +use crate::{internal_err, signer::EthValidatorSigner}; + +use validation::{ + check_dry_run_exit, compute_effective_min_fee, validate_relay_calldata, MAX_FEE_PER_GAS_WEI, + MIN_RELAY_FEE_FALLBACK, RELAY_GAS_LIMIT, SELECTORS_FALLBACK, SHIELDED_POOL_PRECOMPILE, +}; + +// --------------------------------------------------------------------------- +// Server struct +// --------------------------------------------------------------------------- + +pub struct OrbinumRelay { + client: Arc, + pool: Arc

, + signer: EthValidatorSigner, + /// Serializes relay submissions AND tracks the optimistic next-nonce. + /// + /// Why `Option` instead of `()`: + /// + /// Each submission is: (1) read confirmed nonce, (2) sign, (3) submit. With a plain + /// Mutex<()> two requests in the same block both read `confirmed = N`, sign with N, + /// and the second submission fails with "nonce already used", burning gas for nothing. + /// + /// By storing the last submitted nonce we can compute: + /// actual_nonce = max(confirmed_nonce, memory_nonce) + /// which allows multiple submissions within the same block to use N, N+1, N+2… + /// On the next block `confirmed_nonce` catches up and `memory_nonce` resets naturally. + /// + /// `None` = no submission has been made yet; read from runtime. + submit_lock: Arc>>, + _phantom: std::marker::PhantomData<(B, BE)>, +} + +impl OrbinumRelay +where + B: BlockT, +{ + pub fn new(client: Arc, pool: Arc

, signer: EthValidatorSigner) -> Self { + Self { + client, + pool, + signer, + submit_lock: Arc::new(tokio::sync::Mutex::new(None)), + _phantom: Default::default(), + } + } +} + +#[jsonrpsee::core::async_trait] +impl OrbinumRelayApiServer for OrbinumRelay +where + B: BlockT, + C: ProvideRuntimeApi + HeaderBackend + StorageProvider + 'static, + C::Api: EthereumRuntimeRPCApi + + ConvertTransactionRuntimeApi + + ShieldedPoolRuntimeApi + + RelayerRuntimeApi, + BE: Backend + 'static, + P: TransactionPool + 'static, +{ + async fn relay_shielded_call(&self, calldata: Bytes) -> RpcResult { + let data = calldata.into_vec(); + + // Load fee/selector config from the runtime on every call so governance changes + // (set_min_relay_fee extrinsic) take immediate effect without a node restart. + // MIN_RELAY_FEE_FALLBACK is only used when the Runtime API call fails entirely. + let best_hash = self.client.info().best_hash; + let (min_fee_planck, allowed_selectors) = { + match self.client.runtime_api().relay_config(best_hash) { + Ok(cfg) => (cfg.min_fee_planck, cfg.allowed_selectors), + Err(e) => { + log::warn!( + target: "orbinum-relay", + "relay_config Runtime API unavailable, using fallback: {e}" + ); + (MIN_RELAY_FEE_FALLBACK, SELECTORS_FALLBACK.to_vec()) + } + } + }; + + // Compute 2× gas floor: the relay must earn at least twice what it spends on EVM gas. + // 1 wei == 1 plank in Orbinum, so no unit conversion is required. + let base_fee_wei: u128 = self + .client + .runtime_api() + .gas_price(best_hash) + .map(|p| p.as_u128()) + .unwrap_or(0); + let effective_min_fee = compute_effective_min_fee(min_fee_planck, base_fee_wei); + + if let Err(e) = validate_relay_calldata(&data, effective_min_fee, &allowed_selectors) { + if e == "fee below minimum" { + // Extract the fee from slot 6 for the log (validated length already). + let provided_fee = if data.len() >= 228 { + U256::from_big_endian(&data[196..228]) + } else { + U256::zero() + }; + log::warn!( + target: "orbinum-relay", + "relay rejected: fee below minimum — provided={provided_fee} required={effective_min_fee} (base_fee={base_fee_wei} governance={min_fee_planck})", + ); + } + return Err(internal_err(e)); + } + + // Dry-run: simulate the EVM call without broadcasting a transaction. + // This catches invalid ZK proofs, already-spent nullifiers, and any other + // on-chain rejection BEFORE the relayer signs and pays gas. + { + let relayer_addr = self.signer.address(); + let dry_result = self.client.runtime_api().call( + best_hash, + relayer_addr, + H160::from(SHIELDED_POOL_PRECOMPILE), + data.clone(), + U256::zero(), + U256::from(RELAY_GAS_LIMIT), + Some(U256::from(MAX_FEE_PER_GAS_WEI)), + Some(U256::from(1_000_000_000u64)), + None, // nonce — not needed for simulation + false, // estimate = false: real execution semantics + None, // access_list + None, // authorization_list + ); + match dry_result { + Err(e) => { + log::warn!( + target: "orbinum-relay", + "dry-run Runtime API error: {e}" + ); + return Err(internal_err(format!("dry-run runtime error: {e}"))); + } + Ok(Err(dispatch_err)) => { + log::warn!( + target: "orbinum-relay", + "dry-run dispatch error: {dispatch_err:?}" + ); + return Err(internal_err(format!( + "calldata rejected by runtime: {dispatch_err:?}" + ))); + } + Ok(Ok(info)) => { + if let Err(e) = check_dry_run_exit(&info.exit_reason) { + log::warn!( + target: "orbinum-relay", + "dry-run EVM execution failed — exit={:?} revert_data={:?}", + info.exit_reason, + info.value + ); + return Err(internal_err(e)); + } + } + } + } + + // Hold the lock for the full sign-and-submit sequence. + // The lock also carries the optimistic next-nonce so multiple submissions + // within the same block don't collide on the same confirmed nonce. + let mut nonce_guard = self.submit_lock.lock().await; + + let relayer_addr = self.signer.address(); + + let (chain_id, nonce) = { + let api = self.client.runtime_api(); + let chain_id = api + .chain_id(best_hash) + .map_err(|e| internal_err(format!("chain_id: {e}")))?; + let confirmed_nonce = api + .account_basic(best_hash, relayer_addr) + .map_err(|e| internal_err(format!("account_basic: {e}")))? + .nonce; + // Use the in-memory nonce when it's ahead of the confirmed one. + // This lets us submit N txs within a single block using nonces N, N+1, N+2… + // Once the block is imported the confirmed nonce catches up naturally. + let nonce = match *nonce_guard { + Some(mem) if mem > confirmed_nonce => mem, + _ => confirmed_nonce, + }; + (chain_id, nonce) + }; + + let message = TransactionMessage::EIP1559(ethereum::EIP1559TransactionMessage { + chain_id, + nonce, + max_priority_fee_per_gas: U256::from(1_000_000_000u64), + max_fee_per_gas: U256::from(MAX_FEE_PER_GAS_WEI), + gas_limit: U256::from(RELAY_GAS_LIMIT), + action: TransactionAction::Call(H160::from(SHIELDED_POOL_PRECOMPILE)), + value: U256::zero(), + input: data, + access_list: vec![], + }); + + use crate::signer::EthSigner as _; + let transaction = self.signer.sign(message, &relayer_addr)?; + let tx_hash = transaction.hash(); + + let extrinsic = { + let api = self.client.runtime_api(); + api.convert_transaction(best_hash, transaction) + .map_err(|e| internal_err(format!("convert_transaction: {e}")))? + }; + + let submit_result = self + .pool + .submit_one(best_hash, TransactionSource::Local, extrinsic) + .await + .map(|_| tx_hash) + .map_err(|e| internal_err(format!("pool submit: {e}"))); + + // Advance the in-memory nonce only after a successful submit. + // On failure the nonce slot is still free and the next call will retry with + // the same (or a freshly confirmed) nonce. + if submit_result.is_ok() { + *nonce_guard = Some(nonce + U256::one()); + } + + submit_result + } + + async fn relayer_status(&self) -> RpcResult { + let best_hash = self.client.info().best_hash; + let api = self.client.runtime_api(); + + let min_fee_planck = api + .relay_config(best_hash) + .map(|cfg| cfg.min_fee_planck) + .unwrap_or(MIN_RELAY_FEE_FALLBACK); + + let base_fee_wei: u128 = api.gas_price(best_hash).map(|p| p.as_u128()).unwrap_or(0); + let min_fee = compute_effective_min_fee(min_fee_planck, base_fee_wei); + + let balance = { + api.account_basic(best_hash, self.signer.address()) + .map_err(|e| internal_err(format!("account_basic: {e}")))? + .balance + }; + // Capa 2: check whether this relay's EVM address is registered on-chain. + let is_registered = api + .is_relayer_evm(best_hash, self.signer.address().0) + .unwrap_or(false); + // Consider relay operational when it can cover at least one worst-case tx. + let min_operational = U256::from(min_fee); + Ok(RelayerStatus { + address: self.signer.address(), + min_fee: format!("{min_fee}"), + balance_wei: format!("{balance}"), + enabled: balance >= min_operational, + is_registered, + }) + } +} diff --git a/client/rpc/src/relay/operations.rs b/client/rpc/src/relay/operations.rs new file mode 100644 index 00000000..05feab1b --- /dev/null +++ b/client/rpc/src/relay/operations.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +//! Capa 3 — Generic `RelayableOperation` trait and built-in implementations. +//! +//! Each operation describes how to validate calldata for a specific on-chain call: +//! its ABI selector, the minimum calldata length required, and where to find the +//! embedded relay fee. Adding support for a new relayable precompile call only +//! requires implementing this trait and registering the struct in +//! [`default_operations`]. + +use ethereum_types::U256; + +/// 4-byte ABI selector for `unshield(...)`. +pub(crate) const SELECTOR_UNSHIELD: [u8; 4] = [0x47, 0xfc, 0x44, 0xa2]; + +/// 4-byte ABI selector for `privateTransfer(...)`. +pub(crate) const SELECTOR_PRIVATE_TRANSFER: [u8; 4] = [0x8c, 0x0f, 0x5d, 0x24]; + +/// Describes how to validate calldata for a specific relayable on-chain operation. +/// +/// Implementing this trait for a new operation allows the relay to accept it +/// without modifying [`validate_relay_calldata`] — Capa 3 of the relay architecture. +/// +/// [`validate_relay_calldata`]: super::validation::validate_relay_calldata +pub(crate) trait RelayableOperation: Send + Sync { + /// 4-byte ABI selector identifying this operation. + fn selector(&self) -> [u8; 4]; + + /// Human-readable name (used in log messages and RPC responses). + fn name(&self) -> &'static str; + + /// Minimum required calldata length (bytes) for this operation's ABI layout. + fn min_calldata_len(&self) -> usize; + + /// Extract the relay fee (planck/wei) from validated calldata. + /// + /// Only called after `data.len() >= min_calldata_len()` is confirmed. + fn extract_fee(&self, calldata: &[u8]) -> u128; +} + +/// `unshield(proof, root, nullifier, asset_id, amount, recipient, fee)` — `0x47fc44a2` +/// +/// Fee is in ABI slot 6: `calldata[196..228]`. +pub(crate) struct UnshieldOp; + +impl RelayableOperation for UnshieldOp { + fn selector(&self) -> [u8; 4] { + SELECTOR_UNSHIELD + } + + fn name(&self) -> &'static str { + "unshield" + } + + fn min_calldata_len(&self) -> usize { + 228 + } + + fn extract_fee(&self, calldata: &[u8]) -> u128 { + let bytes: [u8; 32] = calldata[196..228].try_into().unwrap(); + U256::from_big_endian(&bytes).as_u128() + } +} + +/// `privateTransfer(proof, root, nullifiers, commitments, memos, asset_id, fee)` — `0x8c0f5d24` +/// +/// Fee is in ABI slot 6: `calldata[196..228]`. +pub(crate) struct PrivateTransferOp; + +impl RelayableOperation for PrivateTransferOp { + fn selector(&self) -> [u8; 4] { + SELECTOR_PRIVATE_TRANSFER + } + + fn name(&self) -> &'static str { + "privateTransfer" + } + + fn min_calldata_len(&self) -> usize { + 228 + } + + fn extract_fee(&self, calldata: &[u8]) -> u128 { + let bytes: [u8; 32] = calldata[196..228].try_into().unwrap(); + U256::from_big_endian(&bytes).as_u128() + } +} + +/// All operations the relay supports out of the box. +/// +/// New operations can be registered by adding them here and implementing +/// [`RelayableOperation`]. The governance `AllowedSelectors` whitelist acts as +/// an additional filter — only operations whose selectors are in the whitelist +/// are accepted at runtime. +pub(crate) fn default_operations() -> Vec> { + vec![Box::new(UnshieldOp), Box::new(PrivateTransferOp)] +} diff --git a/client/rpc/src/relay/types.rs b/client/rpc/src/relay/types.rs new file mode 100644 index 00000000..c41abc16 --- /dev/null +++ b/client/rpc/src/relay/types.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +//! RPC trait definition and response types for the Orbinum relay. + +use ethereum_types::{H160, H256}; +use fc_rpc_core::types::Bytes; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use serde::{Deserialize, Serialize}; + +#[rpc(server)] +pub trait OrbinumRelayApi { + /// Relay a shielded-pool call (unshield or privateTransfer) on behalf of a user. + /// + /// `calldata` must be ABI-encoded EVM calldata for the ShieldedPool precompile, + /// including the 4-byte selector. The fee in ABI slot index 6 must be ≥ MIN_RELAY_FEE_WEI. + /// + /// Returns the Ethereum transaction hash. + #[method(name = "orbinum_relayShieldedCall")] + async fn relay_shielded_call(&self, calldata: Bytes) -> RpcResult; + + /// Returns the relay status: EVM address, minimum fee, current balance, and whether + /// the relay has sufficient funds to process at least one transaction. + #[method(name = "orbinum_relayerStatus")] + async fn relayer_status(&self) -> RpcResult; +} + +/// Relay status returned by `orbinum_relayerStatus`. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RelayerStatus { + pub address: H160, + pub min_fee: String, + /// Current EVM balance of the relay wallet (wei). Lets callers verify the relay is funded. + pub balance_wei: String, + /// True only when balance ≥ effective min fee (enough to cover at least one relay tx). + pub enabled: bool, + /// True when this relay's EVM address is registered on-chain via `register_relayer`. + /// Unregistered relays still forward transactions but fees are attributed to the block author + /// instead of the intended validator. Capa 2 of the relay architecture. + pub is_registered: bool, +} diff --git a/client/rpc/src/relay/validation.rs b/client/rpc/src/relay/validation.rs new file mode 100644 index 00000000..3793af5b --- /dev/null +++ b/client/rpc/src/relay/validation.rs @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +//! Pure calldata validation — no runtime or async dependencies. +//! +//! All functions here are synchronous and fully unit-testable without spinning up +//! a Substrate node. The tests at the bottom of this file cover every validation +//! branch and fee-floor scenario. + +use fp_evm::ExitReason; + +use super::operations::default_operations; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +/// Maximum fee per gas paid by the relay tx (10 gwei). +/// Used when building the EIP-1559 transaction. +pub(crate) const MAX_FEE_PER_GAS_WEI: u64 = 10_000_000_000; + +/// Gas limit used for relay transactions. +pub(crate) const RELAY_GAS_LIMIT: u64 = 2_000_000; + +/// Last-resort fallback minimum fee used ONLY when the Runtime API call fails entirely. +/// +/// The authoritative value lives in `pallet-relayer::MinRelayFee` storage and is +/// modifiable by governance via `set_min_relay_fee`. This constant is never used in +/// normal operation — it only kicks in if the node runs a pre-API runtime that does +/// not expose `relay_config()`. +/// +/// Set to the same default as `pallet-relayer::DefaultMinRelayFee` (0.001 ORB) so all +/// three sources are identical out of the box. +pub(crate) const MIN_RELAY_FEE_FALLBACK: u128 = 1_000_000_000_000_000; // 0.001 ORB in planck + +/// Static fallback selector whitelist for when the Runtime API is unavailable. +pub(crate) const SELECTORS_FALLBACK: [[u8; 4]; 2] = [ + [0x47, 0xfc, 0x44, 0xa2], // unshield + [0x8c, 0x0f, 0x5d, 0x24], // privateTransfer +]; + +/// Maximum calldata size accepted by the relay (32 KB). +/// +/// A realistic shielded-pool calldata is ~2–5 KB (256 B Groth16 proof + ABI head + Merkle path). +/// This cap prevents DoS: an attacker could craft calldata that passes selector/fee checks +/// but carries megabytes of data the relayer would have to pay calldata gas for. +pub(crate) const MAX_CALLDATA_BYTES: usize = 32_768; + +/// ShieldedPool precompile: 0x0000000000000000000000000000000000000801 +pub(crate) const SHIELDED_POOL_PRECOMPILE: [u8; 20] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x08, 0x01, +]; + +// --------------------------------------------------------------------------- +// Pure validation functions +// --------------------------------------------------------------------------- + +/// Computes the effective minimum fee the user must include in their calldata. +/// +/// The relay must earn at least twice what it spends on EVM gas, so the floor is: +/// `2 × RELAY_GAS_LIMIT × base_fee_per_gas` +/// +/// The governance-set `min_fee_planck` is the absolute lower bound; the 2× gas floor +/// applies on top whenever network gas prices are high enough to make it exceed governance. +/// Both values are in planck (= wei in Orbinum's 1:1 mapping). +pub(crate) fn compute_effective_min_fee(min_fee_planck: u128, base_fee_wei: u128) -> u128 { + let two_x_gas_floor = (RELAY_GAS_LIMIT as u128) + .saturating_mul(2) + .saturating_mul(base_fee_wei); + min_fee_planck.max(two_x_gas_floor) +} + +/// Validates relay calldata against a runtime-provided minimum fee and selector whitelist. +/// +/// `min_fee_wei` — from `ShieldedPoolRuntimeApi::relay_config().min_fee_planck` +/// (or `MIN_RELAY_FEE_FALLBACK` if the API is unavailable). +/// `allowed_selectors` — from `relay_config().allowed_selectors`. +/// +/// Both `unshield` and `privateTransfer` share the same ABI head layout: +/// ```text +/// bytes [0..4] selector +/// bytes [4..36] slot 0 — offset pointer for proof (bytes/dynamic) +/// bytes [36..68] slot 1 — bytes32 root +/// bytes [68..100] slot 2 — bytes32 nullifier / bytes32[] nullifiers offset +/// bytes [100..132] slot 3 — uint32 asset_id / bytes32[] commits offset +/// bytes [132..164] slot 4 — uint256 amount / bytes[] memos offset +/// bytes [164..196] slot 5 — bytes32 recipient / uint32 asset_id +/// bytes [196..228] slot 6 — uint256 fee ← checked here +/// ``` +/// Minimum head size = 4 + 7 × 32 = 228 bytes. +pub(crate) fn validate_relay_calldata( + data: &[u8], + min_fee_wei: u128, + allowed_selectors: &[[u8; 4]], +) -> Result<(), &'static str> { + // Global minimum: selector (4 B) + 6 ABI head slots (6 × 32 B) = 228 B. + if data.len() < 228 { + return Err("calldata too short"); + } + if data.len() > MAX_CALLDATA_BYTES { + return Err("calldata too large"); + } + + let selector: [u8; 4] = data[..4].try_into().unwrap(); + if !allowed_selectors.contains(&selector) { + return Err("unsupported selector"); + } + + // Capa 3: dispatch to the registered RelayableOperation for per-op fee extraction. + // If the governance whitelist contains a selector the node does not implement, + // the relay rejects it — requiring a node update to support new operations. + let op = default_operations() + .into_iter() + .find(|op| op.selector() == selector) + .ok_or("unsupported selector")?; + + log::trace!(target: "orbinum-relay", "validating {} calldata ({} bytes)", op.name(), data.len()); + + if data.len() < op.min_calldata_len() { + return Err("calldata too short"); + } + + if op.extract_fee(data) < min_fee_wei { + return Err("fee below minimum"); + } + + Ok(()) +} + +/// Interprets the `exit_reason` from an EVM dry-run and returns whether the call +/// would succeed on-chain. +/// +/// Returns `Ok(())` only when `exit_reason` is `ExitReason::Succeed(_)`. +/// All other outcomes (revert, error, fatal) return `Err` with a human-readable +/// description so callers can surface it to the user without paying gas. +pub(crate) fn check_dry_run_exit(exit_reason: &ExitReason) -> Result<(), String> { + match exit_reason { + ExitReason::Succeed(_) => Ok(()), + other => Err(format!("calldata would fail on-chain: {other:?}")), + } +} + +// --------------------------------------------------------------------------- +// Unit tests — pure validation logic, no runtime required +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use ethereum_types::U256; + + use super::super::operations::{SELECTOR_PRIVATE_TRANSFER, SELECTOR_UNSHIELD}; + use super::*; + + /// Build minimal valid calldata for the given selector and fee. + /// + /// Head layout (228 bytes total): + /// ```text + /// [0..4] selector + /// [4..36] slot 0 — proof offset: 0xE0 (= 7×32 = 224, past all head slots) + /// [36..68] slot 1 — bytes32 zeroes (root) + /// [68..100] slot 2 — bytes32 zeroes (nullifier / nullifiers offset) + /// [100..132] slot 3 — uint32 zeroes (assetId / commitments offset) + /// [132..164] slot 4 — uint256 zeroes (amount / memos offset) + /// [164..196] slot 5 — bytes32 zeroes (recipient / assetId) + /// [196..228] slot 6 — uint256 fee + /// ``` + fn build_calldata(selector: [u8; 4], fee_wei: u128) -> Vec { + let mut data = vec![0u8; 228]; + data[..4].copy_from_slice(&selector); + // Proof-bytes offset: 7×32 = 224 = 0xE0 (big-endian U256 → only last byte set) + data[35] = 0xE0; + // Fee at slot 6 = data[196..228] + data[196..228].copy_from_slice(&U256::from(fee_wei).to_big_endian()); + data + } + + // ── Length checks ────────────────────────────────────────────────────── + + #[test] + fn rejects_empty_calldata() { + assert_eq!( + validate_relay_calldata(&[], MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Err("calldata too short") + ); + } + + #[test] + fn rejects_calldata_227_bytes() { + assert_eq!( + validate_relay_calldata(&[0u8; 227], MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Err("calldata too short") + ); + } + + #[test] + fn rejects_calldata_196_bytes_old_wrong_limit() { + // Ensure the old (incorrect) limit of 196 is no longer accepted + let data = vec![0u8; 196]; + assert_eq!( + validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Err("calldata too short") + ); + } + + // ── Selector checks ──────────────────────────────────────────────────── + + #[test] + fn rejects_unknown_selector() { + let mut data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK); + data[..4].copy_from_slice(&[0xde, 0xad, 0xbe, 0xef]); + assert_eq!( + validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Err("unsupported selector") + ); + } + + #[test] + fn rejects_shield_selector() { + // shield = 0x781442b9 — NOT in relay whitelist + let mut data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK); + data[..4].copy_from_slice(&[0x78, 0x14, 0x42, 0xb9]); + assert_eq!( + validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Err("unsupported selector") + ); + } + + // ── Fee checks ───────────────────────────────────────────────────────── + + #[test] + fn rejects_zero_fee() { + let data = build_calldata(SELECTOR_UNSHIELD, 0); + assert_eq!( + validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Err("fee below minimum") + ); + } + + #[test] + fn rejects_fee_one_wei_below_minimum() { + let data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK - 1); + assert_eq!( + validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Err("fee below minimum") + ); + } + + #[test] + fn rejects_fee_one_wei_below_minimum_private_transfer() { + let data = build_calldata(SELECTOR_PRIVATE_TRANSFER, MIN_RELAY_FEE_FALLBACK - 1); + assert_eq!( + validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Err("fee below minimum") + ); + } + + // ── Valid calldata ───────────────────────────────────────────────────── + + #[test] + fn accepts_unshield_with_exact_minimum_fee() { + let data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK); + assert_eq!( + validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Ok(()) + ); + } + + #[test] + fn accepts_private_transfer_with_exact_minimum_fee() { + let data = build_calldata(SELECTOR_PRIVATE_TRANSFER, MIN_RELAY_FEE_FALLBACK); + assert_eq!( + validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Ok(()) + ); + } + + #[test] + fn accepts_large_fee() { + let data = build_calldata(SELECTOR_UNSHIELD, u128::MAX); + assert_eq!( + validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Ok(()) + ); + } + + #[test] + fn accepts_calldata_longer_than_228_bytes() { + let mut data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK); + // Append tail bytes (proof data and dynamic arrays) + data.extend_from_slice(&[0xaa; 128]); + assert_eq!( + validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Ok(()) + ); + } + + // ── Fee is read from the correct position ────────────────────────────── + + #[test] + fn fee_at_slot_5_is_not_read_as_fee() { + // Put a value >= MIN_RELAY_FEE_FALLBACK in slot 5 (data[164..196]) but zero in slot 6 + let mut data = build_calldata(SELECTOR_UNSHIELD, 0); + // Overwrite slot 5 with MIN_RELAY_FEE_FALLBACK (this is recipient in unshield — NOT the fee) + data[164..196].copy_from_slice(&U256::from(MIN_RELAY_FEE_FALLBACK).to_big_endian()); + // Fee (slot 6, data[196..228]) is still zero → should reject + assert_eq!( + validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Err("fee below minimum") + ); + } + + #[test] + fn fee_at_slot_6_is_correctly_read() { + // Slot 5 = zero, slot 6 = MIN_RELAY_FEE_FALLBACK → should accept + let data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK); + assert_eq!( + validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Ok(()) + ); + } + + // ── Calldata size upper bound ────────────────────────────────────────── + + #[test] + fn rejects_calldata_above_max_size() { + let mut data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK); + data.resize(MAX_CALLDATA_BYTES + 1, 0u8); + assert_eq!( + validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Err("calldata too large") + ); + } + + #[test] + fn accepts_calldata_at_exact_max_size() { + let mut data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK); + data.resize(MAX_CALLDATA_BYTES, 0u8); + assert_eq!( + validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), + Ok(()) + ); + } + + // ── compute_effective_min_fee ────────────────────────────────────────── + + /// At zero base_fee the governance floor must win. + #[test] + fn effective_min_fee_zero_base_fee_returns_governance_floor() { + assert_eq!( + compute_effective_min_fee(MIN_RELAY_FEE_FALLBACK, 0), + MIN_RELAY_FEE_FALLBACK + ); + } + + /// At 10 gwei the 2× gas floor (40_000_000_000_000_000) must beat governance (0.001 ORB). + #[test] + fn effective_min_fee_gas_floor_dominates_at_10_gwei() { + let base_fee = 10_000_000_000u128; // 10 gwei + let expected = (RELAY_GAS_LIMIT as u128) * 2 * base_fee; + assert!( + expected > MIN_RELAY_FEE_FALLBACK, + "test precondition: gas floor must exceed governance" + ); + assert_eq!( + compute_effective_min_fee(MIN_RELAY_FEE_FALLBACK, base_fee), + expected + ); + } + + /// At 1 wei base_fee the gas floor (4_000_000) is far below governance (0.001 ORB). + #[test] + fn effective_min_fee_governance_floor_dominates_at_negligible_base_fee() { + let base_fee = 1u128; + let gas_floor = (RELAY_GAS_LIMIT as u128) * 2 * base_fee; + assert!( + MIN_RELAY_FEE_FALLBACK > gas_floor, + "test precondition: governance must exceed gas floor" + ); + assert_eq!( + compute_effective_min_fee(MIN_RELAY_FEE_FALLBACK, base_fee), + MIN_RELAY_FEE_FALLBACK + ); + } + + /// The crossover point is at base_fee = governance / (gas_limit × 2) = 250_000_000 (0.25 gwei). + /// Below this threshold governance wins; at or above, the gas floor wins. + #[test] + fn effective_min_fee_crossover_at_250_mwei() { + // threshold = 1_000_000_000_000_000 / (2_000_000 × 2) = 250_000_000 + let threshold = MIN_RELAY_FEE_FALLBACK / (RELAY_GAS_LIMIT as u128 * 2); + // At threshold: 2×gas == governance, max returns governance. + let at = compute_effective_min_fee(MIN_RELAY_FEE_FALLBACK, threshold); + // One wei above: 2×gas > governance. + let above = compute_effective_min_fee(MIN_RELAY_FEE_FALLBACK, threshold + 1); + assert_eq!(at, MIN_RELAY_FEE_FALLBACK); + assert!(above > MIN_RELAY_FEE_FALLBACK); + } + + /// A governance floor higher than the gas floor must win regardless of base_fee. + #[test] + fn effective_min_fee_custom_high_governance_beats_gas_floor() { + let governance = 100_000_000_000_000_000u128; // 0.1 ORB + let base_fee = 1_000_000_000u128; // 1 gwei + let gas_floor = (RELAY_GAS_LIMIT as u128) * 2 * base_fee; + assert!( + governance > gas_floor, + "test precondition: governance must exceed gas floor" + ); + assert_eq!(compute_effective_min_fee(governance, base_fee), governance); + } + + /// Saturating arithmetic must not panic on u128::MAX inputs. + #[test] + fn effective_min_fee_saturates_without_panic() { + let result = compute_effective_min_fee(u128::MAX, u128::MAX); + assert_eq!(result, u128::MAX); + } + + /// The calldata fee must be validated against `effective_min_fee`, not raw `min_fee_planck`. + /// Simulates the scenario where the gas floor is the active minimum. + #[test] + fn validate_calldata_rejects_fee_below_gas_floor_even_above_governance() { + let governance = MIN_RELAY_FEE_FALLBACK; + let base_fee = 10_000_000_000u128; // 10 gwei + let gas_floor = (RELAY_GAS_LIMIT as u128) * 2 * base_fee; // 40_000_000_000_000_000 + assert!(gas_floor > governance); + let effective = compute_effective_min_fee(governance, base_fee); + // A fee that clears governance but not the gas floor is rejected. + let below_gas_floor = gas_floor - 1; + let data = build_calldata(SELECTOR_UNSHIELD, below_gas_floor); + assert_eq!( + validate_relay_calldata(&data, effective, &SELECTORS_FALLBACK), + Err("fee below minimum") + ); + } + + /// Fee that exactly equals the gas floor (when it dominates) must be accepted. + #[test] + fn validate_calldata_accepts_exact_gas_floor() { + let governance = MIN_RELAY_FEE_FALLBACK; + let base_fee = 10_000_000_000u128; // 10 gwei + let effective = compute_effective_min_fee(governance, base_fee); + let data = build_calldata(SELECTOR_UNSHIELD, effective); + assert_eq!( + validate_relay_calldata(&data, effective, &SELECTORS_FALLBACK), + Ok(()) + ); + } + + // ── check_dry_run_exit ───────────────────────────────────────────────── + + /// A successful EVM execution must pass the dry-run check. + #[test] + fn dry_run_exit_succeed_returns_ok() { + use evm::ExitSucceed; + assert!(check_dry_run_exit(&ExitReason::Succeed(ExitSucceed::Returned)).is_ok()); + assert!(check_dry_run_exit(&ExitReason::Succeed(ExitSucceed::Stopped)).is_ok()); + assert!(check_dry_run_exit(&ExitReason::Succeed(ExitSucceed::Suicided)).is_ok()); + } + + /// A revert (invalid ZK proof, double-spend nullifier, etc.) must be rejected. + #[test] + fn dry_run_exit_revert_returns_err() { + use evm::ExitRevert; + let result = check_dry_run_exit(&ExitReason::Revert(ExitRevert::Reverted)); + assert!(result.is_err()); + let msg = result.unwrap_err(); + assert!( + msg.contains("calldata would fail on-chain"), + "expected 'calldata would fail on-chain' in: {msg}" + ); + assert!(msg.contains("Revert"), "expected 'Revert' in: {msg}"); + } + + /// An EVM error (OutOfGas, etc.) must be rejected. + #[test] + fn dry_run_exit_error_out_of_gas_returns_err() { + use evm::ExitError; + let result = check_dry_run_exit(&ExitReason::Error(ExitError::OutOfGas)); + assert!(result.is_err()); + let msg = result.unwrap_err(); + assert!(msg.contains("calldata would fail on-chain"), "{msg}"); + assert!(msg.contains("OutOfGas"), "expected 'OutOfGas' in: {msg}"); + } + + /// A call-too-deep EVM error (stack overflow) must be rejected. + #[test] + fn dry_run_exit_error_call_too_deep_returns_err() { + use evm::ExitError; + let result = check_dry_run_exit(&ExitReason::Error(ExitError::CallTooDeep)); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("CallTooDeep")); + } + + /// A fatal EVM error must be rejected. + #[test] + fn dry_run_exit_fatal_returns_err() { + use evm::ExitFatal; + let result = check_dry_run_exit(&ExitReason::Fatal(ExitFatal::NotSupported)); + assert!(result.is_err()); + let msg = result.unwrap_err(); + assert!(msg.contains("calldata would fail on-chain"), "{msg}"); + assert!(msg.contains("Fatal"), "expected 'Fatal' in: {msg}"); + } + + /// The error message must embed the exit reason so callers can surface it to users. + #[test] + fn dry_run_exit_error_message_includes_reason() { + use evm::ExitError; + let reason = ExitReason::Error(ExitError::OutOfFund); + let err = check_dry_run_exit(&reason).unwrap_err(); + // The message should contain both the prefix and the specific variant. + assert!(err.starts_with("calldata would fail on-chain:"), "{err}"); + assert!(err.contains("OutOfFund"), "{err}"); + } +} From 399c2392f62ac505f1a5c324f8c9b56911247fc3 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 13:36:26 -0400 Subject: [PATCH 10/18] feat: integrate Chain RPC and Relayer API for enhanced Ethereum compatibility --- template/node/src/rpc/eth.rs | 2 ++ template/node/src/rpc/mod.rs | 7 ++++--- template/runtime/src/lib.rs | 12 ++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/template/node/src/rpc/eth.rs b/template/node/src/rpc/eth.rs index 9dd99e43..babeec2a 100644 --- a/template/node/src/rpc/eth.rs +++ b/template/node/src/rpc/eth.rs @@ -23,6 +23,7 @@ pub use fc_rpc::{EthBlockDataCacheTask, EthConfig}; pub use fc_rpc_core::types::{FeeHistoryCache, FeeHistoryCacheLimit, FilterPool}; use fc_storage::StorageOverride; use fp_rpc::{ConvertTransaction, ConvertTransactionRuntimeApi, EthereumRuntimeRPCApi}; +use pallet_relayer_runtime_api::RelayerRuntimeApi; use pallet_shielded_pool_runtime_api::ShieldedPoolRuntimeApi; /// Extra dependencies for Ethereum compatibility. @@ -84,6 +85,7 @@ where + BlockBuilderApi + ConvertTransactionRuntimeApi + EthereumRuntimeRPCApi + + RelayerRuntimeApi + ShieldedPoolRuntimeApi, C: HeaderBackend + HeaderMetadata, C: BlockchainEvents + AuxStore + UsageProvider + StorageProvider + 'static, diff --git a/template/node/src/rpc/mod.rs b/template/node/src/rpc/mod.rs index 6fbda094..469a865d 100644 --- a/template/node/src/rpc/mod.rs +++ b/template/node/src/rpc/mod.rs @@ -87,8 +87,8 @@ where use sc_consensus_manual_seal::rpc::{ManualSeal, ManualSealApiServer}; use substrate_frame_rpc_system::{System, SystemApiServer}; - // Orbinum Privacy RPC - use fc_rpc_v2::{PrivacyApiServer, PrivacyRpc}; + // Orbinum Privacy + Chain RPC + use fc_rpc_v2::{ChainApiServer, ChainRpc, PrivacyApiServer, PrivacyRpc}; let mut io = RpcModule::new(()); let FullDeps { @@ -105,8 +105,9 @@ where io.merge(ZkVerifier::new(client.clone()).into_rpc())?; io.merge(Relayer::new(client.clone()).into_rpc())?; - // Orbinum Privacy RPC + // Orbinum Privacy + Chain RPC io.merge(PrivacyRpc::new(client.clone()).into_rpc())?; + io.merge(ChainRpc::new(client.clone()).into_rpc())?; if let Some(command_sink) = command_sink { io.merge( diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index 8e66ac26..f401338e 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -1401,6 +1401,18 @@ impl_runtime_apis! { fn registered_evm_address(account: sp_runtime::AccountId32) -> Option<[u8; 20]> { pallet_relayer::RelayerByAccount::::get(&account).map(|h| h.0) } + + fn get_active_relayers() -> sp_std::vec::Vec<([u8; 20], sp_runtime::AccountId32)> { + pallet_relayer::RelayerRegistry::::iter() + .map(|(h160, account)| (h160.0, account)) + .collect() + } + + fn is_relayer_evm(evm_address: [u8; 20]) -> bool { + pallet_relayer::RelayerRegistry::::contains_key( + sp_core::H160::from(evm_address), + ) + } } #[cfg(feature = "runtime-benchmarks")] From 36d5a5fabd488a8fd6666e255832b938cb93e4c3 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 13:37:09 -0400 Subject: [PATCH 11/18] feat: remove Orbinum relay RPC implementation and related code --- client/rpc/src/relay.rs | 798 ---------------------------------------- 1 file changed, 798 deletions(-) delete mode 100644 client/rpc/src/relay.rs diff --git a/client/rpc/src/relay.rs b/client/rpc/src/relay.rs deleted file mode 100644 index ca959c30..00000000 --- a/client/rpc/src/relay.rs +++ /dev/null @@ -1,798 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -//! Node-native EVM relay RPC. -//! -//! When the node is started with `--evm-relayer-key `, it exposes: -//! -//! - `orbinum_relayShieldedCall(calldata: "0x...")` → `txHash` -//! - `orbinum_relayerStatus()` → `{ address, minFee, balanceWei, enabled }` -//! -//! The relay only accepts calls to the ShieldedPool precompile -//! (`0x0000000000000000000000000000000000000801`) with selector -//! `0x47fc44a2` (unshield) or `0x8c0f5d24` (privateTransfer). -//! It checks the fee embedded in ABI slot 6 is ≥ the current `min_relay_fee` from -//! `pallet-relayer` (queried dynamically via Runtime API so forkless upgrades take effect immediately). - -use std::sync::Arc; - -use ethereum::TransactionAction; -use ethereum_types::{H160, H256, U256}; -use jsonrpsee::core::RpcResult; -use jsonrpsee::proc_macros::rpc; -use serde::{Deserialize, Serialize}; -// Substrate -use sc_client_api::backend::{Backend, StorageProvider}; -use sc_transaction_pool_api::{TransactionPool, TransactionSource}; -use sp_api::ProvideRuntimeApi; -use sp_blockchain::HeaderBackend; -use sp_runtime::traits::Block as BlockT; -// Frontier -use fc_rpc_core::types::{Bytes, TransactionMessage}; -use fp_evm::ExitReason; -use fp_rpc::{ConvertTransactionRuntimeApi, EthereumRuntimeRPCApi}; -// Orbinum -use pallet_shielded_pool_runtime_api::ShieldedPoolRuntimeApi; - -use crate::{internal_err, signer::EthValidatorSigner}; - -/// Maximum fee per gas paid by the relay tx (10 gwei). -/// Used when building the EIP-1559 transaction. -const MAX_FEE_PER_GAS_WEI: u64 = 10_000_000_000; - -/// Gas limit used for relay transactions. -const RELAY_GAS_LIMIT: u64 = 2_000_000; - -/// Last-resort fallback minimum fee used ONLY when the Runtime API call fails entirely. -/// -/// The authoritative value lives in `pallet-relayer::MinRelayFee` storage and is -/// modifiable by governance via `set_min_relay_fee`. This constant is never used in -/// normal operation — it only kicks in if the node runs a pre-API runtime that does -/// not expose `relay_config()`. -/// -/// Set to the same default as `pallet-relayer::DefaultMinRelayFee` (0.001 ORB) so all -/// three sources are identical out of the box. -const MIN_RELAY_FEE_FALLBACK: u128 = 1_000_000_000_000_000; // 0.001 ORB in planck - -/// Static fallback selector whitelist for when the Runtime API is unavailable. -const SELECTORS_FALLBACK: [[u8; 4]; 2] = [ - [0x47, 0xfc, 0x44, 0xa2], // unshield - [0x8c, 0x0f, 0x5d, 0x24], // privateTransfer -]; - -/// Maximum calldata size accepted by the relay (32 KB). -/// -/// A realistic shielded-pool calldata is ~2–5 KB (256 B Groth16 proof + ABI head + Merkle path). -/// This cap prevents DoS: an attacker could craft calldata that passes selector/fee checks -/// but carries megabytes of data the relayer would have to pay calldata gas for. -const MAX_CALLDATA_BYTES: usize = 32_768; - -/// ShieldedPool precompile: 0x0000000000000000000000000000000000000801 -const SHIELDED_POOL_PRECOMPILE: [u8; 20] = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x08, 0x01, -]; - -// Keep the static selector names as test helpers (used in build_calldata). -#[cfg(test)] -const SELECTOR_UNSHIELD: [u8; 4] = [0x47, 0xfc, 0x44, 0xa2]; -#[cfg(test)] -const SELECTOR_PRIVATE_TRANSFER: [u8; 4] = [0x8c, 0x0f, 0x5d, 0x24]; - -// --------------------------------------------------------------------------- -// Calldata validation (pure, no runtime deps — unit-testable) -// --------------------------------------------------------------------------- - -/// Validates relay calldata against a runtime-provided minimum fee and selector whitelist. -/// -/// `min_fee_wei` — from `ShieldedPoolRuntimeApi::relay_config().min_fee_planck` -/// (or `MIN_RELAY_FEE_FALLBACK` if the API is unavailable). -/// `allowed_selectors` — from `relay_config().allowed_selectors`. -/// -/// Both `unshield` and `privateTransfer` share the same ABI head layout: -/// ```text -/// bytes [0..4] selector -/// bytes [4..36] slot 0 — offset pointer for proof (bytes/dynamic) -/// bytes [36..68] slot 1 — bytes32 root -/// bytes [68..100] slot 2 — bytes32 nullifier / bytes32[] nullifiers offset -/// bytes [100..132] slot 3 — uint32 asset_id / bytes32[] commits offset -/// bytes [132..164] slot 4 — uint256 amount / bytes[] memos offset -/// bytes [164..196] slot 5 — bytes32 recipient / uint32 asset_id -/// bytes [196..228] slot 6 — uint256 fee ← checked here -/// ``` -/// Minimum head size = 4 + 7 × 32 = 228 bytes. -/// Computes the effective minimum fee the user must include in their calldata. -/// -/// The relay must earn at least twice what it spends on EVM gas, so the floor is: -/// `2 × RELAY_GAS_LIMIT × base_fee_per_gas` -/// -/// The governance-set `min_fee_planck` is the absolute lower bound; the 2× gas floor -/// applies on top whenever network gas prices are high enough to make it exceed governance. -/// Both values are in plank (= wei in Orbinum's 1:1 mapping). -pub(crate) fn compute_effective_min_fee(min_fee_planck: u128, base_fee_wei: u128) -> u128 { - let two_x_gas_floor = (RELAY_GAS_LIMIT as u128) - .saturating_mul(2) - .saturating_mul(base_fee_wei); - min_fee_planck.max(two_x_gas_floor) -} - -pub(crate) fn validate_relay_calldata( - data: &[u8], - min_fee_wei: u128, - allowed_selectors: &[[u8; 4]], -) -> Result<(), &'static str> { - if data.len() < 228 { - return Err("calldata too short"); - } - if data.len() > MAX_CALLDATA_BYTES { - return Err("calldata too large"); - } - - let selector: [u8; 4] = data[..4].try_into().unwrap(); - if !allowed_selectors.contains(&selector) { - return Err("unsupported selector"); - } - - // Fee is in ABI slot 6 (7th param, 0-indexed): data[196..228]. - let fee_bytes: [u8; 32] = data[196..228].try_into().unwrap(); - let fee = U256::from_big_endian(&fee_bytes); - if fee < U256::from(min_fee_wei) { - return Err("fee below minimum"); - } - - Ok(()) -} - -/// Interprets the `exit_reason` from an EVM dry-run and returns whether the call -/// would succeed on-chain. -/// -/// Returns `Ok(())` only when `exit_reason` is `ExitReason::Succeed(_)`. -/// All other outcomes (revert, error, fatal) return `Err` with a human-readable -/// description so callers can surface it to the user without paying gas. -pub(crate) fn check_dry_run_exit(exit_reason: &ExitReason) -> Result<(), String> { - match exit_reason { - ExitReason::Succeed(_) => Ok(()), - other => Err(format!("calldata would fail on-chain: {other:?}")), - } -} - -// --------------------------------------------------------------------------- -// RPC trait -// --------------------------------------------------------------------------- - -#[rpc(server)] -pub trait OrbinumRelayApi { - /// Relay a shielded-pool call (unshield or privateTransfer) on behalf of a user. - /// - /// `calldata` must be ABI-encoded EVM calldata for the ShieldedPool precompile, - /// including the 4-byte selector. The fee in ABI slot index 6 must be ≥ MIN_RELAY_FEE_WEI. - /// - /// Returns the Ethereum transaction hash. - #[method(name = "orbinum_relayShieldedCall")] - async fn relay_shielded_call(&self, calldata: Bytes) -> RpcResult; - - /// Returns the relay status: EVM address, minimum fee, current balance, and whether - /// the relay has sufficient funds to process at least one transaction. - #[method(name = "orbinum_relayerStatus")] - async fn relayer_status(&self) -> RpcResult; -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RelayerStatus { - pub address: H160, - pub min_fee: String, - /// Current EVM balance of the relay wallet (wei). Lets callers verify the relay is funded. - pub balance_wei: String, - /// True only when balance ≥ MIN_RELAY_FEE_WEI (enough to cover at least one relay tx). - pub enabled: bool, -} - -// --------------------------------------------------------------------------- -// Server struct -// --------------------------------------------------------------------------- - -pub struct OrbinumRelay { - client: Arc, - pool: Arc

, - signer: EthValidatorSigner, - /// Serializes relay submissions AND tracks the optimistic next-nonce. - /// - /// Why `Option` instead of `()`: - /// - /// Each submission is: (1) read confirmed nonce, (2) sign, (3) submit. With a plain - /// Mutex<()> two requests in the same block both read `confirmed = N`, sign with N, - /// and the second submission fails with "nonce already used", burning gas for nothing. - /// - /// By storing the last submitted nonce we can compute: - /// actual_nonce = max(confirmed_nonce, memory_nonce) - /// which allows multiple submissions within the same block to use N, N+1, N+2… - /// On the next block `confirmed_nonce` catches up and `memory_nonce` resets naturally. - /// - /// `None` = no submission has been made yet; read from runtime. - submit_lock: Arc>>, - _phantom: std::marker::PhantomData<(B, BE)>, -} - -impl OrbinumRelay -where - B: BlockT, -{ - pub fn new(client: Arc, pool: Arc

, signer: EthValidatorSigner) -> Self { - Self { - client, - pool, - signer, - submit_lock: Arc::new(tokio::sync::Mutex::new(None)), - _phantom: Default::default(), - } - } -} - -#[jsonrpsee::core::async_trait] -impl OrbinumRelayApiServer for OrbinumRelay -where - B: BlockT, - C: ProvideRuntimeApi + HeaderBackend + StorageProvider + 'static, - C::Api: EthereumRuntimeRPCApi + ConvertTransactionRuntimeApi + ShieldedPoolRuntimeApi, - BE: Backend + 'static, - P: TransactionPool + 'static, -{ - async fn relay_shielded_call(&self, calldata: Bytes) -> RpcResult { - let data = calldata.into_vec(); - - // Load fee/selector config from the runtime on every call so governance changes - // (set_min_relay_fee extrinsic) take immediate effect without a node restart. - // MIN_RELAY_FEE_FALLBACK is only used when the Runtime API call fails entirely. - let best_hash = self.client.info().best_hash; - let (min_fee_planck, allowed_selectors) = { - match self.client.runtime_api().relay_config(best_hash) { - Ok(cfg) => (cfg.min_fee_planck, cfg.allowed_selectors), - Err(e) => { - log::warn!( - target: "orbinum-relay", - "relay_config Runtime API unavailable, using fallback: {e}" - ); - (MIN_RELAY_FEE_FALLBACK, SELECTORS_FALLBACK.to_vec()) - } - } - }; - - // Compute 2× gas floor: the relay must earn at least twice what it spends on EVM gas. - // 1 wei == 1 plank in Orbinum, so no unit conversion is required. - let base_fee_wei: u128 = self - .client - .runtime_api() - .gas_price(best_hash) - .map(|p| p.as_u128()) - .unwrap_or(0); - let effective_min_fee = compute_effective_min_fee(min_fee_planck, base_fee_wei); - - if let Err(e) = validate_relay_calldata(&data, effective_min_fee, &allowed_selectors) { - if e == "fee below minimum" { - // Extract the fee from slot 6 for the log (validated length already). - let provided_fee = if data.len() >= 228 { - U256::from_big_endian(&data[196..228]) - } else { - U256::zero() - }; - log::warn!( - target: "orbinum-relay", - "relay rejected: fee below minimum — provided={provided_fee} required={effective_min_fee} (base_fee={base_fee_wei} governance={min_fee_planck})", - ); - } - return Err(internal_err(e)); - } - - // Dry-run: simulate the EVM call without broadcasting a transaction. - // This catches invalid ZK proofs, already-spent nullifiers, and any other - // on-chain rejection BEFORE the relayer signs and pays gas. - { - let relayer_addr = self.signer.address(); - let dry_result = self.client.runtime_api().call( - best_hash, - relayer_addr, - H160::from(SHIELDED_POOL_PRECOMPILE), - data.clone(), - U256::zero(), - U256::from(RELAY_GAS_LIMIT), - Some(U256::from(MAX_FEE_PER_GAS_WEI)), - Some(U256::from(1_000_000_000u64)), - None, // nonce — not needed for simulation - false, // estimate = false: real execution semantics - None, // access_list - None, // authorization_list - ); - match dry_result { - Err(e) => { - log::warn!( - target: "orbinum-relay", - "dry-run Runtime API error: {e}" - ); - return Err(internal_err(format!("dry-run runtime error: {e}"))); - } - Ok(Err(dispatch_err)) => { - log::warn!( - target: "orbinum-relay", - "dry-run dispatch error: {dispatch_err:?}" - ); - return Err(internal_err(format!( - "calldata rejected by runtime: {dispatch_err:?}" - ))); - } - Ok(Ok(info)) => { - if let Err(e) = check_dry_run_exit(&info.exit_reason) { - log::warn!( - target: "orbinum-relay", - "dry-run EVM execution failed — exit={:?} revert_data={:?}", - info.exit_reason, - info.value - ); - return Err(internal_err(e)); - } - } - } - } - - // Hold the lock for the full sign-and-submit sequence. - // The lock also carries the optimistic next-nonce so multiple submissions - // within the same block don't collide on the same confirmed nonce. - let mut nonce_guard = self.submit_lock.lock().await; - - let relayer_addr = self.signer.address(); - - let (chain_id, nonce) = { - let api = self.client.runtime_api(); - let chain_id = api - .chain_id(best_hash) - .map_err(|e| internal_err(format!("chain_id: {e}")))?; - let confirmed_nonce = api - .account_basic(best_hash, relayer_addr) - .map_err(|e| internal_err(format!("account_basic: {e}")))? - .nonce; - // Use the in-memory nonce when it's ahead of the confirmed one. - // This lets us submit N txs within a single block using nonces N, N+1, N+2… - // Once the block is imported the confirmed nonce catches up naturally. - let nonce = match *nonce_guard { - Some(mem) if mem > confirmed_nonce => mem, - _ => confirmed_nonce, - }; - (chain_id, nonce) - }; - - let message = TransactionMessage::EIP1559(ethereum::EIP1559TransactionMessage { - chain_id, - nonce, - max_priority_fee_per_gas: U256::from(1_000_000_000u64), - max_fee_per_gas: U256::from(MAX_FEE_PER_GAS_WEI), - gas_limit: U256::from(RELAY_GAS_LIMIT), - action: TransactionAction::Call(H160::from(SHIELDED_POOL_PRECOMPILE)), - value: U256::zero(), - input: data, - access_list: vec![], - }); - - use crate::signer::EthSigner as _; - let transaction = self.signer.sign(message, &relayer_addr)?; - let tx_hash = transaction.hash(); - - let extrinsic = { - let api = self.client.runtime_api(); - api.convert_transaction(best_hash, transaction) - .map_err(|e| internal_err(format!("convert_transaction: {e}")))? - }; - - let submit_result = self - .pool - .submit_one(best_hash, TransactionSource::Local, extrinsic) - .await - .map(|_| tx_hash) - .map_err(|e| internal_err(format!("pool submit: {e}"))); - - // Advance the in-memory nonce only after a successful submit. - // On failure the nonce slot is still free and the next call will retry with - // the same (or a freshly confirmed) nonce. - if submit_result.is_ok() { - *nonce_guard = Some(nonce + U256::one()); - } - - submit_result - } - - async fn relayer_status(&self) -> RpcResult { - let best_hash = self.client.info().best_hash; - let api = self.client.runtime_api(); - - let min_fee_planck = api - .relay_config(best_hash) - .map(|cfg| cfg.min_fee_planck) - .unwrap_or(MIN_RELAY_FEE_FALLBACK); - - let base_fee_wei: u128 = api.gas_price(best_hash).map(|p| p.as_u128()).unwrap_or(0); - let min_fee = compute_effective_min_fee(min_fee_planck, base_fee_wei); - - let balance = { - api.account_basic(best_hash, self.signer.address()) - .map_err(|e| internal_err(format!("account_basic: {e}")))? - .balance - }; - // Consider relay operational when it can cover at least one worst-case tx. - let min_operational = U256::from(min_fee); - Ok(RelayerStatus { - address: self.signer.address(), - min_fee: format!("{min_fee}"), - balance_wei: format!("{balance}"), - enabled: balance >= min_operational, - }) - } -} - -// --------------------------------------------------------------------------- -// Unit tests — pure validation logic, no runtime required -// --------------------------------------------------------------------------- - -#[cfg(test)] -mod tests { - use super::*; - - /// Build minimal valid calldata for the given selector and fee. - /// - /// Head layout (228 bytes total): - /// ```text - /// [0..4] selector - /// [4..36] slot 0 — proof offset: 0xE0 (= 7×32 = 224, past all head slots) - /// [36..68] slot 1 — bytes32 zeroes (root) - /// [68..100] slot 2 — bytes32 zeroes (nullifier / nullifiers offset) - /// [100..132] slot 3 — uint32 zeroes (assetId / commitments offset) - /// [132..164] slot 4 — uint256 zeroes (amount / memos offset) - /// [164..196] slot 5 — bytes32 zeroes (recipient / assetId) - /// [196..228] slot 6 — uint256 fee - /// ``` - fn build_calldata(selector: [u8; 4], fee_wei: u128) -> Vec { - let mut data = vec![0u8; 228]; - data[..4].copy_from_slice(&selector); - // Proof-bytes offset: 7×32 = 224 = 0xE0 (big-endian U256 → only last byte set) - data[35] = 0xE0; - // Fee at slot 6 = data[196..228] - data[196..228].copy_from_slice(&U256::from(fee_wei).to_big_endian()); - data - } - - // ── Length checks ────────────────────────────────────────────────────── - - #[test] - fn rejects_empty_calldata() { - assert_eq!( - validate_relay_calldata(&[], MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Err("calldata too short") - ); - } - - #[test] - fn rejects_calldata_227_bytes() { - assert_eq!( - validate_relay_calldata(&[0u8; 227], MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Err("calldata too short") - ); - } - - #[test] - fn rejects_calldata_196_bytes_old_wrong_limit() { - // Ensure the old (incorrect) limit of 196 is no longer accepted - let data = vec![0u8; 196]; - assert_eq!( - validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Err("calldata too short") - ); - } - - // ── Selector checks ──────────────────────────────────────────────────── - - #[test] - fn rejects_unknown_selector() { - let mut data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK); - data[..4].copy_from_slice(&[0xde, 0xad, 0xbe, 0xef]); - assert_eq!( - validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Err("unsupported selector") - ); - } - - #[test] - fn rejects_shield_selector() { - // shield = 0x781442b9 — NOT in relay whitelist - let mut data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK); - data[..4].copy_from_slice(&[0x78, 0x14, 0x42, 0xb9]); - assert_eq!( - validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Err("unsupported selector") - ); - } - - // ── Fee checks ───────────────────────────────────────────────────────── - - #[test] - fn rejects_zero_fee() { - let data = build_calldata(SELECTOR_UNSHIELD, 0); - assert_eq!( - validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Err("fee below minimum") - ); - } - - #[test] - fn rejects_fee_one_wei_below_minimum() { - let data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK - 1); - assert_eq!( - validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Err("fee below minimum") - ); - } - - #[test] - fn rejects_fee_one_wei_below_minimum_private_transfer() { - let data = build_calldata(SELECTOR_PRIVATE_TRANSFER, MIN_RELAY_FEE_FALLBACK - 1); - assert_eq!( - validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Err("fee below minimum") - ); - } - - // ── Valid calldata ───────────────────────────────────────────────────── - - #[test] - fn accepts_unshield_with_exact_minimum_fee() { - let data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK); - assert_eq!( - validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Ok(()) - ); - } - - #[test] - fn accepts_private_transfer_with_exact_minimum_fee() { - let data = build_calldata(SELECTOR_PRIVATE_TRANSFER, MIN_RELAY_FEE_FALLBACK); - assert_eq!( - validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Ok(()) - ); - } - - #[test] - fn accepts_large_fee() { - let data = build_calldata(SELECTOR_UNSHIELD, u128::MAX); - assert_eq!( - validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Ok(()) - ); - } - - #[test] - fn accepts_calldata_longer_than_228_bytes() { - let mut data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK); - // Append tail bytes (proof data and dynamic arrays) - data.extend_from_slice(&[0xaa; 128]); - assert_eq!( - validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Ok(()) - ); - } - - // ── Fee is read from the correct position ────────────────────────────── - - #[test] - fn fee_at_slot_5_is_not_read_as_fee() { - // Put a value >= MIN_RELAY_FEE_FALLBACK in slot 5 (data[164..196]) but zero in slot 6 - let mut data = build_calldata(SELECTOR_UNSHIELD, 0); - // Overwrite slot 5 with MIN_RELAY_FEE_FALLBACK (this is recipient in unshield — NOT the fee) - data[164..196].copy_from_slice(&U256::from(MIN_RELAY_FEE_FALLBACK).to_big_endian()); - // Fee (slot 6, data[196..228]) is still zero → should reject - assert_eq!( - validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Err("fee below minimum") - ); - } - - #[test] - fn fee_at_slot_6_is_correctly_read() { - // Slot 5 = zero, slot 6 = MIN_RELAY_FEE_FALLBACK → should accept - let data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK); - assert_eq!( - validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Ok(()) - ); - } - - // ── Calldata size upper bound ────────────────────────────────────────── - - #[test] - fn rejects_calldata_above_max_size() { - let mut data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK); - data.resize(MAX_CALLDATA_BYTES + 1, 0u8); - assert_eq!( - validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Err("calldata too large") - ); - } - - #[test] - fn accepts_calldata_at_exact_max_size() { - let mut data = build_calldata(SELECTOR_UNSHIELD, MIN_RELAY_FEE_FALLBACK); - data.resize(MAX_CALLDATA_BYTES, 0u8); - assert_eq!( - validate_relay_calldata(&data, MIN_RELAY_FEE_FALLBACK, &SELECTORS_FALLBACK), - Ok(()) - ); - } - - // ── compute_effective_min_fee ────────────────────────────────────────── - - /// At zero base_fee the governance floor must win. - #[test] - fn effective_min_fee_zero_base_fee_returns_governance_floor() { - assert_eq!( - compute_effective_min_fee(MIN_RELAY_FEE_FALLBACK, 0), - MIN_RELAY_FEE_FALLBACK - ); - } - - /// At 10 gwei the 2× gas floor (40_000_000_000_000_000) must beat governance (0.001 ORB). - #[test] - fn effective_min_fee_gas_floor_dominates_at_10_gwei() { - let base_fee = 10_000_000_000u128; // 10 gwei - let expected = (RELAY_GAS_LIMIT as u128) * 2 * base_fee; - assert!( - expected > MIN_RELAY_FEE_FALLBACK, - "test precondition: gas floor must exceed governance" - ); - assert_eq!( - compute_effective_min_fee(MIN_RELAY_FEE_FALLBACK, base_fee), - expected - ); - } - - /// At 1 wei base_fee the gas floor (4_000_000) is far below governance (0.001 ORB). - #[test] - fn effective_min_fee_governance_floor_dominates_at_negligible_base_fee() { - let base_fee = 1u128; - let gas_floor = (RELAY_GAS_LIMIT as u128) * 2 * base_fee; - assert!( - MIN_RELAY_FEE_FALLBACK > gas_floor, - "test precondition: governance must exceed gas floor" - ); - assert_eq!( - compute_effective_min_fee(MIN_RELAY_FEE_FALLBACK, base_fee), - MIN_RELAY_FEE_FALLBACK - ); - } - - /// The crossover point is at base_fee = governance / (gas_limit × 2) = 250_000_000 (0.25 gwei). - /// Below this threshold governance wins; at or above, the gas floor wins. - #[test] - fn effective_min_fee_crossover_at_250_mwei() { - // threshold = 1_000_000_000_000_000 / (2_000_000 × 2) = 250_000_000 - let threshold = MIN_RELAY_FEE_FALLBACK / (RELAY_GAS_LIMIT as u128 * 2); - // At threshold: 2×gas == governance, max returns governance. - let at = compute_effective_min_fee(MIN_RELAY_FEE_FALLBACK, threshold); - // One wei above: 2×gas > governance. - let above = compute_effective_min_fee(MIN_RELAY_FEE_FALLBACK, threshold + 1); - assert_eq!(at, MIN_RELAY_FEE_FALLBACK); - assert!(above > MIN_RELAY_FEE_FALLBACK); - } - - /// A governance floor higher than the gas floor must win regardless of base_fee. - #[test] - fn effective_min_fee_custom_high_governance_beats_gas_floor() { - let governance = 100_000_000_000_000_000u128; // 0.1 ORB - let base_fee = 1_000_000_000u128; // 1 gwei - let gas_floor = (RELAY_GAS_LIMIT as u128) * 2 * base_fee; - assert!( - governance > gas_floor, - "test precondition: governance must exceed gas floor" - ); - assert_eq!(compute_effective_min_fee(governance, base_fee), governance); - } - - /// Saturating arithmetic must not panic on u128::MAX inputs. - #[test] - fn effective_min_fee_saturates_without_panic() { - let result = compute_effective_min_fee(u128::MAX, u128::MAX); - assert_eq!(result, u128::MAX); - } - - /// The calldata fee must be validated against `effective_min_fee`, not raw `min_fee_planck`. - /// Simulates the scenario where the gas floor is the active minimum. - #[test] - fn validate_calldata_rejects_fee_below_gas_floor_even_above_governance() { - let governance = MIN_RELAY_FEE_FALLBACK; - let base_fee = 10_000_000_000u128; // 10 gwei - let gas_floor = (RELAY_GAS_LIMIT as u128) * 2 * base_fee; // 40_000_000_000_000_000 - assert!(gas_floor > governance); - let effective = compute_effective_min_fee(governance, base_fee); - // A fee that clears governance but not the gas floor is rejected. - let below_gas_floor = gas_floor - 1; - let data = build_calldata(SELECTOR_UNSHIELD, below_gas_floor); - assert_eq!( - validate_relay_calldata(&data, effective, &SELECTORS_FALLBACK), - Err("fee below minimum") - ); - } - - /// Fee that exactly equals the gas floor (when it dominates) must be accepted. - #[test] - fn validate_calldata_accepts_exact_gas_floor() { - let governance = MIN_RELAY_FEE_FALLBACK; - let base_fee = 10_000_000_000u128; // 10 gwei - let effective = compute_effective_min_fee(governance, base_fee); - let data = build_calldata(SELECTOR_UNSHIELD, effective); - assert_eq!( - validate_relay_calldata(&data, effective, &SELECTORS_FALLBACK), - Ok(()) - ); - } - - // ── check_dry_run_exit ───────────────────────────────────────────────── - - /// A successful EVM execution must pass the dry-run check. - #[test] - fn dry_run_exit_succeed_returns_ok() { - use evm::ExitSucceed; - assert!(check_dry_run_exit(&ExitReason::Succeed(ExitSucceed::Returned)).is_ok()); - assert!(check_dry_run_exit(&ExitReason::Succeed(ExitSucceed::Stopped)).is_ok()); - assert!(check_dry_run_exit(&ExitReason::Succeed(ExitSucceed::Suicided)).is_ok()); - } - - /// A revert (invalid ZK proof, double-spend nullifier, etc.) must be rejected. - #[test] - fn dry_run_exit_revert_returns_err() { - use evm::ExitRevert; - let result = check_dry_run_exit(&ExitReason::Revert(ExitRevert::Reverted)); - assert!(result.is_err()); - let msg = result.unwrap_err(); - assert!( - msg.contains("calldata would fail on-chain"), - "expected 'calldata would fail on-chain' in: {msg}" - ); - assert!(msg.contains("Revert"), "expected 'Revert' in: {msg}"); - } - - /// An EVM error (OutOfGas, etc.) must be rejected. - #[test] - fn dry_run_exit_error_out_of_gas_returns_err() { - use evm::ExitError; - let result = check_dry_run_exit(&ExitReason::Error(ExitError::OutOfGas)); - assert!(result.is_err()); - let msg = result.unwrap_err(); - assert!(msg.contains("calldata would fail on-chain"), "{msg}"); - assert!(msg.contains("OutOfGas"), "expected 'OutOfGas' in: {msg}"); - } - - /// A call-too-deep EVM error (stack overflow) must be rejected. - #[test] - fn dry_run_exit_error_call_too_deep_returns_err() { - use evm::ExitError; - let result = check_dry_run_exit(&ExitReason::Error(ExitError::CallTooDeep)); - assert!(result.is_err()); - assert!(result.unwrap_err().contains("CallTooDeep")); - } - - /// A fatal EVM error must be rejected. - #[test] - fn dry_run_exit_fatal_returns_err() { - use evm::ExitFatal; - let result = check_dry_run_exit(&ExitReason::Fatal(ExitFatal::NotSupported)); - assert!(result.is_err()); - let msg = result.unwrap_err(); - assert!(msg.contains("calldata would fail on-chain"), "{msg}"); - assert!(msg.contains("Fatal"), "expected 'Fatal' in: {msg}"); - } - - /// The error message must embed the exit reason so callers can surface it to users. - #[test] - fn dry_run_exit_error_message_includes_reason() { - use evm::ExitError; - let reason = ExitReason::Error(ExitError::OutOfFund); - let err = check_dry_run_exit(&reason).unwrap_err(); - // The message should contain both the prefix and the specific variant. - assert!(err.starts_with("calldata would fail on-chain:"), "{err}"); - assert!(err.contains("OutOfFund"), "{err}"); - } -} From 954926c3cabe20c707bbc82e39ad500da929b272 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 13:42:23 -0400 Subject: [PATCH 12/18] chore: update Cargo.lock staging-xcm 17.0.0 -> 17.0.1 --- Cargo.lock | 365 +++++++++++++++++++++++++++-------------------------- 1 file changed, 183 insertions(+), 182 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dcc7d77b..5755ec1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1021,7 +1021,7 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "binary-merkle-tree" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "hash-db", "log", @@ -2044,7 +2044,7 @@ dependencies = [ [[package]] name = "cumulus-client-parachain-inherent" version = "0.18.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "cumulus-primitives-core", @@ -2065,7 +2065,7 @@ dependencies = [ [[package]] name = "cumulus-pallet-parachain-system" version = "0.21.2" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "array-bytes 6.2.3", "bytes", @@ -2103,7 +2103,7 @@ dependencies = [ [[package]] name = "cumulus-pallet-parachain-system-proc-macro" version = "0.6.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", @@ -2114,7 +2114,7 @@ dependencies = [ [[package]] name = "cumulus-pallet-weight-reclaim" version = "0.3.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "cumulus-primitives-storage-weight-reclaim", "derive-where", @@ -2133,7 +2133,7 @@ dependencies = [ [[package]] name = "cumulus-primitives-core" version = "0.19.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "polkadot-core-primitives", @@ -2150,7 +2150,7 @@ dependencies = [ [[package]] name = "cumulus-primitives-parachain-inherent" version = "0.19.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "cumulus-primitives-core", @@ -2164,7 +2164,7 @@ dependencies = [ [[package]] name = "cumulus-primitives-proof-size-hostfunction" version = "0.13.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "sp-externalities", "sp-runtime-interface", @@ -2174,7 +2174,7 @@ dependencies = [ [[package]] name = "cumulus-primitives-storage-weight-reclaim" version = "12.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "cumulus-primitives-core", "cumulus-primitives-proof-size-hostfunction", @@ -2191,7 +2191,7 @@ dependencies = [ [[package]] name = "cumulus-relay-chain-interface" version = "0.24.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "cumulus-primitives-core", @@ -2211,7 +2211,7 @@ dependencies = [ [[package]] name = "cumulus-test-relay-sproof-builder" version = "0.20.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "cumulus-primitives-core", "parity-scale-codec", @@ -3269,6 +3269,7 @@ dependencies = [ "libsecp256k1", "log", "pallet-evm", + "pallet-relayer-runtime-api", "pallet-shielded-pool-runtime-api", "parity-scale-codec", "prometheus", @@ -3523,7 +3524,7 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "fork-tree" version = "13.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", ] @@ -3655,7 +3656,7 @@ dependencies = [ [[package]] name = "frame-benchmarking" version = "41.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-support", "frame-support-procedural", @@ -3679,7 +3680,7 @@ dependencies = [ [[package]] name = "frame-benchmarking-cli" version = "49.1.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "Inflector", "array-bytes 6.2.3", @@ -3758,7 +3759,7 @@ dependencies = [ [[package]] name = "frame-election-provider-solution-type" version = "16.1.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", @@ -3769,7 +3770,7 @@ dependencies = [ [[package]] name = "frame-election-provider-support" version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-election-provider-solution-type", "frame-support", @@ -3786,7 +3787,7 @@ dependencies = [ [[package]] name = "frame-executive" version = "41.0.2" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "aquamarine", "frame-support", @@ -3839,7 +3840,7 @@ dependencies = [ [[package]] name = "frame-metadata-hash-extension" version = "0.9.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "array-bytes 6.2.3", "const-hex", @@ -3855,7 +3856,7 @@ dependencies = [ [[package]] name = "frame-storage-access-test-runtime" version = "0.2.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "cumulus-pallet-parachain-system", "parity-scale-codec", @@ -3869,7 +3870,7 @@ dependencies = [ [[package]] name = "frame-support" version = "41.0.2" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "aquamarine", "array-bytes 6.2.3", @@ -3910,7 +3911,7 @@ dependencies = [ [[package]] name = "frame-support-procedural" version = "34.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "Inflector", "cfg-expr", @@ -3930,7 +3931,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools" version = "13.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-support-procedural-tools-derive", "proc-macro-crate 3.5.0", @@ -3942,7 +3943,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools-derive" version = "12.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "proc-macro2", "quote", @@ -3952,7 +3953,7 @@ dependencies = [ [[package]] name = "frame-system" version = "41.0.2" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "cfg-if", "docify", @@ -3971,7 +3972,7 @@ dependencies = [ [[package]] name = "frame-system-benchmarking" version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-benchmarking", "frame-support", @@ -3985,7 +3986,7 @@ dependencies = [ [[package]] name = "frame-system-rpc-runtime-api" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "docify", "parity-scale-codec", @@ -3995,7 +3996,7 @@ dependencies = [ [[package]] name = "frame-try-runtime" version = "0.47.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-support", "parity-scale-codec", @@ -7344,7 +7345,7 @@ dependencies = [ [[package]] name = "orbinum-zk-core" -version = "1.0.0" +version = "1.0.1" dependencies = [ "ark-bn254", "ark-ff 0.5.0", @@ -7357,7 +7358,7 @@ dependencies = [ [[package]] name = "orbinum-zk-verifier" -version = "1.0.0" +version = "1.1.0" dependencies = [ "ark-bn254", "ark-ec 0.5.0", @@ -7456,7 +7457,7 @@ dependencies = [ [[package]] name = "pallet-asset-conversion" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-benchmarking", "frame-support", @@ -7474,7 +7475,7 @@ dependencies = [ [[package]] name = "pallet-asset-rate" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-benchmarking", "frame-support", @@ -7488,7 +7489,7 @@ dependencies = [ [[package]] name = "pallet-aura" version = "40.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-support", "frame-system", @@ -7504,7 +7505,7 @@ dependencies = [ [[package]] name = "pallet-authority-discovery" version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-support", "frame-system", @@ -7519,7 +7520,7 @@ dependencies = [ [[package]] name = "pallet-authorship" version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-support", "frame-system", @@ -7532,7 +7533,7 @@ dependencies = [ [[package]] name = "pallet-babe" version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-benchmarking", "frame-support", @@ -7555,7 +7556,7 @@ dependencies = [ [[package]] name = "pallet-balances" version = "42.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "docify", "frame-benchmarking", @@ -7585,7 +7586,7 @@ dependencies = [ [[package]] name = "pallet-broker" version = "0.20.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "bitvec", "frame-benchmarking", @@ -7620,7 +7621,7 @@ dependencies = [ [[package]] name = "pallet-election-provider-multi-phase" version = "40.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -7944,7 +7945,7 @@ dependencies = [ [[package]] name = "pallet-fast-unstake" version = "40.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "docify", "frame-benchmarking", @@ -7962,7 +7963,7 @@ dependencies = [ [[package]] name = "pallet-grandpa" version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-benchmarking", "frame-support", @@ -7999,7 +8000,7 @@ dependencies = [ [[package]] name = "pallet-identity" version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "enumflags2", "frame-benchmarking", @@ -8015,7 +8016,7 @@ dependencies = [ [[package]] name = "pallet-message-queue" version = "44.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "environmental", "frame-benchmarking", @@ -8033,8 +8034,8 @@ dependencies = [ [[package]] name = "pallet-mmr" -version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +version = "41.0.1" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "log", "parity-scale-codec", @@ -8045,7 +8046,7 @@ dependencies = [ [[package]] name = "pallet-relayer" -version = "0.1.0" +version = "0.2.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -8087,7 +8088,7 @@ dependencies = [ [[package]] name = "pallet-session" version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-support", "frame-system", @@ -8107,7 +8108,7 @@ dependencies = [ [[package]] name = "pallet-shielded-pool" -version = "0.7.0" +version = "0.8.0" dependencies = [ "ark-bn254", "ark-ff 0.5.0", @@ -8160,7 +8161,7 @@ dependencies = [ [[package]] name = "pallet-staking" version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -8182,7 +8183,7 @@ dependencies = [ [[package]] name = "pallet-staking-reward-fn" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "log", "sp-arithmetic", @@ -8191,7 +8192,7 @@ dependencies = [ [[package]] name = "pallet-sudo" version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "docify", "frame-benchmarking", @@ -8206,7 +8207,7 @@ dependencies = [ [[package]] name = "pallet-timestamp" version = "40.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "docify", "frame-benchmarking", @@ -8224,7 +8225,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment" version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-benchmarking", "frame-support", @@ -8239,7 +8240,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc" version = "44.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "jsonrpsee", "pallet-transaction-payment-rpc-runtime-api", @@ -8255,7 +8256,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc-runtime-api" version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "pallet-transaction-payment", "parity-scale-codec", @@ -8267,7 +8268,7 @@ dependencies = [ [[package]] name = "pallet-treasury" version = "40.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "docify", "frame-benchmarking", @@ -8286,7 +8287,7 @@ dependencies = [ [[package]] name = "pallet-utility" version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-benchmarking", "frame-support", @@ -8301,7 +8302,7 @@ dependencies = [ [[package]] name = "pallet-vesting" version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-benchmarking", "frame-support", @@ -8314,7 +8315,7 @@ dependencies = [ [[package]] name = "pallet-zk-verifier" -version = "0.5.1" +version = "0.6.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -8711,7 +8712,7 @@ dependencies = [ [[package]] name = "polkadot-core-primitives" version = "18.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "scale-info", @@ -8722,7 +8723,7 @@ dependencies = [ [[package]] name = "polkadot-node-metrics" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "bs58", "futures", @@ -8739,7 +8740,7 @@ dependencies = [ [[package]] name = "polkadot-node-network-protocol" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-channel 1.9.0", "async-trait", @@ -8764,7 +8765,7 @@ dependencies = [ [[package]] name = "polkadot-node-primitives" version = "20.1.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "bitvec", "bounded-vec", @@ -8788,7 +8789,7 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-types" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "derive_more 0.99.20", @@ -8816,7 +8817,7 @@ dependencies = [ [[package]] name = "polkadot-overseer" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "futures", @@ -8836,7 +8837,7 @@ dependencies = [ [[package]] name = "polkadot-parachain-primitives" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "bounded-collections", "derive_more 0.99.20", @@ -8852,7 +8853,7 @@ dependencies = [ [[package]] name = "polkadot-primitives" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "bitvec", "bounded-collections", @@ -8881,7 +8882,7 @@ dependencies = [ [[package]] name = "polkadot-runtime-common" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "bitvec", "frame-benchmarking", @@ -8931,7 +8932,7 @@ dependencies = [ [[package]] name = "polkadot-runtime-metrics" version = "21.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "bs58", "frame-benchmarking", @@ -8943,7 +8944,7 @@ dependencies = [ [[package]] name = "polkadot-runtime-parachains" version = "20.0.3" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "bitflags 1.3.2", "bitvec", @@ -8991,7 +8992,7 @@ dependencies = [ [[package]] name = "polkadot-sdk-frame" version = "0.10.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "docify", "frame-benchmarking", @@ -9026,7 +9027,7 @@ dependencies = [ [[package]] name = "polkadot-statement-table" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "polkadot-primitives", @@ -10332,7 +10333,7 @@ dependencies = [ [[package]] name = "sc-allocator" version = "32.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "log", "sp-core", @@ -10343,7 +10344,7 @@ dependencies = [ [[package]] name = "sc-authority-discovery" version = "0.51.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "futures", @@ -10374,7 +10375,7 @@ dependencies = [ [[package]] name = "sc-basic-authorship" version = "0.50.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "futures", "log", @@ -10395,7 +10396,7 @@ dependencies = [ [[package]] name = "sc-block-builder" version = "0.45.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "sp-api", @@ -10410,7 +10411,7 @@ dependencies = [ [[package]] name = "sc-chain-spec" version = "44.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "array-bytes 6.2.3", "docify", @@ -10436,7 +10437,7 @@ dependencies = [ [[package]] name = "sc-chain-spec-derive" version = "12.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", @@ -10447,7 +10448,7 @@ dependencies = [ [[package]] name = "sc-cli" version = "0.53.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "array-bytes 6.2.3", "chrono", @@ -10489,7 +10490,7 @@ dependencies = [ [[package]] name = "sc-client-api" version = "40.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "fnv", "futures", @@ -10515,7 +10516,7 @@ dependencies = [ [[package]] name = "sc-client-db" version = "0.47.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "hash-db", "kvdb", @@ -10543,7 +10544,7 @@ dependencies = [ [[package]] name = "sc-consensus" version = "0.50.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "futures", @@ -10566,7 +10567,7 @@ dependencies = [ [[package]] name = "sc-consensus-aura" version = "0.51.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "futures", @@ -10595,7 +10596,7 @@ dependencies = [ [[package]] name = "sc-consensus-babe" version = "0.51.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "fork-tree", @@ -10631,7 +10632,7 @@ dependencies = [ [[package]] name = "sc-consensus-epochs" version = "0.50.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "fork-tree", "parity-scale-codec", @@ -10644,7 +10645,7 @@ dependencies = [ [[package]] name = "sc-consensus-grandpa" version = "0.36.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "ahash", "array-bytes 6.2.3", @@ -10688,7 +10689,7 @@ dependencies = [ [[package]] name = "sc-consensus-manual-seal" version = "0.52.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "assert_matches", "async-trait", @@ -10723,7 +10724,7 @@ dependencies = [ [[package]] name = "sc-consensus-slots" version = "0.50.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "futures", @@ -10746,7 +10747,7 @@ dependencies = [ [[package]] name = "sc-executor" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "parking_lot 0.12.5", @@ -10769,7 +10770,7 @@ dependencies = [ [[package]] name = "sc-executor-common" version = "0.39.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "polkavm 0.24.0", "sc-allocator", @@ -10782,7 +10783,7 @@ dependencies = [ [[package]] name = "sc-executor-polkavm" version = "0.36.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "log", "polkavm 0.24.0", @@ -10793,7 +10794,7 @@ dependencies = [ [[package]] name = "sc-executor-wasmtime" version = "0.39.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "anyhow", "log", @@ -10809,7 +10810,7 @@ dependencies = [ [[package]] name = "sc-informant" version = "0.50.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "console", "futures", @@ -10825,7 +10826,7 @@ dependencies = [ [[package]] name = "sc-keystore" version = "36.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "array-bytes 6.2.3", "parking_lot 0.12.5", @@ -10839,7 +10840,7 @@ dependencies = [ [[package]] name = "sc-mixnet" version = "0.21.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "array-bytes 6.2.3", "arrayvec 0.7.6", @@ -10867,7 +10868,7 @@ dependencies = [ [[package]] name = "sc-network" version = "0.51.2" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "array-bytes 6.2.3", "async-channel 1.9.0", @@ -10917,7 +10918,7 @@ dependencies = [ [[package]] name = "sc-network-common" version = "0.49.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "bitflags 1.3.2", "parity-scale-codec", @@ -10927,7 +10928,7 @@ dependencies = [ [[package]] name = "sc-network-gossip" version = "0.51.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "ahash", "futures", @@ -10946,7 +10947,7 @@ dependencies = [ [[package]] name = "sc-network-light" version = "0.50.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "array-bytes 6.2.3", "async-channel 1.9.0", @@ -10967,7 +10968,7 @@ dependencies = [ [[package]] name = "sc-network-sync" version = "0.50.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "array-bytes 6.2.3", "async-channel 1.9.0", @@ -11002,7 +11003,7 @@ dependencies = [ [[package]] name = "sc-network-transactions" version = "0.50.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "array-bytes 6.2.3", "futures", @@ -11021,7 +11022,7 @@ dependencies = [ [[package]] name = "sc-network-types" version = "0.17.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "bs58", "bytes", @@ -11042,7 +11043,7 @@ dependencies = [ [[package]] name = "sc-offchain" version = "46.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "bytes", "fnv", @@ -11076,7 +11077,7 @@ dependencies = [ [[package]] name = "sc-proposer-metrics" version = "0.20.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "log", "substrate-prometheus-endpoint", @@ -11085,7 +11086,7 @@ dependencies = [ [[package]] name = "sc-rpc" version = "46.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "futures", "jsonrpsee", @@ -11117,7 +11118,7 @@ dependencies = [ [[package]] name = "sc-rpc-api" version = "0.50.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -11137,7 +11138,7 @@ dependencies = [ [[package]] name = "sc-rpc-server" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "dyn-clone", "forwarded-header-value", @@ -11161,7 +11162,7 @@ dependencies = [ [[package]] name = "sc-rpc-spec-v2" version = "0.51.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "array-bytes 6.2.3", "futures", @@ -11194,7 +11195,7 @@ dependencies = [ [[package]] name = "sc-runtime-utilities" version = "0.3.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "sc-executor", @@ -11209,7 +11210,7 @@ dependencies = [ [[package]] name = "sc-service" version = "0.52.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "directories", @@ -11273,7 +11274,7 @@ dependencies = [ [[package]] name = "sc-state-db" version = "0.39.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "log", "parity-scale-codec", @@ -11284,7 +11285,7 @@ dependencies = [ [[package]] name = "sc-sysinfo" version = "43.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "derive_more 0.99.20", "futures", @@ -11304,7 +11305,7 @@ dependencies = [ [[package]] name = "sc-telemetry" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "chrono", "futures", @@ -11323,7 +11324,7 @@ dependencies = [ [[package]] name = "sc-tracing" version = "40.0.2" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "chrono", "console", @@ -11353,7 +11354,7 @@ dependencies = [ [[package]] name = "sc-tracing-proc-macro" version = "11.1.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", @@ -11364,7 +11365,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool" version = "40.1.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "futures", @@ -11395,7 +11396,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool-api" version = "40.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "futures", @@ -11412,7 +11413,7 @@ dependencies = [ [[package]] name = "sc-utils" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-channel 1.9.0", "futures", @@ -12039,7 +12040,7 @@ checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" [[package]] name = "slot-range-helper" version = "18.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "enumn", "parity-scale-codec", @@ -12222,7 +12223,7 @@ dependencies = [ [[package]] name = "sp-api" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "docify", "hash-db", @@ -12244,7 +12245,7 @@ dependencies = [ [[package]] name = "sp-api-proc-macro" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "Inflector", "blake2 0.10.6", @@ -12258,7 +12259,7 @@ dependencies = [ [[package]] name = "sp-application-crypto" version = "41.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "scale-info", @@ -12270,7 +12271,7 @@ dependencies = [ [[package]] name = "sp-arithmetic" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "docify", "integer-sqrt", @@ -12284,7 +12285,7 @@ dependencies = [ [[package]] name = "sp-authority-discovery" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "scale-info", @@ -12296,7 +12297,7 @@ dependencies = [ [[package]] name = "sp-block-builder" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "sp-api", "sp-inherents", @@ -12306,7 +12307,7 @@ dependencies = [ [[package]] name = "sp-blockchain" version = "40.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "futures", "parity-scale-codec", @@ -12325,7 +12326,7 @@ dependencies = [ [[package]] name = "sp-consensus" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "futures", @@ -12339,7 +12340,7 @@ dependencies = [ [[package]] name = "sp-consensus-aura" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "parity-scale-codec", @@ -12355,7 +12356,7 @@ dependencies = [ [[package]] name = "sp-consensus-babe" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "parity-scale-codec", @@ -12373,7 +12374,7 @@ dependencies = [ [[package]] name = "sp-consensus-grandpa" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "finality-grandpa", "log", @@ -12390,7 +12391,7 @@ dependencies = [ [[package]] name = "sp-consensus-slots" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "scale-info", @@ -12401,7 +12402,7 @@ dependencies = [ [[package]] name = "sp-core" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "ark-vrf", "array-bytes 6.2.3", @@ -12463,7 +12464,7 @@ dependencies = [ [[package]] name = "sp-crypto-hashing" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "blake2b_simd", "byteorder", @@ -12476,7 +12477,7 @@ dependencies = [ [[package]] name = "sp-crypto-hashing-proc-macro" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2506)", @@ -12486,7 +12487,7 @@ dependencies = [ [[package]] name = "sp-database" version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "kvdb", "parking_lot 0.12.5", @@ -12495,7 +12496,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "proc-macro2", "quote", @@ -12505,7 +12506,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.30.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "environmental", "parity-scale-codec", @@ -12515,7 +12516,7 @@ dependencies = [ [[package]] name = "sp-genesis-builder" version = "0.18.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "scale-info", @@ -12527,7 +12528,7 @@ dependencies = [ [[package]] name = "sp-inherents" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "impl-trait-for-tuples", @@ -12540,7 +12541,7 @@ dependencies = [ [[package]] name = "sp-io" version = "41.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "bytes", "docify", @@ -12566,7 +12567,7 @@ dependencies = [ [[package]] name = "sp-keyring" version = "42.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "sp-core", "sp-runtime", @@ -12576,7 +12577,7 @@ dependencies = [ [[package]] name = "sp-keystore" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "parking_lot 0.12.5", @@ -12587,7 +12588,7 @@ dependencies = [ [[package]] name = "sp-maybe-compressed-blob" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "thiserror 1.0.69", "zstd 0.12.4", @@ -12596,7 +12597,7 @@ dependencies = [ [[package]] name = "sp-metadata-ir" version = "0.11.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "frame-metadata 23.0.1", "parity-scale-codec", @@ -12606,7 +12607,7 @@ dependencies = [ [[package]] name = "sp-mixnet" version = "0.15.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "scale-info", @@ -12617,7 +12618,7 @@ dependencies = [ [[package]] name = "sp-mmr-primitives" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "log", "parity-scale-codec", @@ -12634,7 +12635,7 @@ dependencies = [ [[package]] name = "sp-npos-elections" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "scale-info", @@ -12647,7 +12648,7 @@ dependencies = [ [[package]] name = "sp-offchain" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "sp-api", "sp-core", @@ -12657,7 +12658,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" version = "13.0.2" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "backtrace", "regex", @@ -12666,7 +12667,7 @@ dependencies = [ [[package]] name = "sp-rpc" version = "35.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "rustc-hash 1.1.0", "serde", @@ -12676,7 +12677,7 @@ dependencies = [ [[package]] name = "sp-runtime" version = "42.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "binary-merkle-tree", "docify", @@ -12705,7 +12706,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "30.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -12724,7 +12725,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "Inflector", "expander", @@ -12737,7 +12738,7 @@ dependencies = [ [[package]] name = "sp-session" version = "39.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "scale-info", @@ -12751,7 +12752,7 @@ dependencies = [ [[package]] name = "sp-staking" version = "39.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -12764,7 +12765,7 @@ dependencies = [ [[package]] name = "sp-state-machine" version = "0.46.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "hash-db", "log", @@ -12784,7 +12785,7 @@ dependencies = [ [[package]] name = "sp-statement-store" version = "21.2.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "aes-gcm", "curve25519-dalek", @@ -12808,12 +12809,12 @@ dependencies = [ [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" [[package]] name = "sp-storage" version = "22.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "impl-serde", "parity-scale-codec", @@ -12825,7 +12826,7 @@ dependencies = [ [[package]] name = "sp-timestamp" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "parity-scale-codec", @@ -12837,7 +12838,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "17.1.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "tracing", @@ -12848,7 +12849,7 @@ dependencies = [ [[package]] name = "sp-transaction-pool" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "sp-api", "sp-runtime", @@ -12857,7 +12858,7 @@ dependencies = [ [[package]] name = "sp-transaction-storage-proof" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "async-trait", "parity-scale-codec", @@ -12871,7 +12872,7 @@ dependencies = [ [[package]] name = "sp-trie" version = "40.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "ahash", "foldhash 0.1.5", @@ -12896,7 +12897,7 @@ dependencies = [ [[package]] name = "sp-version" version = "40.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "impl-serde", "parity-scale-codec", @@ -12913,7 +12914,7 @@ dependencies = [ [[package]] name = "sp-version-proc-macro" version = "15.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "parity-scale-codec", "proc-macro-warning", @@ -12925,7 +12926,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "22.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "anyhow", "impl-trait-for-tuples", @@ -12937,7 +12938,7 @@ dependencies = [ [[package]] name = "sp-weights" version = "32.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "bounded-collections", "parity-scale-codec", @@ -13110,8 +13111,8 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "staging-xcm" -version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +version = "17.0.1" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "array-bytes 6.2.3", "bounded-collections", @@ -13132,7 +13133,7 @@ dependencies = [ [[package]] name = "staging-xcm-builder" version = "21.1.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "environmental", "frame-support", @@ -13156,7 +13157,7 @@ dependencies = [ [[package]] name = "staging-xcm-executor" version = "20.0.2" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "environmental", "frame-benchmarking", @@ -13267,7 +13268,7 @@ dependencies = [ [[package]] name = "substrate-bip39" version = "0.6.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "hmac 0.12.1", "pbkdf2", @@ -13292,12 +13293,12 @@ dependencies = [ [[package]] name = "substrate-build-script-utils" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" [[package]] name = "substrate-frame-rpc-system" version = "45.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "docify", "frame-system-rpc-runtime-api", @@ -13317,7 +13318,7 @@ dependencies = [ [[package]] name = "substrate-prometheus-endpoint" version = "0.17.6" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "http-body-util", "hyper 1.9.0", @@ -13331,7 +13332,7 @@ dependencies = [ [[package]] name = "substrate-test-client" version = "2.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "array-bytes 6.2.3", "async-trait", @@ -13356,7 +13357,7 @@ dependencies = [ [[package]] name = "substrate-test-runtime" version = "2.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "array-bytes 6.2.3", "frame-executive", @@ -13402,7 +13403,7 @@ dependencies = [ [[package]] name = "substrate-test-runtime-client" version = "2.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "futures", "sc-block-builder", @@ -13420,7 +13421,7 @@ dependencies = [ [[package]] name = "substrate-wasm-builder" version = "27.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "array-bytes 6.2.3", "build-helper", @@ -14216,7 +14217,7 @@ dependencies = [ [[package]] name = "tracing-gum" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "coarsetime", "polkadot-primitives", @@ -14227,7 +14228,7 @@ dependencies = [ [[package]] name = "tracing-gum-proc-macro" version = "5.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "expander", "proc-macro-crate 3.5.0", @@ -15777,7 +15778,7 @@ dependencies = [ [[package]] name = "xcm-procedural" version = "11.0.2" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#a428e1d728ae5062abaf1ff448e7b6a969796009" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2506#93b592c0dc58bcda77c5c6666ec4d105f68e0e18" dependencies = [ "Inflector", "proc-macro2", From 9a13e81df18f1fde4567e7e5c77d77f368fb6199 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 14:53:14 -0400 Subject: [PATCH 13/18] feat: remove unused CommitmentMemos import from benchmarking module --- frame/shielded-pool/src/benchmarking.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frame/shielded-pool/src/benchmarking.rs b/frame/shielded-pool/src/benchmarking.rs index 26212f03..aa7f937e 100644 --- a/frame/shielded-pool/src/benchmarking.rs +++ b/frame/shielded-pool/src/benchmarking.rs @@ -23,9 +23,7 @@ use alloc::vec; mod benchmarks { use super::*; use crate::FrameEncryptedMemo; - use crate::pallet::{ - Assets, CommitmentMemos, HistoricPoseidonRoots, NextAssetId, PoolBalancePerAsset, - }; + use crate::pallet::{Assets, HistoricPoseidonRoots, NextAssetId, PoolBalancePerAsset}; use sp_std::vec::Vec; fn setup_benchmark_env() -> (T::AccountId, u32) { From 1df70f6a8678e8bed47b888ed54376437144d098 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 15:35:16 -0400 Subject: [PATCH 14/18] feat: optimize public signals handling in claim_shielded_fees tests --- .../shielded-pool/src/calls/claim_shielded_fees.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 02312c21..52f702ec 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 @@ -357,7 +357,7 @@ mod tests { ASSET_ID, &valid_memo(), PROOF, - &make_public_signals(commitment, AMOUNT as u64, ASSET_ID).to_vec(), + make_public_signals(commitment, AMOUNT as u64, ASSET_ID).as_ref(), ); let h = MockHandle::new(input.clone()); match super::decode::(&h, &input).unwrap() { @@ -378,7 +378,7 @@ mod tests { ASSET_ID, &valid_memo(), PROOF, - &ps.to_vec(), + ps.as_ref(), ); let h = MockHandle::new(input.clone()); match super::decode::(&h, &input).unwrap() { @@ -398,7 +398,7 @@ mod tests { 0, &valid_memo(), PROOF, - &make_public_signals(COMMITMENT, AMOUNT as u64, 0).to_vec(), + make_public_signals(COMMITMENT, AMOUNT as u64, 0).as_ref(), ); let h = MockHandle::new(input.clone()); match super::decode::(&h, &input).unwrap() { @@ -504,7 +504,7 @@ mod tests { ASSET_ID, &valid_memo(), PROOF, - &mismatched.to_vec(), + mismatched.as_ref(), ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -523,7 +523,7 @@ mod tests { ASSET_ID, &valid_memo(), PROOF, - &mismatched.to_vec(), + mismatched.as_ref(), ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -542,7 +542,7 @@ mod tests { ASSET_ID, // param asset_id 0 ≠ signals asset_id 99 &valid_memo(), PROOF, - &mismatched.to_vec(), + mismatched.as_ref(), ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); @@ -633,7 +633,7 @@ mod tests { 99, // unregistered &valid_memo(), PROOF, - &ps.to_vec(), + ps.as_ref(), ); let mut h = MockHandle::new(input); expect_error(ShieldedPoolPrecompile::::execute(&mut h)); From c48b88646b0b3bd458672a35b1a66dd77e3d5214 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 16:24:41 -0400 Subject: [PATCH 15/18] feat: remove unused mock_evm_address_set function from testing module --- frame/shielded-pool/src/mock.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/frame/shielded-pool/src/mock.rs b/frame/shielded-pool/src/mock.rs index f2149cfc..7447f172 100644 --- a/frame/shielded-pool/src/mock.rs +++ b/frame/shielded-pool/src/mock.rs @@ -171,14 +171,6 @@ pub fn mock_evm_address_get(who: u64) -> Option { .map(sp_core::H160::from) } -/// Write the registered EVM address for an account to raw test storage. -pub fn mock_evm_address_set(who: u64, addr: sp_core::H160) { - use parity_scale_codec::Encode; - let key = [b"mock:evm:".as_ref(), who.encode().as_slice()].concat(); - // Encode as fixed [u8; 20] — no compact length prefix — so decode matches. - sp_io::storage::set(&key, &addr.as_fixed_bytes().encode()); -} - /// Write a minimum relay fee to raw test storage. /// By default `MockRelayer::min_relay_fee()` returns 0; call this to raise the floor. pub fn mock_set_min_relay_fee(fee: u128) { From bd5e71b50c6ee290d497b96d76f5f7591885dd48 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 16:24:54 -0400 Subject: [PATCH 16/18] feat: remove sync-circuit-artifacts.sh and generate-vk-rust.sh scripts --- scripts/sync-circuits/generate-vk-rust.sh | 235 ------------------ .../sync-circuits/sync-circuit-artifacts.sh | 125 ---------- 2 files changed, 360 deletions(-) delete mode 100755 scripts/sync-circuits/generate-vk-rust.sh delete mode 100755 scripts/sync-circuits/sync-circuit-artifacts.sh diff --git a/scripts/sync-circuits/generate-vk-rust.sh b/scripts/sync-circuits/generate-vk-rust.sh deleted file mode 100755 index 776170bf..00000000 --- a/scripts/sync-circuits/generate-vk-rust.sh +++ /dev/null @@ -1,235 +0,0 @@ -#!/usr/bin/env bash -# -# Generate a Rust VK file from a JSON verification key. -# -# Requires: jq (https://jqlang.org) — available on all platforms, no Python needed. -# -# Usage: -# ./scripts/generate-vk-rust.sh -# -# Example: -# ./scripts/generate-vk-rust.sh disclosure \ -# artifacts/verification_key_disclosure.json \ -# primitives/zk-verifier/src/infrastructure/storage/verification_keys/disclosure.rs -# -set -euo pipefail - -if [ $# -ne 3 ]; then - echo "Usage: generate-vk-rust.sh " >&2 - exit 1 -fi - -CIRCUIT_NAME="$1" -JSON_PATH="$2" -OUTPUT_PATH="$3" - -if [ ! -f "$JSON_PATH" ]; then - echo "Error: JSON file not found: $JSON_PATH" >&2 - exit 1 -fi - -if ! command -v jq &>/dev/null; then - echo "Error: 'jq' is required but not installed." >&2 - echo " macOS: brew install jq" >&2 - echo " Ubuntu: apt-get install -y jq" >&2 - exit 1 -fi - -# ── Helpers ────────────────────────────────────────────────────────────────── - -# jq path shortcuts -JQ="jq -r" -JSON="$JSON_PATH" - -CIRCUIT_UPPER="$(echo "$CIRCUIT_NAME" | tr '[:lower:]' '[:upper:]')" -CIRCUIT_TITLE="$(echo "$CIRCUIT_NAME" | awk '{print toupper(substr($0,1,1)) substr($0,2)}')" -GENERATED_AT="$(date +"%Y-%m-%d %H:%M:%S %Z")" - -# Read scalar field elements -alpha_g1_x=$($JQ '.vk_alpha_1[0]' "$JSON") -alpha_g1_y=$($JQ '.vk_alpha_1[1]' "$JSON") - -beta_g2_x0=$($JQ '.vk_beta_2[0][0]' "$JSON") -beta_g2_x1=$($JQ '.vk_beta_2[0][1]' "$JSON") -beta_g2_y0=$($JQ '.vk_beta_2[1][0]' "$JSON") -beta_g2_y1=$($JQ '.vk_beta_2[1][1]' "$JSON") - -gamma_g2_x0=$($JQ '.vk_gamma_2[0][0]' "$JSON") -gamma_g2_x1=$($JQ '.vk_gamma_2[0][1]' "$JSON") -gamma_g2_y0=$($JQ '.vk_gamma_2[1][0]' "$JSON") -gamma_g2_y1=$($JQ '.vk_gamma_2[1][1]' "$JSON") - -delta_g2_x0=$($JQ '.vk_delta_2[0][0]' "$JSON") -delta_g2_x1=$($JQ '.vk_delta_2[0][1]' "$JSON") -delta_g2_y0=$($JQ '.vk_delta_2[1][0]' "$JSON") -delta_g2_y1=$($JQ '.vk_delta_2[1][1]' "$JSON") - -# Number of IC points -IC_LEN=$($JQ '.IC | length' "$JSON") - -# ── Begin generating output ─────────────────────────────────────────────────── - -mkdir -p "$(dirname "$OUTPUT_PATH")" - -{ -cat <

VerifyingKey { - // Alpha G1 - let alpha_g1 = G1Affine::new_unchecked( - Fq::from_str( - "${alpha_g1_x}", - ) - .unwrap(), - Fq::from_str( - "${alpha_g1_y}", - ) - .unwrap(), - ); - - // Beta G2 - let beta_g2 = G2Affine::new_unchecked( - Fq2::new( - Fq::from_str( - "${beta_g2_x0}", - ) - .unwrap(), - Fq::from_str( - "${beta_g2_x1}", - ) - .unwrap(), - ), - Fq2::new( - Fq::from_str( - "${beta_g2_y0}", - ) - .unwrap(), - Fq::from_str( - "${beta_g2_y1}", - ) - .unwrap(), - ), - ); - - // Gamma G2 - let gamma_g2 = G2Affine::new_unchecked( - Fq2::new( - Fq::from_str( - "${gamma_g2_x0}", - ) - .unwrap(), - Fq::from_str( - "${gamma_g2_x1}", - ) - .unwrap(), - ), - Fq2::new( - Fq::from_str( - "${gamma_g2_y0}", - ) - .unwrap(), - Fq::from_str( - "${gamma_g2_y1}", - ) - .unwrap(), - ), - ); - - // Delta G2 - let delta_g2 = G2Affine::new_unchecked( - Fq2::new( - Fq::from_str( - "${delta_g2_x0}", - ) - .unwrap(), - Fq::from_str( - "${delta_g2_x1}", - ) - .unwrap(), - ), - Fq2::new( - Fq::from_str( - "${delta_g2_y0}", - ) - .unwrap(), - Fq::from_str( - "${delta_g2_y1}", - ) - .unwrap(), - ), - ); - - // IC points (gamma_abc_g1) -HEADER - -# Generate one let binding per IC point -for (( i=0; i alloc::vec::Vec { - let vk = get_vk(); - let mut bytes = alloc::vec::Vec::new(); - vk.serialize_compressed(&mut bytes) - .expect("VK serialization should not fail"); - bytes -} -FOOTER - -} > "$OUTPUT_PATH" - -echo "✓ Generated $OUTPUT_PATH" diff --git a/scripts/sync-circuits/sync-circuit-artifacts.sh b/scripts/sync-circuits/sync-circuit-artifacts.sh deleted file mode 100755 index 02545732..00000000 --- a/scripts/sync-circuits/sync-circuit-artifacts.sh +++ /dev/null @@ -1,125 +0,0 @@ -#!/bin/bash -# -# Sync Circuit Verification Keys Script -# -# Downloads verification keys from the latest release of the orbinum/circuits repository -# and regenerates Rust files in primitives/zk-verifier. -# -# Usage: -# ./scripts/sync-circuits/sync-circuit-artifacts.sh # latest version (automatic) -# ./scripts/sync-circuits/sync-circuit-artifacts.sh v0.3.1 # specific version -# -set -e -# ============================================================================ -# Configuration -# ============================================================================ -CIRCUITS_REPO="orbinum/circuits" -CIRCUITS_API="https://api.github.com/repos/${CIRCUITS_REPO}/releases/latest" -# Directories -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -WORKSPACE_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" -VK_DIR="${WORKSPACE_ROOT}/primitives/zk-verifier/src/infrastructure/storage/verification_keys" -TEMP_DIR="${SCRIPT_DIR}/tmp" -# Expected circuits -CIRCUITS=("disclosure" "transfer" "unshield" "private_link") -# Colors -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' -log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } -log_error() { echo -e "${RED}[ERROR]${NC} $1"; } - -cleanup() { - rm -rf "$TEMP_DIR" -} - -trap cleanup EXIT -# ============================================================================ -# Step 1: Resolve version -# ============================================================================ -log_info "════════════════════════════════════════════════════════════════" -log_info " Sync Circuit Verification Keys" -log_info "════════════════════════════════════════════════════════════════" -if [ -n "$1" ]; then - VERSION="$1" - log_info "Specified version: ${VERSION}" -else - log_info "Fetching latest version from GitHub API..." - VERSION="$(curl -fsSL "$CIRCUITS_API" | jq -r '.tag_name')" - if [ -z "$VERSION" ]; then - log_error "Failed to fetch latest version from ${CIRCUITS_API}" - exit 1 - fi - log_info "Latest version detected: ${VERSION}" -fi -TARBALL_NAME="orbinum-verification-keys-${VERSION}.tar.gz" -TARBALL_URL="https://github.com/${CIRCUITS_REPO}/releases/download/${VERSION}/${TARBALL_NAME}" -# ============================================================================ -# Step 2: Download tarball -# ============================================================================ -log_info "" -log_info "Downloading verification keys..." -log_info " URL: ${TARBALL_URL}" -rm -rf "$TEMP_DIR" -mkdir -p "$TEMP_DIR" -TARBALL_PATH="${TEMP_DIR}/${TARBALL_NAME}" -if ! curl -fsSL -o "$TARBALL_PATH" "$TARBALL_URL"; then - log_error "Download failed: ${TARBALL_URL}" - exit 1 -fi -log_info " ✓ Downloaded ($(du -h "$TARBALL_PATH" | cut -f1))" -# ============================================================================ -# Step 3: Extract -# ============================================================================ -log_info "" -log_info "Extracting files..." -EXTRACT_DIR="${TEMP_DIR}/extracted" -mkdir -p "$EXTRACT_DIR" -tar -xzf "$TARBALL_PATH" -C "$EXTRACT_DIR" -log_info " ✓ Extracted to ${EXTRACT_DIR}" -# ============================================================================ -# Step 4: Verify JSON and generate Rust -# ============================================================================ -log_info "" -log_info "Generating Rust files from JSON..." -mkdir -p "$VK_DIR" -MISSING=() -for circuit in "${CIRCUITS[@]}"; do - # Search for JSON at any sublevel of the extracted tarball - VK_JSON="$(find "$EXTRACT_DIR" -name "verification_key_${circuit}.json" | head -1)" - if [ -z "$VK_JSON" ]; then - log_error " ✗ verification_key_${circuit}.json not found in tarball" - MISSING+=("verification_key_${circuit}.json") - continue - fi - log_info " verification_key_${circuit}.json ($(du -h "$VK_JSON" | cut -f1))" - VK_RUST="${VK_DIR}/${circuit}.rs" - if bash "${WORKSPACE_ROOT}/scripts/sync-circuits/generate-vk-rust.sh" "$circuit" "$VK_JSON" "$VK_RUST"; then - log_info " ✓ Generated ${circuit}.rs" - else - log_error " ✗ Failed to generate ${circuit}.rs" - MISSING+=("${circuit}.rs") - fi -done -# ============================================================================ -# Summary -# ============================================================================ -log_info "" -log_info "════════════════════════════════════════════════════════════════" -if [ ${#MISSING[@]} -ne 0 ]; then - log_error "❌ Missing files:" - for f in "${MISSING[@]}"; do log_error " - $f"; done - exit 1 -fi -log_info "✅ Verification keys synchronized successfully (${VERSION})" -log_info "" -log_info "Rust files generated in:" -log_info " ${VK_DIR}/" -log_info "" -log_info "Next steps:" -log_info " 1. cargo check --package orbinum-zk-verifier" -log_info " 2. git add primitives/zk-verifier/src/infrastructure/storage/verification_keys/" -log_info " git commit -m \"chore: sync vk to ${VERSION}\"" -log_info "════════════════════════════════════════════════════════════════" From 7a3b12a6500f908aac8c19e778daf27d30888e47 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 16:25:03 -0400 Subject: [PATCH 17/18] feat: update README to clarify value proofs and enhance privacy features --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 225f0c06..0a045087 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Orbinum Network -Orbinum is a privacy-focused blockchain network built on Substrate that enables confidential transactions with selective disclosure capabilities. +Orbinum is a privacy-focused blockchain network built on Substrate that enables confidential transactions with cryptographic value proofs. ## Overview -Orbinum combines the transparency benefits of blockchain technology with advanced cryptographic privacy features, allowing users to control what information they reveal while maintaining verifiable trust. +Orbinum combines the transparency benefits of blockchain technology with advanced cryptographic privacy features, allowing users to transact privately and prove specific note properties without revealing underlying data. ## Key Features @@ -13,10 +13,10 @@ Orbinum combines the transparency benefits of blockchain technology with advance - **Private Transfers**: Send and receive assets without revealing amounts, sender, or recipient - **Shield/Unshield**: Move assets between transparent and private domains seamlessly -### Selective Disclosure -- **Proof Generation**: Create cryptographic proofs that reveal specific transaction properties without exposing underlying data -- **Compliance Tools**: Selectively disclose transaction details to authorized parties -- **Audit Trail**: Maintain verifiable records while preserving user privacy +### Value Proofs +- **Value Proof Circuit**: Cryptographic proof that a note commitment encodes the declared value and asset, used for relay fee claiming (`claim_shielded_fees`) +- **Proof of Note Ownership**: Any note owner can generate a value proof to demonstrate knowledge of a commitment's preimage without revealing the blinding factor +- **Audit Trail**: Maintain verifiable records for compliance while preserving user privacy ### EVM Compatibility - **Frontier Integration**: Full Ethereum Virtual Machine compatibility layer @@ -33,10 +33,10 @@ Orbinum combines the transparency benefits of blockchain technology with advance Orbinum is built using Substrate's FRAME framework and implements Clean Architecture principles across all components: -- **Pallets**: Modular runtime components (`pallet-shielded-pool`, `pallet-zk-verifier`) +- **Pallets**: Modular runtime components (`pallet-shielded-pool`, `pallet-zk-verifier`, `pallet-relayer`) - **Primitives**: Core cryptographic libraries (`zk-core`, `zk-verifier`, `zk-circuits`) - **Client**: RPC layer and blockchain infrastructure -- **Circuits**: TypeScript/Circom zero-knowledge circuits +- **Circuits**: Circom zero-knowledge circuits (`value_proof`, `transfer`, `unshield`, `private_link`) ## License From d7b998bd4da53c552dd4f21186758c7858aea758 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Tue, 19 May 2026 16:25:10 -0400 Subject: [PATCH 18/18] Implement feature X to enhance user experience and optimize performance --- docs/package-lock.json | 1713 ---------------------------------------- 1 file changed, 1713 deletions(-) delete mode 100644 docs/package-lock.json diff --git a/docs/package-lock.json b/docs/package-lock.json deleted file mode 100644 index f7f44cc6..00000000 --- a/docs/package-lock.json +++ /dev/null @@ -1,1713 +0,0 @@ -{ - "name": "docs", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@mdit/plugin-footnote": "^0.22.2", - "vitepress": "^1.6.3" - } - }, - "node_modules/@algolia/autocomplete-core": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", - "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", - "@algolia/autocomplete-shared": "1.17.7" - } - }, - "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", - "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-shared": "1.17.7" - }, - "peerDependencies": { - "search-insights": ">= 1 < 3" - } - }, - "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", - "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-shared": "1.17.7" - }, - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/autocomplete-shared": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", - "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", - "license": "MIT", - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/client-abtesting": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.30.0.tgz", - "integrity": "sha512-Q3OQXYlTNqVUN/V1qXX8VIzQbLjP3yrRBO9m6NRe1CBALmoGHh9JrYosEGvfior28+DjqqU3Q+nzCSuf/bX0Gw==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.30.0", - "@algolia/requester-browser-xhr": "5.30.0", - "@algolia/requester-fetch": "5.30.0", - "@algolia/requester-node-http": "5.30.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-analytics": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.30.0.tgz", - "integrity": "sha512-/b+SAfHjYjx/ZVeVReCKTTnFAiZWOyvYLrkYpeNMraMT6akYRR8eC1AvFcvR60GLG/jytxcJAp42G8nN5SdcLg==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.30.0", - "@algolia/requester-browser-xhr": "5.30.0", - "@algolia/requester-fetch": "5.30.0", - "@algolia/requester-node-http": "5.30.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-common": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.30.0.tgz", - "integrity": "sha512-tbUgvkp2d20mHPbM0+NPbLg6SzkUh0lADUUjzNCF+HiPkjFRaIW3NGMlESKw5ia4Oz6ZvFzyREquUX6rdkdJcQ==", - "license": "MIT", - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-insights": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.30.0.tgz", - "integrity": "sha512-caXuZqJK761m32KoEAEkjkE2WF/zYg1McuGesWXiLSgfxwZZIAf+DljpiSToBUXhoPesvjcLtINyYUzbkwE0iw==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.30.0", - "@algolia/requester-browser-xhr": "5.30.0", - "@algolia/requester-fetch": "5.30.0", - "@algolia/requester-node-http": "5.30.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-personalization": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.30.0.tgz", - "integrity": "sha512-7K6P7TRBHLX1zTmwKDrIeBSgUidmbj6u3UW/AfroLRDGf9oZFytPKU49wg28lz/yulPuHY0nZqiwbyAxq9V17w==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.30.0", - "@algolia/requester-browser-xhr": "5.30.0", - "@algolia/requester-fetch": "5.30.0", - "@algolia/requester-node-http": "5.30.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-query-suggestions": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.30.0.tgz", - "integrity": "sha512-WMjWuBjYxJheRt7Ec5BFr33k3cV0mq2WzmH9aBf5W4TT8kUp34x91VRsYVaWOBRlxIXI8o/WbhleqSngiuqjLA==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.30.0", - "@algolia/requester-browser-xhr": "5.30.0", - "@algolia/requester-fetch": "5.30.0", - "@algolia/requester-node-http": "5.30.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-search": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.30.0.tgz", - "integrity": "sha512-puc1/LREfSqzgmrOFMY5L/aWmhYOlJ0TTpa245C0ZNMKEkdOkcimFbXTXQ8lZhzh+rlyFgR7cQGNtXJ5H0XgZg==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.30.0", - "@algolia/requester-browser-xhr": "5.30.0", - "@algolia/requester-fetch": "5.30.0", - "@algolia/requester-node-http": "5.30.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/ingestion": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.30.0.tgz", - "integrity": "sha512-NfqiIKVgGKTLr6T9F81oqB39pPiEtILTy0z8ujxPKg2rCvI/qQeDqDWFBmQPElCfUTU6kk67QAgMkQ7T6fE+gg==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.30.0", - "@algolia/requester-browser-xhr": "5.30.0", - "@algolia/requester-fetch": "5.30.0", - "@algolia/requester-node-http": "5.30.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/monitoring": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.30.0.tgz", - "integrity": "sha512-/eeM3aqLKro5KBZw0W30iIA6afkGa+bcpvEM0NDa92m5t3vil4LOmJI9FkgzfmSkF4368z/SZMOTPShYcaVXjA==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.30.0", - "@algolia/requester-browser-xhr": "5.30.0", - "@algolia/requester-fetch": "5.30.0", - "@algolia/requester-node-http": "5.30.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/recommend": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.30.0.tgz", - "integrity": "sha512-iWeAUWqw+xT+2IyUyTqnHCK+cyCKYV5+B6PXKdagc9GJJn6IaPs8vovwoC0Za5vKCje/aXQ24a2Z1pKpc/tdHg==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.30.0", - "@algolia/requester-browser-xhr": "5.30.0", - "@algolia/requester-fetch": "5.30.0", - "@algolia/requester-node-http": "5.30.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.30.0.tgz", - "integrity": "sha512-alo3ly0tdNLjfMSPz9dmNwYUFHx7guaz5dTGlIzVGnOiwLgIoM6NgA+MJLMcH6e1S7OpmE2AxOy78svlhst2tQ==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.30.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-fetch": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.30.0.tgz", - "integrity": "sha512-WOnTYUIY2InllHBy6HHMpGIOo7Or4xhYUx/jkoSK/kPIa1BRoFEHqa8v4pbKHtoG7oLvM2UAsylSnjVpIhGZXg==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.30.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-node-http": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.30.0.tgz", - "integrity": "sha512-uSTUh9fxeHde1c7KhvZKUrivk90sdiDftC+rSKNFKKEU9TiIKAGA7B2oKC+AoMCqMymot1vW9SGbeESQPTZd0w==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.30.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz", - "integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@docsearch/css": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", - "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", - "license": "MIT" - }, - "node_modules/@docsearch/js": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", - "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", - "license": "MIT", - "dependencies": { - "@docsearch/react": "3.8.2", - "preact": "^10.0.0" - } - }, - "node_modules/@docsearch/react": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", - "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-core": "1.17.7", - "@algolia/autocomplete-preset-algolia": "1.17.7", - "@docsearch/css": "3.8.2", - "algoliasearch": "^5.14.2" - }, - "peerDependencies": { - "@types/react": ">= 16.8.0 < 19.0.0", - "react": ">= 16.8.0 < 19.0.0", - "react-dom": ">= 16.8.0 < 19.0.0", - "search-insights": ">= 1 < 3" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "search-insights": { - "optional": true - } - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@iconify-json/simple-icons": { - "version": "1.2.41", - "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.41.tgz", - "integrity": "sha512-4tt29cKzNsxvt6rjAOVhEgpZV0L8jleTDTMdtvIJjF14Afp9aH8peuwGYyX35l6idfFwuzbvjSVfVyVjJtfmYA==", - "license": "CC0-1.0", - "dependencies": { - "@iconify/types": "*" - } - }, - "node_modules/@iconify/types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "license": "MIT" - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@mdit/plugin-footnote": { - "version": "0.22.2", - "resolved": "https://registry.npmjs.org/@mdit/plugin-footnote/-/plugin-footnote-0.22.2.tgz", - "integrity": "sha512-lHB6AV61QruvrWXIu/oWncltH2ED8cBUuvX4IO+5TvtWSyyc6wOm3ErPqqTFJqy1SJ1p21oLNcqRGdPF+S3N4w==", - "license": "MIT", - "dependencies": { - "@types/markdown-it": "^14.1.2" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "markdown-it": "^14.1.0" - } - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz", - "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@shikijs/core": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", - "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", - "license": "MIT", - "dependencies": { - "@shikijs/engine-javascript": "2.5.0", - "@shikijs/engine-oniguruma": "2.5.0", - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.4" - } - }, - "node_modules/@shikijs/engine-javascript": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", - "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2", - "oniguruma-to-es": "^3.1.0" - } - }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", - "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2" - } - }, - "node_modules/@shikijs/langs": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", - "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "2.5.0" - } - }, - "node_modules/@shikijs/themes": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", - "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "2.5.0" - } - }, - "node_modules/@shikijs/transformers": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", - "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", - "license": "MIT", - "dependencies": { - "@shikijs/core": "2.5.0", - "@shikijs/types": "2.5.0" - } - }, - "node_modules/@shikijs/types": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", - "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", - "license": "MIT", - "dependencies": { - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/@shikijs/vscode-textmate": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", - "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "license": "MIT" - }, - "node_modules/@types/markdown-it": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", - "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", - "license": "MIT", - "dependencies": { - "@types/linkify-it": "^5", - "@types/mdurl": "^2" - } - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "license": "MIT" - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.21", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", - "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "license": "ISC" - }, - "node_modules/@vitejs/plugin-vue": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", - "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0", - "vue": "^3.2.25" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.17.tgz", - "integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.27.5", - "@vue/shared": "3.5.17", - "entities": "^4.5.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz", - "integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.17", - "@vue/shared": "3.5.17" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz", - "integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.27.5", - "@vue/compiler-core": "3.5.17", - "@vue/compiler-dom": "3.5.17", - "@vue/compiler-ssr": "3.5.17", - "@vue/shared": "3.5.17", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.17", - "postcss": "^8.5.6", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz", - "integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.17", - "@vue/shared": "3.5.17" - } - }, - "node_modules/@vue/devtools-api": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz", - "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==", - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^7.7.7" - } - }, - "node_modules/@vue/devtools-kit": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz", - "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==", - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^7.7.7", - "birpc": "^2.3.0", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^1.0.0", - "speakingurl": "^14.0.1", - "superjson": "^2.2.2" - } - }, - "node_modules/@vue/devtools-shared": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz", - "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==", - "license": "MIT", - "dependencies": { - "rfdc": "^1.4.1" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.17.tgz", - "integrity": "sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.17" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.17.tgz", - "integrity": "sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.17", - "@vue/shared": "3.5.17" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz", - "integrity": "sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.17", - "@vue/runtime-core": "3.5.17", - "@vue/shared": "3.5.17", - "csstype": "^3.1.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.17.tgz", - "integrity": "sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.17", - "@vue/shared": "3.5.17" - }, - "peerDependencies": { - "vue": "3.5.17" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.17.tgz", - "integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==", - "license": "MIT" - }, - "node_modules/@vueuse/core": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", - "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "12.8.2", - "@vueuse/shared": "12.8.2", - "vue": "^3.5.13" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/integrations": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", - "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", - "license": "MIT", - "dependencies": { - "@vueuse/core": "12.8.2", - "@vueuse/shared": "12.8.2", - "vue": "^3.5.13" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "async-validator": "^4", - "axios": "^1", - "change-case": "^5", - "drauu": "^0.4", - "focus-trap": "^7", - "fuse.js": "^7", - "idb-keyval": "^6", - "jwt-decode": "^4", - "nprogress": "^0.2", - "qrcode": "^1.5", - "sortablejs": "^1", - "universal-cookie": "^7" - }, - "peerDependenciesMeta": { - "async-validator": { - "optional": true - }, - "axios": { - "optional": true - }, - "change-case": { - "optional": true - }, - "drauu": { - "optional": true - }, - "focus-trap": { - "optional": true - }, - "fuse.js": { - "optional": true - }, - "idb-keyval": { - "optional": true - }, - "jwt-decode": { - "optional": true - }, - "nprogress": { - "optional": true - }, - "qrcode": { - "optional": true - }, - "sortablejs": { - "optional": true - }, - "universal-cookie": { - "optional": true - } - } - }, - "node_modules/@vueuse/metadata": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", - "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", - "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", - "license": "MIT", - "dependencies": { - "vue": "^3.5.13" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/algoliasearch": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.30.0.tgz", - "integrity": "sha512-ILSdPX4je0n5WUKD34TMe57/eqiXUzCIjAsdtLQYhomqOjTtFUg1s6dE7kUegc4Mc43Xr7IXYlMutU9HPiYfdw==", - "license": "MIT", - "dependencies": { - "@algolia/client-abtesting": "5.30.0", - "@algolia/client-analytics": "5.30.0", - "@algolia/client-common": "5.30.0", - "@algolia/client-insights": "5.30.0", - "@algolia/client-personalization": "5.30.0", - "@algolia/client-query-suggestions": "5.30.0", - "@algolia/client-search": "5.30.0", - "@algolia/ingestion": "1.30.0", - "@algolia/monitoring": "1.30.0", - "@algolia/recommend": "5.30.0", - "@algolia/requester-browser-xhr": "5.30.0", - "@algolia/requester-fetch": "5.30.0", - "@algolia/requester-node-http": "5.30.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0", - "peer": true - }, - "node_modules/birpc": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.4.0.tgz", - "integrity": "sha512-5IdNxTyhXHv2UlgnPHQ0h+5ypVmkrYHzL8QT+DwFZ//2N/oNV8Ch+BCRmTJ3x6/z9Axo/cXYBc9eprsUVK/Jsg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/copy-anything": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", - "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", - "license": "MIT", - "dependencies": { - "is-what": "^4.1.8" - }, - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/emoji-regex-xs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", - "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", - "license": "MIT" - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/focus-trap": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.5.tgz", - "integrity": "sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg==", - "license": "MIT", - "dependencies": { - "tabbable": "^6.2.0" - } - }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "license": "MIT" - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-what": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", - "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", - "license": "MIT", - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "uc.micro": "^2.0.0" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/mark.js": { - "version": "8.11.1", - "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", - "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", - "license": "MIT" - }, - "node_modules/markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", - "license": "MIT", - "peer": true, - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "license": "MIT", - "peer": true - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/minisearch": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.2.tgz", - "integrity": "sha512-R1Pd9eF+MD5JYDDSPAp/q1ougKglm14uEkPMvQ/05RGmx6G9wvmLTrTI/Q5iPNJLYqNdsDQ7qTGIcNWR+FrHmA==", - "license": "MIT" - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/oniguruma-to-es": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", - "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", - "license": "MIT", - "dependencies": { - "emoji-regex-xs": "^1.0.0", - "regex": "^6.0.1", - "regex-recursion": "^6.0.2" - } - }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/preact": { - "version": "10.26.9", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.9.tgz", - "integrity": "sha512-SSjF9vcnF27mJK1XyFMNJzFd5u3pQiATFqoaDy03XuN00u4ziveVVEGt5RKJrDR8MHE/wJo9Nnad56RLzS2RMA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", - "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-recursion": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", - "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-utilities": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", - "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", - "license": "MIT" - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", - "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==", - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.44.1", - "@rollup/rollup-android-arm64": "4.44.1", - "@rollup/rollup-darwin-arm64": "4.44.1", - "@rollup/rollup-darwin-x64": "4.44.1", - "@rollup/rollup-freebsd-arm64": "4.44.1", - "@rollup/rollup-freebsd-x64": "4.44.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.44.1", - "@rollup/rollup-linux-arm-musleabihf": "4.44.1", - "@rollup/rollup-linux-arm64-gnu": "4.44.1", - "@rollup/rollup-linux-arm64-musl": "4.44.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.44.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1", - "@rollup/rollup-linux-riscv64-gnu": "4.44.1", - "@rollup/rollup-linux-riscv64-musl": "4.44.1", - "@rollup/rollup-linux-s390x-gnu": "4.44.1", - "@rollup/rollup-linux-x64-gnu": "4.44.1", - "@rollup/rollup-linux-x64-musl": "4.44.1", - "@rollup/rollup-win32-arm64-msvc": "4.44.1", - "@rollup/rollup-win32-ia32-msvc": "4.44.1", - "@rollup/rollup-win32-x64-msvc": "4.44.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/search-insights": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", - "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", - "license": "MIT", - "peer": true - }, - "node_modules/shiki": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", - "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", - "license": "MIT", - "dependencies": { - "@shikijs/core": "2.5.0", - "@shikijs/engine-javascript": "2.5.0", - "@shikijs/engine-oniguruma": "2.5.0", - "@shikijs/langs": "2.5.0", - "@shikijs/themes": "2.5.0", - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/speakingurl": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/superjson": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", - "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", - "license": "MIT", - "dependencies": { - "copy-anything": "^3.0.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", - "license": "MIT" - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "license": "MIT", - "peer": true - }, - "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vite": { - "version": "5.4.19", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", - "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vitepress": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.3.tgz", - "integrity": "sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==", - "license": "MIT", - "dependencies": { - "@docsearch/css": "3.8.2", - "@docsearch/js": "3.8.2", - "@iconify-json/simple-icons": "^1.2.21", - "@shikijs/core": "^2.1.0", - "@shikijs/transformers": "^2.1.0", - "@shikijs/types": "^2.1.0", - "@types/markdown-it": "^14.1.2", - "@vitejs/plugin-vue": "^5.2.1", - "@vue/devtools-api": "^7.7.0", - "@vue/shared": "^3.5.13", - "@vueuse/core": "^12.4.0", - "@vueuse/integrations": "^12.4.0", - "focus-trap": "^7.6.4", - "mark.js": "8.11.1", - "minisearch": "^7.1.1", - "shiki": "^2.1.0", - "vite": "^5.4.14", - "vue": "^3.5.13" - }, - "bin": { - "vitepress": "bin/vitepress.js" - }, - "peerDependencies": { - "markdown-it-mathjax3": "^4", - "postcss": "^8" - }, - "peerDependenciesMeta": { - "markdown-it-mathjax3": { - "optional": true - }, - "postcss": { - "optional": true - } - } - }, - "node_modules/vue": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.17.tgz", - "integrity": "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.17", - "@vue/compiler-sfc": "3.5.17", - "@vue/runtime-dom": "3.5.17", - "@vue/server-renderer": "3.5.17", - "@vue/shared": "3.5.17" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -}