diff --git a/crates/kilt-dip-support/src/did.rs b/crates/kilt-dip-support/src/did.rs index f598614925..dfdce46dba 100644 --- a/crates/kilt-dip-support/src/did.rs +++ b/crates/kilt-dip-support/src/did.rs @@ -16,6 +16,8 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org +//! Module to deal with cross-chain KILT DIDs. + use did::{ did_details::{DidPublicKey, DidPublicKeyDetails, DidVerificationKey}, DidSignature, DidVerificationKeyRelationship, @@ -31,15 +33,24 @@ use crate::{ traits::{Bump, DidSignatureVerifierContext, DipCallOriginFilter}, }; +/// Type returned by the Merkle proof verifier component of the DIP consumer +/// after verifying a DIP Merkle proof. #[derive(Encode, Decode, RuntimeDebug, Clone, Eq, PartialEq, TypeInfo)] pub(crate) struct RevealedDidKeysAndSignature { + /// The keys revelaed in the Merkle proof. pub merkle_leaves: RevealedDidKeys, + /// The [`DIDSignature`] + consumer chain block number to which the DID + /// signature is anchored. pub did_signature: TimeBoundDidSignature, } +/// A DID signature anchored to a specific block height. #[derive(Encode, Decode, RuntimeDebug, Clone, Eq, PartialEq, TypeInfo)] pub struct TimeBoundDidSignature { + /// The signature. pub signature: DidSignature, + /// The block number, in the context of the local executor, to which the + /// signature is anchored. pub block_number: BlockNumber, } @@ -75,6 +86,30 @@ impl From for u8 { } } +/// Proof verifier that tries to verify a DID signature over a given payload by +/// using one of the DID keys revealed in the Merkle proof. This verifier is +/// typically used in conjunction with a verifier that takes a user-provided +/// input Merkle proof, verifies it, and transforms it into a struct that this +/// and other verifiers can easily consume, e.g., a list of DID keys. +/// The generic types are the following: +/// * `Call`: The call to be dispatched on the local chain after verifying the +/// DID signature. +/// * `Submitter`: The blockchain account (**not** the identity subject) +/// submitting the cross-chain transaction (and paying for its execution +/// fees). +/// * `DidLocalDetails`: Any information associated to the identity subject that +/// is stored locally, e.g., under the `IdentityEntries` map of the +/// `pallet-dip-consumer` pallet. +/// * `MerkleProofEntries`: The type returned by the Merkle proof verifier that +/// includes the identity parts revealed in the Merkle proof. +/// * `ContextProvider`: Provides additional local context (e.g., current block +/// number) to verify the DID signature. +/// * `RemoteKeyId`: Definition of a DID key ID as specified by the provider. +/// * `RemoteAccountId`: Definition of a linked account ID as specified by the +/// provider. +/// * `RemoteBlockNumber`: Definition of a block number on the provider chain. +/// * `CallVerifier`: A type specifying whether the provided `Call` can be +/// dispatched with the information provided in the DIP proof. pub(crate) struct RevealedDidKeysSignatureAndCallVerifier< Call, Submitter, @@ -135,7 +170,6 @@ impl< DipCallOriginFilter, DidVerificationKeyRelationship)>, { #[cfg(not(feature = "runtime-benchmarks"))] - #[allow(clippy::result_unit_err)] pub(crate) fn verify_did_signature_for_call( call: &Call, submitter: &Submitter, @@ -147,7 +181,7 @@ impl< > { use frame_support::ensure; - let block_number = ContextProvider::block_number(); + let block_number = ContextProvider::current_block_number(); let is_signature_fresh = if let Some(blocks_ago_from_now) = block_number.checked_sub(&merkle_revealed_did_signature.did_signature.block_number) { @@ -213,7 +247,7 @@ impl< > { use sp_core::ed25519; - let block_number = ContextProvider::block_number(); + let block_number = ContextProvider::current_block_number(); // Computed but ignored if let Some(blocks_ago_from_now) = block_number.checked_sub(&merkle_revealed_did_signature.did_signature.block_number) diff --git a/crates/kilt-dip-support/src/export/child.rs b/crates/kilt-dip-support/src/export/child.rs index 6fd215de33..00a897b538 100644 --- a/crates/kilt-dip-support/src/export/child.rs +++ b/crates/kilt-dip-support/src/export/child.rs @@ -34,13 +34,17 @@ use crate::{ merkle::{DidMerkleProofVerifierError, RevealedDidMerkleProofLeaf, RevealedDidMerkleProofLeaves}, state_proofs::{parachain::DipIdentityCommitmentProofVerifierError, relay_chain::ParachainHeadProofVerifierError}, traits::{ - Bump, DidSignatureVerifierContext, DipCallOriginFilter, HistoricalBlockRegistry, ProviderParachainStateInfo, + Bump, DidSignatureVerifierContext, DipCallOriginFilter, HistoricalBlockRegistry, ProviderParachainStorageInfo, RelayChainStorageInfo, }, utils::OutputOf, BoundedBlindedValue, FrameSystemDidSignatureContext, ProviderParachainStateInfoViaProviderPallet, }; +/// A KILT-specific DIP identity proof for a parent consumer that supports +/// versioning. +/// +/// For more info, refer to the version-specific proofs. #[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, Clone)] #[non_exhaustive] pub enum VersionedChildParachainDipStateProof< @@ -118,8 +122,48 @@ where } } -// Implements the same `IdentityProvider` trait, but it is internally configured -// by receiving the runtime definitions of both the provider and the receiver. +/// Proof verifier configured given a specific KILT runtime implementation. +/// +/// A specialization of the +/// [`GenericVersionedDipChildProviderStateProofVerifier`] type, with +/// configurations derived from the provided KILT runtime. +/// +/// The generic types are the following: +/// * `KiltRuntime`: A KILT runtime definition. +/// * `KiltParachainId`: The ID of the specific KILT parachain instance. +/// * `RelayChainInfo`: The type providing information about the consumer +/// (relay)chain. +/// * `KiltDipMerkleHasher`: The hashing algorithm used by the KILT parachain +/// for the generation of the DIP identity commitment. +/// * `LocalDidCallVerifier`: Logic to map `RuntimeCall`s to a specific DID key +/// relationship. This information is used once the Merkle proof is verified, +/// to filter only the revealed keys that match the provided relationship. +/// * `MAX_REVEALED_KEYS_COUNT`: Max number of DID keys that the verifier will +/// accept revealed as part of the DIP identity proof. +/// * `MAX_REVEALED_ACCOUNTS_COUNT`: Max number of linked accounts that the +/// verifier will accept revealed as part of the DIP identity proof. +/// * `MAX_DID_SIGNATURE_DURATION`: Max number of blocks a cross-chain DID +/// signature is considered fresh. +/// +/// It specializes the [`GenericVersionedDipChildProviderStateProofVerifier`] +/// type by using the following types for its generics: +/// * `RelayChainInfo`: The provided `RelayChainInfo`. +/// * `ChildProviderParachainId`: The provided `KiltParachainId`. +/// * `ChildProviderStateInfo`: The +/// [`ProviderParachainStateInfoViaProviderPallet`] type configured with the +/// provided `KiltRuntime`. +/// * `ProviderDipMerkleHasher`: The provided `KiltDipMerkleHasher`. +/// * `ProviderDidKeyId`: The [`KeyIdOf`] type configured with the provided +/// `KiltRuntime`. +/// * `ProviderAccountId`: The `KiltRuntime::AccountId` type. +/// * `ProviderWeb3Name`: The `KiltRuntime::Web3Name` type. +/// * `ProviderLinkedAccountId`: The [`LinkableAccountId`] type. +/// * `MAX_REVEALED_KEYS_COUNT`: The provided `MAX_REVEALED_KEYS_COUNT`. +/// * `MAX_REVEALED_ACCOUNTS_COUNT`: The provided `MAX_REVEALED_ACCOUNTS_COUNT`. +/// * `LocalContextProvider`: The [`FrameSystemDidSignatureContext`] type +/// configured with the provided `KiltRuntime` and +/// `MAX_DID_SIGNATURE_DURATION`. +/// * `LocalDidCallVerifier`: The provided `LocalDidCallVerifier`. pub struct KiltVersionedChildProviderVerifier< KiltRuntime, KiltParachainId, @@ -258,10 +302,15 @@ impl< } } -// More generic version compared to `VersionedChildKiltProviderVerifier`, to be -// used in cases in which it is not possible or not desirable to depend on the -// whole provider runtime definition. Hence, required types must be filled in -// manually. +/// Generic proof verifier for KILT-specific DIP identity proofs of different +/// versions coming from a child provider running one of the available KILT +/// runtimes. +/// +/// It expects the DIP proof to be a [`VersionedChildParachainDipStateProof`], +/// and returns [`RevealedDidMerkleProofLeaves`] if the proof is successfully +/// verified. +/// +/// For more info, refer to the version-specific proof identifiers. pub struct GenericVersionedDipChildProviderStateProofVerifier< RelayChainInfo, ChildProviderParachainId, @@ -346,8 +395,10 @@ impl< ChildProviderParachainId: Get, - ChildProviderStateInfo: - ProviderParachainStateInfo, + ChildProviderStateInfo: ProviderParachainStorageInfo< + Identifier = ConsumerRuntime::Identifier, + Commitment = ProviderDipMerkleHasher::Out, + >, OutputOf: Ord + From::Hasher>>, ChildProviderStateInfo::BlockNumber: Parameter + 'static, ChildProviderStateInfo::Commitment: Decode, @@ -431,7 +482,7 @@ pub mod latest { pub use super::v0::ChildParachainDipStateProof; } -mod v0 { +pub mod v0 { use super::*; use parity_scale_codec::Codec; @@ -457,11 +508,15 @@ mod v0 { }, traits::{ Bump, DidSignatureVerifierContext, DipCallOriginFilter, HistoricalBlockRegistry, - ProviderParachainStateInfo, RelayChainStorageInfo, + ProviderParachainStorageInfo, RelayChainStorageInfo, }, utils::OutputOf, }; + /// The expected format of a cross-chain DIP identity proof when the + /// identity information is bridged from a provider that is a child of + /// the chain where the information is consumed (i.e., consumer + /// chain). #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct ChildParachainDipStateProof< ParentBlockHeight: Copy + Into + TryFrom, @@ -469,12 +524,85 @@ mod v0 { DipMerkleProofBlindedValues, DipMerkleProofRevealedLeaf, > { + /// The state proof for the given parachain head. para_state_root: ParachainRootStateProof, + /// The relaychain header for the relaychain block specified in the + /// `para_state_root`. relay_header: Header, + /// The raw state proof for the DIP commitment of the given subject. dip_identity_commitment: Vec>, + /// The cross-chain DID signature. did: DipMerkleProofAndDidSignature, } + /// Generic proof verifier for KILT-specific DIP identity proofs coming from + /// a child provider running one of the available KILT runtimes. + /// The proof verification step is performed on every request, and this + /// specific verifier has no knowledge of caching or storing state about the + /// subject. It only takes the provided + /// `ConsumerRuntime::LocalIdentityInfo` and increases it if the proof is + /// successfully verified, to prevent replay attacks. If additional logic is + /// to be stored under the `ConsumerRuntime::LocalIdentityInfo` entry, a + /// different verifier or a wrapper around this verifier must be built. + /// + /// It expects the DIP proof to be a + /// [`VersionedChildParachainDipStateProof`], and returns + /// [`RevealedDidMerkleProofLeaves`] if the proof is successfully verified. + /// This information is then made availabe as an origin to the downstream + /// call dispatched. + /// + /// The verifier performs the following steps: + /// 1. Verifies the state proof about the state root of the relaychain block + /// at the provided height. The state root is retrieved from the provided + /// relaychain header, which is checked to be the header of a + /// previously-finalized relaychain block. + /// 2. Verifies the state proof about the DIP commitment value on the + /// provider parachain at the block finalized at the given relaychain + /// block, using the relay state root validated in the previous step. + /// 3. Verifies the DIP Merkle proof revealing parts of the subject's DID + /// Document against the retrieved DIP commitment validated in the + /// previous step. + /// 4. Verifies the cross-chain DID signature over the payload composed by + /// the SCALE-encoded tuple of `(C, D, S, B, G, E)`, with: + /// * `C`: The `RuntimeCall` to dispatch after performing DIP + /// verification. + /// * `D`: The local details associated to the DID subject as stored in + /// the [`pallet_dip_consumer`] `IdentityEntries` storage map. + /// * `S`: The tx submitter's address. + /// * `B`: The block number of the consumer chain provided in the + /// cross-chain DID signature. + /// * `G`: The genesis hash of the consumer chain. + /// * `E`: Any additional information provided by the + /// `LocalContextProvider` implementation. + /// The generic types + /// indicate the following: + /// * `RelayChainInfo`: The type providing information about the consumer + /// (relay)chain. + /// * `ChildProviderParachainId`: The parachain ID of the provider KILT + /// child parachain. + /// * `ChildProviderStateInfo`: The type providing storage and state + /// information about the provider KILT child parachain. + /// * `ProviderDipMerkleHasher`: The hashing algorithm used by the KILT + /// parachain for the generation of the DIP identity commitment. + /// * `ProviderDidKeyId`: The runtime type of a DID key ID as defined by the + /// KILT child parachain. + /// * `ProviderAccountId`: The runtime type of an account ID as defined by + /// the KILT child parachain. + /// * `ProviderWeb3Name`: The runtime type of a web3name as defined by the + /// KILT child parachain. + /// * `ProviderLinkedAccountId`: The runtime type of a linked account ID as + /// defined by the KILT child parachain. + /// * `MAX_REVEALED_KEYS_COUNT`: Max number of DID keys that the verifier + /// will accept revealed as part of the DIP identity proof. + /// * `MAX_REVEALED_ACCOUNTS_COUNT`: Max number of linked accounts that the + /// verifier will accept revealed as part of the DIP identity proof. + /// * `LocalContextProvider`: The type providing context of the consumer + /// chain (e.g., current block number) for the sake of cross-chain DID + /// signature verification. + /// * `LocalDidCallVerifier`: Logic to map `RuntimeCall`s to a specific DID + /// key relationship. This information is used once the Merkle proof is + /// verified, to filter only the revealed keys that match the provided + /// relationship. pub struct DipChildProviderStateProofVerifier< RelayChainInfo, ChildProviderParachainId, @@ -559,7 +687,7 @@ mod v0 { ChildProviderParachainId: Get, - ChildProviderStateInfo: ProviderParachainStateInfo< + ChildProviderStateInfo: ProviderParachainStorageInfo< Identifier = ConsumerRuntime::Identifier, Commitment = ProviderDipMerkleHasher::Out, >, diff --git a/crates/kilt-dip-support/src/export/common.rs b/crates/kilt-dip-support/src/export/common.rs index fa98de6f16..455c5848a9 100644 --- a/crates/kilt-dip-support/src/export/common.rs +++ b/crates/kilt-dip-support/src/export/common.rs @@ -29,7 +29,9 @@ pub mod v0 { #[derive(Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo, Clone)] pub struct ParachainRootStateProof { + /// The relaychain block height for which the proof has been generated. pub(crate) relay_block_height: RelayBlockHeight, + /// The raw state proof. pub(crate) proof: BoundedBlindedValue, } @@ -49,7 +51,10 @@ pub mod v0 { #[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, Clone)] pub struct DipMerkleProofAndDidSignature { + /// The DIP Merkle proof revealing some leaves about the DID subject's + /// identity. pub(crate) leaves: DidMerkleProof, + /// The cross-chain DID signature. pub(crate) signature: TimeBoundDidSignature, } diff --git a/crates/kilt-dip-support/src/export/mod.rs b/crates/kilt-dip-support/src/export/mod.rs index 8d2f289926..01fd5ad2a1 100644 --- a/crates/kilt-dip-support/src/export/mod.rs +++ b/crates/kilt-dip-support/src/export/mod.rs @@ -16,7 +16,9 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org +/// Verification logic to integrate a child chain as a DIP provider. pub mod child; +/// Verification logic to integrate a sibling chain as a DIP provider. pub mod sibling; mod common; diff --git a/crates/kilt-dip-support/src/export/sibling.rs b/crates/kilt-dip-support/src/export/sibling.rs index 0fd6ec989e..daf3759881 100644 --- a/crates/kilt-dip-support/src/export/sibling.rs +++ b/crates/kilt-dip-support/src/export/sibling.rs @@ -38,6 +38,10 @@ use crate::{ BoundedBlindedValue, FrameSystemDidSignatureContext, ProviderParachainStateInfoViaProviderPallet, }; +/// A KILT-specific DIP identity proof for a sibling consumer that supports +/// versioning. +/// +/// For more info, refer to the version-specific proofs. #[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, Clone)] #[non_exhaustive] pub enum VersionedSiblingParachainDipStateProof< @@ -130,8 +134,49 @@ where } } } -// Implements the same `IdentityProvider` trait, but it is internally configured -// by receiving the runtime definitions of both the provider and the receiver. + +/// Proof verifier configured given a specific KILT runtime implementation. +/// +/// It is a specialization of the +/// [`GenericVersionedDipSiblingProviderStateProofVerifier`] type, with +/// configurations derived from the provided KILT runtime. +/// +/// The generic types +/// indicate the following: +/// * `KiltRuntime`: A KILT runtime definition. +/// * `KiltParachainId`: The ID of the specific KILT parachain instance. +/// * `RelayChainInfo`: The type providing information about the relaychain. +/// * `KiltDipMerkleHasher`: The hashing algorithm used by the KILT parachain +/// for the generation of the DIP identity commitment. +/// * `LocalDidCallVerifier`: Logic to map `RuntimeCall`s to a specific DID key +/// relationship. This information is used once the Merkle proof is verified, +/// to filter only the revealed keys that match the provided relationship. +/// * `MAX_REVEALED_KEYS_COUNT`: Max number of DID keys that the verifier will +/// accept revealed as part of the DIP identity proof. +/// * `MAX_REVEALED_ACCOUNTS_COUNT`: Max number of linked accounts that the +/// verifier will accept revealed as part of the DIP identity proof. +/// * `MAX_DID_SIGNATURE_DURATION`: Max number of blocks a cross-chain DID +/// signature is considered fresh. +/// +/// It specializes the [`GenericVersionedDipSiblingProviderStateProofVerifier`] +/// type by using the following types for its generics: +/// * `RelayChainInfo`: The provided `RelayChainInfo`. +/// * `ChildProviderParachainId`: The provided `KiltParachainId`. +/// * `ChildProviderStateInfo`: The +/// [`ProviderParachainStateInfoViaProviderPallet`] type configured with the +/// provided `KiltRuntime`. +/// * `ProviderDipMerkleHasher`: The provided `KiltDipMerkleHasher`. +/// * `ProviderDidKeyId`: The [`KeyIdOf`] type configured with the provided +/// `KiltRuntime`. +/// * `ProviderAccountId`: The `KiltRuntime::AccountId` type. +/// * `ProviderWeb3Name`: The `KiltRuntime::Web3Name` type. +/// * `ProviderLinkedAccountId`: The [`LinkableAccountId`] type. +/// * `MAX_REVEALED_KEYS_COUNT`: The provided `MAX_REVEALED_KEYS_COUNT`. +/// * `MAX_REVEALED_ACCOUNTS_COUNT`: The provided `MAX_REVEALED_ACCOUNTS_COUNT`. +/// * `LocalContextProvider`: The [`FrameSystemDidSignatureContext`] type +/// configured with the provided `KiltRuntime` and +/// `MAX_DID_SIGNATURE_DURATION`. +/// * `LocalDidCallVerifier`: The provided `LocalDidCallVerifier`. pub struct KiltVersionedSiblingProviderVerifier< KiltRuntime, KiltParachainId, @@ -253,10 +298,15 @@ impl< } } -// More generic version compared to `VersionedSiblingKiltProviderVerifier`, to -// be used in cases in which it is not possible or not desirable to depend on -// the whole provider runtime definition. Hence, required types must be filled -// in manually. +/// Generic proof verifier for KILT-specific DIP identity proofs of different +/// versions coming from a sibling provider running one of the available KILT +/// runtimes. +/// +/// It expects the DIP proof to be a [`VersionedSiblingParachainDipStateProof`], +/// and returns [`RevealedDidMerkleProofLeaves`] if the proof is successfully +/// verified. +/// +/// For more info, refer to the version-specific proof identifiers. pub struct GenericVersionedDipSiblingProviderStateProofVerifier< RelayChainStateInfo, SiblingProviderParachainId, @@ -325,7 +375,7 @@ impl< SiblingProviderParachainId: Get, - SiblingProviderStateInfo: traits::ProviderParachainStateInfo< + SiblingProviderStateInfo: traits::ProviderParachainStorageInfo< Identifier = ConsumerRuntime::Identifier, Commitment = ProviderDipMerkleHasher::Out, >, @@ -412,7 +462,7 @@ pub mod latest { pub use super::v0::SiblingParachainDipStateProof; } -mod v0 { +pub mod v0 { use super::*; use frame_support::Parameter; @@ -423,9 +473,13 @@ mod v0 { export::common::v0::{DipMerkleProofAndDidSignature, ParachainRootStateProof}, merkle::DidMerkleProofVerifier, state_proofs::{parachain::DipIdentityCommitmentProofVerifier, relay_chain::ParachainHeadProofVerifier}, - traits::ProviderParachainStateInfo, + traits::ProviderParachainStorageInfo, }; + /// The expected format of a cross-chain DIP identity proof when the + /// identity information is bridged from a provider that is a sibling + /// of the chain where the information is consumed (i.e., consumer + /// chain). #[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, Clone)] pub struct SiblingParachainDipStateProof< RelayBlockHeight, @@ -433,8 +487,11 @@ mod v0 { DipMerkleProofRevealedLeaf, LocalBlockNumber, > { + /// The state proof for the given parachain head. pub(crate) para_state_root: ParachainRootStateProof, + /// The raw state proof for the DIP commitment of the given subject. pub(crate) dip_identity_commitment: BoundedBlindedValue, + /// The cross-chain DID signature. pub(crate) did: DipMerkleProofAndDidSignature, } @@ -463,6 +520,73 @@ mod v0 { } } + /// Generic proof verifier for KILT-specific DIP identity proofs coming from + /// a sibling provider running one of the available KILT runtimes. + /// + /// The proof verification step is performed on every request, and this + /// specific verifier has no knowledge of caching or storing state about the + /// subject. It only takes the provided + /// `ConsumerRuntime::LocalIdentityInfo` and increases it if the proof is + /// successfully verified, to prevent replay attacks. If additional logic is + /// to be stored under the `ConsumerRuntime::LocalIdentityInfo` entry, a + /// different verifier or a wrapper around this verifier must be built. + /// + /// It expects the DIP proof to be a + /// [`VersionedSiblingParachainDipStateProof`], and returns + /// [`RevealedDidMerkleProofLeaves`] if the proof is successfully verified. + /// This information is then made availabe as an origin to the downstream + /// call dispatched. + /// + /// The verifier performs the following steps: + /// 1. Verifies the state proof about the state root of the relaychain block + /// at the provided height. The state root is provided by the + /// `RelayChainInfo` type. + /// 2. Verifies the state proof about the DIP commitment value on the + /// provider parachain at the block finalized at the given relaychain + /// block, using the relay state root validated in the previous step. + /// 3. Verifies the DIP Merkle proof revealing parts of the subject's DID + /// Document against the retrieved DIP commitment validated in the + /// previous step. + /// 4. Verifies the cross-chain DID signature over the payload composed by + /// the SCALE-encoded tuple of `(C, D, S, B, G, E)`, with: + /// * `C`: The `RuntimeCall` to dispatch after performing DIP + /// verification. + /// * `D`: The local details associated to the DID subject as stored in + /// the [`pallet_dip_consumer`] `IdentityEntries` storage map. + /// * `S`: The tx submitter's address. + /// * `B`: The block number of the consumer chain provided in the + /// cross-chain DID signature. + /// * `G`: The genesis hash of the consumer chain. + /// * `E`: Any additional information provided by the + /// `LocalContextProvider` implementation. + /// The generic types + /// indicate the following: + /// * `RelayChainInfo`: The type providing information about the relaychain. + /// * `SiblingProviderParachainId`: The parachain ID of the provider KILT + /// sibling parachain. + /// * `SiblingProviderStateInfo`: The type providing storage and state + /// information about the provider KILT sibling parachain. + /// * `ProviderDipMerkleHasher`: The hashing algorithm used by the KILT + /// parachain for the generation of the DIP identity commitment. + /// * `ProviderDidKeyId`: The runtime type of a DID key ID as defined by the + /// KILT child parachain. + /// * `ProviderAccountId`: The runtime type of an account ID as defined by + /// the KILT child parachain. + /// * `ProviderWeb3Name`: The runtime type of a web3name as defined by the + /// KILT child parachain. + /// * `ProviderLinkedAccountId`: The runtime type of a linked account ID as + /// defined by the KILT child parachain. + /// * `MAX_REVEALED_KEYS_COUNT`: Max number of DID keys that the verifier + /// will accept revealed as part of the DIP identity proof. + /// * `MAX_REVEALED_ACCOUNTS_COUNT`: Max number of linked accounts that the + /// verifier will accept revealed as part of the DIP identity proof. + /// * `LocalContextProvider`: The type providing context of the consumer + /// chain (e.g., current block number) for the sake of cross-chain DID + /// signature verification. + /// * `LocalDidCallVerifier`: Logic to map `RuntimeCall`s to a specific DID + /// key relationship. This information is used once the Merkle proof is + /// verified, to filter only the revealed keys that match the provided + /// relationship. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct DipSiblingProviderStateProofVerifier< RelayChainStateInfo, @@ -532,7 +656,7 @@ mod v0 { SiblingProviderParachainId: Get, - SiblingProviderStateInfo: traits::ProviderParachainStateInfo< + SiblingProviderStateInfo: traits::ProviderParachainStorageInfo< Identifier = ConsumerRuntime::Identifier, Commitment = ProviderDipMerkleHasher::Out, >, @@ -612,7 +736,7 @@ mod v0 { let proof_leaves: RevealedDidMerkleProofLeaves< ProviderDidKeyId, ProviderAccountId, - ::BlockNumber, + ::BlockNumber, ProviderWeb3Name, ProviderLinkedAccountId, MAX_REVEALED_KEYS_COUNT, diff --git a/crates/kilt-dip-support/src/lib.rs b/crates/kilt-dip-support/src/lib.rs index 9d7a415616..66ec155cdc 100644 --- a/crates/kilt-dip-support/src/lib.rs +++ b/crates/kilt-dip-support/src/lib.rs @@ -16,7 +16,13 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -// TODO: Crate documentation +//! Collection of support traits, types, and functions for integrating KILT as +//! an identity provider following the Decentralized Identity Provider (DIP) +//! protocol. +//! +//! Consumers of KILT identities should prefer directly using +//! [`KiltVersionedChildProviderVerifier`] for consumer relaychains and +//! [`KiltVersionedSiblingProviderVerifier`] for consumer sibling parachains. #![cfg_attr(not(feature = "std"), no_std)] diff --git a/crates/kilt-dip-support/src/merkle.rs b/crates/kilt-dip-support/src/merkle.rs index 68e30e9917..ebe2826c8d 100644 --- a/crates/kilt-dip-support/src/merkle.rs +++ b/crates/kilt-dip-support/src/merkle.rs @@ -16,6 +16,8 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org +//! Module to deal with cross-chain Merkle proof as generated by the KILT chain. + use did::{ did_details::{DidPublicKeyDetails, DidVerificationKey}, DidVerificationKeyRelationship, @@ -28,6 +30,7 @@ use sp_runtime::{BoundedVec, SaturatedConversion}; use sp_std::{fmt::Debug, marker::PhantomData, vec::Vec}; use sp_trie::{verify_trie_proof, LayoutV1}; +/// Type of a Merkle proof containing DID-related information. #[derive(Encode, Decode, RuntimeDebug, Clone, Eq, PartialEq, Default, TypeInfo)] pub struct DidMerkleProof { pub blinded: BlindedValues, @@ -49,6 +52,7 @@ where } } +/// Relationship of a key to a DID Document. #[derive(Clone, Copy, RuntimeDebug, Encode, Decode, PartialEq, Eq, TypeInfo, PartialOrd, Ord, MaxEncodedLen)] pub enum DidKeyRelationship { Encryption, @@ -73,6 +77,7 @@ impl TryFrom for DidVerificationKeyRelationship { } } +/// The key of a Merkle leaf revealing a DID key for a DID Document. #[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] pub struct DidKeyMerkleKey(pub KeyId, pub DidKeyRelationship); @@ -81,7 +86,7 @@ impl From<(KeyId, DidKeyRelationship)> for DidKeyMerkleKey { Self(value.0, value.1) } } - +/// The value of a Merkle leaf revealing a DID key for a DID Document. #[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] pub struct DidKeyMerkleValue(pub DidPublicKeyDetails); @@ -93,6 +98,7 @@ impl From> } } +/// The key of a Merkle leaf revealing the web3name linked to a DID Document. #[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] pub struct Web3NameMerkleKey(pub Web3Name); @@ -101,6 +107,7 @@ impl From for Web3NameMerkleKey { Self(value) } } +/// The value of a Merkle leaf revealing the web3name linked to a DID Document. #[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] pub struct Web3NameMerkleValue(pub BlockNumber); @@ -110,6 +117,7 @@ impl From for Web3NameMerkleValue { } } +/// The key of a Merkle leaf revealing an account linked to a DID Document. #[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] pub struct LinkedAccountMerkleKey(pub AccountId); @@ -118,7 +126,8 @@ impl From for LinkedAccountMerkleKey { Self(value) } } - +/// The value of a Merkle leaf revealing an account linked to a DID +/// Document. #[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] pub struct LinkedAccountMerkleValue; @@ -128,10 +137,10 @@ impl From<()> for LinkedAccountMerkleValue { } } +/// All possible Merkle leaf types that can be revealed as part of a DIP +/// identity Merkle proof. #[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] pub enum RevealedDidMerkleProofLeaf { - // 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. DidKey(DidKeyMerkleKey, DidKeyMerkleValue), Web3Name(Web3NameMerkleKey, Web3NameMerkleValue), LinkedAccount(LinkedAccountMerkleKey, LinkedAccountMerkleValue), @@ -187,19 +196,32 @@ where } } +/// The details of a DID key after it has been successfully verified in a Merkle +/// proof. #[derive(Clone, Encode, Decode, PartialEq, MaxEncodedLen, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] pub struct RevealedDidKey { + /// The key ID, according to the provider's definition. pub id: KeyId, + /// The key relationship to the subject's DID Document. pub relationship: DidKeyRelationship, + /// The details of the DID Key, including its creation block number on the + /// provider chain. pub details: DidPublicKeyDetails, } +/// The details of a web3name after it has been successfully verified in a +/// Merkle proof. #[derive(Clone, Encode, Decode, PartialEq, MaxEncodedLen, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] pub struct RevealedWeb3Name { + /// The web3name. pub web3_name: Web3Name, + /// The block number on the provider chain in which it was linked to the DID + /// subject. pub claimed_at: BlockNumber, } +/// The complete set of information that is provided by the DIP Merkle proof +/// verifier upon successful verification of a DIP Merkle proof. #[derive(Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen, Encode, Decode, DefaultNoBound)] pub struct RevealedDidMerkleProofLeaves< KeyId, @@ -210,8 +232,13 @@ pub struct RevealedDidMerkleProofLeaves< const MAX_REVEALED_KEYS_COUNT: u32, const MAX_REVEALED_ACCOUNTS_COUNT: u32, > { + /// The list of [`RevealedDidKey`]s revealed in the Merkle proof, up to a + /// maximum of `MAX_REVEALED_KEYS_COUNT`. pub did_keys: BoundedVec, ConstU32>, + /// The optional [`RevealedWeb3Name`] revealed in the Merkle proof. pub web3_name: Option>, + /// The list of linked accounts revealed in the Merkle proof, up to a + /// maximum of `MAX_REVEALED_ACCOUNTS_COUNT`. pub linked_accounts: BoundedVec>, } @@ -255,9 +282,30 @@ impl From for u8 { } } -/// 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. +/// A type that verifies a DIP Merkle proof revealing some leaves representing +/// parts of a KILT DID identity stored on the KILT chain. +/// If cross-chain DID signatures are not required for the specific use case, +/// this verifier can also be used on its own, without any DID signature +/// verification. +/// The Merkle proof is assumed to have been generated using one of the +/// versioned identity commitment generators, as shown in the [KILT runtime +/// definitions](../../../runtimes/common/src/dip/README.md). +/// The generic types are the following: +/// * `Hasher`: The hasher used by the producer to hash the Merkle leaves and +/// produce the identity commitment. +/// * `KeyId`: The type of a DID key ID according to the producer's definition. +/// * `AccountId`: The type of an account ID according to the producer's +/// definition. +/// * `BlockNumber`: The type of a block number according to the producer's +/// definition. +/// * `Web3Name`: The type of a web3names according to the producer's +/// definition. +/// * `LinkedAccountId`: The type of a DID-linked account ID according to the +/// producer's definition. +/// * `MAX_REVEALED_KEYS_COUNT`: The maximum number of DID keys that are +/// supported when verifying the Merkle proof. +/// * `MAX_REVEALED_ACCOUNTS_COUNT`: The maximum number of linked accounts that +/// are supported when verifying the Merkle proof. pub(crate) struct DidMerkleProofVerifier< Hasher, KeyId, @@ -297,7 +345,6 @@ impl< Web3Name: Encode + Clone, { #[cfg(not(feature = "runtime-benchmarks"))] - #[allow(clippy::type_complexity)] pub(crate) fn verify_dip_merkle_proof( identity_commitment: &Hasher::Out, proof: DidMerkleProof< diff --git a/crates/kilt-dip-support/src/state_proofs.rs b/crates/kilt-dip-support/src/state_proofs.rs index 019161c27f..58619eb6bb 100644 --- a/crates/kilt-dip-support/src/state_proofs.rs +++ b/crates/kilt-dip-support/src/state_proofs.rs @@ -16,6 +16,8 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org +//! Module to deal with cross-chain state proofs. + use parity_scale_codec::{Decode, Encode, HasCompact}; use sp_core::{storage::StorageKey, RuntimeDebug, U256}; use sp_runtime::generic::Header; @@ -85,6 +87,7 @@ mod substrate_no_std_port { } } +/// Relaychain-related state proof logic. pub(super) mod relay_chain { use super::*; @@ -111,6 +114,12 @@ pub(super) mod relay_chain { } } + /// Verifier of state proofs that reveal the value of a parachain head at a + /// given relaychain block number. + /// The generic types are the following: + /// * `RelayChainState`: defines the relaychain runtime types relevant for + /// state proof verification, and returns the relaychain runtime's storage + /// key identifying a parachain with a given ID. pub struct ParachainHeadProofVerifier(PhantomData); // Uses the provided `root` to verify the proof. @@ -121,6 +130,8 @@ pub(super) mod relay_chain { RelayChainState::BlockNumber: Copy + Into + TryFrom + HasCompact, RelayChainState::Key: AsRef<[u8]>, { + /// Given a relaychain state root, verify a state proof for the + /// parachain with the provided ID. #[cfg(not(feature = "runtime-benchmarks"))] pub fn verify_proof_for_parachain_with_root( para_id: &RelayChainState::ParaId, @@ -187,6 +198,9 @@ pub(super) mod relay_chain { RelayChainState::BlockNumber: Copy + Into + TryFrom + HasCompact, RelayChainState::Key: AsRef<[u8]>, { + /// Given a relaychain state root provided by the `RelayChainState` + /// generic type, verify a state proof for the parachain with the + /// provided ID. #[cfg(not(feature = "runtime-benchmarks"))] pub fn verify_proof_for_parachain( para_id: &RelayChainState::ParaId, @@ -209,6 +223,12 @@ pub(super) mod relay_chain { } } + /// Implementor of the [`RelayChainStorageInfo`] trait that return the state + /// root of a relaychain block with a given number by retrieving it from the + /// [`pallet_relay_store::Pallet`] pallet storage. It hardcodes the + /// relaychain `BlockNumber`, `Hasher`, `StorageKey`, and `ParaId` to the + /// ones used by Polkadot-based relaychains. This type cannot be used with + /// relaychains that adopt a different definition for any on those types. pub struct RelayStateRootsViaRelayStorePallet(PhantomData); impl RelayChainStorageInfo for RelayStateRootsViaRelayStorePallet @@ -308,10 +328,11 @@ pub(super) mod relay_chain { } } +/// Parachain-related state proof logic. pub(super) mod parachain { use super::*; - use crate::traits::ProviderParachainStateInfo; + use crate::traits::ProviderParachainStorageInfo; #[derive(RuntimeDebug)] pub enum DipIdentityCommitmentProofVerifierError { @@ -330,15 +351,24 @@ pub(super) mod parachain { } } + /// Verifier of state proofs that reveal the value of the DIP commitment for + /// a given subject on the provider chain. The generic types indicate the + /// following: + /// * `ParaInfo`: defines the provider parachain runtime types relevant for + /// state proof verification, and returns the provider's runtime storage + /// key identifying the identity commitment for a subject with the given + /// identifier. pub struct DipIdentityCommitmentProofVerifier(PhantomData); impl DipIdentityCommitmentProofVerifier where - ParaInfo: ProviderParachainStateInfo, + ParaInfo: ProviderParachainStorageInfo, OutputOf: Ord, ParaInfo::Commitment: Decode, ParaInfo::Key: AsRef<[u8]>, { + /// Given a parachain state root, verify a state proof for the + /// commitment of a given subject identifier. #[cfg(not(feature = "runtime-benchmarks"))] #[allow(clippy::result_unit_err)] pub fn verify_proof_for_identifier( @@ -408,7 +438,7 @@ pub(super) mod parachain { struct StaticSpiritnetInfoProvider; // We use the `system::eventCount()` storage entry as a unit test here. - impl ProviderParachainStateInfo for StaticSpiritnetInfoProvider { + impl ProviderParachainStorageInfo for StaticSpiritnetInfoProvider { type BlockNumber = u32; // The type of the `eventCount()` storage entry. type Commitment = u32; diff --git a/crates/kilt-dip-support/src/traits.rs b/crates/kilt-dip-support/src/traits.rs index aa426aae17..7d0967c797 100644 --- a/crates/kilt-dip-support/src/traits.rs +++ b/crates/kilt-dip-support/src/traits.rs @@ -45,6 +45,8 @@ where /// A trait for types that implement some sort of access control logic on the /// provided input `Call` type. +/// The generic types are the following: +/// * `Call`: The type of the call being checked. pub trait DipCallOriginFilter { /// The error type for cases where the checks fail. type Error; @@ -54,35 +56,62 @@ pub trait DipCallOriginFilter { /// The success type for cases where the checks succeed. type Success; + /// Check whether the provided call can be dispatch with the given origin + /// information. fn check_call_origin_info(call: &Call, info: &Self::OriginInfo) -> Result; } +/// A trait that provides context (e.g., runtime type definitions, storage keys) +/// about the relaychain that is relevant for cross-chain state proofs. pub trait RelayChainStorageInfo { + /// The type of relaychain block numbers. type BlockNumber; + /// The type of the relaychain hashing algorithm. type Hasher: sp_runtime::traits::Hash; + /// The type of the relaychain storage key. type Key; + /// The type of parachain IDs. type ParaId; + /// Return the storage key pointing to the head of the parachain + /// identified by the provided ID. fn parachain_head_storage_key(para_id: &Self::ParaId) -> Self::Key; } +/// A trait that provides state information about specific relaychain blocks. pub trait RelayChainStateInfo: RelayChainStorageInfo { + /// Return the relaychain state root at a given block height. fn state_root_for_block(block_height: &Self::BlockNumber) -> Option>; } -pub trait ProviderParachainStateInfo { +/// A trait that provides context (e.g., runtime type definitions, storage keys) +/// about the DIP provider parachain that is relevant for cross-chain state +/// proofs. +pub trait ProviderParachainStorageInfo { + /// The type of the provider chain's block numbers. type BlockNumber; + /// The type of the provider chain's identity commitments. type Commitment; + /// The type of the provider chain's storage keys. type Key; + /// The type of the provider chain's hashing algorithm. type Hasher: sp_runtime::traits::Hash; + /// The type of the provider chain's identity subject identifiers. type Identifier; + /// Return the storage key pointing to the identity commitment for the given + /// identifier and version. fn dip_subject_storage_key(identifier: &Self::Identifier, version: IdentityCommitmentVersion) -> Self::Key; } +/// Implementation of the [`ProviderParachainStorageInfo`] trait that builds on +/// the definitions of a runtime that includes the DIP provider pallet (e.g., +/// KILT runtimes). +/// The generic types are the following: +/// * `T`: The runtime including the [`pallet_dip_provider::Pallet`] pallet. pub struct ProviderParachainStateInfoViaProviderPallet(PhantomData); -impl ProviderParachainStateInfo for ProviderParachainStateInfoViaProviderPallet +impl ProviderParachainStorageInfo for ProviderParachainStateInfoViaProviderPallet where T: pallet_dip_provider::Config, { @@ -99,18 +128,37 @@ where } } +/// A trait that provides the consumer parachain runtime additional context to +/// verify cross-chain DID signatures by subjects of the provider parachain. pub trait DidSignatureVerifierContext { + /// Max number of blocks a cross-chain DID signature can have to be + /// considered fresh. const SIGNATURE_VALIDITY: u16; + /// The type of consumer parachain's block numbers. type BlockNumber; + /// The type of consumer parachain's hashes. type Hash; + /// Additional information that must be included in the payload being + /// DID-signed by the subject. type SignedExtra; - fn block_number() -> Self::BlockNumber; + /// Returns the block number of the consumer's chain in which the DID + /// signature is being evaluated. + fn current_block_number() -> Self::BlockNumber; + /// Returns the genesis hash of the consumer's chain. fn genesis_hash() -> Self::Hash; + /// Returns any additional info that must be appended to the payload before + /// verifying a cross-chain DID signature. fn signed_extra() -> Self::SignedExtra; } +/// Implementation of the [`DidSignatureVerifierContext`] trait that draws +/// information dynamically from the consumer's runtime using its system pallet. +/// The generic types are the following: +/// * `T`: The runtime including the [`frame_system::Pallet`] pallet. +/// * `SIGNATURE_VALIDITY`: The max number of blocks DID signatures can have to +/// be considered valid. pub struct FrameSystemDidSignatureContext(PhantomData); impl DidSignatureVerifierContext @@ -124,7 +172,7 @@ where type Hash = T::Hash; type SignedExtra = (); - fn block_number() -> Self::BlockNumber { + fn current_block_number() -> Self::BlockNumber { frame_system::Pallet::::block_number() } @@ -135,10 +183,14 @@ where fn signed_extra() -> Self::SignedExtra {} } +/// A trait that provides access to information on historical blocks. pub trait HistoricalBlockRegistry { + /// The runtime definition of block numbers. type BlockNumber; + /// The runtime hashing algorithm. type Hasher: sp_runtime::traits::Hash; + /// Retrieve a block hash given its number. fn block_hash_for(block: &Self::BlockNumber) -> Option>; } diff --git a/crates/kilt-dip-support/src/utils.rs b/crates/kilt-dip-support/src/utils.rs index 825826ce74..184ea12e8a 100644 --- a/crates/kilt-dip-support/src/utils.rs +++ b/crates/kilt-dip-support/src/utils.rs @@ -21,8 +21,11 @@ use scale_info::TypeInfo; use sp_core::RuntimeDebug; use sp_std::vec::Vec; +/// The output of a type implementing the [`sp_runtime::traits::Hash`] trait. pub type OutputOf = ::Output; +/// The vector of vectors that implements a statically-configured maximum length +/// without requiring const generics, used in benchmarking worst cases. #[derive(Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo, Clone)] pub struct BoundedBlindedValue(Vec>); diff --git a/dip-template/nodes/dip-consumer/Cargo.toml b/dip-template/nodes/dip-consumer/Cargo.toml index 1f5435f436..76fcdebdca 100644 --- a/dip-template/nodes/dip-consumer/Cargo.toml +++ b/dip-template/nodes/dip-consumer/Cargo.toml @@ -70,6 +70,7 @@ cumulus-relay-chain-interface.workspace = true substrate-build-script-utils.workspace = true [features] +default = [] runtime-benchmarks = [ "frame-benchmarking-cli/runtime-benchmarks", "dip-consumer-runtime-template/runtime-benchmarks", diff --git a/dip-template/nodes/dip-consumer/src/command.rs b/dip-template/nodes/dip-consumer/src/command.rs index 36898a0190..f41f3c337a 100644 --- a/dip-template/nodes/dip-consumer/src/command.rs +++ b/dip-template/nodes/dip-consumer/src/command.rs @@ -35,15 +35,17 @@ use sc_telemetry::TelemetryEndpoints; use sp_runtime::traits::AccountIdConversion; use crate::{ - chain_spec::{development_config, Extensions}, + chain_spec::{development_config, ChainSpec as ConsumerChainSpec, Extensions}, cli::{Cli, RelayChainCli, Subcommand}, service::{new_partial, start_parachain_node}, }; fn load_spec(id: &str) -> std::result::Result, String> { match id { - "dev" => Ok(Box::new(development_config())), - _ => Err("Unrecognized spec ID.".into()), + "dev" | "" => Ok(Box::new(development_config())), + path => Ok(Box::new(ConsumerChainSpec::from_json_file(std::path::PathBuf::from( + path, + ))?)), } } diff --git a/dip-template/nodes/dip-provider/Cargo.toml b/dip-template/nodes/dip-provider/Cargo.toml index 584bca26ee..6ca76df480 100644 --- a/dip-template/nodes/dip-provider/Cargo.toml +++ b/dip-template/nodes/dip-provider/Cargo.toml @@ -70,6 +70,7 @@ cumulus-relay-chain-interface.workspace = true substrate-build-script-utils.workspace = true [features] +default = [] runtime-benchmarks = [ "frame-benchmarking-cli/runtime-benchmarks", "dip-provider-runtime-template/runtime-benchmarks" diff --git a/dip-template/nodes/dip-provider/src/command.rs b/dip-template/nodes/dip-provider/src/command.rs index 976c311898..3fb8b8322b 100644 --- a/dip-template/nodes/dip-provider/src/command.rs +++ b/dip-template/nodes/dip-provider/src/command.rs @@ -35,15 +35,17 @@ use sc_telemetry::TelemetryEndpoints; use sp_runtime::traits::AccountIdConversion; use crate::{ - chain_spec::{development_config, Extensions}, + chain_spec::{development_config, ChainSpec as ProviderChainSpec, Extensions}, cli::{Cli, RelayChainCli, Subcommand}, service::{new_partial, start_parachain_node}, }; fn load_spec(id: &str) -> std::result::Result, String> { match id { - "dev" => Ok(Box::new(development_config())), - _ => Err("Unrecognized spec ID.".into()), + "dev" | "" => Ok(Box::new(development_config())), + path => Ok(Box::new(ProviderChainSpec::from_json_file(std::path::PathBuf::from( + path, + ))?)), } } diff --git a/dip-template/runtimes/dip-consumer/src/dip.rs b/dip-template/runtimes/dip-consumer/src/dip.rs index 03ef8efd18..823fcd37f3 100644 --- a/dip-template/runtimes/dip-consumer/src/dip.rs +++ b/dip-template/runtimes/dip-consumer/src/dip.rs @@ -30,6 +30,10 @@ use sp_runtime::traits::BlakeTwo256; use crate::{weights, AccountId, DidIdentifier, Runtime, RuntimeCall, RuntimeOrigin}; pub type MerkleProofVerifierOutput = >::VerificationResult; +/// The verifier logic assumes the provider is a sibling KILT parachain, and +/// that a KILT subject can provide DIP proof that reveal at most 10 DID keys +/// and 10 linked accounts. Calls that do not pass the [`DipCallFilter`] will be +/// discarded early on in the verification process. pub type ProofVerifier = KiltVersionedSiblingProviderVerifier< ProviderRuntime, ConstU32<2_000>, @@ -43,8 +47,14 @@ pub type ProofVerifier = KiltVersionedSiblingProviderVerifier< impl pallet_dip_consumer::Config for Runtime { type DipCallOriginFilter = PreliminaryDipOriginFilter; + // Any signed origin can submit a cross-chain DIP tx, since subject + // authentication (and optional binding to the tx submitter) is performed in the + // DIP proof verification step. type DispatchOriginCheck = EnsureSigned; type Identifier = DidIdentifier; + // Local identity info contains a simple `u128` representing a nonce. This means + // that two cross-chain operations targeting the same chain and with the same + // nonce cannot be both successfully evaluated. type LocalIdentityInfo = u128; type ProofVerifier = ProofVerifier; type RuntimeCall = RuntimeCall; @@ -52,6 +62,8 @@ impl pallet_dip_consumer::Config for Runtime { type WeightInfo = weights::pallet_dip_consumer::WeightInfo; } +/// A preliminary DID call filter that only allows dispatching of extrinsics +/// from the [`pallet_postit::Pallet`] pallet. pub struct PreliminaryDipOriginFilter; impl Contains for PreliminaryDipOriginFilter { @@ -72,6 +84,9 @@ impl Contains for PreliminaryDipOriginFilter { } } +/// Calls to the [`pallet_postit::Pallet`] pallet or batches containing only +/// calls to the [`pallet_postit::Pallet`] pallet will go through if authorized +/// by a DID's authentication key. Everything else will fail. fn derive_verification_key_relationship(call: &RuntimeCall) -> Option { match call { RuntimeCall::PostIt { .. } => Some(DidVerificationKeyRelationship::Authentication), @@ -104,11 +119,18 @@ fn single_key_relationship<'a>( }) } +/// Errors generated by calls that do not pass the filter. pub enum DipCallFilterError { + /// The call cannot be dispatched with the provided origin. BadOrigin, + /// The call could be dispatched with the provided origin, but it has been + /// authorized with the wrong DID key. WrongVerificationRelationship, } +/// A call filter that requires calls to the [`pallet_postit::Pallet`] pallet to +/// be authorized with a DID signature generated with a key of a given +/// verification relationship. pub struct DipCallFilter; impl DipCallOriginFilter for DipCallFilter { @@ -129,6 +151,8 @@ impl DipCallOriginFilter for DipCallFilter { } impl pallet_relay_store::Config for Runtime { + // The pallet stores the last 100 relaychain state roots, making state proofs + // valid for at most 100 * 6 = 600 seconds. type MaxRelayBlocksStored = ConstU32<100>; type WeightInfo = weights::pallet_relay_store::WeightInfo; } diff --git a/dip-template/runtimes/dip-consumer/src/lib.rs b/dip-template/runtimes/dip-consumer/src/lib.rs index daf21c15fa..06e031d2d5 100644 --- a/dip-template/runtimes/dip-consumer/src/lib.rs +++ b/dip-template/runtimes/dip-consumer/src/lib.rs @@ -16,6 +16,17 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org +//! Runtime template of a Decentralized Identity Provider (DIP) consumer, which +//! does not itself include any identity-related pallets, but only the +//! [`pallet_dip_consumer::Pallet`] pallet (configured to work with the +//! [`dip_provider_runtime_template::Runtime`] template runtime), the +//! [`pallet_relay_store::Pallet`] pallet to keep track of finalized relaychain +//! state roots, and the example [`pallet_postit::Pallet`], which allows any +//! entity that can be identified with a username (e.g., a web3name carried over +//! from the provider chain) to post a message on chain, reply to another +//! on-chain message (including another reply), or like a message and/or any of +//! its replies. + #![cfg_attr(not(feature = "std"), no_std)] #![recursion_limit = "256"] diff --git a/dip-template/runtimes/dip-consumer/src/origin_adapter.rs b/dip-template/runtimes/dip-consumer/src/origin_adapter.rs index ebde676e7a..5e0319f62e 100644 --- a/dip-template/runtimes/dip-consumer/src/origin_adapter.rs +++ b/dip-template/runtimes/dip-consumer/src/origin_adapter.rs @@ -24,6 +24,11 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_core::RuntimeDebug; +/// An origin adapter which is used to make sure that a given [`DipOrigin`] +/// contains, among other things, a web3name. If a pallet extrinsic that +/// requires this origin is called with a DIP proof that does not revealed the +/// web3name linked to the subject, the extrinsic will fail with a `BadOrigin` +/// error. pub struct EnsureDipOriginAdapter; impl EnsureOrigin for EnsureDipOriginAdapter { @@ -39,6 +44,8 @@ impl EnsureOrigin for EnsureDipOriginAdapter { } } +/// A wrapper around a [`DipOrigin`] that makes sure the origin has a web3name, +/// or else the origin is invalid. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct DipOriginAdapter(DipOrigin); diff --git a/dip-template/runtimes/dip-provider/src/dip.rs b/dip-template/runtimes/dip-provider/src/dip.rs index 66f8ee88ec..f6cd7abb2e 100644 --- a/dip-template/runtimes/dip-provider/src/dip.rs +++ b/dip-template/runtimes/dip-provider/src/dip.rs @@ -39,12 +39,21 @@ const MAX_LINKED_ACCOUNTS: u32 = 20; pub mod runtime_api { use super::*; + /// Parameters for a DIP proof request. #[derive(Encode, Decode, TypeInfo)] pub struct DipProofRequest { + /// The subject identifier for which to generate the DIP proof. pub(crate) identifier: DidIdentifier, + /// The DIP version. pub(crate) version: IdentityCommitmentVersion, + /// The DID key IDs of the subject's DID Document to reveal in the DIP + /// proof. pub(crate) keys: Vec>, + /// The list of accounts linked to the subject's DID to reveal in the + /// DIP proof. pub(crate) accounts: Vec, + /// A flag indicating whether the web3name claimed by the DID subject + /// should revealed in the DIP proof. pub(crate) should_include_web3_name: bool, } @@ -73,6 +82,8 @@ pub mod deposit { DipProvider, } + /// The namespace to use in the [`pallet_deposit_storage::Pallet`] to store + /// all deposits related to DIP commitments. pub struct DipProviderDepositNamespace; impl Get for DipProviderDepositNamespace { @@ -81,8 +92,11 @@ pub mod deposit { } } + /// The amount of tokens locked for each identity commitment. pub const DEPOSIT_AMOUNT: Balance = 2 * UNIT; + /// The additional logic to execute whenever a deposit is removed by its + /// owner directly via the [`pallet_deposit_storage::Pallet`] pallet. pub type DepositCollectorHooks = FixedDepositCollectorViaDepositsPallet>; @@ -100,6 +114,11 @@ pub mod deposit { } } + /// The logic to execute whenever an identity commitment is generated and + /// stored in the [`pallet_dip_provider::Pallet`] pallet. + /// + /// Upon storing and removing identity commitments, this hook will reserve + /// or release deposits from the [`pallet_deposit_storage::Pallet`] pallet. pub struct DepositHooks; impl DepositStorageHooks for DepositHooks { @@ -172,7 +191,10 @@ pub mod deposit { impl pallet_deposit_storage::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHooks = deposit::PalletDepositStorageBenchmarkHooks; + // Any signed origin can submit the tx, which will go through only if the + // deposit payer matches the signed origin. type CheckOrigin = EnsureSigned; + // The balances pallet is used to reserve/unreserve tokens. type Currency = Balances; type DepositHooks = DepositHooks; type MaxKeyLength = ConstU32<256>; @@ -183,10 +205,16 @@ impl pallet_deposit_storage::Config for Runtime { } impl pallet_dip_provider::Config for Runtime { + // Only DID origins can submit the commitment identity tx, which will go through + // only if the DID in the origin matches the identifier specified in the tx. type CommitOriginCheck = EnsureDidOrigin; type CommitOrigin = DidRawOrigin; type Identifier = DidIdentifier; + // The identity commitment is defined as the Merkle root of the linked identity + // info, as specified by the [`LinkedDidInfoProvider`]. type IdentityCommitmentGenerator = DidMerkleRootGenerator; + // Identity info is defined as the collection of DID keys, linked accounts, and + // the optional web3name of a given DID subject. type IdentityProvider = LinkedDidInfoProvider; type ProviderHooks = deposit::DepositCollectorHooks; type RuntimeEvent = RuntimeEvent; diff --git a/dip-template/runtimes/dip-provider/src/lib.rs b/dip-template/runtimes/dip-provider/src/lib.rs index 90e96c9a94..8a083b92b9 100644 --- a/dip-template/runtimes/dip-provider/src/lib.rs +++ b/dip-template/runtimes/dip-provider/src/lib.rs @@ -16,6 +16,12 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org +//! Runtime template of a Decentralized Identity Provider (DIP) provider, which +//! includes, beyond system pallets, [`did::Pallet`], +//! [`pallet_web3_names::Pallet`], and [`pallet_did_lookup::Pallet`] pallets, as +//! well as the [`pallet_dip_provider::Pallet`] pallet and the +//! [`pallet_deposit_storage::Pallet`] pallet. + #![cfg_attr(not(feature = "std"), no_std)] #![recursion_limit = "256"] diff --git a/pallets/pallet-deposit-storage/src/deposit.rs b/pallets/pallet-deposit-storage/src/deposit.rs index c01296e23e..36b902df5b 100644 --- a/pallets/pallet-deposit-storage/src/deposit.rs +++ b/pallets/pallet-deposit-storage/src/deposit.rs @@ -32,12 +32,18 @@ use sp_std::marker::PhantomData; use crate::{BalanceOf, Config, Error, HoldReason, Pallet}; +/// Details associated to an on-chain deposit. #[derive(Clone, Debug, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo, MaxEncodedLen)] pub struct DepositEntry { + /// The [`Deposit`] entry. pub(crate) deposit: Deposit, + /// The `Reason` for the deposit. pub(crate) reason: Reason, } +/// Type implementing the [`DipProviderHooks`] hooks trait by taking a deposit +/// whenever an identity commitment is stored, and releasing the deposit +/// whenever an identity commitment is removed. pub struct FixedDepositCollectorViaDepositsPallet( PhantomData<(DepositsNamespace, FixedDepositAmount)>, ); diff --git a/pallets/pallet-deposit-storage/src/lib.rs b/pallets/pallet-deposit-storage/src/lib.rs index eeca637f53..0043f9eedf 100644 --- a/pallets/pallet-deposit-storage/src/lib.rs +++ b/pallets/pallet-deposit-storage/src/lib.rs @@ -16,8 +16,14 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org +//! Pallet to store namespaced deposits for the configured `Currency`. It allows +//! the original payer of a deposit to claim it back, triggering a hook to +//! optionally perform related actions somewhere else in the runtime. +//! Each deposit is identified by a namespace and a key. There cannot be two +//! equal keys under the same namespace, but the same key can be present under +//! different namespaces. + #![cfg_attr(not(feature = "std"), no_std)] -#![recursion_limit = "256"] mod default_weights; mod deposit; @@ -67,20 +73,30 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { + /// The maximum length of keys. #[pallet::constant] type MaxKeyLength: Get; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHooks: crate::traits::BenchmarkHooks; + /// The origin check, returning an `AccountId` upon completion, for who + /// can reclaim a deposit. type CheckOrigin: EnsureOrigin; + /// The currency from which deposits are to be taken. type Currency: Mutate; + /// Additional logic to execute whenever a new deposit a created or a + /// deposit is released. type DepositHooks: DepositStorageHooks; + /// The type of a deposit namespace. type Namespace: Parameter + MaxEncodedLen; + /// The aggregated `Event` type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The aggregated `HoldReason` type. type RuntimeHoldReason: From + Clone + PartialEq + Debug + FullCodec + MaxEncodedLen + TypeInfo; type WeightInfo: WeightInfo; } + /// The hold reasons for deposits taken by the pallet. #[pallet::composite_enum] pub enum HoldReason { Deposit, @@ -88,28 +104,45 @@ pub mod pallet { #[pallet::error] pub enum Error { + /// The deposit with the provided key was not found within the specified + /// namespace. DepositNotFound, + /// A deposit with the provided key already exists within the specified + /// namespace. DepositExisting, + /// The origin was not authorized to perform the operation on the + /// specified deposit entry. Unauthorized, + /// The external hook failed. Hook(u16), } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { + /// A new deposit has been reserved and stored. DepositAdded { + /// The deposit namespace. namespace: T::Namespace, + /// The deposit key. key: DepositKeyOf, + /// The deposit details. deposit_entry: DepositEntryOf, }, + /// A deposit has been released and deleted from storage. DepositReclaimed { + /// The deposit namespace. namespace: T::Namespace, + /// The deposit key. key: DepositKeyOf, + /// The deposit details. deposit_entry: DepositEntryOf, }, } - // Double map (namespace, key) -> deposit + /// Storage of all deposits. Its first key is a namespace, and the second + /// one the deposit key. Its value includes the details associated to a + /// deposit instance. #[pallet::storage] #[pallet::getter(fn deposits)] pub(crate) type Deposits = @@ -121,6 +154,10 @@ pub mod pallet { #[pallet::call] impl Pallet { + /// Reclaim a deposit that was previously taken. If there is no deposit + /// with the given key under the given namespace, it returns an error. + /// If a deposit exists, the deposit hooks are invoked after the deposit + /// has been removed from the pallet storage. #[pallet::call_index(0)] #[pallet::weight({ ::WeightInfo::reclaim_deposit() @@ -135,6 +172,10 @@ pub mod pallet { } impl Pallet { + /// Add a deposit identified by the given key under the given namespace. + /// If there is already a deposit entry for the same key under the same + /// namespace, it returns an error. It also returns an error if the + /// deposit cannot be reserved on the pallet's `Currency`. pub fn add_deposit(namespace: T::Namespace, key: DepositKeyOf, entry: DepositEntryOf) -> DispatchResult { Deposits::::try_mutate(&namespace, &key, |deposit_entry| match deposit_entry { Some(_) => Err(DispatchError::from(Error::::DepositExisting)), @@ -156,6 +197,11 @@ pub mod pallet { Ok(()) } + /// Remove and release a deposit identified by the given key under the + /// given namespace. If there is no deposit with under the provided + /// namespace with the provided key, it returns an error. It also + /// returns an error if the deposit cannot be released on the pallet's + /// `Currency`. pub fn remove_deposit( namespace: &T::Namespace, key: &DepositKeyOf, diff --git a/pallets/pallet-deposit-storage/src/traits.rs b/pallets/pallet-deposit-storage/src/traits.rs index b4e179937a..fb0270e764 100644 --- a/pallets/pallet-deposit-storage/src/traits.rs +++ b/pallets/pallet-deposit-storage/src/traits.rs @@ -18,12 +18,16 @@ use crate::{Config, DepositEntryOf, DepositKeyOf}; +/// A trait to configure additional custom logic whenever a deposit-related +/// operation takes place. pub trait DepositStorageHooks where Runtime: Config, { type Error: Into; + /// Called by the pallet whenever a deposit for a given namespace and key is + /// removed. fn on_deposit_reclaimed( namespace: &Runtime::Namespace, key: &DepositKeyOf, @@ -31,6 +35,7 @@ where ) -> Result<(), Self::Error>; } +/// Dummy implementation of the [`DepositStorageHooks`] trait that does a noop. pub struct NoopDepositStorageHooks; impl DepositStorageHooks for NoopDepositStorageHooks diff --git a/pallets/pallet-dip-consumer/Cargo.toml b/pallets/pallet-dip-consumer/Cargo.toml index 5217db0458..f19f17ab72 100644 --- a/pallets/pallet-dip-consumer/Cargo.toml +++ b/pallets/pallet-dip-consumer/Cargo.toml @@ -6,7 +6,7 @@ edition.workspace = true homepage.workspace = true license-file.workspace = true name = "pallet-dip-consumer" -readme.workspace = true +readme = "README.md" repository.workspace = true version.workspace = true diff --git a/pallets/pallet-dip-consumer/README.md b/pallets/pallet-dip-consumer/README.md new file mode 100644 index 0000000000..e19a811d07 --- /dev/null +++ b/pallets/pallet-dip-consumer/README.md @@ -0,0 +1,60 @@ +# Decentralized Identity Provider (DIP) provider consumer pallet + +This pallet is a core component of the Decentralized Identity Provider protocol. +It enables entities with an identity on a connected Substrate-based chain (provider) to use those identities on the chain this pallet is deployed (consumers) without requiring those entities to set up a new identity locally. +A consumer chain is *connected* to a provider if there is a way for the consumer chain to verify state proofs about parts of the state of the provider chain. + +A cross-chain transaction with DIP assumes the entity submitting the transaction has already generated a cross-chain identity commitment on the provider chain, by interacting with the DIP provider pallet on the provider chain. +With a generated identity commitment, a cross-chain transaction flow for a generic entity `A` works as follows: + +1. `A` generates a state proof proving the state of the identity commitment on the provider chain. +2. `A` generates any additional information required for an identity proof to be successfully verified by the consumer runtime. +3. `A`, using their account `AccC` on the consumer chain, calls the `dispatch_as` extrinsic by providing its identifier on the provider chain, the generated proof, and the `Call` to be dispatched on the consumer chain. + 1. This pallet verifies if the proof is correct, if not it returns an error. + 2. This pallet dispatches the provided `Call` with a new origin created by this pallet, returning any errors the dispatch action returns. The origin contains the information revealed in the proof, the identifier of the acting subject and the account `AccC` dispatching the transaction. + +The pallet is agnostic over the chain-specific definition of *identity proof verifier* and *identifier*, although, when deployed, they must be configured to respect the definition of identity and identity commitment established by the provider this pallet is linked to. + +For instance, if the provider establishes that an identity commitment is a Merkle root of a set of public keys, an identity proof for the consumer will most likely be a Merkle proof revealing a subset of those keys. +Similarly, if the provider defines an identity commitment as some ZK-commitment, the respective identity proof on the consumer chain will be a ZK-proof verifying the validity of the commitment and therefore of the revealed information. + +For identifiers, if the provider establishes that an identifier is a public key, the same definition must be used in the consumer pallet. +Other definitions for an identifier, such as a simple integer or a [Decentralized Identifier (DID)](https://www.w3.org/TR/did-core/), must also be configured in the same way. + +The pallet allows the consumer runtime to define some `LocalIdentityInfo` associated with each identifier, which the pallet's proof verifier can access and optionally modify upon proof verification. +Any changes made to the `LocalIdentityInfo` will be persisted if the identity proof is verified correctly and the extrinsic executed successfully. + +If the consumer does not need to store anything in addition to the information an identity proof conveys, they can use an empty tuple `()` for the local identity info. +Another example could be the use of signatures, which requires a nonce to avoid replay protections. +In this case, a numeric type such as a `u64` or a `u128` could be used, and increased by the proof verifier when validating each new cross-chain transaction proof. + +## The `Config` trait + +Being chain-agnostic, most of the runtime configurations must be passed to the pallet's `Config` trait. +Nevertheless, most of the types provided must reflect the definition of identity and identity commitment that the identity provider chain has established. +The trait has the following components: + +* `type DipCallOriginFilter: Contains>`: A preliminary filter that checks whether a provided `Call` accepts a DIP origin or not. If a call such as a system call does not accept a DIP origin, there is no need to verify the identity proof, hence the execution can bail out early. This does not guarantee that the dispatch call will succeed, but rather than it will mostly not fail with a `BadOrigin` error. +* `type DispatchOriginCheck: EnsureOrigin<::RuntimeOrigin, Success = Self::AccountId>`: The origin check on the `dispatch_as` extrinsic to verify that the caller is authorized to call the extrinsic. If successful, the check must return a `AccountId` as defined by the consumer runtime. +* `type Identifier: Parameter + MaxEncodedLen`: The type of a subject identifier. This must match the definition of `Identifier` the identity provider has defined in their deployment of the provider pallet. +* `type LocalIdentityInfo: FullCodec + TypeInfo + MaxEncodedLen`: Any additional information that must be available only to the provider runtime that is required to provide additional context when verifying a cross-chain identity proof. +* `type ProofVerifier: IdentityProofVerifier`: The core component of this pallet. It takes care of validating an identity proof and optionally update any `LocalIdentityInfo`. It also defines, via its associated type, the structure of the identity proof that must be passed to the `dispatch_as` extrinsic. Although not directly, the proof structure depends on the information that goes into the identity commitment on the provider chain, as that defines what information can be revealed as part of the commitment proof. Additional info to satisfy requirements according to the `LocalIdentityInfo` (e.g., a signature) must also be provided in the proof. +* `type RuntimeCall: Parameter + Dispatchable::RuntimeOrigin>`: The aggregated `Call` type. +* `type RuntimeOrigin: From> + From<::RuntimeOrigin>`: The aggregated `Origin` type, which must include the origin exposed by this pallet. + +## Storage + +The pallet contains a single storage element, the `IdentityEntries` map. +It maps from a subject `Identifier` to an instance of `LocalIdentityInfo`. + +This information is updated by the proof verifier whenever a new cross-chain transaction and its proof is submitted. + +## Origin + +Because the pallet allows other `Call`s to be dispatched after an identity proof has been verified, it also exposes a `Origin` that can be used for those calls that require indeed a call to be DIP-authorized. + +The origin is created after the identity proof has been successfully verified by the proof verifier, and it includes the identifier of the subject, the address of the tx submitter, and the result returned by the proof verifier upon successful verification. + +## Calls (bullet numbers represent each call's encoded index) + +0. `pub fn dispatch_as(origin: OriginFor, identifier: T::Identifier, proof: IdentityProofOf, call: Box>) -> DispatchResult`: Try to dispatch a new local call only if it passes all the DIP requirements. Specifically, the call will be dispatched if it passes the preliminary `DipCallOriginFilter` and if the proof verifier returns an `Ok(verification_result)` value. The value is then added to the `DipOrigin` and passed down as the origin for the specified `Call`. If the whole execution terminates successfully, any changes applied to the `LocalIdentityInfo` by the proof verifier are persisted to the pallet storage. diff --git a/pallets/pallet-dip-consumer/src/identity.rs b/pallets/pallet-dip-consumer/src/identity.rs deleted file mode 100644 index eed39f68d4..0000000000 --- a/pallets/pallet-dip-consumer/src/identity.rs +++ /dev/null @@ -1,43 +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 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 ccdd1e0883..148eb88b82 100644 --- a/pallets/pallet-dip-consumer/src/lib.rs +++ b/pallets/pallet-dip-consumer/src/lib.rs @@ -16,14 +16,13 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -// TODO: Pallet description - #![cfg_attr(not(feature = "std"), no_std)] +#![doc = include_str!("../README.md")] -mod default_weights; -pub mod identity; pub mod traits; +mod default_weights; + #[cfg(test)] pub mod mock; @@ -57,34 +56,58 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); - #[pallet::storage] - #[pallet::getter(fn identity_proofs)] - pub(crate) type IdentityEntries = - StorageMap<_, Twox64Concat, ::Identifier, ::LocalIdentityInfo>; - #[pallet::config] pub trait Config: frame_system::Config { - /// Preliminary filter to filter out calls before doing any heavier - /// computations. + /// A preliminary filter that checks whether a provided `Call` accepts a + /// DIP origin or not. If a call such as a system call does not accept a + /// DIP origin, there is no need to verify the identity proof, hence the + /// execution can bail out early. This does not guarantee that the + /// dispatch call will succeed, but rather than it will mostly not fail + /// with a `BadOrigin` error. type DipCallOriginFilter: Contains>; - /// The origin check for the `dispatch_as` call. + /// The origin check on the `dispatch_as` extrinsic to verify that the + /// caller is authorized to call the extrinsic. If successful, the check + /// must return a `AccountId` as defined by the consumer runtime. type DispatchOriginCheck: EnsureOriginWithArg< ::RuntimeOrigin, Self::Identifier, Success = Self::AccountId, >; - /// The identifier of a subject, e.g., a DID. + /// The type of a subject identifier. This must match the definition of + /// `Identifier` the identity provider has defined in their deployment + /// of the provider pallet. type Identifier: Parameter + MaxEncodedLen; - /// The details stored in this pallet associated with any given subject. + /// Any additional information that must be available only to the + /// provider runtime that is required to provide additional context when + /// verifying a cross-chain identity proof. type LocalIdentityInfo: FullCodec + TypeInfo + MaxEncodedLen; - /// The logic of the proof verifier, called upon each execution of the - /// `dispatch_as` extrinsic. + /// The core component of this pallet. It takes care of validating an + /// identity proof and optionally update any `LocalIdentityInfo`. It + /// also defines, via its associated type, the structure of the identity + /// proof that must be passed to the `dispatch_as` extrinsic. Although + /// not directly, the proof structure depends on the information that + /// goes into the identity commitment on the provider chain, as that + /// defines what information can be revealed as part of the commitment + /// proof. Additional info to satisfy requirements according to the + /// `LocalIdentityInfo` (e.g., a signature) must also be provided in the + /// proof. type ProofVerifier: IdentityProofVerifier; + /// The aggregated `Call` type. type RuntimeCall: Parameter + Dispatchable::RuntimeOrigin> + GetDispatchInfo; + /// The aggregated `Origin` type, which must include the origin exposed + /// by this pallet. type RuntimeOrigin: From> + From<::RuntimeOrigin>; type WeightInfo: WeightInfo; } + /// The pallet contains a single storage element, the `IdentityEntries` map. + /// It maps from a subject `Identifier` to an instance of + /// `LocalIdentityInfo`. + #[pallet::storage] + #[pallet::getter(fn identity_proofs)] + pub(crate) type IdentityEntries = + StorageMap<_, Twox64Concat, ::Identifier, ::LocalIdentityInfo>; + #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); @@ -97,15 +120,24 @@ pub mod pallet { Filtered, } - /// The origin this pallet creates after a user has provided a valid - /// identity proof to dispatch other calls. + /// The origin is created after the identity proof has been successfully + /// verified by the proof verifier, and it includes the identifier of the + /// subject, the address of the tx submitter, and the result returned by the + /// proof verifier upon successful verification. #[pallet::origin] pub type Origin = DipOrigin<::Identifier, ::AccountId, VerificationResultOf>; #[pallet::call] impl Pallet { - // TODO: Replace with a SignedExtra. + /// Try to dispatch a new local call only if it passes all the DIP + /// requirements. Specifically, the call will be dispatched if it passes + /// the preliminary `DipCallOriginFilter` and if the proof verifier + /// returns a `Ok(verification_result)` value. The value is then added + /// to the `DipOrigin` and passed down as the origin for the specified + /// `Call`. If the whole execution terminates successfully, any changes + /// applied to the `LocalIdentityInfo` by the proof verifier are + /// persisted to the pallet storage. #[pallet::call_index(0)] #[pallet::weight({ let extrinsic_weight = ::WeightInfo::dispatch_as(); diff --git a/pallets/pallet-dip-consumer/src/origin.rs b/pallets/pallet-dip-consumer/src/origin.rs index 415d055e0f..5281b25914 100644 --- a/pallets/pallet-dip-consumer/src/origin.rs +++ b/pallets/pallet-dip-consumer/src/origin.rs @@ -22,13 +22,21 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_std::marker::PhantomData; +/// An origin passed down to the to-be-dispatched `Call` upon successful DIP +/// proof verification. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct DipOrigin { + /// The subject identifier which is performing the DIP operation. pub identifier: Identifier, + /// The local account address of the tx submitter. pub account_address: AccountId, + /// Details returned by the proof verifier upon successful proof + /// verification. pub details: Details, } +/// Implementation of the `EnsureOrigin` trait verifying that a given origin is +/// a `DipOrigin`. pub struct EnsureDipOrigin(PhantomData<(Identifier, AccountId, Details)>); #[cfg(not(feature = "runtime-benchmarks"))] diff --git a/pallets/pallet-dip-consumer/src/traits.rs b/pallets/pallet-dip-consumer/src/traits.rs index 9213166288..bfc0bedee5 100644 --- a/pallets/pallet-dip-consumer/src/traits.rs +++ b/pallets/pallet-dip-consumer/src/traits.rs @@ -20,14 +20,25 @@ use frame_support::Parameter; use crate::{Config, RuntimeCallOf}; +/// A trait to verify a given DIP identity proof. The trait depends on the +/// runtime definition of the consumer pallet's `Identifier` and of the system +/// pallet's `AccountId`. The type of proof expected and the type returned upon +/// successful verification is defined as an associated type. pub trait IdentityProofVerifier where Runtime: Config, { + /// The error returned upon failed DIP proof verification. type Error: Into; + /// The accepted type for a DIP identity proof. type Proof: Parameter; + /// The type returned upon successful DIP proof verification. type VerificationResult; + /// Verify a given DIP proof given the calling context, including the call + /// being dispatched, the DIP subject dispatching it, the account submitting + /// the DIP tx, and the identity details of the DIP subject as stored in the + /// consumer pallet. fn verify_proof_for_call_against_details( call: &RuntimeCallOf, subject: &Runtime::Identifier, @@ -37,7 +48,8 @@ where ) -> Result; } -// Always returns success. +/// Dummy implementation of the [`IdentityProofVerifier`] trait which always +/// returns `Ok(())`. pub struct SuccessfulProofVerifier; impl IdentityProofVerifier for SuccessfulProofVerifier where diff --git a/pallets/pallet-dip-provider/Cargo.toml b/pallets/pallet-dip-provider/Cargo.toml index 6c3c0942d3..6e284e0d1c 100644 --- a/pallets/pallet-dip-provider/Cargo.toml +++ b/pallets/pallet-dip-provider/Cargo.toml @@ -6,7 +6,7 @@ edition.workspace = true homepage.workspace = true license-file.workspace = true name = "pallet-dip-provider" -readme.workspace = true +readme = "README.md" repository.workspace = true version.workspace = true diff --git a/pallets/pallet-dip-provider/README.md b/pallets/pallet-dip-provider/README.md new file mode 100644 index 0000000000..0b384009e0 --- /dev/null +++ b/pallets/pallet-dip-provider/README.md @@ -0,0 +1,50 @@ +# Decentralized Identity Provider (DIP) provider pallet + +This pallet is a core component of the Decentralized Identity Provider protocol. +It enables a Substrate-based chain (provider) to bridge the identities of its users to other connected chains (consumers) trustlessly. +A consumer chain is *connected* to a provider if there is a way for the consumer chain to verify state proofs about parts of the state of the provider chain. + +The pallet is agnostic over the chain-specific definition of *identity*, and delegates the definition of it to the provider chain's runtime. + +What the pallet stores are *identity commitments*, which are opaque byte blobs put in the pallet storage and on which the cross-chain identity bridging protocol can be built. +As for identities, the definition of an identity commitment must be provided by the runtime and is therefore provider-specific. +Naturally, this definition must be made available to consumers willing to integrate the identities living on the provider chain. + +Because providers and consumers evolve at different speeds, identity commitments are versioned. +This allows the provider chain to upgrade to a newer commitment scheme, while still giving its users the possibility to use the old version, if the chains on which they want to use their identity does not yet support the new scheme. + +Identity commitments can be replaced (e.g., if something in the identity info changes), or removed altogether by the identity subject. +After removal, the identity becomes unusable cross-chain, although it will still continue to exist on the provider chain and will be usable for local operations. + +## The `Config` trait + +Being chain-agnostic, most of the runtime configurations must be passed to the pallet's `Config` trait. Specifically: + +* `type CommitOriginCheck: EnsureOrigin`: The check ensuring a given runtime origin is allowed to generate and remove identity commitments. +* `type CommitOrigin: SubmitterInfo`: The resulting origin if `CommitOriginCheck` returns with errors. The origin is not required to be an `AccountId`, but must include information about the `AccountId` of the tx submitter. +* `type Identifier: Parameter + MaxEncodedLen`: The type of an identifier used to retrieve identity information about a subject. +* `type IdentityCommitmentGenerator: IdentityCommitmentGenerator`: The type responsible for generating identity commitments, given the identity information associated to a given `Identifier`. +* `type IdentityProvider: IdentityProvider`: The type responsible for retrieving the information associated to a subject given their identifier. The information can potentially be retrieved from any source, using a combination of on-chain and off-chain solutions. +* `type IdentityProvider: IdentityProvider`: Customizable external logic to handle events in which a new identity commitment is generated or removed. +* `type RuntimeEvent: From> + IsType<::RuntimeEvent>`: The aggregate `Event` type. + +## Storage + +The pallet contains a single storage element, the `IdentityCommitments` double map. +Its first key is the `Identifier` of subjects, while the second key is the commitment version. +The values are identity commitments. + +As mentioned above, a double map allows the same subject to have one commitment for each version supported by the provider, without forcing consumers to upgrade to a new version to support the latest commitment scheme. + +## Events + +The pallet generates two events: a `VersionedIdentityCommitted` and a `VersionedIdentityDeleted`. + +The `VersionedIdentityCommited` is called whenever a new commitment is stored, and contains information about the `Identifier` of the subject, the value of the commitment, and the commitment version. + +Similarly, the `VersionedIdentityDeleted`, is called whenever a commitment is deleted, and contains information about the `Identifier` of the subject and the version of the commitment deleted. + +## Calls (bullet numbers represent each call's encoded index) + +0. `pub fn commit_identity(origin: OriginFor, identifier: T::Identifier, version: Option ) -> DispatchResult`: Generate a new versioned commitment for the subject identified by the provided `Identifier`. If an old commitment for the same version is present, it is overridden. Hooks are called before the new commitment is stored, and optionally before the old one is replaced. +1. `pub fn delete_identity_commitment(origin: OriginFor, identifier: T::Identifier, version: Option) -> DispatchResult`: Delete an identity commitment of a specific version for a specific `Identifier`. If a commitment of the provided version does not exist for the given `Identifier`, an error is returned. Hooks are called after the commitment has been removed. diff --git a/pallets/pallet-dip-provider/src/lib.rs b/pallets/pallet-dip-provider/src/lib.rs index a3672eded9..e6eea5e5a8 100644 --- a/pallets/pallet-dip-provider/src/lib.rs +++ b/pallets/pallet-dip-provider/src/lib.rs @@ -16,9 +16,8 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -// TODO: Pallet description - #![cfg_attr(not(feature = "std"), no_std)] +#![doc = include_str!("../README.md")] mod default_weights; pub mod traits; @@ -56,16 +55,36 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { + /// The check ensuring a given runtime origin is allowed to generate and + /// remove identity commitments. type CommitOriginCheck: EnsureOriginWithArg; + /// The resulting origin if `CommitOriginCheck` returns with errors. The + /// origin is not required to be an `AccountId`, but must include + /// information about the `AccountId` of the tx submitter. type CommitOrigin: SubmitterInfo; + /// The type of an identifier used to retrieve identity information + /// about a subject. type Identifier: Parameter + MaxEncodedLen; + /// The type responsible for generating identity commitments, given the + /// identity information associated to a given `Identifier`. type IdentityCommitmentGenerator: IdentityCommitmentGenerator; + /// The type responsible for retrieving the information associated to a + /// subject given their identifier. The information can potentially be + /// retrieved from any source, using a combination of on-chain and + /// off-chain solutions. type IdentityProvider: IdentityProvider; + /// Customizable external logic to handle events in which a new identity + /// commitment is generated or removed. type ProviderHooks: ProviderHooks; + /// The aggregate `Event` type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; type WeightInfo: WeightInfo; } + /// The pallet contains a single storage element, the `IdentityCommitments` + /// double map. Its first key is the `Identifier` of subjects, while the + /// second key is the commitment version. The values are identity + /// commitments. #[pallet::storage] #[pallet::getter(fn identity_commitments)] pub type IdentityCommitments = StorageDoubleMap< @@ -84,27 +103,42 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { + /// A new commitment has been stored. VersionedIdentityCommitted { + /// The identifier of the identity committed. identifier: T::Identifier, + /// The value of the commitment. commitment: IdentityCommitmentOf, + /// The version of the commitment. version: IdentityCommitmentVersion, }, + /// A commitment has been deleted. VersionedIdentityDeleted { + /// The identifier of the identity committed. identifier: T::Identifier, + /// The version of the commitment. version: IdentityCommitmentVersion, }, } #[pallet::error] pub enum Error { + /// The specified commitment cannot be found. CommitmentNotFound, + /// Error when retrieving the identity details of the provided subject. IdentityProvider(u16), + /// Error when generating a commitment for the retrieved identity. IdentityCommitmentGenerator(u16), + /// Error inside the external hook logic. Hook(u16), } #[pallet::call] impl Pallet { + /// Generate a new versioned commitment for the subject identified by + /// the provided `Identifier`. If an old commitment for the same version + /// is present, it is overridden. Hooks are called before the new + /// commitment is stored, and optionally before the old one is replaced. #[pallet::call_index(0)] #[pallet::weight({ ::WeightInfo::commit_identity() @@ -151,6 +185,10 @@ pub mod pallet { Ok(()) } + /// Delete an identity commitment of a specific version for a specific + /// `Identifier`. If a commitment of the provided version does not exist + /// for the given `Identifier`, an error is returned. Hooks are called + /// after the commitment has been removed. #[pallet::call_index(1)] #[pallet::weight({ ::WeightInfo::delete_identity_commitment() diff --git a/pallets/pallet-dip-provider/src/traits.rs b/pallets/pallet-dip-provider/src/traits.rs index a475c0c9ed..760ad5cc46 100644 --- a/pallets/pallet-dip-provider/src/traits.rs +++ b/pallets/pallet-dip-provider/src/traits.rs @@ -27,6 +27,9 @@ pub mod identity_provision { use sp_std::marker::PhantomData; + /// A trait to retrieve identity information for a given identifier. The + /// information can come from a variety of different sources, as this pallet + /// does not impose any restrictions on that. pub trait IdentityProvider where Runtime: Config, @@ -34,10 +37,13 @@ pub mod identity_provision { type Error: Into; type Success; + /// Return the identity information for the identifier, if found. + /// Otherwise, return an error. fn retrieve(identifier: &Runtime::Identifier) -> Result; } - // Return the `Default` value if `Identity` and `Details` both implement it. + /// Return the `Default` value of the provided `Identity` type if it + /// implements the `Default` trait. pub struct DefaultIdentityProvider(PhantomData); impl IdentityProvider for DefaultIdentityProvider @@ -64,6 +70,8 @@ pub mod identity_generation { use scale_info::TypeInfo; use sp_std::{fmt::Debug, marker::PhantomData}; + /// A trait to generate an identity commitment of a given version for some + /// identity info retrieved by the [`IdentityProvider`]. pub trait IdentityCommitmentGenerator where Runtime: Config, @@ -72,6 +80,8 @@ pub mod identity_generation { type Error: Into; type Output: Clone + Eq + Debug + TypeInfo + FullCodec + MaxEncodedLen; + /// Return the identity commitment for the given version and identity + /// information. fn generate_commitment( identifier: &Runtime::Identifier, identity: &IdentityOf, @@ -79,8 +89,8 @@ pub mod identity_generation { ) -> Result; } - // Implement the `IdentityCommitmentGenerator` by returning the `Default` value - // for the `Output` type. + /// Implement the [`IdentityCommitmentGenerator`] trait by returning the + /// `Default` value for the `Output` type. pub struct DefaultIdentityCommitmentGenerator(PhantomData); impl IdentityCommitmentGenerator for DefaultIdentityCommitmentGenerator @@ -101,6 +111,8 @@ pub mod identity_generation { } } +/// A trait for types that, among other things, contain information about the +/// submitter of a tx. pub trait SubmitterInfo { type Submitter; @@ -137,6 +149,8 @@ where } } +/// Hooks for additional customizable logic to be executed when new identity +/// commitments are stored or old ones are removed. pub trait ProviderHooks where Runtime: Config, @@ -158,6 +172,7 @@ where ) -> Result<(), Self::Error>; } +/// Implement the [`ProviderHooks`] trait with noops. pub struct NoopHooks; impl ProviderHooks for NoopHooks diff --git a/pallets/pallet-relay-store/Cargo.toml b/pallets/pallet-relay-store/Cargo.toml index 96f1051b04..6cec553a96 100644 --- a/pallets/pallet-relay-store/Cargo.toml +++ b/pallets/pallet-relay-store/Cargo.toml @@ -6,7 +6,7 @@ edition.workspace = true homepage.workspace = true license-file.workspace = true name = "pallet-relay-store" -readme.workspace = true +readme = "README.md" repository.workspace = true version.workspace = true diff --git a/pallets/pallet-relay-store/src/lib.rs b/pallets/pallet-relay-store/src/lib.rs index bebe071c46..084f88232a 100644 --- a/pallets/pallet-relay-store/src/lib.rs +++ b/pallets/pallet-relay-store/src/lib.rs @@ -16,7 +16,10 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -// TODO: Pallet description +//! Pallet to store the last N (configurable) relay chain state roots to be used +//! for cross-chain state proof verification. The pallet relies on the +//! cumulus_parachain_system hook to populate the block `ValidationData` with +//! the latest relay chain state root. #![cfg_attr(not(feature = "std"), no_std)] @@ -43,18 +46,24 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + /// Maps from a relaychain block height to its related information, + /// including the state root. #[pallet::storage] #[pallet::getter(fn latest_relay_head_for_block)] pub(crate) type LatestRelayHeads = StorageMap<_, Twox64Concat, u32, RelayParentInfo>; // TODO: Replace this with a fixed-length array once support for const generics // is fully supported in Substrate. + /// Storage value complimentary to [`LatestRelayHeads`] implementing a FIFO + /// queue of the last N relay chain blocks info. #[pallet::storage] pub(crate) type LatestBlockHeights = StorageValue<_, BoundedVec, ValueQuery>; #[pallet::config] pub trait Config: frame_system::Config { + /// The maximum number of relaychain block details to store. When the + /// limit is reached, oldest blocks are overridden with new ones. #[pallet::constant] type MaxRelayBlocksStored: Get; type WeightInfo: WeightInfo; diff --git a/pallets/pallet-relay-store/src/relay.rs b/pallets/pallet-relay-store/src/relay.rs index 9dfaf69f95..1bca00ba53 100644 --- a/pallets/pallet-relay-store/src/relay.rs +++ b/pallets/pallet-relay-store/src/relay.rs @@ -20,7 +20,9 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_core::RuntimeDebug; +/// Information associated to a relaychain block. #[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug, MaxEncodedLen)] pub struct RelayParentInfo { + /// The relaychain block storage root. pub relay_parent_storage_root: Hash, } diff --git a/runtime-api/dip-provider/src/lib.rs b/runtime-api/dip-provider/src/lib.rs index b910f734c1..1b7b8170da 100644 --- a/runtime-api/dip-provider/src/lib.rs +++ b/runtime-api/dip-provider/src/lib.rs @@ -21,11 +21,13 @@ use parity_scale_codec::Codec; sp_api::decl_runtime_apis! { + /// Runtime API to generate a DIP proof with the provided parameters. pub trait DipProvider where ProofRequest: Codec, Success: Codec, Error: Codec, { + /// Generate a DIP proof with the parameters specified in the request. fn generate_proof(request: ProofRequest) -> Result; } } diff --git a/runtimes/common/src/dip/README.md b/runtimes/common/src/dip/README.md new file mode 100644 index 0000000000..47664afb0d --- /dev/null +++ b/runtimes/common/src/dip/README.md @@ -0,0 +1,16 @@ +# KILT Decentralized Identity Provider (DIP) provider specification + +Specification of the format of a DIP identity commitment and the expected format of a DIP identity proof for cross-chain transactions using KILT identities. + +## V0 + +The V0 of the KILT DIP Provider specification defines the following components: + +* **Identity details**: What are the pieces of a KILT identity that can be used for cross-chain transactions. V0 defines them to include the following information: + * All `DidKey`s stored under the subject's DID Document. For more details about how these keys are defined, read the [KILT DID pallet](../../../../pallets/did). + * All the `LinkableAccountId`s the DID subject has linked to the DID via the KILT linking pallet. For more details about how on-chain linking works, read the [KILT lookup pallet](../../../../pallets/pallet-did-lookup/). + * (OPTIONAL) The web3name of the DID subject, if present. For more details about how web3names work, read the [KILT web3name pallet](../../../../pallets/pallet-web3-names/). +* **Identity commitment**: Defines how the identity details above are aggregated into a value which will be selectively shared on a consumer chain for a cross-chain transaction. V0 defines the identity commitment as a Merkle root of all the elements above that uses the shame hashing algorithm as the runtime. Using a Merkle root allows the DID subject to generate proof that can selectively disclose different pieces of identity for different operations on different chains providing, among other things, better scalability for cases in which the linked information becomes large. The leaves encoded in the commitment can be of the following type: + * DID key leaf: with leaf name being the key ID, and leaf value being the key details as defined in the `DidPublicKeyDetails` type. + * Linked account leaf: with leaf name being the linked account ID, and leaf value being an empty tuple `()`. + * Web3name leaf: with leaf name being the web3name, and leaf value being the KILT block number in which it was linked to the DID. diff --git a/runtimes/common/src/dip/did.rs b/runtimes/common/src/dip/did.rs index 4b6ca4d0e3..6490684fec 100644 --- a/runtimes/common/src/dip/did.rs +++ b/runtimes/common/src/dip/did.rs @@ -53,15 +53,24 @@ impl From for u16 { pub type Web3OwnershipOf = RevealedWeb3Name<::Web3Name, BlockNumberFor>; +/// Identity information related to a KILT DID relevant for cross-chain +/// transactions via the DIP protocol. pub struct LinkedDidInfoOf where Runtime: did::Config + pallet_web3_names::Config, { + /// The DID Document of the subject. pub did_details: DidDetails, + /// The optional web3name details linked to the subject. pub web3_name_details: Option>, + /// The list of accounts the subject has previously linked via the linking + /// pallet. pub linked_accounts: BoundedVec>, } +/// Type implementing the [`IdentityProvider`] trait which is responsible for +/// collecting the DID information relevant for DIP cross-chain transactions by +/// interacting with the different pallets involved. pub struct LinkedDidInfoProvider; impl IdentityProvider for LinkedDidInfoProvider diff --git a/runtimes/common/src/dip/merkle.rs b/runtimes/common/src/dip/merkle.rs index 5eb5829a8d..15e833a8f9 100644 --- a/runtimes/common/src/dip/merkle.rs +++ b/runtimes/common/src/dip/merkle.rs @@ -34,6 +34,8 @@ use kilt_dip_support::merkle::{DidKeyRelationship, RevealedDidMerkleProofLeaf}; use crate::dip::did::LinkedDidInfoOf; pub type BlindedValue = Vec; +/// Type of the Merkle proof revealing parts of the DIP identity of a given DID +/// subject. pub type DidMerkleProofOf = DidMerkleProof< Vec, RevealedDidMerkleProofLeaf< @@ -45,9 +47,13 @@ pub type DidMerkleProofOf = DidMerkleProof< >, >; +/// Type of a complete DIP Merkle proof. #[derive(Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)] pub struct CompleteMerkleProof { + /// The Merkle root. pub root: Root, + /// The Merkle proof revealing parts of the commitment that verify against + /// the provided root. pub proof: Proof, } @@ -75,6 +81,7 @@ impl From for u16 { pub mod v0 { use super::*; + /// Type of a Merkle leaf revealed as part of a DIP Merkle proof. type ProofLeafOf = RevealedDidMerkleProofLeaf< KeyIdOf, ::AccountId, @@ -83,6 +90,8 @@ pub mod v0 { LinkableAccountId, >; + /// Given the provided DID info, it calculates the Merkle commitment (root) + /// using the provided in-memory DB. pub(super) fn calculate_root_with_db( identity: &LinkedDidInfoOf, db: &mut MemoryDB, @@ -232,6 +241,11 @@ pub mod v0 { Ok(trie_builder.root().to_owned()) } + /// Given the provided DID info, and a set of DID key IDs, account IDs and a + /// web3name, generates a Merkle proof that reveals only the provided + /// identity components. The function fails if no key or account with the + /// specified ID can be found, or if a web3name is requested to be revealed + /// in the proof but is not present in the provided identity details. pub(super) fn generate_proof<'a, Runtime, K, A, const MAX_LINKED_ACCOUNT: u32>( identity: &LinkedDidInfoOf, key_ids: K, @@ -323,6 +337,7 @@ pub mod v0 { }) } + /// Given the provided DID info, generates a Merkle commitment (root). pub(super) fn generate_commitment( identity: &IdentityOf, ) -> Result @@ -335,6 +350,9 @@ pub mod v0 { } } +/// Type implementing the [`IdentityCommitmentGenerator`] and generating a +/// Merkle root of the provided identity details, according to the description +/// provided in the [README.md](./README.md), pub struct DidMerkleRootGenerator(PhantomData); impl IdentityCommitmentGenerator for DidMerkleRootGenerator diff --git a/runtimes/common/src/dip/mod.rs b/runtimes/common/src/dip/mod.rs index a3955297d6..9a578cec07 100644 --- a/runtimes/common/src/dip/mod.rs +++ b/runtimes/common/src/dip/mod.rs @@ -16,5 +16,9 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org +#![doc = include_str!("./README.md")] + +/// Logic for collecting information related to a KILT DID. pub mod did; +/// Logic for generating Merkle commitments of a KILT DID identity. pub mod merkle;