diff --git a/Cargo.lock b/Cargo.lock index aaa9d25203..aa6e5b264a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2278,10 +2278,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", @@ -2424,7 +2426,6 @@ dependencies = [ "frame-support", "parity-scale-codec", "scale-info", - "sp-std", ] [[package]] @@ -2435,9 +2436,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", @@ -4148,6 +4149,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" @@ -9312,10 +9329,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 221c4cac81..4be4c260c6 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 0b0a7af1e1..276b3929ac 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 bdbda97bf6..bde5dbfee3 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 a795d7c855..0000000000 --- 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 0000000000..ebc86ee9b0 --- /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 0000000000..07bd50d2e6 --- /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 0000000000..c2f18ba0f8 --- /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 0000000000..d86e84475a --- /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 0000000000..56d4dd09b1 --- /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 2073d9d275..de8f00d270 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 77fc5e79cf..639ff16e6b 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 955a43885f..602e4af539 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 6fa0d577bd..e358eff8a5 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 d1db46563a..b3766aa160 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 91e6ae3fbb..6be31a5cb1 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 9c04ad8abb..a868a64ea1 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 0000000000..eed39f68d4 --- /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 a351ae1b9a..f9712bf5a0 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 c134a038f5..7f4645f166 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 4af958ccd8..c826d191a7 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 a589fa3b1d..fd9b90d046 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 6d6284722e..4b1e03c0a6 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 8c6c492850..0000000000 --- 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 0000000000..e954f008e7 --- /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 0f555a199a..f92cc96805 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 645f1ea4d6..a3955297d6 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 b1ce9f9430..0000000000 --- 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 e94d361b43..bdc0477de2 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 8bedf0b7e0..eb1e10cc7c 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 5efb9ac4f0..796cf8e0fa 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.