diff --git a/Cargo.lock b/Cargo.lock index 5c153d92cc..85ca535051 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2496,7 +2496,9 @@ dependencies = [ "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", + "hex-literal 0.3.4", "kilt-dip-primitives", + "kilt-support", "pallet-aura", "pallet-authorship", "pallet-balances", @@ -2519,6 +2521,7 @@ dependencies = [ "sp-consensus-aura", "sp-core", "sp-inherents", + "sp-io", "sp-offchain", "sp-runtime", "sp-session", @@ -2899,6 +2902,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "enum-iterator" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "600536cfe9e2da0820aa498e570f6b2b9223eec3ce2f835c8ae4861304fa4794" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03cdc46ec28bd728e67540c528013c6a10eb69a02eb31078a1bda695438cbfb8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "enumflags2" version = "0.7.8" @@ -4601,9 +4624,10 @@ dependencies = [ name = "kilt-dip-primitives" version = "1.13.0-dev" dependencies = [ - "cfg-if", + "cumulus-pallet-parachain-system", "cumulus-primitives-core", "did", + "enum-iterator", "frame-support", "frame-system", "hash-db", @@ -4616,6 +4640,8 @@ dependencies = [ "pallet-relay-store", "pallet-web3-names", "parity-scale-codec", + "peregrine-runtime", + "rococo-runtime", "scale-info", "sp-core", "sp-io", @@ -4623,6 +4649,7 @@ dependencies = [ "sp-state-machine", "sp-std", "sp-trie", + "spiritnet-runtime", ] [[package]] @@ -6581,6 +6608,8 @@ dependencies = [ "frame-support", "frame-system", "kilt-support", + "pallet-balances", + "pallet-did-lookup", "parity-scale-codec", "scale-info", "sp-io", @@ -7091,6 +7120,7 @@ dependencies = [ "sp-keystore", "sp-runtime", "sp-std", + "sp-trie", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 48a43bcd5e..7e17e3cd67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ base58 = {version = "0.2.0", default-features = false} bitflags = {version = "1.3.2", default-features = false} cfg-if = "1.0" clap = "4.1.6" +enum-iterator = "2.0.0" env_logger = "0.10.0" fluent-uri = { version = "0.1.4", default-features = false } futures = {version = "0.3.21", default-features = false} diff --git a/crates/kilt-dip-primitives/Cargo.toml b/crates/kilt-dip-primitives/Cargo.toml index 2b7f4adce1..6d5ea8ff39 100644 --- a/crates/kilt-dip-primitives/Cargo.toml +++ b/crates/kilt-dip-primitives/Cargo.toml @@ -14,7 +14,6 @@ version.workspace = true # External dependencies hash-db.workspace = true log.workspace = true -cfg-if.workspace = true # Internal dependencies did.workspace = true @@ -43,7 +42,12 @@ sp-trie.workspace = true cumulus-primitives-core.workspace = true [dev-dependencies] +cumulus-pallet-parachain-system = { workspace = true, features = ["std"] } +enum-iterator.workspace = true hex-literal.workspace = true +peregrine-runtime = { workspace = true, features = ["std"] } +rococo-runtime = { workspace = true, features = ["std"] } +spiritnet-runtime = { workspace = true, features = ["std"] } sp-io = { workspace = true, features = ["std"] } [features] @@ -73,5 +77,5 @@ std = [ runtime-benchmarks = [ "kilt-support/runtime-benchmarks", "pallet-dip-consumer/runtime-benchmarks", - "pallet-dip-provider/runtime-benchmarks", + "pallet-dip-provider/runtime-benchmarks" ] diff --git a/crates/kilt-dip-primitives/src/lib.rs b/crates/kilt-dip-primitives/src/lib.rs index 30623e96ca..6e87f2ab2c 100644 --- a/crates/kilt-dip-primitives/src/lib.rs +++ b/crates/kilt-dip-primitives/src/lib.rs @@ -27,7 +27,7 @@ #![cfg_attr(not(feature = "std"), no_std)] /// Module to deal with cross-chain Merkle proof as generated by the KILT chain. -pub mod merkle; +pub mod merkle_proofs; /// Module to deal with cross-chain state proofs. pub mod state_proofs; /// Collection of traits used throughout the crate and useful for both providers @@ -38,6 +38,6 @@ pub mod utils; /// deployed both on a sibling parachain and on a parent relaychain. pub mod verifier; -pub use merkle::latest::*; +pub use merkle_proofs::latest::*; pub use traits::RelayStateRootsViaRelayStorePallet; pub use verifier::*; diff --git a/crates/kilt-dip-primitives/src/merkle/v0.rs b/crates/kilt-dip-primitives/src/merkle/v0.rs deleted file mode 100644 index a5719e4c84..0000000000 --- a/crates/kilt-dip-primitives/src/merkle/v0.rs +++ /dev/null @@ -1,1341 +0,0 @@ -// KILT Blockchain – https://botlabs.org -// Copyright (C) 2019-2023 BOTLabs GmbH - -// The KILT Blockchain is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The KILT Blockchain is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -// If you feel like getting in touch with us, you can do so at info@botlabs.org - -//! Module to deal with cross-chain Merkle proof as generated by the KILT chain. -use did::{ - did_details::{DidPublicKey, DidPublicKeyDetails}, - DidSignature, DidVerificationKeyRelationship, -}; -use frame_support::ensure; -use pallet_dip_provider::IdentityCommitmentOf; -use parity_scale_codec::{Codec, Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; -use sp_core::{ConstU32, U256}; -use sp_runtime::{ - generic::Header, - traits::{AtLeast32BitUnsigned, Hash, Header as HeaderT, MaybeDisplay, Member}, - BoundedVec, SaturatedConversion, -}; -use sp_std::{fmt::Debug, vec::Vec}; -use sp_trie::{verify_trie_proof, LayoutV1}; - -use crate::{ - state_proofs::{verify_storage_value_proof, verify_storage_value_proof_with_decoder, MerkleProofError}, - traits::{BenchmarkDefault, GetWithArg}, - utils::{ - calculate_dip_identity_commitment_storage_key_for_runtime, calculate_parachain_head_storage_key, - BoundedBlindedValue, OutputOf, - }, -}; - -/// The state proof for a parachain head. -/// -/// The generic types indicate the following: -/// * `RelayBlockNumber`: The `BlockNumber` definition of the relaychain. -#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo)] -pub struct ProviderHeadStateProof { - pub(crate) relay_block_number: RelayBlockNumber, - pub(crate) proof: BoundedBlindedValue, -} - -#[cfg(feature = "runtime-benchmarks")] -impl kilt_support::traits::GetWorstCase for ProviderHeadStateProof -where - RelayBlockNumber: Default, -{ - fn worst_case(context: Context) -> Self { - Self { - relay_block_number: RelayBlockNumber::default(), - proof: BoundedBlindedValue::worst_case(context), - } - } -} - -/// The state proof for a DIP commitment. -#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo)] -pub struct DipCommitmentStateProof(pub(crate) BoundedBlindedValue); - -#[cfg(feature = "runtime-benchmarks")] -impl kilt_support::traits::GetWorstCase for DipCommitmentStateProof { - fn worst_case(context: Context) -> Self { - Self(BoundedBlindedValue::worst_case(context)) - } -} - -/// The Merkle proof for a KILT DID. -/// -/// The generic types indicate the following: -/// * `ProviderDidKeyId`: The DID key ID type configured by the provider. -/// * `ProviderAccountId`: The `AccountId` type configured by the provider. -/// * `ProviderBlockNumber`: The `BlockNumber` type configured by the provider. -/// * `ProviderWeb3Name`: The web3name type configured by the provider. -/// * `ProviderLinkableAccountId`: The linkable account ID type configured by -/// the provider. -#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo)] -pub struct DidMerkleProof< - ProviderDidKeyId, - ProviderAccountId, - ProviderBlockNumber, - ProviderWeb3Name, - ProviderLinkableAccountId, -> { - pub(crate) blinded: BoundedBlindedValue, - pub(crate) revealed: Vec< - RevealedDidMerkleProofLeaf< - ProviderDidKeyId, - ProviderAccountId, - ProviderBlockNumber, - ProviderWeb3Name, - ProviderLinkableAccountId, - >, - >, -} - -impl - DidMerkleProof -{ - pub fn new( - blinded: BoundedBlindedValue, - revealed: Vec< - RevealedDidMerkleProofLeaf< - ProviderDidKeyId, - ProviderAccountId, - ProviderBlockNumber, - ProviderWeb3Name, - ProviderLinkableAccountId, - >, - >, - ) -> Self { - Self { blinded, revealed } - } -} - -#[cfg(feature = "runtime-benchmarks")] -impl< - ProviderDidKeyId, - ProviderAccountId, - ProviderBlockNumber, - ProviderWeb3Name, - ProviderLinkableAccountId, - Context, - > kilt_support::traits::GetWorstCase - for DidMerkleProof< - ProviderDidKeyId, - ProviderAccountId, - ProviderBlockNumber, - ProviderWeb3Name, - ProviderLinkableAccountId, - > where - ProviderDidKeyId: Default + Clone, - ProviderAccountId: Clone, - ProviderBlockNumber: Default + Clone, - ProviderWeb3Name: Clone, - ProviderLinkableAccountId: Clone, -{ - fn worst_case(context: Context) -> Self { - Self { - blinded: BoundedBlindedValue::worst_case(context), - revealed: sp_std::vec![RevealedDidMerkleProofLeaf::default(); 64], - } - } -} - -/// A DID signature anchored to a specific block height. -/// -/// The generic types indicate the following: -/// * `BlockNumber`: The `BlockNumber` definition of the chain consuming (i.e., -/// validating) this signature. -#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo)] -pub struct TimeBoundDidSignature { - /// The signature. - pub(crate) signature: DidSignature, - /// The block number until the signature is to be considered valid. - pub(crate) valid_until: BlockNumber, -} - -impl TimeBoundDidSignature { - pub fn new(signature: DidSignature, valid_until: BlockNumber) -> Self { - Self { signature, valid_until } - } -} - -#[cfg(feature = "runtime-benchmarks")] -impl kilt_support::traits::GetWorstCase for TimeBoundDidSignature -where - DidSignature: kilt_support::traits::GetWorstCase, - BlockNumber: Default, -{ - fn worst_case(context: Context) -> Self { - Self { - signature: DidSignature::worst_case(context), - valid_until: BlockNumber::default(), - } - } -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] -pub enum Error { - InvalidRelayHeader, - RelayBlockNotFound, - RelayStateRootNotFound, - InvalidDidMerkleProof, - TooManyLeavesRevealed, - InvalidSignatureTime, - InvalidDidKeyRevealed, - ParaHeadMerkleProof(MerkleProofError), - DipCommitmentMerkleProof(MerkleProofError), - Internal, -} - -impl From for u8 { - fn from(value: Error) -> Self { - match value { - // DO NOT USE 0 - // Errors of different sub-parts are separated by a `u8::MAX`. - // A value of 0 would make it confusing whether it's the previous sub-part error (u8::MAX) - // or the new sub-part error (u8::MAX + 0). - Error::InvalidRelayHeader => 1, - Error::RelayBlockNotFound => 2, - Error::RelayStateRootNotFound => 3, - Error::InvalidDidMerkleProof => 4, - Error::TooManyLeavesRevealed => 5, - Error::InvalidSignatureTime => 6, - Error::InvalidDidKeyRevealed => 7, - Error::ParaHeadMerkleProof(error) => match error { - MerkleProofError::InvalidProof => 11, - MerkleProofError::RequiredLeafNotRevealed => 12, - MerkleProofError::ResultDecoding => 13, - }, - Error::DipCommitmentMerkleProof(error) => match error { - MerkleProofError::InvalidProof => 21, - MerkleProofError::RequiredLeafNotRevealed => 22, - MerkleProofError::ResultDecoding => 23, - }, - Error::Internal => u8::MAX, - } - } -} - -/// A DIP proof submitted to a relaychain consumer. -/// -/// The generic types indicate the following: -/// * `RelayBlockNumber`: The `BlockNumber` definition of the relaychain. -/// * `RelayHasher`: The hashing algorithm used by the relaychain. -/// * `KiltDidKeyId`: The DID key ID type configured by the KILT chain. -/// * `KiltAccountId`: The `AccountId` type configured by the KILT chain. -/// * `KiltBlockNumber`: The `BlockNumber` type configured by the KILT chain. -/// * `KiltWeb3Name`: The web3name type configured by the KILT chain. -/// * `KiltLinkableAccountId`: The linkable account ID type configured by the -/// KILT chain. -#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo)] -pub struct RelayDipDidProof< - RelayBlockNumber: Copy + Into + TryFrom, - RelayHasher: Hash, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, -> { - /// The relaychain header for the relaychain block specified in the - /// `provider_head_proof`. - pub(crate) relay_header: Header, - /// The state proof for the given parachain head. - pub(crate) provider_head_proof: ProviderHeadStateProof, - /// The raw state proof for the DIP commitment of the given subject. - pub(crate) dip_commitment_proof: DipCommitmentStateProof, - /// The Merkle proof of the subject's DID details. - pub(crate) dip_proof: - DidMerkleProof, - /// The cross-chain DID signature. - pub(crate) signature: TimeBoundDidSignature, -} - -impl< - RelayBlockNumber: Member + sp_std::hash::Hash + Copy + MaybeDisplay + AtLeast32BitUnsigned + Codec + Into + TryFrom, - RelayHasher: Hash, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - > - RelayDipDidProof< - RelayBlockNumber, - RelayHasher, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - > -{ - /// Verifies the relaychain part of the state proof using the provided block - /// hash. - #[allow(clippy::type_complexity)] - pub fn verify_relay_header_with_block_hash( - self, - block_hash: &OutputOf, - ) -> Result< - RelayDipDidProofWithVerifiedRelayStateRoot< - OutputOf, - RelayBlockNumber, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - >, - Error, - > { - if block_hash != &self.relay_header.hash() { - return Err(Error::InvalidRelayHeader); - } - - Ok(RelayDipDidProofWithVerifiedRelayStateRoot { - relay_state_root: self.relay_header.state_root, - provider_head_proof: self.provider_head_proof, - dip_commitment_proof: self.dip_commitment_proof, - dip_proof: self.dip_proof, - signature: self.signature, - }) - } - - /// Verifies the relaychain part of the state proof using the block hash - /// returned by the provided implementation. - /// - /// The generic types indicate the following: - /// * `RelayHashStore`: The type that returns a relaychain block hash given - /// a relaychain block number. - #[allow(clippy::type_complexity)] - pub fn verify_relay_header( - self, - ) -> Result< - RelayDipDidProofWithVerifiedRelayStateRoot< - OutputOf, - RelayBlockNumber, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - >, - Error, - > - where - RelayHashStore: GetWithArg>>, - { - let relay_block_hash = RelayHashStore::get(&self.relay_header.number).ok_or(Error::RelayBlockNotFound)?; - self.verify_relay_header_with_block_hash(&relay_block_hash) - } -} - -/// A DIP proof submitted to a relaychain consumer that has had the proof header -/// verified against a given block hash. -/// -/// The generic types indicate the following: -/// * `StateRoot`: The type of the state root used by the relaychain. -/// * `RelayBlockNumber`: The `BlockNumber` definition of the relaychain. -/// * `KiltDidKeyId`: The DID key ID type configured by the KILT chain. -/// * `KiltAccountId`: The `AccountId` type configured by the KILT chain. -/// * `KiltBlockNumber`: The `BlockNumber` type configured by the KILT chain. -/// * `KiltWeb3Name`: The web3name type configured by the KILT chain. -/// * `KiltLinkableAccountId`: The linkable account ID type configured by the -/// KILT chain. -#[derive(Debug)] -pub struct RelayDipDidProofWithVerifiedRelayStateRoot< - StateRoot, - RelayBlockNumber, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, -> { - /// The verified state root for the relaychain at the block specified in the - /// proof. - pub(crate) relay_state_root: StateRoot, - /// The state proof for the given parachain head. - pub(crate) provider_head_proof: ProviderHeadStateProof, - /// The raw state proof for the DIP commitment of the given subject. - pub(crate) dip_commitment_proof: DipCommitmentStateProof, - /// The Merkle proof of the subject's DID details. - pub(crate) dip_proof: - DidMerkleProof, - /// The cross-chain DID signature. - pub(crate) signature: TimeBoundDidSignature, -} - -impl< - StateRoot, - RelayBlockNumber, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - > - RelayDipDidProofWithVerifiedRelayStateRoot< - StateRoot, - RelayBlockNumber, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - > where - KiltBlockNumber: BenchmarkDefault, -{ - /// Verifies the head data of the state proof for the provider with the - /// given para ID. - /// - /// The generic types indicate the following: - /// * `RelayHasher`: The head data hashing algorithm used by the relaychain. - /// * `ProviderHeader`: The type of the parachain header to be revealed in - /// the state proof. - #[allow(clippy::type_complexity)] - pub fn verify_provider_head_proof( - self, - provider_para_id: u32, - ) -> Result< - DipDidProofWithVerifiedRelayStateRoot< - OutputOf, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - RelayBlockNumber, - >, - Error, - > - where - RelayHasher: Hash, - ProviderHeader: Decode + HeaderT, Number = KiltBlockNumber>, - { - let parachain_dip_proof = ParachainDipDidProof { - provider_head_proof: self.provider_head_proof, - dip_commitment_proof: self.dip_commitment_proof, - dip_proof: self.dip_proof, - signature: self.signature, - }; - - parachain_dip_proof.verify_provider_head_proof_with_state_root::( - provider_para_id, - &self.relay_state_root, - ) - } -} - -/// A DIP proof submitted to a parachain consumer. -/// -/// The generic types indicate the following: -/// * `RelayBlockNumber`: The `BlockNumber` definition of the relaychain. -/// * `KiltDidKeyId`: The DID key ID type configured by the KILT chain. -/// * `KiltAccountId`: The `AccountId` type configured by the KILT chain. -/// * `KiltBlockNumber`: The `BlockNumber` type configured by the KILT chain. -/// * `KiltWeb3Name`: The web3name type configured by the KILT chain. -/// * `KiltLinkableAccountId`: The linkable account ID type configured by the -/// KILT chain. -/// * `ConsumerBlockNumber`: The `BlockNumber` definition of the consumer -/// parachain. -#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo)] -pub struct ParachainDipDidProof< - RelayBlockNumber, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, -> { - /// The state proof for the given parachain head. - pub(crate) provider_head_proof: ProviderHeadStateProof, - /// The raw state proof for the DIP commitment of the given subject. - pub(crate) dip_commitment_proof: DipCommitmentStateProof, - /// The Merkle proof of the subject's DID details. - pub(crate) dip_proof: - DidMerkleProof, - /// The cross-chain DID signature. - pub(crate) signature: TimeBoundDidSignature, -} - -#[cfg(feature = "runtime-benchmarks")] -impl< - RelayBlockNumber, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - Context, - > kilt_support::traits::GetWorstCase - for ParachainDipDidProof< - RelayBlockNumber, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - > where - RelayBlockNumber: Default, - KiltDidKeyId: Default + Clone, - KiltAccountId: Clone, - KiltBlockNumber: Default + Clone, - KiltWeb3Name: Clone, - KiltLinkableAccountId: Clone, - ConsumerBlockNumber: Default, - Context: Clone, -{ - fn worst_case(context: Context) -> Self { - Self { - provider_head_proof: ProviderHeadStateProof::worst_case(context.clone()), - dip_commitment_proof: DipCommitmentStateProof::worst_case(context.clone()), - dip_proof: DidMerkleProof::worst_case(context.clone()), - signature: TimeBoundDidSignature::worst_case(context), - } - } -} - -impl< - RelayBlockNumber, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - > - ParachainDipDidProof< - RelayBlockNumber, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - > where - KiltBlockNumber: BenchmarkDefault, -{ - /// Verifies the head data of the state proof for the provider with the - /// given para ID and relaychain state root. - /// - /// The generic types indicate the following: - /// * `RelayHasher`: The head data hashing algorithm used by the relaychain. - /// * `ProviderHeader`: The type of the parachain header to be revealed in - /// the state proof. - #[allow(clippy::type_complexity)] - pub fn verify_provider_head_proof_with_state_root( - self, - provider_para_id: u32, - relay_state_root: &OutputOf, - ) -> Result< - DipDidProofWithVerifiedRelayStateRoot< - OutputOf, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - >, - Error, - > - where - RelayHasher: Hash, - ProviderHeader: Decode + HeaderT, Number = KiltBlockNumber>, - { - let provider_head_storage_key = calculate_parachain_head_storage_key(provider_para_id); - // TODO: Figure out why RPC call returns 2 bytes in front which we don't need - let provider_header_result = verify_storage_value_proof_with_decoder::<_, RelayHasher, ProviderHeader>( - &provider_head_storage_key, - *relay_state_root, - self.provider_head_proof.proof, - |input| { - if input.len() < 2 { - return None; - } - let mut trimmed_input = &input[2..]; - ProviderHeader::decode(&mut trimmed_input).ok() - }, - ); - cfg_if::cfg_if! { - if #[cfg(feature = "runtime-benchmarks")] { - let provider_header = provider_header_result.unwrap_or_else(|_| ProviderHeader::new(::Number::default(), ::Hash::default(), ::Hash::default(), ::Hash::default(), sp_runtime::Digest::default())); - } else { - let provider_header = provider_header_result.map_err(Error::ParaHeadMerkleProof)?; - } - } - Ok(DipDidProofWithVerifiedRelayStateRoot { - state_root: *provider_header.state_root(), - dip_commitment_proof: self.dip_commitment_proof, - dip_proof: self.dip_proof, - signature: self.signature, - }) - } - - /// Verifies the head data of the state proof for the provider with the - /// given para ID using the state root returned by the provided - /// implementation. - /// - /// The generic types indicate the following: - /// * `RelayHasher`: The hashing algorithm used on the relaychain to - /// generate the parachains head data. - /// * `StateRootStore`: The type that returns a relaychain state root given - /// a relaychain block number. - /// * `ProviderHeader`: The type of the parachain header to be revealed in - /// the state proof. - #[allow(clippy::type_complexity)] - pub fn verify_provider_head_proof( - self, - provider_para_id: u32, - ) -> Result< - DipDidProofWithVerifiedRelayStateRoot< - OutputOf, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - >, - Error, - > - where - RelayHasher: Hash, - StateRootStore: GetWithArg>>, - ProviderHeader: Decode + HeaderT, Number = KiltBlockNumber>, - { - let relay_state_root = StateRootStore::get(&self.provider_head_proof.relay_block_number); - cfg_if::cfg_if! { - if #[cfg(feature = "runtime-benchmarks")] { - let relay_state_root = relay_state_root.unwrap_or_default(); - } else { - let relay_state_root = relay_state_root.ok_or(Error::RelayStateRootNotFound)?; - } - } - self.verify_provider_head_proof_with_state_root::( - provider_para_id, - &relay_state_root, - ) - } -} - -/// A DIP proof that has had the proof header and the relaychain state verified -/// for the provided relaychain block number. -/// -/// The generic types indicate the following: -/// * `StateRoot`: The type of the relaychain state root. -/// * `KiltDidKeyId`: The DID key ID type configured by the KILT chain. -/// * `KiltAccountId`: The `AccountId` type configured by the KILT chain. -/// * `KiltBlockNumber`: The `BlockNumber` type configured by the KILT chain. -/// * `KiltWeb3Name`: The web3name type configured by the KILT chain. -/// * `KiltLinkableAccountId`: The linkable account ID type configured by the -/// KILT chain. -/// * `ConsumerBlockNumber`: The `BlockNumber` definition of the consumer -/// parachain. -#[derive(Debug)] -pub struct DipDidProofWithVerifiedRelayStateRoot< - StateRoot, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, -> { - /// The relaychain state root for the block specified in the DIP proof. - pub(crate) state_root: StateRoot, - /// The raw state proof for the DIP commitment of the given subject. - pub(crate) dip_commitment_proof: DipCommitmentStateProof, - /// The Merkle proof of the subject's DID details. - pub(crate) dip_proof: - DidMerkleProof, - /// The cross-chain DID signature. - pub(crate) signature: TimeBoundDidSignature, -} - -impl< - StateRoot, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - > - DipDidProofWithVerifiedRelayStateRoot< - StateRoot, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - > -{ - /// Verifies the DIP commitment part of the state proof for the subject with - /// the given identifier. - /// - /// The generic types indicate the following: - /// * `ParachainHasher`: The hashing algorithm used to hash storage on the - /// parachain. - /// * `ProviderRuntime`: The provider runtime definition. - #[allow(clippy::type_complexity)] - pub fn verify_dip_commitment_proof_for_subject( - self, - subject: &ProviderRuntime::Identifier, - ) -> Result< - DipDidProofWithVerifiedSubjectCommitment< - IdentityCommitmentOf, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - >, - Error, - > - where - StateRoot: Ord, - ParachainHasher: Hash, - ProviderRuntime: pallet_dip_provider::Config, - IdentityCommitmentOf: BenchmarkDefault, - { - let dip_commitment_storage_key = - calculate_dip_identity_commitment_storage_key_for_runtime::(subject, 0); - let dip_commitment_result = - verify_storage_value_proof::<_, ParachainHasher, IdentityCommitmentOf>( - &dip_commitment_storage_key, - self.state_root, - self.dip_commitment_proof.0, - ); - cfg_if::cfg_if! { - if #[cfg(feature = "runtime-benchmarks")] { - let dip_commitment = dip_commitment_result.unwrap_or_default(); - } else { - let dip_commitment = dip_commitment_result.map_err(Error::DipCommitmentMerkleProof)?; - } - } - Ok(DipDidProofWithVerifiedSubjectCommitment { - dip_commitment, - dip_proof: self.dip_proof, - signature: self.signature, - }) - } -} - -/// A DIP proof that has had the relaychain state and the DIP commitment -/// verified for the provided relaychain block number. -/// -/// The generic types indicate the following: -/// * `Commitment`: The DIP identity commitment type configured by the KILT -/// chain. -/// * `KiltDidKeyId`: The DID key ID type configured by the KILT chain. -/// * `KiltAccountId`: The `AccountId` type configured by the KILT chain. -/// * `KiltBlockNumber`: The `BlockNumber` type configured by the KILT chain. -/// * `KiltWeb3Name`: The web3name type configured by the KILT chain. -/// * `KiltLinkableAccountId`: The linkable account ID type configured by the -/// KILT chain. -/// * `ConsumerBlockNumber`: The `BlockNumber` definition of the consumer -/// parachain. -#[derive(Debug)] -pub struct DipDidProofWithVerifiedSubjectCommitment< - Commitment, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, -> { - /// The verified DIP identity commitment. - pub(crate) dip_commitment: Commitment, - /// The Merkle proof of the subject's DID details. - pub(crate) dip_proof: - DidMerkleProof, - /// The cross-chain DID signature. - pub(crate) signature: TimeBoundDidSignature, -} - -impl< - Commitment, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - > - DipDidProofWithVerifiedSubjectCommitment< - Commitment, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - > -{ - pub fn new( - dip_commitment: Commitment, - dip_proof: DidMerkleProof, - signature: TimeBoundDidSignature, - ) -> Self { - Self { - dip_commitment, - dip_proof, - signature, - } - } -} - -impl< - Commitment, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - > - DipDidProofWithVerifiedSubjectCommitment< - Commitment, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - > where - KiltDidKeyId: Encode, - KiltAccountId: Encode, - KiltBlockNumber: Encode, - KiltWeb3Name: Encode, - KiltLinkableAccountId: Encode, -{ - /// Verifies the Merkle proof of the subject's DID details. - /// - /// The generic types indicate the following: - /// * `DidMerkleHasher`: The hashing algorithm used to merkleize the DID - /// details. - /// * `MAX_REVEALED_LEAVES_COUNT`: The maximum number of leaves revealable - /// in the proof. - pub fn verify_dip_proof( - self, - ) -> Result< - DipRevealedDetailsAndUnverifiedDidSignature< - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - MAX_REVEALED_LEAVES_COUNT, - >, - Error, - > - where - DidMerkleHasher: Hash, - { - ensure!( - self.dip_proof.revealed.len() <= MAX_REVEALED_LEAVES_COUNT.saturated_into(), - Error::TooManyLeavesRevealed - ); - - let proof_leaves_key_value_pairs = self - .dip_proof - .revealed - .iter() - .map(|revealed_leaf| (revealed_leaf.encoded_key(), Some(revealed_leaf.encoded_value()))) - .collect::>(); - let proof_verification_result = verify_trie_proof::, _, _, _>( - &self.dip_commitment, - self.dip_proof.blinded.as_slice(), - proof_leaves_key_value_pairs.as_slice(), - ); - - cfg_if::cfg_if! { - if #[cfg(feature = "runtime-benchmarks")] { - drop(proof_verification_result); - } else { - proof_verification_result.map_err(|_| Error::InvalidDidMerkleProof)?; - } - } - let revealed_leaves = BoundedVec::try_from(self.dip_proof.revealed).map_err(|_| { - log::error!("Should not fail to construct BoundedVec since bounds were checked before."); - Error::Internal - })?; - - Ok(DipRevealedDetailsAndUnverifiedDidSignature { - revealed_leaves, - signature: self.signature, - }) - } -} - -/// A DIP proof whose information has been verified but that contains a -/// cross-chain [`TimeBoundDidSignature`] that still needs verification. -/// -/// The generic types indicate the following: -/// * `KiltDidKeyId`: The DID key ID type configured by the KILT chain. -/// * `KiltAccountId`: The `AccountId` type configured by the KILT chain. -/// * `KiltBlockNumber`: The `BlockNumber` type configured by the KILT chain. -/// * `KiltWeb3Name`: The web3name type configured by the KILT chain. -/// * `KiltLinkableAccountId`: The linkable account ID type configured by the -/// KILT chain. -/// * `ConsumerBlockNumber`: The `BlockNumber` definition of the consumer -/// parachain. -/// * `MAX_REVEALED_LEAVES_COUNT`: The maximum number of leaves revealable in -/// the proof. -#[derive(Debug)] -pub struct DipRevealedDetailsAndUnverifiedDidSignature< - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - const MAX_REVEALED_LEAVES_COUNT: u32, -> { - /// The parts of the subject's DID details revealed in the DIP proof. - pub(crate) revealed_leaves: BoundedVec< - RevealedDidMerkleProofLeaf, - ConstU32, - >, - /// The cross-chain DID signature. - pub(crate) signature: TimeBoundDidSignature, -} - -impl< - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - const MAX_REVEALED_LEAVES_COUNT: u32, - > - DipRevealedDetailsAndUnverifiedDidSignature< - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - MAX_REVEALED_LEAVES_COUNT, - > where - ConsumerBlockNumber: PartialOrd, -{ - /// Verifies that the DIP proof signature is anchored to a block that has - /// not passed on the consumer chain. - pub fn verify_signature_time( - self, - block_number: &ConsumerBlockNumber, - ) -> Result< - DipRevealedDetailsAndVerifiedDidSignatureFreshness< - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - MAX_REVEALED_LEAVES_COUNT, - >, - Error, - > { - cfg_if::cfg_if! { - if #[cfg(feature = "runtime-benchmarks")] { - let _ = self.signature.valid_until >= *block_number; - } else { - frame_support::ensure!(self.signature.valid_until >= *block_number, Error::InvalidSignatureTime); - } - } - Ok(DipRevealedDetailsAndVerifiedDidSignatureFreshness { - revealed_leaves: self.revealed_leaves, - signature: self.signature.signature, - }) - } -} - -/// A DIP proof whose information has been verified and whose signature has been -/// verified not to be expired, but that yet does not contain information as to -/// which of the revealed keys has generated the signature. -/// -/// The generic types indicate the following: -/// * `KiltDidKeyId`: The DID key ID type configured by the KILT chain. -/// * `KiltAccountId`: The `AccountId` type configured by the KILT chain. -/// * `KiltBlockNumber`: The `BlockNumber` type configured by the KILT chain. -/// * `KiltWeb3Name`: The web3name type configured by the KILT chain. -/// * `KiltLinkableAccountId`: The linkable account ID type configured by the -/// KILT chain. -/// * `MAX_REVEALED_LEAVES_COUNT`: The maximum number of leaves revealable in -/// the proof. -#[derive(Debug)] -pub struct DipRevealedDetailsAndVerifiedDidSignatureFreshness< - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - const MAX_REVEALED_LEAVES_COUNT: u32, -> { - /// The parts of the subject's DID details revealed in the DIP proof. - pub(crate) revealed_leaves: BoundedVec< - RevealedDidMerkleProofLeaf, - ConstU32, - >, - /// The cross-chain DID signature without time information. - pub(crate) signature: DidSignature, -} - -impl< - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - const MAX_REVEALED_LEAVES_COUNT: u32, - > - DipRevealedDetailsAndVerifiedDidSignatureFreshness< - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - MAX_REVEALED_LEAVES_COUNT, - > where - KiltDidKeyId: BenchmarkDefault, - KiltBlockNumber: BenchmarkDefault, -{ - /// Iterates over the revealed DID leafs to find the one that generated a - /// valid signature for the provided payload. - pub fn retrieve_signing_leaf_for_payload( - self, - payload: &[u8], - ) -> Result< - DipOriginInfo< - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - MAX_REVEALED_LEAVES_COUNT, - >, - Error, - > { - let revealed_verification_keys = self.revealed_leaves.iter().filter(|leaf| { - matches!( - leaf, - RevealedDidMerkleProofLeaf::DidKey(RevealedDidKey { - relationship: DidKeyRelationship::Verification(_), - .. - }) - ) - }); - let maybe_signing_key_index = revealed_verification_keys - .enumerate() - .find(|(_, revealed_verification_key)| { - let RevealedDidMerkleProofLeaf::DidKey(RevealedDidKey { - details: - DidPublicKeyDetails { - key: DidPublicKey::PublicVerificationKey(verification_key), - .. - }, - .. - }) = revealed_verification_key - else { - return false; - }; - verification_key.verify_signature(payload, &self.signature).is_ok() - }) - .map(|(index, _)| u32::saturated_from(index)); - - let signing_key_entry = if let Some(index) = maybe_signing_key_index { - (self.revealed_leaves, index) - } else { - cfg_if::cfg_if! { - if #[cfg(feature = "runtime-benchmarks")] { - return Ok(DipOriginInfo::default()); - } else { - return Err(Error::InvalidDidKeyRevealed); - } - } - }; - - Ok(DipOriginInfo { - revealed_leaves: signing_key_entry.0, - signing_leaf_index: signing_key_entry.1, - }) - } -} - -/// Information, available as an origin, after the whole DIP proof has been -/// verified. -#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct DipOriginInfo< - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - const MAX_REVEALED_LEAVES_COUNT: u32, -> { - /// The parts of the subject's DID details revealed in the DIP proof. - revealed_leaves: BoundedVec< - RevealedDidMerkleProofLeaf, - ConstU32, - >, - /// The index of the signing leaf from the vector above, - signing_leaf_index: u32, -} - -impl< - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - const MAX_REVEALED_LEAVES_COUNT: u32, - > - DipOriginInfo< - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - MAX_REVEALED_LEAVES_COUNT, - > -{ - /// Returns an iterator over the revealed DID leaves. - pub fn iter_leaves( - &self, - ) -> impl Iterator< - Item = &RevealedDidMerkleProofLeaf< - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - >, - > { - self.revealed_leaves.iter() - } - - /// Returns a reference to the leaf that signed the cross-chain operation. - /// This operation should never fail, so the only error it returns is an - /// `Error::Internal` which, anyway, should never happen. - pub fn get_signing_leaf(&self) -> Result<&RevealedDidKey, Error> { - let leaf = &self - .revealed_leaves - .get(usize::saturated_from(self.signing_leaf_index)) - .ok_or_else(|| { - log::error!("Should never fail to retrieve the signing leaf."); - Error::Internal - })?; - let RevealedDidMerkleProofLeaf::DidKey(did_key) = leaf else { - log::error!("Should never fail to convert the signing leaf to a DID Key leaf."); - return Err(Error::Internal); - }; - Ok(did_key) - } -} - -#[cfg(feature = "runtime-benchmarks")] -impl< - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - const MAX_REVEALED_LEAVES_COUNT: u32, - > Default - for DipOriginInfo< - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - MAX_REVEALED_LEAVES_COUNT, - > where - KiltDidKeyId: BenchmarkDefault, - KiltBlockNumber: BenchmarkDefault, -{ - fn default() -> Self { - let default_keys = sp_std::vec![RevealedDidKey { - id: KiltDidKeyId::default(), - details: DidPublicKeyDetails { - key: did::did_details::DidVerificationKey::Ed25519(sp_core::ed25519::Public::from_raw([0u8; 32])) - .into(), - block_number: KiltBlockNumber::default() - }, - relationship: DidVerificationKeyRelationship::Authentication.into() - } - .into()]; - let bounded_keys = default_keys - .try_into() - // To avoid requiring types to implement `Debug`. - .map_err(|_| "Should not fail to convert single element to a BoundedVec.") - .unwrap(); - Self { - revealed_leaves: bounded_keys, - signing_leaf_index: 0u32, - } - } -} - -/// Relationship of a key to a DID Document. -#[derive(Clone, Copy, Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, TypeInfo, MaxEncodedLen)] -pub enum DidKeyRelationship { - Encryption, - Verification(DidVerificationKeyRelationship), -} - -impl From for DidKeyRelationship { - fn from(value: DidVerificationKeyRelationship) -> Self { - Self::Verification(value) - } -} - -impl TryFrom for DidVerificationKeyRelationship { - type Error = (); - - fn try_from(value: DidKeyRelationship) -> Result { - if let DidKeyRelationship::Verification(rel) = value { - Ok(rel) - } else { - Err(()) - } - } -} - -/// All possible Merkle leaf types that can be revealed as part of a DIP -/// identity Merkle proof. -#[derive(Clone, Copy, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub enum RevealedDidMerkleProofLeaf { - DidKey(RevealedDidKey), - Web3Name(RevealedWeb3Name), - LinkedAccount(RevealedAccountId), -} - -impl From> - for RevealedDidMerkleProofLeaf -{ - fn from(value: RevealedDidKey) -> Self { - Self::DidKey(value) - } -} - -impl From> - for RevealedDidMerkleProofLeaf -{ - fn from(value: RevealedWeb3Name) -> Self { - Self::Web3Name(value) - } -} - -impl From> - for RevealedDidMerkleProofLeaf -{ - fn from(value: RevealedAccountId) -> Self { - Self::LinkedAccount(value) - } -} - -#[cfg(feature = "runtime-benchmarks")] -impl Default - for RevealedDidMerkleProofLeaf -where - KeyId: Default, - BlockNumber: Default, -{ - fn default() -> Self { - RevealedDidKey { - id: KeyId::default(), - relationship: DidVerificationKeyRelationship::Authentication.into(), - details: DidPublicKeyDetails { - key: did::did_details::DidVerificationKey::Ed25519(sp_core::ed25519::Public::from_raw([0u8; 32])) - .into(), - block_number: BlockNumber::default(), - }, - } - .into() - } -} - -impl - RevealedDidMerkleProofLeaf -where - KeyId: Encode, - Web3Name: Encode, - LinkedAccountId: Encode, -{ - pub fn encoded_key(&self) -> Vec { - match self { - RevealedDidMerkleProofLeaf::DidKey(RevealedDidKey { id, relationship, .. }) => (id, relationship).encode(), - RevealedDidMerkleProofLeaf::Web3Name(RevealedWeb3Name { web3_name, .. }) => web3_name.encode(), - RevealedDidMerkleProofLeaf::LinkedAccount(RevealedAccountId(account_id)) => account_id.encode(), - } - } -} - -impl - RevealedDidMerkleProofLeaf -where - AccountId: Encode, - BlockNumber: Encode, -{ - pub fn encoded_value(&self) -> Vec { - match self { - RevealedDidMerkleProofLeaf::DidKey(RevealedDidKey { details, .. }) => details.encode(), - RevealedDidMerkleProofLeaf::Web3Name(RevealedWeb3Name { claimed_at, .. }) => claimed_at.encode(), - RevealedDidMerkleProofLeaf::LinkedAccount(_) => ().encode(), - } - } -} - -/// The details of a DID key after it has been successfully verified in a Merkle -/// proof. -#[derive(Clone, Copy, Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, TypeInfo, MaxEncodedLen)] -pub struct RevealedDidKey { - /// The key ID, according to the provider's definition. - pub id: KeyId, - /// The key relationship to the subject's DID Document. - pub relationship: DidKeyRelationship, - /// The details of the DID Key, including its creation block number on the - /// provider chain. - pub details: DidPublicKeyDetails, -} - -/// The details of a web3name after it has been successfully verified in a -/// Merkle proof. -#[derive(Clone, Copy, Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, TypeInfo, MaxEncodedLen)] -pub struct RevealedWeb3Name { - /// The web3name. - pub web3_name: Web3Name, - /// The block number on the provider chain in which it was linked to the DID - /// subject. - pub claimed_at: BlockNumber, -} - -/// The details of an account after it has been successfully verified in a -/// Merkle proof. -#[derive(Clone, Copy, Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, TypeInfo, MaxEncodedLen)] -pub struct RevealedAccountId(pub AccountId); diff --git a/crates/kilt-dip-primitives/src/merkle/mod.rs b/crates/kilt-dip-primitives/src/merkle_proofs/mod.rs similarity index 100% rename from crates/kilt-dip-primitives/src/merkle/mod.rs rename to crates/kilt-dip-primitives/src/merkle_proofs/mod.rs diff --git a/crates/kilt-dip-primitives/src/merkle_proofs/v0/dip_subject_state/mod.rs b/crates/kilt-dip-primitives/src/merkle_proofs/v0/dip_subject_state/mod.rs new file mode 100644 index 0000000000..b525297407 --- /dev/null +++ b/crates/kilt-dip-primitives/src/merkle_proofs/v0/dip_subject_state/mod.rs @@ -0,0 +1,221 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use did::{ + did_details::{DidPublicKey, DidPublicKeyDetails}, + DidSignature, +}; +use frame_support::ensure; +use sp_core::ConstU32; +use sp_runtime::{traits::SaturatedConversion, BoundedVec}; +use sp_std::vec::Vec; + +use crate::{ + merkle_proofs::v0::{ + input_common::TimeBoundDidSignature, + output_common::{DidKeyRelationship, DipOriginInfo, RevealedDidKey, RevealedDidMerkleProofLeaf}, + }, + Error, +}; + +#[cfg(test)] +mod tests; + +/// A DIP proof whose information has been verified but that contains a +/// cross-chain [`TimeBoundDidSignature`] that still needs verification. +/// +/// The generic types indicate the following: +/// * `KiltDidKeyId`: The DID key ID type configured by the KILT chain. +/// * `KiltAccountId`: The `AccountId` type configured by the KILT chain. +/// * `KiltBlockNumber`: The `BlockNumber` type configured by the KILT chain. +/// * `KiltWeb3Name`: The web3name type configured by the KILT chain. +/// * `KiltLinkableAccountId`: The linkable account ID type configured by the +/// KILT chain. +/// * `ConsumerBlockNumber`: The `BlockNumber` definition of the consumer +/// parachain. +/// * `MAX_REVEALED_LEAVES_COUNT`: The maximum number of leaves revealable in +/// the proof. +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(test, derive(Clone))] +pub struct DipRevealedDetailsAndUnverifiedDidSignature< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + const MAX_REVEALED_LEAVES_COUNT: u32, +> { + /// The parts of the subject's DID details revealed in the DIP proof. + pub(crate) revealed_leaves: BoundedVec< + RevealedDidMerkleProofLeaf, + ConstU32, + >, + /// The cross-chain DID signature. + pub(crate) signature: TimeBoundDidSignature, +} + +impl< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + const MAX_REVEALED_LEAVES_COUNT: u32, + > + DipRevealedDetailsAndUnverifiedDidSignature< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + MAX_REVEALED_LEAVES_COUNT, + > where + ConsumerBlockNumber: PartialOrd, +{ + /// Verifies that the DIP proof signature is anchored to a block that has + /// not passed on the consumer chain. + pub fn verify_signature_time( + self, + block_number: &ConsumerBlockNumber, + ) -> Result< + DipRevealedDetailsAndVerifiedDidSignatureFreshness< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + MAX_REVEALED_LEAVES_COUNT, + >, + Error, + > { + ensure!(self.signature.valid_until >= *block_number, Error::InvalidSignatureTime); + Ok(DipRevealedDetailsAndVerifiedDidSignatureFreshness { + revealed_leaves: self.revealed_leaves, + signature: self.signature.signature, + }) + } +} + +/// A DIP proof whose information has been verified and whose signature has been +/// verified not to be expired, but that yet does not contain information as to +/// which of the revealed keys has generated the signature. +/// +/// The generic types indicate the following: +/// * `KiltDidKeyId`: The DID key ID type configured by the KILT chain. +/// * `KiltAccountId`: The `AccountId` type configured by the KILT chain. +/// * `KiltBlockNumber`: The `BlockNumber` type configured by the KILT chain. +/// * `KiltWeb3Name`: The web3name type configured by the KILT chain. +/// * `KiltLinkableAccountId`: The linkable account ID type configured by the +/// KILT chain. +/// * `MAX_REVEALED_LEAVES_COUNT`: The maximum number of leaves revealable in +/// the proof. +#[derive(Debug, PartialEq, Eq)] +pub struct DipRevealedDetailsAndVerifiedDidSignatureFreshness< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + const MAX_REVEALED_LEAVES_COUNT: u32, +> { + /// The parts of the subject's DID details revealed in the DIP proof. + pub(crate) revealed_leaves: BoundedVec< + RevealedDidMerkleProofLeaf, + ConstU32, + >, + /// The cross-chain DID signature without time information. + pub(crate) signature: DidSignature, +} + +impl< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + const MAX_REVEALED_LEAVES_COUNT: u32, + > + DipRevealedDetailsAndVerifiedDidSignatureFreshness< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + MAX_REVEALED_LEAVES_COUNT, + > +{ + /// Iterates over the revealed DID leaves to find the ones that generated a + /// valid signature for the provided payload. + pub fn retrieve_signing_leaves_for_payload( + self, + payload: &[u8], + ) -> Result< + DipOriginInfo< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + MAX_REVEALED_LEAVES_COUNT, + >, + Error, + > { + let revealed_verification_keys = self.revealed_leaves.iter().filter(|leaf| { + matches!( + leaf, + RevealedDidMerkleProofLeaf::DidKey(RevealedDidKey { + relationship: DidKeyRelationship::Verification(_), + .. + }) + ) + }); + let signing_leaves_indices: Vec<_> = revealed_verification_keys + .enumerate() + .filter(|(_, revealed_verification_key)| { + let RevealedDidMerkleProofLeaf::DidKey(RevealedDidKey { + details: + DidPublicKeyDetails { + key: DidPublicKey::PublicVerificationKey(verification_key), + .. + }, + .. + }) = revealed_verification_key + else { + return false; + }; + verification_key.verify_signature(payload, &self.signature).is_ok() + }) + .map(|(index, _)| u32::saturated_from(index)) + .collect(); + + ensure!(!signing_leaves_indices.is_empty(), Error::InvalidDidKeyRevealed); + + let signing_leaves_indices_vector = signing_leaves_indices.try_into().map_err(|_| { + log::error!("Should never fail to convert vector of signing leaf indices into BoundedVec."); + Error::Internal + })?; + + Ok(DipOriginInfo { + revealed_leaves: self.revealed_leaves, + signing_leaves_indices: signing_leaves_indices_vector, + }) + } +} diff --git a/crates/kilt-dip-primitives/src/merkle_proofs/v0/dip_subject_state/tests.rs b/crates/kilt-dip-primitives/src/merkle_proofs/v0/dip_subject_state/tests.rs new file mode 100644 index 0000000000..49f2a8048f --- /dev/null +++ b/crates/kilt-dip-primitives/src/merkle_proofs/v0/dip_subject_state/tests.rs @@ -0,0 +1,202 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +mod dip_revealed_details_and_unverified_did_signature { + use frame_support::{assert_err, assert_ok}; + + use crate::{DipRevealedDetailsAndUnverifiedDidSignature, Error, TimeBoundDidSignature}; + + impl< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + const MAX_REVEALED_LEAVES_COUNT: u32, + > + DipRevealedDetailsAndUnverifiedDidSignature< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + MAX_REVEALED_LEAVES_COUNT, + > where + KiltDidKeyId: Default, + KiltBlockNumber: Default, + ConsumerBlockNumber: Default, + { + fn with_signature_time(valid_until: ConsumerBlockNumber) -> Self { + Self { + signature: TimeBoundDidSignature { + valid_until, + ..Default::default() + }, + revealed_leaves: Default::default(), + } + } + } + + #[test] + fn verify_signature_time_successful() { + let signature = + DipRevealedDetailsAndUnverifiedDidSignature::<(), (), (), (), (), _, 1>::with_signature_time(10u32); + assert_ok!(signature.clone().verify_signature_time(&0)); + assert_ok!(signature.clone().verify_signature_time(&1)); + assert_ok!(signature.clone().verify_signature_time(&9)); + assert_ok!(signature.verify_signature_time(&10)); + } + + #[test] + fn verify_signature_time_too_old() { + let signature = + DipRevealedDetailsAndUnverifiedDidSignature::<(), (), (), (), (), _, 1>::with_signature_time(10u32); + assert_err!( + signature.clone().verify_signature_time(&11), + Error::InvalidSignatureTime + ); + assert_err!(signature.verify_signature_time(&u32::MAX), Error::InvalidSignatureTime); + } +} + +mod dip_revealed_details_and_verified_did_signature_freshness { + use did::{ + did_details::{DidPublicKeyDetails, DidVerificationKey}, + DidVerificationKeyRelationship, + }; + use frame_support::assert_err; + use parity_scale_codec::Encode; + use sp_core::{ed25519, ConstU32, Pair}; + use sp_runtime::{AccountId32, BoundedVec}; + + use crate::{ + DipOriginInfo, DipRevealedDetailsAndVerifiedDidSignatureFreshness, Error, RevealedDidKey, + RevealedDidMerkleProofLeaf, + }; + + #[test] + fn retrieve_signing_leaves_for_payload_single_leaf_successful() { + let payload = b"Hello, world!"; + let (did_key_pair, _) = ed25519::Pair::generate(); + let did_auth_key: DidVerificationKey = did_key_pair.public().into(); + let revealed_leaves: BoundedVec, ConstU32<1>> = + vec![RevealedDidKey { + id: 0u32, + relationship: DidVerificationKeyRelationship::Authentication.into(), + details: DidPublicKeyDetails { + key: did_auth_key.into(), + block_number: 0u32, + }, + } + .into()] + .try_into() + .unwrap(); + let revealed_details: DipRevealedDetailsAndVerifiedDidSignatureFreshness<_, _, _, _, _, 1> = + DipRevealedDetailsAndVerifiedDidSignatureFreshness { + revealed_leaves: revealed_leaves.clone(), + signature: did_key_pair.sign(&payload.encode()).into(), + }; + assert_eq!( + revealed_details.retrieve_signing_leaves_for_payload(&payload.encode()), + Ok(DipOriginInfo { + signing_leaves_indices: vec![0].try_into().unwrap(), + revealed_leaves, + }) + ); + } + + #[test] + fn retrieve_signing_leaves_for_payload_multiple_leaves_successful() { + let payload = b"Hello, world!"; + let (did_key_pair, _) = ed25519::Pair::generate(); + let did_auth_key: DidVerificationKey = did_key_pair.public().into(); + let revealed_leaves: BoundedVec, ConstU32<3>> = vec![ + RevealedDidKey { + id: 0u32, + relationship: DidVerificationKeyRelationship::Authentication.into(), + details: DidPublicKeyDetails { + key: did_auth_key.clone().into(), + block_number: 0u32, + }, + } + .into(), + RevealedDidKey { + id: 0u32, + relationship: DidVerificationKeyRelationship::CapabilityDelegation.into(), + details: DidPublicKeyDetails { + // This key should be filtered out from the result, since it does not verify successfully for the + // provided payload and signature. + key: DidVerificationKey::from(ed25519::Public([100; 32])).into(), + block_number: 0u32, + }, + } + .into(), + RevealedDidKey { + id: 0u32, + relationship: DidVerificationKeyRelationship::AssertionMethod.into(), + details: DidPublicKeyDetails { + key: did_auth_key.into(), + block_number: 0u32, + }, + } + .into(), + ] + .try_into() + .unwrap(); + let revealed_details: DipRevealedDetailsAndVerifiedDidSignatureFreshness<_, _, _, _, _, 3> = + DipRevealedDetailsAndVerifiedDidSignatureFreshness { + revealed_leaves: revealed_leaves.clone(), + signature: did_key_pair.sign(&payload.encode()).into(), + }; + assert_eq!( + revealed_details.retrieve_signing_leaves_for_payload(&payload.encode()), + Ok(DipOriginInfo { + signing_leaves_indices: vec![0, 2].try_into().unwrap(), + revealed_leaves, + }) + ); + } + + #[test] + fn retrieve_signing_leaves_for_payload_no_key_present() { + let did_auth_key: DidVerificationKey = ed25519::Public([0u8; 32]).into(); + let revealed_leaves: BoundedVec, ConstU32<1>> = + vec![RevealedDidKey { + id: 0u32, + relationship: DidVerificationKeyRelationship::Authentication.into(), + details: DidPublicKeyDetails { + key: did_auth_key.into(), + block_number: 0u32, + }, + } + .into()] + .try_into() + .unwrap(); + let revealed_details: DipRevealedDetailsAndVerifiedDidSignatureFreshness<_, _, _, _, _, 1> = + DipRevealedDetailsAndVerifiedDidSignatureFreshness { + revealed_leaves, + signature: ed25519::Signature([100u8; 64]).into(), + }; + assert_err!( + revealed_details.retrieve_signing_leaves_for_payload(&().encode()), + Error::InvalidDidKeyRevealed + ); + } +} diff --git a/crates/kilt-dip-primitives/src/merkle_proofs/v0/error.rs b/crates/kilt-dip-primitives/src/merkle_proofs/v0/error.rs new file mode 100644 index 0000000000..c7e120c3b6 --- /dev/null +++ b/crates/kilt-dip-primitives/src/merkle_proofs/v0/error.rs @@ -0,0 +1,89 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use scale_info::TypeInfo; + +use crate::state_proofs::MerkleProofError; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] +#[cfg_attr(test, derive(enum_iterator::Sequence))] +pub enum Error { + InvalidRelayHeader, + RelayBlockNotFound, + RelayStateRootNotFound, + InvalidDidMerkleProof, + TooManyLeavesRevealed, + InvalidSignatureTime, + InvalidDidKeyRevealed, + ParaHeadMerkleProof(MerkleProofError), + DipCommitmentMerkleProof(MerkleProofError), + Internal, +} + +impl From for u8 { + fn from(value: Error) -> Self { + match value { + // DO NOT USE 0 + // Errors of different sub-parts are separated by a `u8::MAX`. + // A value of 0 would make it confusing whether it's the previous sub-part error (u8::MAX) + // or the new sub-part error (u8::MAX + 0). + Error::InvalidRelayHeader => 1, + Error::RelayBlockNotFound => 2, + Error::RelayStateRootNotFound => 3, + Error::InvalidDidMerkleProof => 4, + Error::TooManyLeavesRevealed => 5, + Error::InvalidSignatureTime => 6, + Error::InvalidDidKeyRevealed => 7, + Error::ParaHeadMerkleProof(error) => match error { + MerkleProofError::InvalidProof => 11, + MerkleProofError::RequiredLeafNotRevealed => 12, + MerkleProofError::ResultDecoding => 13, + }, + Error::DipCommitmentMerkleProof(error) => match error { + MerkleProofError::InvalidProof => 21, + MerkleProofError::RequiredLeafNotRevealed => 22, + MerkleProofError::ResultDecoding => 23, + }, + Error::Internal => u8::MAX, + } + } +} + +#[test] +fn error_value_never_zero() { + assert!( + enum_iterator::all::().all(|e| u8::from(e) != 0), + "One of the u8 values for the error is 0, which is not allowed." + ); +} + +#[test] +fn error_value_not_duplicated() { + enum_iterator::all::().fold( + sp_std::collections::btree_set::BTreeSet::::new(), + |mut values, new_value| { + let new_encoded_value = u8::from(new_value); + assert!( + values.insert(new_encoded_value), + "Failed to add unique value {:#?} for error variant", + new_encoded_value + ); + values + }, + ); +} diff --git a/crates/kilt-dip-primitives/src/merkle_proofs/v0/input_common.rs b/crates/kilt-dip-primitives/src/merkle_proofs/v0/input_common.rs new file mode 100644 index 0000000000..f9a5d6b5b3 --- /dev/null +++ b/crates/kilt-dip-primitives/src/merkle_proofs/v0/input_common.rs @@ -0,0 +1,167 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use did::DidSignature; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_std::vec::Vec; + +use crate::merkle_proofs::v0::output_common::RevealedDidMerkleProofLeaf; + +/// The state proof for a parachain head. +/// +/// The generic types indicate the following: +/// * `RelayBlockNumber`: The `BlockNumber` definition of the relaychain. +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo)] +#[cfg_attr(test, derive(Default))] +pub struct ProviderHeadStateProof { + pub(crate) relay_block_number: RelayBlockNumber, + pub(crate) proof: Vec>, +} + +impl ProviderHeadStateProof { + pub fn new(relay_block_number: RelayBlockNumber, proof: Vec>) -> Self { + Self { + proof, + relay_block_number, + } + } +} + +/// The state proof for a DIP commitment. +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo)] +#[cfg_attr(test, derive(Default))] +pub struct DipCommitmentStateProof(pub(crate) Vec>); + +impl DipCommitmentStateProof { + pub fn new(proof: Vec>) -> Self { + Self(proof) + } +} + +/// The Merkle proof for a KILT DID. +/// +/// The generic types indicate the following: +/// * `ProviderDidKeyId`: The DID key ID type configured by the provider. +/// * `ProviderAccountId`: The `AccountId` type configured by the provider. +/// * `ProviderBlockNumber`: The `BlockNumber` type configured by the provider. +/// * `ProviderWeb3Name`: The web3name type configured by the provider. +/// * `ProviderLinkableAccountId`: The linkable account ID type configured by +/// the provider. +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo)] +pub struct DidMerkleProof< + ProviderDidKeyId, + ProviderAccountId, + ProviderBlockNumber, + ProviderWeb3Name, + ProviderLinkableAccountId, +> { + pub(crate) blinded: Vec>, + pub(crate) revealed: Vec< + RevealedDidMerkleProofLeaf< + ProviderDidKeyId, + ProviderAccountId, + ProviderBlockNumber, + ProviderWeb3Name, + ProviderLinkableAccountId, + >, + >, +} + +impl + DidMerkleProof +{ + pub fn new( + blinded: Vec>, + revealed: Vec< + RevealedDidMerkleProofLeaf< + ProviderDidKeyId, + ProviderAccountId, + ProviderBlockNumber, + ProviderWeb3Name, + ProviderLinkableAccountId, + >, + >, + ) -> Self { + Self { blinded, revealed } + } + + pub fn revealed( + &self, + ) -> &[RevealedDidMerkleProofLeaf< + ProviderDidKeyId, + ProviderAccountId, + ProviderBlockNumber, + ProviderWeb3Name, + ProviderLinkableAccountId, + >] { + self.revealed.as_ref() + } +} + +#[cfg(test)] +impl Default + for DidMerkleProof< + ProviderDidKeyId, + ProviderAccountId, + ProviderBlockNumber, + ProviderWeb3Name, + ProviderLinkableAccountId, + > where + ProviderDidKeyId: Default, + ProviderBlockNumber: Default, +{ + fn default() -> Self { + Self { + revealed: Default::default(), + blinded: Default::default(), + } + } +} + +/// A DID signature anchored to a specific block height. +/// +/// The generic types indicate the following: +/// * `BlockNumber`: The `BlockNumber` definition of the chain consuming (i.e., +/// validating) this signature. +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo)] +pub struct TimeBoundDidSignature { + /// The signature. + pub(crate) signature: DidSignature, + /// The block number until the signature is to be considered valid. + pub(crate) valid_until: BlockNumber, +} + +impl TimeBoundDidSignature { + pub fn new(signature: DidSignature, valid_until: BlockNumber) -> Self { + Self { signature, valid_until } + } +} + +#[cfg(test)] +impl Default for TimeBoundDidSignature +where + BlockNumber: Default, +{ + fn default() -> Self { + Self { + signature: DidSignature::Ed25519(sp_core::ed25519::Signature([0u8; 64])), + valid_until: BlockNumber::default(), + } + } +} diff --git a/crates/kilt-dip-primitives/src/merkle_proofs/v0/mod.rs b/crates/kilt-dip-primitives/src/merkle_proofs/v0/mod.rs new file mode 100644 index 0000000000..6628e444d3 --- /dev/null +++ b/crates/kilt-dip-primitives/src/merkle_proofs/v0/mod.rs @@ -0,0 +1,33 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +//! Module to deal with cross-chain Merkle proof as generated by the KILT chain. + +mod dip_subject_state; +mod error; +mod input_common; +mod output_common; +mod provider_state; +mod relay_state; + +pub use dip_subject_state::*; +pub use error::*; +pub use input_common::*; +pub use output_common::*; +pub use provider_state::*; +pub use relay_state::*; diff --git a/crates/kilt-dip-primitives/src/merkle_proofs/v0/output_common.rs b/crates/kilt-dip-primitives/src/merkle_proofs/v0/output_common.rs new file mode 100644 index 0000000000..502cd1dc43 --- /dev/null +++ b/crates/kilt-dip-primitives/src/merkle_proofs/v0/output_common.rs @@ -0,0 +1,300 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use did::{did_details::DidPublicKeyDetails, DidVerificationKeyRelationship}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_core::ConstU32; +use sp_runtime::{BoundedVec, SaturatedConversion}; +use sp_std::{fmt::Debug, vec::Vec}; + +use crate::Error; + +/// Information, available as an origin, after the whole DIP proof has been +/// verified. +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct DipOriginInfo< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + const MAX_REVEALED_LEAVES_COUNT: u32, +> { + /// The parts of the subject's DID details revealed in the DIP proof. + pub(crate) revealed_leaves: BoundedVec< + RevealedDidMerkleProofLeaf, + ConstU32, + >, + /// The index of the signing leaves from the vector above. + pub(crate) signing_leaves_indices: BoundedVec>, +} + +impl< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + const MAX_REVEALED_LEAVES_COUNT: u32, + > + DipOriginInfo< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + MAX_REVEALED_LEAVES_COUNT, + > +{ + /// Returns an iterator over the revealed DID leaves. + pub fn iter_leaves( + &self, + ) -> impl Iterator< + Item = &RevealedDidMerkleProofLeaf< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + >, + > { + self.revealed_leaves.iter() + } + + /// Returns an owned iterator over the revealed DID leaves. + pub fn into_iter_leaves( + self, + ) -> impl Iterator< + Item = RevealedDidMerkleProofLeaf< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + >, + > { + self.revealed_leaves.into_iter() + } + + /// Returns a reference to the leaves that signed the cross-chain operation. + /// This operation should never fail, so the only error it returns is an + /// `Error::Internal` which, anyway, should never happen. + pub fn get_signing_leaves( + &self, + ) -> Result>, Error> { + let leaves = self + .signing_leaves_indices + .iter() + .map(|index| { + let leaf = self.revealed_leaves.get(usize::saturated_from(*index)).ok_or_else(|| { + log::error!("Should never fail to retrieve the signing leaf at index {:#?}.", index); + Error::Internal + })?; + let RevealedDidMerkleProofLeaf::DidKey(did_key) = leaf else { + log::error!("Should never fail to convert the signing leaf to a DID Key leaf."); + return Err(Error::Internal); + }; + Ok(did_key) + }) + .collect::, _>>()?; + Ok(leaves.into_iter()) + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + const MAX_REVEALED_LEAVES_COUNT: u32, + > Default + for DipOriginInfo< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + MAX_REVEALED_LEAVES_COUNT, + > where + KiltDidKeyId: crate::traits::BenchmarkDefault, + KiltBlockNumber: crate::traits::BenchmarkDefault, +{ + fn default() -> Self { + let default_keys = sp_std::vec![RevealedDidKey { + id: KiltDidKeyId::default(), + details: DidPublicKeyDetails { + key: did::did_details::DidVerificationKey::Ed25519(sp_core::ed25519::Public::from_raw([0u8; 32])) + .into(), + block_number: KiltBlockNumber::default() + }, + relationship: DidVerificationKeyRelationship::Authentication.into() + } + .into()]; + let bounded_keys = default_keys + .try_into() + // To avoid requiring types to implement `Debug`. + .map_err(|_| "Should not fail to convert single element to a BoundedVec.") + .unwrap(); + Self { + revealed_leaves: bounded_keys, + signing_leaves_indices: Default::default(), + } + } +} + +/// Relationship of a key to a DID Document. +#[derive(Clone, Copy, Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, TypeInfo, MaxEncodedLen)] +pub enum DidKeyRelationship { + Encryption, + Verification(DidVerificationKeyRelationship), +} + +impl From for DidKeyRelationship { + fn from(value: DidVerificationKeyRelationship) -> Self { + Self::Verification(value) + } +} + +impl TryFrom for DidVerificationKeyRelationship { + type Error = (); + + fn try_from(value: DidKeyRelationship) -> Result { + if let DidKeyRelationship::Verification(rel) = value { + Ok(rel) + } else { + Err(()) + } + } +} + +/// All possible Merkle leaf types that can be revealed as part of a DIP +/// identity Merkle proof. +#[derive(Clone, Copy, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub enum RevealedDidMerkleProofLeaf { + DidKey(RevealedDidKey), + Web3Name(RevealedWeb3Name), + LinkedAccount(RevealedAccountId), +} + +impl From> + for RevealedDidMerkleProofLeaf +{ + fn from(value: RevealedDidKey) -> Self { + Self::DidKey(value) + } +} + +impl From> + for RevealedDidMerkleProofLeaf +{ + fn from(value: RevealedWeb3Name) -> Self { + Self::Web3Name(value) + } +} + +impl From> + for RevealedDidMerkleProofLeaf +{ + fn from(value: RevealedAccountId) -> Self { + Self::LinkedAccount(value) + } +} + +#[cfg(any(test, feature = "runtime-benchmarks"))] +impl Default + for RevealedDidMerkleProofLeaf +where + KeyId: Default, + BlockNumber: Default, +{ + fn default() -> Self { + RevealedDidKey { + id: KeyId::default(), + relationship: DidVerificationKeyRelationship::Authentication.into(), + details: DidPublicKeyDetails { + key: did::did_details::DidVerificationKey::Ed25519(sp_core::ed25519::Public::from_raw([0u8; 32])) + .into(), + block_number: BlockNumber::default(), + }, + } + .into() + } +} + +impl + RevealedDidMerkleProofLeaf +where + KeyId: Encode, + Web3Name: Encode, + LinkedAccountId: Encode, +{ + pub fn encoded_key(&self) -> Vec { + match self { + RevealedDidMerkleProofLeaf::DidKey(RevealedDidKey { id, relationship, .. }) => (id, relationship).encode(), + RevealedDidMerkleProofLeaf::Web3Name(RevealedWeb3Name { web3_name, .. }) => web3_name.encode(), + RevealedDidMerkleProofLeaf::LinkedAccount(RevealedAccountId(account_id)) => account_id.encode(), + } + } +} + +impl + RevealedDidMerkleProofLeaf +where + AccountId: Encode, + BlockNumber: Encode, +{ + pub fn encoded_value(&self) -> Vec { + match self { + RevealedDidMerkleProofLeaf::DidKey(RevealedDidKey { details, .. }) => details.encode(), + RevealedDidMerkleProofLeaf::Web3Name(RevealedWeb3Name { claimed_at, .. }) => claimed_at.encode(), + RevealedDidMerkleProofLeaf::LinkedAccount(_) => ().encode(), + } + } +} + +/// The details of a DID key after it has been successfully verified in a Merkle +/// proof. +#[derive(Clone, Copy, Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, TypeInfo, MaxEncodedLen)] +pub struct RevealedDidKey { + /// The key ID, according to the provider's definition. + pub id: KeyId, + /// The key relationship to the subject's DID Document. + pub relationship: DidKeyRelationship, + /// The details of the DID Key, including its creation block number on the + /// provider chain. + pub details: DidPublicKeyDetails, +} + +/// The details of a web3name after it has been successfully verified in a +/// Merkle proof. +#[derive(Clone, Copy, Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, TypeInfo, MaxEncodedLen)] +pub struct RevealedWeb3Name { + /// The web3name. + pub web3_name: Web3Name, + /// The block number on the provider chain in which it was linked to the DID + /// subject. + pub claimed_at: BlockNumber, +} + +/// The details of an account after it has been successfully verified in a +/// Merkle proof. +#[derive(Clone, Copy, Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, TypeInfo, MaxEncodedLen)] +pub struct RevealedAccountId(pub AccountId); diff --git a/crates/kilt-dip-primitives/src/merkle_proofs/v0/provider_state/mod.rs b/crates/kilt-dip-primitives/src/merkle_proofs/v0/provider_state/mod.rs new file mode 100644 index 0000000000..c4e2fe271c --- /dev/null +++ b/crates/kilt-dip-primitives/src/merkle_proofs/v0/provider_state/mod.rs @@ -0,0 +1,481 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use frame_support::ensure; +use pallet_dip_provider::IdentityCommitmentOf; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{Hash, Header as HeaderT}, + BoundedVec, SaturatedConversion, +}; +use sp_std::{fmt::Debug, vec::Vec}; +use sp_trie::{verify_trie_proof, LayoutV1}; + +use crate::{ + merkle_proofs::v0::{ + dip_subject_state::DipRevealedDetailsAndUnverifiedDidSignature, + input_common::{DidMerkleProof, DipCommitmentStateProof, ProviderHeadStateProof, TimeBoundDidSignature}, + }, + state_proofs::{verify_storage_value_proof, verify_storage_value_proof_with_decoder}, + traits::GetWithArg, + utils::{ + calculate_dip_identity_commitment_storage_key_for_runtime, calculate_parachain_head_storage_key, OutputOf, + }, + Error, +}; + +#[cfg(test)] +mod tests; + +/// A DIP proof submitted to a parachain consumer. +/// +/// The generic types indicate the following: +/// * `RelayBlockNumber`: The `BlockNumber` definition of the relaychain. +/// * `KiltDidKeyId`: The DID key ID type configured by the KILT chain. +/// * `KiltAccountId`: The `AccountId` type configured by the KILT chain. +/// * `KiltBlockNumber`: The `BlockNumber` type configured by the KILT chain. +/// * `KiltWeb3Name`: The web3name type configured by the KILT chain. +/// * `KiltLinkableAccountId`: The linkable account ID type configured by the +/// KILT chain. +/// * `ConsumerBlockNumber`: The `BlockNumber` definition of the consumer +/// parachain. +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo)] +pub struct ParachainDipDidProof< + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, +> { + /// The state proof for the given parachain head. + pub(crate) provider_head_proof: ProviderHeadStateProof, + /// The raw state proof for the DIP commitment of the given subject. + pub(crate) dip_commitment_proof: DipCommitmentStateProof, + /// The Merkle proof of the subject's DID details. + pub(crate) dip_proof: + DidMerkleProof, + /// The cross-chain DID signature. + pub(crate) signature: TimeBoundDidSignature, +} + +impl< + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > + ParachainDipDidProof< + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > +{ + pub fn new( + provider_head_proof: ProviderHeadStateProof, + dip_commitment_proof: DipCommitmentStateProof, + dip_proof: DidMerkleProof, + signature: TimeBoundDidSignature, + ) -> Self { + Self { + dip_commitment_proof, + dip_proof, + provider_head_proof, + signature, + } + } + + pub fn provider_head_proof(&self) -> &ProviderHeadStateProof { + &self.provider_head_proof + } + + pub fn dip_commitment_proof(&self) -> &DipCommitmentStateProof { + &self.dip_commitment_proof + } + + pub fn dip_proof( + &self, + ) -> &DidMerkleProof { + &self.dip_proof + } + + pub fn signature(&self) -> &TimeBoundDidSignature { + &self.signature + } +} + +impl< + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > + ParachainDipDidProof< + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > +{ + /// Verifies the head data of the state proof for the provider with the + /// given para ID and relaychain state root. + /// + /// The generic types indicate the following: + /// * `RelayHasher`: The head data hashing algorithm used by the relaychain. + /// * `ProviderHeader`: The type of the parachain header to be revealed in + /// the state proof. + #[allow(clippy::type_complexity)] + pub fn verify_provider_head_proof_with_state_root( + self, + provider_para_id: u32, + relay_state_root: &OutputOf, + ) -> Result< + DipDidProofWithVerifiedStateRoot< + OutputOf, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + >, + Error, + > + where + RelayHasher: Hash, + ProviderHeader: Decode + HeaderT, Number = KiltBlockNumber>, + { + let provider_head_storage_key = calculate_parachain_head_storage_key(provider_para_id); + // TODO: Figure out why RPC call returns 2 bytes in front which we don't need + //This could be the reason (and the solution): https://substrate.stackexchange.com/a/1891/1795 + let provider_header = verify_storage_value_proof_with_decoder::<_, RelayHasher, ProviderHeader>( + &provider_head_storage_key, + *relay_state_root, + self.provider_head_proof.proof, + |input| { + if input.len() < 2 { + return None; + } + let mut trimmed_input = &input[2..]; + ProviderHeader::decode(&mut trimmed_input).ok() + }, + ) + .map_err(Error::ParaHeadMerkleProof)?; + Ok(DipDidProofWithVerifiedStateRoot { + state_root: *provider_header.state_root(), + dip_commitment_proof: self.dip_commitment_proof, + dip_proof: self.dip_proof, + signature: self.signature, + }) + } + + /// Verifies the head data of the state proof for the provider with the + /// given para ID using the state root returned by the provided + /// implementation. + /// + /// The generic types indicate the following: + /// * `RelayHasher`: The hashing algorithm used on the relaychain to + /// generate the parachains head data. + /// * `StateRootStore`: The type that returns a relaychain state root given + /// a relaychain block number. + /// * `ProviderHeader`: The type of the parachain header to be revealed in + /// the state proof. + #[allow(clippy::type_complexity)] + pub fn verify_provider_head_proof( + self, + provider_para_id: u32, + ) -> Result< + DipDidProofWithVerifiedStateRoot< + OutputOf, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + >, + Error, + > + where + RelayHasher: Hash, + StateRootStore: GetWithArg>>, + ProviderHeader: Decode + HeaderT, Number = KiltBlockNumber>, + { + let relay_state_root = + StateRootStore::get(&self.provider_head_proof.relay_block_number).ok_or(Error::RelayStateRootNotFound)?; + self.verify_provider_head_proof_with_state_root::( + provider_para_id, + &relay_state_root, + ) + } +} + +/// A DIP proof that has had the proof header and the relaychain state verified +/// for the provided relaychain block number. +/// +/// The generic types indicate the following: +/// * `StateRoot`: The type of the relaychain state root. +/// * `KiltDidKeyId`: The DID key ID type configured by the KILT chain. +/// * `KiltAccountId`: The `AccountId` type configured by the KILT chain. +/// * `KiltBlockNumber`: The `BlockNumber` type configured by the KILT chain. +/// * `KiltWeb3Name`: The web3name type configured by the KILT chain. +/// * `KiltLinkableAccountId`: The linkable account ID type configured by the +/// KILT chain. +/// * `ConsumerBlockNumber`: The `BlockNumber` definition of the consumer +/// parachain. +#[derive(Debug, PartialEq, Eq)] +pub struct DipDidProofWithVerifiedStateRoot< + StateRoot, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, +> { + /// The provider state root for the block specified in the DIP proof. + pub(crate) state_root: StateRoot, + /// The raw state proof for the DIP commitment of the given subject. + pub(crate) dip_commitment_proof: DipCommitmentStateProof, + /// The Merkle proof of the subject's DID details. + pub(crate) dip_proof: + DidMerkleProof, + /// The cross-chain DID signature. + pub(crate) signature: TimeBoundDidSignature, +} + +impl< + StateRoot, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > + DipDidProofWithVerifiedStateRoot< + StateRoot, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > +{ + /// Verifies the DIP commitment part of the state proof for the subject with + /// the given identifier. + /// + /// The generic types indicate the following: + /// * `ParachainHasher`: The hashing algorithm used to hash storage on the + /// parachain. + /// * `ProviderRuntime`: The provider runtime definition. + #[allow(clippy::type_complexity)] + pub fn verify_dip_commitment_proof_for_subject( + self, + subject: &ProviderRuntime::Identifier, + ) -> Result< + DipDidProofWithVerifiedSubjectCommitment< + IdentityCommitmentOf, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + >, + Error, + > + where + StateRoot: Ord, + ParachainHasher: Hash, + ProviderRuntime: pallet_dip_provider::Config, + { + let dip_commitment_storage_key = + calculate_dip_identity_commitment_storage_key_for_runtime::(subject, 0); + let dip_commitment = verify_storage_value_proof::<_, ParachainHasher, IdentityCommitmentOf>( + &dip_commitment_storage_key, + self.state_root, + self.dip_commitment_proof.0, + ) + .map_err(Error::DipCommitmentMerkleProof)?; + Ok(DipDidProofWithVerifiedSubjectCommitment { + dip_commitment, + dip_proof: self.dip_proof, + signature: self.signature, + }) + } +} + +/// A DIP proof that has had the relaychain state and the DIP commitment +/// verified for the provided relaychain block number. +/// +/// The generic types indicate the following: +/// * `Commitment`: The DIP identity commitment type configured by the KILT +/// chain. +/// * `KiltDidKeyId`: The DID key ID type configured by the KILT chain. +/// * `KiltAccountId`: The `AccountId` type configured by the KILT chain. +/// * `KiltBlockNumber`: The `BlockNumber` type configured by the KILT chain. +/// * `KiltWeb3Name`: The web3name type configured by the KILT chain. +/// * `KiltLinkableAccountId`: The linkable account ID type configured by the +/// KILT chain. +/// * `ConsumerBlockNumber`: The `BlockNumber` definition of the consumer +/// parachain. +#[derive(Debug, PartialEq, Eq)] +pub struct DipDidProofWithVerifiedSubjectCommitment< + Commitment, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, +> { + /// The verified DIP identity commitment. + pub(crate) dip_commitment: Commitment, + /// The Merkle proof of the subject's DID details. + pub(crate) dip_proof: + DidMerkleProof, + /// The cross-chain DID signature. + pub(crate) signature: TimeBoundDidSignature, +} + +impl< + Commitment, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > + DipDidProofWithVerifiedSubjectCommitment< + Commitment, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > +{ + pub fn new( + dip_commitment: Commitment, + dip_proof: DidMerkleProof, + signature: TimeBoundDidSignature, + ) -> Self { + Self { + dip_commitment, + dip_proof, + signature, + } + } +} + +impl< + Commitment, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > + DipDidProofWithVerifiedSubjectCommitment< + Commitment, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > where + KiltDidKeyId: Encode, + KiltAccountId: Encode, + KiltBlockNumber: Encode, + KiltWeb3Name: Encode, + KiltLinkableAccountId: Encode, +{ + /// Verifies the Merkle proof of the subject's DID details. + /// + /// The generic types indicate the following: + /// * `DidMerkleHasher`: The hashing algorithm used to merkleize the DID + /// details. + /// * `MAX_REVEALED_LEAVES_COUNT`: The maximum number of leaves revealable + /// in the proof. + pub fn verify_dip_proof( + self, + ) -> Result< + DipRevealedDetailsAndUnverifiedDidSignature< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + MAX_REVEALED_LEAVES_COUNT, + >, + Error, + > + where + DidMerkleHasher: Hash, + { + ensure!( + self.dip_proof.revealed.len() <= MAX_REVEALED_LEAVES_COUNT.saturated_into(), + Error::TooManyLeavesRevealed + ); + + let proof_leaves_key_value_pairs = self + .dip_proof + .revealed + .iter() + .map(|revealed_leaf| (revealed_leaf.encoded_key(), Some(revealed_leaf.encoded_value()))) + .collect::>(); + verify_trie_proof::, _, _, _>( + &self.dip_commitment, + self.dip_proof.blinded.as_slice(), + proof_leaves_key_value_pairs.as_slice(), + ) + .map_err(|_| Error::InvalidDidMerkleProof)?; + + let revealed_leaves = BoundedVec::try_from(self.dip_proof.revealed).map_err(|_| { + log::error!("Should not fail to construct BoundedVec since bounds were checked before."); + Error::Internal + })?; + + Ok(DipRevealedDetailsAndUnverifiedDidSignature { + revealed_leaves, + signature: self.signature, + }) + } +} diff --git a/crates/kilt-dip-primitives/src/merkle_proofs/v0/provider_state/tests.rs b/crates/kilt-dip-primitives/src/merkle_proofs/v0/provider_state/tests.rs new file mode 100644 index 0000000000..cf3fdf8ab5 --- /dev/null +++ b/crates/kilt-dip-primitives/src/merkle_proofs/v0/provider_state/tests.rs @@ -0,0 +1,544 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +// These test cases are, for now, the same as the ones in +// [`super::relay_state::relay_dip_did_proof_with_verified_relay_state_root`], +// since the functions in there are a wrapper for functions in here. +// Nevertheless, these two components can diverge in the future. +mod parachain_dip_did_proof { + use frame_support::assert_err; + use frame_system::pallet_prelude::HeaderFor; + use hex_literal::hex; + use sp_core::H256; + use sp_runtime::traits::{BlakeTwo256, Keccak256}; + use spiritnet_runtime::Runtime as SpiritnetRuntime; + + use crate::{state_proofs::MerkleProofError, Error, ParachainDipDidProof, ProviderHeadStateProof}; + + impl< + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > + ParachainDipDidProof< + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > where + KiltDidKeyId: Default, + KiltBlockNumber: Default, + ConsumerBlockNumber: Default, + { + fn with_provider_head_proof(provider_head_proof: ProviderHeadStateProof) -> Self { + Self { + provider_head_proof, + dip_commitment_proof: Default::default(), + dip_proof: Default::default(), + signature: Default::default(), + } + } + } + + // Storage proof generated at Polkadot block `19_663_508` with hash + // `0x6e87866fb4f412e1e691e25d294019a7695d5a756ee7bc8d012c25177b5e1e13` for + // storage key + // `0xcd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c32c0cfd6c23b92a7826080000` + // (`paras::heads(2_086)`) + fn get_provider_head_proof() -> (H256, ProviderHeadStateProof) { + (hex!("623b36bddae282e9fefab4707697171a594fdb27e90fd4ada4ebcc356438b070").into(), ProviderHeadStateProof { + relay_block_number: 19_663_508, + proof: vec![ + hex!("560cfd6c23b92a7826080000f102e902fb1bd938b2f4fcea70641da8e64e0e11098b92b767279227cdfdc0ae9500da99d27e5f012937179bfe939750c770f2aa6e84c6b8cf9d0aa9ab852243ceb78e3eeb93fc56eacc28c5503a155c4d8bc7ee4797c38e212428cefff0a7ad19b28ebbab793e64080661757261204a207d0800000000056175726101017cda19117c87384aaebfd2ae546771bcfbfe7011a91119932883382cc62be3050d745c9734f422228c7c43d87e6172519019829b14b2d3b64afafb1fb7d3a683").to_vec(), + hex!("80021080f0c4027f5eba380b623a2d3382ab03961b2b7e753e62d3475a6940207db367cc80fde8c5a37120e2f1d987f5302783d22f8ac1b213c580030a7f5b15e706df6262").to_vec(), + hex!("8004648019885bbc2709cbd3a89f9a8813867f322e5663c99fdea9af3ff2ba0010455d5d8091eb4873541a81f69d22ccd903b864c36c747910eec7433d947e5a61f87eb7db80ca85a51ad63cbcb7a988023f3b082492a6937f9957c029eb34d6d618279d232d8048dcb685fcf3963e7697a630c6ce64ff06f325063bb05e34e679eb01a4e2b644").to_vec(), + hex!("80ffff8003ff6c42a935aca27c743910dbb62aae8009854a21741d74080abb406c26b1f58084a7c1351a2986c948c9a111c955d0f8635e4bd305c24f9b6680405fdce955a180ed003737744c7fba94d0c2cb57f96e7bf3310d9c7a285ae789e25af8b79091b38017a0734a39f27a75f6f648bca2facca2381325b529d32bcf82e75aaf6b7d82dd8042e2e666a38ce9fdbe60164d0c3a351ce06c931931d2cd6650378c1ad691c21480d0cc4967746360ee3895a6937608d7f36674426928790cb8ca7426289ad74469804e8940ff6b30dfb0f92341c3a738f262bed9ca03de9b868eb99cfc282aa7786780acda22345d4597dfe6fd831509b944254e26a00fd56e77bc2cb780c0775a520a808c0dae720727cec94dbc853812332bfd6d5f2cc5e287bcd1e5efc530053dbd2280a16a8184b9f2e555d4991995fd479b1ee7b35653f2215f74f822436dbbb2331580984648137ae9c8ecf33f878cedffdba73fb4282ba3ec033102aa6d7442466517801132afeed824c180373b2450b32c72c84a21cdfddbe0f1bf8e76d6958963669580357f2107df0a82f2605f90e39c5665bdf69e1d6222bc425f8390bde67c1d414780c4e048c8dc0ea614a190375a1b215c8e8ff5f5098cd43a93d59be907a2258a74807ad4cd868c49acc40e389d45a1e7e7629e666972ed747c67b607b07f637c1f0b8021bbaa444a77faac92b771c0e1b19162ace64b5ce745892d3ce59f820cba2dc7").to_vec(), + hex!("9e710b30bd2eab0352ddcc26417aa1945fd3802284b6ec6d4b3138fca93d003a58421ba947ecbc14c39e76572061105bbc568b809f8b23e74053dd98b58424e102ba5ac16f028714ec16a61522011fe6e16771ff80ed3e43ac278948816e8c9e8adda2dbeefe552702cf8144fd9b50e0b8db99bfcf80694abb8b23315ab79cdb22ca6826e867a9157415a832ad38f376dd819107d3ea80b9aee043e378f8313e68a6030679ccf3880fa1e7ab19b6244b5c262b7a152f004c5f03c716fb8fff3de61a883bb76adb34a2040080f15f37adeb10597dac54c2c65393277b2ca62aa27b2d16a23a78a4cc55ef15bb8008a0c609ab4888f02c2545c002153297c2641c5a7b4f3d8e25c634e721f80bea80b6617c764df278313c426c46961ccde8ee7a03f9007b74bc8bc6c49d1583cf7d801c9a4a3457ad4a568dd4c9abe231304689c9bec78be932ef0a2d30690ca428848059ef8bbe3a06c98792f41b3e0a6cdf1f157d9be85e12a7c1daf9c30f969daba4").to_vec(), + hex!("9f0b3c252fcb29d88eff4f3de5de4476c3ffbf805254dc9131b269f3bbbb71f58a76a5034b2bc2faaab0d1cf45c3819dc6e69740804bc059c3d96f627e09a3b6c0f9851d902f84ac68006617289ac0b7d0a272b36280d97e2394406f94be4266da29b6fe7f3178059525eaf3c9b540064389af020bf180636959b43018d3ff8a55246d5874a16c93e85bd2a58c82ebfc1b54dd9b2a7d0780d3c1a10188200f31459d722f7efc693736d1a36af5644fd949b2e411d7942597800328f24d0485b9701135913a569f6ccbf261a05d055183abf3e4ecb4e4375b7c80f3229cd59de7b1e604f110cbcf814466f2d2973e9bdb6c106a662c576e0820e480b66b29cbd45f93602dbc9f1175407c6f69bd686d23dd22a8f0dfe9cff08843ad80ddb2d426c0c546068b429e77253e0a8a32e818151f5fc031e899a0f6acad157580ea7fb3cad8e128cc295194658016f4865ef37501e5759fb4f15cb2ecb689e85e80e9f3cac1b25842da7fbaf947952dc30329a1d19037ab21baed3851acbee629f6800d898e2a4a6ee9969a233c4741e4441c0fe393104b3cfc5adcf348f3ef20fc7480ac6c622536e593ae3c9d423a461faafc7abbf01ecb129e69d66f3382eaf484dc80c7ba3cadffaea5acd013dba51c96129ae93ea6cd45f3930e4302f5b100f6deae806f29f805e30029363e42381d6609ecb6837411bd6fd676c0a37621a3b5588101").to_vec() + ], + }) + } + + #[test] + fn verify_provider_head_proof_with_state_root_successful() { + let (relay_state_root, provider_head_proof) = get_provider_head_proof(); + // Only interested in the parachain head verification part, we skip everything + // else. + let proof = ParachainDipDidProof::<_, (), (), _, (), (), ()>::with_provider_head_proof(provider_head_proof); + let proof_verification_result = proof + .verify_provider_head_proof_with_state_root::>( + 2_086, + &relay_state_root, + ) + .unwrap(); + assert_eq!( + proof_verification_result.state_root, + hex!("2937179bfe939750c770f2aa6e84c6b8cf9d0aa9ab852243ceb78e3eeb93fc56").into() + ); + } + + #[test] + fn verify_provider_head_proof_with_state_root_multi_storage() { + // Storage proof generated at Polkadot block `19_663_508` with hash + // `0x6e87866fb4f412e1e691e25d294019a7695d5a756ee7bc8d012c25177b5e1e13` for + // storage keys + // [`0xcd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c32c0cfd6c23b92a7826080000`, `0xcd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c3b6ff6f7d467b87a9e8030000`] + // ([`paras::heads(2_086)`, `paras::heads(1_000)]`) + let relay_state_root: H256 = hex!("623b36bddae282e9fefab4707697171a594fdb27e90fd4ada4ebcc356438b070").into(); + let provider_head_proof = ProviderHeadStateProof { + relay_block_number: 19_663_508, + proof: vec![ + hex!("560cfd6c23b92a7826080000f102e902fb1bd938b2f4fcea70641da8e64e0e11098b92b767279227cdfdc0ae9500da99d27e5f012937179bfe939750c770f2aa6e84c6b8cf9d0aa9ab852243ceb78e3eeb93fc56eacc28c5503a155c4d8bc7ee4797c38e212428cefff0a7ad19b28ebbab793e64080661757261204a207d0800000000056175726101017cda19117c87384aaebfd2ae546771bcfbfe7011a91119932883382cc62be3050d745c9734f422228c7c43d87e6172519019829b14b2d3b64afafb1fb7d3a683").to_vec(), + hex!("56ff6f7d467b87a9e80300009903910331d9f8f427be99ba3b36ad6f66c49b5448e16745fc3cbe08821204e2e94c9abe1ef65e01dc0f3c52d9aaef735eae1aa679e6c3020e993fb3eef8fab9c32cc7b55dfc85362c771d9a07e5b6cf9f8e30b67598c513ca087574ecce0b3e205eb4de8783a4630c0661757261204a207d08000000000452505352906ca2562dd3ddae0ecb7076465e223753e76792653f739d5dfb00ad76a6b3607d4a2ab00405617572610101dadcb6f606d8a71dc6d0d4d20ccc3bd67bae8816c86491b14fa899242cd872f3bf5fe9635d4414f4329a578a0627cf367dcaa3e86beca64a9aaef9afd124c701").to_vec(), + hex!("80021080f0c4027f5eba380b623a2d3382ab03961b2b7e753e62d3475a6940207db367cc80fde8c5a37120e2f1d987f5302783d22f8ac1b213c580030a7f5b15e706df6262").to_vec(), + hex!("8004648019885bbc2709cbd3a89f9a8813867f322e5663c99fdea9af3ff2ba0010455d5d8091eb4873541a81f69d22ccd903b864c36c747910eec7433d947e5a61f87eb7db80ca85a51ad63cbcb7a988023f3b082492a6937f9957c029eb34d6d618279d232d8048dcb685fcf3963e7697a630c6ce64ff06f325063bb05e34e679eb01a4e2b644").to_vec(), + hex!("80d510805396188100731505c3fe5f51e7d4a9c6e6e4cd2c50ff6d122f5f091a186b2f9780e69515c0c399ad09a7b5da0afb5a8bbd22c6873b69f9f2da18e26a8bd04c6e9d80d647e804958d947c20337a2ac3714b3eca41be52847542b065da3614230decab806dbb5b1913c89acb68a2e85013c4b7adf37ab010cf9b9d7346348d0ca9aafd4a80702af779edd6e8d659600cbe342947238af804d41589116a3dd7fb48905aeab18076a51b70378cbf602d939a885bbad80c94ee9325398105ec2173324bd7f59b55").to_vec(), + hex!("80ffff8003ff6c42a935aca27c743910dbb62aae8009854a21741d74080abb406c26b1f58084a7c1351a2986c948c9a111c955d0f8635e4bd305c24f9b6680405fdce955a180ed003737744c7fba94d0c2cb57f96e7bf3310d9c7a285ae789e25af8b79091b38017a0734a39f27a75f6f648bca2facca2381325b529d32bcf82e75aaf6b7d82dd8042e2e666a38ce9fdbe60164d0c3a351ce06c931931d2cd6650378c1ad691c21480d0cc4967746360ee3895a6937608d7f36674426928790cb8ca7426289ad74469804e8940ff6b30dfb0f92341c3a738f262bed9ca03de9b868eb99cfc282aa7786780acda22345d4597dfe6fd831509b944254e26a00fd56e77bc2cb780c0775a520a808c0dae720727cec94dbc853812332bfd6d5f2cc5e287bcd1e5efc530053dbd2280a16a8184b9f2e555d4991995fd479b1ee7b35653f2215f74f822436dbbb2331580984648137ae9c8ecf33f878cedffdba73fb4282ba3ec033102aa6d7442466517801132afeed824c180373b2450b32c72c84a21cdfddbe0f1bf8e76d6958963669580357f2107df0a82f2605f90e39c5665bdf69e1d6222bc425f8390bde67c1d414780c4e048c8dc0ea614a190375a1b215c8e8ff5f5098cd43a93d59be907a2258a74807ad4cd868c49acc40e389d45a1e7e7629e666972ed747c67b607b07f637c1f0b8021bbaa444a77faac92b771c0e1b19162ace64b5ce745892d3ce59f820cba2dc7").to_vec(), + hex!("9e710b30bd2eab0352ddcc26417aa1945fd3802284b6ec6d4b3138fca93d003a58421ba947ecbc14c39e76572061105bbc568b809f8b23e74053dd98b58424e102ba5ac16f028714ec16a61522011fe6e16771ff80ed3e43ac278948816e8c9e8adda2dbeefe552702cf8144fd9b50e0b8db99bfcf80694abb8b23315ab79cdb22ca6826e867a9157415a832ad38f376dd819107d3ea80b9aee043e378f8313e68a6030679ccf3880fa1e7ab19b6244b5c262b7a152f004c5f03c716fb8fff3de61a883bb76adb34a2040080f15f37adeb10597dac54c2c65393277b2ca62aa27b2d16a23a78a4cc55ef15bb8008a0c609ab4888f02c2545c002153297c2641c5a7b4f3d8e25c634e721f80bea80b6617c764df278313c426c46961ccde8ee7a03f9007b74bc8bc6c49d1583cf7d801c9a4a3457ad4a568dd4c9abe231304689c9bec78be932ef0a2d30690ca428848059ef8bbe3a06c98792f41b3e0a6cdf1f157d9be85e12a7c1daf9c30f969daba4").to_vec(), + hex!("9f0b3c252fcb29d88eff4f3de5de4476c3ffbf805254dc9131b269f3bbbb71f58a76a5034b2bc2faaab0d1cf45c3819dc6e69740804bc059c3d96f627e09a3b6c0f9851d902f84ac68006617289ac0b7d0a272b36280d97e2394406f94be4266da29b6fe7f3178059525eaf3c9b540064389af020bf180636959b43018d3ff8a55246d5874a16c93e85bd2a58c82ebfc1b54dd9b2a7d0780d3c1a10188200f31459d722f7efc693736d1a36af5644fd949b2e411d7942597800328f24d0485b9701135913a569f6ccbf261a05d055183abf3e4ecb4e4375b7c80f3229cd59de7b1e604f110cbcf814466f2d2973e9bdb6c106a662c576e0820e480b66b29cbd45f93602dbc9f1175407c6f69bd686d23dd22a8f0dfe9cff08843ad80ddb2d426c0c546068b429e77253e0a8a32e818151f5fc031e899a0f6acad157580ea7fb3cad8e128cc295194658016f4865ef37501e5759fb4f15cb2ecb689e85e80e9f3cac1b25842da7fbaf947952dc30329a1d19037ab21baed3851acbee629f6800d898e2a4a6ee9969a233c4741e4441c0fe393104b3cfc5adcf348f3ef20fc7480ac6c622536e593ae3c9d423a461faafc7abbf01ecb129e69d66f3382eaf484dc80c7ba3cadffaea5acd013dba51c96129ae93ea6cd45f3930e4302f5b100f6deae806f29f805e30029363e42381d6609ecb6837411bd6fd676c0a37621a3b5588101").to_vec() + ], + }; + // Only interested in the parachain head verification part, we skip everything + // else. + let proof = ParachainDipDidProof::<_, (), (), _, (), (), ()>::with_provider_head_proof(provider_head_proof); + let proof_verification_result = proof + .verify_provider_head_proof_with_state_root::>( + 2_086, + &relay_state_root, + ) + .unwrap(); + assert_eq!( + proof_verification_result.state_root, + hex!("2937179bfe939750c770f2aa6e84c6b8cf9d0aa9ab852243ceb78e3eeb93fc56").into() + ); + } + + #[test] + fn verify_provider_head_proof_with_state_root_wrong_relay_hasher() { + let (relay_state_root, provider_head_proof) = get_provider_head_proof(); + let proof = ParachainDipDidProof::<_, (), (), _, (), (), ()>::with_provider_head_proof(provider_head_proof); + assert_err!( + // Using a different hasher for verification + proof.verify_provider_head_proof_with_state_root::>( + 2_086, + &relay_state_root + ), + Error::ParaHeadMerkleProof(MerkleProofError::InvalidProof) + ); + } + + #[test] + fn verify_provider_head_proof_with_state_root_wrong_para_id() { + let (relay_state_root, provider_head_proof) = get_provider_head_proof(); + let proof = ParachainDipDidProof::<_, (), (), _, (), (), ()>::with_provider_head_proof(provider_head_proof); + assert_err!( + proof.verify_provider_head_proof_with_state_root::>( + 1_000, + &relay_state_root + ), + Error::ParaHeadMerkleProof(MerkleProofError::InvalidProof) + ); + } + + #[test] + fn verify_provider_head_proof_with_state_root_invalid_proof() { + let (relay_state_root, provider_head_proof) = get_provider_head_proof(); + // Remove last part of the blinded component to get an invalid proof + let (_, invalid_blinded_proof) = provider_head_proof.proof.split_last().unwrap(); + let invalid_provider_head_proof = ProviderHeadStateProof { + proof: invalid_blinded_proof.to_owned(), + ..provider_head_proof + }; + let proof = + ParachainDipDidProof::<_, (), (), _, (), (), ()>::with_provider_head_proof(invalid_provider_head_proof); + assert_err!( + proof.verify_provider_head_proof_with_state_root::>( + 2_086, + &relay_state_root + ), + Error::ParaHeadMerkleProof(MerkleProofError::InvalidProof) + ); + } +} + +mod dip_did_proof_with_verified_relay_state_root { + use frame_support::{assert_err, construct_runtime, traits::Everything}; + use frame_system::{mocking::MockBlock, EnsureSigned}; + use hex_literal::hex; + use pallet_dip_provider::{DefaultIdentityCommitmentGenerator, DefaultIdentityProvider}; + use sp_core::{crypto::Ss58Codec, ConstU16, ConstU32, ConstU64, H256}; + use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup, Keccak256}, + AccountId32, + }; + + use crate::{state_proofs::MerkleProofError, DipCommitmentStateProof, DipDidProofWithVerifiedStateRoot, Error}; + + impl< + StateRoot, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > + DipDidProofWithVerifiedStateRoot< + StateRoot, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > where + KiltDidKeyId: Default, + KiltBlockNumber: Default, + ConsumerBlockNumber: Default, + { + fn with_state_root_and_dip_commitment_proof( + provider_state_root: StateRoot, + dip_commitment_proof: DipCommitmentStateProof, + ) -> Self { + Self { + state_root: provider_state_root, + dip_commitment_proof, + dip_proof: Default::default(), + signature: Default::default(), + } + } + } + + construct_runtime!( + pub enum TestProviderRuntime { + System: frame_system, + DipProvider: pallet_dip_provider, + } + ); + + impl frame_system::Config for TestProviderRuntime { + type AccountData = (); + type AccountId = AccountId32; + type BaseCallFilter = Everything; + type Block = MockBlock; + type BlockHashCount = ConstU64<256>; + type BlockLength = (); + type BlockWeights = (); + type DbWeight = (); + type Hash = H256; + type Hashing = BlakeTwo256; + type Lookup = IdentityLookup; + type MaxConsumers = ConstU32<16>; + type Nonce = u64; + type OnKilledAccount = (); + type OnNewAccount = (); + type OnSetCode = (); + type PalletInfo = PalletInfo; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type SS58Prefix = ConstU16<1>; + type SystemWeightInfo = (); + type Version = (); + } + + impl pallet_dip_provider::Config for TestProviderRuntime { + type CommitOrigin = AccountId32; + type CommitOriginCheck = EnsureSigned; + type Identifier = AccountId32; + type IdentityCommitmentGenerator = DefaultIdentityCommitmentGenerator; + type IdentityProvider = DefaultIdentityProvider; + type ProviderHooks = (); + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + } + + // Storage proof generated at local Peregrine instance (with new storage hasher) + // for storage key + // `0xb375edf06348b4330d1e88564111cb3d5bf19e4ed2927982e234d989e812f3f366a25a7fa9282d4c8e07cfeb5ec4b0f44cec8bb650a6e6ff111f30916b9ca56a4542f70764e95d7ceb6736d981b2d95d01a12dfa1fa4ab9a0000` + // (`dipProvider::identityCommitments(4pevjN6chwUqWPVaoUF6naRmZyrA4XWfdK8nLQLEjufgW55c, 0)`) + fn get_dip_commitment_proof() -> (H256, DipCommitmentStateProof) { + (hex!("0757487b9dda09be65eae2e4ffeff8de52e66d5187d064f31e24fac44be9f4f7").into(), DipCommitmentStateProof(vec![ + hex!("7f540bf19e4ed2927982e234d989e812f3f366a25a7fa9282d4c8e07cfeb5ec4b0f44cec8bb650a6e6ff111f30916b9ca56a4542f70764e95d7ceb6736d981b2d95d01a12dfa1fa4ab9a00008051b175db0cd3a4071aaa1cdde8f3cc562b9618961d8a80ed77981ec98b91da45").to_vec(), + hex!("800c8080da28793d083b197f8d92fc3e77f5064436f1d8eea0fbea56ddb936aba654450080667f196f66a258b7e851925a9fca0e787fa2080ade3ec203fe940a85a4ef68b080b2aafe11c416356c5a97e233670962facb2a18944c3bdc4b9e27f1fa67a5bafe").to_vec(), + hex!("80ffff80353e4d164b13c87910044f1b4e76277e404a0ab46a7cd6c33a65aaadc2375ba88007b1390da34b4dce1328430fd924a6e193517a8148dd70a912c0dc2f7f8d2d4c803d402a5fdb0bf83f4f6da28178dc3d3b61d639a4c5733d8eaa79b3a159d9a79f80303acb9eafad3fe6028cf2abca4c824bf48af2b7241920ddf31b37d7921ee932802fd5e075dd0ae75eb64c49c178294214311140bc7c62763c839bedfac51cfa3180048fcfdbc81e0bb059327959a95b003093bf9b04e3918eebe0ac05aae2af93bb8014e3e0704c9a07636322335a3c663ec9fd9df8b7bf71d6e8183fefecfbfe0e50808e788bf3aaaea24abc0ee6d00eb102be955c07bd2b134e24cde6bdfbd922fdcf80deb1dbe09dc8972faeb0de3f080bfbb9d688dcf63906c91db762cc20cbf1e761804ee6ab85272b59bf8715509ccdcdbc038eb7ab7c13552f0eedbdc64bb1ccbacf808d42b27ca13475581cb35914e531fc84820bac04a5c6260b18adc6403c9d78d3807282321f53526da2c8f33500a0c90c75f95972c3c28366033c3f2c38beaaadc2804b74323792921a9cd34cd56a233f5768e3732bb41c157789371b110c5248446f80f395b7003a2eb1e39c624b9a707a6cb58c3cb6997932fc80662ae19c785a91f580b5e5172489541dfc581e116554b63de15fddf38ffed2b109394749c20b8f6ce3805a64908ec1ee443f9cff1793aa18d683308ae1bbd100498b5420e34c9a3c59de").to_vec(), + hex!("9e75edf06348b4330d1e88564111cb3d3000505f0e7b9012096b41c4eb3aaf947f6ea429080000806ad99dcfd0f2738b39c05d53a22890f969ba700ab74676cde1b3658e6a1d3b28").to_vec(), + ])) + } + + #[test] + fn verify_dip_commitment_proof_for_subject_successful() { + let (parachain_state_root, dip_commitment_proof) = get_dip_commitment_proof(); + // Only interested in the DIP commitment verification part, we skip everything + // else. + let proof = + DipDidProofWithVerifiedStateRoot::<_, (), (), (), (), (), ()>::with_state_root_and_dip_commitment_proof( + parachain_state_root, + dip_commitment_proof, + ); + let proof_verification_result = proof + .verify_dip_commitment_proof_for_subject::( + &AccountId32::from_ss58check("4pevjN6chwUqWPVaoUF6naRmZyrA4XWfdK8nLQLEjufgW55c").unwrap(), + ) + .unwrap(); + assert_eq!( + proof_verification_result.dip_commitment, + hex!("51b175db0cd3a4071aaa1cdde8f3cc562b9618961d8a80ed77981ec98b91da45").into() + ); + } + + #[test] + fn verify_dip_commitment_proof_for_subject_multi_storage() { + // Storage proof generated at local Peregrine instance (with new storage hasher) + // for storage keys + // [`0xb375edf06348b4330d1e88564111cb3d5bf19e4ed2927982e234d989e812f3f366a25a7fa9282d4c8e07cfeb5ec4b0f44cec8bb650a6e6ff111f30916b9ca56a4542f70764e95d7ceb6736d981b2d95d01a12dfa1fa4ab9a0000`, '0xb375edf06348b4330d1e88564111cb3d5bf19e4ed2927982e234d989e812f3f3324b39c02c5b89191d516a1cb2438497d68f8ab82a2af4df66983a1fd0992711686c0fbf8ff8437552365e26f488c17c01a12dfa1fa4ab9a0000] + // ([`dipProvider::identityCommitments(4pevjN6chwUqWPVaoUF6naRmZyrA4XWfdK8nLQLEjufgW55c, 0)`, `dipProvider::identityCommitments(4smPiDNt9eLaJCe6uq1hGG3kWEmB3ooMpbGbSp1VF9D2vwEg, 0)`]) + let parachain_state_root: H256 = + hex!("506f0aa6af2e04874ab94835b359ab97a9cca1d1773777b5004da93ffd08a088").into(); + let dip_commitment_proof = DipCommitmentStateProof(vec![ + hex!("7f34024b39c02c5b89191d516a1cb2438497d68f8ab82a2af4df66983a1fd0992711686c0fbf8ff8437552365e26f488c17c01a12dfa1fa4ab9a0000806e5f8a286a025f2631fc7e903f16f4732de04623a411da2abda7c81eb7a42e31").to_vec(), + hex!("7f3406a25a7fa9282d4c8e07cfeb5ec4b0f44cec8bb650a6e6ff111f30916b9ca56a4542f70764e95d7ceb6736d981b2d95d01a12dfa1fa4ab9a00008051b175db0cd3a4071aaa1cdde8f3cc562b9618961d8a80ed77981ec98b91da45").to_vec(), + hex!("800c8080da28793d083b197f8d92fc3e77f5064436f1d8eea0fbea56ddb936aba6544500802ac01dbcb6bbdd9e784796b03fa804e22e6c7d552e6432d2e782c78f1fd62ed080b2aafe11c416356c5a97e233670962facb2a18944c3bdc4b9e27f1fa67a5bafe").to_vec(), + hex!("80ffff80353e4d164b13c87910044f1b4e76277e404a0ab46a7cd6c33a65aaadc2375ba88007b1390da34b4dce1328430fd924a6e193517a8148dd70a912c0dc2f7f8d2d4c8056524aa90f9d9955e0f11cb65a3301b3feec8236f6c506f60851cd9182f6dabe809ce9739227a80b74d58ca2731bb7c95fef30c44badeaed70d4cae8ece37b875180f42657aef3c7a9da89d7fa2ead23197e6c7a0d9a56224c30a23d5e72af213b568011c66e7235c652b25a2599a23850ab0b2c45aa6adc8d0340956aeb06f677780e8014e3e0704c9a07636322335a3c663ec9fd9df8b7bf71d6e8183fefecfbfe0e50801f8da04e41cb0e58c2899f258c3606a4f8a9029ce7dd2831fe2f18748714e1b08063a92863797f3ff47d446998ab380abf3e19f9ad052378a890c969e3665ccfc480ac8fc0b324e4a48b6995b1ace16c16896f31a7d342fdd8c2812aebc74b3b1b2080f567d19109fd00674a7d71a364d5036670bd8413170968a2cd7e204ee9762b1d809183d04fbdc18d7dd79fe20d07131563bef1b21aa5cb6861a2dab4fb6173cbe3802588aac7065dd9e759283fcbf53a0c3696e5669564f92ee17d0ea9fabfe9e82880f395b7003a2eb1e39c624b9a707a6cb58c3cb6997932fc80662ae19c785a91f580b5e5172489541dfc581e116554b63de15fddf38ffed2b109394749c20b8f6ce38019dd7bc351b0ddab367f9c10a27d0dad1669e16af8a8a58c577ce0b2fb26ce8d").to_vec(), + hex!("9e75edf06348b4330d1e88564111cb3d3000505f0e7b9012096b41c4eb3aaf947f6ea429080000803cfa8887e3f3605330a40b74e99d031b21aeba65d2ef7f35c24a5cefab5291f1").to_vec(), + hex!("9f0bf19e4ed2927982e234d989e812f3f3480080f3fd8dffe32bd8f539044baf30efd07801d87ea5280154588c3abd3e325f578d8048f06290dfec2596fa70eaca62ea496d3dc0cd2f51fd40c61b58d7e5b476eebd").to_vec(), + ]); + // Only interested in the DIP commitment verification part, we skip everything + // else. + let proof = + DipDidProofWithVerifiedStateRoot::<_, (), (), (), (), (), ()>::with_state_root_and_dip_commitment_proof( + parachain_state_root, + dip_commitment_proof, + ); + let proof_verification_result = proof + .verify_dip_commitment_proof_for_subject::( + &AccountId32::from_ss58check("4pevjN6chwUqWPVaoUF6naRmZyrA4XWfdK8nLQLEjufgW55c").unwrap(), + ) + .unwrap(); + assert_eq!( + proof_verification_result.dip_commitment, + hex!("51b175db0cd3a4071aaa1cdde8f3cc562b9618961d8a80ed77981ec98b91da45").into() + ); + } + + #[test] + fn verify_dip_commitment_proof_for_subject_wrong_provider_hasher() { + let (parachain_state_root, dip_commitment_proof) = get_dip_commitment_proof(); + // Only interested in the DIP commitment verification part, we skip everything + // else. + let proof = + DipDidProofWithVerifiedStateRoot::<_, (), (), (), (), (), ()>::with_state_root_and_dip_commitment_proof( + parachain_state_root, + dip_commitment_proof, + ); + assert_err!( + proof.verify_dip_commitment_proof_for_subject::( + // We try + &AccountId32::from_ss58check("4qVtUbkD2xqp9cqGDjViPpFPesJNdfoJvGeSUgturBxAPyBK").unwrap(), + ), + Error::DipCommitmentMerkleProof(MerkleProofError::InvalidProof) + ); + } + + #[test] + fn verify_dip_commitment_proof_for_subject_different_subject() { + let (parachain_state_root, dip_commitment_proof) = get_dip_commitment_proof(); + let proof = + DipDidProofWithVerifiedStateRoot::<_, (), (), (), (), (), ()>::with_state_root_and_dip_commitment_proof( + parachain_state_root, + dip_commitment_proof, + ); + assert_err!( + proof.verify_dip_commitment_proof_for_subject::( + &AccountId32::from_ss58check("4smPiDNt9eLaJCe6uq1hGG3kWEmB3ooMpbGbSp1VF9D2vwEg").unwrap(), + ), + Error::DipCommitmentMerkleProof(MerkleProofError::RequiredLeafNotRevealed) + ); + } + + #[test] + fn verify_dip_commitment_proof_for_subject_invalid_proof() { + let (parachain_state_root, dip_commitment_proof) = get_dip_commitment_proof(); + // Remove last part of the blinded component to get an invalid proof. + let (_, invalid_blinded_proof) = dip_commitment_proof.0.split_last().unwrap(); + let invalid_dip_commitment_proof = DipCommitmentStateProof(invalid_blinded_proof.to_owned()); + let proof = + DipDidProofWithVerifiedStateRoot::<_, (), (), (), (), (), ()>::with_state_root_and_dip_commitment_proof( + parachain_state_root, + invalid_dip_commitment_proof, + ); + assert_err!( + proof.verify_dip_commitment_proof_for_subject::( + &AccountId32::from_ss58check("4qVtUbkD2xqp9cqGDjViPpFPesJNdfoJvGeSUgturBxAPyBK").unwrap(), + ), + Error::DipCommitmentMerkleProof(MerkleProofError::InvalidProof) + ); + } +} + +mod dip_did_proof_with_verified_subject_commitment { + use did::{ + did_details::{DidPublicKeyDetails, DidVerificationKey}, + DidVerificationKeyRelationship, + }; + use frame_support::assert_err; + use hex_literal::hex; + use pallet_did_lookup::linkable_account::LinkableAccountId; + use sp_core::{ed25519, ConstU32, H256}; + use sp_runtime::{ + traits::{BlakeTwo256, Keccak256}, + AccountId32, BoundedVec, + }; + + use crate::{ + DidMerkleProof, DipDidProofWithVerifiedSubjectCommitment, Error, RevealedDidKey, TimeBoundDidSignature, + }; + + impl< + Commitment, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > + DipDidProofWithVerifiedSubjectCommitment< + Commitment, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > where + ConsumerBlockNumber: Default, + { + fn with_commitment_and_dip_proof( + commitment: Commitment, + dip_proof: DidMerkleProof< + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + >, + ) -> Self { + Self { + dip_commitment: commitment, + dip_proof, + signature: TimeBoundDidSignature::default(), + } + } + } + + // DIP proof generated on Peregrine via the `dipProvider::generateProof` runtime + // API. + #[allow(clippy::type_complexity)] + fn get_dip_proof() -> ( + H256, + DidMerkleProof>, LinkableAccountId>, + ) { + ( + hex!("1997d38bec607be35cab175edc55e2119e0138976021e1f938942c10f9f7b329").into(), + DidMerkleProof { + blinded: vec![ + hex!( + "8027f4809d06d6e9516f8bcbe97b3e1fa94f294b2606a11d00f1162c90bbdbaa0cbc77d480421f140adb34 + 53138eb8c4512f9cff60ee9a62502cbb0ddd30355235c12dbd318001ba7e874784b7c79fdc37d1584ff254 + efb6d167087dcb1227c704fd9f6c21d40080a92c5bdfcfbb286551bc43fb263980bc9148f3645f6bc0743c + 4292b88dc4039f8011e7fd2693a380b14bd3dd83736bec3bbcb7f70c7b7e0aaf30a03d2bbf96bd3b80c5a2 + 1afb7e16c0f8869ca44efbafddef083c89104fe153d0a77698a5aa1eef7d808cf84bd4fa37829f7229d507 + 3cbb504832fc88766def7b06930c5c27f7bf12a080dab6661eac3da9d306e8bbfdffb8ccc901239d8c1664 + 220062a4384224babea0" + ) + .to_vec(), + hex!("7f0400da6646d21f19b4d7d9f80d5beb103fbef7f4bb95eb94e0c02552175b1bff3a010000").to_vec(), + ], + revealed: vec![RevealedDidKey { + id: hex!("50da6646d21f19b4d7d9f80d5beb103fbef7f4bb95eb94e0c02552175b1bff3a").into(), + relationship: DidVerificationKeyRelationship::Authentication.into(), + details: DidPublicKeyDetails { + key: DidVerificationKey::Ed25519(ed25519::Public(hex!( + "43a72e714401762df66b68c26dfbdf2682aaec9f2474eca4613e424a0fbafd3c" + ))) + .into(), + block_number: 0, + }, + } + .into()], + }, + ) + } + + #[test] + fn verify_dip_proof_successful() { + let (dip_commitment, dip_proof) = get_dip_proof(); + let proof = DipDidProofWithVerifiedSubjectCommitment::<_, _, _, _, _, _, ()>::with_commitment_and_dip_proof( + dip_commitment, + dip_proof.clone(), + ); + let proof_verification_result = proof.verify_dip_proof::().unwrap(); + assert_eq!( + proof_verification_result.revealed_leaves.into_inner(), + vec![dip_proof.revealed.first().unwrap().to_owned()] + ); + } + + #[test] + fn verify_dip_proof_wrong_merkle_hasher() { + let (dip_commitment, dip_proof) = get_dip_proof(); + let proof = DipDidProofWithVerifiedSubjectCommitment::<_, _, _, _, _, _, ()>::with_commitment_and_dip_proof( + dip_commitment, + dip_proof, + ); + // Different hasher used for verification + assert_err!(proof.verify_dip_proof::(), Error::InvalidDidMerkleProof); + } + + #[test] + fn verify_dip_proof_too_many_leaves() { + let (dip_commitment, dip_proof) = get_dip_proof(); + let proof = DipDidProofWithVerifiedSubjectCommitment::<_, _, _, _, _, _, ()>::with_commitment_and_dip_proof( + dip_commitment, + dip_proof, + ); + // We set 0 as the maximum limit. + assert_err!(proof.verify_dip_proof::(), Error::TooManyLeavesRevealed); + } + + #[test] + fn verify_dip_proof_invalid_proof() { + let proof = + DipDidProofWithVerifiedSubjectCommitment::<_, (), (), (), (), (), ()>::with_commitment_and_dip_proof( + H256([100; 32]), + DidMerkleProof { + blinded: vec![vec![100; 32]], + revealed: Default::default(), + }, + ); + assert_err!(proof.verify_dip_proof::(), Error::InvalidDidMerkleProof); + } +} diff --git a/crates/kilt-dip-primitives/src/merkle_proofs/v0/relay_state/mod.rs b/crates/kilt-dip-primitives/src/merkle_proofs/v0/relay_state/mod.rs new file mode 100644 index 0000000000..5a08bc9dce --- /dev/null +++ b/crates/kilt-dip-primitives/src/merkle_proofs/v0/relay_state/mod.rs @@ -0,0 +1,248 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use parity_scale_codec::{Codec, Decode, Encode}; +use scale_info::TypeInfo; +use sp_core::U256; +use sp_runtime::{ + generic::Header, + traits::{AtLeast32BitUnsigned, Hash, Header as HeaderT, MaybeDisplay, Member}, +}; + +use crate::{ + merkle_proofs::v0::{ + input_common::{DidMerkleProof, DipCommitmentStateProof, ProviderHeadStateProof, TimeBoundDidSignature}, + provider_state::ParachainDipDidProof, + }, + traits::GetWithArg, + utils::OutputOf, + DipDidProofWithVerifiedStateRoot, Error, +}; + +#[cfg(test)] +mod tests; + +/// A DIP proof submitted to a relaychain consumer. +/// +/// The generic types indicate the following: +/// * `RelayBlockNumber`: The `BlockNumber` definition of the relaychain. +/// * `RelayHasher`: The hashing algorithm used by the relaychain. +/// * `KiltDidKeyId`: The DID key ID type configured by the KILT chain. +/// * `KiltAccountId`: The `AccountId` type configured by the KILT chain. +/// * `KiltBlockNumber`: The `BlockNumber` type configured by the KILT chain. +/// * `KiltWeb3Name`: The web3name type configured by the KILT chain. +/// * `KiltLinkableAccountId`: The linkable account ID type configured by the +/// KILT chain. +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo)] +pub struct RelayDipDidProof< + RelayBlockNumber: Copy + Into + TryFrom, + RelayHasher: Hash, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, +> { + /// The relaychain header for the relaychain block specified in the + /// `provider_head_proof`. + pub(crate) relay_header: Header, + /// The state proof for the given parachain head. + pub(crate) provider_head_proof: ProviderHeadStateProof, + /// The raw state proof for the DIP commitment of the given subject. + pub(crate) dip_commitment_proof: DipCommitmentStateProof, + /// The Merkle proof of the subject's DID details. + pub(crate) dip_proof: + DidMerkleProof, + /// The cross-chain DID signature. + pub(crate) signature: TimeBoundDidSignature, +} + +impl< + RelayBlockNumber: Member + sp_std::hash::Hash + Copy + MaybeDisplay + AtLeast32BitUnsigned + Codec + Into + TryFrom, + RelayHasher: Hash, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + > + RelayDipDidProof< + RelayBlockNumber, + RelayHasher, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + > +{ + /// Verifies the relaychain part of the state proof using the provided block + /// hash. + #[allow(clippy::type_complexity)] + pub fn verify_relay_header_with_block_hash( + self, + block_hash: &OutputOf, + ) -> Result< + RelayDipDidProofWithVerifiedRelayStateRoot< + OutputOf, + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + >, + Error, + > { + if block_hash != &self.relay_header.hash() { + return Err(Error::InvalidRelayHeader); + } + + Ok(RelayDipDidProofWithVerifiedRelayStateRoot { + relay_state_root: self.relay_header.state_root, + provider_head_proof: self.provider_head_proof, + dip_commitment_proof: self.dip_commitment_proof, + dip_proof: self.dip_proof, + signature: self.signature, + }) + } + + /// Verifies the relaychain part of the state proof using the block hash + /// returned by the provided implementation. + /// + /// The generic types indicate the following: + /// * `RelayHashStore`: The type that returns a relaychain block hash given + /// a relaychain block number. + #[allow(clippy::type_complexity)] + pub fn verify_relay_header( + self, + ) -> Result< + RelayDipDidProofWithVerifiedRelayStateRoot< + OutputOf, + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + >, + Error, + > + where + RelayHashStore: GetWithArg>>, + { + let relay_block_hash = RelayHashStore::get(&self.relay_header.number).ok_or(Error::RelayBlockNotFound)?; + self.verify_relay_header_with_block_hash(&relay_block_hash) + } +} + +/// A DIP proof submitted to a relaychain consumer that has had the proof header +/// verified against a given block hash. +/// +/// The generic types indicate the following: +/// * `StateRoot`: The type of the state root used by the relaychain. +/// * `RelayBlockNumber`: The `BlockNumber` definition of the relaychain. +/// * `KiltDidKeyId`: The DID key ID type configured by the KILT chain. +/// * `KiltAccountId`: The `AccountId` type configured by the KILT chain. +/// * `KiltBlockNumber`: The `BlockNumber` type configured by the KILT chain. +/// * `KiltWeb3Name`: The web3name type configured by the KILT chain. +/// * `KiltLinkableAccountId`: The linkable account ID type configured by the +/// KILT chain. +#[derive(Debug, PartialEq, Eq)] +pub struct RelayDipDidProofWithVerifiedRelayStateRoot< + StateRoot, + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, +> { + /// The verified state root for the relaychain at the block specified in the + /// proof. + pub(crate) relay_state_root: StateRoot, + /// The state proof for the given parachain head. + pub(crate) provider_head_proof: ProviderHeadStateProof, + /// The raw state proof for the DIP commitment of the given subject. + pub(crate) dip_commitment_proof: DipCommitmentStateProof, + /// The Merkle proof of the subject's DID details. + pub(crate) dip_proof: + DidMerkleProof, + /// The cross-chain DID signature. + pub(crate) signature: TimeBoundDidSignature, +} + +impl< + StateRoot, + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + > + RelayDipDidProofWithVerifiedRelayStateRoot< + StateRoot, + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + > +{ + /// Verifies the head data of the state proof for the provider with the + /// given para ID. + /// + /// The generic types indicate the following: + /// * `RelayHasher`: The head data hashing algorithm used by the relaychain. + /// * `ProviderHeader`: The type of the parachain header to be revealed in + /// the state proof. + #[allow(clippy::type_complexity)] + pub fn verify_provider_head_proof( + self, + provider_para_id: u32, + ) -> Result< + DipDidProofWithVerifiedStateRoot< + OutputOf, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + RelayBlockNumber, + >, + Error, + > + where + RelayHasher: Hash, + ProviderHeader: Decode + HeaderT, Number = KiltBlockNumber>, + { + let parachain_dip_proof = ParachainDipDidProof { + provider_head_proof: self.provider_head_proof, + dip_commitment_proof: self.dip_commitment_proof, + dip_proof: self.dip_proof, + signature: self.signature, + }; + + parachain_dip_proof.verify_provider_head_proof_with_state_root::( + provider_para_id, + &self.relay_state_root, + ) + } +} diff --git a/crates/kilt-dip-primitives/src/merkle_proofs/v0/relay_state/tests.rs b/crates/kilt-dip-primitives/src/merkle_proofs/v0/relay_state/tests.rs new file mode 100644 index 0000000000..42aef97c56 --- /dev/null +++ b/crates/kilt-dip-primitives/src/merkle_proofs/v0/relay_state/tests.rs @@ -0,0 +1,315 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +mod relay_did_dip_proof { + use frame_support::assert_err; + use hex_literal::hex; + use parity_scale_codec::Codec; + use sp_core::{H256, U256}; + use sp_runtime::{ + generic::Header, + traits::{AtLeast32BitUnsigned, BlakeTwo256, Hash, MaybeDisplay, Member}, + Digest, DigestItem, + }; + + use crate::{Error, RelayDipDidProof}; + + impl< + RelayBlockNumber: Member + + sp_std::hash::Hash + + Copy + + MaybeDisplay + + AtLeast32BitUnsigned + + Codec + + Into + + TryFrom, + RelayHasher: Hash, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + > + RelayDipDidProof< + RelayBlockNumber, + RelayHasher, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + > where + RelayBlockNumber: Default, + KiltDidKeyId: Default, + KiltBlockNumber: Default, + { + fn with_header(header: Header) -> Self { + Self { + relay_header: header, + dip_commitment_proof: Default::default(), + dip_proof: Default::default(), + provider_head_proof: Default::default(), + signature: Default::default(), + } + } + } + + #[test] + fn verify_relay_header_with_block_hash_successful() { + // Polkadot header at block `19_663_508` with hash + // `0x6e87866fb4f412e1e691e25d294019a7695d5a756ee7bc8d012c25177b5e1e13` + let header = Header:: { + parent_hash: hex!("444340c3fdd24466307d23f2f38c4a7787b7092aa9e7e614534a4efa4a2e7e4a").into(), + number: 19_663_508, + state_root: hex!("623b36bddae282e9fefab4707697171a594fdb27e90fd4ada4ebcc356438b070").into(), + extrinsics_root: hex!("6784dc16f12dc55d585bb4d8dc0dd08682e1c5714e081821ac2de07df41bb9fb").into(), + digest: Digest { logs: [ + DigestItem::PreRuntime(*b"BABE", hex!("03c00000009740fa1000000000e088288be02358a7c702eee7d31fdf4bb3f47a2e24bedf8db390405bf3cca51c09f060dd63ae5632993fbc156062635eb921b75588bab688c5d2b62dc9e13e0464ceb3835c9c9806b33718282904b0dcbf2a9ce7f7b8a20f3f4696b50473e406").into()), + DigestItem::Consensus(*b"BEEF", hex!("03ea39d1df20f8a9f2509f42dc42e9868d53c5a5581a95660ca12b74d5acfd92fa").into()), + DigestItem::Seal(*b"BABE", hex!("680c6d5888043c8a468fced27d321b3d0937a6fa4961430edb5be4ee118628698a5c3a05969668bcf229dbd63d0179c92fe2242f8fecdd203a8f5dbb584c348d").into()) + ].to_vec() }, + }; + // Only interested in the header verification part, we skip everything else. + let proof = RelayDipDidProof::<_, _, (), (), (), (), ()>::with_header(header); + let proof_verification_result = proof + .verify_relay_header_with_block_hash( + &hex!("6e87866fb4f412e1e691e25d294019a7695d5a756ee7bc8d012c25177b5e1e13").into(), + ) + .unwrap(); + assert_eq!( + proof_verification_result.relay_state_root, + hex!("623b36bddae282e9fefab4707697171a594fdb27e90fd4ada4ebcc356438b070").into() + ); + } + + #[test] + fn verify_relay_header_with_block_hash_wrong_hash() { + // Polkadot header at block `19_663_508` with hash + // `0x6e87866fb4f412e1e691e25d294019a7695d5a756ee7bc8d012c25177b5e1e13` + let header = Header:: { + parent_hash: hex!("444340c3fdd24466307d23f2f38c4a7787b7092aa9e7e614534a4efa4a2e7e4a").into(), + number: 19_663_508, + state_root: hex!("623b36bddae282e9fefab4707697171a594fdb27e90fd4ada4ebcc356438b070").into(), + extrinsics_root: hex!("6784dc16f12dc55d585bb4d8dc0dd08682e1c5714e081821ac2de07df41bb9fb").into(), + digest: Digest { logs: [ + DigestItem::PreRuntime(*b"BABE", hex!("03c00000009740fa1000000000e088288be02358a7c702eee7d31fdf4bb3f47a2e24bedf8db390405bf3cca51c09f060dd63ae5632993fbc156062635eb921b75588bab688c5d2b62dc9e13e0464ceb3835c9c9806b33718282904b0dcbf2a9ce7f7b8a20f3f4696b50473e406").into()), + DigestItem::Consensus(*b"BEEF", hex!("03ea39d1df20f8a9f2509f42dc42e9868d53c5a5581a95660ca12b74d5acfd92fa").into()), + DigestItem::Seal(*b"BABE", hex!("680c6d5888043c8a468fced27d321b3d0937a6fa4961430edb5be4ee118628698a5c3a05969668bcf229dbd63d0179c92fe2242f8fecdd203a8f5dbb584c348d").into()) + ].to_vec() }, + }; + let proof = RelayDipDidProof::<_, _, (), (), (), (), ()>::with_header(header); + assert_err!( + // Using a different block hash for verification + proof.verify_relay_header_with_block_hash(&H256::default()), + Error::InvalidRelayHeader + ); + } +} + +mod relay_dip_did_proof_with_verified_relay_state_root { + use frame_support::assert_err; + use frame_system::pallet_prelude::HeaderFor; + use hex_literal::hex; + use sp_core::H256; + use sp_runtime::traits::{BlakeTwo256, Keccak256}; + use spiritnet_runtime::Runtime as SpiritnetRuntime; + + use crate::{ + state_proofs::MerkleProofError, Error, ProviderHeadStateProof, RelayDipDidProofWithVerifiedRelayStateRoot, + }; + + impl< + StateRoot, + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + > + RelayDipDidProofWithVerifiedRelayStateRoot< + StateRoot, + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + > where + RelayBlockNumber: Default, + KiltDidKeyId: Default, + KiltBlockNumber: Default, + { + fn with_relay_state_root_and_provider_head_proof( + relay_state_root: StateRoot, + provider_head_proof: ProviderHeadStateProof, + ) -> Self { + Self { + relay_state_root, + provider_head_proof, + dip_commitment_proof: Default::default(), + dip_proof: Default::default(), + signature: Default::default(), + } + } + } + + #[test] + fn verify_provider_head_proof_successful() { + // Storage proof generated at Polkadot block `19_663_508` with hash + // `0x6e87866fb4f412e1e691e25d294019a7695d5a756ee7bc8d012c25177b5e1e13` for + // storage key + // `0xcd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c32c0cfd6c23b92a7826080000` + // (`paras::heads(2_086)`) + let relay_state_root: H256 = hex!("623b36bddae282e9fefab4707697171a594fdb27e90fd4ada4ebcc356438b070").into(); + let provider_head_proof = ProviderHeadStateProof { + relay_block_number: 19_663_508, + proof: vec![ + hex!("560cfd6c23b92a7826080000f102e902fb1bd938b2f4fcea70641da8e64e0e11098b92b767279227cdfdc0ae9500da99d27e5f012937179bfe939750c770f2aa6e84c6b8cf9d0aa9ab852243ceb78e3eeb93fc56eacc28c5503a155c4d8bc7ee4797c38e212428cefff0a7ad19b28ebbab793e64080661757261204a207d0800000000056175726101017cda19117c87384aaebfd2ae546771bcfbfe7011a91119932883382cc62be3050d745c9734f422228c7c43d87e6172519019829b14b2d3b64afafb1fb7d3a683").to_vec(), + hex!("80021080f0c4027f5eba380b623a2d3382ab03961b2b7e753e62d3475a6940207db367cc80fde8c5a37120e2f1d987f5302783d22f8ac1b213c580030a7f5b15e706df6262").to_vec(), + hex!("8004648019885bbc2709cbd3a89f9a8813867f322e5663c99fdea9af3ff2ba0010455d5d8091eb4873541a81f69d22ccd903b864c36c747910eec7433d947e5a61f87eb7db80ca85a51ad63cbcb7a988023f3b082492a6937f9957c029eb34d6d618279d232d8048dcb685fcf3963e7697a630c6ce64ff06f325063bb05e34e679eb01a4e2b644").to_vec(), + hex!("80ffff8003ff6c42a935aca27c743910dbb62aae8009854a21741d74080abb406c26b1f58084a7c1351a2986c948c9a111c955d0f8635e4bd305c24f9b6680405fdce955a180ed003737744c7fba94d0c2cb57f96e7bf3310d9c7a285ae789e25af8b79091b38017a0734a39f27a75f6f648bca2facca2381325b529d32bcf82e75aaf6b7d82dd8042e2e666a38ce9fdbe60164d0c3a351ce06c931931d2cd6650378c1ad691c21480d0cc4967746360ee3895a6937608d7f36674426928790cb8ca7426289ad74469804e8940ff6b30dfb0f92341c3a738f262bed9ca03de9b868eb99cfc282aa7786780acda22345d4597dfe6fd831509b944254e26a00fd56e77bc2cb780c0775a520a808c0dae720727cec94dbc853812332bfd6d5f2cc5e287bcd1e5efc530053dbd2280a16a8184b9f2e555d4991995fd479b1ee7b35653f2215f74f822436dbbb2331580984648137ae9c8ecf33f878cedffdba73fb4282ba3ec033102aa6d7442466517801132afeed824c180373b2450b32c72c84a21cdfddbe0f1bf8e76d6958963669580357f2107df0a82f2605f90e39c5665bdf69e1d6222bc425f8390bde67c1d414780c4e048c8dc0ea614a190375a1b215c8e8ff5f5098cd43a93d59be907a2258a74807ad4cd868c49acc40e389d45a1e7e7629e666972ed747c67b607b07f637c1f0b8021bbaa444a77faac92b771c0e1b19162ace64b5ce745892d3ce59f820cba2dc7").to_vec(), + hex!("9e710b30bd2eab0352ddcc26417aa1945fd3802284b6ec6d4b3138fca93d003a58421ba947ecbc14c39e76572061105bbc568b809f8b23e74053dd98b58424e102ba5ac16f028714ec16a61522011fe6e16771ff80ed3e43ac278948816e8c9e8adda2dbeefe552702cf8144fd9b50e0b8db99bfcf80694abb8b23315ab79cdb22ca6826e867a9157415a832ad38f376dd819107d3ea80b9aee043e378f8313e68a6030679ccf3880fa1e7ab19b6244b5c262b7a152f004c5f03c716fb8fff3de61a883bb76adb34a2040080f15f37adeb10597dac54c2c65393277b2ca62aa27b2d16a23a78a4cc55ef15bb8008a0c609ab4888f02c2545c002153297c2641c5a7b4f3d8e25c634e721f80bea80b6617c764df278313c426c46961ccde8ee7a03f9007b74bc8bc6c49d1583cf7d801c9a4a3457ad4a568dd4c9abe231304689c9bec78be932ef0a2d30690ca428848059ef8bbe3a06c98792f41b3e0a6cdf1f157d9be85e12a7c1daf9c30f969daba4").to_vec(), + hex!("9f0b3c252fcb29d88eff4f3de5de4476c3ffbf805254dc9131b269f3bbbb71f58a76a5034b2bc2faaab0d1cf45c3819dc6e69740804bc059c3d96f627e09a3b6c0f9851d902f84ac68006617289ac0b7d0a272b36280d97e2394406f94be4266da29b6fe7f3178059525eaf3c9b540064389af020bf180636959b43018d3ff8a55246d5874a16c93e85bd2a58c82ebfc1b54dd9b2a7d0780d3c1a10188200f31459d722f7efc693736d1a36af5644fd949b2e411d7942597800328f24d0485b9701135913a569f6ccbf261a05d055183abf3e4ecb4e4375b7c80f3229cd59de7b1e604f110cbcf814466f2d2973e9bdb6c106a662c576e0820e480b66b29cbd45f93602dbc9f1175407c6f69bd686d23dd22a8f0dfe9cff08843ad80ddb2d426c0c546068b429e77253e0a8a32e818151f5fc031e899a0f6acad157580ea7fb3cad8e128cc295194658016f4865ef37501e5759fb4f15cb2ecb689e85e80e9f3cac1b25842da7fbaf947952dc30329a1d19037ab21baed3851acbee629f6800d898e2a4a6ee9969a233c4741e4441c0fe393104b3cfc5adcf348f3ef20fc7480ac6c622536e593ae3c9d423a461faafc7abbf01ecb129e69d66f3382eaf484dc80c7ba3cadffaea5acd013dba51c96129ae93ea6cd45f3930e4302f5b100f6deae806f29f805e30029363e42381d6609ecb6837411bd6fd676c0a37621a3b5588101").to_vec() + ], + }; + // Only interested in the parachain head verification part, we skip everything + // else. + let proof = RelayDipDidProofWithVerifiedRelayStateRoot::<_, _, (), (), _, (), ()>::with_relay_state_root_and_provider_head_proof(relay_state_root, provider_head_proof); + let proof_verification_result = proof + .verify_provider_head_proof::>(2_086) + .unwrap(); + assert_eq!( + proof_verification_result.state_root, + hex!("2937179bfe939750c770f2aa6e84c6b8cf9d0aa9ab852243ceb78e3eeb93fc56").into() + ); + } + + #[test] + fn verify_provider_head_proof_multi_storage() { + // Storage proof generated at Polkadot block `19_663_508` with hash + // `0x6e87866fb4f412e1e691e25d294019a7695d5a756ee7bc8d012c25177b5e1e13` for + // storage keys + // [`0xcd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c32c0cfd6c23b92a7826080000`, `0xcd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c3b6ff6f7d467b87a9e8030000`] + // ([`paras::heads(2_086)`, `paras::heads(1_000)]`) + let relay_state_root: H256 = hex!("623b36bddae282e9fefab4707697171a594fdb27e90fd4ada4ebcc356438b070").into(); + let provider_head_proof = ProviderHeadStateProof { + relay_block_number: 19_663_508, + proof: vec![ + hex!("560cfd6c23b92a7826080000f102e902fb1bd938b2f4fcea70641da8e64e0e11098b92b767279227cdfdc0ae9500da99d27e5f012937179bfe939750c770f2aa6e84c6b8cf9d0aa9ab852243ceb78e3eeb93fc56eacc28c5503a155c4d8bc7ee4797c38e212428cefff0a7ad19b28ebbab793e64080661757261204a207d0800000000056175726101017cda19117c87384aaebfd2ae546771bcfbfe7011a91119932883382cc62be3050d745c9734f422228c7c43d87e6172519019829b14b2d3b64afafb1fb7d3a683").to_vec(), + hex!("56ff6f7d467b87a9e80300009903910331d9f8f427be99ba3b36ad6f66c49b5448e16745fc3cbe08821204e2e94c9abe1ef65e01dc0f3c52d9aaef735eae1aa679e6c3020e993fb3eef8fab9c32cc7b55dfc85362c771d9a07e5b6cf9f8e30b67598c513ca087574ecce0b3e205eb4de8783a4630c0661757261204a207d08000000000452505352906ca2562dd3ddae0ecb7076465e223753e76792653f739d5dfb00ad76a6b3607d4a2ab00405617572610101dadcb6f606d8a71dc6d0d4d20ccc3bd67bae8816c86491b14fa899242cd872f3bf5fe9635d4414f4329a578a0627cf367dcaa3e86beca64a9aaef9afd124c701").to_vec(), + hex!("80021080f0c4027f5eba380b623a2d3382ab03961b2b7e753e62d3475a6940207db367cc80fde8c5a37120e2f1d987f5302783d22f8ac1b213c580030a7f5b15e706df6262").to_vec(), + hex!("8004648019885bbc2709cbd3a89f9a8813867f322e5663c99fdea9af3ff2ba0010455d5d8091eb4873541a81f69d22ccd903b864c36c747910eec7433d947e5a61f87eb7db80ca85a51ad63cbcb7a988023f3b082492a6937f9957c029eb34d6d618279d232d8048dcb685fcf3963e7697a630c6ce64ff06f325063bb05e34e679eb01a4e2b644").to_vec(), + hex!("80d510805396188100731505c3fe5f51e7d4a9c6e6e4cd2c50ff6d122f5f091a186b2f9780e69515c0c399ad09a7b5da0afb5a8bbd22c6873b69f9f2da18e26a8bd04c6e9d80d647e804958d947c20337a2ac3714b3eca41be52847542b065da3614230decab806dbb5b1913c89acb68a2e85013c4b7adf37ab010cf9b9d7346348d0ca9aafd4a80702af779edd6e8d659600cbe342947238af804d41589116a3dd7fb48905aeab18076a51b70378cbf602d939a885bbad80c94ee9325398105ec2173324bd7f59b55").to_vec(), + hex!("80ffff8003ff6c42a935aca27c743910dbb62aae8009854a21741d74080abb406c26b1f58084a7c1351a2986c948c9a111c955d0f8635e4bd305c24f9b6680405fdce955a180ed003737744c7fba94d0c2cb57f96e7bf3310d9c7a285ae789e25af8b79091b38017a0734a39f27a75f6f648bca2facca2381325b529d32bcf82e75aaf6b7d82dd8042e2e666a38ce9fdbe60164d0c3a351ce06c931931d2cd6650378c1ad691c21480d0cc4967746360ee3895a6937608d7f36674426928790cb8ca7426289ad74469804e8940ff6b30dfb0f92341c3a738f262bed9ca03de9b868eb99cfc282aa7786780acda22345d4597dfe6fd831509b944254e26a00fd56e77bc2cb780c0775a520a808c0dae720727cec94dbc853812332bfd6d5f2cc5e287bcd1e5efc530053dbd2280a16a8184b9f2e555d4991995fd479b1ee7b35653f2215f74f822436dbbb2331580984648137ae9c8ecf33f878cedffdba73fb4282ba3ec033102aa6d7442466517801132afeed824c180373b2450b32c72c84a21cdfddbe0f1bf8e76d6958963669580357f2107df0a82f2605f90e39c5665bdf69e1d6222bc425f8390bde67c1d414780c4e048c8dc0ea614a190375a1b215c8e8ff5f5098cd43a93d59be907a2258a74807ad4cd868c49acc40e389d45a1e7e7629e666972ed747c67b607b07f637c1f0b8021bbaa444a77faac92b771c0e1b19162ace64b5ce745892d3ce59f820cba2dc7").to_vec(), + hex!("9e710b30bd2eab0352ddcc26417aa1945fd3802284b6ec6d4b3138fca93d003a58421ba947ecbc14c39e76572061105bbc568b809f8b23e74053dd98b58424e102ba5ac16f028714ec16a61522011fe6e16771ff80ed3e43ac278948816e8c9e8adda2dbeefe552702cf8144fd9b50e0b8db99bfcf80694abb8b23315ab79cdb22ca6826e867a9157415a832ad38f376dd819107d3ea80b9aee043e378f8313e68a6030679ccf3880fa1e7ab19b6244b5c262b7a152f004c5f03c716fb8fff3de61a883bb76adb34a2040080f15f37adeb10597dac54c2c65393277b2ca62aa27b2d16a23a78a4cc55ef15bb8008a0c609ab4888f02c2545c002153297c2641c5a7b4f3d8e25c634e721f80bea80b6617c764df278313c426c46961ccde8ee7a03f9007b74bc8bc6c49d1583cf7d801c9a4a3457ad4a568dd4c9abe231304689c9bec78be932ef0a2d30690ca428848059ef8bbe3a06c98792f41b3e0a6cdf1f157d9be85e12a7c1daf9c30f969daba4").to_vec(), + hex!("9f0b3c252fcb29d88eff4f3de5de4476c3ffbf805254dc9131b269f3bbbb71f58a76a5034b2bc2faaab0d1cf45c3819dc6e69740804bc059c3d96f627e09a3b6c0f9851d902f84ac68006617289ac0b7d0a272b36280d97e2394406f94be4266da29b6fe7f3178059525eaf3c9b540064389af020bf180636959b43018d3ff8a55246d5874a16c93e85bd2a58c82ebfc1b54dd9b2a7d0780d3c1a10188200f31459d722f7efc693736d1a36af5644fd949b2e411d7942597800328f24d0485b9701135913a569f6ccbf261a05d055183abf3e4ecb4e4375b7c80f3229cd59de7b1e604f110cbcf814466f2d2973e9bdb6c106a662c576e0820e480b66b29cbd45f93602dbc9f1175407c6f69bd686d23dd22a8f0dfe9cff08843ad80ddb2d426c0c546068b429e77253e0a8a32e818151f5fc031e899a0f6acad157580ea7fb3cad8e128cc295194658016f4865ef37501e5759fb4f15cb2ecb689e85e80e9f3cac1b25842da7fbaf947952dc30329a1d19037ab21baed3851acbee629f6800d898e2a4a6ee9969a233c4741e4441c0fe393104b3cfc5adcf348f3ef20fc7480ac6c622536e593ae3c9d423a461faafc7abbf01ecb129e69d66f3382eaf484dc80c7ba3cadffaea5acd013dba51c96129ae93ea6cd45f3930e4302f5b100f6deae806f29f805e30029363e42381d6609ecb6837411bd6fd676c0a37621a3b5588101").to_vec() + ], + }; + // Only interested in the parachain head verification part, we skip everything + // else. + let proof = RelayDipDidProofWithVerifiedRelayStateRoot::<_, _, (), (), _, (), ()>::with_relay_state_root_and_provider_head_proof(relay_state_root, provider_head_proof); + let proof_verification_result = proof + .verify_provider_head_proof::>(2_086) + .unwrap(); + assert_eq!( + proof_verification_result.state_root, + hex!("2937179bfe939750c770f2aa6e84c6b8cf9d0aa9ab852243ceb78e3eeb93fc56").into() + ); + } + + #[test] + fn verify_provider_head_proof_wrong_hasher() { + // Storage proof generated at Polkadot block `19_663_508` with hash + // `0x6e87866fb4f412e1e691e25d294019a7695d5a756ee7bc8d012c25177b5e1e13` for + // storage key + // `0xcd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c32c0cfd6c23b92a7826080000` + // (`paras::heads(2_086)`) + let relay_state_root: H256 = hex!("623b36bddae282e9fefab4707697171a594fdb27e90fd4ada4ebcc356438b070").into(); + let provider_head_proof = ProviderHeadStateProof { + relay_block_number: 19_663_508, + proof: vec![ + hex!("560cfd6c23b92a7826080000f102e902fb1bd938b2f4fcea70641da8e64e0e11098b92b767279227cdfdc0ae9500da99d27e5f012937179bfe939750c770f2aa6e84c6b8cf9d0aa9ab852243ceb78e3eeb93fc56eacc28c5503a155c4d8bc7ee4797c38e212428cefff0a7ad19b28ebbab793e64080661757261204a207d0800000000056175726101017cda19117c87384aaebfd2ae546771bcfbfe7011a91119932883382cc62be3050d745c9734f422228c7c43d87e6172519019829b14b2d3b64afafb1fb7d3a683").to_vec(), + hex!("80021080f0c4027f5eba380b623a2d3382ab03961b2b7e753e62d3475a6940207db367cc80fde8c5a37120e2f1d987f5302783d22f8ac1b213c580030a7f5b15e706df6262").to_vec(), + hex!("8004648019885bbc2709cbd3a89f9a8813867f322e5663c99fdea9af3ff2ba0010455d5d8091eb4873541a81f69d22ccd903b864c36c747910eec7433d947e5a61f87eb7db80ca85a51ad63cbcb7a988023f3b082492a6937f9957c029eb34d6d618279d232d8048dcb685fcf3963e7697a630c6ce64ff06f325063bb05e34e679eb01a4e2b644").to_vec(), + hex!("80ffff8003ff6c42a935aca27c743910dbb62aae8009854a21741d74080abb406c26b1f58084a7c1351a2986c948c9a111c955d0f8635e4bd305c24f9b6680405fdce955a180ed003737744c7fba94d0c2cb57f96e7bf3310d9c7a285ae789e25af8b79091b38017a0734a39f27a75f6f648bca2facca2381325b529d32bcf82e75aaf6b7d82dd8042e2e666a38ce9fdbe60164d0c3a351ce06c931931d2cd6650378c1ad691c21480d0cc4967746360ee3895a6937608d7f36674426928790cb8ca7426289ad74469804e8940ff6b30dfb0f92341c3a738f262bed9ca03de9b868eb99cfc282aa7786780acda22345d4597dfe6fd831509b944254e26a00fd56e77bc2cb780c0775a520a808c0dae720727cec94dbc853812332bfd6d5f2cc5e287bcd1e5efc530053dbd2280a16a8184b9f2e555d4991995fd479b1ee7b35653f2215f74f822436dbbb2331580984648137ae9c8ecf33f878cedffdba73fb4282ba3ec033102aa6d7442466517801132afeed824c180373b2450b32c72c84a21cdfddbe0f1bf8e76d6958963669580357f2107df0a82f2605f90e39c5665bdf69e1d6222bc425f8390bde67c1d414780c4e048c8dc0ea614a190375a1b215c8e8ff5f5098cd43a93d59be907a2258a74807ad4cd868c49acc40e389d45a1e7e7629e666972ed747c67b607b07f637c1f0b8021bbaa444a77faac92b771c0e1b19162ace64b5ce745892d3ce59f820cba2dc7").to_vec(), + hex!("9e710b30bd2eab0352ddcc26417aa1945fd3802284b6ec6d4b3138fca93d003a58421ba947ecbc14c39e76572061105bbc568b809f8b23e74053dd98b58424e102ba5ac16f028714ec16a61522011fe6e16771ff80ed3e43ac278948816e8c9e8adda2dbeefe552702cf8144fd9b50e0b8db99bfcf80694abb8b23315ab79cdb22ca6826e867a9157415a832ad38f376dd819107d3ea80b9aee043e378f8313e68a6030679ccf3880fa1e7ab19b6244b5c262b7a152f004c5f03c716fb8fff3de61a883bb76adb34a2040080f15f37adeb10597dac54c2c65393277b2ca62aa27b2d16a23a78a4cc55ef15bb8008a0c609ab4888f02c2545c002153297c2641c5a7b4f3d8e25c634e721f80bea80b6617c764df278313c426c46961ccde8ee7a03f9007b74bc8bc6c49d1583cf7d801c9a4a3457ad4a568dd4c9abe231304689c9bec78be932ef0a2d30690ca428848059ef8bbe3a06c98792f41b3e0a6cdf1f157d9be85e12a7c1daf9c30f969daba4").to_vec(), + hex!("9f0b3c252fcb29d88eff4f3de5de4476c3ffbf805254dc9131b269f3bbbb71f58a76a5034b2bc2faaab0d1cf45c3819dc6e69740804bc059c3d96f627e09a3b6c0f9851d902f84ac68006617289ac0b7d0a272b36280d97e2394406f94be4266da29b6fe7f3178059525eaf3c9b540064389af020bf180636959b43018d3ff8a55246d5874a16c93e85bd2a58c82ebfc1b54dd9b2a7d0780d3c1a10188200f31459d722f7efc693736d1a36af5644fd949b2e411d7942597800328f24d0485b9701135913a569f6ccbf261a05d055183abf3e4ecb4e4375b7c80f3229cd59de7b1e604f110cbcf814466f2d2973e9bdb6c106a662c576e0820e480b66b29cbd45f93602dbc9f1175407c6f69bd686d23dd22a8f0dfe9cff08843ad80ddb2d426c0c546068b429e77253e0a8a32e818151f5fc031e899a0f6acad157580ea7fb3cad8e128cc295194658016f4865ef37501e5759fb4f15cb2ecb689e85e80e9f3cac1b25842da7fbaf947952dc30329a1d19037ab21baed3851acbee629f6800d898e2a4a6ee9969a233c4741e4441c0fe393104b3cfc5adcf348f3ef20fc7480ac6c622536e593ae3c9d423a461faafc7abbf01ecb129e69d66f3382eaf484dc80c7ba3cadffaea5acd013dba51c96129ae93ea6cd45f3930e4302f5b100f6deae806f29f805e30029363e42381d6609ecb6837411bd6fd676c0a37621a3b5588101").to_vec() + ], + }; + let proof = RelayDipDidProofWithVerifiedRelayStateRoot::<_, _, (), (), _, (), ()>::with_relay_state_root_and_provider_head_proof(relay_state_root, provider_head_proof); + assert_err!( + // Using a different hasher for verification + proof.verify_provider_head_proof::>(2_086), + Error::ParaHeadMerkleProof(MerkleProofError::InvalidProof) + ); + } + + #[test] + fn verify_provider_head_proof_wrong_para_id() { + // Storage proof generated at Polkadot block `19_663_508` with hash + // `0x6e87866fb4f412e1e691e25d294019a7695d5a756ee7bc8d012c25177b5e1e13` for + // storage key + // `0xcd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c3b6ff6f7d467b87a9e8030000` + // (`paras::heads(1_000)`) + let relay_state_root: H256 = hex!("623b36bddae282e9fefab4707697171a594fdb27e90fd4ada4ebcc356438b070").into(); + let provider_head_proof = ProviderHeadStateProof { + relay_block_number: 19_663_508, + proof: vec![ + hex!("56ff6f7d467b87a9e80300009903910331d9f8f427be99ba3b36ad6f66c49b5448e16745fc3cbe08821204e2e94c9abe1ef65e01dc0f3c52d9aaef735eae1aa679e6c3020e993fb3eef8fab9c32cc7b55dfc85362c771d9a07e5b6cf9f8e30b67598c513ca087574ecce0b3e205eb4de8783a4630c0661757261204a207d08000000000452505352906ca2562dd3ddae0ecb7076465e223753e76792653f739d5dfb00ad76a6b3607d4a2ab00405617572610101dadcb6f606d8a71dc6d0d4d20ccc3bd67bae8816c86491b14fa899242cd872f3bf5fe9635d4414f4329a578a0627cf367dcaa3e86beca64a9aaef9afd124c701").to_vec(), + hex!("8004648019885bbc2709cbd3a89f9a8813867f322e5663c99fdea9af3ff2ba0010455d5d8091eb4873541a81f69d22ccd903b864c36c747910eec7433d947e5a61f87eb7db80ca85a51ad63cbcb7a988023f3b082492a6937f9957c029eb34d6d618279d232d8048dcb685fcf3963e7697a630c6ce64ff06f325063bb05e34e679eb01a4e2b644").to_vec(), + hex!("80d510805396188100731505c3fe5f51e7d4a9c6e6e4cd2c50ff6d122f5f091a186b2f9780e69515c0c399ad09a7b5da0afb5a8bbd22c6873b69f9f2da18e26a8bd04c6e9d80d647e804958d947c20337a2ac3714b3eca41be52847542b065da3614230decab806dbb5b1913c89acb68a2e85013c4b7adf37ab010cf9b9d7346348d0ca9aafd4a80702af779edd6e8d659600cbe342947238af804d41589116a3dd7fb48905aeab18076a51b70378cbf602d939a885bbad80c94ee9325398105ec2173324bd7f59b55").to_vec(), + hex!("80ffff8003ff6c42a935aca27c743910dbb62aae8009854a21741d74080abb406c26b1f58084a7c1351a2986c948c9a111c955d0f8635e4bd305c24f9b6680405fdce955a180ed003737744c7fba94d0c2cb57f96e7bf3310d9c7a285ae789e25af8b79091b38017a0734a39f27a75f6f648bca2facca2381325b529d32bcf82e75aaf6b7d82dd8042e2e666a38ce9fdbe60164d0c3a351ce06c931931d2cd6650378c1ad691c21480d0cc4967746360ee3895a6937608d7f36674426928790cb8ca7426289ad74469804e8940ff6b30dfb0f92341c3a738f262bed9ca03de9b868eb99cfc282aa7786780acda22345d4597dfe6fd831509b944254e26a00fd56e77bc2cb780c0775a520a808c0dae720727cec94dbc853812332bfd6d5f2cc5e287bcd1e5efc530053dbd2280a16a8184b9f2e555d4991995fd479b1ee7b35653f2215f74f822436dbbb2331580984648137ae9c8ecf33f878cedffdba73fb4282ba3ec033102aa6d7442466517801132afeed824c180373b2450b32c72c84a21cdfddbe0f1bf8e76d6958963669580357f2107df0a82f2605f90e39c5665bdf69e1d6222bc425f8390bde67c1d414780c4e048c8dc0ea614a190375a1b215c8e8ff5f5098cd43a93d59be907a2258a74807ad4cd868c49acc40e389d45a1e7e7629e666972ed747c67b607b07f637c1f0b8021bbaa444a77faac92b771c0e1b19162ace64b5ce745892d3ce59f820cba2dc7").to_vec(), + hex!("9e710b30bd2eab0352ddcc26417aa1945fd3802284b6ec6d4b3138fca93d003a58421ba947ecbc14c39e76572061105bbc568b809f8b23e74053dd98b58424e102ba5ac16f028714ec16a61522011fe6e16771ff80ed3e43ac278948816e8c9e8adda2dbeefe552702cf8144fd9b50e0b8db99bfcf80694abb8b23315ab79cdb22ca6826e867a9157415a832ad38f376dd819107d3ea80b9aee043e378f8313e68a6030679ccf3880fa1e7ab19b6244b5c262b7a152f004c5f03c716fb8fff3de61a883bb76adb34a2040080f15f37adeb10597dac54c2c65393277b2ca62aa27b2d16a23a78a4cc55ef15bb8008a0c609ab4888f02c2545c002153297c2641c5a7b4f3d8e25c634e721f80bea80b6617c764df278313c426c46961ccde8ee7a03f9007b74bc8bc6c49d1583cf7d801c9a4a3457ad4a568dd4c9abe231304689c9bec78be932ef0a2d30690ca428848059ef8bbe3a06c98792f41b3e0a6cdf1f157d9be85e12a7c1daf9c30f969daba4").to_vec(), + hex!("9f0b3c252fcb29d88eff4f3de5de4476c3ffbf805254dc9131b269f3bbbb71f58a76a5034b2bc2faaab0d1cf45c3819dc6e69740804bc059c3d96f627e09a3b6c0f9851d902f84ac68006617289ac0b7d0a272b36280d97e2394406f94be4266da29b6fe7f3178059525eaf3c9b540064389af020bf180636959b43018d3ff8a55246d5874a16c93e85bd2a58c82ebfc1b54dd9b2a7d0780d3c1a10188200f31459d722f7efc693736d1a36af5644fd949b2e411d7942597800328f24d0485b9701135913a569f6ccbf261a05d055183abf3e4ecb4e4375b7c80f3229cd59de7b1e604f110cbcf814466f2d2973e9bdb6c106a662c576e0820e480b66b29cbd45f93602dbc9f1175407c6f69bd686d23dd22a8f0dfe9cff08843ad80ddb2d426c0c546068b429e77253e0a8a32e818151f5fc031e899a0f6acad157580ea7fb3cad8e128cc295194658016f4865ef37501e5759fb4f15cb2ecb689e85e80e9f3cac1b25842da7fbaf947952dc30329a1d19037ab21baed3851acbee629f6800d898e2a4a6ee9969a233c4741e4441c0fe393104b3cfc5adcf348f3ef20fc7480ac6c622536e593ae3c9d423a461faafc7abbf01ecb129e69d66f3382eaf484dc80c7ba3cadffaea5acd013dba51c96129ae93ea6cd45f3930e4302f5b100f6deae806f29f805e30029363e42381d6609ecb6837411bd6fd676c0a37621a3b5588101").to_vec() + ], + }; + let proof = RelayDipDidProofWithVerifiedRelayStateRoot::<_, _, (), (), _, (), ()>::with_relay_state_root_and_provider_head_proof(relay_state_root, provider_head_proof); + assert_err!( + proof.verify_provider_head_proof::>(2_086), + Error::ParaHeadMerkleProof(MerkleProofError::InvalidProof) + ); + } + + #[test] + fn verify_provider_head_proof_invalid_proof() { + // Storage proof generated at Polkadot block `19_663_508` with hash + // `0x6e87866fb4f412e1e691e25d294019a7695d5a756ee7bc8d012c25177b5e1e13` for + // storage key + // `0xcd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c32c0cfd6c23b92a7826080000` + // (`paras::heads(2_086)`) + let relay_state_root: H256 = hex!("623b36bddae282e9fefab4707697171a594fdb27e90fd4ada4ebcc356438b070").into(); + let provider_head_proof = ProviderHeadStateProof { + relay_block_number: 19_663_508, + // Last proof component removed + proof: vec![ + hex!("560cfd6c23b92a7826080000f102e902fb1bd938b2f4fcea70641da8e64e0e11098b92b767279227cdfdc0ae9500da99d27e5f012937179bfe939750c770f2aa6e84c6b8cf9d0aa9ab852243ceb78e3eeb93fc56eacc28c5503a155c4d8bc7ee4797c38e212428cefff0a7ad19b28ebbab793e64080661757261204a207d0800000000056175726101017cda19117c87384aaebfd2ae546771bcfbfe7011a91119932883382cc62be3050d745c9734f422228c7c43d87e6172519019829b14b2d3b64afafb1fb7d3a683").to_vec(), + hex!("80021080f0c4027f5eba380b623a2d3382ab03961b2b7e753e62d3475a6940207db367cc80fde8c5a37120e2f1d987f5302783d22f8ac1b213c580030a7f5b15e706df6262").to_vec(), + hex!("8004648019885bbc2709cbd3a89f9a8813867f322e5663c99fdea9af3ff2ba0010455d5d8091eb4873541a81f69d22ccd903b864c36c747910eec7433d947e5a61f87eb7db80ca85a51ad63cbcb7a988023f3b082492a6937f9957c029eb34d6d618279d232d8048dcb685fcf3963e7697a630c6ce64ff06f325063bb05e34e679eb01a4e2b644").to_vec(), + hex!("80ffff8003ff6c42a935aca27c743910dbb62aae8009854a21741d74080abb406c26b1f58084a7c1351a2986c948c9a111c955d0f8635e4bd305c24f9b6680405fdce955a180ed003737744c7fba94d0c2cb57f96e7bf3310d9c7a285ae789e25af8b79091b38017a0734a39f27a75f6f648bca2facca2381325b529d32bcf82e75aaf6b7d82dd8042e2e666a38ce9fdbe60164d0c3a351ce06c931931d2cd6650378c1ad691c21480d0cc4967746360ee3895a6937608d7f36674426928790cb8ca7426289ad74469804e8940ff6b30dfb0f92341c3a738f262bed9ca03de9b868eb99cfc282aa7786780acda22345d4597dfe6fd831509b944254e26a00fd56e77bc2cb780c0775a520a808c0dae720727cec94dbc853812332bfd6d5f2cc5e287bcd1e5efc530053dbd2280a16a8184b9f2e555d4991995fd479b1ee7b35653f2215f74f822436dbbb2331580984648137ae9c8ecf33f878cedffdba73fb4282ba3ec033102aa6d7442466517801132afeed824c180373b2450b32c72c84a21cdfddbe0f1bf8e76d6958963669580357f2107df0a82f2605f90e39c5665bdf69e1d6222bc425f8390bde67c1d414780c4e048c8dc0ea614a190375a1b215c8e8ff5f5098cd43a93d59be907a2258a74807ad4cd868c49acc40e389d45a1e7e7629e666972ed747c67b607b07f637c1f0b8021bbaa444a77faac92b771c0e1b19162ace64b5ce745892d3ce59f820cba2dc7").to_vec(), + hex!("9e710b30bd2eab0352ddcc26417aa1945fd3802284b6ec6d4b3138fca93d003a58421ba947ecbc14c39e76572061105bbc568b809f8b23e74053dd98b58424e102ba5ac16f028714ec16a61522011fe6e16771ff80ed3e43ac278948816e8c9e8adda2dbeefe552702cf8144fd9b50e0b8db99bfcf80694abb8b23315ab79cdb22ca6826e867a9157415a832ad38f376dd819107d3ea80b9aee043e378f8313e68a6030679ccf3880fa1e7ab19b6244b5c262b7a152f004c5f03c716fb8fff3de61a883bb76adb34a2040080f15f37adeb10597dac54c2c65393277b2ca62aa27b2d16a23a78a4cc55ef15bb8008a0c609ab4888f02c2545c002153297c2641c5a7b4f3d8e25c634e721f80bea80b6617c764df278313c426c46961ccde8ee7a03f9007b74bc8bc6c49d1583cf7d801c9a4a3457ad4a568dd4c9abe231304689c9bec78be932ef0a2d30690ca428848059ef8bbe3a06c98792f41b3e0a6cdf1f157d9be85e12a7c1daf9c30f969daba4").to_vec(), + // hex!("9f0b3c252fcb29d88eff4f3de5de4476c3ffbf805254dc9131b269f3bbbb71f58a76a5034b2bc2faaab0d1cf45c3819dc6e69740804bc059c3d96f627e09a3b6c0f9851d902f84ac68006617289ac0b7d0a272b36280d97e2394406f94be4266da29b6fe7f3178059525eaf3c9b540064389af020bf180636959b43018d3ff8a55246d5874a16c93e85bd2a58c82ebfc1b54dd9b2a7d0780d3c1a10188200f31459d722f7efc693736d1a36af5644fd949b2e411d7942597800328f24d0485b9701135913a569f6ccbf261a05d055183abf3e4ecb4e4375b7c80f3229cd59de7b1e604f110cbcf814466f2d2973e9bdb6c106a662c576e0820e480b66b29cbd45f93602dbc9f1175407c6f69bd686d23dd22a8f0dfe9cff08843ad80ddb2d426c0c546068b429e77253e0a8a32e818151f5fc031e899a0f6acad157580ea7fb3cad8e128cc295194658016f4865ef37501e5759fb4f15cb2ecb689e85e80e9f3cac1b25842da7fbaf947952dc30329a1d19037ab21baed3851acbee629f6800d898e2a4a6ee9969a233c4741e4441c0fe393104b3cfc5adcf348f3ef20fc7480ac6c622536e593ae3c9d423a461faafc7abbf01ecb129e69d66f3382eaf484dc80c7ba3cadffaea5acd013dba51c96129ae93ea6cd45f3930e4302f5b100f6deae806f29f805e30029363e42381d6609ecb6837411bd6fd676c0a37621a3b5588101").to_vec() + ], + }; + let proof = RelayDipDidProofWithVerifiedRelayStateRoot::<_, _, (), (), _, (), ()>::with_relay_state_root_and_provider_head_proof(relay_state_root, provider_head_proof); + assert_err!( + proof.verify_provider_head_proof::>(2_086), + Error::ParaHeadMerkleProof(MerkleProofError::InvalidProof) + ); + } +} diff --git a/crates/kilt-dip-primitives/src/state_proofs/error.rs b/crates/kilt-dip-primitives/src/state_proofs/error.rs new file mode 100644 index 0000000000..2332699351 --- /dev/null +++ b/crates/kilt-dip-primitives/src/state_proofs/error.rs @@ -0,0 +1,65 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2024 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use scale_info::TypeInfo; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] +#[cfg_attr(test, derive(enum_iterator::Sequence))] +pub enum MerkleProofError { + InvalidProof, + RequiredLeafNotRevealed, + ResultDecoding, +} + +impl From for u8 { + fn from(value: MerkleProofError) -> Self { + match value { + // DO NOT USE 0 + // Errors of different sub-parts are separated by a `u8::MAX`. + // A value of 0 would make it confusing whether it's the previous sub-part error (u8::MAX) + // or the new sub-part error (u8::MAX + 0). + MerkleProofError::InvalidProof => 1, + MerkleProofError::RequiredLeafNotRevealed => 2, + MerkleProofError::ResultDecoding => 3, + } + } +} + +#[test] +fn merkle_proof_error_value_never_zero() { + assert!( + enum_iterator::all::().all(|e| u8::from(e) != 0), + "One of the u8 values for the error is 0, which is not allowed." + ); +} + +#[test] +fn merkle_proof_error_value_not_duplicated() { + enum_iterator::all::().fold( + sp_std::collections::btree_set::BTreeSet::::new(), + |mut values, new_value| { + let new_encoded_value = u8::from(new_value); + assert!( + values.insert(new_encoded_value), + "Failed to add unique value {:#?} for error variant", + new_encoded_value + ); + values + }, + ); +} diff --git a/crates/kilt-dip-primitives/src/state_proofs/mod.rs b/crates/kilt-dip-primitives/src/state_proofs/mod.rs index 2f8eb4c531..b58a9213a5 100644 --- a/crates/kilt-dip-primitives/src/state_proofs/mod.rs +++ b/crates/kilt-dip-primitives/src/state_proofs/mod.rs @@ -17,7 +17,6 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org use parity_scale_codec::Decode; -use scale_info::TypeInfo; use sp_runtime::traits::Hash; use sp_std::vec::Vec; use sp_trie::StorageProof; @@ -30,26 +29,8 @@ use crate::{state_proofs::substrate_no_std_port::read_proof_check, utils::Output // kept up-to-date with upstream. mod substrate_no_std_port; -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] -pub enum MerkleProofError { - InvalidProof, - RequiredLeafNotRevealed, - ResultDecoding, -} - -impl From for u8 { - fn from(value: MerkleProofError) -> Self { - match value { - // DO NOT USE 0 - // Errors of different sub-parts are separated by a `u8::MAX`. - // A value of 0 would make it confusing whether it's the previous sub-part error (u8::MAX) - // or the new sub-part error (u8::MAX + 0). - MerkleProofError::InvalidProof => 1, - MerkleProofError::RequiredLeafNotRevealed => 2, - MerkleProofError::ResultDecoding => 3, - } - } -} +mod error; +pub use error::*; /// Verify a Merkle-based storage proof for a given storage key according to the /// provided state root. The generic types indicate the following: @@ -127,7 +108,7 @@ mod test { use crate::state_proofs::verify_storage_value_proof; #[test] - fn spiritnet_system_event_count() { + fn verify_storage_value_proof_spiritnet_system_event_count() { // As of RPC state_getReadProof(" // 0x26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850", // "0x2c0746e7e9ccc6e4d27bcb4118cb6821ae53ae9bf372f4f49ac28d8598f9bed5") @@ -156,7 +137,7 @@ mod test { } #[test] - fn polkadot_parahead_proof_for_spiritnet() { + fn verify_storage_value_proof_polkadot_parahead_proof_for_spiritnet() { // As of RPC state_getReadProof("0xcd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c32c0cfd6c23b92a7826080000", "0x18e90e9aa8e3b063f60386ba1b0415111798e72d01de58b1438d620d42f58e39") let spiritnet_head_storage_key = StorageKey( [ diff --git a/crates/kilt-dip-primitives/src/utils.rs b/crates/kilt-dip-primitives/src/utils.rs index 7d71fb9cc7..2ceca62e8d 100644 --- a/crates/kilt-dip-primitives/src/utils.rs +++ b/crates/kilt-dip-primitives/src/utils.rs @@ -16,91 +16,74 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -use pallet_dip_provider::IdentityCommitmentVersion; -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_core::storage::StorageKey; -use sp_std::{fmt::Debug, vec::Vec}; - /// The output of a type implementing the [`sp_runtime::traits::Hash`] trait. pub type OutputOf = ::Output; -/// The vector of vectors that implements a statically-configured maximum length -/// without requiring const generics, used in benchmarking worst cases. -#[derive(Encode, Decode, PartialEq, Eq, PartialOrd, Ord, Debug, TypeInfo, Clone)] -pub struct BoundedBlindedValue(Vec>); - -impl BoundedBlindedValue { - pub fn into_inner(self) -> Vec> { - self.0 +pub(crate) use calculate_parachain_head_storage_key::*; +mod calculate_parachain_head_storage_key { + use parity_scale_codec::Encode; + use sp_core::storage::StorageKey; + + pub(crate) fn calculate_parachain_head_storage_key(para_id: u32) -> StorageKey { + StorageKey( + [ + frame_support::storage::storage_prefix(b"Paras", b"Heads").as_slice(), + sp_io::hashing::twox_64(para_id.encode().as_ref()).as_slice(), + para_id.encode().as_slice(), + ] + .concat(), + ) } -} -impl From for BoundedBlindedValue -where - C: Iterator>, -{ - fn from(value: C) -> Self { - Self(value.into_iter().collect()) + #[test] + fn calculate_parachain_head_storage_key_successful_spiritnet_parachain() { + assert_eq!( + calculate_parachain_head_storage_key(2_086).0, + hex_literal::hex!( + "cd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c32c0cfd6c23b92a7826080000" + ) + .to_vec() + ); } -} - -impl sp_std::ops::Deref for BoundedBlindedValue { - type Target = Vec>; - - fn deref(&self) -> &Self::Target { - &self.0 + #[test] + fn calculate_parachain_head_storage_key_successful_peregrine_parachain() { + assert_eq!( + calculate_parachain_head_storage_key(2_000).0, + hex_literal::hex!( + "cd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c363f5a4efb16ffa83d0070000" + ) + .to_vec() + ); } } -impl IntoIterator for BoundedBlindedValue { - type IntoIter = > as IntoIterator>::IntoIter; - type Item = > as IntoIterator>::Item; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() +pub(crate) use calculate_dip_identity_commitment_storage_key_for_runtime::*; +mod calculate_dip_identity_commitment_storage_key_for_runtime { + use pallet_dip_provider::IdentityCommitmentVersion; + use sp_core::storage::StorageKey; + + pub(crate) fn calculate_dip_identity_commitment_storage_key_for_runtime( + subject: &Runtime::Identifier, + version: IdentityCommitmentVersion, + ) -> StorageKey + where + Runtime: pallet_dip_provider::Config, + { + StorageKey(pallet_dip_provider::IdentityCommitments::::hashed_key_for( + subject, version, + )) } -} -#[cfg(feature = "runtime-benchmarks")] -impl kilt_support::traits::GetWorstCase for BoundedBlindedValue -where - T: Default + Clone, -{ - fn worst_case(_context: Context) -> Self { - Self(sp_std::vec![sp_std::vec![T::default(); 128]; 64]) + #[test] + fn calculate_dip_identity_commitment_storage_key_for_runtime_successful_peregrine_parachain() { + use did::DidIdentifierOf; + use peregrine_runtime::Runtime as PeregrineRuntime; + use sp_core::crypto::Ss58Codec; + + assert_eq!( + calculate_dip_identity_commitment_storage_key_for_runtime::(&DidIdentifierOf::::from_ss58check("4s3jpR7pzrUdhVUqHHdWoBN6oNQHBC7WRo7zsXdjAzQPT7Cf").unwrap(), 0).0, + hex_literal::hex!("b375edf06348b4330d1e88564111cb3d5bf19e4ed2927982e234d989e812f3f314c9211b34c8b43b2a18d67d5c96de9cb6caebbe9e3adeaaf693a2d198f2881d0b504fc72ed4ac0a7ed24a025fc228ce01a12dfa1fa4ab9a0000") + .to_vec() + ); } } - -#[cfg(feature = "runtime-benchmarks")] -impl Default for BoundedBlindedValue -where - T: Default + Clone, -{ - fn default() -> Self { - Self(sp_std::vec![sp_std::vec![T::default(); 128]; 64]) - } -} - -pub(crate) fn calculate_parachain_head_storage_key(para_id: u32) -> StorageKey { - StorageKey( - [ - frame_support::storage::storage_prefix(b"Paras", b"Heads").as_slice(), - sp_io::hashing::twox_64(para_id.encode().as_ref()).as_slice(), - para_id.encode().as_slice(), - ] - .concat(), - ) -} - -pub(crate) fn calculate_dip_identity_commitment_storage_key_for_runtime( - subject: &Runtime::Identifier, - version: IdentityCommitmentVersion, -) -> StorageKey -where - Runtime: pallet_dip_provider::Config, -{ - StorageKey(pallet_dip_provider::IdentityCommitments::::hashed_key_for( - subject, version, - )) -} diff --git a/crates/kilt-dip-primitives/src/verifier/errors.rs b/crates/kilt-dip-primitives/src/verifier/errors.rs new file mode 100644 index 0000000000..8b041a3d1b --- /dev/null +++ b/crates/kilt-dip-primitives/src/verifier/errors.rs @@ -0,0 +1,27 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2024 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +#[repr(u8)] +pub(crate) enum DipProofComponentTooLargeError { + ParachainHeadProofTooManyLeaves = 0, + ParachainHeadProofLeafTooLarge = 1, + DipCommitmentProofTooManyLeaves = 2, + DipCommitmentProofLeafTooLarge = 3, + DipProofTooManyLeaves = 4, + DipProofLeafTooLarge = 5, +} diff --git a/crates/kilt-dip-primitives/src/verifier/mod.rs b/crates/kilt-dip-primitives/src/verifier/mod.rs index 8ef00aad6f..ee210acc75 100644 --- a/crates/kilt-dip-primitives/src/verifier/mod.rs +++ b/crates/kilt-dip-primitives/src/verifier/mod.rs @@ -16,6 +16,8 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org +mod errors; + /// Verification logic to integrate a sibling chain as a DIP provider. pub mod parachain; /// Verification logic to integrate a child chain as a DIP provider. diff --git a/crates/kilt-dip-primitives/src/verifier/parachain.rs b/crates/kilt-dip-primitives/src/verifier/parachain.rs deleted file mode 100644 index 817e4ad8f9..0000000000 --- a/crates/kilt-dip-primitives/src/verifier/parachain.rs +++ /dev/null @@ -1,486 +0,0 @@ -// KILT Blockchain – https://botlabs.org -// Copyright (C) 2019-2024 BOTLabs GmbH - -// The KILT Blockchain is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The KILT Blockchain is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -// If you feel like getting in touch with us, you can do so at info@botlabs.org - -use did::KeyIdOf; -use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor}; -use pallet_did_lookup::linkable_account::LinkableAccountId; -use pallet_dip_consumer::{traits::IdentityProofVerifier, RuntimeCallOf}; -use pallet_dip_provider::traits::IdentityCommitmentGenerator; -use pallet_web3_names::Web3NameOf; -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_std::{fmt::Debug, marker::PhantomData}; - -use crate::{ - merkle::v0::RevealedDidKey, - traits::{DipCallOriginFilter, GetWithArg, GetWithoutArg, Incrementable}, - utils::OutputOf, - DipOriginInfo, Error, -}; - -/// A KILT-specific DIP identity proof for a sibling consumer that supports -/// versioning. -/// -/// For more info, refer to the version-specific proofs. -#[derive(Encode, Decode, PartialEq, Eq, Debug, TypeInfo, Clone)] -pub enum VersionedDipParachainStateProof< - RelayBlockNumber, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, -> { - V0( - crate::merkle::v0::ParachainDipDidProof< - RelayBlockNumber, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - >, - ), -} - -#[cfg(feature = "runtime-benchmarks")] -impl< - RelayBlockNumber, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - Context, - > kilt_support::traits::GetWorstCase - for VersionedDipParachainStateProof< - RelayBlockNumber, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - ConsumerBlockNumber, - > where - RelayBlockNumber: Default, - KiltDidKeyId: Default + Clone, - KiltAccountId: Clone, - KiltBlockNumber: Default + Clone, - KiltWeb3Name: Clone, - KiltLinkableAccountId: Clone, - ConsumerBlockNumber: Default, - Context: Clone, -{ - fn worst_case(context: Context) -> Self { - Self::V0(crate::merkle::v0::ParachainDipDidProof::worst_case(context)) - } -} - -pub enum DipParachainStateProofVerifierError { - UnsupportedVersion, - ProofComponentTooLarge(u8), - ProofVerification(Error), - DidOriginError(DidOriginError), - Internal, -} - -impl From> for u16 -where - DidOriginError: Into, -{ - fn from(value: DipParachainStateProofVerifierError) -> Self { - match value { - // DO NOT USE 0 - // Errors of different sub-parts are separated by a `u8::MAX`. - // A value of 0 would make it confusing whether it's the previous sub-part error (u8::MAX) - // or the new sub-part error (u8::MAX + 0). - DipParachainStateProofVerifierError::UnsupportedVersion => 1, - DipParachainStateProofVerifierError::ProofComponentTooLarge(component_id) => { - u8::MAX as u16 + component_id as u16 - } - DipParachainStateProofVerifierError::ProofVerification(error) => { - u8::MAX as u16 * 2 + u8::from(error) as u16 - } - DipParachainStateProofVerifierError::DidOriginError(error) => u8::MAX as u16 * 3 + error.into() as u16, - DipParachainStateProofVerifierError::Internal => u16::MAX, - } - } -} - -/// Versioned proof verifier. For version-specific description, refer to each -/// verifier's documentation. -pub struct KiltVersionedParachainVerifier< - RelaychainRuntime, - RelaychainStateRootStore, - const KILT_PARA_ID: u32, - KiltRuntime, - DidCallVerifier, - SignedExtra = (), - const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32 = 64, - const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32 = 1024, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32 = 64, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32 = 1024, - const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32 = 64, - const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32 = 1024, - const MAX_DID_MERKLE_LEAVES_REVEALED: u32 = 64, ->( - PhantomData<( - RelaychainRuntime, - RelaychainStateRootStore, - KiltRuntime, - DidCallVerifier, - SignedExtra, - )>, -); - -impl< - ConsumerRuntime, - RelaychainRuntime, - RelaychainStateRootStore, - const KILT_PARA_ID: u32, - KiltRuntime, - DidCallVerifier, - SignedExtra, - const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32, - const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32, - const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32, - const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32, - const MAX_DID_MERKLE_LEAVES_REVEALED: u32, - > IdentityProofVerifier - for KiltVersionedParachainVerifier< - RelaychainRuntime, - RelaychainStateRootStore, - KILT_PARA_ID, - KiltRuntime, - DidCallVerifier, - SignedExtra, - MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT, - MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE, - MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT, - MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE, - MAX_DID_MERKLE_PROOF_LEAVE_COUNT, - MAX_DID_MERKLE_PROOF_LEAVE_SIZE, - MAX_DID_MERKLE_LEAVES_REVEALED, - > where - ConsumerRuntime: pallet_dip_consumer::Config, - ConsumerRuntime::LocalIdentityInfo: Incrementable + Default, - RelaychainRuntime: frame_system::Config, - RelaychainStateRootStore: - GetWithArg, Result = Option>>, - KiltRuntime: frame_system::Config - + pallet_dip_provider::Config - + did::Config - + pallet_web3_names::Config - + pallet_did_lookup::Config, - KiltRuntime::IdentityCommitmentGenerator: - IdentityCommitmentGenerator, - SignedExtra: GetWithoutArg, - SignedExtra::Result: Encode, - DidCallVerifier: DipCallOriginFilter< - RuntimeCallOf, - OriginInfo = RevealedDidKey, BlockNumberFor, KiltRuntime::AccountId>, - >, - DidCallVerifier::Error: Into, -{ - type Error = DipParachainStateProofVerifierError; - type Proof = VersionedDipParachainStateProof< - BlockNumberFor, - KeyIdOf, - KiltRuntime::AccountId, - BlockNumberFor, - Web3NameOf, - LinkableAccountId, - BlockNumberFor, - >; - type VerificationResult = DipOriginInfo< - KeyIdOf, - KiltRuntime::AccountId, - BlockNumberFor, - Web3NameOf, - LinkableAccountId, - MAX_DID_MERKLE_LEAVES_REVEALED, - >; - - fn verify_proof_for_call_against_details( - call: &RuntimeCallOf, - subject: &ConsumerRuntime::Identifier, - submitter: &ConsumerRuntime::AccountId, - identity_details: &mut Option, - proof: Self::Proof, - ) -> Result { - match proof { - VersionedDipParachainStateProof::V0(v0_proof) => as IdentityProofVerifier>::verify_proof_for_call_against_details( - call, - subject, - submitter, - identity_details, - v0_proof, - ), - } - } -} - -pub mod v0 { - use super::*; - - use frame_support::ensure; - use sp_runtime::{traits::Zero, SaturatedConversion}; - - use crate::merkle::v0::ParachainDipDidProof; - - /// Proof verifier configured given a specific KILT runtime implementation. - /// - /// The generic types - /// indicate the following: - /// * `RelaychainRuntime`: The relaychain runtime definition. - /// * `RelaychainStateRootStore`: A type providing state roots for - /// relaychain blocks. - /// * `KILT_PARA_ID`: The ID of the specific KILT parachain instance. - /// * `KiltRuntime`: A KILT runtime definition. - /// * `DidCallVerifier`: Logic to map `RuntimeCall`s to a specific DID key - /// relationship. This information is used once the Merkle proof is - /// verified, to filter only the revealed keys that match the provided - /// relationship. - /// * `SignedExtra`: Any additional information that must be signed by the - /// DID subject in the cross-chain operation. - /// * `MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT`: The maximum number of leaves - /// that can be revealed as part of the parachain head storage proof. - /// * `MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE`: The maximum size of each leaf - /// revealed as part of the parachain head storage proof. - /// * `MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT`: The maximum number of leaves - /// that can be revealed as part of the DIP commitment storage proof. - /// * `MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE`: The maximum size of each leaf - /// revealed as part of the DIP commitment storage proof. - /// * `MAX_DID_MERKLE_PROOF_LEAVE_COUNT`: The maximum number of *blinded* - /// leaves that can be revealed as part of the DID Merkle proof. - /// * `MAX_DID_MERKLE_PROOF_LEAVE_SIZE`: The maximum size of each *blinded* - /// leaf revealed as part of the DID Merkle proof. - /// * `MAX_DID_MERKLE_LEAVES_REVEALED`: The maximum number of leaves that - /// can be revealed as part of the DID Merkle proof. - pub struct ParachainVerifier< - RelaychainRuntime, - RelaychainStateRootStore, - const KILT_PARA_ID: u32, - KiltRuntime, - DidCallVerifier, - SignedExtra, - const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32, - const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32, - const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32, - const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32, - const MAX_DID_MERKLE_LEAVES_REVEALED: u32, - >( - PhantomData<( - RelaychainRuntime, - RelaychainStateRootStore, - KiltRuntime, - DidCallVerifier, - SignedExtra, - )>, - ); - - impl< - ConsumerRuntime, - RelaychainRuntime, - RelaychainStateRootStore, - const KILT_PARA_ID: u32, - KiltRuntime, - DidCallVerifier, - SignedExtra, - const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32, - const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32, - const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32, - const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32, - const MAX_DID_MERKLE_LEAVES_REVEALED: u32, - > IdentityProofVerifier - for ParachainVerifier< - RelaychainRuntime, - RelaychainStateRootStore, - KILT_PARA_ID, - KiltRuntime, - DidCallVerifier, - SignedExtra, - MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT, - MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE, - MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT, - MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE, - MAX_DID_MERKLE_PROOF_LEAVE_COUNT, - MAX_DID_MERKLE_PROOF_LEAVE_SIZE, - MAX_DID_MERKLE_LEAVES_REVEALED, - > where - ConsumerRuntime: pallet_dip_consumer::Config, - ConsumerRuntime::LocalIdentityInfo: Incrementable + Default, - RelaychainRuntime: frame_system::Config, - RelaychainStateRootStore: - GetWithArg, Result = Option>>, - KiltRuntime: frame_system::Config - + pallet_dip_provider::Config - + did::Config - + pallet_web3_names::Config - + pallet_did_lookup::Config, - KiltRuntime::IdentityCommitmentGenerator: - IdentityCommitmentGenerator, - SignedExtra: GetWithoutArg, - SignedExtra::Result: Encode, - DidCallVerifier: DipCallOriginFilter< - RuntimeCallOf, - OriginInfo = RevealedDidKey, BlockNumberFor, KiltRuntime::AccountId>, - >, - DidCallVerifier::Error: Into, - { - type Error = DipParachainStateProofVerifierError; - type Proof = ParachainDipDidProof< - BlockNumberFor, - KeyIdOf, - KiltRuntime::AccountId, - BlockNumberFor, - Web3NameOf, - LinkableAccountId, - BlockNumberFor, - >; - - type VerificationResult = DipOriginInfo< - KeyIdOf, - KiltRuntime::AccountId, - BlockNumberFor, - Web3NameOf, - LinkableAccountId, - MAX_DID_MERKLE_LEAVES_REVEALED, - >; - - fn verify_proof_for_call_against_details( - call: &RuntimeCallOf, - subject: &::Identifier, - submitter: &::AccountId, - identity_details: &mut Option<::LocalIdentityInfo>, - proof: Self::Proof, - ) -> Result { - // 1. Verify parachain state is finalized by relay chain and fresh. - ensure!( - proof.provider_head_proof.proof.len() <= MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT.saturated_into(), - DipParachainStateProofVerifierError::ProofComponentTooLarge(0) - ); - ensure!( - proof - .provider_head_proof - .proof - .iter() - .all(|l| l.len() <= MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE.saturated_into()), - DipParachainStateProofVerifierError::ProofComponentTooLarge(1) - ); - let proof_without_relaychain = proof - .verify_provider_head_proof::>( - KILT_PARA_ID, - ) - .map_err(DipParachainStateProofVerifierError::ProofVerification)?; - - // 2. Verify commitment is included in provider parachain state. - ensure!( - proof_without_relaychain.dip_commitment_proof.0.len() - <= MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT.saturated_into(), - DipParachainStateProofVerifierError::ProofComponentTooLarge(2) - ); - ensure!( - proof_without_relaychain - .dip_commitment_proof - .0 - .iter() - .all(|l| l.len() <= MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE.saturated_into()), - DipParachainStateProofVerifierError::ProofComponentTooLarge(3) - ); - let proof_without_parachain = proof_without_relaychain - .verify_dip_commitment_proof_for_subject::(subject) - .map_err(DipParachainStateProofVerifierError::ProofVerification)?; - - // 3. Verify DIP Merkle proof. - ensure!( - proof_without_parachain.dip_proof.blinded.len() <= MAX_DID_MERKLE_PROOF_LEAVE_COUNT.saturated_into(), - DipParachainStateProofVerifierError::ProofComponentTooLarge(4) - ); - ensure!( - proof_without_parachain - .dip_proof - .blinded - .iter() - .all(|l| l.len() <= MAX_DID_MERKLE_PROOF_LEAVE_SIZE.saturated_into()), - DipParachainStateProofVerifierError::ProofComponentTooLarge(5) - ); - let proof_without_dip_merkle = proof_without_parachain - .verify_dip_proof::() - .map_err(DipParachainStateProofVerifierError::ProofVerification)?; - - // 4. Verify call is signed by one of the DID keys revealed in the proof - let current_block_number = frame_system::Pallet::::block_number(); - let consumer_genesis_hash = - frame_system::Pallet::::block_hash(BlockNumberFor::::zero()); - let signed_extra = SignedExtra::get(); - let encoded_payload = ( - call, - &identity_details, - submitter, - proof_without_dip_merkle.signature.valid_until, - consumer_genesis_hash, - signed_extra, - ) - .encode(); - let revealed_did_info = proof_without_dip_merkle - .verify_signature_time(¤t_block_number) - .and_then(|p| p.retrieve_signing_leaf_for_payload(&encoded_payload[..])) - .map_err(DipParachainStateProofVerifierError::ProofVerification)?; - - // 5. Verify the signing key fulfills the requirements - let signing_key = revealed_did_info - .get_signing_leaf() - .map_err(DipParachainStateProofVerifierError::ProofVerification)?; - DidCallVerifier::check_call_origin_info(call, signing_key) - .map_err(DipParachainStateProofVerifierError::DidOriginError)?; - - // 6. Increment the local details - if let Some(details) = identity_details { - details.increment(); - } else { - *identity_details = Some(Default::default()); - }; - - Ok(revealed_did_info) - } - } -} diff --git a/crates/kilt-dip-primitives/src/verifier/parachain/error.rs b/crates/kilt-dip-primitives/src/verifier/parachain/error.rs new file mode 100644 index 0000000000..30104794f8 --- /dev/null +++ b/crates/kilt-dip-primitives/src/verifier/parachain/error.rs @@ -0,0 +1,81 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2024 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use crate::Error; + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(test, derive(enum_iterator::Sequence))] +pub enum DipParachainStateProofVerifierError { + UnsupportedVersion, + ProofComponentTooLarge(u8), + ProofVerification(Error), + DidOriginError(DidOriginError), + Internal, +} + +impl From> for u16 +where + DidOriginError: Into, +{ + fn from(value: DipParachainStateProofVerifierError) -> Self { + match value { + // DO NOT USE 0 + // Errors of different sub-parts are separated by a `u8::MAX`. + // A value of 0 would make it confusing whether it's the previous sub-part error (u8::MAX) + // or the new sub-part error (u8::MAX + 0). + DipParachainStateProofVerifierError::UnsupportedVersion => 1, + DipParachainStateProofVerifierError::ProofComponentTooLarge(component_id) => { + u8::MAX as u16 + component_id as u16 + } + DipParachainStateProofVerifierError::ProofVerification(error) => { + u8::MAX as u16 * 2 + u8::from(error) as u16 + } + DipParachainStateProofVerifierError::DidOriginError(error) => u8::MAX as u16 * 3 + error.into() as u16, + DipParachainStateProofVerifierError::Internal => u16::MAX, + } + } +} + +#[test] +fn dip_parachain_state_proof_verifier_error_value_never_zero() { + assert!( + enum_iterator::all::>().all(|e| u16::from(e) != 0), + "One of the u8 values for the error is 0, which is not allowed." + ); +} + +#[test] +fn dip_parachain_state_proof_verifier_error_value_not_duplicated() { + enum_iterator::all::>().fold( + sp_std::collections::btree_set::BTreeSet::::new(), + |mut values, new_value| { + let new_encoded_value = u16::from(new_value); + // DidOriginError is generic, and we cannot test its constraints in this unit + // test, so we skip it. + if new_encoded_value == u8::MAX as u16 * 3 { + return values; + } + assert!( + values.insert(new_encoded_value), + "Failed to add unique value {:#?} for error variant", + new_encoded_value + ); + values + }, + ); +} diff --git a/crates/kilt-dip-primitives/src/verifier/parachain/mod.rs b/crates/kilt-dip-primitives/src/verifier/parachain/mod.rs new file mode 100644 index 0000000000..4ca660b573 --- /dev/null +++ b/crates/kilt-dip-primitives/src/verifier/parachain/mod.rs @@ -0,0 +1,248 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2024 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use did::KeyIdOf; +use frame_system::pallet_prelude::BlockNumberFor; +use pallet_did_lookup::linkable_account::LinkableAccountId; +use pallet_dip_consumer::{traits::IdentityProofVerifier, RuntimeCallOf}; +use pallet_dip_provider::traits::IdentityCommitmentGenerator; +use pallet_web3_names::Web3NameOf; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_std::{fmt::Debug, marker::PhantomData, vec::Vec}; + +use crate::{ + merkle_proofs::v0::RevealedDidKey, + traits::{DipCallOriginFilter, GetWithArg, GetWithoutArg, Incrementable}, + utils::OutputOf, + DipOriginInfo, ParachainDipDidProof, +}; + +pub mod v0; + +mod error; +pub use error::*; + +/// A KILT-specific DIP identity proof for a sibling consumer that supports +/// versioning. +/// +/// For more info, refer to the version-specific proofs. +#[derive(Encode, Decode, PartialEq, Eq, Debug, TypeInfo, Clone)] +pub enum VersionedDipParachainStateProof< + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, +> { + V0( + ParachainDipDidProof< + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + >, + ), +} + +impl< + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > + From< + ParachainDipDidProof< + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + >, + > + for VersionedDipParachainStateProof< + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + > +{ + fn from( + value: ParachainDipDidProof< + RelayBlockNumber, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + ConsumerBlockNumber, + >, + ) -> Self { + Self::V0(value) + } +} + +pub const DEFAULT_MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32 = 128; +pub const DEFAULT_MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32 = 1024; +pub const DEFAULT_MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32 = 128; +pub const DEFAULT_MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32 = 1024; +pub const DEFAULT_MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32 = 128; +pub const DEFAULT_MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32 = 1024; +pub const DEFAULT_MAX_DID_MERKLE_LEAVES_REVEALED: u32 = 128; + +/// Versioned proof verifier. For version-specific description, refer to each +/// verifier's documentation. +pub struct KiltVersionedParachainVerifier< + RelaychainRuntime, + RelaychainStateRootStore, + const KILT_PARA_ID: u32, + KiltRuntime, + DidCallVerifier, + SignedExtra = (), + const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32 = DEFAULT_MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT, + const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32 = DEFAULT_MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32 = DEFAULT_MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32 = DEFAULT_MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE, + const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32 = DEFAULT_MAX_DID_MERKLE_PROOF_LEAVE_COUNT, + const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32 = DEFAULT_MAX_DID_MERKLE_PROOF_LEAVE_SIZE, + const MAX_DID_MERKLE_LEAVES_REVEALED: u32 = DEFAULT_MAX_DID_MERKLE_LEAVES_REVEALED, +>( + PhantomData<( + RelaychainRuntime, + RelaychainStateRootStore, + KiltRuntime, + DidCallVerifier, + SignedExtra, + )>, +); + +impl< + ConsumerRuntime, + RelaychainRuntime, + RelaychainStateRootStore, + const KILT_PARA_ID: u32, + KiltRuntime, + DidCallVerifier, + SignedExtra, + const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32, + const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32, + const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32, + const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32, + const MAX_DID_MERKLE_LEAVES_REVEALED: u32, + > IdentityProofVerifier + for KiltVersionedParachainVerifier< + RelaychainRuntime, + RelaychainStateRootStore, + KILT_PARA_ID, + KiltRuntime, + DidCallVerifier, + SignedExtra, + MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT, + MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE, + MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT, + MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE, + MAX_DID_MERKLE_PROOF_LEAVE_COUNT, + MAX_DID_MERKLE_PROOF_LEAVE_SIZE, + MAX_DID_MERKLE_LEAVES_REVEALED, + > where + ConsumerRuntime: pallet_dip_consumer::Config, + ConsumerRuntime::LocalIdentityInfo: Incrementable + Default, + RelaychainRuntime: frame_system::Config, + RelaychainStateRootStore: + GetWithArg, Result = Option>>, + KiltRuntime: frame_system::Config + + pallet_dip_provider::Config + + did::Config + + pallet_web3_names::Config + + pallet_did_lookup::Config, + KiltRuntime::IdentityCommitmentGenerator: + IdentityCommitmentGenerator, + SignedExtra: GetWithoutArg, + SignedExtra::Result: Encode, + DidCallVerifier: DipCallOriginFilter< + RuntimeCallOf, + OriginInfo = Vec, BlockNumberFor, KiltRuntime::AccountId>>, + >, + DidCallVerifier::Error: Into, +{ + type Error = DipParachainStateProofVerifierError; + type Proof = VersionedDipParachainStateProof< + BlockNumberFor, + KeyIdOf, + KiltRuntime::AccountId, + BlockNumberFor, + Web3NameOf, + LinkableAccountId, + BlockNumberFor, + >; + type VerificationResult = DipOriginInfo< + KeyIdOf, + KiltRuntime::AccountId, + BlockNumberFor, + Web3NameOf, + LinkableAccountId, + MAX_DID_MERKLE_LEAVES_REVEALED, + >; + + fn verify_proof_for_call_against_details( + call: &RuntimeCallOf, + subject: &ConsumerRuntime::Identifier, + submitter: &ConsumerRuntime::AccountId, + identity_details: &mut Option, + proof: Self::Proof, + ) -> Result { + match proof { + VersionedDipParachainStateProof::V0(v0_proof) => as IdentityProofVerifier>::verify_proof_for_call_against_details( + call, + subject, + submitter, + identity_details, + v0_proof, + ), + } + } +} diff --git a/crates/kilt-dip-primitives/src/verifier/parachain/v0/mock.rs b/crates/kilt-dip-primitives/src/verifier/parachain/v0/mock.rs new file mode 100644 index 0000000000..2c852981ab --- /dev/null +++ b/crates/kilt-dip-primitives/src/verifier/parachain/v0/mock.rs @@ -0,0 +1,283 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2024 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use cumulus_pallet_parachain_system::{ParachainSetCode, RelayNumberStrictlyIncreases}; +use cumulus_primitives_core::ParaId; +use did::{ + did_details::{DidPublicKeyDetails, DidVerificationKey}, + DidIdentifierOf, DidVerificationKeyRelationship, KeyIdOf, +}; +use frame_support::{ + construct_runtime, pallet_prelude::ValueQuery, parameter_types, storage_alias, traits::Everything, Twox64Concat, +}; +use frame_system::{mocking::MockBlock, pallet_prelude::BlockNumberFor, EnsureSigned}; +use hex_literal::hex; +use pallet_did_lookup::linkable_account::LinkableAccountId; +use pallet_relay_store::RelayParentInfo; +use pallet_web3_names::Web3NameOf; +use peregrine_runtime::Runtime as PeregrineRuntime; +use rococo_runtime::Runtime as RococoRuntime; +use sp_core::{crypto::Ss58Codec, sr25519, ConstU16, ConstU32, ConstU64, H256}; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + AccountId32, BoundedVec, +}; + +use crate::{ + parachain::v0::{mock::TestRuntime as TestConsumerRuntime, ParachainVerifier}, + traits::DipCallOriginFilter, + DipCommitmentStateProof, ParachainDipDidProof, ProviderHeadStateProof, RelayStateRootsViaRelayStorePallet, + RevealedDidKey, RevealedWeb3Name, TimeBoundDidSignature, +}; + +construct_runtime!( + pub struct TestRuntime { + // Same index as the DIP-consumer template runtime used to generate the cross-chain proof + System: frame_system = 0, + ParachainSystem: cumulus_pallet_parachain_system, + RelayStore: pallet_relay_store, + DipConsumer: pallet_dip_consumer, + } +); + +impl frame_system::Config for TestRuntime { + type AccountData = (); + type AccountId = AccountId32; + type BaseCallFilter = Everything; + type Block = MockBlock; + type BlockHashCount = ConstU64<10>; + type BlockLength = (); + type BlockWeights = (); + type DbWeight = (); + type Hash = H256; + type Hashing = BlakeTwo256; + type Lookup = IdentityLookup; + type MaxConsumers = ConstU32<16>; + type Nonce = u64; + type OnKilledAccount = (); + type OnNewAccount = (); + type OnSetCode = ParachainSetCode; + type PalletInfo = PalletInfo; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type SS58Prefix = ConstU16<1>; + type SystemWeightInfo = (); + type Version = (); +} + +parameter_types! { + pub const ParachainId: ParaId = ParaId::new(2_001); +} + +impl cumulus_pallet_parachain_system::Config for TestRuntime { + type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; + type DmpMessageHandler = (); + type OnSystemEvent = (); + type OutboundXcmpMessageSource = (); + type ReservedDmpWeight = (); + type ReservedXcmpWeight = (); + type RuntimeEvent = RuntimeEvent; + type SelfParaId = ParachainId; + type XcmpMessageHandler = (); +} + +impl pallet_relay_store::Config for TestRuntime { + type MaxRelayBlocksStored = ConstU32<10>; + type WeightInfo = (); +} + +pub struct FilterNothing; + +impl DipCallOriginFilter for FilterNothing { + type Error = u8; + type OriginInfo = Vec< + RevealedDidKey< + KeyIdOf, + BlockNumberFor, + ::AccountId, + >, + >; + type Success = (); + + fn check_call_origin_info(_call: &RuntimeCall, _info: &Self::OriginInfo) -> Result { + Ok(()) + } +} + +pub(crate) const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32 = 64; +pub(crate) const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32 = 1024; +pub(crate) const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32 = 64; +pub(crate) const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32 = 1024; +pub(crate) const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32 = 64; +pub(crate) const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32 = 1024; +pub(crate) const MAX_DID_MERKLE_LEAVES_REVEALED: u32 = 64; +pub type Verifier = ParachainVerifier< + RococoRuntime, + RelayStateRootsViaRelayStorePallet, + 2_000, + PeregrineRuntime, + FilterNothing, + (), + MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT, + MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE, + MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT, + MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE, + MAX_DID_MERKLE_PROOF_LEAVE_COUNT, + MAX_DID_MERKLE_PROOF_LEAVE_SIZE, + MAX_DID_MERKLE_LEAVES_REVEALED, +>; +impl pallet_dip_consumer::Config for TestRuntime { + type DipCallOriginFilter = Everything; + type DispatchOriginCheck = EnsureSigned; + type Identifier = ::DidIdentifier; + type LocalIdentityInfo = u32; + type ProofVerifier = Verifier; + type RuntimeCall = RuntimeCall; + type RuntimeOrigin = RuntimeOrigin; + type WeightInfo = (); +} + +pub(crate) const RELAY_BLOCK: u32 = 421; +pub(crate) const RELAY_STATE_ROOT: H256 = + H256(hex!("6adf8dbf20e1b78f85f6ffe4775640f935d0d8ed38acab327be81089fd90d82d")); +pub(crate) const GENESIS_HASH: H256 = H256(hex!("74f8cd2f3764f676a5e67c45a641ce1025548c6cddcf524a663a9c0aaf7fbee2")); +pub(crate) const WRONG_GENESIS_HASH: H256 = H256([0; 32]); +pub(crate) const IDENTITY_DETAILS: Option = None; +pub(crate) const WRONG_IDENTITY_DETAILS: Option = Some(u32::MAX); +pub(crate) const SIGNATURE_VALID_UNTIL: BlockNumberFor = 199; +pub(crate) const WRONG_SIGNATURE_VALID_UNTIL: BlockNumberFor = 198; + +pub(crate) fn submitter() -> AccountId32 { + AccountId32::from_ss58check("4qgGXhqTwQmi5CaAhR5s2QpsiUzwrdeksoZG5AusPMpaYqP2").unwrap() +} +pub(crate) fn wrong_submitter() -> AccountId32 { + AccountId32::from_ss58check("4pnAJ41mGHGDKCGBGY2zzu1hfvPasPkGAKDgPeprSkxnUmGM").unwrap() +} + +pub(crate) fn subject() -> DidIdentifierOf { + DidIdentifierOf::::from_ss58check("4rTs9KCbLf28yUVsMo5t39ssfW4rPsaqq2UqeZi3hwYLpg3Q").unwrap() +} + +pub(crate) fn call() -> RuntimeCall { + RuntimeCall::System(frame_system::Call::remark { + remark: b"Hello, world!".to_vec(), + }) +} +pub(crate) fn wrong_call() -> RuntimeCall { + RuntimeCall::System(frame_system::Call::remark { + remark: b"Wrong payload!".to_vec(), + }) +} + +// Cross-chain proof generated over the details exported above. +#[allow(clippy::type_complexity)] +pub(crate) fn cross_chain_proof_with_authentication_key_and_web3_name() -> ParachainDipDidProof< + BlockNumberFor, + KeyIdOf, + ::AccountId, + BlockNumberFor, + Web3NameOf, + LinkableAccountId, + BlockNumberFor, +> { + ParachainDipDidProof { provider_head_proof: ProviderHeadStateProof { relay_block_number: RELAY_BLOCK, proof: vec![ + hex!("3703f5a4efb16ffa83d0070000da00d3541403539d4256de2db65d713afc8aedc8abede84d5dc4014019605d94").to_vec(), + hex!("8004648031b60c9237ed343094831987f2bec10b211621255ad0b440cf161fa820d30db480f6f6801e4b41e2e6d8ec194dba122bfb9eb33feb2545ef5144cea79551f7cc52801287b410de904c199ac477f0d317d3a4b9a45b5424236719bbe2b2f0736a505a80c2160c2830b22a1eb05c14f6a9e20639de8f9e21dcd0e621ca18540027f89ba5").to_vec(), + hex!("80ffff80d8655205caee5e0a6b74cdf5b1adc20aea610833ad71da05d3143031b5744be58015f6db81af2768203cf235fa69602e86dd51d963cbaf2e93e3d08a7a71436ac280f48da460759e201ca3c3b9127a366e235ecdbb721c7fdd02673544b39c1a0e7180095af3328f28eb7c21cf96129f628930323efd14acb42e674600f4542a2347e980e277a338d70d91f2da9e3fcdd516aadfaa1e9aa3c91080a74d1580bf033d524d80eb47b9f01723d00dbf42a4227b4b217f2bf928240d54e2f57b32b73f088158fa80c8b5d1d00527c8ba24530642a1f9049ee21ccd7d2923158e31bdb16b1e16b9ed805b682132c52908705526057f73ab7fccab4af6d72a9805634dd8d3cc53f130d180c2d44d371e5fc1f50227d7491ad65ad049630361cefb4ab1844831237609f08380a6a172370370c5b197e769e205270d4e0a36d5d8c300384ad3a04b97f7167a188036f5935fe1e0440c815666c5d68304f0723de7be305845935ab7220dd222ef868040f4d528d1dcbfb62dbc70e0c242402975b6b4009001aec75a1239f23d5650b9809d95d41f288555f74a76e2d8ec9691d240a8d9a9a57851c85e2e390d0fba659780f5528af32ddc75ed1e91e25b644e0d9fc506d1828fe5876beca37860c51b884a806bfdbbf0e0bedcb993b65c9cea1e929a56d78a3b7bc53d1b7ca6fc488e2295ee80e9810f66374c83abcac91f2c4e0b6592dab9bea79e432c469a65efc0488e93d0").to_vec(), + hex!("8103bc05984bd8e93876468ac91f85d3a6afca02e9729db00c0214032d745bcf0d5a4502b7819c39bf85ecf59d380c6b36e0542d8f5f587756fdadc94058c345804ef2f92f35250cbd9abe38f049145ec553be0b232b6b705a7bd95fe6f0134106e0b5440c0661757261209c1e7e0800000000045250535288dba69c63177375777ef2360d7023a05e5af585aa6a1be07aac94cfb0c3979cc88d0605617572610101266c2da415cf67bc39e13f754f212eaba3839d7d5aea0c42376e00e3c376572c1ba3cb1e156ea8b3a3a6dae589a1d62a861f0247487391452b2d0f10862ea780").to_vec(), + hex!("9e710b30bd2eab0352ddcc26417aa1945f43803b3441f15daa8a53147d69c48eac75356fab581febbb8030520b248c5942a14880ec1d5ee4349a9c6f534ce103adef97bc85a794ba786d51bd75d2fe2bc9826134802e2e0716043a02f2f29fdd52922704af194b98545ce5ca832255e8ec41bcdb6480a0718fee6fd849f63aebd00a6e9d09e984d70549c0b5475b16c244090876e628505f0e7b9012096b41c4eb3aaf947f6ea4290800004c5f03c716fb8fff3de61a883bb76adb34a20400808aefdc67024312a782a33b24ee2d1bfa728e3842db64274191fa9a4f0f7a56744c5f0f4993f016e2d2f8e5f43be7bb259486040080949e352413ff8a43f35e73a6077d7a87a2de45fb6ce9bc40ad3717bdbf7a5708").to_vec(), + hex!("9f0b3c252fcb29d88eff4f3de5de4476c3500080680174266144346b8929a369c75acac037ac3b4edfde15b308cddfa28b7def8e805e009b7041665711ae523d4eab10f4cc0b3c7d8f0283381899b208aa42435ba3").to_vec(), + ] }, dip_commitment_proof: DipCommitmentStateProof(vec![ + hex!("7f3200658e5d6cfde41fac5eadc5b800e29cf53cf19360e5cac6055254c77d91a79701381c47e03e17c3284aa85edc851e01a12dfa1fa4ab9a0000802e1cdab36fe7e9ffaa624f5d86fa18b9809536271f60d4363b0bbf672c240f68").to_vec(), + hex!("800c8080da28793d083b197f8d92fc3e77f5064436f1d8eea0fbea56ddb936aba654450080738fe375d48815633f8040a1f7c6311aba813d535b0f23b37e5139c85c6b4f0880b2aafe11c416356c5a97e233670962facb2a18944c3bdc4b9e27f1fa67a5bafe").to_vec(), + hex!("80ffff80353e4d164b13c87910044f1b4e76277e404a0ab46a7cd6c33a65aaadc2375ba88007b1390da34b4dce1328430fd924a6e193517a8148dd70a912c0dc2f7f8d2d4c806322b31235b002ea35614d4eaa1282246a5cefe4a625e5265170d93f2adb9a64802407df9dc8f440a6f8ee7cde4b162e5406ffb5c2a4c99de693bcd20350cc74e0806b313ff9ef1a351bfcc5351cc3b42f8a4fdf1b4a612c350a970677ab3adc91308077be4f344b7438aec6d87a6a29089d64db3dcab9fcec7b91ee4eb37b8afc56c98014e3e0704c9a07636322335a3c663ec9fd9df8b7bf71d6e8183fefecfbfe0e508021d9b25eb4eb0be974f964ba45a39182e42c74034baa38b7499bf7eab8253533804d54f9f6624640788154e78f39dd9b535ba37be663a4ff7b9423bc28637c9f0c8035f638d2e64e75369bba87d0c8aaecf3374294d77818a299ce98dd7d7ff208ee8070de6b035f859c70b5df439f7793eaeca3d858b04dfe70d0a08b1ca06571e87d804aaaff272d09c1b5593870282b1f09e12e8ad325794662edc4a12c02bfd853a880f341940454f25e0b2c93b674eaca644f4ecdd9ace1c3955197f222fda677eebf80f395b7003a2eb1e39c624b9a707a6cb58c3cb6997932fc80662ae19c785a91f580b5e5172489541dfc581e116554b63de15fddf38ffed2b109394749c20b8f6ce3801c763d73cb3d67092a0f18f421080b782403b0a95b6c92bd8dc60e80baf2b1a5").to_vec(), + hex!("810210108082cadebddb74d7ea90430a7205294eedaafa180228e73bd9849228dbabbf32698025b425162cc535f40a255ba9be090e12d1b50aa8a6ec0b108acb60ec048268da").to_vec(), + hex!("9e75edf06348b4330d1e88564111cb3d3000505f0e7b9012096b41c4eb3aaf947f6ea429080000803e5de95874c4bbe730354a3a777b39be54d6141653673cda856be1d5a8893c78").to_vec(), + hex!("9f0bf19e4ed2927982e234d989e812f3f348008028e4e828a83fd632d6d17fa940bb289ef8d04c1c154ecbf583d677460bef22128048f06290dfec2596fa70eaca62ea496d3dc0cd2f51fd40c61b58d7e5b476eebd").to_vec(), + ]), dip_proof: crate::DidMerkleProof { blinded: vec![ + hex!("8020040000").to_vec(), + hex!("6f0c396636316435353033376335383836623033393636633900").to_vec(), + hex!("7f04099e99fc7ce5529bc72a0846778d0f62137ddcbab51a1af2d3e91752962d91b4010000").to_vec(), + ], revealed: vec![ + RevealedDidKey { + id: hex!("a99e99fc7ce5529bc72a0846778d0f62137ddcbab51a1af2d3e91752962d91b4").into(), + relationship: DidVerificationKeyRelationship::Authentication.into(), + details: DidPublicKeyDetails { + key: DidVerificationKey::Sr25519(sr25519::Public(hex!("9cf53cf19360e5cac6055254c77d91a79701381c47e03e17c3284aa85edc851e"))).into(), + block_number: 144 + } + }.into(), + RevealedWeb3Name { + web3_name: b"9f61d55037c5886b03966c9".to_vec().try_into().unwrap(), + claimed_at: 144 + }.into() + ] }, signature: TimeBoundDidSignature::new(did::DidSignature::Sr25519(sr25519::Signature(hex!("3cd5e72f04d248e5155bfdabb94c308a88368db63a8a0cafc15fb3204a709b07da028cf85bd450d9a2bdb6679f2b07ac69188101185ab3acd9f41419cbfb3c81"))), SIGNATURE_VALID_UNTIL) } +} + +// Aliases requires because the pallet does not expose anything public. +#[storage_alias] +type LatestRelayHeads = StorageMap>; +#[storage_alias] +type LatestBlockHeights = StorageValue>, ValueQuery>; + +#[derive(Default)] +pub(crate) struct ExtBuilder(Option, Vec<(u32, H256)>, Option>); + +impl ExtBuilder { + pub(crate) fn with_genesis_hash(mut self, hash: H256) -> Self { + self.0 = Some(hash); + self + } + pub(crate) fn with_relay_roots(mut self, relay_roots: Vec<(u32, H256)>) -> Self { + self.1 = relay_roots; + self + } + pub(crate) fn with_block_number(mut self, block_number: BlockNumberFor) -> Self { + self.2 = Some(block_number); + self + } + + pub(crate) fn build(self) -> sp_io::TestExternalities { + let mut ext = sp_io::TestExternalities::default(); + + ext.execute_with(|| { + if let Some(genesis_hash) = self.0 { + frame_system::BlockHash::::insert(0, genesis_hash); + } + for (relay_block, relay_root) in self.1 { + LatestRelayHeads::insert( + relay_block, + RelayParentInfo { + relay_parent_storage_root: relay_root, + }, + ); + LatestBlockHeights::mutate(|v| { + v.try_push(relay_block).unwrap_or_else(|_| { + panic!("Failed to push relay block ({:#?}, {:#?})", relay_block, relay_root) + }); + }); + } + if let Some(block_number) = self.2 { + System::set_block_number(block_number); + } + }); + + ext + } +} diff --git a/crates/kilt-dip-primitives/src/verifier/parachain/v0/mod.rs b/crates/kilt-dip-primitives/src/verifier/parachain/v0/mod.rs new file mode 100644 index 0000000000..d0e1fc80ff --- /dev/null +++ b/crates/kilt-dip-primitives/src/verifier/parachain/v0/mod.rs @@ -0,0 +1,275 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2024 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use did::KeyIdOf; +use frame_support::ensure; +use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor}; +use pallet_did_lookup::linkable_account::LinkableAccountId; +use pallet_dip_consumer::{traits::IdentityProofVerifier, RuntimeCallOf}; +use pallet_dip_provider::traits::IdentityCommitmentGenerator; +use pallet_web3_names::Web3NameOf; +use parity_scale_codec::Encode; +use sp_runtime::{traits::Zero, SaturatedConversion}; +use sp_std::{marker::PhantomData, vec::Vec}; + +use crate::{ + merkle_proofs::v0::ParachainDipDidProof, + traits::{DipCallOriginFilter, GetWithArg, GetWithoutArg, Incrementable}, + utils::OutputOf, + verifier::errors::DipProofComponentTooLargeError, + DipOriginInfo, DipParachainStateProofVerifierError, RevealedDidKey, +}; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +/// Proof verifier configured given a specific KILT runtime implementation. +/// +/// The generic types +/// indicate the following: +/// * `RelaychainRuntime`: The relaychain runtime definition. +/// * `RelaychainStateRootStore`: A type providing state roots for relaychain +/// blocks. +/// * `KILT_PARA_ID`: The ID of the specific KILT parachain instance. +/// * `KiltRuntime`: A KILT runtime definition. +/// * `DidCallVerifier`: Logic to map `RuntimeCall`s to a specific DID key +/// relationship. This information is used once the Merkle proof is verified, +/// to filter only the revealed keys that match the provided relationship. +/// * `SignedExtra`: Any additional information that must be signed by the DID +/// subject in the cross-chain operation. +/// * `MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT`: The maximum number of leaves that +/// can be revealed as part of the parachain head storage proof. +/// * `MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE`: The maximum size of each leaf +/// revealed as part of the parachain head storage proof. +/// * `MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT`: The maximum number of leaves that +/// can be revealed as part of the DIP commitment storage proof. +/// * `MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE`: The maximum size of each leaf +/// revealed as part of the DIP commitment storage proof. +/// * `MAX_DID_MERKLE_PROOF_LEAVE_COUNT`: The maximum number of *blinded* leaves +/// that can be revealed as part of the DID Merkle proof. +/// * `MAX_DID_MERKLE_PROOF_LEAVE_SIZE`: The maximum size of each *blinded* leaf +/// revealed as part of the DID Merkle proof. +/// * `MAX_DID_MERKLE_LEAVES_REVEALED`: The maximum number of leaves that can be +/// revealed as part of the DID Merkle proof. +pub struct ParachainVerifier< + RelaychainRuntime, + RelaychainStateRootStore, + const KILT_PARA_ID: u32, + KiltRuntime, + DidCallVerifier, + SignedExtra, + const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32, + const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32, + const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32, + const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32, + const MAX_DID_MERKLE_LEAVES_REVEALED: u32, +>( + PhantomData<( + RelaychainRuntime, + RelaychainStateRootStore, + KiltRuntime, + DidCallVerifier, + SignedExtra, + )>, +); + +impl< + ConsumerRuntime, + RelaychainRuntime, + RelaychainStateRootStore, + const KILT_PARA_ID: u32, + KiltRuntime, + DidCallVerifier, + SignedExtra, + const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32, + const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32, + const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32, + const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32, + const MAX_DID_MERKLE_LEAVES_REVEALED: u32, + > IdentityProofVerifier + for ParachainVerifier< + RelaychainRuntime, + RelaychainStateRootStore, + KILT_PARA_ID, + KiltRuntime, + DidCallVerifier, + SignedExtra, + MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT, + MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE, + MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT, + MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE, + MAX_DID_MERKLE_PROOF_LEAVE_COUNT, + MAX_DID_MERKLE_PROOF_LEAVE_SIZE, + MAX_DID_MERKLE_LEAVES_REVEALED, + > where + ConsumerRuntime: pallet_dip_consumer::Config, + ConsumerRuntime::LocalIdentityInfo: Incrementable + Default, + RelaychainRuntime: frame_system::Config, + RelaychainStateRootStore: + GetWithArg, Result = Option>>, + KiltRuntime: frame_system::Config + + pallet_dip_provider::Config + + did::Config + + pallet_web3_names::Config + + pallet_did_lookup::Config, + KiltRuntime::IdentityCommitmentGenerator: + IdentityCommitmentGenerator, + SignedExtra: GetWithoutArg, + SignedExtra::Result: Encode, + DidCallVerifier: DipCallOriginFilter< + RuntimeCallOf, + OriginInfo = Vec, BlockNumberFor, KiltRuntime::AccountId>>, + >, + DidCallVerifier::Error: Into, +{ + type Error = DipParachainStateProofVerifierError; + type Proof = ParachainDipDidProof< + BlockNumberFor, + KeyIdOf, + KiltRuntime::AccountId, + BlockNumberFor, + Web3NameOf, + LinkableAccountId, + BlockNumberFor, + >; + + type VerificationResult = DipOriginInfo< + KeyIdOf, + KiltRuntime::AccountId, + BlockNumberFor, + Web3NameOf, + LinkableAccountId, + MAX_DID_MERKLE_LEAVES_REVEALED, + >; + + fn verify_proof_for_call_against_details( + call: &RuntimeCallOf, + subject: &::Identifier, + submitter: &::AccountId, + identity_details: &mut Option<::LocalIdentityInfo>, + proof: Self::Proof, + ) -> Result { + // 1. Verify parachain state is finalized by relay chain and fresh. + ensure!( + proof.provider_head_proof.proof.len() <= MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT.saturated_into(), + DipParachainStateProofVerifierError::ProofComponentTooLarge( + DipProofComponentTooLargeError::ParachainHeadProofTooManyLeaves as u8 + ) + ); + ensure!( + proof + .provider_head_proof + .proof + .iter() + .all(|l| l.len() <= MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE.saturated_into()), + DipParachainStateProofVerifierError::ProofComponentTooLarge( + DipProofComponentTooLargeError::ParachainHeadProofLeafTooLarge as u8 + ) + ); + let proof_without_relaychain = proof + .verify_provider_head_proof::>( + KILT_PARA_ID, + ) + .map_err(DipParachainStateProofVerifierError::ProofVerification)?; + + // 2. Verify commitment is included in provider parachain state. + ensure!( + proof_without_relaychain.dip_commitment_proof.0.len() + <= MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT.saturated_into(), + DipParachainStateProofVerifierError::ProofComponentTooLarge( + DipProofComponentTooLargeError::DipCommitmentProofTooManyLeaves as u8 + ) + ); + ensure!( + proof_without_relaychain + .dip_commitment_proof + .0 + .iter() + .all(|l| l.len() <= MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE.saturated_into()), + DipParachainStateProofVerifierError::ProofComponentTooLarge( + DipProofComponentTooLargeError::DipCommitmentProofLeafTooLarge as u8 + ) + ); + let proof_without_parachain = proof_without_relaychain + .verify_dip_commitment_proof_for_subject::(subject) + .map_err(DipParachainStateProofVerifierError::ProofVerification)?; + + // 3. Verify DIP Merkle proof. + ensure!( + proof_without_parachain.dip_proof.blinded.len() <= MAX_DID_MERKLE_PROOF_LEAVE_COUNT.saturated_into(), + DipParachainStateProofVerifierError::ProofComponentTooLarge( + DipProofComponentTooLargeError::DipProofTooManyLeaves as u8 + ) + ); + ensure!( + proof_without_parachain + .dip_proof + .blinded + .iter() + .all(|l| l.len() <= MAX_DID_MERKLE_PROOF_LEAVE_SIZE.saturated_into()), + DipParachainStateProofVerifierError::ProofComponentTooLarge( + DipProofComponentTooLargeError::DipProofLeafTooLarge as u8 + ) + ); + let proof_without_dip_merkle = proof_without_parachain + .verify_dip_proof::() + .map_err(DipParachainStateProofVerifierError::ProofVerification)?; + + // 4. Verify call is signed by one of the DID keys revealed in the proof + let current_block_number = frame_system::Pallet::::block_number(); + let consumer_genesis_hash = + frame_system::Pallet::::block_hash(BlockNumberFor::::zero()); + let signed_extra = SignedExtra::get(); + let encoded_payload = ( + call, + &identity_details, + submitter, + proof_without_dip_merkle.signature.valid_until, + consumer_genesis_hash, + signed_extra, + ) + .encode(); + let revealed_did_info = proof_without_dip_merkle + .verify_signature_time(¤t_block_number) + .and_then(|p| p.retrieve_signing_leaves_for_payload(&encoded_payload[..])) + .map_err(DipParachainStateProofVerifierError::ProofVerification)?; + + // 5. Verify the signing key fulfills the requirements + let signing_keys = revealed_did_info + .get_signing_leaves() + .map_err(DipParachainStateProofVerifierError::ProofVerification)?; + DidCallVerifier::check_call_origin_info(call, &signing_keys.cloned().collect::>()) + .map_err(DipParachainStateProofVerifierError::DidOriginError)?; + + // 6. Increment the local details + if let Some(details) = identity_details { + details.increment(); + } else { + *identity_details = Some(Default::default()); + }; + + Ok(revealed_did_info) + } +} diff --git a/crates/kilt-dip-primitives/src/verifier/parachain/v0/tests.rs b/crates/kilt-dip-primitives/src/verifier/parachain/v0/tests.rs new file mode 100644 index 0000000000..76a53ee910 --- /dev/null +++ b/crates/kilt-dip-primitives/src/verifier/parachain/v0/tests.rs @@ -0,0 +1,534 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2024 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use frame_support::{assert_noop, assert_ok}; +use pallet_dip_consumer::traits::IdentityProofVerifier; +use sp_runtime::AccountId32; + +use crate::{ + parachain::v0::mock::{ + call, cross_chain_proof_with_authentication_key_and_web3_name, subject, submitter, wrong_call, wrong_submitter, + ExtBuilder, TestRuntime, Verifier, GENESIS_HASH, IDENTITY_DETAILS, MAX_DID_MERKLE_LEAVES_REVEALED, + MAX_DID_MERKLE_PROOF_LEAVE_COUNT, MAX_DID_MERKLE_PROOF_LEAVE_SIZE, MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT, + MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE, MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT, MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE, + RELAY_BLOCK, RELAY_STATE_ROOT, WRONG_GENESIS_HASH, WRONG_IDENTITY_DETAILS, WRONG_SIGNATURE_VALID_UNTIL, + }, + state_proofs::MerkleProofError, + DipParachainStateProofVerifierError, Error, RevealedAccountId, +}; + +#[test] +fn verify_proof_for_call_against_details_successful() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = cross_chain_proof_with_authentication_key_and_web3_name(); + + ExtBuilder::default() + .with_genesis_hash(GENESIS_HASH) + .with_relay_roots(vec![(RELAY_BLOCK, RELAY_STATE_ROOT)]) + .build() + .execute_with(|| { + assert_ok!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ) + ); + // If details are none, they are inizialited with their default value. + assert_eq!(*details, Some(u32::default())); + }) +} + +#[test] +fn verify_proof_for_call_against_details_relay_proof_too_many_leaves() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = { + let mut proof = cross_chain_proof_with_authentication_key_and_web3_name(); + let leaves_count = proof.provider_head_proof.proof.len(); + // Extend the relaychain proof to include MAX + 1 leaves, causing the proof + // verification to fail + proof.provider_head_proof.proof.extend( + vec![ + vec![0u8; MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE as usize]; + MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT as usize - leaves_count + 1 + ] + .into_iter(), + ); + proof + }; + + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofComponentTooLarge(0) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_relay_proof_leaf_too_large() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = { + let mut proof = cross_chain_proof_with_authentication_key_and_web3_name(); + let last_leave = proof.provider_head_proof.proof.last_mut().unwrap(); + let last_leave_size = last_leave.len(); + // Extend the last leaf of the relaychain proof to include MAX + 1 bytes, + // causing the proof verification to fail + last_leave.extend(vec![0u8; MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE as usize - last_leave_size + 1].into_iter()); + proof + }; + + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofComponentTooLarge(1) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_relay_root_not_found() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = cross_chain_proof_with_authentication_key_and_web3_name(); + + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofVerification(Error::RelayStateRootNotFound) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_relay_proof_invalid() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = { + let mut proof = cross_chain_proof_with_authentication_key_and_web3_name(); + // Reset the provider head proof to an empty proof. + proof.provider_head_proof.proof = Default::default(); + proof + }; + + ExtBuilder::default() + .with_relay_roots(vec![(RELAY_BLOCK, RELAY_STATE_ROOT)]) + .build() + .execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofVerification(Error::ParaHeadMerkleProof( + MerkleProofError::InvalidProof + )) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_parachain_proof_too_many_leaves() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = { + let mut proof = cross_chain_proof_with_authentication_key_and_web3_name(); + let leaves_count = proof.dip_commitment_proof.0.len(); + // Extend the DIP commitment proof to include MAX + 1 leaves, causing the proof + // verification to fail + proof.dip_commitment_proof.0.extend( + vec![ + vec![0u8; MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE as usize]; + MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT as usize - leaves_count + 1 + ] + .into_iter(), + ); + proof + }; + + ExtBuilder::default() + .with_relay_roots(vec![(RELAY_BLOCK, RELAY_STATE_ROOT)]) + .build() + .execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofComponentTooLarge(2) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_parachain_proof_leaf_too_large() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = { + let mut proof = cross_chain_proof_with_authentication_key_and_web3_name(); + let last_leave = proof.dip_commitment_proof.0.last_mut().unwrap(); + let last_leave_size = last_leave.len(); + // Extend the last leaf of the parachain proof to include MAX + 1 bytes, + // causing the proof verification to fail + last_leave.extend(vec![0u8; MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE as usize - last_leave_size + 1].into_iter()); + proof + }; + + ExtBuilder::default() + .with_relay_roots(vec![(RELAY_BLOCK, RELAY_STATE_ROOT)]) + .build() + .execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofComponentTooLarge(3) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_parachain_proof_invalid() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = { + let mut proof = cross_chain_proof_with_authentication_key_and_web3_name(); + // Reset the DIP commitment proof to an empty proof. + proof.dip_commitment_proof.0 = Default::default(); + proof + }; + + ExtBuilder::default() + .with_relay_roots(vec![(RELAY_BLOCK, RELAY_STATE_ROOT)]) + .build() + .execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofVerification(Error::DipCommitmentMerkleProof( + MerkleProofError::InvalidProof + )) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_dip_proof_too_many_leaves() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = { + let mut proof = cross_chain_proof_with_authentication_key_and_web3_name(); + let leaves_count = proof.dip_proof.blinded.len(); + // Extend the DIP proof to include MAX + 1 leaves, causing the proof + // verification to fail + proof.dip_proof.blinded.extend( + vec![ + vec![0u8; MAX_DID_MERKLE_PROOF_LEAVE_SIZE as usize]; + MAX_DID_MERKLE_PROOF_LEAVE_COUNT as usize - leaves_count + 1 + ] + .into_iter(), + ); + proof + }; + + ExtBuilder::default() + .with_relay_roots(vec![(RELAY_BLOCK, RELAY_STATE_ROOT)]) + .build() + .execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofComponentTooLarge(4) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_dip_proof_leaf_too_large() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = { + let mut proof = cross_chain_proof_with_authentication_key_and_web3_name(); + let last_leave = proof.dip_proof.blinded.last_mut().unwrap(); + let last_leave_size = last_leave.len(); + // Extend the last leaf of the parachain proof to include MAX + 1 bytes, + // causing the proof verification to fail + last_leave.extend(vec![0u8; MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE as usize - last_leave_size + 1].into_iter()); + proof + }; + + ExtBuilder::default() + .with_relay_roots(vec![(RELAY_BLOCK, RELAY_STATE_ROOT)]) + .build() + .execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofComponentTooLarge(5) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_dip_proof_too_many_revealed_keys() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = { + let mut proof = cross_chain_proof_with_authentication_key_and_web3_name(); + let leaves_count = proof.dip_proof.revealed.len(); + // Extend the DIP proof to include MAX + 1 revealed leaves, causing the proof + // verification to fail + proof.dip_proof.revealed.extend( + vec![ + RevealedAccountId(AccountId32::new([100; 32]).into()).into(); + MAX_DID_MERKLE_LEAVES_REVEALED as usize - leaves_count + 1 + ] + .into_iter(), + ); + proof + }; + + ExtBuilder::default() + .with_relay_roots(vec![(RELAY_BLOCK, RELAY_STATE_ROOT)]) + .build() + .execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofVerification(Error::TooManyLeavesRevealed) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_dip_proof_invalid() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = { + let mut proof = cross_chain_proof_with_authentication_key_and_web3_name(); + // Reset the DIP proof to an empty proof. + proof.dip_proof.blinded = Default::default(); + proof + }; + + ExtBuilder::default() + .with_relay_roots(vec![(RELAY_BLOCK, RELAY_STATE_ROOT)]) + .build() + .execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofVerification(Error::InvalidDidMerkleProof) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_did_signature_signature_not_fresh() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = cross_chain_proof_with_authentication_key_and_web3_name(); + + ExtBuilder::default() + // We get past the maximum block at which the signature is to be considered valid. + .with_block_number(proof.signature.valid_until + 1) + .with_relay_roots(vec![(RELAY_BLOCK, RELAY_STATE_ROOT)]) + .build() + .execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofVerification(Error::InvalidSignatureTime) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_did_signature_different_call() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = cross_chain_proof_with_authentication_key_and_web3_name(); + + ExtBuilder::default() + .with_relay_roots(vec![(RELAY_BLOCK, RELAY_STATE_ROOT)]) + .with_genesis_hash(GENESIS_HASH) + .build() + .execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + // Different encoding for the call, will result in DID signature verification failure. + &wrong_call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofVerification(Error::InvalidDidKeyRevealed) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_did_signature_different_identity_details() { + // Wrong details, will result in DID signature verification failure. + let details = &mut WRONG_IDENTITY_DETAILS.clone(); + let proof = cross_chain_proof_with_authentication_key_and_web3_name(); + + ExtBuilder::default() + .with_relay_roots(vec![(RELAY_BLOCK, RELAY_STATE_ROOT)]) + .with_genesis_hash(GENESIS_HASH) + .build() + .execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofVerification(Error::InvalidDidKeyRevealed) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_did_signature_different_submitter_address() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = cross_chain_proof_with_authentication_key_and_web3_name(); + + ExtBuilder::default() + .with_relay_roots(vec![(RELAY_BLOCK, RELAY_STATE_ROOT)]) + .with_genesis_hash(GENESIS_HASH) + .build() + .execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + // Different submitter, will result in DID signature verification failure. + &wrong_submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofVerification(Error::InvalidDidKeyRevealed) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_did_signature_different_signature_expiration() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = { + let mut proof = cross_chain_proof_with_authentication_key_and_web3_name(); + // Different signature expiration, will result in DID signature verification + // failure. + proof.signature.valid_until = WRONG_SIGNATURE_VALID_UNTIL; + proof + }; + + ExtBuilder::default() + .with_relay_roots(vec![(RELAY_BLOCK, RELAY_STATE_ROOT)]) + .with_genesis_hash(GENESIS_HASH) + .build() + .execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofVerification(Error::InvalidDidKeyRevealed) + ); + }) +} + +#[test] +fn verify_proof_for_call_against_details_did_signature_different_genesis_hash() { + let details = &mut IDENTITY_DETAILS.clone(); + let proof = cross_chain_proof_with_authentication_key_and_web3_name(); + + ExtBuilder::default() + .with_relay_roots(vec![(RELAY_BLOCK, RELAY_STATE_ROOT)]) + // Different genesis hash, will result in DID signature verification failure. + .with_genesis_hash(WRONG_GENESIS_HASH) + .build() + .execute_with(|| { + assert_noop!( + >::verify_proof_for_call_against_details( + &call(), + &subject(), + &submitter(), + details, + proof, + ), + DipParachainStateProofVerifierError::ProofVerification(Error::InvalidDidKeyRevealed) + ); + }) +} diff --git a/crates/kilt-dip-primitives/src/verifier/relaychain.rs b/crates/kilt-dip-primitives/src/verifier/relaychain.rs deleted file mode 100644 index a97d2e7e1e..0000000000 --- a/crates/kilt-dip-primitives/src/verifier/relaychain.rs +++ /dev/null @@ -1,429 +0,0 @@ -// KILT Blockchain – https://botlabs.org -// Copyright (C) 2019-2024 BOTLabs GmbH - -// The KILT Blockchain is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The KILT Blockchain is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -// If you feel like getting in touch with us, you can do so at info@botlabs.org - -use did::KeyIdOf; -use frame_system::pallet_prelude::BlockNumberFor; -use pallet_did_lookup::linkable_account::LinkableAccountId; -use pallet_dip_consumer::{traits::IdentityProofVerifier, RuntimeCallOf}; -use pallet_dip_provider::{traits::IdentityCommitmentGenerator, IdentityCommitmentOf}; -use pallet_web3_names::Web3NameOf; -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_core::U256; -use sp_runtime::traits::Hash; -use sp_std::{fmt::Debug, marker::PhantomData}; - -use crate::{ - merkle::v0::RevealedDidKey, - traits::{DipCallOriginFilter, GetWithArg, GetWithoutArg, Incrementable}, - utils::OutputOf, - DipOriginInfo, Error, -}; - -/// A KILT-specific DIP identity proof for a parent consumer that supports -/// versioning. -/// -/// For more info, refer to the version-specific proofs. -#[derive(Encode, Decode, PartialEq, Eq, Debug, TypeInfo, Clone)] -pub enum VersionedRelaychainStateProof< - ConsumerBlockNumber: Copy + Into + TryFrom, - ConsumerBlockHasher: Hash, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, -> { - V0( - crate::merkle::v0::RelayDipDidProof< - ConsumerBlockNumber, - ConsumerBlockHasher, - KiltDidKeyId, - KiltAccountId, - KiltBlockNumber, - KiltWeb3Name, - KiltLinkableAccountId, - >, - ), -} - -pub enum DipRelaychainStateProofVerifierError { - UnsupportedVersion, - ProofComponentTooLarge(u8), - ProofVerification(Error), - DidOriginError(DidOriginError), - Internal, -} - -impl From> for u16 -where - DidOriginError: Into, -{ - fn from(value: DipRelaychainStateProofVerifierError) -> Self { - match value { - // DO NOT USE 0 - // Errors of different sub-parts are separated by a `u8::MAX`. - // A value of 0 would make it confusing whether it's the previous sub-part error (u8::MAX) - // or the new sub-part error (u8::MAX + 0). - DipRelaychainStateProofVerifierError::UnsupportedVersion => 1, - DipRelaychainStateProofVerifierError::ProofComponentTooLarge(component_id) => { - u8::MAX as u16 + component_id as u16 - } - DipRelaychainStateProofVerifierError::ProofVerification(error) => { - u8::MAX as u16 * 2 + u8::from(error) as u16 - } - DipRelaychainStateProofVerifierError::DidOriginError(error) => u8::MAX as u16 * 3 + error.into() as u16, - DipRelaychainStateProofVerifierError::Internal => u16::MAX, - } - } -} - -/// Versioned proof verifier. For version-specific description, refer to each -/// verifier's documentation. -pub struct KiltVersionedRelaychainVerifier< - ConsumerBlockHashStore, - const KILT_PARA_ID: u32, - KiltRuntime, - DidCallVerifier, - SignedExtra = (), - const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32 = 64, - const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32 = 128, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32 = 64, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32 = 128, - const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32 = 64, - const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32 = 128, - const MAX_DID_MERKLE_LEAVES_REVEALED: u32 = 64, ->(#[allow(clippy::type_complexity)] PhantomData<(ConsumerBlockHashStore, KiltRuntime, DidCallVerifier, SignedExtra)>); - -impl< - ConsumerRuntime, - ConsumerBlockHashStore, - const KILT_PARA_ID: u32, - KiltRuntime, - DidCallVerifier, - SignedExtra, - const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32, - const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32, - const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32, - const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32, - const MAX_DID_MERKLE_LEAVES_REVEALED: u32, - > IdentityProofVerifier - for KiltVersionedRelaychainVerifier< - ConsumerBlockHashStore, - KILT_PARA_ID, - KiltRuntime, - DidCallVerifier, - SignedExtra, - MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT, - MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE, - MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT, - MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE, - MAX_DID_MERKLE_PROOF_LEAVE_COUNT, - MAX_DID_MERKLE_PROOF_LEAVE_SIZE, - MAX_DID_MERKLE_LEAVES_REVEALED, - > where - ConsumerRuntime: pallet_dip_consumer::Config, - ConsumerRuntime::LocalIdentityInfo: Incrementable + Default, - BlockNumberFor: Into + TryFrom, - ConsumerBlockHashStore: - GetWithArg, Result = Option>>, - KiltRuntime: frame_system::Config - + pallet_dip_provider::Config - + did::Config - + pallet_web3_names::Config - + pallet_did_lookup::Config, - KiltRuntime::IdentityCommitmentGenerator: IdentityCommitmentGenerator, - SignedExtra: GetWithoutArg, - SignedExtra::Result: Encode, - DidCallVerifier: DipCallOriginFilter< - RuntimeCallOf, - OriginInfo = RevealedDidKey, BlockNumberFor, KiltRuntime::AccountId>, - >, - DidCallVerifier::Error: Into, -{ - type Error = DipRelaychainStateProofVerifierError; - type Proof = VersionedRelaychainStateProof< - BlockNumberFor, - ConsumerRuntime::Hashing, - KeyIdOf, - KiltRuntime::AccountId, - BlockNumberFor, - Web3NameOf, - LinkableAccountId, - >; - type VerificationResult = DipOriginInfo< - KeyIdOf, - KiltRuntime::AccountId, - BlockNumberFor, - Web3NameOf, - LinkableAccountId, - MAX_DID_MERKLE_LEAVES_REVEALED, - >; - - fn verify_proof_for_call_against_details( - call: &RuntimeCallOf, - subject: &ConsumerRuntime::Identifier, - submitter: &ConsumerRuntime::AccountId, - identity_details: &mut Option, - proof: Self::Proof, - ) -> Result { - match proof { - VersionedRelaychainStateProof::V0(v0_proof) => as IdentityProofVerifier>::verify_proof_for_call_against_details( - call, - subject, - submitter, - identity_details, - v0_proof, - ), - } - } -} - -pub mod v0 { - use super::*; - - use frame_support::ensure; - use frame_system::pallet_prelude::HeaderFor; - use sp_runtime::{traits::Zero, SaturatedConversion}; - - use crate::RelayDipDidProof; - - /// Proof verifier configured given a specific KILT runtime implementation. - /// - /// The generic types are the following: - /// - /// * `ConsumerBlockHashStore`: A type providing block hashes for the - /// relaychain blocks. - /// * `KILT_PARA_ID`: The ID of the specific KILT parachain instance. - /// * `KiltRuntime`: A KILT runtime definition. - /// * `DidCallVerifier`: Logic to map `RuntimeCall`s to a specific DID key - /// relationship. This information is used once the Merkle proof is - /// verified, to filter only the revealed keys that match the provided - /// relationship. - /// * `SignedExtra`: Any additional information that must be signed by the - /// DID subject in the cross-chain operation. - /// * `MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT`: The maximum number of leaves - /// that can be revealed as part of the parachain head storage proof. - /// * `MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE`: The maximum size of each leaf - /// revealed as part of the parachain head storage proof. - /// * `MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT`: The maximum number of leaves - /// that can be revealed as part of the DIP commitment storage proof. - /// * `MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE`: The maximum size of each leaf - /// revealed as part of the DIP commitment storage proof. - /// * `MAX_DID_MERKLE_PROOF_LEAVE_COUNT`: The maximum number of *blinded* - /// leaves that can be revealed as part of the DID Merkle proof. - /// * `MAX_DID_MERKLE_PROOF_LEAVE_SIZE`: The maximum size of each *blinded* - /// leaf revealed as part of the DID Merkle proof. - /// * `MAX_DID_MERKLE_PROOF_LEAVE_SIZE`: The maximum number of leaves that - /// can be revealed as part of the DID Merkle proof. - pub struct RelaychainVerifier< - ConsumerBlockHashStore, - const KILT_PARA_ID: u32, - KiltRuntime, - DidCallVerifier, - SignedExtra, - const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32, - const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32, - const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32, - const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32, - const MAX_DID_MERKLE_LEAVES_REVEALED: u32, - >( - #[allow(clippy::type_complexity)] - PhantomData<(ConsumerBlockHashStore, KiltRuntime, DidCallVerifier, SignedExtra)>, - ); - - impl< - ConsumerRuntime, - ConsumerBlockHashStore, - const KILT_PARA_ID: u32, - KiltRuntime, - DidCallVerifier, - SignedExtra, - const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32, - const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32, - const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32, - const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32, - const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32, - const MAX_DID_MERKLE_LEAVES_REVEALED: u32, - > IdentityProofVerifier - for RelaychainVerifier< - ConsumerBlockHashStore, - KILT_PARA_ID, - KiltRuntime, - DidCallVerifier, - SignedExtra, - MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT, - MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE, - MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT, - MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE, - MAX_DID_MERKLE_PROOF_LEAVE_COUNT, - MAX_DID_MERKLE_PROOF_LEAVE_SIZE, - MAX_DID_MERKLE_LEAVES_REVEALED, - > where - ConsumerRuntime: pallet_dip_consumer::Config, - ConsumerRuntime::LocalIdentityInfo: Incrementable + Default, - BlockNumberFor: Into + TryFrom, - ConsumerBlockHashStore: - GetWithArg, Result = Option>>, - KiltRuntime: frame_system::Config - + pallet_dip_provider::Config - + did::Config - + pallet_web3_names::Config - + pallet_did_lookup::Config, - KiltRuntime::IdentityCommitmentGenerator: - IdentityCommitmentGenerator, - IdentityCommitmentOf: Into, - SignedExtra: GetWithoutArg, - SignedExtra::Result: Encode, - DidCallVerifier: DipCallOriginFilter< - RuntimeCallOf, - OriginInfo = RevealedDidKey, BlockNumberFor, KiltRuntime::AccountId>, - >, - DidCallVerifier::Error: Into, - { - type Error = DipRelaychainStateProofVerifierError; - type Proof = RelayDipDidProof< - BlockNumberFor, - ConsumerRuntime::Hashing, - KeyIdOf, - KiltRuntime::AccountId, - BlockNumberFor, - Web3NameOf, - LinkableAccountId, - >; - type VerificationResult = DipOriginInfo< - KeyIdOf, - KiltRuntime::AccountId, - BlockNumberFor, - Web3NameOf, - LinkableAccountId, - MAX_DID_MERKLE_LEAVES_REVEALED, - >; - - fn verify_proof_for_call_against_details( - call: &RuntimeCallOf, - subject: &ConsumerRuntime::Identifier, - submitter: &ConsumerRuntime::AccountId, - identity_details: &mut Option, - proof: Self::Proof, - ) -> Result { - // 1. Verify provided relaychain header. - let proof_without_header = proof - .verify_relay_header::() - .map_err(DipRelaychainStateProofVerifierError::ProofVerification)?; - - // 2. Verify parachain state is finalized by relay chain and fresh. - ensure!( - proof_without_header.provider_head_proof.proof.len() - <= MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT.saturated_into(), - DipRelaychainStateProofVerifierError::ProofComponentTooLarge(0) - ); - ensure!( - proof_without_header - .provider_head_proof - .proof - .iter() - .all(|l| l.len() <= MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE.saturated_into()), - DipRelaychainStateProofVerifierError::ProofComponentTooLarge(1) - ); - let proof_without_relaychain = proof_without_header - .verify_provider_head_proof::>(KILT_PARA_ID) - .map_err(DipRelaychainStateProofVerifierError::ProofVerification)?; - - // 3. Verify commitment is included in provider parachain state. - ensure!( - proof_without_relaychain.dip_commitment_proof.0.len() - <= MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT.saturated_into(), - DipRelaychainStateProofVerifierError::ProofComponentTooLarge(2) - ); - ensure!( - proof_without_relaychain - .dip_commitment_proof - .0 - .iter() - .all(|l| l.len() <= MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE.saturated_into()), - DipRelaychainStateProofVerifierError::ProofComponentTooLarge(3) - ); - let proof_without_parachain = proof_without_relaychain - .verify_dip_commitment_proof_for_subject::(subject) - .map_err(DipRelaychainStateProofVerifierError::ProofVerification)?; - - // 4. Verify DIP Merkle proof. - ensure!( - proof_without_parachain.dip_proof.blinded.len() <= MAX_DID_MERKLE_PROOF_LEAVE_COUNT.saturated_into(), - DipRelaychainStateProofVerifierError::ProofComponentTooLarge(4) - ); - ensure!( - proof_without_parachain - .dip_proof - .blinded - .iter() - .all(|l| l.len() <= MAX_DID_MERKLE_PROOF_LEAVE_SIZE.saturated_into()), - DipRelaychainStateProofVerifierError::ProofComponentTooLarge(5) - ); - let proof_without_dip_merkle = proof_without_parachain - .verify_dip_proof::() - .map_err(DipRelaychainStateProofVerifierError::ProofVerification)?; - - // 5. Verify call is signed by one of the DID keys revealed in the proof - let current_block_number = frame_system::Pallet::::block_number(); - let consumer_genesis_hash = - frame_system::Pallet::::block_hash(BlockNumberFor::::zero()); - let signed_extra = SignedExtra::get(); - let encoded_payload = (call, &identity_details, submitter, consumer_genesis_hash, signed_extra).encode(); - let revealed_did_info = proof_without_dip_merkle - .verify_signature_time(¤t_block_number) - .and_then(|p| p.retrieve_signing_leaf_for_payload(&encoded_payload[..])) - .map_err(DipRelaychainStateProofVerifierError::ProofVerification)?; - - // 6. Verify the signing key fulfills the requirements - let signing_key = revealed_did_info - .get_signing_leaf() - .map_err(DipRelaychainStateProofVerifierError::ProofVerification)?; - DidCallVerifier::check_call_origin_info(call, signing_key) - .map_err(DipRelaychainStateProofVerifierError::DidOriginError)?; - - // 7. Increment the local details - if let Some(details) = identity_details { - details.increment(); - } else { - *identity_details = Some(Default::default()); - }; - - Ok(revealed_did_info) - } - } -} diff --git a/crates/kilt-dip-primitives/src/verifier/relaychain/error.rs b/crates/kilt-dip-primitives/src/verifier/relaychain/error.rs new file mode 100644 index 0000000000..9bfd8e2a70 --- /dev/null +++ b/crates/kilt-dip-primitives/src/verifier/relaychain/error.rs @@ -0,0 +1,80 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2024 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use crate::Error; + +#[cfg_attr(test, derive(enum_iterator::Sequence))] +pub enum DipRelaychainStateProofVerifierError { + UnsupportedVersion, + ProofComponentTooLarge(u8), + ProofVerification(Error), + DidOriginError(DidOriginError), + Internal, +} + +impl From> for u16 +where + DidOriginError: Into, +{ + fn from(value: DipRelaychainStateProofVerifierError) -> Self { + match value { + // DO NOT USE 0 + // Errors of different sub-parts are separated by a `u8::MAX`. + // A value of 0 would make it confusing whether it's the previous sub-part error (u8::MAX) + // or the new sub-part error (u8::MAX + 0). + DipRelaychainStateProofVerifierError::UnsupportedVersion => 1, + DipRelaychainStateProofVerifierError::ProofComponentTooLarge(component_id) => { + u8::MAX as u16 + component_id as u16 + } + DipRelaychainStateProofVerifierError::ProofVerification(error) => { + u8::MAX as u16 * 2 + u8::from(error) as u16 + } + DipRelaychainStateProofVerifierError::DidOriginError(error) => u8::MAX as u16 * 3 + error.into() as u16, + DipRelaychainStateProofVerifierError::Internal => u16::MAX, + } + } +} + +#[test] +fn dip_relaychain_state_proof_verifier_error_value_never_zero() { + assert!( + enum_iterator::all::>().all(|e| u16::from(e) != 0), + "One of the u8 values for the error is 0, which is not allowed." + ); +} + +#[test] +fn dip_relaychain_state_proof_verifier_error_value_not_duplicated() { + enum_iterator::all::>().fold( + sp_std::collections::btree_set::BTreeSet::::new(), + |mut values, new_value| { + let new_encoded_value = u16::from(new_value); + // DidOriginError is generic, and we cannot test its constraints in this unit + // test, so we skip it. + if new_encoded_value == u8::MAX as u16 * 3 { + return values; + } + assert!( + values.insert(new_encoded_value), + "Failed to add unique value {:#?} for error variant", + new_encoded_value + ); + values + }, + ); +} diff --git a/crates/kilt-dip-primitives/src/verifier/relaychain/mod.rs b/crates/kilt-dip-primitives/src/verifier/relaychain/mod.rs new file mode 100644 index 0000000000..e3d98bd74c --- /dev/null +++ b/crates/kilt-dip-primitives/src/verifier/relaychain/mod.rs @@ -0,0 +1,237 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2024 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use did::KeyIdOf; +use frame_system::pallet_prelude::BlockNumberFor; +use pallet_did_lookup::linkable_account::LinkableAccountId; +use pallet_dip_consumer::{traits::IdentityProofVerifier, RuntimeCallOf}; +use pallet_dip_provider::traits::IdentityCommitmentGenerator; +use pallet_web3_names::Web3NameOf; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_core::U256; +use sp_runtime::traits::Hash; +use sp_std::{fmt::Debug, marker::PhantomData, vec::Vec}; + +use crate::{ + merkle_proofs::v0::RevealedDidKey, + traits::{DipCallOriginFilter, GetWithArg, GetWithoutArg, Incrementable}, + utils::OutputOf, + DipOriginInfo, RelayDipDidProof, +}; + +pub mod v0; + +mod error; +pub use error::*; + +/// A KILT-specific DIP identity proof for a parent consumer that supports +/// versioning. +/// +/// For more info, refer to the version-specific proofs. +#[derive(Encode, Decode, PartialEq, Eq, Debug, TypeInfo, Clone)] +pub enum VersionedRelaychainStateProof< + ConsumerBlockNumber: Copy + Into + TryFrom, + ConsumerBlockHasher: Hash, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, +> { + V0( + RelayDipDidProof< + ConsumerBlockNumber, + ConsumerBlockHasher, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + >, + ), +} + +impl< + ConsumerBlockNumber: Copy + Into + TryFrom, + ConsumerBlockHasher: Hash, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + > + From< + RelayDipDidProof< + ConsumerBlockNumber, + ConsumerBlockHasher, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + >, + > + for VersionedRelaychainStateProof< + ConsumerBlockNumber, + ConsumerBlockHasher, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + > +{ + fn from( + value: RelayDipDidProof< + ConsumerBlockNumber, + ConsumerBlockHasher, + KiltDidKeyId, + KiltAccountId, + KiltBlockNumber, + KiltWeb3Name, + KiltLinkableAccountId, + >, + ) -> Self { + Self::V0(value) + } +} + +pub const DEFAULT_MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32 = 128; +pub const DEFAULT_MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32 = 1024; +pub const DEFAULT_MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32 = 128; +pub const DEFAULT_MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32 = 1024; +pub const DEFAULT_MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32 = 128; +pub const DEFAULT_MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32 = 1024; +pub const DEFAULT_MAX_DID_MERKLE_LEAVES_REVEALED: u32 = 128; + +/// Versioned proof verifier. For version-specific description, refer to each +/// verifier's documentation. +pub struct KiltVersionedRelaychainVerifier< + ConsumerBlockHashStore, + const KILT_PARA_ID: u32, + KiltRuntime, + DidCallVerifier, + SignedExtra = (), + const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32 = DEFAULT_MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT, + const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32 = DEFAULT_MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32 = DEFAULT_MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32 = DEFAULT_MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE, + const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32 = DEFAULT_MAX_DID_MERKLE_PROOF_LEAVE_COUNT, + const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32 = DEFAULT_MAX_DID_MERKLE_PROOF_LEAVE_SIZE, + const MAX_DID_MERKLE_LEAVES_REVEALED: u32 = DEFAULT_MAX_DID_MERKLE_LEAVES_REVEALED, +>(#[allow(clippy::type_complexity)] PhantomData<(ConsumerBlockHashStore, KiltRuntime, DidCallVerifier, SignedExtra)>); + +impl< + ConsumerRuntime, + ConsumerBlockHashStore, + const KILT_PARA_ID: u32, + KiltRuntime, + DidCallVerifier, + SignedExtra, + const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32, + const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32, + const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32, + const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32, + const MAX_DID_MERKLE_LEAVES_REVEALED: u32, + > IdentityProofVerifier + for KiltVersionedRelaychainVerifier< + ConsumerBlockHashStore, + KILT_PARA_ID, + KiltRuntime, + DidCallVerifier, + SignedExtra, + MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT, + MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE, + MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT, + MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE, + MAX_DID_MERKLE_PROOF_LEAVE_COUNT, + MAX_DID_MERKLE_PROOF_LEAVE_SIZE, + MAX_DID_MERKLE_LEAVES_REVEALED, + > where + ConsumerRuntime: pallet_dip_consumer::Config, + ConsumerRuntime::LocalIdentityInfo: Incrementable + Default, + BlockNumberFor: Into + TryFrom, + ConsumerBlockHashStore: + GetWithArg, Result = Option>>, + KiltRuntime: frame_system::Config + + pallet_dip_provider::Config + + did::Config + + pallet_web3_names::Config + + pallet_did_lookup::Config, + KiltRuntime::IdentityCommitmentGenerator: IdentityCommitmentGenerator, + SignedExtra: GetWithoutArg, + SignedExtra::Result: Encode, + DidCallVerifier: DipCallOriginFilter< + RuntimeCallOf, + OriginInfo = Vec, BlockNumberFor, KiltRuntime::AccountId>>, + >, + DidCallVerifier::Error: Into, +{ + type Error = DipRelaychainStateProofVerifierError; + type Proof = VersionedRelaychainStateProof< + BlockNumberFor, + ConsumerRuntime::Hashing, + KeyIdOf, + KiltRuntime::AccountId, + BlockNumberFor, + Web3NameOf, + LinkableAccountId, + >; + type VerificationResult = DipOriginInfo< + KeyIdOf, + KiltRuntime::AccountId, + BlockNumberFor, + Web3NameOf, + LinkableAccountId, + MAX_DID_MERKLE_LEAVES_REVEALED, + >; + + fn verify_proof_for_call_against_details( + call: &RuntimeCallOf, + subject: &ConsumerRuntime::Identifier, + submitter: &ConsumerRuntime::AccountId, + identity_details: &mut Option, + proof: Self::Proof, + ) -> Result { + match proof { + VersionedRelaychainStateProof::V0(v0_proof) => as IdentityProofVerifier>::verify_proof_for_call_against_details( + call, + subject, + submitter, + identity_details, + v0_proof, + ), + } + } +} diff --git a/crates/kilt-dip-primitives/src/verifier/relaychain/v0/mod.rs b/crates/kilt-dip-primitives/src/verifier/relaychain/v0/mod.rs new file mode 100644 index 0000000000..0dc7af8ac3 --- /dev/null +++ b/crates/kilt-dip-primitives/src/verifier/relaychain/v0/mod.rs @@ -0,0 +1,252 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2024 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use did::KeyIdOf; +use frame_support::ensure; +use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor}; +use pallet_did_lookup::linkable_account::LinkableAccountId; +use pallet_dip_consumer::{traits::IdentityProofVerifier, RuntimeCallOf}; +use pallet_dip_provider::{traits::IdentityCommitmentGenerator, IdentityCommitmentOf}; +use pallet_web3_names::Web3NameOf; +use parity_scale_codec::Encode; +use sp_core::U256; +use sp_runtime::{traits::Zero, SaturatedConversion}; +use sp_std::{marker::PhantomData, vec::Vec}; + +use crate::{ + traits::{DipCallOriginFilter, GetWithArg, GetWithoutArg, Incrementable}, + utils::OutputOf, + verifier::errors::DipProofComponentTooLargeError, + DipOriginInfo, DipRelaychainStateProofVerifierError, RelayDipDidProof, RevealedDidKey, +}; + +/// Proof verifier configured given a specific KILT runtime implementation. +/// +/// The generic types are the following: +/// +/// * `ConsumerBlockHashStore`: A type providing block hashes for the relaychain +/// blocks. +/// * `KILT_PARA_ID`: The ID of the specific KILT parachain instance. +/// * `KiltRuntime`: A KILT runtime definition. +/// * `DidCallVerifier`: Logic to map `RuntimeCall`s to a specific DID key +/// relationship. This information is used once the Merkle proof is verified, +/// to filter only the revealed keys that match the provided relationship. +/// * `SignedExtra`: Any additional information that must be signed by the DID +/// subject in the cross-chain operation. +/// * `MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT`: The maximum number of leaves that +/// can be revealed as part of the parachain head storage proof. +/// * `MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE`: The maximum size of each leaf +/// revealed as part of the parachain head storage proof. +/// * `MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT`: The maximum number of leaves that +/// can be revealed as part of the DIP commitment storage proof. +/// * `MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE`: The maximum size of each leaf +/// revealed as part of the DIP commitment storage proof. +/// * `MAX_DID_MERKLE_PROOF_LEAVE_COUNT`: The maximum number of *blinded* leaves +/// that can be revealed as part of the DID Merkle proof. +/// * `MAX_DID_MERKLE_PROOF_LEAVE_SIZE`: The maximum size of each *blinded* leaf +/// revealed as part of the DID Merkle proof. +/// * `MAX_DID_MERKLE_PROOF_LEAVE_SIZE`: The maximum number of leaves that can +/// be revealed as part of the DID Merkle proof. +pub struct RelaychainVerifier< + ConsumerBlockHashStore, + const KILT_PARA_ID: u32, + KiltRuntime, + DidCallVerifier, + SignedExtra, + const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32, + const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32, + const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32, + const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32, + const MAX_DID_MERKLE_LEAVES_REVEALED: u32, +>(#[allow(clippy::type_complexity)] PhantomData<(ConsumerBlockHashStore, KiltRuntime, DidCallVerifier, SignedExtra)>); + +impl< + ConsumerRuntime, + ConsumerBlockHashStore, + const KILT_PARA_ID: u32, + KiltRuntime, + DidCallVerifier, + SignedExtra, + const MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT: u32, + const MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE: u32, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT: u32, + const MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE: u32, + const MAX_DID_MERKLE_PROOF_LEAVE_COUNT: u32, + const MAX_DID_MERKLE_PROOF_LEAVE_SIZE: u32, + const MAX_DID_MERKLE_LEAVES_REVEALED: u32, + > IdentityProofVerifier + for RelaychainVerifier< + ConsumerBlockHashStore, + KILT_PARA_ID, + KiltRuntime, + DidCallVerifier, + SignedExtra, + MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT, + MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE, + MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT, + MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE, + MAX_DID_MERKLE_PROOF_LEAVE_COUNT, + MAX_DID_MERKLE_PROOF_LEAVE_SIZE, + MAX_DID_MERKLE_LEAVES_REVEALED, + > where + ConsumerRuntime: pallet_dip_consumer::Config, + ConsumerRuntime::LocalIdentityInfo: Incrementable + Default, + BlockNumberFor: Into + TryFrom, + ConsumerBlockHashStore: + GetWithArg, Result = Option>>, + KiltRuntime: frame_system::Config + + pallet_dip_provider::Config + + did::Config + + pallet_web3_names::Config + + pallet_did_lookup::Config, + KiltRuntime::IdentityCommitmentGenerator: IdentityCommitmentGenerator, + IdentityCommitmentOf: Into, + SignedExtra: GetWithoutArg, + SignedExtra::Result: Encode, + DidCallVerifier: DipCallOriginFilter< + RuntimeCallOf, + OriginInfo = Vec, BlockNumberFor, KiltRuntime::AccountId>>, + >, + DidCallVerifier::Error: Into, +{ + type Error = DipRelaychainStateProofVerifierError; + type Proof = RelayDipDidProof< + BlockNumberFor, + ConsumerRuntime::Hashing, + KeyIdOf, + KiltRuntime::AccountId, + BlockNumberFor, + Web3NameOf, + LinkableAccountId, + >; + type VerificationResult = DipOriginInfo< + KeyIdOf, + KiltRuntime::AccountId, + BlockNumberFor, + Web3NameOf, + LinkableAccountId, + MAX_DID_MERKLE_LEAVES_REVEALED, + >; + + fn verify_proof_for_call_against_details( + call: &RuntimeCallOf, + subject: &ConsumerRuntime::Identifier, + submitter: &ConsumerRuntime::AccountId, + identity_details: &mut Option, + proof: Self::Proof, + ) -> Result { + // 1. Verify provided relaychain header. + let proof_without_header = proof + .verify_relay_header::() + .map_err(DipRelaychainStateProofVerifierError::ProofVerification)?; + + // 2. Verify parachain state is finalized by relay chain and fresh. + ensure!( + proof_without_header.provider_head_proof.proof.len() + <= MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT.saturated_into(), + DipRelaychainStateProofVerifierError::ProofComponentTooLarge( + DipProofComponentTooLargeError::ParachainHeadProofTooManyLeaves as u8 + ) + ); + ensure!( + proof_without_header + .provider_head_proof + .proof + .iter() + .all(|l| l.len() <= MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE.saturated_into()), + DipRelaychainStateProofVerifierError::ProofComponentTooLarge( + DipProofComponentTooLargeError::ParachainHeadProofLeafTooLarge as u8 + ) + ); + let proof_without_relaychain = proof_without_header + .verify_provider_head_proof::>(KILT_PARA_ID) + .map_err(DipRelaychainStateProofVerifierError::ProofVerification)?; + + // 3. Verify commitment is included in provider parachain state. + ensure!( + proof_without_relaychain.dip_commitment_proof.0.len() + <= MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT.saturated_into(), + DipRelaychainStateProofVerifierError::ProofComponentTooLarge( + DipProofComponentTooLargeError::DipCommitmentProofTooManyLeaves as u8 + ) + ); + ensure!( + proof_without_relaychain + .dip_commitment_proof + .0 + .iter() + .all(|l| l.len() <= MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE.saturated_into()), + DipRelaychainStateProofVerifierError::ProofComponentTooLarge( + DipProofComponentTooLargeError::DipCommitmentProofLeafTooLarge as u8 + ) + ); + let proof_without_parachain = proof_without_relaychain + .verify_dip_commitment_proof_for_subject::(subject) + .map_err(DipRelaychainStateProofVerifierError::ProofVerification)?; + + // 4. Verify DIP Merkle proof. + ensure!( + proof_without_parachain.dip_proof.blinded.len() <= MAX_DID_MERKLE_PROOF_LEAVE_COUNT.saturated_into(), + DipRelaychainStateProofVerifierError::ProofComponentTooLarge( + DipProofComponentTooLargeError::DipProofTooManyLeaves as u8 + ) + ); + ensure!( + proof_without_parachain + .dip_proof + .blinded + .iter() + .all(|l| l.len() <= MAX_DID_MERKLE_PROOF_LEAVE_SIZE.saturated_into()), + DipRelaychainStateProofVerifierError::ProofComponentTooLarge( + DipProofComponentTooLargeError::DipProofLeafTooLarge as u8 + ) + ); + let proof_without_dip_merkle = proof_without_parachain + .verify_dip_proof::() + .map_err(DipRelaychainStateProofVerifierError::ProofVerification)?; + + // 5. Verify call is signed by one of the DID keys revealed in the proof + let current_block_number = frame_system::Pallet::::block_number(); + let consumer_genesis_hash = + frame_system::Pallet::::block_hash(BlockNumberFor::::zero()); + let signed_extra = SignedExtra::get(); + let encoded_payload = (call, &identity_details, submitter, consumer_genesis_hash, signed_extra).encode(); + let revealed_did_info = proof_without_dip_merkle + .verify_signature_time(¤t_block_number) + .and_then(|p| p.retrieve_signing_leaves_for_payload(&encoded_payload[..])) + .map_err(DipRelaychainStateProofVerifierError::ProofVerification)?; + + // 6. Verify the signing key fulfills the requirements + let signing_keys = revealed_did_info + .get_signing_leaves() + .map_err(DipRelaychainStateProofVerifierError::ProofVerification)?; + DidCallVerifier::check_call_origin_info(call, &signing_keys.cloned().collect::>()) + .map_err(DipRelaychainStateProofVerifierError::DidOriginError)?; + + // 7. Increment the local details + if let Some(details) = identity_details { + details.increment(); + } else { + *identity_details = Some(Default::default()); + }; + + Ok(revealed_did_info) + } +} diff --git a/dip-template/runtimes/dip-consumer/Cargo.toml b/dip-template/runtimes/dip-consumer/Cargo.toml index 5a2986fbd5..cb7a7c4267 100644 --- a/dip-template/runtimes/dip-consumer/Cargo.toml +++ b/dip-template/runtimes/dip-consumer/Cargo.toml @@ -13,6 +13,9 @@ version.workspace = true [build-dependencies] substrate-wasm-builder.workspace = true +[dev-dependencies] +sp-io = {workspace = true, features = ["std"]} + [dependencies] parity-scale-codec = {workspace = true, features = ["derive"]} scale-info = {workspace = true, features = ["derive"]} @@ -66,6 +69,8 @@ rococo-runtime.workspace = true # Benchmarks frame-benchmarking = {workspace = true, optional = true} frame-system-benchmarking = {workspace = true, optional = true} +kilt-support = { workspace = true, optional = true } +hex-literal = { workspace = true, optional = true } [features] default = [ @@ -114,6 +119,7 @@ std = [ "rococo-runtime/std", "frame-benchmarking?/std", "frame-system-benchmarking?/std", + "kilt-support?/std", ] runtime-benchmarks = [ @@ -133,4 +139,6 @@ runtime-benchmarks = [ "rococo-runtime/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks", "frame-system-benchmarking/runtime-benchmarks", + "kilt-support/runtime-benchmarks", + "hex-literal" ] diff --git a/dip-template/runtimes/dip-consumer/src/dip.rs b/dip-template/runtimes/dip-consumer/src/dip.rs index 1e6ec1daee..91a0dbbfab 100644 --- a/dip-template/runtimes/dip-consumer/src/dip.rs +++ b/dip-template/runtimes/dip-consumer/src/dip.rs @@ -17,32 +17,515 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org use did::{DidVerificationKeyRelationship, KeyIdOf}; -use dip_provider_runtime_template::{AccountId as ProviderAccountId, Runtime as ProviderRuntime}; +use dip_provider_runtime_template::{ + AccountId as ProviderAccountId, Runtime as ProviderRuntime, MAX_PUBLIC_KEYS_PER_DID, MAX_REVEALABLE_LINKED_ACCOUNTS, +}; use frame_support::traits::Contains; use frame_system::{pallet_prelude::BlockNumberFor, EnsureSigned}; use kilt_dip_primitives::{ - traits::DipCallOriginFilter, KiltVersionedParachainVerifier, RelayStateRootsViaRelayStorePallet, RevealedDidKey, + parachain::{ + DEFAULT_MAX_DID_MERKLE_PROOF_LEAVE_COUNT, DEFAULT_MAX_DID_MERKLE_PROOF_LEAVE_SIZE, + DEFAULT_MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT, DEFAULT_MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE, + DEFAULT_MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT, DEFAULT_MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE, + }, + traits::DipCallOriginFilter, + KiltVersionedParachainVerifier, RelayStateRootsViaRelayStorePallet, RevealedDidKey, }; use pallet_dip_consumer::traits::IdentityProofVerifier; use rococo_runtime::Runtime as RelaychainRuntime; use sp_core::ConstU32; -use sp_std::marker::PhantomData; +use sp_std::{marker::PhantomData, vec::Vec}; use crate::{weights, AccountId, DidIdentifier, Runtime, RuntimeCall, RuntimeOrigin}; -pub type MerkleProofVerifierOutput = >::VerificationResult; -/// The verifier logic assumes the provider is a sibling KILT parachain, the relaychain is a Rococo relaychain, and -/// that a KILT subject can provide DIP proof that reveal at most 10 DID keys -/// and 10 linked accounts (defaults provided by the -/// `KiltVersionedParachainVerifier` type). Calls that do not pass the -/// [`DipCallFilter`] will be discarded early on in the verification process. -pub type ProofVerifier = KiltVersionedParachainVerifier< +// +1 for the web3name. +const MAX_PROVIDER_REVEALABLE_KEYS_COUNT: u32 = MAX_PUBLIC_KEYS_PER_DID + MAX_REVEALABLE_LINKED_ACCOUNTS + 1; + +/// The verifier logic is tied to the provider template runtime definition. +pub type ProviderTemplateProofVerifier = KiltVersionedParachainVerifier< RelaychainRuntime, RelayStateRootsViaRelayStorePallet, 2_000, ProviderRuntime, DipCallFilter, BlockNumberFor, ProviderAccountId>, + (), + DEFAULT_MAX_PROVIDER_HEAD_PROOF_LEAVE_COUNT, + DEFAULT_MAX_PROVIDER_HEAD_PROOF_LEAVE_SIZE, + DEFAULT_MAX_DIP_COMMITMENT_PROOF_LEAVE_COUNT, + DEFAULT_MAX_DIP_COMMITMENT_PROOF_LEAVE_SIZE, + DEFAULT_MAX_DID_MERKLE_PROOF_LEAVE_COUNT, + DEFAULT_MAX_DID_MERKLE_PROOF_LEAVE_SIZE, + MAX_PROVIDER_REVEALABLE_KEYS_COUNT, >; +pub type MerkleProofVerifierInput = >::Proof; +pub type MerkleProofVerifierOutput = + >::VerificationResult; +// Wrapper around the verifier to implement the `GetWorstCase` trait (required +// due to orphan rule). +pub struct ProviderTemplateProofVerifierWrapper; + +// Delegate verification logic to the specialized version of +// `KiltVersionedParachainVerifier`. +impl IdentityProofVerifier for ProviderTemplateProofVerifierWrapper { + type Error = >::Error; + type Proof = MerkleProofVerifierInput; + type VerificationResult = MerkleProofVerifierOutput; + + fn verify_proof_for_call_against_details( + call: &pallet_dip_consumer::RuntimeCallOf, + subject: &::Identifier, + submitter: &::AccountId, + identity_details: &mut Option<::LocalIdentityInfo>, + proof: Self::Proof, + ) -> Result { + >::verify_proof_for_call_against_details( + call, + subject, + submitter, + identity_details, + proof, + ) + } +} + +// Implement worst-case logic for this specific verifier. +#[cfg(feature = "runtime-benchmarks")] +impl kilt_support::traits::GetWorstCase for ProviderTemplateProofVerifierWrapper { + type Output = pallet_dip_consumer::benchmarking::WorstCaseOf; + + fn worst_case(_context: ()) -> Self::Output { + use did::{ + did_details::{DidEncryptionKey, DidPublicKeyDetails, DidVerificationKey}, + DidSignature, + }; + use frame_support::{pallet_prelude::ValueQuery, storage_alias, Twox64Concat}; + use hex_literal::hex; + use kilt_dip_primitives::{ + DidKeyRelationship, DidMerkleProof, DipCommitmentStateProof, ParachainDipDidProof, ProviderHeadStateProof, + RevealedAccountId, RevealedWeb3Name, TimeBoundDidSignature, + }; + use pallet_dip_consumer::benchmarking::WorstCaseOf; + use pallet_relay_store::RelayParentInfo; + use sp_core::{ed25519, sr25519, H256}; + use sp_runtime::AccountId32; + use sp_std::vec; + + #[storage_alias] + type BlockHash = StorageMap< + System, + Twox64Concat, + BlockNumberFor, + ::Hash, + ValueQuery, + >; + #[storage_alias] + type LatestRelayHeads = StorageMap>; + + const PROOF_RELAY_BLOCK: u32 = 589; + + let provider_head_state_proof = ProviderHeadStateProof::new(PROOF_RELAY_BLOCK, vec![ + hex!("3703f5a4efb16ffa83d007000088e2fdf5c9b8f94277579ae683ead98aae1e06facab1d301144a0271157399ee").to_vec(), + hex!("8004648031b60c9237ed343094831987f2bec10b211621255ad0b440cf161fa820d30db480f6f6801e4b41e2e6d8ec194dba122bfb9eb33feb2545ef5144cea79551f7cc5280bfc5f17f5701ebcd8a25d9e08a90343321779f9c335471d0b22c2686bd57d9c0800ad654b674c2cd45843018e4083f71d892f9463aab9920f166d47499aecc3e1d").to_vec(), + hex!("80ffff8002af9d53d0fe38d916e77086562a2af535ec94a36494384d66273d2339604f2380c5801068e98806370ad5939ba17df962ca6c5e7a7b06b34def0bd9a286f3349780173d3299944e3f85dac5b2c2ecb3f1f13f26df47f38c25d937077d0f344caa0780521bb76b6b176fa67e1f40de0f8cdf439ef8dc3e6ca5e483055eeba380bc7b7a809a6d265f539abd682eb0f593cd3c0006367a48bc4a4bd7eb6b755bc48b187c9f8039c39d126632e3b9af053befe643119111fb627077145ebf8ab8277f4e791f6a80646452cc2e74ebf3311ffcdfcaa4bdbd0b31c19d6fc777be9f7a4f5808d96cc2805b682132c52908705526057f73ab7fccab4af6d72a9805634dd8d3cc53f130d180c2d44d371e5fc1f50227d7491ad65ad049630361cefb4ab1844831237609f08380fe93a2a86fd60f3c6b30051cec7e5e72d331b6f4b6fc142834eac567e6a3aca1808b058239988689d9aa6cb8721760371dce42f384ec0f95771555e195320d3e18800ff113df26dc01f6916caf9728ad8ddf2d278362ab63312ec47e40e63ac7ac9880603666dff2710c1262571a56902a9bae2066026f57a9499c98ef56a700abf94c808c1356abbbc74f6009a7b95604976482b2b4573ba58a14072283b5ca5b37bafa806bfdbbf0e0bedcb993b65c9cea1e929a56d78a3b7bc53d1b7ca6fc488e2295ee801f25bd16505bdd55875b871aa63dc73faff8929e8010ca2b535868849af770ed").to_vec(), + hex!("810338345b941f7b5396da7c8a8ee4a561ea107105bc488887d68344fa716bc271a691030290b49b480f77d69ceb87a8b854012d2f704508d735c7a5e03df7a098869e2b6391949b5d132b311d09614c6bcf46b282c8dee37128faef7f10353ec1f310c40c066175726120f01e7e0800000000045250535288db2cb81d66fb9974bab34f6375abcb3531524bec8f7f20c09cea921e9eae84092d09056175726101017a967f2b9621cdaaf8860e8887f82f950580bd90f366382092452b019a71217039afdf809b48809cfe00b68a958ff74bb38b070216f90d10ffad2d1aa665bc82").to_vec(), + hex!("9e710b30bd2eab0352ddcc26417aa1945f43803b3441f15daa8a53147d69c48eac75356fab581febbb8030520b248c5942a148804dbeecdd4792782a820b4f713c58dece06bd69e8fcb9b506fe052a24eb7eb0eb802e2e0716043a02f2f29fdd52922704af194b98545ce5ca832255e8ec41bcdb6480a0718fee6fd849f63aebd00a6e9d09e984d70549c0b5475b16c244090876e628505f0e7b9012096b41c4eb3aaf947f6ea4290800004c5f03c716fb8fff3de61a883bb76adb34a20400808aefdc67024312a782a33b24ee2d1bfa728e3842db64274191fa9a4f0f7a56744c5f0f4993f016e2d2f8e5f43be7bb259486040080949e352413ff8a43f35e73a6077d7a87a2de45fb6ce9bc40ad3717bdbf7a5708").to_vec(), + hex!("9f0b3c252fcb29d88eff4f3de5de4476c3500080d94a128016b9dd6dce1aca270b09fa36eeb8227e134f24b89ca7bde76723c44c808f4595bab11d5f07ca595107dbae994cd82279c9b5e437230d387e2be69c49bd").to_vec(), + ]); + let dip_commitment_proof = DipCommitmentStateProof::new(vec![ + hex!("7f340f4ac20413f4e00f0a9eaae0343e8e56e68a94309d0adee950b6a63a0a141a3166c15e8ef25c301531f75e25086fe05a01a12dfa1fa4ab9a000080dc96078e1aa097ade1ee470e32ebdd2e6f5808cbfa62f1a625cc21b88677c272").to_vec(), + hex!("800c8080da28793d083b197f8d92fc3e77f5064436f1d8eea0fbea56ddb936aba654450080a1588f087b233f3494cdc5cdf5147d6dbfa9651bd2974f5e82b3b00dcbfdc0f18081acf868c884e3bbeb26e53acb3a2e4eca7bd36b22d30e0cffb36ed0c48305bb").to_vec(), + hex!("80ffff80353e4d164b13c87910044f1b4e76277e404a0ab46a7cd6c33a65aaadc2375ba88007b1390da34b4dce1328430fd924a6e193517a8148dd70a912c0dc2f7f8d2d4c80f0a8aefb3eb62ec86937a4e49657b03d7de0588ad6bb795a4ebb0b5654d9e63880fef940f449f15a0e6fa92eeac30b55e5a69d939d85dfc9e6b75545ea5fb5b6f680048e707e1b93570f4506833da06205a54a4e7ff36092237904359e7461fd44d38020f7b28bc23361dcfa7b988a30f92202a9fd05d783f27b89f304c41eeca500958014e3e0704c9a07636322335a3c663ec9fd9df8b7bf71d6e8183fefecfbfe0e5080259cb4cd05acf09d7a9d7e9935585ff95670dc498cfc365c25b217d66b985f28802943cf2440d02afc0e2a7fb7567af93eba7d83bba93e8e8d7dc4abe20544cb53802b320a7ee167d52bd32ffcd3d94312f8d17c0eadeb2840c0b448de09ac54e3e7803ee8e59b8b261a960b3d00106c36cecfa42b5f72dac70d37530f1958a080da8080b6d9076aa2cd7e8700fb5a5b3ef182975c6515077195c911748da5d21434220e800eaec11c028f926112839db018f6de72c505168b910bbe589d8c83ebdc4fca8d80f395b7003a2eb1e39c624b9a707a6cb58c3cb6997932fc80662ae19c785a91f580b5e5172489541dfc581e116554b63de15fddf38ffed2b109394749c20b8f6ce380949007eae7367a82ee80988be32c8f8f6d936593122a6576d186ba6be490b5bb").to_vec(), + hex!("9e75edf06348b4330d1e88564111cb3d3000505f0e7b9012096b41c4eb3aaf947f6ea42908000080f20e8f088dd913ee6a53e72e9de980ad8256cb48c3718f8080c0efeeb43e64cd").to_vec(), + hex!("9f0bf19e4ed2927982e234d989e812f3f348408028e4e828a83fd632d6d17fa940bb289ef8d04c1c154ecbf583d677460bef22128048f06290dfec2596fa70eaca62ea496d3dc0cd2f51fd40c61b58d7e5b476eebd80c898c636c42ebafd67de87f4ebe2e79a6de88441d420e423dba761169752355b").to_vec(), + ]); + let dip_proof = DidMerkleProof::new( + vec![ + hex!("80bf2f000000000000000000000000").to_vec(), + hex!("800281000000").to_vec(), + hex!("809697000000000000000000").to_vec(), + hex!("7f000207da77a11b67f17653408a8d6cf85d10b3f366c7e7be82f3b30a8eb935c66c00").to_vec(), + hex!("7f0000acfd57871165f2330ca49a4ddafabc52698bc894c899d6368107056ee90c2200").to_vec(), + hex!("7f000afc8c7501fd42bd62db9953e4c54bdf154ff9f5255ebd362b2b795a271b3a7b00").to_vec(), + hex!("8002020000").to_vec(), + hex!("7edb492c2503f35d8b783e6d077875aedf473c502c3f641c5c87dad957e3f98b00").to_vec(), + hex!("7e1dfe90617727b1c2c2d4a570b6e7d042b228c62eba1aeb0f1d43a99d2ee88300").to_vec(), + hex!("7f0006ad76d64191ec2a4bfee79fadbb7085fa8ccfb7a590cb91b0f3ebd7ec943df900").to_vec(), + hex!("7f0008fd1a85f17803a48501005e8fc59bb69ede7407062f83f1a950b917951f9bba00").to_vec(), + hex!("7f0007beb4c2f6b8b2143dcec8771011006b6380ab3a65530ebc849a6a518e4f586000").to_vec(), + hex!("7f000ceb4ca89584fa1bbb95318204596d8f883101dfeb6b8ebfa61f3a2d081789fe00").to_vec(), + hex!("7f0007a3e3a7ffb4e10170a73b41039e7298b67ae2fe1d7b8cbfbcb9a19122c51e4b00").to_vec(), + hex!("7f01c204f1ff9fb3da19442271d014cf3fafa761d4f624d718e729efba11065e300000").to_vec(), + hex!("7f014c3e671d00ed67683177268a1aae0f7faf290e4754730bb8b0fff18243cc600000").to_vec(), + hex!("802120000000").to_vec(), + hex!("7f036e6b1fa2d0ac6b81387fce6eb985b760f70a43a6d8e0c3f9e78c8a9d9e548e010100").to_vec(), + hex!("7f01ffb682b21cd48217b4010102721378f80e0463cbfbd5a39b0f08b4801d57520000").to_vec(), + hex!("7f01bf0e5f1c3a6536b9b6c7cd2da10e0dfaba631f50ed16a115b6dc53ba1ff2060000").to_vec(), + hex!("7f020af4abba9639e828f74df06a5729504ac2ab50e417065f717ed66ee85d1ff88f0000").to_vec(), + hex!("8000110000").to_vec(), + hex!("7f01ca9fef5649916accf658e00f703dc2d66bca2fe39b3daa24bbbb096a18bdbf0000").to_vec(), + hex!("7f017939667bd5080dd837a5187381b02e5944960f73a364fc3499d39ed10ba47d0000").to_vec(), + hex!("7f020f5333e95049a79201ad8be14bc94440590a41402384ee141b4f17be5b94e57f0000").to_vec(), + hex!("800250000000").to_vec(), + hex!("7f010abbb2522022332bec89495323df12567b5abe2f8fcc2e3da40756bfbb7b5e0000").to_vec(), + hex!("6e353639396133633537343834316234653335336433373700").to_vec(), + hex!("7f0314c0826d524d79a17cb5bc5fd61f9b2d364c9af73a5db87408f389e83afcdf010300").to_vec(), + hex!("8000030000").to_vec(), + hex!("7f03e54fc7807f8c1cbd6e3dac9f3291096e7a2d8ab879934edb402f320a3d46a0010000").to_vec(), + hex!("7f0194423645f905c2ecc8d07b89babe374ebf761c2b4676c95a749ae7f3f840720000").to_vec(), + hex!("7f02058867de4a252085d0a8a1078b6d72b8adf1912565bac7733be05b4bf3cbb4ae0000").to_vec(), + hex!("805800000000").to_vec(), + hex!("7f0131acdacf05ed4d81448e501ff82a979ddfe90342b62c2d439df8b2bdf6f56a0000").to_vec(), + hex!("7f0198d9c99157dc9b19fbe30fa8057f0337cfa0b2e23181081b20137f0a2bba5d0000").to_vec(), + hex!("7f01091a1d3dbf3b1f12c41cf1b4e9c7cf59039aa6407e3010f32d5079ceba07a30000").to_vec(), + hex!("7f020e59e300e930fa773ef7b8ed42a07c77c2913650a8323caa2fd143ecbb75bfac0000").to_vec(), + hex!("7f020a4664e1571d22e18e0d45969da0479cbfb8b2bc5fb37850719f7f7fa506267d0000").to_vec(), + hex!("7f020522e1a7e2ae92f98383e9ff7eb0fbb5baaf09999db7c4161652d3e233af0d140000").to_vec(), + ], + vec![ + RevealedDidKey { + id: hex!("78e54fc7807f8c1cbd6e3dac9f3291096e7a2d8ab879934edb402f320a3d46a0").into(), + relationship: DidVerificationKeyRelationship::Authentication.into(), + details: DidPublicKeyDetails { + key: DidVerificationKey::Sr25519(sr25519::Public(hex!( + "e68a94309d0adee950b6a63a0a141a3166c15e8ef25c301531f75e25086fe05a" + ))) + .into(), + block_number: 227u64, + }, + } + .into(), + RevealedDidKey { + id: hex!("08c204f1ff9fb3da19442271d014cf3fafa761d4f624d718e729efba11065e30").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "bd09a314a5f66ad2c56639140862bfaad56071044c78e41ba4756ab21147b824" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("0f4c3e671d00ed67683177268a1aae0f7faf290e4754730bb8b0fff18243cc60").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "e503588f6016e08c7ff79c7e74817ecd264b2a97707998748527ef7766819e27" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("15ffb682b21cd48217b4010102721378f80e0463cbfbd5a39b0f08b4801d5752").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "ac95eb8c17f951bb9ae41d19fa9dac75342c6b9b901be6da6a5f42265b491635" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("1dbf0e5f1c3a6536b9b6c7cd2da10e0dfaba631f50ed16a115b6dc53ba1ff206").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "c102dfa2aa8b9ed85e5a67c0612bcf6a3b702ad10fab937881bf57e8a344eb5c" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("2af4abba9639e828f74df06a5729504ac2ab50e417065f717ed66ee85d1ff88f").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "89d45b096b0cd8163dc18bd0bf74399c933116f9de79bda845f00d27b3f2c657" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("38ca9fef5649916accf658e00f703dc2d66bca2fe39b3daa24bbbb096a18bdbf").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "5ce39370f803bea2f945a82e97a06bdea1d340a210dba62f865077018c45cb16" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("3c7939667bd5080dd837a5187381b02e5944960f73a364fc3499d39ed10ba47d").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "f675a09e224219c63b3e33b067ff0b2dc1584c504f1908ed2518d5cceae20347" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("4f5333e95049a79201ad8be14bc94440590a41402384ee141b4f17be5b94e57f").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "7849c1371c98d2bc940df61b22b5094124eaa82151c85068b97f2a37e5abd713" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("510abbb2522022332bec89495323df12567b5abe2f8fcc2e3da40756bfbb7b5e").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "581f2d1e3988a7cf7a695bf77485aa06473b9a67b077df3171dcc15e4d88f521" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("7994423645f905c2ecc8d07b89babe374ebf761c2b4676c95a749ae7f3f84072").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "82545402deeffdc4a6c8d53f8b2442f54e4ec5ed0c26f59d8089300699b7a40a" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("858867de4a252085d0a8a1078b6d72b8adf1912565bac7733be05b4bf3cbb4ae").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "f201c6ca5bb324698e6e1fcebeef42f34f66fd62cd183df1149892a7dcf7cb48" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("9331acdacf05ed4d81448e501ff82a979ddfe90342b62c2d439df8b2bdf6f56a").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "baa2ab5a4663e440b13417864b043ce18af6990f5d1563afdb0e2fec040aed3f" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("9498d9c99157dc9b19fbe30fa8057f0337cfa0b2e23181081b20137f0a2bba5d").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "3f6fb782a6809668998634e264abe3ecc97f15b8f726b86c6a5024fec1d39e53" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("96091a1d3dbf3b1f12c41cf1b4e9c7cf59039aa6407e3010f32d5079ceba07a3").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "de2f7b17ca8a01055027dc2d424ef9b01c0df98ae42fea41a462f84e447ca230" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("ae59e300e930fa773ef7b8ed42a07c77c2913650a8323caa2fd143ecbb75bfac").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "755bdbb3dc4f43d8b3a8c8b19f07bc362ab9015fe3276b3f64439f9ec67e8b0f" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("ba4664e1571d22e18e0d45969da0479cbfb8b2bc5fb37850719f7f7fa506267d").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "bdeaf31cab91d67cea1c6b3f64fa9e9e66826e271e01690f181851d8831e4317" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("d522e1a7e2ae92f98383e9ff7eb0fbb5baaf09999db7c4161652d3e233af0d14").into(), + relationship: DidKeyRelationship::Encryption, + details: DidPublicKeyDetails { + key: DidEncryptionKey::X25519(hex!( + "cb7cb8c59b2784b87d2270ab4e41f661b4626590954dbd3400208eb1f958f77e" + )) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("106e6b1fa2d0ac6b81387fce6eb985b760f70a43a6d8e0c3f9e78c8a9d9e548e").into(), + relationship: DidVerificationKeyRelationship::CapabilityDelegation.into(), + details: DidPublicKeyDetails { + key: DidVerificationKey::Ed25519(ed25519::Public(hex!( + "39985b639d8d21629190f2a310b0e2b935894a6261e45ba58f0fbf2bd6c0c832" + ))) + .into(), + block_number: 227, + }, + } + .into(), + RevealedDidKey { + id: hex!("5e14c0826d524d79a17cb5bc5fd61f9b2d364c9af73a5db87408f389e83afcdf").into(), + relationship: DidVerificationKeyRelationship::AssertionMethod.into(), + details: DidPublicKeyDetails { + key: DidVerificationKey::Ed25519(ed25519::Public(hex!( + "6c89991144954da6d916f88e59ce0c52bc2dcea2e7edd065e750234ebbb8d8eb" + ))) + .into(), + block_number: 227, + }, + } + .into(), + RevealedAccountId( + AccountId32::new(hex!("a7beb4c2f6b8b2143dcec8771011006b6380ab3a65530ebc849a6a518e4f5860")).into(), + ) + .into(), + RevealedAccountId( + AccountId32::new(hex!("86ad76d64191ec2a4bfee79fadbb7085fa8ccfb7a590cb91b0f3ebd7ec943df9")).into(), + ) + .into(), + RevealedAccountId( + AccountId32::new(hex!("791dfe90617727b1c2c2d4a570b6e7d042b228c62eba1aeb0f1d43a99d2ee883")).into(), + ) + .into(), + RevealedAccountId( + AccountId32::new(hex!("71db492c2503f35d8b783e6d077875aedf473c502c3f641c5c87dad957e3f98b")).into(), + ) + .into(), + RevealedAccountId( + AccountId32::new(hex!("f7a3e3a7ffb4e10170a73b41039e7298b67ae2fe1d7b8cbfbcb9a19122c51e4b")).into(), + ) + .into(), + RevealedAccountId( + AccountId32::new(hex!("20acfd57871165f2330ca49a4ddafabc52698bc894c899d6368107056ee90c22")).into(), + ) + .into(), + RevealedAccountId( + AccountId32::new(hex!("1207da77a11b67f17653408a8d6cf85d10b3f366c7e7be82f3b30a8eb935c66c")).into(), + ) + .into(), + RevealedAccountId( + AccountId32::new(hex!("98fd1a85f17803a48501005e8fc59bb69ede7407062f83f1a950b917951f9bba")).into(), + ) + .into(), + RevealedAccountId( + AccountId32::new(hex!("cceb4ca89584fa1bbb95318204596d8f883101dfeb6b8ebfa61f3a2d081789fe")).into(), + ) + .into(), + RevealedAccountId( + AccountId32::new(hex!("4afc8c7501fd42bd62db9953e4c54bdf154ff9f5255ebd362b2b795a271b3a7b")).into(), + ) + .into(), + RevealedWeb3Name { + web3_name: b"5699a3c574841b4e353d377".to_vec().try_into().unwrap(), + claimed_at: 227, + } + .into(), + ], + ); + let signature = TimeBoundDidSignature::new(DidSignature::Sr25519(sr25519::Signature(hex!("1ca20d39357dba602862e6b6371887c6b1ec46c86ead3c92178cca814e3ff45f7fd6a58395d422b53b6e1d1ab7be5944dbc2c6e640ecfac67c02a218607cc881"))), 282 as BlockNumberFor); + let proof = ParachainDipDidProof::new(provider_head_state_proof, dip_commitment_proof, dip_proof, signature); + + BlockHash::insert( + 0, + H256(hex!("74f8cd2f3764f676a5e67c45a641ce1025548c6cddcf524a663a9c0aaf7fbee2")), + ); + LatestRelayHeads::insert( + PROOF_RELAY_BLOCK, + RelayParentInfo { + relay_parent_storage_root: H256(hex!( + "29575e65f298648588bc53a45346098e89a99c7330f53d93a899efbb24ddfb69" + )), + }, + ); + + WorstCaseOf { + proof: proof.into(), + call: pallet_postit::Call::post { + text: b"Hello, world!".to_vec().try_into().unwrap(), + } + .into(), + // 4t8M197K3r1xygdVNoRLRBCpWf6G58VcWTKQUiv5kbJiQhvs + subject: DidIdentifier::new(hex!("e68a94309d0adee950b6a63a0a141a3166c15e8ef25c301531f75e25086fe05a")), + // 4rBcMBgT7HzH9NaTpgcBT8AfDUmJjRWiiYGpsqa19CJTSHL3 + submitter: AccountId::new(hex!("908f818bebf2db6d64d86cce811d2133e2d9c9ac447c6c5cc61b23ab04e1fc30")), + } + } +} + +#[cfg(all(test, feature = "runtime-benchmarks"))] +mod worst_case_tests { + use kilt_dip_primitives::VersionedDipParachainStateProof; + use kilt_support::traits::GetWorstCase; + use pallet_dip_consumer::benchmarking::WorstCaseOf; + + use crate::{dip::MAX_PROVIDER_REVEALABLE_KEYS_COUNT, ProviderTemplateProofVerifierWrapper}; + + // Test that the worst case actually refers to the worst case that the provider + // can generate. + #[test] + fn worst_case_max_limits() { + sp_io::TestExternalities::default().execute_with(|| { + let WorstCaseOf { proof, .. } = ::worst_case(()); + let VersionedDipParachainStateProof::V0(proof) = proof; + // We test that the worst case reveals the maximum number of leaves revealable. + // This is required since the worst case is generated elsewhere and used here as + // a fixture. + sp_io::TestExternalities::default().execute_with(|| { + assert_eq!( + proof.dip_proof().revealed().len(), + MAX_PROVIDER_REVEALABLE_KEYS_COUNT as usize + ); + }); + }); + } +} impl pallet_dip_consumer::Config for Runtime { type DipCallOriginFilter = PreliminaryDipOriginFilter; @@ -55,7 +538,7 @@ impl pallet_dip_consumer::Config for Runtime { // that two cross-chain operations targeting the same chain and with the same // nonce cannot be both successfully evaluated. type LocalIdentityInfo = u128; - type ProofVerifier = ProofVerifier; + type ProofVerifier = ProviderTemplateProofVerifierWrapper; type RuntimeCall = RuntimeCall; type RuntimeOrigin = RuntimeOrigin; type WeightInfo = weights::pallet_dip_consumer::WeightInfo; @@ -153,18 +636,18 @@ impl DipCallOriginFilt for DipCallFilter { type Error = DipCallFilterError; - type OriginInfo = RevealedDidKey; + type OriginInfo = Vec>; type Success = (); // Accepts only a DipOrigin for the DidLookup pallet calls. fn check_call_origin_info(call: &RuntimeCall, info: &Self::OriginInfo) -> Result { - let revealed_key_relationship: DidVerificationKeyRelationship = info - .relationship - .try_into() - .map_err(|_| DipCallFilterError::WrongVerificationRelationship)?; let expected_key_relationship = single_key_relationship([call].into_iter()).map_err(|_| DipCallFilterError::BadOrigin)?; - if revealed_key_relationship == expected_key_relationship { + // If any of the keys revealed is of the right relationship, it's ok. + if info + .iter() + .any(|did_key| did_key.relationship == expected_key_relationship.into()) + { Ok(()) } else { Err(DipCallFilterError::WrongVerificationRelationship) diff --git a/dip-template/runtimes/dip-provider/src/dip.rs b/dip-template/runtimes/dip-provider/src/dip.rs index 4480940671..72f8998de3 100644 --- a/dip-template/runtimes/dip-provider/src/dip.rs +++ b/dip-template/runtimes/dip-provider/src/dip.rs @@ -34,7 +34,7 @@ use crate::{ weights, AccountId, Balances, DidIdentifier, Runtime, RuntimeEvent, RuntimeHoldReason, }; -const MAX_LINKED_ACCOUNTS: u32 = 20; +pub const MAX_REVEALABLE_LINKED_ACCOUNTS: u32 = 10; pub mod runtime_api { use super::*; @@ -243,7 +243,7 @@ impl pallet_dip_provider::Config for Runtime { type IdentityCommitmentGenerator = DidMerkleRootGenerator; // Identity info is defined as the collection of DID keys, linked accounts, and // the optional web3name of a given DID subject. - type IdentityProvider = LinkedDidInfoProvider; + type IdentityProvider = LinkedDidInfoProvider; type ProviderHooks = deposit::DepositCollectorHooks; type RuntimeEvent = RuntimeEvent; type WeightInfo = weights::pallet_dip_provider::WeightInfo; diff --git a/dip-template/runtimes/dip-provider/src/lib.rs b/dip-template/runtimes/dip-provider/src/lib.rs index 5ebb7ce1e0..3826a49ebf 100644 --- a/dip-template/runtimes/dip-provider/src/lib.rs +++ b/dip-template/runtimes/dip-provider/src/lib.rs @@ -376,11 +376,13 @@ impl did::DeriveDidCallAuthorizationVerificationKeyRelationship for RuntimeCall } } +pub const MAX_PUBLIC_KEYS_PER_DID: u32 = 20; +const MAX_TOTAL_KEY_AGREEMENT_KEYS: u32 = MAX_PUBLIC_KEYS_PER_DID - 1; parameter_types! { #[derive(Debug, Clone, Eq, PartialEq)] - pub const MaxTotalKeyAgreementKeys: u32 = 50; + pub const MaxTotalKeyAgreementKeys: u32 = MAX_TOTAL_KEY_AGREEMENT_KEYS; #[derive(Debug, Clone, Eq, PartialEq, TypeInfo, Encode, Decode)] - pub const MaxNewKeyAgreementKeys: u32 = 50; + pub const MaxNewKeyAgreementKeys: u32 = MAX_TOTAL_KEY_AGREEMENT_KEYS; } impl did::Config for Runtime { @@ -397,7 +399,7 @@ impl did::Config for Runtime { type MaxNumberOfServicesPerDid = ConstU32<1>; type MaxNumberOfTypesPerService = ConstU32<1>; type MaxNumberOfUrlsPerService = ConstU32<1>; - type MaxPublicKeysPerDid = ConstU32<53>; + type MaxPublicKeysPerDid = ConstU32; type MaxServiceIdLength = ConstU32<100>; type MaxServiceTypeLength = ConstU32<100>; type MaxServiceUrlLength = ConstU32<100>; diff --git a/pallets/did/src/did_details.rs b/pallets/did/src/did_details.rs index d28291f96d..29a2a24bdc 100644 --- a/pallets/did/src/did_details.rs +++ b/pallets/did/src/did_details.rs @@ -191,13 +191,6 @@ impl From for DidSignature { } } -#[cfg(feature = "runtime-benchmarks")] -impl kilt_support::traits::GetWorstCase for DidSignature { - fn worst_case(_context: Context) -> Self { - Self::Sr25519(sp_core::sr25519::Signature::from_raw([0u8; 64])) - } -} - pub trait DidVerifiableIdentifier { /// Allows a verifiable identifier to verify a signature it produces and /// return the public key diff --git a/pallets/pallet-deposit-storage/src/lib.rs b/pallets/pallet-deposit-storage/src/lib.rs index 0e05c2bcbf..4953223a38 100644 --- a/pallets/pallet-deposit-storage/src/lib.rs +++ b/pallets/pallet-deposit-storage/src/lib.rs @@ -149,8 +149,14 @@ pub mod pallet { /// deposit instance. #[pallet::storage] #[pallet::getter(fn deposits)] - pub(crate) type Deposits = - StorageDoubleMap<_, Twox64Concat, ::Namespace, Twox64Concat, DepositKeyOf, DepositEntryOf>; + pub(crate) type Deposits = StorageDoubleMap< + _, + Blake2_128Concat, + ::Namespace, + Blake2_128Concat, + DepositKeyOf, + DepositEntryOf, + >; #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] diff --git a/pallets/pallet-dip-consumer/Cargo.toml b/pallets/pallet-dip-consumer/Cargo.toml index edfd082d6f..752bb04067 100644 --- a/pallets/pallet-dip-consumer/Cargo.toml +++ b/pallets/pallet-dip-consumer/Cargo.toml @@ -14,6 +14,8 @@ version.workspace = true targets = ["x86_64-unknown-linux-gnu"] [dev-dependencies] +pallet-balances = {workspace = true, features = ["std"]} +pallet-did-lookup = {workspace = true, features = ["std"]} sp-io = {workspace = true, features = ["std"]} sp-keystore = {workspace = true, features = ["std"]} sp-runtime = {workspace = true, features = ["std"]} diff --git a/pallets/pallet-dip-consumer/src/benchmarking.rs b/pallets/pallet-dip-consumer/src/benchmarking.rs index 0052ebbb0d..3a998bf9ca 100644 --- a/pallets/pallet-dip-consumer/src/benchmarking.rs +++ b/pallets/pallet-dip-consumer/src/benchmarking.rs @@ -16,50 +16,42 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -use crate::{traits::IdentityProofVerifier, Call, Config, IdentityEntries, Pallet}; +use crate::{Config, IdentityProofOf, RuntimeCallOf}; use frame_benchmarking::v2::*; -use frame_system::RawOrigin; -use kilt_support::{ - benchmark::IdentityContext, - traits::{GetWorstCase, Instanciate}, -}; + +pub struct WorstCaseOf { + pub submitter: T::AccountId, + pub subject: T::Identifier, + pub proof: IdentityProofOf, + pub call: RuntimeCallOf, +} #[benchmarks( where - T::AccountId: Instanciate, - T::Identifier: Instanciate, - <::ProofVerifier as IdentityProofVerifier>::Proof: GetWorstCase>, - ::RuntimeCall: From>, + T::ProofVerifier: GetWorstCase>, + ::RuntimeCall: From>, )] mod benchmarks { + use frame_system::RawOrigin; + use kilt_support::traits::GetWorstCase; + use sp_std::{boxed::Box, vec}; - use super::*; - - type IdentityContextOf = - IdentityContext<::Identifier, ::AccountId>; + use crate::{benchmarking::WorstCaseOf, Call, Config, IdentityEntries, Pallet}; #[benchmark] fn dispatch_as() { - let submitter = T::AccountId::new(1); - let subject = T::Identifier::new(1); - - let context = IdentityContext:: { - did: subject.clone(), - submitter: submitter.clone(), - }; + let WorstCaseOf { + submitter, + subject, + proof, + call, + } = ::worst_case(()); assert!(IdentityEntries::::get(&subject).is_none()); let origin = RawOrigin::Signed(submitter); - - let call: ::RuntimeCall = frame_system::Call::::remark { remark: vec![] }.into(); - let boxed_call = Box::from(call); - let proof = <<::ProofVerifier as IdentityProofVerifier>::Proof as GetWorstCase< - IdentityContextOf, - >>::worst_case(context); - let origin = ::RuntimeOrigin::from(origin); #[extrinsic_call] diff --git a/pallets/pallet-dip-consumer/src/lib.rs b/pallets/pallet-dip-consumer/src/lib.rs index bc633025bb..ab72c18e7b 100644 --- a/pallets/pallet-dip-consumer/src/lib.rs +++ b/pallets/pallet-dip-consumer/src/lib.rs @@ -24,7 +24,10 @@ pub mod traits; mod default_weights; #[cfg(test)] -pub mod mock; +mod mock; + +#[cfg(test)] +mod tests; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; @@ -41,7 +44,6 @@ pub mod pallet { dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, pallet_prelude::*, traits::{Contains, EnsureOriginWithArg}, - Twox64Concat, }; use frame_system::pallet_prelude::*; use parity_scale_codec::{FullCodec, MaxEncodedLen}; @@ -108,7 +110,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn identity_proofs)] pub(crate) type IdentityEntries = - StorageMap<_, Twox64Concat, ::Identifier, ::LocalIdentityInfo>; + StorageMap<_, Blake2_128Concat, ::Identifier, ::LocalIdentityInfo>; #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] diff --git a/pallets/pallet-dip-consumer/src/mock.rs b/pallets/pallet-dip-consumer/src/mock.rs index 401dc2f239..c83ebb897d 100644 --- a/pallets/pallet-dip-consumer/src/mock.rs +++ b/pallets/pallet-dip-consumer/src/mock.rs @@ -23,21 +23,27 @@ use frame_support::{ traits::{BlakeTwo256, IdentityLookup}, AccountId32, }, - traits::{ConstU16, ConstU32, ConstU64, Contains, Everything}, + traits::{ConstU16, ConstU32, ConstU64, Contains, Currency, Everything}, }; use frame_system::{mocking::MockBlock, EnsureSigned}; -use crate::traits::SuccessfulProofVerifier; +use crate::{traits::IdentityProofVerifier, Config, DipOrigin, EnsureDipOrigin, IdentityEntries, RuntimeCallOf}; +// This mock is used both for benchmarks and unit tests. +// For benchmarks, the `system::remark` call must be allowed to be dispatched, +// while for the unit tests we use the `pallet_did_lookup` as an example pallet +// consuming the generated DIP origin. construct_runtime!( pub struct TestRuntime { System: frame_system, + Balances: pallet_balances, + DidLookup: pallet_did_lookup, DipConsumer: crate, } ); impl frame_system::Config for TestRuntime { - type AccountData = (); + type AccountData = pallet_balances::AccountData; type AccountId = AccountId32; type BaseCallFilter = Everything; type Block = MockBlock; @@ -62,36 +68,134 @@ impl frame_system::Config for TestRuntime { type Version = (); } -pub struct CallFilter; +impl pallet_balances::Config for TestRuntime { + type AccountStore = System; + type Balance = u64; + type DustRemoval = (); + type ExistentialDeposit = ConstU64<1>; + type FreezeIdentifier = [u8; 8]; + type MaxFreezes = ConstU32<10>; + type MaxHolds = ConstU32<10>; + type MaxLocks = ConstU32<10>; + type MaxReserves = ConstU32<10>; + type ReserveIdentifier = [u8; 8]; + type RuntimeEvent = RuntimeEvent; + type RuntimeHoldReason = RuntimeHoldReason; + type WeightInfo = (); +} + +impl pallet_did_lookup::Config for TestRuntime { + type BalanceMigrationManager = (); + type Currency = Balances; + type Deposit = ConstU64<1>; + type DidIdentifier = AccountId32; + type EnsureOrigin = EnsureDipOrigin; + type OriginSuccess = DipOrigin; + type RuntimeEvent = RuntimeEvent; + type RuntimeHoldReason = RuntimeHoldReason; + type WeightInfo = (); +} + +pub struct OnlySystemRemarksWithoutEventsAndDidLookupCalls; -impl Contains for CallFilter { +impl Contains for OnlySystemRemarksWithoutEventsAndDidLookupCalls { fn contains(t: &RuntimeCall) -> bool { - matches!(t, RuntimeCall::System { .. }) + matches!( + t, + // Required by the benchmarking logic + RuntimeCall::System(frame_system::Call::remark { .. }) | + // Used in these tests + RuntimeCall::DidLookup { .. } + ) + } +} + +// Returns success if `Proof` is `true`, and bumps the identity details by one, +// or instantiates them to `Default` if they're `None`. +pub struct BooleanProofVerifier; +impl IdentityProofVerifier for BooleanProofVerifier { + type Error = u16; + type Proof = bool; + type VerificationResult = (); + + fn verify_proof_for_call_against_details( + _call: &RuntimeCallOf, + _subject: &::Identifier, + _submitter: &::AccountId, + identity_details: &mut Option<::LocalIdentityInfo>, + proof: Self::Proof, + ) -> Result { + if proof { + *identity_details = identity_details.map(|d| Some(d + 1)).unwrap_or(Some(u128::default())); + Ok(()) + } else { + Err(1) + } + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl kilt_support::traits::GetWorstCase for BooleanProofVerifier { + type Output = crate::benchmarking::WorstCaseOf; + + fn worst_case(_context: ()) -> Self::Output { + crate::benchmarking::WorstCaseOf { + call: frame_system::Call::remark { + remark: b"Hello!".to_vec(), + } + .into(), + proof: true, + subject: AccountId32::new([100; 32]), + submitter: AccountId32::new([200; 32]), + } } } impl crate::Config for TestRuntime { type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type ProofVerifier = SuccessfulProofVerifier; + type ProofVerifier = BooleanProofVerifier; type LocalIdentityInfo = u128; type Identifier = AccountId32; type DispatchOriginCheck = EnsureSigned; - type DipCallOriginFilter = CallFilter; + type DipCallOriginFilter = OnlySystemRemarksWithoutEventsAndDidLookupCalls; type WeightInfo = (); } +pub(crate) const SUBMITTER: AccountId32 = AccountId32::new([100u8; 32]); +pub(crate) const SUBJECT: AccountId32 = AccountId32::new([200u8; 32]); + #[derive(Default)] -pub(crate) struct ExtBuilder; +pub(crate) struct ExtBuilder(Vec<(AccountId32, u64)>, Vec<(AccountId32, u128)>); impl ExtBuilder { - pub fn _build(self) -> sp_io::TestExternalities { - sp_io::TestExternalities::default() + pub(crate) fn with_balances(mut self, balances: Vec<(AccountId32, u64)>) -> Self { + self.0 = balances; + self + } + pub(crate) fn with_identity_details(mut self, details: Vec<(AccountId32, u128)>) -> Self { + self.1 = details; + self + } + + pub(crate) fn build(self) -> sp_io::TestExternalities { + let mut ext = sp_io::TestExternalities::default(); + + ext.execute_with(|| { + for (account_id, balance) in self.0 { + Balances::make_free_balance_be(&account_id, balance); + } + for (subject, details) in self.1 { + IdentityEntries::::insert(subject, details) + } + }); + + ext } #[cfg(feature = "runtime-benchmarks")] - pub fn build_with_keystore(self) -> sp_io::TestExternalities { - let mut ext = self._build(); + pub(crate) fn build_with_keystore(self) -> sp_io::TestExternalities { + let mut ext = self.build(); let keystore = sp_keystore::testing::MemoryKeystore::new(); ext.register_extension(sp_keystore::KeystoreExt(sp_std::sync::Arc::new(keystore))); ext diff --git a/pallets/pallet-dip-consumer/src/tests/dispatch_as.rs b/pallets/pallet-dip-consumer/src/tests/dispatch_as.rs new file mode 100644 index 0000000000..80387616cf --- /dev/null +++ b/pallets/pallet-dip-consumer/src/tests/dispatch_as.rs @@ -0,0 +1,110 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2024 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use frame_support::{assert_noop, assert_ok}; +use frame_system::RawOrigin; + +use crate::{ + mock::{ExtBuilder, System, TestRuntime, SUBJECT, SUBMITTER}, + Error, IdentityEntries, Pallet, +}; + +#[test] +fn dispatch_as_successful_no_details() { + ExtBuilder::default() + .with_balances(vec![(SUBMITTER, 10_000)]) + .build() + .execute_with(|| { + // Needed to test event generation. See for more context. + frame_system::Pallet::::set_block_number(1); + assert!(IdentityEntries::::get(SUBJECT).is_none()); + assert_ok!(Pallet::::dispatch_as( + RawOrigin::Signed(SUBMITTER).into(), + SUBJECT, + true, + Box::new(pallet_did_lookup::Call::associate_sender {}.into()) + )); + System::assert_last_event( + pallet_did_lookup::Event::::AssociationEstablished(SUBMITTER.into(), SUBJECT).into(), + ); + assert_eq!(IdentityEntries::::get(SUBJECT), Some(0)); + }); +} + +#[test] +fn dispatch_as_successful_existing_details() { + ExtBuilder::default() + .with_balances(vec![(SUBMITTER, 10_000)]) + .with_identity_details(vec![(SUBJECT, 100)]) + .build() + .execute_with(|| { + // Needed to test event generation. See for more context. + frame_system::Pallet::::set_block_number(1); + assert_ok!(Pallet::::dispatch_as( + RawOrigin::Signed(SUBMITTER).into(), + SUBJECT, + true, + Box::new(pallet_did_lookup::Call::associate_sender {}.into()) + )); + System::assert_last_event( + pallet_did_lookup::Event::::AssociationEstablished(SUBMITTER.into(), SUBJECT).into(), + ); + // Details have been bumped up by the proof verifier, and correctly stored in the storage. + assert_eq!(IdentityEntries::::get(SUBJECT), Some(101)); + }); +} + +#[test] +fn dispatch_as_filtered() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Pallet::::dispatch_as( + RawOrigin::Signed(SUBMITTER).into(), + SUBJECT, + true, + Box::new( + frame_system::Call::remark_with_event { + remark: b"Hello!".to_vec(), + } + .into(), + ), + ), + Error::::Filtered + ); + }); +} + +#[test] +fn dispatch_as_invalid_proof() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Pallet::::dispatch_as( + RawOrigin::Signed(SUBMITTER).into(), + SUBJECT, + false, + Box::new( + frame_system::Call::remark { + remark: b"Hello!".to_vec(), + } + .into(), + ), + ), + Error::::InvalidProof(1) + ); + }); +} diff --git a/pallets/pallet-dip-consumer/src/tests/mod.rs b/pallets/pallet-dip-consumer/src/tests/mod.rs new file mode 100644 index 0000000000..93b84f7b9a --- /dev/null +++ b/pallets/pallet-dip-consumer/src/tests/mod.rs @@ -0,0 +1,19 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2024 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +mod dispatch_as; diff --git a/pallets/pallet-dip-provider/src/benchmarking.rs b/pallets/pallet-dip-provider/src/benchmarking.rs index a0dcbe4f7f..31aa3e2e84 100644 --- a/pallets/pallet-dip-provider/src/benchmarking.rs +++ b/pallets/pallet-dip-provider/src/benchmarking.rs @@ -28,7 +28,7 @@ use kilt_support::{ T::CommitOriginCheck: GenerateBenchmarkOrigin, T::AccountId: Instanciate, T::Identifier: Instanciate, - <::IdentityProvider as IdentityProvider>::Success: GetWorstCase> + <::IdentityProvider as IdentityProvider>::Success: GetWorstCase, Output = <::IdentityProvider as IdentityProvider>::Success> )] mod benchmarks { diff --git a/pallets/pallet-dip-provider/src/lib.rs b/pallets/pallet-dip-provider/src/lib.rs index 75ad0e4c51..311649319d 100644 --- a/pallets/pallet-dip-provider/src/lib.rs +++ b/pallets/pallet-dip-provider/src/lib.rs @@ -91,7 +91,7 @@ pub mod pallet { #[pallet::getter(fn identity_commitments)] pub type IdentityCommitments = StorageDoubleMap< _, - Twox64Concat, + Blake2_128Concat, ::Identifier, Twox64Concat, IdentityCommitmentVersion, diff --git a/pallets/pallet-dip-provider/src/traits.rs b/pallets/pallet-dip-provider/src/traits.rs index 5f5369b733..6fc7111de7 100644 --- a/pallets/pallet-dip-provider/src/traits.rs +++ b/pallets/pallet-dip-provider/src/traits.rs @@ -44,7 +44,7 @@ pub mod identity_provision { /// Return the `Default` value of the provided `Identity` type if it /// implements the `Default` trait. - pub struct DefaultIdentityProvider(PhantomData); + pub struct DefaultIdentityProvider(PhantomData); impl IdentityProvider for DefaultIdentityProvider where @@ -91,7 +91,7 @@ pub mod identity_generation { /// Implement the [`IdentityCommitmentGenerator`] trait by returning the /// `Default` value for the `Output` type. - pub struct DefaultIdentityCommitmentGenerator(PhantomData); + pub struct DefaultIdentityCommitmentGenerator(PhantomData); impl IdentityCommitmentGenerator for DefaultIdentityCommitmentGenerator where diff --git a/pallets/pallet-migration/src/benchmarking.rs b/pallets/pallet-migration/src/benchmarking.rs index 5f221d48a8..598b7b864c 100644 --- a/pallets/pallet-migration/src/benchmarking.rs +++ b/pallets/pallet-migration/src/benchmarking.rs @@ -71,7 +71,7 @@ benchmarks! { ::AccountId: From, ::EnsureOrigin: GenerateBenchmarkOrigin<::RuntimeOrigin, T::AccountId, ::AttesterId>, BlockNumberFor: From, - ::SubjectId: GetWorstCase + sp_std::fmt::Debug + Into> , + ::SubjectId: GetWorstCase::SubjectId> + sp_std::fmt::Debug + Into> , T: ctype::Config::AttesterId>, ::DelegationNodeId: From, ::DelegationEntityId: From, diff --git a/pallets/pallet-migration/src/mock.rs b/pallets/pallet-migration/src/mock.rs index bbf3ba6b80..4a21c99c60 100644 --- a/pallets/pallet-migration/src/mock.rs +++ b/pallets/pallet-migration/src/mock.rs @@ -377,7 +377,9 @@ pub mod runtime { #[cfg(feature = "runtime-benchmarks")] impl kilt_support::traits::GetWorstCase for TestSubjectId { // Only used for benchmark testing, not really relevant. - fn worst_case(_context: Context) -> Self { + type Output = Self; + + fn worst_case(_context: Context) -> Self::Output { crate::mock::TestSubjectId::default() } } diff --git a/pallets/pallet-relay-store/Cargo.toml b/pallets/pallet-relay-store/Cargo.toml index 724b327558..f9ac62b733 100644 --- a/pallets/pallet-relay-store/Cargo.toml +++ b/pallets/pallet-relay-store/Cargo.toml @@ -14,13 +14,14 @@ version.workspace = true targets = ["x86_64-unknown-linux-gnu"] [dev-dependencies] -cumulus-primitives-core = { workspace = true, features = ["std"] } sp-io = {workspace = true, features = ["std"]} sp-keystore = {workspace = true, features = ["std"]} sp-runtime = {workspace = true, features = ["std"]} +sp-trie = {workspace = true, features = ["std"]} [dependencies] cumulus-pallet-parachain-system.workspace = true +cumulus-primitives-core.workspace = true frame-support.workspace = true frame-system.workspace = true log.workspace = true @@ -37,6 +38,7 @@ sp-runtime = {workspace = true, optional = true} default = ["std"] std = [ "cumulus-pallet-parachain-system/std", + "cumulus-primitives-core/std", "frame-support/std", "frame-system/std", "parity-scale-codec/std", @@ -53,4 +55,7 @@ runtime-benchmarks = [ "frame-benchmarking", "sp-runtime/runtime-benchmarks", ] -try-runtime = [] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/pallets/pallet-relay-store/src/lib.rs b/pallets/pallet-relay-store/src/lib.rs index 27ad314cf4..28cfe0a777 100644 --- a/pallets/pallet-relay-store/src/lib.rs +++ b/pallets/pallet-relay-store/src/lib.rs @@ -32,12 +32,16 @@ mod benchmarking; #[cfg(test)] mod mock; -pub use crate::{default_weights::WeightInfo, pallet::*}; +#[cfg(test)] +mod tests; + +pub use crate::{default_weights::WeightInfo, pallet::*, relay::*}; #[frame_support::pallet] pub mod pallet { use super::*; + use cumulus_primitives_core::PersistedValidationData; use frame_support::{pallet_prelude::*, BoundedVec}; use frame_system::pallet_prelude::*; use sp_core::H256; @@ -97,6 +101,10 @@ pub mod pallet { let Some(new_validation_data) = cumulus_pallet_parachain_system::Pallet::::validation_data() else { return; }; + Self::store_new_validation_data(new_validation_data) + } + + pub(crate) fn store_new_validation_data(validation_data: PersistedValidationData) { let mut latest_block_heights = LatestBlockHeights::::get(); // Remove old relay block from both storage entries. if latest_block_heights.is_full() { @@ -108,11 +116,11 @@ pub mod pallet { ); } // Set the new relay block in storage. - let relay_block_height = new_validation_data.relay_parent_number; + let relay_block_height = validation_data.relay_parent_number; log::trace!( "Adding new relay block with state root {:#02x?} and number {:?}", - new_validation_data.relay_parent_storage_root, - new_validation_data.relay_parent_number, + validation_data.relay_parent_storage_root, + validation_data.relay_parent_number, ); let push_res = latest_block_heights.try_push(relay_block_height); if let Err(err) = push_res { @@ -125,7 +133,7 @@ pub mod pallet { LatestRelayHeads::::insert( relay_block_height, RelayParentInfo { - relay_parent_storage_root: new_validation_data.relay_parent_storage_root, + relay_parent_storage_root: validation_data.relay_parent_storage_root, }, ); } diff --git a/pallets/pallet-relay-store/src/mock.rs b/pallets/pallet-relay-store/src/mock.rs index 13a53b6263..8ee0e87c70 100644 --- a/pallets/pallet-relay-store/src/mock.rs +++ b/pallets/pallet-relay-store/src/mock.rs @@ -17,7 +17,7 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org use cumulus_pallet_parachain_system::{ParachainSetCode, RelayNumberStrictlyIncreases}; -use cumulus_primitives_core::ParaId; +use cumulus_primitives_core::{ParaId, PersistedValidationData}; use frame_support::{ construct_runtime, parameter_types, sp_runtime::{ @@ -25,9 +25,13 @@ use frame_support::{ traits::{BlakeTwo256, IdentityLookup}, AccountId32, }, + storage_alias, traits::{ConstU16, ConstU32, ConstU64, Everything}, }; use frame_system::mocking::MockBlock; +use sp_runtime::BoundedVec; + +use crate::{Config, Pallet}; construct_runtime!( pub struct TestRuntime { @@ -84,17 +88,52 @@ impl crate::Config for TestRuntime { type WeightInfo = (); } +// Alias to the ParachainSystem storage which cannot be modified directly. +#[storage_alias] +type ValidationData = StorageValue; + #[derive(Default)] -pub(crate) struct ExtBuilder; +pub(crate) struct ExtBuilder( + Option<(u32, H256)>, + BoundedVec<(u32, H256), ::MaxRelayBlocksStored>, +); impl ExtBuilder { - pub fn _build(self) -> sp_io::TestExternalities { - sp_io::TestExternalities::default() + pub(crate) fn with_new_relay_state_root(mut self, relay_root: (u32, H256)) -> Self { + self.0 = Some(relay_root); + self + } + + pub(crate) fn with_stored_relay_roots(mut self, relay_roots: Vec<(u32, H256)>) -> Self { + self.1 = relay_roots.try_into().unwrap(); + self + } + + pub(crate) fn build(self) -> sp_io::TestExternalities { + let mut ext = sp_io::TestExternalities::default(); + ext.execute_with(|| { + if let Some(new_relay_state_root) = self.0 { + ValidationData::put(PersistedValidationData { + relay_parent_number: new_relay_state_root.0, + relay_parent_storage_root: new_relay_state_root.1, + ..Default::default() + }); + } + for (stored_relay_block_number, stored_relay_state_root) in self.1 { + Pallet::::store_new_validation_data(PersistedValidationData { + relay_parent_number: stored_relay_block_number, + relay_parent_storage_root: stored_relay_state_root, + ..Default::default() + }); + } + }); + + ext } #[cfg(feature = "runtime-benchmarks")] - pub fn build_with_keystore(self) -> sp_io::TestExternalities { - let mut ext = self._build(); + pub(crate) fn build_with_keystore(self) -> sp_io::TestExternalities { + let mut ext = self.build(); let keystore = sp_keystore::testing::MemoryKeystore::new(); ext.register_extension(sp_keystore::KeystoreExt(sp_std::sync::Arc::new(keystore))); ext diff --git a/pallets/pallet-relay-store/src/tests/mod.rs b/pallets/pallet-relay-store/src/tests/mod.rs new file mode 100644 index 0000000000..aff45dd1d3 --- /dev/null +++ b/pallets/pallet-relay-store/src/tests/mod.rs @@ -0,0 +1,19 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2024 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +mod on_finalize; diff --git a/pallets/pallet-relay-store/src/tests/on_finalize.rs b/pallets/pallet-relay-store/src/tests/on_finalize.rs new file mode 100644 index 0000000000..560124660f --- /dev/null +++ b/pallets/pallet-relay-store/src/tests/on_finalize.rs @@ -0,0 +1,86 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2024 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use frame_support::traits::Hooks; +use sp_runtime::traits::Zero; + +use crate::{ + mock::{ExtBuilder, System, TestRuntime}, + relay::RelayParentInfo, + LatestBlockHeights, LatestRelayHeads, Pallet, +}; + +#[test] +fn on_finalize_empty_state() { + ExtBuilder::default() + .with_new_relay_state_root((1, [100; 32].into())) + .build() + .execute_with(|| { + assert!(LatestRelayHeads::::iter().count().is_zero()); + assert!(LatestBlockHeights::::get().is_empty()); + + Pallet::::on_finalize(System::block_number()); + + assert_eq!( + LatestRelayHeads::::iter().collect::>(), + vec![( + 1, + RelayParentInfo { + relay_parent_storage_root: [100; 32].into() + } + )] + ); + assert_eq!(LatestBlockHeights::::get(), vec![1]); + }); +} + +// This should never happen, but we add a test to make sure the code in here +// never panics. +#[test] +fn on_finalize_empty_validation_data() { + ExtBuilder::default().build().execute_with(|| { + Pallet::::on_finalize(System::block_number()); + assert!(LatestRelayHeads::::iter().count().is_zero()); + assert!(LatestBlockHeights::::get().is_empty()); + }); +} + +#[test] +fn on_finalize_full_state() { + ExtBuilder::default() + .with_stored_relay_roots(vec![ + (1, [1; 32].into()), + (2, [2; 32].into()), + (3, [3; 32].into()), + (4, [4; 32].into()), + (5, [5; 32].into()), + ]) + .with_new_relay_state_root((6, [6; 32].into())) + .build() + .execute_with(|| { + Pallet::::on_finalize(System::block_number()); + assert!(LatestRelayHeads::::get(1).is_none(),); + assert_eq!( + LatestRelayHeads::::get(6), + Some(RelayParentInfo { + relay_parent_storage_root: [6; 32].into() + }) + ); + assert_eq!(LatestBlockHeights::::get(), vec![2, 3, 4, 5, 6]); + }); +} diff --git a/pallets/public-credentials/src/benchmarking.rs b/pallets/public-credentials/src/benchmarking.rs index 4a6113f7c5..3fbbdd63c8 100644 --- a/pallets/public-credentials/src/benchmarking.rs +++ b/pallets/public-credentials/src/benchmarking.rs @@ -59,7 +59,7 @@ benchmarks! { T: Config, T: ctype::Config, ::EnsureOrigin: GenerateBenchmarkOrigin, - ::SubjectId: GetWorstCase + Into> + sp_std::fmt::Debug, + ::SubjectId: GetWorstCase::SubjectId> + Into> + sp_std::fmt::Debug, ::CredentialId: Default, BlockNumberFor: From, ::Currency: Mutate, diff --git a/pallets/public-credentials/src/mock.rs b/pallets/public-credentials/src/mock.rs index 18655b3e22..a444563031 100644 --- a/pallets/public-credentials/src/mock.rs +++ b/pallets/public-credentials/src/mock.rs @@ -249,7 +249,9 @@ pub(crate) mod runtime { #[cfg(feature = "runtime-benchmarks")] impl kilt_support::traits::GetWorstCase for TestSubjectId { // Only used for benchmark testing, not really relevant. - fn worst_case(_context: Context) -> Self { + type Output = Self; + + fn worst_case(_context: Context) -> Self::Output { crate::mock::TestSubjectId::default() } } diff --git a/runtimes/common/src/assets.rs b/runtimes/common/src/assets.rs index 855e09e157..21787709c5 100644 --- a/runtimes/common/src/assets.rs +++ b/runtimes/common/src/assets.rs @@ -105,7 +105,9 @@ mod benchmarks { } impl kilt_support::traits::GetWorstCase for AssetDid { - fn worst_case(_context: Context) -> Self { + type Output = Self; + + fn worst_case(_context: Context) -> Self::Output { // Returns the worst case for an AssetDID, which is represented by the longest // identifier according to the spec. Self::try_from( diff --git a/runtimes/common/src/dip/did/mod.rs b/runtimes/common/src/dip/did/mod.rs index 660fc945d5..a8533aafd3 100644 --- a/runtimes/common/src/dip/did/mod.rs +++ b/runtimes/common/src/dip/did/mod.rs @@ -175,7 +175,9 @@ where ::AccountId: Into + From, ::AccountId: AsRef<[u8; 32]> + From<[u8; 32]>, { - fn worst_case(context: IdentityContext) -> Self { + type Output = Self; + + fn worst_case(context: IdentityContext) -> Self::Output { use did::{ did_details::DidVerificationKey, mock_utils::{generate_base_did_creation_details, get_key_agreement_keys}, diff --git a/runtimes/common/src/dip/merkle/v0/mod.rs b/runtimes/common/src/dip/merkle/v0/mod.rs index fdb66b959b..591c7e43de 100644 --- a/runtimes/common/src/dip/merkle/v0/mod.rs +++ b/runtimes/common/src/dip/merkle/v0/mod.rs @@ -313,10 +313,7 @@ where Ok(CompleteMerkleProof { root, - proof: DidMerkleProofOf::::new( - proof.into_iter().into(), - leaves.into_iter().flatten().collect::>(), - ), + proof: DidMerkleProofOf::::new(proof, leaves.into_iter().flatten().collect::>()), }) } diff --git a/runtimes/common/src/dip/merkle/v0/tests/generate_proof.rs b/runtimes/common/src/dip/merkle/v0/tests/generate_proof.rs index 5948ff4c1c..ea40d90aeb 100644 --- a/runtimes/common/src/dip/merkle/v0/tests/generate_proof.rs +++ b/runtimes/common/src/dip/merkle/v0/tests/generate_proof.rs @@ -83,7 +83,7 @@ fn generate_proof_for_complete_linked_info() { let dip_origin_info = cross_chain_proof .verify_dip_proof::() .and_then(|r| r.verify_signature_time(&50)) - .and_then(|r| r.retrieve_signing_leaf_for_payload(&().encode())) + .and_then(|r| r.retrieve_signing_leaves_for_payload(&().encode())) .unwrap(); // All key agreement keys, plus authentication, attestation, and delegation key, // plus all linked accounts, plus web3name. @@ -185,7 +185,7 @@ fn generate_proof_for_complete_linked_info() { let dip_origin_info = cross_chain_proof .verify_dip_proof::() .and_then(|r| r.verify_signature_time(&50)) - .and_then(|r| r.retrieve_signing_leaf_for_payload(&().encode())) + .and_then(|r| r.retrieve_signing_leaves_for_payload(&().encode())) .unwrap(); // Only the authentication key. let expected_leaves_revealed = 1; @@ -256,7 +256,7 @@ fn generate_proof_for_complete_linked_info() { let dip_origin_info = cross_chain_proof .verify_dip_proof::() .and_then(|r| r.verify_signature_time(&50)) - .and_then(|r| r.retrieve_signing_leaf_for_payload(&().encode())) + .and_then(|r| r.retrieve_signing_leaves_for_payload(&().encode())) .unwrap(); // The authentication key and the web3name. let expected_leaves_revealed = 2; @@ -310,7 +310,7 @@ fn generate_proof_for_complete_linked_info() { let dip_origin_info = cross_chain_proof .verify_dip_proof::() .and_then(|r| r.verify_signature_time(&50)) - .and_then(|r| r.retrieve_signing_leaf_for_payload(&().encode())) + .and_then(|r| r.retrieve_signing_leaves_for_payload(&().encode())) .unwrap(); // The authentication key and the web3name. let expected_leaves_revealed = 2; @@ -446,7 +446,7 @@ fn generate_proof_with_two_keys_with_same_id() { let dip_origin_info = cross_chain_proof .verify_dip_proof::() .and_then(|r| r.verify_signature_time(&50)) - .and_then(|r| r.retrieve_signing_leaf_for_payload(&().encode())) + .and_then(|r| r.retrieve_signing_leaves_for_payload(&().encode())) .unwrap(); // Authentication key and attestation key have the same key ID, but they are // different keys, so there should be 2 leaves. diff --git a/support/src/traits.rs b/support/src/traits.rs index 52a0c5e114..535fc65f9c 100644 --- a/support/src/traits.rs +++ b/support/src/traits.rs @@ -82,19 +82,30 @@ pub trait GenerateBenchmarkOrigin { /// only when running benchmarks. #[cfg(feature = "runtime-benchmarks")] pub trait GetWorstCase { - fn worst_case(context: Context) -> Self; + type Output; + fn worst_case(context: Context) -> Self::Output; } #[cfg(feature = "runtime-benchmarks")] impl GetWorstCase for u32 { - fn worst_case(_context: T) -> Self { + type Output = Self; + fn worst_case(_context: T) -> Self::Output { u32::MAX } } #[cfg(feature = "runtime-benchmarks")] impl GetWorstCase for () { - fn worst_case(_context: T) -> Self {} + type Output = Self; + fn worst_case(_context: T) -> Self::Output {} +} + +#[cfg(feature = "runtime-benchmarks")] +impl GetWorstCase for bool { + type Output = Self; + fn worst_case(_context: T) -> Self::Output { + true + } } /// Trait that allows instanciating multiple instances of a type.