From 738c08e254107722e550ca49f26a3002580ee62d Mon Sep 17 00:00:00 2001 From: Antonio Date: Mon, 22 May 2023 16:05:27 +0200 Subject: [PATCH] feat: add DID signature verification and replay protection (#516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes https://github.com/KILTprotocol/ticket/issues/2553. The main changes are: * Removed half-baked versioning support, to re-introduce a proper one once versioning and version negotiation will be tackeld * Moved most of the logic from the `runtime-common` crate to a new `kilt-dip-support` crate, after making all the types generic over all the runtime configurations * Updated the `pallet-dip-consumer` pallet to have a bunch more details, more info below * Updated the XCM-simulator based tests ## Updates to the `dip-support` crate The `dip-support` does not contain any versioned types anymore. Support for versioning was half-baked and has been removed. Proper thought around versioning and version negotiation will happen in the (short) future. ## New `kilt-dip-support` crate The new `kilt-dip-support` crate contains types and traits that are not generic over ANY DIP deployment, but that can be used by every consumer that relies on the KILT chain as their provider. Hence, types are opinionated towards the way KILT implements the DIP protocol, but generic over the runtime configuration. The most relevant types are `MerkleProofAndDidSignatureVerifier`, exported from the root, `DidMerkleProofVerifier`, exported from the `merkle` module, and `DidSignatureAndCallVerifier`, exported from the `did` module. When they are chained together, they allow a user to submit a Merkle proof revealing parts of their DID Document, and to use one of the revealed keys to sign a specifically-crafted payload which provides authenticity guarantees of the DID subject. The default verification logic for the DID signature is the following: 1. Verify that the block number specified in the signature is no older than `SIGNATURE_VALIDITY` blocks, as specified by the runtime 2. Verify that the signature can be verified over the encoded tuple formed by `(call, identity_details, submitter_address, block_number, genesis_hash, signed_extra)`, where `signed_extra` can be anything more that the runtime might require be included in the signature. 3. Verify that one of the keys revealed in the Merkle proof can be used to verify the provided signature 4. [OPTIONAL] If the type variant which also performs authorization checks on the call itself is used, i.e., the `DidSignatureAndCallVerifier` type, then the verification will also include checking whether the used verification relationship can be used to dispatch the specified call or not, as we do ourselves in our did pallet within the `submit_did_call`, but in a more generic way. The verification logic for Merkle proofs has remained unchanged, and it can now be chained with the DID verification logic. ## Refreshed `pallet-dip-consumer` The pallet has been updated so that now the `dispatch_as` extrinsic takes a generic `Proof` instead of an `IdentityProof`, and internally performs the following checks: 1. Verify the origin is a signed origin (as before) 2. Verify that the call passes a preliminary filter, before any heavy computation is executed on the provided proof, via the `DipCallOriginFilter` type. This step will typically immediately filter out any calls that cannot be called with a Dip origin regardless of the content of the proof. 3. Retrieves the identity details from storage, if they exist 4. Delegate everything to the `ProofVerifier`, which has a mutable reference to the details fetched at step 3, in case they need to update some values that will be written to storage (e.g., the nonce) 5. The maybe mutated details are written back into storage 6. The call is dispatched with the new `DipOrigin`, which carries the additional information as returned by the `ProofVerifier`. In the demo runtime, this will be the set of keys revealed as part of the Merkle proof, that have been verified, parsed, and used for verifying the DID signature provided as part of the proof. The pallet logic is very simple, and most of the (complex) logic lies in the types that have been exposed as part of the `kilt-dip-support` crate. This makes the pallet easier to understand, and more generic to be used in different contexts potentially for different identity providers as well, where each provider might require users to provide a different identity proof. ## Minor changes Other things have been updated to reflect the relocation of some files into the new `kilt-dip-support` crate. ## How to test 1. Build [this version](https://github.com/KILTprotocol/sdk-js/pull/751) of the SDK 2. Clone and `yarn install && yarn build` [this version](https://github.com/KILTprotocol/polkadot-apps/pull/8) of the PolkadotJS apps 3. Copy-paste the `@kiltprotocol/type-definitions` output into the root `node_modules` folder of the PolkadotJS apps 4. Follow the steps to set up the HRMP channels, create the DIDs, and push the identity commitment from provider to consumer 5. Use [this version](https://github.com/KILTprotocol/kilt-did-utilities/pull/14) of the kilt-did-utilities CLI tool to generate a valid DIP DID signature. 6. Enjoy ☀️☀️☀️ --- Cargo.lock | 23 +- Cargo.toml | 3 +- crates/{dip => dip-support}/Cargo.toml | 2 - .../{dip/src/v1.rs => dip-support/src/lib.rs} | 14 +- crates/dip/src/lib.rs | 73 ---- crates/kilt-dip-support/Cargo.toml | 44 +++ crates/kilt-dip-support/src/did.rs | 239 +++++++++++++ crates/kilt-dip-support/src/lib.rs | 95 ++++++ crates/kilt-dip-support/src/merkle.rs | 251 ++++++++++++++ crates/kilt-dip-support/src/traits.rs | 78 +++++ dip-template/runtimes/dip-consumer/Cargo.toml | 4 + dip-template/runtimes/dip-consumer/src/dip.rs | 95 ++++-- dip-template/runtimes/dip-consumer/src/lib.rs | 5 +- dip-template/runtimes/dip-provider/src/dip.rs | 8 +- dip-template/runtimes/dip-provider/src/lib.rs | 6 +- dip-template/runtimes/xcm-tests/Cargo.toml | 2 +- dip-template/runtimes/xcm-tests/src/tests.rs | 54 ++- pallets/pallet-dip-consumer/src/identity.rs | 43 +++ pallets/pallet-dip-consumer/src/lib.rs | 117 ++++--- pallets/pallet-dip-consumer/src/traits.rs | 47 ++- pallets/pallet-dip-provider/src/lib.rs | 19 +- pallets/pallet-dip-provider/src/traits.rs | 10 +- runtimes/common/Cargo.toml | 4 +- runtimes/common/src/dip/consumer.rs | 118 ------- runtimes/common/src/dip/did.rs | 42 +++ .../common/src/dip/{provider.rs => merkle.rs} | 41 +-- runtimes/common/src/dip/mod.rs | 66 +--- runtimes/common/src/dip/tests.rs | 315 ------------------ runtimes/common/src/lib.rs | 6 +- runtimes/peregrine/src/lib.rs | 4 +- runtimes/spiritnet/src/lib.rs | 4 +- 31 files changed, 1075 insertions(+), 757 deletions(-) rename crates/{dip => dip-support}/Cargo.toml (94%) rename crates/{dip/src/v1.rs => dip-support/src/lib.rs} (74%) delete mode 100644 crates/dip/src/lib.rs create mode 100644 crates/kilt-dip-support/Cargo.toml create mode 100644 crates/kilt-dip-support/src/did.rs create mode 100644 crates/kilt-dip-support/src/lib.rs create mode 100644 crates/kilt-dip-support/src/merkle.rs create mode 100644 crates/kilt-dip-support/src/traits.rs create mode 100644 pallets/pallet-dip-consumer/src/identity.rs delete mode 100644 runtimes/common/src/dip/consumer.rs create mode 100644 runtimes/common/src/dip/did.rs rename runtimes/common/src/dip/{provider.rs => merkle.rs} (86%) delete mode 100644 runtimes/common/src/dip/tests.rs diff --git a/Cargo.lock b/Cargo.lock index a47264471..e51576ab8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2341,10 +2341,12 @@ dependencies = [ "cumulus-primitives-timestamp", "cumulus-primitives-utility", "did", + "dip-support", "frame-executive", "frame-support", "frame-system", "frame-system-rpc-runtime-api", + "kilt-dip-support", "pallet-aura", "pallet-authorship", "pallet-balances", @@ -2487,7 +2489,6 @@ dependencies = [ "frame-support", "parity-scale-codec", "scale-info", - "sp-std", ] [[package]] @@ -2498,9 +2499,9 @@ dependencies = [ "did", "dip-consumer-runtime-template", "dip-provider-runtime-template", - "dip-support", "frame-support", "frame-system", + "kilt-dip-support", "kilt-support", "pallet-balances", "pallet-did-lookup", @@ -4211,6 +4212,22 @@ dependencies = [ "sp-std", ] +[[package]] +name = "kilt-dip-support" +version = "1.11.0-dev" +dependencies = [ + "did", + "frame-support", + "frame-system", + "pallet-dip-consumer", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", + "sp-trie", +] + [[package]] name = "kilt-parachain" version = "1.11.0-dev" @@ -9400,10 +9417,10 @@ dependencies = [ "cumulus-primitives-core", "delegation", "did", - "dip-support", "frame-support", "frame-system", "kilt-asset-dids", + "kilt-dip-support", "kilt-support", "log", "pallet-authorship", diff --git a/Cargo.toml b/Cargo.toml index 221c4cac8..4be4c260c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,8 +60,9 @@ parachain-staking = {path = "pallets/parachain-staking", default-features = fals public-credentials = {path = "pallets/public-credentials", default-features = false} # Internal support (with default disabled) -dip-support = {path = "crates/dip", default-features = false} +dip-support = {path = "crates/dip-support", default-features = false} kilt-asset-dids = {path = "crates/assets", default-features = false} +kilt-dip-support = {path = "crates/kilt-dip-support", default-features = false} kilt-support = {path = "support", default-features = false} runtime-common = {path = "runtimes/common", default-features = false} diff --git a/crates/dip/Cargo.toml b/crates/dip-support/Cargo.toml similarity index 94% rename from crates/dip/Cargo.toml rename to crates/dip-support/Cargo.toml index 0b0a7af1e..276b3929a 100644 --- a/crates/dip/Cargo.toml +++ b/crates/dip-support/Cargo.toml @@ -14,7 +14,6 @@ version.workspace = true # Parity dependencies parity-scale-codec = {workspace = true, features = ["derive"]} scale-info = {workspace = true, features = ["derive"]} -sp-std.workspace = true # Substrate dependencies frame-support.workspace = true @@ -24,6 +23,5 @@ default = ["std"] std = [ "parity-scale-codec/std", "scale-info/std", - "sp-std/std", "frame-support/std" ] diff --git a/crates/dip/src/v1.rs b/crates/dip-support/src/lib.rs similarity index 74% rename from crates/dip/src/v1.rs rename to crates/dip-support/src/lib.rs index bdbda97bf..bde5dbfee 100644 --- a/crates/dip/src/v1.rs +++ b/crates/dip-support/src/lib.rs @@ -16,20 +16,16 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org +// TODO: Crate documentation + +#![cfg_attr(not(feature = "std"), no_std)] + use frame_support::RuntimeDebug; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_std::vec::Vec; -#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebug)] pub enum IdentityProofAction { Updated(Identifier, Proof, Details), Deleted(Identifier), } - -#[derive(Encode, Decode, RuntimeDebug, Clone, Eq, PartialEq, TypeInfo, Default)] -pub struct Proof { - pub blinded: BlindedValue, - // TODO: Probably replace with a different data structure for better lookup capabilities - pub revealed: Vec, -} diff --git a/crates/dip/src/lib.rs b/crates/dip/src/lib.rs deleted file mode 100644 index a795d7c85..000000000 --- a/crates/dip/src/lib.rs +++ /dev/null @@ -1,73 +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 - -// TODO: Crate documentation - -#![cfg_attr(not(feature = "std"), no_std)] - -use frame_support::RuntimeDebug; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; - -// Export v1 behind a namespace and also as the latest -pub mod v1; -pub mod latest { - pub use crate::v1::*; -} - -#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[non_exhaustive] -pub enum VersionedIdentityProofAction { - #[codec(index = 1)] - V1(v1::IdentityProofAction), -} - -impl From> - for VersionedIdentityProofAction -{ - fn from(value: v1::IdentityProofAction) -> Self { - Self::V1(value) - } -} - -#[derive(Encode, Decode, RuntimeDebug, Clone, Eq, PartialEq, TypeInfo)] -#[non_exhaustive] -pub enum VersionedIdentityProof { - #[codec(index = 1)] - V1(v1::Proof), -} - -impl From> for VersionedIdentityProof { - fn from(value: v1::Proof) -> Self { - Self::V1(value) - } -} - -impl TryFrom> for v1::Proof { - // Proper error handling - type Error = (); - - fn try_from(value: VersionedIdentityProof) -> Result { - #[allow(irrefutable_let_patterns)] - if let VersionedIdentityProof::V1(v1::Proof { blinded, revealed }) = value { - Ok(Self { blinded, revealed }) - } else { - Err(()) - } - } -} diff --git a/crates/kilt-dip-support/Cargo.toml b/crates/kilt-dip-support/Cargo.toml new file mode 100644 index 000000000..ebc86ee9b --- /dev/null +++ b/crates/kilt-dip-support/Cargo.toml @@ -0,0 +1,44 @@ +[package] +authors.workspace = true +description = "Support types, traits, and functions for the KILT Decentralized Identity Provider (DIP) functionality as implemented by the KILT blockchain." +documentation.workspace = true +edition.workspace = true +homepage.workspace = true +license-file.workspace = true +name = "kilt-dip-support" +readme.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] +# Internal dependencies +did.workspace = true +pallet-dip-consumer.workspace = true + +# Parity dependencies +parity-scale-codec = {workspace = true, features = ["derive"]} +scale-info = {workspace = true, features = ["derive"]} + +# Substrate dependencies +frame-system.workspace = true +frame-support.workspace = true +sp-runtime.workspace = true +sp-core.workspace = true +sp-trie.workspace = true +sp-std.workspace = true + +[features] +default = ["std"] +std = [ + "did/std", + "pallet-dip-consumer/std", + "parity-scale-codec/std", + "scale-info/std", + "frame-system/std", + "frame-support/std", + "sp-runtime/std", + "sp-core/std", + "sp-trie/std", + "sp-std/std" +] +runtime-benchmarks = [] diff --git a/crates/kilt-dip-support/src/did.rs b/crates/kilt-dip-support/src/did.rs new file mode 100644 index 000000000..07bd50d2e --- /dev/null +++ b/crates/kilt-dip-support/src/did.rs @@ -0,0 +1,239 @@ +// 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, DidVerificationKey}, + DidSignature, DidVerificationKeyRelationship, +}; +use frame_support::ensure; +use pallet_dip_consumer::{identity::IdentityDetails, traits::IdentityProofVerifier}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_core::{ConstU64, Get, RuntimeDebug}; +use sp_runtime::traits::CheckedSub; +use sp_std::marker::PhantomData; + +use crate::{ + merkle::ProofEntry, + traits::{Bump, DidDipOriginFilter}, +}; + +#[derive(Encode, Decode, RuntimeDebug, Clone, Eq, PartialEq, TypeInfo)] +pub struct TimeBoundDidSignature { + pub signature: DidSignature, + pub block_number: BlockNumber, +} + +#[derive(Encode, Decode, RuntimeDebug, Clone, Eq, PartialEq, TypeInfo)] +pub struct MerkleEntriesAndDidSignature { + pub merkle_entries: MerkleEntries, + pub did_signature: TimeBoundDidSignature, +} + +/// A type that verifies a DID signature over some DID keys revealed by a +/// previously-verified Merkle proof. It requires the `Details` type to +/// implement the `Bump` trait to avoid replay attacks. The basic verification +/// logic verifies that the signature has been generated over the encoded tuple +/// (call, identity details, submitter_address, submission_block_number, +/// genesis_hash). Additional details can be added to the end of the tuple by +/// providing a `SignedExtraProvider`. +pub struct MerkleRevealedDidSignatureVerifier< + BlockNumber, + Digest, + Details, + AccountId, + MerkleProofEntries, + BlockNumberProvider, + const SIGNATURE_VALIDITY: u64, + GenesisHashProvider, + Hash, + SignedExtraProvider = (), + SignedExtra = (), +>( + #[allow(clippy::type_complexity)] + PhantomData<( + BlockNumber, + Digest, + Details, + AccountId, + MerkleProofEntries, + BlockNumberProvider, + ConstU64, + GenesisHashProvider, + Hash, + SignedExtraProvider, + SignedExtra, + )>, +); + +impl< + Call, + Subject, + BlockNumber, + Digest, + Details, + AccountId, + MerkleProofEntries, + BlockNumberProvider, + const SIGNATURE_VALIDITY: u64, + GenesisHashProvider, + Hash, + SignedExtraProvider, + SignedExtra, + > IdentityProofVerifier + for MerkleRevealedDidSignatureVerifier< + BlockNumber, + Digest, + Details, + AccountId, + MerkleProofEntries, + BlockNumberProvider, + SIGNATURE_VALIDITY, + GenesisHashProvider, + Hash, + SignedExtraProvider, + SignedExtra, + > where + AccountId: Encode, + BlockNumber: Encode + CheckedSub + Into + PartialOrd + sp_std::fmt::Debug, + Call: Encode, + Digest: Encode, + Details: Bump + Encode, + MerkleProofEntries: AsRef<[ProofEntry]>, + BlockNumberProvider: Get, + GenesisHashProvider: Get, + Hash: Encode, + SignedExtraProvider: Get, + SignedExtra: Encode, +{ + // TODO: Error handling + type Error = (); + /// The proof must be a list of Merkle leaves that have been previously + /// verified by the Merkle proof verifier, and the additional DID signature. + type Proof = MerkleEntriesAndDidSignature; + /// The `Details` that are part of the identity details must implement the + /// `Bump` trait. + type IdentityDetails = IdentityDetails; + /// The type of the submitter's accounts. + type Submitter = AccountId; + /// Successful verifications return the verification key used to validate + /// the provided signature and its relationship to the DID subject. + type VerificationResult = (DidVerificationKey, DidVerificationKeyRelationship); + + fn verify_proof_for_call_against_entry( + call: &Call, + _subject: &Subject, + submitter: &Self::Submitter, + proof_entry: &mut Self::IdentityDetails, + proof: &Self::Proof, + ) -> Result { + let block_number = BlockNumberProvider::get(); + let is_signature_fresh = + if let Some(blocks_ago_from_now) = block_number.checked_sub(&proof.did_signature.block_number) { + // False if the signature is too old. + blocks_ago_from_now.into() <= SIGNATURE_VALIDITY + } else { + // Signature generated at a future time, not possible to verify. + false + }; + ensure!(is_signature_fresh, ()); + let encoded_payload = ( + call, + &proof_entry.details, + submitter, + &proof.did_signature.block_number, + GenesisHashProvider::get(), + SignedExtraProvider::get(), + ) + .encode(); + // Only consider verification keys from the set of revealed Merkle leaves. + let mut proof_verification_keys = proof.merkle_entries.as_ref().iter().filter_map( + |ProofEntry { + key: DidPublicKeyDetails { key, .. }, + relationship, + }| { + if let DidPublicKey::PublicVerificationKey(k) = key { + Some(( + k, + DidVerificationKeyRelationship::try_from(*relationship).expect("Should never fail to build a VerificationRelationship from the given DidKeyRelationship because we have already made sure the conditions hold."), + )) + } else { + None + } + }, + ); + let valid_signing_key = proof_verification_keys.find(|(verification_key, _)| { + verification_key + .verify_signature(&encoded_payload, &proof.did_signature.signature) + .is_ok() + }); + if let Some((key, relationship)) = valid_signing_key { + proof_entry.details.bump(); + Ok((key.clone(), relationship)) + } else { + Err(()) + } + } +} + +/// A type that chains a DID signature verification, as provided by +/// `MerkleRevealedDidSignatureVerifier`, and a call filtering logic based on +/// the type of key used in the signature. +/// Verification bails out early in case of invalid DID signatures. Otherwise, +/// the retrieved key and its relationship is passed to the call verifier to do +/// some additional lookups on the call. +/// The `CallVerifier` only performs internal checks, while all input and output +/// types are taken from the provided `DidSignatureVerifier` type. +pub struct DidSignatureAndCallVerifier( + PhantomData<(DidSignatureVerifier, CallVerifier)>, +); + +impl IdentityProofVerifier + for DidSignatureAndCallVerifier +where + DidSignatureVerifier: IdentityProofVerifier, + CallVerifier: DidDipOriginFilter, +{ + // FIXME: Better error handling + type Error = (); + /// The input proof is the same accepted by the `DidSignatureVerifier`. + type Proof = DidSignatureVerifier::Proof; + /// The identity details are the same accepted by the + /// `DidSignatureVerifier`. + type IdentityDetails = DidSignatureVerifier::IdentityDetails; + /// The submitter address is the same accepted by the + /// `DidSignatureVerifier`. + type Submitter = DidSignatureVerifier::Submitter; + /// The verification result is the same accepted by the + /// `DidSignatureVerifier`. + type VerificationResult = DidSignatureVerifier::VerificationResult; + + fn verify_proof_for_call_against_entry( + call: &Call, + subject: &Subject, + submitter: &Self::Submitter, + proof_entry: &mut Self::IdentityDetails, + proof: &Self::Proof, + ) -> Result { + let did_signing_key = + DidSignatureVerifier::verify_proof_for_call_against_entry(call, subject, submitter, proof_entry, proof) + .map_err(|_| ())?; + CallVerifier::check_call_origin_info(call, &did_signing_key).map_err(|_| ())?; + Ok(did_signing_key) + } +} diff --git a/crates/kilt-dip-support/src/lib.rs b/crates/kilt-dip-support/src/lib.rs new file mode 100644 index 000000000..c2f18ba0f --- /dev/null +++ b/crates/kilt-dip-support/src/lib.rs @@ -0,0 +1,95 @@ +// 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 + +// TODO: Crate documentation + +#![cfg_attr(not(feature = "std"), no_std)] + +use pallet_dip_consumer::traits::IdentityProofVerifier; +use sp_std::marker::PhantomData; + +use crate::did::MerkleEntriesAndDidSignature; + +pub mod did; +pub mod merkle; +pub mod traits; + +/// A type that chains a Merkle proof verification with a DID signature +/// verification. The required input of this type is a tuple (A, B) where A is +/// the type of input required by the `MerkleProofVerifier` and B is a +/// `DidSignature`. +/// The successful output of this type is the output type of the +/// `MerkleProofVerifier`, meaning that DID signature verification happens +/// internally and does not transform the result in any way. +pub struct MerkleProofAndDidSignatureVerifier( + PhantomData<(BlockNumber, MerkleProofVerifier, DidSignatureVerifier)>, +); + +impl IdentityProofVerifier + for MerkleProofAndDidSignatureVerifier +where + BlockNumber: Clone, + MerkleProofVerifier: IdentityProofVerifier, + // TODO: get rid of this if possible + MerkleProofVerifier::VerificationResult: Clone, + DidSignatureVerifier: IdentityProofVerifier< + Call, + Subject, + Proof = MerkleEntriesAndDidSignature, + IdentityDetails = MerkleProofVerifier::IdentityDetails, + Submitter = MerkleProofVerifier::Submitter, + >, +{ + // FIXME: Better error handling + type Error = (); + // FIXME: Better type declaration + type Proof = MerkleEntriesAndDidSignature; + type IdentityDetails = DidSignatureVerifier::IdentityDetails; + type Submitter = MerkleProofVerifier::Submitter; + type VerificationResult = MerkleProofVerifier::VerificationResult; + + fn verify_proof_for_call_against_entry( + call: &Call, + subject: &Subject, + submitter: &Self::Submitter, + proof_entry: &mut Self::IdentityDetails, + proof: &Self::Proof, + ) -> Result { + let merkle_proof_verification = MerkleProofVerifier::verify_proof_for_call_against_entry( + call, + subject, + submitter, + proof_entry, + &proof.merkle_entries, + ) + .map_err(|_| ())?; + DidSignatureVerifier::verify_proof_for_call_against_entry( + call, + subject, + submitter, + proof_entry, + // FIXME: Remove `clone()` requirement + &MerkleEntriesAndDidSignature { + merkle_entries: merkle_proof_verification.clone(), + did_signature: proof.did_signature.clone(), + }, + ) + .map_err(|_| ())?; + Ok(merkle_proof_verification) + } +} diff --git a/crates/kilt-dip-support/src/merkle.rs b/crates/kilt-dip-support/src/merkle.rs new file mode 100644 index 000000000..d86e84475 --- /dev/null +++ b/crates/kilt-dip-support/src/merkle.rs @@ -0,0 +1,251 @@ +// 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 frame_support::{traits::ConstU32, RuntimeDebug}; +use pallet_dip_consumer::{identity::IdentityDetails, traits::IdentityProofVerifier}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::BoundedVec; +use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, marker::PhantomData, vec::Vec}; +use sp_trie::{verify_trie_proof, LayoutV1}; + +pub type BlindedValue = Vec; + +#[derive(Encode, Decode, RuntimeDebug, Clone, Eq, PartialEq, TypeInfo, Default)] +pub struct MerkleProof { + pub blinded: BlindedValue, + // TODO: Probably replace with a different data structure for better lookup capabilities + pub revealed: Vec, +} + +#[derive(Clone, Copy, RuntimeDebug, Encode, Decode, PartialEq, Eq, TypeInfo, PartialOrd, Ord, MaxEncodedLen)] +pub enum DidKeyRelationship { + Encryption, + Verification(DidVerificationKeyRelationship), +} + +impl From for DidKeyRelationship { + fn from(value: DidVerificationKeyRelationship) -> Self { + Self::Verification(value) + } +} + +impl TryFrom for DidVerificationKeyRelationship { + // TODO: Error handling + type Error = (); + + fn try_from(value: DidKeyRelationship) -> Result { + if let DidKeyRelationship::Verification(rel) = value { + Ok(rel) + } else { + Err(()) + } + } +} + +#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] +pub struct KeyReferenceKey(pub KeyId, pub DidKeyRelationship); +#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] +pub struct KeyReferenceValue; +#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] +pub struct KeyDetailsKey(pub KeyId); +#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] +pub struct KeyDetailsValue(pub DidPublicKeyDetails); + +#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] +pub enum ProofLeaf { + // The key and value for the leaves of a merkle proof that contain a reference + // (by ID) to the key details, provided in a separate leaf. + KeyReference(KeyReferenceKey, KeyReferenceValue), + // The key and value for the leaves of a merkle proof that contain the actual + // details of a DID public key. The key is the ID of the key, and the value is its details, including creation + // block number. + KeyDetails(KeyDetailsKey, KeyDetailsValue), +} + +impl ProofLeaf +where + KeyId: Encode, +{ + pub fn encoded_key(&self) -> Vec { + match self { + ProofLeaf::KeyReference(key, _) => key.encode(), + ProofLeaf::KeyDetails(key, _) => key.encode(), + } + } +} + +impl ProofLeaf +where + BlockNumber: Encode, +{ + pub fn encoded_value(&self) -> Vec { + match self { + ProofLeaf::KeyReference(_, value) => value.encode(), + ProofLeaf::KeyDetails(_, value) => value.encode(), + } + } +} + +// TODO: Avoid repetition of the same key if it appears multiple times, e.g., by +// having a vector of `DidKeyRelationship` instead. +#[derive(Clone, RuntimeDebug, PartialEq, Eq, TypeInfo, MaxEncodedLen, Encode, Decode)] +pub struct ProofEntry { + pub key: DidPublicKeyDetails, + pub relationship: DidKeyRelationship, +} + +#[cfg(feature = "runtime-benchmarks")] +impl Default for ProofEntry +where + BlockNumber: Default, +{ + fn default() -> Self { + Self { + key: DidPublicKeyDetails { + key: did::did_details::DidPublicKey::PublicEncryptionKey(did::did_details::DidEncryptionKey::X25519( + [0u8; 32], + )), + block_number: BlockNumber::default(), + }, + relationship: DidVerificationKeyRelationship::Authentication.into(), + } + } +} + +// Contains the list of revealed public keys after a given Merkle proof has been +// correctly verified. +#[derive(Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen, Encode, Decode, Default)] +pub struct VerificationResult( + pub BoundedVec, ConstU32>, +); + +impl TryFrom>> + for VerificationResult +{ + // TODO: Better error handling + type Error = (); + + fn try_from(value: Vec>) -> Result { + let bounded_inner = value.try_into().map_err(|_| ())?; + Ok(Self(bounded_inner)) + } +} + +impl AsRef<[ProofEntry]> + for VerificationResult +{ + fn as_ref(&self) -> &[ProofEntry] { + self.0.as_ref() + } +} + +/// A type that verifies a Merkle proof that reveals some leaves representing +/// keys in a DID Document. +/// Can also be used on its own, without any DID signature verification. +pub struct DidMerkleProofVerifier( + PhantomData<( + Hasher, + AccountId, + KeyId, + BlockNumber, + Details, + ConstU32, + )>, +); + +impl + IdentityProofVerifier + for DidMerkleProofVerifier +where + // TODO: Remove `Debug` bound + BlockNumber: Encode + Clone + Debug, + Hasher: sp_core::Hasher, + KeyId: Encode + Clone + Ord + Into, +{ + // TODO: Proper error handling + type Error = (); + type Proof = MerkleProof>, ProofLeaf>; + type IdentityDetails = IdentityDetails; + type Submitter = AccountId; + type VerificationResult = VerificationResult; + + fn verify_proof_for_call_against_entry( + _call: &Call, + _subject: &Subject, + _submitter: &Self::Submitter, + proof_entry: &mut Self::IdentityDetails, + proof: &Self::Proof, + ) -> Result { + // TODO: more efficient by removing cloning and/or collecting. + // Did not find another way of mapping a Vec<(Vec, Vec)> to a + // Vec<(Vec, Option>)>. + let proof_leaves = proof + .revealed + .iter() + .map(|leaf| (leaf.encoded_key(), Some(leaf.encoded_value()))) + .collect::, Option>)>>(); + verify_trie_proof::, _, _, _>( + &proof_entry.digest.clone().into(), + &proof.blinded, + &proof_leaves, + ) + .map_err(|_| ())?; + + // At this point, we know the proof is valid. We just need to map the revealed + // leaves to something the consumer can easily operate on. + + // Create a map of the revealed public keys + //TODO: Avoid cloning, and use a map of references for the lookup + let public_keys: BTreeMap> = proof + .revealed + .clone() + .into_iter() + .filter_map(|leaf| { + if let ProofLeaf::KeyDetails(KeyDetailsKey(key_id), KeyDetailsValue(key_details)) = leaf { + Some((key_id, key_details)) + } else { + None + } + }) + .collect(); + // Create a list of the revealed keys by consuming the provided key reference + // leaves, and looking up the full details from the just-built `public_keys` + // map. + let keys: Vec> = proof + .revealed + .iter() + .filter_map(|leaf| { + if let ProofLeaf::KeyReference(KeyReferenceKey(key_id, key_relationship), _) = leaf { + // TODO: Better error handling. + let key_details = public_keys + .get(key_id) + .expect("Key ID should be present in the map of revealed public keys."); + Some(ProofEntry { + key: key_details.clone(), + relationship: *key_relationship, + }) + } else { + None + } + }) + .collect(); + keys.try_into() + } +} diff --git a/crates/kilt-dip-support/src/traits.rs b/crates/kilt-dip-support/src/traits.rs new file mode 100644 index 000000000..56d4dd09b --- /dev/null +++ b/crates/kilt-dip-support/src/traits.rs @@ -0,0 +1,78 @@ +// 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 sp_core::Get; +use sp_runtime::traits::{CheckedAdd, One, Zero}; +use sp_std::marker::PhantomData; + +// TODO: Switch to the `Incrementable` trait once it's added to the root of +// `frame_support`. +/// A trait for "bumpable" types, i.e., types that have some notion of order of +/// its members. +pub trait Bump { + /// Bump the type instance to its next value. Overflows are assumed to be + /// taken care of by the type internal logic. + fn bump(&mut self); +} + +impl Bump for T +where + T: CheckedAdd + Zero + One, +{ + // FIXME: Better implementation? + fn bump(&mut self) { + *self = self.checked_add(&Self::one()).unwrap_or_else(Self::zero); + } +} + +/// A trait for types that implement some sort of access control logic on the +/// provided input `Call` type. +pub trait DidDipOriginFilter { + /// The error type for cases where the checks fail. + type Error; + /// The type of additional information required by the type to perform the + /// checks on the `Call` input. + type OriginInfo; + /// The success type for cases where the checks succeed. + type Success; + + fn check_call_origin_info(call: &Call, info: &Self::OriginInfo) -> Result; +} + +pub struct GenesisProvider(PhantomData); + +impl Get for GenesisProvider +where + T: frame_system::Config, + T::BlockNumber: Zero, +{ + fn get() -> T::Hash { + frame_system::Pallet::::block_hash(T::BlockNumber::zero()) + } +} + +pub struct BlockNumberProvider(PhantomData); + +impl Get for BlockNumberProvider +where + T: frame_system::Config, +{ + fn get() -> T::BlockNumber { + frame_system::Pallet::::block_number() + } +} diff --git a/dip-template/runtimes/dip-consumer/Cargo.toml b/dip-template/runtimes/dip-consumer/Cargo.toml index 2073d9d27..de8f00d27 100644 --- a/dip-template/runtimes/dip-consumer/Cargo.toml +++ b/dip-template/runtimes/dip-consumer/Cargo.toml @@ -18,7 +18,9 @@ parity-scale-codec = {workspace = true, features = ["derive"]} scale-info = {workspace = true, features = ["derive"]} # DIP +dip-support.workspace = true did.workspace = true +kilt-dip-support.workspace = true pallet-did-lookup.workspace = true pallet-dip-consumer.workspace = true runtime-common.workspace = true @@ -75,7 +77,9 @@ default = [ std = [ "parity-scale-codec/std", "scale-info/std", + "dip-support/std", "did/std", + "kilt-dip-support/std", "pallet-did-lookup/std", "pallet-dip-consumer/std", "runtime-common/std", diff --git a/dip-template/runtimes/dip-consumer/src/dip.rs b/dip-template/runtimes/dip-consumer/src/dip.rs index 77fc5e79c..639ff16e6 100644 --- a/dip-template/runtimes/dip-consumer/src/dip.rs +++ b/dip-template/runtimes/dip-consumer/src/dip.rs @@ -16,34 +16,71 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -use did::DidVerificationKeyRelationship; -use pallet_dip_consumer::traits::DipCallOriginFilter; -use runtime_common::dip::{ - consumer::{DidMerkleProofVerifier, VerificationResult}, - ProofLeaf, +use did::{did_details::DidVerificationKey, DidVerificationKeyRelationship, KeyIdOf}; +use frame_support::traits::Contains; +use kilt_dip_support::{ + did::{DidSignatureAndCallVerifier, MerkleEntriesAndDidSignature, MerkleRevealedDidSignatureVerifier}, + merkle::{DidMerkleProofVerifier, MerkleProof, ProofLeaf}, + traits::{BlockNumberProvider, DidDipOriginFilter, GenesisProvider}, + MerkleProofAndDidSignatureVerifier, }; +use pallet_dip_consumer::traits::IdentityProofVerifier; use sp_std::vec::Vec; -use crate::{BlockNumber, DidIdentifier, Hash, Hasher, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin}; +use crate::{AccountId, BlockNumber, DidIdentifier, Hash, Hasher, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin}; + +pub type MerkleProofVerifier = DidMerkleProofVerifier, BlockNumber, u128, 10>; +pub type MerkleProofVerifierOutputOf = + >::VerificationResult; +pub type MerkleDidSignatureVerifierOf = MerkleRevealedDidSignatureVerifier< + BlockNumber, + Hash, + u128, + AccountId, + MerkleProofVerifierOutputOf, + BlockNumberProvider, + // Signatures are valid for 50 blocks + 50, + GenesisProvider, + Hash, +>; impl pallet_dip_consumer::Config for Runtime { - type BlindedValue = Vec>; - type DipCallOriginFilter = DipCallFilter; + type DipCallOriginFilter = PreliminaryDipOriginFilter; type Identifier = DidIdentifier; - type ProofLeaf = ProofLeaf; + type IdentityDetails = u128; + type Proof = MerkleEntriesAndDidSignature>, ProofLeaf>, BlockNumber>; type ProofDigest = Hash; - type ProofVerifier = DidMerkleProofVerifier; + type ProofVerifier = MerkleProofAndDidSignatureVerifier< + BlockNumber, + MerkleProofVerifier, + DidSignatureAndCallVerifier, DipCallFilter>, + >; type RuntimeCall = RuntimeCall; type RuntimeEvent = RuntimeEvent; type RuntimeOrigin = RuntimeOrigin; } +pub struct PreliminaryDipOriginFilter; + +impl Contains for PreliminaryDipOriginFilter { + fn contains(t: &RuntimeCall) -> bool { + matches!( + t, + RuntimeCall::DidLookup { .. } + | RuntimeCall::Utility(pallet_utility::Call::batch { .. }) + | RuntimeCall::Utility(pallet_utility::Call::batch_all { .. }) + | RuntimeCall::Utility(pallet_utility::Call::force_batch { .. }) + ) + } +} + fn derive_verification_key_relationship(call: &RuntimeCall) -> Option { match call { RuntimeCall::DidLookup { .. } => Some(DidVerificationKeyRelationship::Authentication), - RuntimeCall::Utility(pallet_utility::Call::batch { calls }) => single_key_relationship(calls).ok(), - RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => single_key_relationship(calls).ok(), - RuntimeCall::Utility(pallet_utility::Call::force_batch { calls }) => single_key_relationship(calls).ok(), + RuntimeCall::Utility(pallet_utility::Call::batch { calls }) => single_key_relationship(calls.iter()).ok(), + RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => single_key_relationship(calls.iter()).ok(), + RuntimeCall::Utility(pallet_utility::Call::force_batch { calls }) => single_key_relationship(calls.iter()).ok(), _ => None, } } @@ -51,11 +88,15 @@ fn derive_verification_key_relationship(call: &RuntimeCall) -> Option Result { - let first_call_relationship = calls.get(0).and_then(derive_verification_key_relationship).ok_or(())?; +fn single_key_relationship<'a>( + calls: impl Iterator, +) -> Result { + let mut calls = calls.peekable(); + let first_call_relationship = calls + .peek() + .and_then(|k| derive_verification_key_relationship(k)) + .ok_or(())?; calls - .iter() - .skip(1) .map(derive_verification_key_relationship) .try_fold(first_call_relationship, |acc, next| { if next == Some(acc) { @@ -68,15 +109,15 @@ fn single_key_relationship(calls: &[RuntimeCall]) -> Result for DipCallFilter { +impl DidDipOriginFilter for DipCallFilter { type Error = (); - type Proof = VerificationResult; + type OriginInfo = (DidVerificationKey, DidVerificationKeyRelationship); type Success = (); // Accepts only a DipOrigin for the DidLookup pallet calls. - fn check_proof(call: RuntimeCall, proof: Self::Proof) -> Result { - let key_relationship = single_key_relationship(&[call])?; - if proof.0.iter().any(|l| l.relationship == key_relationship.into()) { + fn check_call_origin_info(call: &RuntimeCall, info: &Self::OriginInfo) -> Result { + let key_relationship = single_key_relationship([call].into_iter())?; + if info.1 == key_relationship { Ok(()) } else { Err(()) @@ -95,21 +136,21 @@ mod dip_call_origin_filter_tests { // Can call DidLookup functions with an authentication key let did_lookup_call = RuntimeCall::DidLookup(pallet_did_lookup::Call::associate_sender {}); assert_eq!( - single_key_relationship(&[did_lookup_call]), + single_key_relationship(vec![did_lookup_call].iter()), Ok(DidVerificationKeyRelationship::Authentication) ); // Can't call System functions with a DID key (hence a DIP origin) let system_call = RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); - assert_err!(single_key_relationship(&[system_call]), ()); + assert_err!(single_key_relationship(vec![system_call].iter()), ()); // Can't call empty batch with a DID key let empty_batch_call = RuntimeCall::Utility(pallet_utility::Call::batch_all { calls: vec![] }); - assert_err!(single_key_relationship(&[empty_batch_call]), ()); + assert_err!(single_key_relationship(vec![empty_batch_call].iter()), ()); // Can call batch with a DipLookup with an authentication key let did_lookup_batch_call = RuntimeCall::Utility(pallet_utility::Call::batch_all { calls: vec![pallet_did_lookup::Call::associate_sender {}.into()], }); assert_eq!( - single_key_relationship(&[did_lookup_batch_call]), + single_key_relationship(vec![did_lookup_batch_call].iter()), Ok(DidVerificationKeyRelationship::Authentication) ); // Can't call a batch with different required keys @@ -121,6 +162,6 @@ mod dip_call_origin_filter_tests { frame_system::Call::remark { remark: vec![] }.into(), ], }); - assert_err!(single_key_relationship(&[did_lookup_batch_call]), ()); + assert_err!(single_key_relationship(vec![did_lookup_batch_call].iter()), ()); } } diff --git a/dip-template/runtimes/dip-consumer/src/lib.rs b/dip-template/runtimes/dip-consumer/src/lib.rs index 955a43885..602e4af53 100644 --- a/dip-template/runtimes/dip-consumer/src/lib.rs +++ b/dip-template/runtimes/dip-consumer/src/lib.rs @@ -22,6 +22,7 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +use kilt_dip_support::merkle::VerificationResult; pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; pub use sp_runtime::{MultiAddress, Perbill, Permill}; @@ -366,8 +367,8 @@ impl pallet_did_lookup::Config for Runtime { type Currency = Balances; type Deposit = ConstU128; type DidIdentifier = DidIdentifier; - type EnsureOrigin = EnsureDipOrigin; - type OriginSuccess = DipOrigin; + type EnsureOrigin = EnsureDipOrigin>; + type OriginSuccess = DipOrigin>; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); } diff --git a/dip-template/runtimes/dip-provider/src/dip.rs b/dip-template/runtimes/dip-provider/src/dip.rs index 6fa0d577b..e358eff8a 100644 --- a/dip-template/runtimes/dip-provider/src/dip.rs +++ b/dip-template/runtimes/dip-provider/src/dip.rs @@ -17,10 +17,10 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org use did::did_details::DidDetails; -use dip_support::VersionedIdentityProofAction; +use dip_support::IdentityProofAction; use pallet_dip_provider::traits::{TxBuilder, XcmRouterDispatcher}; use parity_scale_codec::{Decode, Encode}; -use runtime_common::dip::provider::{DidIdentityProvider, DidMerkleRootGenerator}; +use runtime_common::dip::{did::DidIdentityProvider, merkle::DidMerkleRootGenerator}; use xcm::{latest::MultiLocation, DoubleEncoded}; use crate::{DidIdentifier, Hash, Runtime, RuntimeEvent, XcmRouter}; @@ -34,7 +34,7 @@ enum ConsumerParachainCalls { #[derive(Encode, Decode)] enum ConsumerParachainDipConsumerCalls { #[codec(index = 0)] - ProcessIdentityAction(VersionedIdentityProofAction), + ProcessIdentityAction(IdentityProofAction), } pub struct ConsumerParachainTxBuilder; @@ -43,7 +43,7 @@ impl TxBuilder for ConsumerParachainTxBuilder { fn build( _dest: MultiLocation, - action: VersionedIdentityProofAction, + action: IdentityProofAction, ) -> Result, Self::Error> { let double_encoded: DoubleEncoded<()> = ConsumerParachainCalls::DipConsumer(ConsumerParachainDipConsumerCalls::ProcessIdentityAction(action)) diff --git a/dip-template/runtimes/dip-provider/src/lib.rs b/dip-template/runtimes/dip-provider/src/lib.rs index d1db46563..b3766aa16 100644 --- a/dip-template/runtimes/dip-provider/src/lib.rs +++ b/dip-template/runtimes/dip-provider/src/lib.rs @@ -51,7 +51,7 @@ use pallet_collator_selection::IdentityCollator; use pallet_dip_provider::traits::IdentityProvider; use pallet_session::{FindAccountFromAuthorIndex, PeriodicSessions}; use pallet_transaction_payment::{CurrencyAdapter, FeeDetails, RuntimeDispatchInfo}; -use runtime_common::dip::provider::{CompleteMerkleProof, DidMerkleProof, DidMerkleRootGenerator}; +use runtime_common::dip::merkle::{CompleteMerkleProof, DidMerkleProofOf, DidMerkleRootGenerator}; use sp_api::impl_runtime_apis; use sp_consensus_aura::SlotDuration; use sp_core::{crypto::KeyTypeId, ConstU128, ConstU16, OpaqueMetadata}; @@ -530,8 +530,8 @@ impl_runtime_apis! { // TODO: `keys` could and should be a BTreeSet, but it makes it more complicated for clients to build the type. So we use a Vec, since the keys are deduplicated anyway at proof creation time. // TODO: Support generating different versions of the proof, based on the provided parameter - impl kilt_runtime_api_dip_provider::DipProvider, Vec>, CompleteMerkleProof>, ()> for Runtime { - fn generate_proof(identifier: DidIdentifier, keys: Vec>) -> Result>, ()> { + impl kilt_runtime_api_dip_provider::DipProvider, Vec>, CompleteMerkleProof>, ()> for Runtime { + fn generate_proof(identifier: DidIdentifier, keys: Vec>) -> Result>, ()> { if let Ok(Some((did_details, _))) = ::IdentityProvider::retrieve(&identifier) { DidMerkleRootGenerator::::generate_proof(&did_details, keys.iter()) } else { diff --git a/dip-template/runtimes/xcm-tests/Cargo.toml b/dip-template/runtimes/xcm-tests/Cargo.toml index 91e6ae3fb..6be31a5cb 100644 --- a/dip-template/runtimes/xcm-tests/Cargo.toml +++ b/dip-template/runtimes/xcm-tests/Cargo.toml @@ -15,9 +15,9 @@ cumulus-pallet-xcmp-queue = { workspace = true, features = ["std"] } did = { workspace = true, features = ["std"] } dip-consumer-runtime-template = { workspace = true, features = ["std"] } dip-provider-runtime-template = { workspace = true, features = ["std"] } -dip-support = { workspace = true, features = ["std"] } frame-support = { workspace = true, features = ["std"] } frame-system = { workspace = true, features = ["std"] } +kilt-dip-support = { workspace = true, features = ["std"] } kilt-support = { workspace = true, features = ["std"] } pallet-balances = { workspace = true, features = ["std"] } pallet-did-lookup = { workspace = true, features = ["std"] } diff --git a/dip-template/runtimes/xcm-tests/src/tests.rs b/dip-template/runtimes/xcm-tests/src/tests.rs index 9c04ad8ab..a868a64ea 100644 --- a/dip-template/runtimes/xcm-tests/src/tests.rs +++ b/dip-template/runtimes/xcm-tests/src/tests.rs @@ -18,13 +18,18 @@ use super::*; -use did::Did; -use dip_support::latest::Proof; +use did::{Did, DidSignature}; use frame_support::{assert_ok, weights::Weight}; use frame_system::RawOrigin; +use kilt_dip_support::{ + did::{MerkleEntriesAndDidSignature, TimeBoundDidSignature}, + merkle::MerkleProof, +}; use pallet_did_lookup::linkable_account::LinkableAccountId; -use runtime_common::dip::provider::{CompleteMerkleProof, DidMerkleRootGenerator}; +use parity_scale_codec::Encode; +use runtime_common::dip::merkle::{CompleteMerkleProof, DidMerkleRootGenerator}; use sp_core::Pair; +use sp_runtime::traits::Zero; use xcm::latest::{ Junction::Parachain, Junctions::{Here, X1}, @@ -34,7 +39,7 @@ use xcm_emulator::TestExt; use cumulus_pallet_xcmp_queue::Event as XcmpEvent; use dip_consumer_runtime_template::{ - DidIdentifier, DidLookup, DipConsumer, Runtime as ConsumerRuntime, RuntimeCall as ConsumerRuntimeCall, + BlockNumber, DidIdentifier, DidLookup, DipConsumer, Runtime as ConsumerRuntime, RuntimeCall as ConsumerRuntimeCall, RuntimeEvent, System, }; use dip_provider_runtime_template::{AccountId as ProviderAccountId, DipProvider, Runtime as ProviderRuntime}; @@ -69,34 +74,53 @@ fn commit_identity() { // 2.2 Verify the proof digest was stored correctly. assert!(DipConsumer::identity_proofs(&did).is_some()); }); - // 3. Call an extrinsic on the consumer chain with a valid proof + // 3. Call an extrinsic on the consumer chain with a valid proof and signature let did_details = ProviderParachain::execute_with(|| { Did::get(&did).expect("DID details should be stored on the provider chain.") }); + let call = ConsumerRuntimeCall::DidLookup(pallet_did_lookup::Call::::associate_sender {}); // 3.1 Generate a proof let CompleteMerkleProof { proof, .. } = DidMerkleRootGenerator::::generate_proof( &did_details, [did_details.authentication_key].iter(), ) .expect("Proof generation should not fail"); - // 3.2 Call the `dispatch_as` extrinsic on the consumer chain with the generated + // 3.2 Generate a DID signature + let genesis_hash = + ConsumerParachain::execute_with(|| frame_system::Pallet::::block_hash(BlockNumber::zero())); + let system_block = ConsumerParachain::execute_with(frame_system::Pallet::::block_number); + let payload = ( + call.clone(), + 0u128, + para::consumer::DISPATCHER_ACCOUNT, + system_block, + genesis_hash, + ); + let signature: DidSignature = para::provider::did_auth_key().sign(&payload.encode()).into(); + // 3.3 Call the `dispatch_as` extrinsic on the consumer chain with the generated // proof ConsumerParachain::execute_with(|| { assert_ok!(DipConsumer::dispatch_as( RawOrigin::Signed(para::consumer::DISPATCHER_ACCOUNT).into(), did.clone(), - Proof { - blinded: proof.blinded, - revealed: proof.revealed, - } - .into(), - Box::new(ConsumerRuntimeCall::DidLookup(pallet_did_lookup::Call::< - ConsumerRuntime, - >::associate_sender {})), + MerkleEntriesAndDidSignature { + merkle_entries: MerkleProof { + blinded: proof.blinded, + revealed: proof.revealed, + }, + did_signature: TimeBoundDidSignature { + signature, + block_number: system_block + } + }, + Box::new(call), )); // Verify the account -> DID link exists and contains the right information let linked_did = DidLookup::connected_dids::(para::consumer::DISPATCHER_ACCOUNT.into()) .map(|link| link.did); - assert_eq!(linked_did, Some(did)); + assert_eq!(linked_did, Some(did.clone())); + // Verify that the details of the DID subject have been bumped + let details = DipConsumer::identity_proofs(&did).map(|entry| entry.details); + assert_eq!(details, Some(1u128)); }); } diff --git a/pallets/pallet-dip-consumer/src/identity.rs b/pallets/pallet-dip-consumer/src/identity.rs new file mode 100644 index 000000000..eed39f68d --- /dev/null +++ b/pallets/pallet-dip-consumer/src/identity.rs @@ -0,0 +1,43 @@ +// 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::RuntimeDebug; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +/// The identity entry for any given user that uses the DIP protocol. +#[derive(Encode, Decode, MaxEncodedLen, Default, TypeInfo, RuntimeDebug)] +pub struct IdentityDetails { + /// The identity digest information, typically used to verify identity + /// proofs. + pub digest: Digest, + /// The details related to the user, stored in the pallet storage. + pub details: Details, +} + +impl From for IdentityDetails +where + Details: Default, +{ + fn from(value: Digest) -> Self { + Self { + digest: value, + details: Details::default(), + } + } +} diff --git a/pallets/pallet-dip-consumer/src/lib.rs b/pallets/pallet-dip-consumer/src/lib.rs index a351ae1b9..f9712bf5a 100644 --- a/pallets/pallet-dip-consumer/src/lib.rs +++ b/pallets/pallet-dip-consumer/src/lib.rs @@ -20,9 +20,11 @@ #![cfg_attr(not(feature = "std"), no_std)] -mod origin; +pub mod identity; pub mod traits; +mod origin; + pub use crate::{origin::*, pallet::*}; #[frame_support::pallet] @@ -30,40 +32,64 @@ pub mod pallet { use super::*; use cumulus_pallet_xcm::ensure_sibling_para; - use frame_support::{dispatch::Dispatchable, pallet_prelude::*, Twox64Concat}; + use frame_support::{dispatch::Dispatchable, pallet_prelude::*, traits::Contains, Twox64Concat}; use frame_system::pallet_prelude::*; + use parity_scale_codec::MaxEncodedLen; use sp_std::boxed::Box; - use dip_support::{latest::IdentityProofAction, VersionedIdentityProof, VersionedIdentityProofAction}; + use dip_support::IdentityProofAction; - use crate::traits::{DipCallOriginFilter, IdentityProofVerifier}; + use crate::{identity::IdentityDetails, traits::IdentityProofVerifier}; - pub type VerificationResultOf = <::ProofVerifier as IdentityProofVerifier>::VerificationResult; - pub type VersionedIdentityProofOf = - VersionedIdentityProof<::BlindedValue, ::ProofLeaf>; + pub type VerificationResultOf = <::ProofVerifier as IdentityProofVerifier< + ::RuntimeCall, + ::Identifier, + >>::VerificationResult; const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); // TODO: Store also additional details received by the provider. #[pallet::storage] #[pallet::getter(fn identity_proofs)] - pub(crate) type IdentityProofs = - StorageMap<_, Twox64Concat, ::Identifier, ::ProofDigest>; + pub(crate) type IdentityProofs = StorageMap< + _, + Twox64Concat, + ::Identifier, + IdentityDetails<::ProofDigest, ::IdentityDetails>, + >; #[pallet::config] pub trait Config: frame_system::Config { - type BlindedValue: Parameter; - type DipCallOriginFilter: DipCallOriginFilter<::RuntimeCall, Proof = VerificationResultOf>; + /// Preliminary filter to filter out calls before doing any heavier + /// computations. + type DipCallOriginFilter: Contains<::RuntimeCall>; + /// The identifier of a subject, e.g., a DID. type Identifier: Parameter + MaxEncodedLen; - type ProofLeaf: Parameter; + /// The details stored in this pallet associated with any given subject. + type IdentityDetails: Parameter + MaxEncodedLen + Default; + /// The proof users must provide to operate with their higher-level + /// identity. Depending on the use cases, this proof can contain + /// heterogeneous bits of information that the proof verifier will + /// utilize. For instance, a proof could contain both a Merkle proof and + /// a DID signature. + type Proof: Parameter; + /// The type of the committed proof digest used as the basis for + /// verifying identity proofs. type ProofDigest: Parameter + MaxEncodedLen; + /// The logic of the proof verifier, called upon each execution of the + /// `dispatch_as` extrinsic. type ProofVerifier: IdentityProofVerifier< - BlindedValue = Self::BlindedValue, - ProofDigest = Self::ProofDigest, - ProofLeaf = Self::ProofLeaf, + ::RuntimeCall, + Self::Identifier, + Proof = Self::Proof, + IdentityDetails = IdentityDetails, + Submitter = ::AccountId, >; + /// The overarching runtime call type. type RuntimeCall: Parameter + Dispatchable::RuntimeOrigin>; + /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The overarching runtime origin type. type RuntimeOrigin: From> + From<::RuntimeOrigin> + Into::RuntimeOrigin>>; @@ -77,26 +103,29 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { + /// The identity information related to a given subject has been + /// deleted. IdentityInfoDeleted(T::Identifier), + /// The identity information related to a given subject has been updated + /// to a new digest. IdentityInfoUpdated(T::Identifier, T::ProofDigest), } #[pallet::error] pub enum Error { - BadOrigin, - Dispatch, + /// An identity with the provided identifier could not be found. IdentityNotFound, + /// The identity proof provided could not be successfully verified. InvalidProof, - UnsupportedVersion, + /// The specified call could not be dispatched. + Dispatch, } - // The new origin other pallets can use. + /// The origin this pallet creates after a user has provided a valid + /// identity proof to dispatch other calls. #[pallet::origin] - pub type Origin = DipOrigin< - ::Identifier, - ::AccountId, - <::DipCallOriginFilter as DipCallOriginFilter<::RuntimeCall>>::Success, - >; + pub type Origin = + DipOrigin<::Identifier, ::AccountId, VerificationResultOf>; // TODO: Benchmarking #[pallet::call] @@ -105,20 +134,24 @@ pub mod pallet { #[pallet::weight(0)] pub fn process_identity_action( origin: OriginFor, - action: VersionedIdentityProofAction, + action: IdentityProofAction, ) -> DispatchResult { ensure_sibling_para(::RuntimeOrigin::from(origin))?; let event = match action { - VersionedIdentityProofAction::V1(IdentityProofAction::Updated(identifier, proof, _)) => { - IdentityProofs::::mutate(&identifier, |entry| *entry = Some(proof.clone())); + IdentityProofAction::Updated(identifier, proof, _) => { + IdentityProofs::::mutate( + &identifier, + |entry: &mut Option< + IdentityDetails<::ProofDigest, ::IdentityDetails>, + >| { *entry = Some(proof.clone().into()) }, + ); Ok::<_, Error>(Event::::IdentityInfoUpdated(identifier, proof)) } - VersionedIdentityProofAction::V1(IdentityProofAction::Deleted(identifier)) => { + IdentityProofAction::Deleted(identifier) => { IdentityProofs::::remove(&identifier); Ok::<_, Error>(Event::::IdentityInfoDeleted(identifier)) } - _ => Err(Error::::UnsupportedVersion), }?; Self::deposit_event(event); @@ -132,22 +165,28 @@ pub mod pallet { pub fn dispatch_as( origin: OriginFor, identifier: T::Identifier, - proof: VersionedIdentityProofOf, + proof: T::Proof, call: Box<::RuntimeCall>, ) -> DispatchResult { let submitter = ensure_signed(origin)?; - let proof_digest = IdentityProofs::::get(&identifier).ok_or(Error::::IdentityNotFound)?; - let proof_verification_result = T::ProofVerifier::verify_proof_against_digest(proof, proof_digest) - .map_err(|_| Error::::InvalidProof)?; - // TODO: Better error handling - // TODO: Avoid cloning `call` - let proof_result = T::DipCallOriginFilter::check_proof(*call.clone(), proof_verification_result) - .map_err(|_| Error::::BadOrigin)?; - // TODO: Proper DID signature verification (and cross-chain replay protection) + // TODO: Proper error handling + ensure!(T::DipCallOriginFilter::contains(&*call), Error::::Dispatch); + let mut proof_entry = IdentityProofs::::get(&identifier).ok_or(Error::::IdentityNotFound)?; + let proof_verification_result = T::ProofVerifier::verify_proof_for_call_against_entry( + &*call, + &identifier, + &submitter, + &mut proof_entry, + &proof, + ) + .map_err(|_| Error::::InvalidProof)?; + // Write the identity info to storage after it has optionally been updated by + // the `ProofVerifier`. + IdentityProofs::::mutate(&identifier, |entry| *entry = Some(proof_entry)); let did_origin = DipOrigin { identifier, account_address: submitter, - details: proof_result, + details: proof_verification_result, }; // TODO: Use dispatch info for weight calculation let _ = call.dispatch(did_origin.into()).map_err(|_| Error::::Dispatch)?; diff --git a/pallets/pallet-dip-consumer/src/traits.rs b/pallets/pallet-dip-consumer/src/traits.rs index c134a038f..7f4645f16 100644 --- a/pallets/pallet-dip-consumer/src/traits.rs +++ b/pallets/pallet-dip-consumer/src/traits.rs @@ -16,45 +16,42 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -use dip_support::VersionedIdentityProof; use sp_std::marker::PhantomData; -pub trait IdentityProofVerifier { - type BlindedValue; +pub trait IdentityProofVerifier { type Error; - type ProofDigest; - type ProofLeaf; + type Proof; + type IdentityDetails; + type Submitter; type VerificationResult; - fn verify_proof_against_digest( - proof: VersionedIdentityProof, - digest: Self::ProofDigest, + fn verify_proof_for_call_against_entry( + call: &Call, + subject: &Subject, + submitter: &Self::Submitter, + proof_entry: &mut Self::IdentityDetails, + proof: &Self::Proof, ) -> Result; } // Always returns success. -pub struct SuccessfulProofVerifier(PhantomData<(ProofDigest, Leaf, BlindedValue)>); -impl IdentityProofVerifier - for SuccessfulProofVerifier +pub struct SuccessfulProofVerifier(PhantomData<(Proof, ProofEntry, Submitter)>); +impl IdentityProofVerifier + for SuccessfulProofVerifier { - type BlindedValue = BlindedValue; type Error = (); - type ProofDigest = ProofDigest; - type ProofLeaf = Leaf; + type Proof = Proof; + type IdentityDetails = ProofEntry; + type Submitter = Submitter; type VerificationResult = (); - fn verify_proof_against_digest( - _proof: VersionedIdentityProof, - _digest: Self::ProofDigest, + fn verify_proof_for_call_against_entry( + _call: &Call, + _subject: &Subject, + _submitter: &Self::Submitter, + _proof_entry: &mut Self::IdentityDetails, + _proof: &Self::Proof, ) -> Result { Ok(()) } } - -pub trait DipCallOriginFilter { - type Error; - type Proof; - type Success; - - fn check_proof(call: Call, proof: Self::Proof) -> Result; -} diff --git a/pallets/pallet-dip-provider/src/lib.rs b/pallets/pallet-dip-provider/src/lib.rs index 4af958ccd..c826d191a 100644 --- a/pallets/pallet-dip-provider/src/lib.rs +++ b/pallets/pallet-dip-provider/src/lib.rs @@ -33,13 +33,11 @@ pub mod pallet { use sp_std::{boxed::Box, fmt::Debug}; use xcm::{latest::prelude::*, VersionedMultiAsset, VersionedMultiLocation}; - use dip_support::{v1::IdentityProofAction, VersionedIdentityProofAction}; + use dip_support::IdentityProofAction; use crate::traits::{IdentityProofDispatcher, IdentityProofGenerator, IdentityProvider, TxBuilder}; pub type IdentityProofActionOf = IdentityProofAction<::Identifier, ::ProofOutput>; - pub type VersionedIdentityProofActionOf = - VersionedIdentityProofAction<::Identifier, ::ProofOutput>; const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); @@ -72,7 +70,7 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - IdentityInfoDispatched(VersionedIdentityProofActionOf, Box), + IdentityInfoDispatched(IdentityProofActionOf, Box), } #[pallet::error] @@ -110,23 +108,18 @@ pub mod pallet { Err(_) => Err(Error::::IdentityNotFound), }?; // TODO: Add correct version creation based on lookup (?) - let versioned_action = VersionedIdentityProofAction::V1(action); let asset: MultiAsset = (*asset).try_into().map_err(|_| Error::::BadVersion)?; - let (ticket, _) = T::IdentityProofDispatcher::pre_dispatch::( - versioned_action.clone(), - asset, - weight, - destination, - ) - .map_err(|_| Error::::Predispatch)?; + let (ticket, _) = + T::IdentityProofDispatcher::pre_dispatch::(action.clone(), asset, weight, destination) + .map_err(|_| Error::::Predispatch)?; // TODO: Use returned asset of `pre_dispatch` to charge the tx submitter for the // fee, in addition to the cost on the target chain. T::IdentityProofDispatcher::dispatch(ticket).map_err(|_| Error::::Dispatch)?; - Self::deposit_event(Event::IdentityInfoDispatched(versioned_action, Box::new(destination))); + Self::deposit_event(Event::IdentityInfoDispatched(action, Box::new(destination))); Ok(()) } } diff --git a/pallets/pallet-dip-provider/src/traits.rs b/pallets/pallet-dip-provider/src/traits.rs index a589fa3b1..fd9b90d04 100644 --- a/pallets/pallet-dip-provider/src/traits.rs +++ b/pallets/pallet-dip-provider/src/traits.rs @@ -16,7 +16,7 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -use dip_support::VersionedIdentityProofAction; +use dip_support::IdentityProofAction; use xcm::{latest::prelude::*, DoubleEncoded}; pub use identity_generation::*; @@ -61,7 +61,7 @@ pub mod identity_dispatch { type Error; fn pre_dispatch>( - action: VersionedIdentityProofAction, + action: IdentityProofAction, asset: MultiAsset, weight: Weight, destination: MultiLocation, @@ -80,7 +80,7 @@ pub mod identity_dispatch { type Error = (); fn pre_dispatch<_B>( - _action: VersionedIdentityProofAction, + _action: IdentityProofAction, _asset: MultiAsset, _weight: Weight, _destination: MultiLocation, @@ -111,7 +111,7 @@ pub mod identity_dispatch { type Error = SendError; fn pre_dispatch>( - action: VersionedIdentityProofAction, + action: IdentityProofAction, asset: MultiAsset, weight: Weight, destination: MultiLocation, @@ -190,6 +190,6 @@ pub trait TxBuilder { fn build( dest: MultiLocation, - action: VersionedIdentityProofAction, + action: IdentityProofAction, ) -> Result, Self::Error>; } diff --git a/runtimes/common/Cargo.toml b/runtimes/common/Cargo.toml index 6d6284722..4b1e03c0a 100644 --- a/runtimes/common/Cargo.toml +++ b/runtimes/common/Cargo.toml @@ -26,7 +26,7 @@ attestation.workspace = true ctype.workspace = true delegation = {workspace = true, optional = true} did.workspace = true -dip-support.workspace = true +kilt-dip-support.workspace = true pallet-did-lookup = {workspace = true, optional = true} pallet-dip-consumer.workspace = true pallet-dip-provider.workspace = true @@ -84,10 +84,10 @@ std = [ "ctype/std", "cumulus-primitives-core/std", "did/std", - "dip-support/std", "frame-support/std", "frame-system/std", "kilt-asset-dids/std", + "kilt-dip-support/std", "kilt-support/std", "log/std", "pallet-authorship/std", diff --git a/runtimes/common/src/dip/consumer.rs b/runtimes/common/src/dip/consumer.rs deleted file mode 100644 index 8c6c49285..000000000 --- a/runtimes/common/src/dip/consumer.rs +++ /dev/null @@ -1,118 +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 - -use did::did_details::DidPublicKeyDetails; -use dip_support::{v1, VersionedIdentityProof}; -use frame_support::RuntimeDebug; -use pallet_dip_consumer::traits::IdentityProofVerifier; -use parity_scale_codec::Encode; -use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, vec::Vec}; -use sp_trie::{verify_trie_proof, LayoutV1}; - -use crate::dip::{provider, KeyDetailsKey, KeyDetailsValue, KeyReferenceKey, KeyRelationship, ProofLeaf}; - -// TODO: Avoid repetition of the same key if it appears multiple times, e.g., by -// having a vector of `KeyRelationship` instead. -#[derive(RuntimeDebug, PartialEq, Eq)] -pub struct ProofEntry { - pub key: DidPublicKeyDetails, - pub relationship: KeyRelationship, -} - -// Contains the list of revealed public keys after a given merkle proof has been -// correctly verified. -#[derive(RuntimeDebug, PartialEq, Eq)] -pub struct VerificationResult(pub Vec>); - -impl From>> for VerificationResult { - fn from(value: Vec>) -> Self { - Self(value) - } -} - -pub struct DidMerkleProofVerifier(PhantomData<(KeyId, BlockNumber, Hasher)>); - -impl IdentityProofVerifier for DidMerkleProofVerifier -where - KeyId: Encode + Clone + Ord, - BlockNumber: Encode + Clone + Ord, - Hasher: sp_core::Hasher, -{ - type BlindedValue = Vec; - // TODO: Proper error handling - type Error = (); - type ProofDigest = ::Out; - type ProofLeaf = ProofLeaf; - type VerificationResult = VerificationResult; - - fn verify_proof_against_digest( - proof: VersionedIdentityProof, - digest: Self::ProofDigest, - ) -> Result { - let proof: v1::Proof<_, _> = proof.try_into()?; - // TODO: more efficient by removing cloning and/or collecting. - // Did not find another way of mapping a Vec<(Vec, Vec)> to a - // Vec<(Vec, Option>)>. - let proof_leaves = proof - .revealed - .iter() - .map(|leaf| (leaf.encoded_key(), Some(leaf.encoded_value()))) - .collect::, Option>)>>(); - verify_trie_proof::, _, _, _>(&digest, &proof.blinded, &proof_leaves).map_err(|_| ())?; - - // At this point, we know the proof is valid. We just need to map the revealed - // leaves to something the consumer can easily operate on. - - // Create a map of the revealed public keys - //TODO: Avoid cloning, and use a map of references for the lookup - let public_keys: BTreeMap> = proof - .revealed - .clone() - .into_iter() - .filter_map(|leaf| { - if let ProofLeaf::KeyDetails(KeyDetailsKey(key_id), KeyDetailsValue(key_details)) = leaf { - Some((key_id, key_details)) - } else { - None - } - }) - .collect(); - // Create a list of the revealed keys by consuming the provided key reference - // leaves, and looking up the full details from the just-built `public_keys` - // map. - let keys: Vec> = proof - .revealed - .into_iter() - .filter_map(|leaf| { - if let ProofLeaf::KeyReference(KeyReferenceKey(key_id, key_relationship), _) = leaf { - // TODO: Better error handling. - let key_details = public_keys - .get(&key_id) - .expect("Key ID should be present in the map of revealed public keys."); - Some(ProofEntry { - key: key_details.clone(), - relationship: key_relationship, - }) - } else { - None - } - }) - .collect(); - Ok(keys.into()) - } -} diff --git a/runtimes/common/src/dip/did.rs b/runtimes/common/src/dip/did.rs new file mode 100644 index 000000000..e954f008e --- /dev/null +++ b/runtimes/common/src/dip/did.rs @@ -0,0 +1,42 @@ +// 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::DidDetails; +use pallet_dip_provider::traits::IdentityProvider; +use sp_std::marker::PhantomData; + +pub struct DidIdentityProvider(PhantomData); + +impl IdentityProvider, ()> for DidIdentityProvider +where + T: did::Config, +{ + // TODO: Proper error handling + type Error = (); + + fn retrieve(identifier: &T::DidIdentifier) -> Result, ())>, Self::Error> { + match ( + did::Pallet::::get_did(identifier), + did::Pallet::::get_deleted_did(identifier), + ) { + (Some(details), _) => Ok(Some((details, ()))), + (_, Some(_)) => Ok(None), + _ => Err(()), + } + } +} diff --git a/runtimes/common/src/dip/provider.rs b/runtimes/common/src/dip/merkle.rs similarity index 86% rename from runtimes/common/src/dip/provider.rs rename to runtimes/common/src/dip/merkle.rs index 0f555a199..f92cc9680 100644 --- a/runtimes/common/src/dip/provider.rs +++ b/runtimes/common/src/dip/merkle.rs @@ -17,19 +17,21 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org use did::{did_details::DidDetails, DidVerificationKeyRelationship, KeyIdOf}; -use dip_support::latest::Proof; use frame_support::RuntimeDebug; -use pallet_dip_provider::traits::{IdentityProofGenerator, IdentityProvider}; +use kilt_dip_support::merkle::MerkleProof; +use pallet_dip_provider::traits::IdentityProofGenerator; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_std::{borrow::ToOwned, collections::btree_set::BTreeSet, marker::PhantomData, vec::Vec}; use sp_trie::{generate_trie_proof, LayoutV1, MemoryDB, TrieDBMutBuilder, TrieHash, TrieMut}; -use crate::dip::{KeyDetailsKey, KeyDetailsValue, KeyReferenceKey, KeyReferenceValue, KeyRelationship, ProofLeaf}; +use kilt_dip_support::merkle::{ + DidKeyRelationship, KeyDetailsKey, KeyDetailsValue, KeyReferenceKey, KeyReferenceValue, ProofLeaf, +}; pub type BlindedValue = Vec; - -pub type DidMerkleProof = Proof, ProofLeaf, ::BlockNumber>>; +pub type DidMerkleProofOf = + MerkleProof, ProofLeaf, ::BlockNumber>>; #[derive(Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)] pub struct CompleteMerkleProof { @@ -95,7 +97,7 @@ where .iter() .try_for_each(|id| -> Result<(), ()> { let enc_leaf = ProofLeaf::<_, T::BlockNumber>::KeyReference( - KeyReferenceKey(*id, KeyRelationship::Encryption), + KeyReferenceKey(*id, DidKeyRelationship::Encryption), KeyReferenceValue, ); trie_builder @@ -127,7 +129,7 @@ where pub fn generate_proof<'a, K>( identity: &DidDetails, mut key_ids: K, - ) -> Result>, ()> + ) -> Result>, ()> where K: Iterator>, { @@ -159,7 +161,7 @@ where } if identity.key_agreement_keys.contains(key_id) { set.insert(ProofLeaf::KeyReference( - KeyReferenceKey(*key_id, KeyRelationship::Encryption), + KeyReferenceKey(*key_id, DidKeyRelationship::Encryption), KeyReferenceValue, )); }; @@ -177,7 +179,7 @@ where let proof = generate_trie_proof::, _, _, _>(&db, root, &encoded_keys).map_err(|_| ())?; Ok(CompleteMerkleProof { root, - proof: DidMerkleProof:: { + proof: DidMerkleProofOf:: { blinded: proof, revealed: leaves.into_iter().collect::>(), }, @@ -198,24 +200,3 @@ where Self::calculate_root_with_db(identity, &mut db) } } - -pub struct DidIdentityProvider(PhantomData); - -impl IdentityProvider, ()> for DidIdentityProvider -where - T: did::Config, -{ - // TODO: Proper error handling - type Error = (); - - fn retrieve(identifier: &T::DidIdentifier) -> Result, ())>, Self::Error> { - match ( - did::Pallet::::get_did(identifier), - did::Pallet::::get_deleted_did(identifier), - ) { - (Some(details), _) => Ok(Some((details, ()))), - (_, Some(_)) => Ok(None), - _ => Err(()), - } - } -} diff --git a/runtimes/common/src/dip/mod.rs b/runtimes/common/src/dip/mod.rs index 645f1ea4d..a3955297d 100644 --- a/runtimes/common/src/dip/mod.rs +++ b/runtimes/common/src/dip/mod.rs @@ -16,67 +16,5 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -use did::{did_details::DidPublicKeyDetails, DidVerificationKeyRelationship}; -use frame_support::RuntimeDebug; -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_std::vec::Vec; - -pub mod consumer; -pub mod provider; - -#[cfg(test)] -mod tests; - -#[derive(Clone, RuntimeDebug, Encode, Decode, PartialEq, Eq, TypeInfo, PartialOrd, Ord)] -pub enum KeyRelationship { - Encryption, - Verification(DidVerificationKeyRelationship), -} - -impl From for KeyRelationship { - fn from(value: DidVerificationKeyRelationship) -> Self { - Self::Verification(value) - } -} - -#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] -pub struct KeyReferenceKey(pub KeyId, pub KeyRelationship); -#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] -pub struct KeyReferenceValue; - -#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] -pub struct KeyDetailsKey(pub KeyId); -#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] -pub struct KeyDetailsValue(pub DidPublicKeyDetails); - -#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] -pub enum ProofLeaf { - // The key and value for the leaves of a merkle proof that contain a reference - // (by ID) to the key details, provided in a separate leaf. - KeyReference(KeyReferenceKey, KeyReferenceValue), - // The key and value for the leaves of a merkle proof that contain the actual - // details of a DID public key. The key is the ID of the key, and the value is its details, including creation - // block number. - KeyDetails(KeyDetailsKey, KeyDetailsValue), -} - -impl ProofLeaf -where - KeyId: Encode, - BlockNumber: Encode, -{ - pub(crate) fn encoded_key(&self) -> Vec { - match self { - ProofLeaf::KeyReference(key, _) => key.encode(), - ProofLeaf::KeyDetails(key, _) => key.encode(), - } - } - - pub(crate) fn encoded_value(&self) -> Vec { - match self { - ProofLeaf::KeyReference(_, value) => value.encode(), - ProofLeaf::KeyDetails(_, value) => value.encode(), - } - } -} +pub mod did; +pub mod merkle; diff --git a/runtimes/common/src/dip/tests.rs b/runtimes/common/src/dip/tests.rs deleted file mode 100644 index b1ce9f943..000000000 --- a/runtimes/common/src/dip/tests.rs +++ /dev/null @@ -1,315 +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 - -use did::{ - did_details::{DidCreationDetails, DidEncryptionKey}, - DidVerificationKeyRelationship, KeyIdOf, -}; -use dip_support::latest::Proof; -use frame_support::{ - assert_err, assert_ok, construct_runtime, parameter_types, traits::Everything, weights::constants::RocksDbWeight, -}; -use frame_system::{ - mocking::{MockBlock, MockUncheckedExtrinsic}, - EnsureSigned, RawOrigin, -}; -use pallet_dip_consumer::traits::IdentityProofVerifier; -use parity_scale_codec::Encode; -use sp_core::{ecdsa, ed25519, sr25519, ConstU16, ConstU32, ConstU64, Hasher, Pair}; -use sp_io::TestExternalities; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentifyAccount, IdentityLookup}, - AccountId32, -}; -use sp_std::collections::btree_set::BTreeSet; - -use crate::dip::{ - consumer::DidMerkleProofVerifier, - provider::{CompleteMerkleProof, DidMerkleRootGenerator}, - ProofLeaf, -}; - -pub(crate) type AccountId = AccountId32; -pub(crate) type Balance = u128; -pub(crate) type Block = MockBlock; -pub(crate) type BlockNumber = u64; -pub(crate) type Hashing = BlakeTwo256; -pub(crate) type Index = u64; -pub(crate) type UncheckedExtrinsic = MockUncheckedExtrinsic; - -construct_runtime!( - pub enum TestRuntime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system, - Balances: pallet_balances, - Did: did, - } -); - -impl frame_system::Config for TestRuntime { - type AccountData = pallet_balances::AccountData; - type AccountId = AccountId; - type BaseCallFilter = Everything; - type BlockHashCount = ConstU64<250>; - type BlockLength = (); - type BlockNumber = BlockNumber; - type BlockWeights = (); - type DbWeight = RocksDbWeight; - type Hash = ::Out; - type Hashing = Hashing; - type Header = Header; - type Index = Index; - type Lookup = IdentityLookup; - type MaxConsumers = ConstU32<16>; - type OnKilledAccount = (); - type OnNewAccount = (); - type OnSetCode = (); - type PalletInfo = PalletInfo; - type RuntimeCall = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type RuntimeOrigin = RuntimeOrigin; - type SS58Prefix = ConstU16<38>; - type SystemWeightInfo = (); - type Version = (); -} - -parameter_types! { - pub ExistentialDeposit: Balance = 500u64.into(); -} - -impl pallet_balances::Config for TestRuntime { - type AccountStore = System; - type Balance = Balance; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type MaxLocks = ConstU32<50>; - type MaxReserves = ConstU32<50>; - type ReserveIdentifier = [u8; 8]; - type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); -} - -parameter_types! { - pub Deposit: Balance = 500u64.into(); - pub Fee: Balance = 500u64.into(); - pub MaxBlocksTxValidity: BlockNumber = 10u64; - #[derive(Debug, Clone, Eq, PartialEq)] - pub const MaxTotalKeyAgreementKeys: u32 = 2; -} - -impl did::DeriveDidCallAuthorizationVerificationKeyRelationship for RuntimeCall { - fn derive_verification_key_relationship(&self) -> did::DeriveDidCallKeyRelationshipResult { - Ok(DidVerificationKeyRelationship::Authentication) - } - - #[cfg(feature = "runtime-benchmarks")] - fn get_call_for_did_call_benchmark() -> Self { - RuntimeCall::System(frame_system::Call::remark { remark: vec![] }) - } -} - -impl did::Config for TestRuntime { - type Currency = Balances; - type Deposit = Deposit; - type DidIdentifier = AccountId; - type EnsureOrigin = EnsureSigned; - type Fee = Fee; - type FeeCollector = (); - type MaxBlocksTxValidity = MaxBlocksTxValidity; - type MaxNewKeyAgreementKeys = ConstU32<2>; - type MaxNumberOfServicesPerDid = ConstU32<1>; - type MaxNumberOfTypesPerService = ConstU32<1>; - type MaxNumberOfUrlsPerService = ConstU32<1>; - type MaxPublicKeysPerDid = ConstU32<5>; - type MaxServiceIdLength = ConstU32<100>; - type MaxServiceTypeLength = ConstU32<100>; - type MaxServiceUrlLength = ConstU32<100>; - type MaxTotalKeyAgreementKeys = MaxTotalKeyAgreementKeys; - type OriginSuccess = AccountId; - type RuntimeCall = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type RuntimeOrigin = RuntimeOrigin; - type WeightInfo = (); -} - -fn base_ext() -> TestExternalities { - TestExternalities::new( - frame_system::GenesisConfig::default() - .build_storage::() - .unwrap(), - ) -} - -const ALICE: AccountId = AccountId::new([1u8; 32]); - -#[test] -fn minimal_did_merkle_proof() { - base_ext().execute_with(|| { - // Give Alice some balance - assert_ok!(Balances::set_balance(RawOrigin::Root.into(), ALICE, 1_000_000_000, 0)); - // Generate a DID for alice - let did_auth_key = ed25519::Pair::from_seed(&[100u8; 32]); - let did: AccountId = did_auth_key.public().into_account().into(); - let create_details = DidCreationDetails { - did: did.clone(), - submitter: ALICE, - new_attestation_key: None, - new_delegation_key: None, - new_key_agreement_keys: BTreeSet::new().try_into().unwrap(), - new_service_details: vec![], - }; - // Create Alice's DID with only authentication key - assert_ok!(Did::create( - RawOrigin::Signed(ALICE).into(), - Box::new(create_details.clone()), - did_auth_key.sign(&create_details.encode()).into() - )); - let did_details = Did::get_did(&did).expect("DID should be present"); - - // 1. Create the DID merkle proof revealing only the authentication key - let CompleteMerkleProof { root, proof } = DidMerkleRootGenerator::::generate_proof( - &did_details, - [did_details.authentication_key].iter(), - ) - .expect("Merkle proof generation should not fail."); - println!("{:?} - {:?} - {:?} bytes", root, proof, proof.encoded_size()); - // Verify the generated merkle proof - assert_ok!( - DidMerkleProofVerifier::, BlockNumber, Hashing>::verify_proof_against_digest( - proof.clone().into(), - root - ) - ); - - // 2. Fail to generate a Merkle proof for a key that does not exist - assert_err!( - DidMerkleRootGenerator::::generate_proof( - &did_details, - [<::Out>::default()].iter() - ), - () - ); - - // 3. Fail to verify a merkle proof with a compromised merkle root - let new_root = <::Out>::default(); - assert_err!( - DidMerkleProofVerifier::, BlockNumber, Hashing>::verify_proof_against_digest( - proof.into(), - new_root - ), - () - ); - }) -} - -#[test] -fn complete_did_merkle_proof() { - base_ext().execute_with(|| { - // Give Alice some balance - assert_ok!(Balances::set_balance(RawOrigin::Root.into(), ALICE, 1_000_000_000, 0)); - // Generate a DID for alice - let did_auth_key = ed25519::Pair::from_seed(&[100u8; 32]); - let did_att_key = sr25519::Pair::from_seed(&[150u8; 32]); - let did_del_key = ecdsa::Pair::from_seed(&[200u8; 32]); - let enc_keys = BTreeSet::from_iter(vec![ - DidEncryptionKey::X25519([250u8; 32]), - DidEncryptionKey::X25519([251u8; 32]), - ]); - let did: AccountId = did_auth_key.public().into_account().into(); - let create_details = DidCreationDetails { - did: did.clone(), - submitter: ALICE, - new_attestation_key: Some(did_att_key.public().into()), - new_delegation_key: Some(did_del_key.public().into()), - new_key_agreement_keys: enc_keys - .try_into() - .expect("BTreeSet to BoundedBTreeSet should not fail"), - new_service_details: vec![], - }; - // Create Alice's DID with only authentication key - assert_ok!(Did::create( - RawOrigin::Signed(ALICE).into(), - Box::new(create_details.clone()), - did_auth_key.sign(&create_details.encode()).into() - )); - let did_details = Did::get_did(&did).expect("DID should be present"); - - // 1. Create the DID merkle proof revealing only the authentication key - let CompleteMerkleProof { root, proof } = DidMerkleRootGenerator::::generate_proof( - &did_details, - [did_details.authentication_key].iter(), - ) - .expect("Merkle proof generation should not fail."); - // Verify the generated merkle proof - assert_ok!( - DidMerkleProofVerifier::, BlockNumber, Hashing>::verify_proof_against_digest( - proof.into(), - root - ) - ); - - // 2. Create the DID merkle proof revealing all the keys - let CompleteMerkleProof { root, proof } = DidMerkleRootGenerator::::generate_proof( - &did_details, - [ - did_details.authentication_key, - did_details.attestation_key.unwrap(), - did_details.delegation_key.unwrap(), - ] - .iter() - .chain(did_details.key_agreement_keys.iter()), - ) - .expect("Merkle proof generation should not fail."); - // Verify the generated merkle proof - assert_ok!( - DidMerkleProofVerifier::, BlockNumber, Hashing>::verify_proof_against_digest( - proof.into(), - root - ) - ); - - // 2. Create the DID merkle proof revealing only the key reference and not the - // key ID - let CompleteMerkleProof { root, proof } = DidMerkleRootGenerator::::generate_proof( - &did_details, - [did_details.authentication_key].iter(), - ) - .expect("Merkle proof generation should not fail."); - let reference_only_authentication_leaf: Vec<_> = proof - .revealed - .into_iter() - .filter(|l| !matches!(l, ProofLeaf::KeyDetails(_, _))) - .collect(); - // Fail to verify the generated merkle proof - assert_err!( - DidMerkleProofVerifier::, BlockNumber, Hashing>::verify_proof_against_digest( - Proof { - blinded: proof.blinded, - revealed: reference_only_authentication_leaf - } - .into(), - root - ), - () - ); - }) -} diff --git a/runtimes/common/src/lib.rs b/runtimes/common/src/lib.rs index e94d361b4..bdc0477de 100644 --- a/runtimes/common/src/lib.rs +++ b/runtimes/common/src/lib.rs @@ -35,7 +35,7 @@ use frame_system::limits; use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment}; use sp_runtime::{ generic, - traits::{Bounded, IdentifyAccount, Verify}, + traits::{BlakeTwo256, Bounded, IdentifyAccount, Verify}, FixedPointNumber, MultiSignature, Perquintill, SaturatedConversion, }; use sp_std::marker::PhantomData; @@ -101,8 +101,10 @@ pub type Amount = i128; /// Index of a transaction in the chain. pub type Index = u64; +/// Hasher for chain data. +pub type Hasher = BlakeTwo256; /// A hash of some data used by the chain. -pub type Hash = sp_core::H256; +pub type Hash = ::Out; /// Digest item type. pub type DigestItem = generic::DigestItem; diff --git a/runtimes/peregrine/src/lib.rs b/runtimes/peregrine/src/lib.rs index 722594edb..dbfd756d7 100644 --- a/runtimes/peregrine/src/lib.rs +++ b/runtimes/peregrine/src/lib.rs @@ -62,7 +62,7 @@ use runtime_common::{ errors::PublicCredentialsApiError, fees::{ToAuthor, WeightToFee}, pallet_id, AccountId, AuthorityId, Balance, BlockHashCount, BlockLength, BlockNumber, BlockWeights, DidIdentifier, - FeeSplit, Hash, Header, Index, Signature, SlowAdjustingFeeUpdate, + FeeSplit, Hash, Hasher, Header, Index, Signature, SlowAdjustingFeeUpdate, }; use crate::xcm_config::{XcmConfig, XcmOriginToTransactDispatchOrigin}; @@ -130,7 +130,7 @@ impl frame_system::Config for Runtime { /// The type for hashing blocks and tries. type Hash = Hash; /// The hashing algorithm used. - type Hashing = BlakeTwo256; + type Hashing = Hasher; /// The header type. type Header = runtime_common::Header; /// The ubiquitous event type. diff --git a/runtimes/spiritnet/src/lib.rs b/runtimes/spiritnet/src/lib.rs index 1ef0c9817..0bc1c83c9 100644 --- a/runtimes/spiritnet/src/lib.rs +++ b/runtimes/spiritnet/src/lib.rs @@ -62,7 +62,7 @@ use runtime_common::{ errors::PublicCredentialsApiError, fees::{ToAuthor, WeightToFee}, pallet_id, AccountId, AuthorityId, Balance, BlockHashCount, BlockLength, BlockNumber, BlockWeights, DidIdentifier, - FeeSplit, Hash, Header, Index, Signature, SlowAdjustingFeeUpdate, + FeeSplit, Hash, Hasher, Header, Index, Signature, SlowAdjustingFeeUpdate, }; use crate::xcm_config::{XcmConfig, XcmOriginToTransactDispatchOrigin}; @@ -130,7 +130,7 @@ impl frame_system::Config for Runtime { /// The type for hashing blocks and tries. type Hash = Hash; /// The hashing algorithm used. - type Hashing = BlakeTwo256; + type Hashing = Hasher; /// The header type. type Header = runtime_common::Header; /// The ubiquitous event type.