diff --git a/Cargo.lock b/Cargo.lock index e51576ab8..d033efc86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2341,6 +2341,7 @@ dependencies = [ "cumulus-primitives-timestamp", "cumulus-primitives-utility", "did", + "dip-provider-runtime-template", "dip-support", "frame-executive", "frame-support", @@ -2454,12 +2455,14 @@ dependencies = [ "pallet-authorship", "pallet-balances", "pallet-collator-selection", + "pallet-did-lookup", "pallet-dip-provider", "pallet-session", "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", + "pallet-web3-names", "pallet-xcm", "parachain-info", "parity-scale-codec", @@ -2505,6 +2508,7 @@ dependencies = [ "kilt-support", "pallet-balances", "pallet-did-lookup", + "pallet-web3-names", "parachain-info", "parity-scale-codec", "polkadot-parachain", @@ -4220,6 +4224,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-dip-consumer", + "pallet-dip-provider", "parity-scale-codec", "scale-info", "sp-core", diff --git a/crates/dip-support/src/lib.rs b/crates/dip-support/src/lib.rs index bde5dbfee..31256a1dd 100644 --- a/crates/dip-support/src/lib.rs +++ b/crates/dip-support/src/lib.rs @@ -25,7 +25,7 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; #[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebug)] -pub enum IdentityProofAction { +pub enum IdentityDetailsAction { Updated(Identifier, Proof, Details), Deleted(Identifier), } diff --git a/crates/kilt-dip-support/Cargo.toml b/crates/kilt-dip-support/Cargo.toml index ebc86ee9b..e06e17664 100644 --- a/crates/kilt-dip-support/Cargo.toml +++ b/crates/kilt-dip-support/Cargo.toml @@ -14,6 +14,7 @@ version.workspace = true # Internal dependencies did.workspace = true pallet-dip-consumer.workspace = true +pallet-dip-provider.workspace = true # Parity dependencies parity-scale-codec = {workspace = true, features = ["derive"]} @@ -32,6 +33,7 @@ default = ["std"] std = [ "did/std", "pallet-dip-consumer/std", + "pallet-dip-provider/std", "parity-scale-codec/std", "scale-info/std", "frame-system/std", diff --git a/crates/kilt-dip-support/src/did.rs b/crates/kilt-dip-support/src/did.rs index 07bd50d2e..c1d37be9a 100644 --- a/crates/kilt-dip-support/src/did.rs +++ b/crates/kilt-dip-support/src/did.rs @@ -22,6 +22,7 @@ use did::{ }; use frame_support::ensure; use pallet_dip_consumer::{identity::IdentityDetails, traits::IdentityProofVerifier}; +use pallet_dip_provider::traits::IdentityProvider; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_core::{ConstU64, Get, RuntimeDebug}; @@ -29,7 +30,7 @@ use sp_runtime::traits::CheckedSub; use sp_std::marker::PhantomData; use crate::{ - merkle::ProofEntry, + merkle::RevealedDidKey, traits::{Bump, DidDipOriginFilter}, }; @@ -40,8 +41,8 @@ pub struct TimeBoundDidSignature { } #[derive(Encode, Decode, RuntimeDebug, Clone, Eq, PartialEq, TypeInfo)] -pub struct MerkleEntriesAndDidSignature { - pub merkle_entries: MerkleEntries, +pub struct MerkleLeavesAndDidSignature { + pub merkle_leaves: MerkleLeaves, pub did_signature: TimeBoundDidSignature, } @@ -53,6 +54,7 @@ pub struct MerkleEntriesAndDidSignature { /// genesis_hash). Additional details can be added to the end of the tuple by /// providing a `SignedExtraProvider`. pub struct MerkleRevealedDidSignatureVerifier< + KeyId, BlockNumber, Digest, Details, @@ -67,6 +69,7 @@ pub struct MerkleRevealedDidSignatureVerifier< >( #[allow(clippy::type_complexity)] PhantomData<( + KeyId, BlockNumber, Digest, Details, @@ -84,6 +87,7 @@ pub struct MerkleRevealedDidSignatureVerifier< impl< Call, Subject, + KeyId, BlockNumber, Digest, Details, @@ -97,6 +101,7 @@ impl< SignedExtra, > IdentityProofVerifier for MerkleRevealedDidSignatureVerifier< + KeyId, BlockNumber, Digest, Details, @@ -114,7 +119,7 @@ impl< Call: Encode, Digest: Encode, Details: Bump + Encode, - MerkleProofEntries: AsRef<[ProofEntry]>, + MerkleProofEntries: AsRef<[RevealedDidKey]>, BlockNumberProvider: Get, GenesisHashProvider: Get, Hash: Encode, @@ -125,7 +130,7 @@ impl< type Error = (); /// The proof must be a list of Merkle leaves that have been previously /// verified by the Merkle proof verifier, and the additional DID signature. - type Proof = MerkleEntriesAndDidSignature; + type Proof = MerkleLeavesAndDidSignature; /// The `Details` that are part of the identity details must implement the /// `Bump` trait. type IdentityDetails = IdentityDetails; @@ -135,11 +140,11 @@ impl< /// the provided signature and its relationship to the DID subject. type VerificationResult = (DidVerificationKey, DidVerificationKeyRelationship); - fn verify_proof_for_call_against_entry( + fn verify_proof_for_call_against_details( call: &Call, _subject: &Subject, submitter: &Self::Submitter, - proof_entry: &mut Self::IdentityDetails, + identity_details: &mut Self::IdentityDetails, proof: &Self::Proof, ) -> Result { let block_number = BlockNumberProvider::get(); @@ -151,43 +156,30 @@ impl< // Signature generated at a future time, not possible to verify. false }; + ensure!(is_signature_fresh, ()); let encoded_payload = ( call, - &proof_entry.details, + &identity_details.details, submitter, &proof.did_signature.block_number, GenesisHashProvider::get(), SignedExtraProvider::get(), ) .encode(); - // Only consider verification keys from the set of revealed Merkle leaves. - let mut proof_verification_keys = proof.merkle_entries.as_ref().iter().filter_map( - |ProofEntry { - key: DidPublicKeyDetails { key, .. }, - relationship, - }| { - if let DidPublicKey::PublicVerificationKey(k) = key { - Some(( - k, - DidVerificationKeyRelationship::try_from(*relationship).expect("Should never fail to build a VerificationRelationship from the given DidKeyRelationship because we have already made sure the conditions hold."), - )) - } else { - None - } - }, - ); + // Only consider verification keys from the set of revealed keys. + let mut proof_verification_keys = proof.merkle_leaves.as_ref().iter().filter_map(|RevealedDidKey { relationship, details: DidPublicKeyDetails { key, .. }, .. } | { + let DidPublicKey::PublicVerificationKey(key) = key else { return None }; + Some((key, DidVerificationKeyRelationship::try_from(*relationship).expect("Should never fail to build a VerificationRelationship from the given DidKeyRelationship because we have already made sure the conditions hold."))) + }); let valid_signing_key = proof_verification_keys.find(|(verification_key, _)| { verification_key .verify_signature(&encoded_payload, &proof.did_signature.signature) .is_ok() }); - if let Some((key, relationship)) = valid_signing_key { - proof_entry.details.bump(); - Ok((key.clone(), relationship)) - } else { - Err(()) - } + let Some((key, relationship)) = valid_signing_key else { return Err(()) }; + identity_details.details.bump(); + Ok((key.clone(), relationship)) } } @@ -223,17 +215,114 @@ where /// `DidSignatureVerifier`. type VerificationResult = DidSignatureVerifier::VerificationResult; - fn verify_proof_for_call_against_entry( + fn verify_proof_for_call_against_details( call: &Call, subject: &Subject, submitter: &Self::Submitter, - proof_entry: &mut Self::IdentityDetails, + identity_details: &mut Self::IdentityDetails, proof: &Self::Proof, ) -> Result { - let did_signing_key = - DidSignatureVerifier::verify_proof_for_call_against_entry(call, subject, submitter, proof_entry, proof) - .map_err(|_| ())?; + let did_signing_key = DidSignatureVerifier::verify_proof_for_call_against_details( + call, + subject, + submitter, + identity_details, + proof, + ) + .map_err(|_| ())?; CallVerifier::check_call_origin_info(call, &did_signing_key).map_err(|_| ())?; Ok(did_signing_key) } } + +pub struct CombinedIdentityResult { + pub a: OutputA, + pub b: OutputB, + pub c: OutputC, +} + +impl From<(OutputA, OutputB, OutputC)> + for CombinedIdentityResult +{ + fn from(value: (OutputA, OutputB, OutputC)) -> Self { + Self { + a: value.0, + b: value.1, + c: value.2, + } + } +} + +impl CombinedIdentityResult +where + OutputB: Default, + OutputC: Default, +{ + pub fn from_a(a: OutputA) -> Self { + Self { + a, + b: OutputB::default(), + c: OutputC::default(), + } + } +} + +impl CombinedIdentityResult +where + OutputA: Default, + OutputC: Default, +{ + pub fn from_b(b: OutputB) -> Self { + Self { + a: OutputA::default(), + b, + c: OutputC::default(), + } + } +} + +impl CombinedIdentityResult +where + OutputA: Default, + OutputB: Default, +{ + pub fn from_c(c: OutputC) -> Self { + Self { + a: OutputA::default(), + b: OutputB::default(), + c, + } + } +} + +pub struct CombineIdentityFrom(PhantomData<(A, B, C)>); + +impl IdentityProvider for CombineIdentityFrom +where + A: IdentityProvider, + B: IdentityProvider, + C: IdentityProvider, +{ + // TODO: Proper error handling + type Error = (); + type Success = CombinedIdentityResult, Option, Option>; + + fn retrieve(identifier: &Identifier) -> Result, Self::Error> { + match ( + A::retrieve(identifier), + B::retrieve(identifier), + C::retrieve(identifier), + ) { + // If no details is returned, return None for the whole result + (Ok(None), Ok(None), Ok(None)) => Ok(None), + // Otherwise, return `Some` or `None` depending on each result + (Ok(ok_a), Ok(ok_b), Ok(ok_c)) => Ok(Some(CombinedIdentityResult { + a: ok_a, + b: ok_b, + c: ok_c, + })), + // If any of them returns an `Err`, return an `Err` + _ => Err(()), + } + } +} diff --git a/crates/kilt-dip-support/src/lib.rs b/crates/kilt-dip-support/src/lib.rs index c2f18ba0f..103a5c504 100644 --- a/crates/kilt-dip-support/src/lib.rs +++ b/crates/kilt-dip-support/src/lib.rs @@ -23,7 +23,7 @@ use pallet_dip_consumer::traits::IdentityProofVerifier; use sp_std::marker::PhantomData; -use crate::did::MerkleEntriesAndDidSignature; +use crate::did::MerkleLeavesAndDidSignature; pub mod did; pub mod merkle; @@ -50,7 +50,7 @@ where DidSignatureVerifier: IdentityProofVerifier< Call, Subject, - Proof = MerkleEntriesAndDidSignature, + Proof = MerkleLeavesAndDidSignature, IdentityDetails = MerkleProofVerifier::IdentityDetails, Submitter = MerkleProofVerifier::Submitter, >, @@ -58,34 +58,34 @@ where // FIXME: Better error handling type Error = (); // FIXME: Better type declaration - type Proof = MerkleEntriesAndDidSignature; + type Proof = MerkleLeavesAndDidSignature; type IdentityDetails = DidSignatureVerifier::IdentityDetails; type Submitter = MerkleProofVerifier::Submitter; type VerificationResult = MerkleProofVerifier::VerificationResult; - fn verify_proof_for_call_against_entry( + fn verify_proof_for_call_against_details( call: &Call, subject: &Subject, submitter: &Self::Submitter, - proof_entry: &mut Self::IdentityDetails, + identity_details: &mut Self::IdentityDetails, proof: &Self::Proof, ) -> Result { - let merkle_proof_verification = MerkleProofVerifier::verify_proof_for_call_against_entry( + let merkle_proof_verification = MerkleProofVerifier::verify_proof_for_call_against_details( call, subject, submitter, - proof_entry, - &proof.merkle_entries, + identity_details, + &proof.merkle_leaves, ) .map_err(|_| ())?; - DidSignatureVerifier::verify_proof_for_call_against_entry( + DidSignatureVerifier::verify_proof_for_call_against_details( call, subject, submitter, - proof_entry, + identity_details, // FIXME: Remove `clone()` requirement - &MerkleEntriesAndDidSignature { - merkle_entries: merkle_proof_verification.clone(), + &MerkleLeavesAndDidSignature { + merkle_leaves: merkle_proof_verification.clone(), did_signature: proof.did_signature.clone(), }, ) diff --git a/crates/kilt-dip-support/src/merkle.rs b/crates/kilt-dip-support/src/merkle.rs index d86e84475..01722d555 100644 --- a/crates/kilt-dip-support/src/merkle.rs +++ b/crates/kilt-dip-support/src/merkle.rs @@ -21,8 +21,8 @@ use frame_support::{traits::ConstU32, RuntimeDebug}; use pallet_dip_consumer::{identity::IdentityDetails, traits::IdentityProofVerifier}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_runtime::BoundedVec; -use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, marker::PhantomData, vec::Vec}; +use sp_runtime::{BoundedVec, SaturatedConversion}; +use sp_std::{fmt::Debug, marker::PhantomData, vec::Vec}; use sp_trie::{verify_trie_proof, LayoutV1}; pub type BlindedValue = Vec; @@ -60,137 +60,222 @@ impl TryFrom for DidVerificationKeyRelationship { } #[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] -pub struct KeyReferenceKey(pub KeyId, pub DidKeyRelationship); +pub struct DidKeyMerkleKey(pub KeyId, pub DidKeyRelationship); + +impl From<(KeyId, DidKeyRelationship)> for DidKeyMerkleKey { + fn from(value: (KeyId, DidKeyRelationship)) -> Self { + Self(value.0, value.1) + } +} + #[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] -pub struct KeyReferenceValue; +pub struct DidKeyMerkleValue(pub DidPublicKeyDetails); + +impl From> for DidKeyMerkleValue { + fn from(value: DidPublicKeyDetails) -> Self { + Self(value) + } +} + #[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] -pub struct KeyDetailsKey(pub KeyId); +pub struct Web3NameMerkleKey(pub Web3Name); + +impl From for Web3NameMerkleKey { + fn from(value: Web3Name) -> Self { + Self(value) + } +} #[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] -pub struct KeyDetailsValue(pub DidPublicKeyDetails); +pub struct Web3NameMerkleValue(BlockNumber); + +impl From for Web3NameMerkleValue { + fn from(value: BlockNumber) -> Self { + Self(value) + } +} #[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] -pub enum ProofLeaf { +pub struct LinkedAccountMerkleKey(pub AccountId); + +impl From for LinkedAccountMerkleKey { + fn from(value: AccountId) -> Self { + Self(value) + } +} + +#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] +pub struct LinkedAccountMerkleValue; + +impl From<()> for LinkedAccountMerkleValue { + fn from(_value: ()) -> Self { + Self + } +} + +#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] +pub enum ProofLeaf { // The key and value for the leaves of a merkle proof that contain a reference // (by ID) to the key details, provided in a separate leaf. - KeyReference(KeyReferenceKey, KeyReferenceValue), - // The key and value for the leaves of a merkle proof that contain the actual - // details of a DID public key. The key is the ID of the key, and the value is its details, including creation - // block number. - KeyDetails(KeyDetailsKey, KeyDetailsValue), + DidKey(DidKeyMerkleKey, DidKeyMerkleValue), + Web3Name(Web3NameMerkleKey, Web3NameMerkleValue), + LinkedAccount(LinkedAccountMerkleKey, LinkedAccountMerkleValue), } -impl ProofLeaf +impl ProofLeaf where KeyId: Encode, + Web3Name: Encode, + LinkedAccountId: Encode, { pub fn encoded_key(&self) -> Vec { match self { - ProofLeaf::KeyReference(key, _) => key.encode(), - ProofLeaf::KeyDetails(key, _) => key.encode(), + ProofLeaf::DidKey(key, _) => key.encode(), + ProofLeaf::Web3Name(key, _) => key.encode(), + ProofLeaf::LinkedAccount(key, _) => key.encode(), } } } -impl ProofLeaf +impl ProofLeaf where BlockNumber: Encode, { pub fn encoded_value(&self) -> Vec { match self { - ProofLeaf::KeyReference(_, value) => value.encode(), - ProofLeaf::KeyDetails(_, value) => value.encode(), + ProofLeaf::DidKey(_, value) => value.encode(), + ProofLeaf::Web3Name(_, value) => value.encode(), + ProofLeaf::LinkedAccount(_, value) => value.encode(), } } } -// TODO: Avoid repetition of the same key if it appears multiple times, e.g., by -// having a vector of `DidKeyRelationship` instead. -#[derive(Clone, RuntimeDebug, PartialEq, Eq, TypeInfo, MaxEncodedLen, Encode, Decode)] -pub struct ProofEntry { - pub key: DidPublicKeyDetails, +#[derive(Clone, Encode, Decode, PartialEq, MaxEncodedLen, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] +pub struct RevealedDidKey { + pub id: KeyId, pub relationship: DidKeyRelationship, + pub details: DidPublicKeyDetails, } -#[cfg(feature = "runtime-benchmarks")] -impl Default for ProofEntry -where - BlockNumber: Default, -{ - fn default() -> Self { - Self { - key: DidPublicKeyDetails { - key: did::did_details::DidPublicKey::PublicEncryptionKey(did::did_details::DidEncryptionKey::X25519( - [0u8; 32], - )), - block_number: BlockNumber::default(), - }, - relationship: DidVerificationKeyRelationship::Authentication.into(), - } - } +#[derive(Clone, Encode, Decode, PartialEq, MaxEncodedLen, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] +pub struct RevealedWeb3Name { + pub web3_name: Web3Name, + pub claimed_at: BlockNumber, } -// Contains the list of revealed public keys after a given Merkle proof has been -// correctly verified. #[derive(Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen, Encode, Decode, Default)] -pub struct VerificationResult( - pub BoundedVec, ConstU32>, -); - -impl TryFrom>> - for VerificationResult -{ - // TODO: Better error handling - type Error = (); - - fn try_from(value: Vec>) -> Result { - let bounded_inner = value.try_into().map_err(|_| ())?; - Ok(Self(bounded_inner)) - } +pub struct VerificationResult< + KeyId, + BlockNumber, + Web3Name, + LinkedAccountId, + const MAX_REVEALED_KEYS_COUNT: u32, + const MAX_REVEALED_ACCOUNTS_COUNT: u32, +> { + pub did_keys: BoundedVec, ConstU32>, + pub web3_name: Option>, + pub linked_accounts: BoundedVec>, } -impl AsRef<[ProofEntry]> - for VerificationResult +impl< + KeyId, + BlockNumber, + Web3Name, + LinkedAccountId, + const MAX_REVEALED_KEYS_COUNT: u32, + const MAX_REVEALED_ACCOUNTS_COUNT: u32, + > AsRef<[RevealedDidKey]> + for VerificationResult< + KeyId, + BlockNumber, + Web3Name, + LinkedAccountId, + MAX_REVEALED_KEYS_COUNT, + MAX_REVEALED_ACCOUNTS_COUNT, + > { - fn as_ref(&self) -> &[ProofEntry] { - self.0.as_ref() + fn as_ref(&self) -> &[RevealedDidKey] { + self.did_keys.as_ref() } } /// A type that verifies a Merkle proof that reveals some leaves representing /// keys in a DID Document. /// Can also be used on its own, without any DID signature verification. -pub struct DidMerkleProofVerifier( +pub struct DidMerkleProofVerifier< + Hasher, + AccountId, + KeyId, + BlockNumber, + Details, + Web3Name, + LinkedAccountId, + const MAX_REVEALED_KEYS_COUNT: u32, + const MAX_REVEALED_ACCOUNTS_COUNT: u32, +>( + #[allow(clippy::type_complexity)] PhantomData<( Hasher, AccountId, KeyId, BlockNumber, Details, - ConstU32, + Web3Name, + LinkedAccountId, + ConstU32, + ConstU32, )>, ); -impl - IdentityProofVerifier - for DidMerkleProofVerifier -where +impl< + Call, + Subject, + Hasher, + AccountId, + KeyId, + BlockNumber, + Details, + Web3Name, + LinkedAccountId, + const MAX_REVEALED_KEYS_COUNT: u32, + const MAX_REVEALED_ACCOUNTS_COUNT: u32, + > IdentityProofVerifier + for DidMerkleProofVerifier< + Hasher, + AccountId, + KeyId, + BlockNumber, + Details, + Web3Name, + LinkedAccountId, + MAX_REVEALED_KEYS_COUNT, + MAX_REVEALED_ACCOUNTS_COUNT, + > where // TODO: Remove `Debug` bound BlockNumber: Encode + Clone + Debug, Hasher: sp_core::Hasher, KeyId: Encode + Clone + Ord + Into, + LinkedAccountId: Encode + Clone, + Web3Name: Encode + Clone, { // TODO: Proper error handling type Error = (); - type Proof = MerkleProof>, ProofLeaf>; + type Proof = MerkleProof>, ProofLeaf>; type IdentityDetails = IdentityDetails; type Submitter = AccountId; - type VerificationResult = VerificationResult; + type VerificationResult = VerificationResult< + KeyId, + BlockNumber, + Web3Name, + LinkedAccountId, + MAX_REVEALED_KEYS_COUNT, + MAX_REVEALED_ACCOUNTS_COUNT, + >; - fn verify_proof_for_call_against_entry( + fn verify_proof_for_call_against_details( _call: &Call, _subject: &Subject, _submitter: &Self::Submitter, - proof_entry: &mut Self::IdentityDetails, + identity_details: &mut Self::IdentityDetails, proof: &Self::Proof, ) -> Result { // TODO: more efficient by removing cloning and/or collecting. @@ -202,7 +287,7 @@ where .map(|leaf| (leaf.encoded_key(), Some(leaf.encoded_value()))) .collect::, Option>)>>(); verify_trie_proof::, _, _, _>( - &proof_entry.digest.clone().into(), + &identity_details.digest.clone().into(), &proof.blinded, &proof_leaves, ) @@ -210,42 +295,48 @@ where // At this point, we know the proof is valid. We just need to map the revealed // leaves to something the consumer can easily operate on. - - // Create a map of the revealed public keys - //TODO: Avoid cloning, and use a map of references for the lookup - let public_keys: BTreeMap> = proof - .revealed - .clone() - .into_iter() - .filter_map(|leaf| { - if let ProofLeaf::KeyDetails(KeyDetailsKey(key_id), KeyDetailsValue(key_details)) = leaf { - Some((key_id, key_details)) - } else { - None - } - }) - .collect(); - // Create a list of the revealed keys by consuming the provided key reference - // leaves, and looking up the full details from the just-built `public_keys` - // map. - let keys: Vec> = proof - .revealed - .iter() - .filter_map(|leaf| { - if let ProofLeaf::KeyReference(KeyReferenceKey(key_id, key_relationship), _) = leaf { - // TODO: Better error handling. - let key_details = public_keys - .get(key_id) - .expect("Key ID should be present in the map of revealed public keys."); - Some(ProofEntry { - key: key_details.clone(), - relationship: *key_relationship, + #[allow(clippy::type_complexity)] + let (did_keys, web3_name, linked_accounts): ( + BoundedVec, ConstU32>, + Option>, + BoundedVec>, + ) = proof.revealed.iter().try_fold( + ( + BoundedVec::with_bounded_capacity(MAX_REVEALED_KEYS_COUNT.saturated_into()), + None, + BoundedVec::with_bounded_capacity(MAX_REVEALED_ACCOUNTS_COUNT.saturated_into()), + ), + |(mut keys, web3_name, mut linked_accounts), leaf| match leaf { + ProofLeaf::DidKey(key_id, key_value) => { + keys.try_push(RevealedDidKey { + // TODO: Avoid cloning if possible + id: key_id.0.clone(), + relationship: key_id.1, + details: key_value.0.clone(), }) - } else { - None + .map_err(|_| ())?; + Ok::<_, ()>((keys, web3_name, linked_accounts)) } - }) - .collect(); - keys.try_into() + // TODO: Avoid cloning if possible + ProofLeaf::Web3Name(revealed_web3_name, details) => Ok(( + keys, + Some(RevealedWeb3Name { + web3_name: revealed_web3_name.0.clone(), + claimed_at: details.0.clone(), + }), + linked_accounts, + )), + ProofLeaf::LinkedAccount(account_id, _) => { + linked_accounts.try_push(account_id.0.clone()).map_err(|_| ())?; + Ok::<_, ()>((keys, web3_name, linked_accounts)) + } + }, + )?; + + Ok(VerificationResult { + did_keys, + web3_name, + linked_accounts, + }) } } diff --git a/crates/kilt-dip-support/src/traits.rs b/crates/kilt-dip-support/src/traits.rs index 56d4dd09b..66417991e 100644 --- a/crates/kilt-dip-support/src/traits.rs +++ b/crates/kilt-dip-support/src/traits.rs @@ -34,7 +34,6 @@ impl Bump for T where T: CheckedAdd + Zero + One, { - // FIXME: Better implementation? fn bump(&mut self) { *self = self.checked_add(&Self::one()).unwrap_or_else(Self::zero); } diff --git a/dip-template/nodes/dip-provider/src/chain_spec.rs b/dip-template/nodes/dip-provider/src/chain_spec.rs index 7c323d7be..af6493f67 100644 --- a/dip-template/nodes/dip-provider/src/chain_spec.rs +++ b/dip-template/nodes/dip-provider/src/chain_spec.rs @@ -100,6 +100,7 @@ fn testnet_genesis( aura: Default::default(), aura_ext: Default::default(), polkadot_xcm: Default::default(), + did_lookup: Default::default(), } } diff --git a/dip-template/runtimes/dip-consumer/Cargo.toml b/dip-template/runtimes/dip-consumer/Cargo.toml index de8f00d27..b5a18965e 100644 --- a/dip-template/runtimes/dip-consumer/Cargo.toml +++ b/dip-template/runtimes/dip-consumer/Cargo.toml @@ -18,6 +18,7 @@ parity-scale-codec = {workspace = true, features = ["derive"]} scale-info = {workspace = true, features = ["derive"]} # DIP +dip-provider-runtime-template.workspace = true dip-support.workspace = true did.workspace = true kilt-dip-support.workspace = true @@ -77,6 +78,7 @@ default = [ std = [ "parity-scale-codec/std", "scale-info/std", + "dip-provider-runtime-template/std", "dip-support/std", "did/std", "kilt-dip-support/std", @@ -125,6 +127,7 @@ std = [ ] runtime-benchmarks = [ + "dip-provider-runtime-template/runtime-benchmarks", "pallet-dip-consumer/runtime-benchmarks", "runtime-common/runtime-benchmarks", "frame-support/runtime-benchmarks", diff --git a/dip-template/runtimes/dip-consumer/src/dip.rs b/dip-template/runtimes/dip-consumer/src/dip.rs index 639ff16e6..b629fe582 100644 --- a/dip-template/runtimes/dip-consumer/src/dip.rs +++ b/dip-template/runtimes/dip-consumer/src/dip.rs @@ -17,22 +17,26 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org use did::{did_details::DidVerificationKey, DidVerificationKeyRelationship, KeyIdOf}; +use dip_provider_runtime_template::Web3Name; use frame_support::traits::Contains; use kilt_dip_support::{ - did::{DidSignatureAndCallVerifier, MerkleEntriesAndDidSignature, MerkleRevealedDidSignatureVerifier}, + did::{DidSignatureAndCallVerifier, MerkleLeavesAndDidSignature, MerkleRevealedDidSignatureVerifier}, merkle::{DidMerkleProofVerifier, MerkleProof, ProofLeaf}, traits::{BlockNumberProvider, DidDipOriginFilter, GenesisProvider}, MerkleProofAndDidSignatureVerifier, }; +use pallet_did_lookup::linkable_account::LinkableAccountId; use pallet_dip_consumer::traits::IdentityProofVerifier; use sp_std::vec::Vec; use crate::{AccountId, BlockNumber, DidIdentifier, Hash, Hasher, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin}; -pub type MerkleProofVerifier = DidMerkleProofVerifier, BlockNumber, u128, 10>; +pub type MerkleProofVerifier = + DidMerkleProofVerifier, BlockNumber, u128, Web3Name, LinkableAccountId, 10, 10>; pub type MerkleProofVerifierOutputOf = >::VerificationResult; pub type MerkleDidSignatureVerifierOf = MerkleRevealedDidSignatureVerifier< + KeyIdOf, BlockNumber, Hash, u128, @@ -49,7 +53,10 @@ impl pallet_dip_consumer::Config for Runtime { type DipCallOriginFilter = PreliminaryDipOriginFilter; type Identifier = DidIdentifier; type IdentityDetails = u128; - type Proof = MerkleEntriesAndDidSignature>, ProofLeaf>, BlockNumber>; + type Proof = MerkleLeavesAndDidSignature< + MerkleProof>, ProofLeaf>, + BlockNumber, + >; type ProofDigest = Hash; type ProofVerifier = MerkleProofAndDidSignatureVerifier< BlockNumber, diff --git a/dip-template/runtimes/dip-consumer/src/lib.rs b/dip-template/runtimes/dip-consumer/src/lib.rs index 602e4af53..03fcfb5df 100644 --- a/dip-template/runtimes/dip-consumer/src/lib.rs +++ b/dip-template/runtimes/dip-consumer/src/lib.rs @@ -22,7 +22,10 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +use did::KeyIdOf; +use dip_provider_runtime_template::Web3Name; use kilt_dip_support::merkle::VerificationResult; +use pallet_did_lookup::linkable_account::LinkableAccountId; pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; pub use sp_runtime::{MultiAddress, Perbill, Permill}; @@ -367,8 +370,16 @@ impl pallet_did_lookup::Config for Runtime { type Currency = Balances; type Deposit = ConstU128; type DidIdentifier = DidIdentifier; - type EnsureOrigin = EnsureDipOrigin>; - type OriginSuccess = DipOrigin>; + type EnsureOrigin = EnsureDipOrigin< + DidIdentifier, + AccountId, + VerificationResult, BlockNumber, Web3Name, LinkableAccountId, 10, 10>, + >; + type OriginSuccess = DipOrigin< + DidIdentifier, + AccountId, + VerificationResult, BlockNumber, Web3Name, LinkableAccountId, 10, 10>, + >; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); } diff --git a/dip-template/runtimes/dip-provider/Cargo.toml b/dip-template/runtimes/dip-provider/Cargo.toml index 77647b7d8..ce1a7582b 100644 --- a/dip-template/runtimes/dip-provider/Cargo.toml +++ b/dip-template/runtimes/dip-provider/Cargo.toml @@ -21,7 +21,9 @@ scale-info = {workspace = true, features = ["derive"]} did.workspace = true dip-support.workspace = true kilt-runtime-api-dip-provider.workspace = true +pallet-did-lookup.workspace = true pallet-dip-provider.workspace = true +pallet-web3-names.workspace = true runtime-common.workspace = true # Substrate @@ -77,7 +79,9 @@ std = [ "did/std", "dip-support/std", "kilt-runtime-api-dip-provider/std", + "pallet-did-lookup/std", "pallet-dip-provider/std", + "pallet-web3-names/std", "runtime-common/std", "frame-executive/std", "frame-support/std", @@ -119,7 +123,9 @@ std = [ ] runtime-benchmarks = [ "did/runtime-benchmarks", + "pallet-did-lookup/runtime-benchmarks", "pallet-dip-provider/runtime-benchmarks", + "pallet-web3-names/runtime-benchmarks", "runtime-common/runtime-benchmarks", "frame-system/runtime-benchmarks", "frame-support/runtime-benchmarks", diff --git a/dip-template/runtimes/dip-provider/src/dip.rs b/dip-template/runtimes/dip-provider/src/dip.rs index e358eff8a..97afed38f 100644 --- a/dip-template/runtimes/dip-provider/src/dip.rs +++ b/dip-template/runtimes/dip-provider/src/dip.rs @@ -16,11 +16,10 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -use did::did_details::DidDetails; -use dip_support::IdentityProofAction; +use dip_support::IdentityDetailsAction; use pallet_dip_provider::traits::{TxBuilder, XcmRouterDispatcher}; use parity_scale_codec::{Decode, Encode}; -use runtime_common::dip::{did::DidIdentityProvider, merkle::DidMerkleRootGenerator}; +use runtime_common::dip::{did::LinkedDidInfoProviderOf, merkle::DidMerkleRootGenerator}; use xcm::{latest::MultiLocation, DoubleEncoded}; use crate::{DidIdentifier, Hash, Runtime, RuntimeEvent, XcmRouter}; @@ -34,7 +33,7 @@ enum ConsumerParachainCalls { #[derive(Encode, Decode)] enum ConsumerParachainDipConsumerCalls { #[codec(index = 0)] - ProcessIdentityAction(IdentityProofAction), + ProcessIdentityAction(IdentityDetailsAction), } pub struct ConsumerParachainTxBuilder; @@ -43,7 +42,7 @@ impl TxBuilder for ConsumerParachainTxBuilder { fn build( _dest: MultiLocation, - action: IdentityProofAction, + action: IdentityDetailsAction, ) -> Result, Self::Error> { let double_encoded: DoubleEncoded<()> = ConsumerParachainCalls::DipConsumer(ConsumerParachainDipConsumerCalls::ProcessIdentityAction(action)) @@ -55,10 +54,9 @@ impl TxBuilder for ConsumerParachainTxBuilder { impl pallet_dip_provider::Config for Runtime { type Identifier = DidIdentifier; - type Identity = DidDetails; type IdentityProofDispatcher = XcmRouterDispatcher; type IdentityProofGenerator = DidMerkleRootGenerator; - type IdentityProvider = DidIdentityProvider; + type IdentityProvider = LinkedDidInfoProviderOf; type ProofOutput = Hash; type RuntimeEvent = RuntimeEvent; type TxBuilder = ConsumerParachainTxBuilder; diff --git a/dip-template/runtimes/dip-provider/src/lib.rs b/dip-template/runtimes/dip-provider/src/lib.rs index b986a95e5..ce1a98ea3 100644 --- a/dip-template/runtimes/dip-provider/src/lib.rs +++ b/dip-template/runtimes/dip-provider/src/lib.rs @@ -22,6 +22,9 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +use pallet_did_lookup::linkable_account::LinkableAccountId; +use pallet_web3_names::web3_name::AsciiWeb3Name; +use parity_scale_codec::{Decode, Encode}; pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; pub use sp_runtime::{MultiAddress, Perbill, Permill}; @@ -140,6 +143,8 @@ construct_runtime!( // DID Did: did = 40, + DidLookup: pallet_did_lookup = 41, + Web3Names: pallet_web3_names = 42, // DIP DipProvider: pallet_dip_provider = 50, @@ -365,7 +370,7 @@ impl did::DeriveDidCallAuthorizationVerificationKeyRelationship for RuntimeCall parameter_types! { #[derive(Debug, Clone, Eq, PartialEq)] - pub const MaxTotalKeyAgreementKeys: u32 = 1; + pub const MaxTotalKeyAgreementKeys: u32 = 50; } impl did::Config for Runtime { @@ -377,11 +382,11 @@ impl did::Config for Runtime { type FeeCollector = (); type KeyDeposit = ConstU128; type MaxBlocksTxValidity = ConstU32; - type MaxNewKeyAgreementKeys = ConstU32<1>; + type MaxNewKeyAgreementKeys = ConstU32<50>; type MaxNumberOfServicesPerDid = ConstU32<1>; type MaxNumberOfTypesPerService = ConstU32<1>; type MaxNumberOfUrlsPerService = ConstU32<1>; - type MaxPublicKeysPerDid = ConstU32<4>; + type MaxPublicKeysPerDid = ConstU32<53>; type MaxServiceIdLength = ConstU32<100>; type MaxServiceTypeLength = ConstU32<100>; type MaxServiceUrlLength = ConstU32<100>; @@ -394,6 +399,40 @@ impl did::Config for Runtime { type WeightInfo = (); } +impl pallet_did_lookup::Config for Runtime { + type Currency = Balances; + type Deposit = ConstU128; + type DidIdentifier = DidIdentifier; + type EnsureOrigin = EnsureDidOrigin; + type OriginSuccess = DidRawOrigin; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +pub type Web3Name = AsciiWeb3Name; + +impl pallet_web3_names::Config for Runtime { + type BanOrigin = EnsureRoot; + type Currency = Balances; + type Deposit = ConstU128; + type MaxNameLength = ConstU32<32>; + type MinNameLength = ConstU32<3>; + type OriginSuccess = DidRawOrigin; + type OwnerOrigin = EnsureDidOrigin; + type RuntimeEvent = RuntimeEvent; + type Web3Name = Web3Name; + type Web3NameOwner = DidIdentifier; + type WeightInfo = (); +} + +#[derive(Encode, Decode)] +pub struct DipProofRequest { + identifier: DidIdentifier, + keys: Vec>, + accounts: Vec, + should_include_web3_name: bool, +} + impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> SlotDuration { @@ -530,15 +569,14 @@ impl_runtime_apis! { } } - // TODO: `keys` could and should be a BTreeSet, but it makes it more complicated for clients to build the type. So we use a Vec, since the keys are deduplicated anyway at proof creation time. // TODO: Support generating different versions of the proof, based on the provided parameter - impl kilt_runtime_api_dip_provider::DipProvider, Vec>, CompleteMerkleProof>, ()> for Runtime { - fn generate_proof(identifier: DidIdentifier, keys: Vec>) -> Result>, ()> { - if let Ok(Some((did_details, _))) = ::IdentityProvider::retrieve(&identifier) { - DidMerkleRootGenerator::::generate_proof(&did_details, keys.iter()) - } else { - Err(()) + impl kilt_runtime_api_dip_provider::DipProvider>, ()> for Runtime { + fn generate_proof(request: DipProofRequest) -> Result>, ()> { + let Some(linked_did_info) = ::IdentityProvider::retrieve(&request.identifier)? else { return Err(()) }; + if linked_did_info.a.is_none() { + return Err(()); } + DidMerkleRootGenerator::::generate_proof(&linked_did_info, request.keys.iter(), request.should_include_web3_name, request.accounts.iter()) } } } diff --git a/dip-template/runtimes/xcm-tests/Cargo.toml b/dip-template/runtimes/xcm-tests/Cargo.toml index 6be31a5cb..ae3b735c7 100644 --- a/dip-template/runtimes/xcm-tests/Cargo.toml +++ b/dip-template/runtimes/xcm-tests/Cargo.toml @@ -21,6 +21,7 @@ kilt-dip-support = { workspace = true, features = ["std"] } kilt-support = { workspace = true, features = ["std"] } pallet-balances = { workspace = true, features = ["std"] } pallet-did-lookup = { workspace = true, features = ["std"] } +pallet-web3-names = { workspace = true, features = ["std"] } parachain-info = { workspace = true, features = ["std"] } parity-scale-codec = {workspace = true, features = ["std", "derive"]} polkadot-parachain = { workspace = true, features = ["std"] } diff --git a/dip-template/runtimes/xcm-tests/src/para.rs b/dip-template/runtimes/xcm-tests/src/para.rs index 77e5f3907..c671ae0c8 100644 --- a/dip-template/runtimes/xcm-tests/src/para.rs +++ b/dip-template/runtimes/xcm-tests/src/para.rs @@ -25,9 +25,15 @@ pub(super) mod provider { pub(crate) use dip_provider_runtime_template::{DidIdentifier, DmpQueue, Runtime, RuntimeOrigin, XcmpQueue}; use did::did_details::{DidDetails, DidEncryptionKey, DidVerificationKey}; - use dip_provider_runtime_template::{AccountId, System}; + use dip_provider_runtime_template::{AccountId, Balance, BlockNumber, System, Web3Name}; use kilt_support::deposit::Deposit; + use pallet_did_lookup::{linkable_account::LinkableAccountId, ConnectionRecord}; + use pallet_web3_names::web3_name::Web3NameOwnership; use sp_core::{ecdsa, ed25519, sr25519, Pair}; + use sp_runtime::{ + traits::{One, Zero}, + AccountId32, SaturatedConversion, + }; use super::*; @@ -53,9 +59,13 @@ pub(super) mod provider { .unwrap(); details.update_attestation_key(att_key, 0u32).unwrap(); details.update_delegation_key(del_key, 0u32).unwrap(); - details - .add_key_agreement_key(DidEncryptionKey::X25519([100u8; 32]), 0u32) - .unwrap(); + let max_key_agreement_key_count: u8 = + ::MaxTotalKeyAgreementKeys::get().saturated_into(); + (1u8..max_key_agreement_key_count).for_each(|s| { + details + .add_key_agreement_key(DidEncryptionKey::X25519([s; 32]), 0u32) + .unwrap(); + }); details } @@ -74,8 +84,37 @@ pub(super) mod provider { let mut ext = TestExternalities::new(t); let did: DidIdentifier = did_auth_key().public().into(); let details = generate_did_details(); + let acc: AccountId32 = did_auth_key().public().into(); + let web3_name: Web3Name = b"test".to_vec().try_into().unwrap(); ext.execute_with(|| { did::pallet::Did::::insert(&did, details); + pallet_did_lookup::pallet::ConnectedDids::::insert( + LinkableAccountId::from(acc.clone()), + ConnectionRecord { + did: did.clone(), + deposit: Deposit { + amount: Balance::one(), + owner: acc.clone(), + }, + }, + ); + pallet_did_lookup::pallet::ConnectedAccounts::::insert( + &did, + LinkableAccountId::from(acc.clone()), + (), + ); + pallet_web3_names::pallet::Owner::::insert( + &web3_name, + Web3NameOwnership { + claimed_at: BlockNumber::zero(), + owner: did.clone(), + deposit: Deposit { + amount: Balance::one(), + owner: acc.clone(), + }, + }, + ); + pallet_web3_names::pallet::Names::::insert(did, web3_name); System::set_block_number(1); }); ext diff --git a/dip-template/runtimes/xcm-tests/src/tests.rs b/dip-template/runtimes/xcm-tests/src/tests.rs index a868a64ea..3577a3932 100644 --- a/dip-template/runtimes/xcm-tests/src/tests.rs +++ b/dip-template/runtimes/xcm-tests/src/tests.rs @@ -22,12 +22,16 @@ use did::{Did, DidSignature}; use frame_support::{assert_ok, weights::Weight}; use frame_system::RawOrigin; use kilt_dip_support::{ - did::{MerkleEntriesAndDidSignature, TimeBoundDidSignature}, + did::{MerkleLeavesAndDidSignature, TimeBoundDidSignature}, merkle::MerkleProof, }; -use pallet_did_lookup::linkable_account::LinkableAccountId; +use pallet_did_lookup::{linkable_account::LinkableAccountId, ConnectedAccounts}; +use pallet_web3_names::{Names, Owner}; use parity_scale_codec::Encode; -use runtime_common::dip::merkle::{CompleteMerkleProof, DidMerkleRootGenerator}; +use runtime_common::dip::{ + did::Web3OwnershipOf, + merkle::{CompleteMerkleProof, DidMerkleRootGenerator}, +}; use sp_core::Pair; use sp_runtime::traits::Zero; use xcm::latest::{ @@ -50,7 +54,7 @@ fn commit_identity() { let did: DidIdentifier = para::provider::did_auth_key().public().into(); - // 1. Send identity proof from DIP provider to DIP consumer. + // 1. Send identity commitment from DIP provider to DIP consumer. ProviderParachain::execute_with(|| { assert_ok!(DipProvider::commit_identity( RawOrigin::Signed(ProviderAccountId::from([0u8; 32])).into(), @@ -60,7 +64,7 @@ fn commit_identity() { Weight::from_ref_time(4_000), )); }); - // 2. Verify that the proof has made it to the DIP consumer. + // 2. Verify that the commitment has made it to the DIP consumer. ConsumerParachain::execute_with(|| { // 2.1 Verify that there was no XCM error. assert!(!System::events().iter().any(|r| matches!( @@ -78,13 +82,54 @@ fn commit_identity() { let did_details = ProviderParachain::execute_with(|| { Did::get(&did).expect("DID details should be stored on the provider chain.") }); + println!( + "Complete DID details encoded size: {:?} bytes", + did_details.encoded_size() + ); + let (web3_name, ownership_details) = ProviderParachain::execute_with(|| { + let web3_name = + Names::::get(&did).expect("Web3name should be linked to the DID on the provider chain."); + let ownership_details = Owner::::get(&web3_name) + .expect("Web3name details should be present for the retrieved web3name."); + (web3_name, ownership_details) + }); + println!( + "Web3name and ownership size: ({:?}, {:?}) bytes", + web3_name.encoded_size(), + ownership_details.encoded_size(), + ); + let linked_accounts = ProviderParachain::execute_with(|| { + ConnectedAccounts::::iter_key_prefix(&did).collect::>() + }); + println!("Linked accounts size: {:?} bytes", linked_accounts.encoded_size()); let call = ConsumerRuntimeCall::DidLookup(pallet_did_lookup::Call::::associate_sender {}); // 3.1 Generate a proof let CompleteMerkleProof { proof, .. } = DidMerkleRootGenerator::::generate_proof( - &did_details, - [did_details.authentication_key].iter(), + &( + Some(did_details.clone()), + Some(Web3OwnershipOf:: { + web3_name, + claimed_at: ownership_details.claimed_at, + }), + Some(linked_accounts.clone()), + ) + .into(), + [ + did_details.authentication_key, + did_details.attestation_key.unwrap(), + did_details.delegation_key.unwrap(), + ] + .iter(), + true, + linked_accounts.iter(), ) .expect("Proof generation should not fail"); + println!( + "Complete merkle proof size: {:?} bytes. Blinded part: {:?} bytes. Revealed part: {:?} bytes.", + proof.encoded_size(), + proof.blinded.encoded_size(), + proof.revealed.encoded_size() + ); // 3.2 Generate a DID signature let genesis_hash = ConsumerParachain::execute_with(|| frame_system::Pallet::::block_hash(BlockNumber::zero())); @@ -103,8 +148,8 @@ fn commit_identity() { assert_ok!(DipConsumer::dispatch_as( RawOrigin::Signed(para::consumer::DISPATCHER_ACCOUNT).into(), did.clone(), - MerkleEntriesAndDidSignature { - merkle_entries: MerkleProof { + MerkleLeavesAndDidSignature { + merkle_leaves: MerkleProof { blinded: proof.blinded, revealed: proof.revealed, }, diff --git a/pallets/pallet-did-lookup/src/linkable_account.rs b/pallets/pallet-did-lookup/src/linkable_account.rs index 0f2e97b57..4d6ccc6f8 100644 --- a/pallets/pallet-did-lookup/src/linkable_account.rs +++ b/pallets/pallet-did-lookup/src/linkable_account.rs @@ -50,3 +50,12 @@ impl std::fmt::Display for LinkableAccountId { } } } + +// Default implementation required by the DipDidOrigin origin type, only for +// benchmarks. +#[cfg(feature = "runtime-benchmarks")] +impl Default for LinkableAccountId { + fn default() -> Self { + AccountId32::new([0u8; 32]).into() + } +} diff --git a/pallets/pallet-dip-consumer/src/lib.rs b/pallets/pallet-dip-consumer/src/lib.rs index f9712bf5a..61818b668 100644 --- a/pallets/pallet-dip-consumer/src/lib.rs +++ b/pallets/pallet-dip-consumer/src/lib.rs @@ -37,7 +37,7 @@ pub mod pallet { use parity_scale_codec::MaxEncodedLen; use sp_std::boxed::Box; - use dip_support::IdentityProofAction; + use dip_support::IdentityDetailsAction; use crate::{identity::IdentityDetails, traits::IdentityProofVerifier}; @@ -51,7 +51,7 @@ pub mod pallet { // TODO: Store also additional details received by the provider. #[pallet::storage] #[pallet::getter(fn identity_proofs)] - pub(crate) type IdentityProofs = StorageMap< + pub(crate) type IdentityEntries = StorageMap< _, Twox64Concat, ::Identifier, @@ -134,13 +134,13 @@ pub mod pallet { #[pallet::weight(0)] pub fn process_identity_action( origin: OriginFor, - action: IdentityProofAction, + action: IdentityDetailsAction, ) -> DispatchResult { ensure_sibling_para(::RuntimeOrigin::from(origin))?; let event = match action { - IdentityProofAction::Updated(identifier, proof, _) => { - IdentityProofs::::mutate( + IdentityDetailsAction::Updated(identifier, proof, _) => { + IdentityEntries::::mutate( &identifier, |entry: &mut Option< IdentityDetails<::ProofDigest, ::IdentityDetails>, @@ -148,8 +148,8 @@ pub mod pallet { ); Ok::<_, Error>(Event::::IdentityInfoUpdated(identifier, proof)) } - IdentityProofAction::Deleted(identifier) => { - IdentityProofs::::remove(&identifier); + IdentityDetailsAction::Deleted(identifier) => { + IdentityEntries::::remove(&identifier); Ok::<_, Error>(Event::::IdentityInfoDeleted(identifier)) } }?; @@ -171,18 +171,18 @@ pub mod pallet { let submitter = ensure_signed(origin)?; // TODO: Proper error handling ensure!(T::DipCallOriginFilter::contains(&*call), Error::::Dispatch); - let mut proof_entry = IdentityProofs::::get(&identifier).ok_or(Error::::IdentityNotFound)?; - let proof_verification_result = T::ProofVerifier::verify_proof_for_call_against_entry( + let mut identity_entry = IdentityEntries::::get(&identifier).ok_or(Error::::IdentityNotFound)?; + let proof_verification_result = T::ProofVerifier::verify_proof_for_call_against_details( &*call, &identifier, &submitter, - &mut proof_entry, + &mut identity_entry, &proof, ) .map_err(|_| Error::::InvalidProof)?; // Write the identity info to storage after it has optionally been updated by // the `ProofVerifier`. - IdentityProofs::::mutate(&identifier, |entry| *entry = Some(proof_entry)); + IdentityEntries::::mutate(&identifier, |entry| *entry = Some(identity_entry)); let did_origin = DipOrigin { identifier, account_address: submitter, diff --git a/pallets/pallet-dip-consumer/src/origin.rs b/pallets/pallet-dip-consumer/src/origin.rs index f03004447..415d055e0 100644 --- a/pallets/pallet-dip-consumer/src/origin.rs +++ b/pallets/pallet-dip-consumer/src/origin.rs @@ -17,6 +17,7 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org use frame_support::{traits::EnsureOrigin, RuntimeDebug}; +use kilt_support::traits::CallSources; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_std::marker::PhantomData; @@ -70,8 +71,7 @@ where } } -impl kilt_support::traits::CallSources - for DipOrigin +impl CallSources for DipOrigin where Identifier: Clone, AccountId: Clone, diff --git a/pallets/pallet-dip-consumer/src/traits.rs b/pallets/pallet-dip-consumer/src/traits.rs index 7f4645f16..63a5c8a83 100644 --- a/pallets/pallet-dip-consumer/src/traits.rs +++ b/pallets/pallet-dip-consumer/src/traits.rs @@ -25,11 +25,11 @@ pub trait IdentityProofVerifier { type Submitter; type VerificationResult; - fn verify_proof_for_call_against_entry( + fn verify_proof_for_call_against_details( call: &Call, subject: &Subject, submitter: &Self::Submitter, - proof_entry: &mut Self::IdentityDetails, + identity_details: &mut Self::IdentityDetails, proof: &Self::Proof, ) -> Result; } @@ -45,11 +45,11 @@ impl IdentityProofVerifier Result { Ok(()) diff --git a/pallets/pallet-dip-provider/src/lib.rs b/pallets/pallet-dip-provider/src/lib.rs index c826d191a..a970e22d5 100644 --- a/pallets/pallet-dip-provider/src/lib.rs +++ b/pallets/pallet-dip-provider/src/lib.rs @@ -33,26 +33,26 @@ pub mod pallet { use sp_std::{boxed::Box, fmt::Debug}; use xcm::{latest::prelude::*, VersionedMultiAsset, VersionedMultiLocation}; - use dip_support::IdentityProofAction; + use dip_support::IdentityDetailsAction; use crate::traits::{IdentityProofDispatcher, IdentityProofGenerator, IdentityProvider, TxBuilder}; - pub type IdentityProofActionOf = IdentityProofAction<::Identifier, ::ProofOutput>; + pub type IdentityOf = <::IdentityProvider as IdentityProvider<::Identifier>>::Success; + pub type IdentityProofActionOf = IdentityDetailsAction<::Identifier, ::ProofOutput>; const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); #[pallet::config] pub trait Config: frame_system::Config { type Identifier: Parameter; - type Identity; - type ProofOutput: Clone + Eq + Debug; type IdentityProofGenerator: IdentityProofGenerator< Self::Identifier, - Self::Identity, + IdentityOf, Output = Self::ProofOutput, >; type IdentityProofDispatcher: IdentityProofDispatcher; - type IdentityProvider: IdentityProvider; + type IdentityProvider: IdentityProvider; + type ProofOutput: Clone + Eq + Debug; type RuntimeEvent: From> + IsType<::RuntimeEvent>; type TxBuilder: TxBuilder; } @@ -62,11 +62,6 @@ pub mod pallet { #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); - // #[pallet::storage] - // #[pallet::getter(fn destination_info)] - // pub type DestinationInfos = StorageMap<_, Blake2_128Concat, NetworkId, - // ()>; - #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -99,12 +94,12 @@ pub mod pallet { let destination: MultiLocation = (*destination).try_into().map_err(|_| Error::::BadVersion)?; let action: IdentityProofActionOf = match T::IdentityProvider::retrieve(&identifier) { - Ok(Some((identity, _))) => { + Ok(Some(identity)) => { let identity_proof = T::IdentityProofGenerator::generate_commitment(&identifier, &identity) .map_err(|_| Error::::IdentityProofGeneration)?; - Ok(IdentityProofAction::Updated(identifier, identity_proof, ())) + Ok(IdentityDetailsAction::Updated(identifier, identity_proof, ())) } - Ok(None) => Ok(IdentityProofAction::Deleted(identifier)), + Ok(None) => Ok(IdentityDetailsAction::Deleted(identifier)), Err(_) => Err(Error::::IdentityNotFound), }?; // TODO: Add correct version creation based on lookup (?) diff --git a/pallets/pallet-dip-provider/src/traits.rs b/pallets/pallet-dip-provider/src/traits.rs index fd9b90d04..12784472e 100644 --- a/pallets/pallet-dip-provider/src/traits.rs +++ b/pallets/pallet-dip-provider/src/traits.rs @@ -16,7 +16,7 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -use dip_support::IdentityProofAction; +use dip_support::IdentityDetailsAction; use xcm::{latest::prelude::*, DoubleEncoded}; pub use identity_generation::*; @@ -61,7 +61,7 @@ pub mod identity_dispatch { type Error; fn pre_dispatch>( - action: IdentityProofAction, + action: IdentityDetailsAction, asset: MultiAsset, weight: Weight, destination: MultiLocation, @@ -80,7 +80,7 @@ pub mod identity_dispatch { type Error = (); fn pre_dispatch<_B>( - _action: IdentityProofAction, + _action: IdentityDetailsAction, _asset: MultiAsset, _weight: Weight, _destination: MultiLocation, @@ -111,7 +111,7 @@ pub mod identity_dispatch { type Error = SendError; fn pre_dispatch>( - action: IdentityProofAction, + action: IdentityDetailsAction, asset: MultiAsset, weight: Weight, destination: MultiLocation, @@ -149,35 +149,38 @@ pub mod identity_dispatch { pub use identity_provision::*; pub mod identity_provision { + use sp_std::marker::PhantomData; - pub trait IdentityProvider { + pub trait IdentityProvider { type Error; + type Success; - fn retrieve(identifier: &Identifier) -> Result, Self::Error>; + fn retrieve(identifier: &Identifier) -> Result, Self::Error>; } // Return the `Default` value if `Identity` adn `Details` both implement it. - pub struct DefaultIdentityProvider; + pub struct DefaultIdentityProvider(PhantomData); - impl IdentityProvider for DefaultIdentityProvider + impl IdentityProvider for DefaultIdentityProvider where Identity: Default, - Details: Default, { type Error = (); + type Success = Identity; - fn retrieve(_identifier: &Identifier) -> Result, Self::Error> { - Ok(Some((Identity::default(), Details::default()))) + fn retrieve(_identifier: &Identifier) -> Result, Self::Error> { + Ok(Some(Identity::default())) } } // Always return `None`. Might be useful for tests. pub struct NoneIdentityProvider; - impl IdentityProvider for NoneIdentityProvider { + impl IdentityProvider for NoneIdentityProvider { type Error = (); + type Success = (); - fn retrieve(_identifier: &Identifier) -> Result, Self::Error> { + fn retrieve(_identifier: &Identifier) -> Result, Self::Error> { Ok(None) } } @@ -190,6 +193,6 @@ pub trait TxBuilder { fn build( dest: MultiLocation, - action: IdentityProofAction, + action: IdentityDetailsAction, ) -> Result, Self::Error>; } diff --git a/pallets/pallet-web3-names/src/lib.rs b/pallets/pallet-web3-names/src/lib.rs index d6ed7c4a1..43ecd1f63 100644 --- a/pallets/pallet-web3-names/src/lib.rs +++ b/pallets/pallet-web3-names/src/lib.rs @@ -115,6 +115,8 @@ pub mod pallet { /// The max encoded length of a name. #[pallet::constant] type MaxNameLength: Get; + // FIXME: Refactor the definition of AsciiWeb3Name so that we don't need to + // require `Ord` here /// The type of a name. type Web3Name: FullCodec + Debug @@ -122,7 +124,8 @@ pub mod pallet { + Clone + TypeInfo + TryFrom, Error = Error> - + MaxEncodedLen; + + MaxEncodedLen + + Ord; /// The type of a name owner. type Web3NameOwner: Parameter + MaxEncodedLen; /// Weight information for extrinsics in this pallet. diff --git a/pallets/pallet-web3-names/src/web3_name.rs b/pallets/pallet-web3-names/src/web3_name.rs index 37b393a16..e83e221aa 100644 --- a/pallets/pallet-web3-names/src/web3_name.rs +++ b/pallets/pallet-web3-names/src/web3_name.rs @@ -82,6 +82,27 @@ impl PartialEq for AsciiWeb3Name { } } +// FIXME: did not find a way to automatically implement this. +impl Eq for AsciiWeb3Name { + fn assert_receiver_is_total_eq(&self) { + self.0.assert_receiver_is_total_eq() + } +} + +// FIXME: did not find a way to automatically implement this. +impl PartialOrd for AsciiWeb3Name { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.as_slice().partial_cmp(other.as_slice()) + } +} + +// FIXME: did not find a way to automatically implement this. +impl Ord for AsciiWeb3Name { + fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + // FIXME: did not find a way to automatically implement this. impl Clone for AsciiWeb3Name { fn clone(&self) -> Self { @@ -89,6 +110,13 @@ impl Clone for AsciiWeb3Name { } } +// FIXME: did not find a way to automatically implement this. +impl Default for AsciiWeb3Name { + fn default() -> Self { + Self(BoundedVec::default(), PhantomData) + } +} + /// KILT web3 name ownership details. #[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, TypeInfo, MaxEncodedLen)] pub struct Web3NameOwnership { diff --git a/runtime-api/dip-provider/src/lib.rs b/runtime-api/dip-provider/src/lib.rs index 2cdcb534c..b910f734c 100644 --- a/runtime-api/dip-provider/src/lib.rs +++ b/runtime-api/dip-provider/src/lib.rs @@ -21,13 +21,11 @@ use parity_scale_codec::Codec; sp_api::decl_runtime_apis! { - pub trait DipProvider where - DidIdentifier: Codec, - KeyId: Codec, - KeyIds: Codec + IntoIterator, + pub trait DipProvider where + ProofRequest: Codec, Success: Codec, Error: Codec, { - fn generate_proof(identifier: DidIdentifier, keys: KeyIds) -> Result; + fn generate_proof(request: ProofRequest) -> Result; } } diff --git a/runtimes/common/Cargo.toml b/runtimes/common/Cargo.toml index 4b1e03c0a..c650a681e 100644 --- a/runtimes/common/Cargo.toml +++ b/runtimes/common/Cargo.toml @@ -27,7 +27,7 @@ ctype.workspace = true delegation = {workspace = true, optional = true} did.workspace = true kilt-dip-support.workspace = true -pallet-did-lookup = {workspace = true, optional = true} +pallet-did-lookup.workspace = true pallet-dip-consumer.workspace = true pallet-dip-provider.workspace = true pallet-inflation = {workspace = true, optional = true} @@ -66,6 +66,7 @@ runtime-benchmarks = [ "attestation/runtime-benchmarks", "ctype/runtime-benchmarks", "did/runtime-benchmarks", + "pallet-did-lookup/runtime-benchmarks", "pallet-dip-consumer/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", @@ -92,6 +93,7 @@ std = [ "log/std", "pallet-authorship/std", "pallet-balances/std", + "pallet-did-lookup/std", "pallet-dip-consumer/std", "pallet-dip-provider/std", "pallet-membership/std", @@ -119,7 +121,7 @@ try-runtime = [ "kilt-support/try-runtime", "pallet-authorship/try-runtime", "pallet-balances/try-runtime", - "pallet-did-lookup", + "pallet-did-lookup/try-runtime", "pallet-inflation", "pallet-membership/try-runtime", "pallet-transaction-payment/try-runtime", diff --git a/runtimes/common/src/dip/did.rs b/runtimes/common/src/dip/did.rs index e954f008e..cebbf104c 100644 --- a/runtimes/common/src/dip/did.rs +++ b/runtimes/common/src/dip/did.rs @@ -17,26 +17,77 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org use did::did_details::DidDetails; +use kilt_dip_support::{ + did::{CombineIdentityFrom, CombinedIdentityResult}, + merkle::RevealedWeb3Name, +}; +use pallet_did_lookup::linkable_account::LinkableAccountId; use pallet_dip_provider::traits::IdentityProvider; -use sp_std::marker::PhantomData; +use sp_std::{marker::PhantomData, vec::Vec}; pub struct DidIdentityProvider(PhantomData); -impl IdentityProvider, ()> for DidIdentityProvider +impl IdentityProvider for DidIdentityProvider where T: did::Config, { // TODO: Proper error handling type Error = (); + type Success = DidDetails; - fn retrieve(identifier: &T::DidIdentifier) -> Result, ())>, Self::Error> { + fn retrieve(identifier: &T::DidIdentifier) -> Result, Self::Error> { match ( did::Pallet::::get_did(identifier), did::Pallet::::get_deleted_did(identifier), ) { - (Some(details), _) => Ok(Some((details, ()))), + (Some(details), _) => Ok(Some(details)), (_, Some(_)) => Ok(None), _ => Err(()), } } } + +pub type Web3OwnershipOf = + RevealedWeb3Name<::Web3Name, ::BlockNumber>; + +pub struct DidWeb3NameProvider(PhantomData); + +impl IdentityProvider for DidWeb3NameProvider +where + T: pallet_web3_names::Config, +{ + // TODO: Proper error handling + type Error = (); + type Success = Web3OwnershipOf; + + fn retrieve(identifier: &T::Web3NameOwner) -> Result, Self::Error> { + let Some(web3_name) = pallet_web3_names::Pallet::::names(identifier) else { return Ok(None) }; + let Some(details) = pallet_web3_names::Pallet::::owner(&web3_name) else { return Err(()) }; + Ok(Some(Web3OwnershipOf:: { + web3_name, + claimed_at: details.claimed_at, + })) + } +} + +pub struct DidLinkedAccountsProvider(PhantomData); + +impl IdentityProvider for DidLinkedAccountsProvider +where + T: pallet_did_lookup::Config, +{ + // TODO: Proper error handling + type Error = (); + type Success = Vec; + + fn retrieve(identifier: &T::DidIdentifier) -> Result, Self::Error> { + Ok(Some( + pallet_did_lookup::ConnectedAccounts::::iter_key_prefix(identifier).collect(), + )) + } +} + +pub type LinkedDidInfoProviderOf = + CombineIdentityFrom, DidWeb3NameProvider, DidLinkedAccountsProvider>; +pub type LinkedDidInfoOf = + CombinedIdentityResult>, Option>, Option>>; diff --git a/runtimes/common/src/dip/merkle.rs b/runtimes/common/src/dip/merkle.rs index f92cc9680..254380b65 100644 --- a/runtimes/common/src/dip/merkle.rs +++ b/runtimes/common/src/dip/merkle.rs @@ -16,22 +16,30 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -use did::{did_details::DidDetails, DidVerificationKeyRelationship, KeyIdOf}; +use did::{DidVerificationKeyRelationship, KeyIdOf}; use frame_support::RuntimeDebug; -use kilt_dip_support::merkle::MerkleProof; +use kilt_dip_support::merkle::{DidKeyMerkleKey, DidKeyMerkleValue, MerkleProof}; +use pallet_did_lookup::linkable_account::LinkableAccountId; use pallet_dip_provider::traits::IdentityProofGenerator; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; -use sp_std::{borrow::ToOwned, collections::btree_set::BTreeSet, marker::PhantomData, vec::Vec}; +use sp_std::{borrow::ToOwned, marker::PhantomData, vec::Vec}; use sp_trie::{generate_trie_proof, LayoutV1, MemoryDB, TrieDBMutBuilder, TrieHash, TrieMut}; -use kilt_dip_support::merkle::{ - DidKeyRelationship, KeyDetailsKey, KeyDetailsValue, KeyReferenceKey, KeyReferenceValue, ProofLeaf, -}; +use kilt_dip_support::merkle::{DidKeyRelationship, ProofLeaf}; + +use crate::{dip::did::LinkedDidInfoOf, DidIdentifier}; pub type BlindedValue = Vec; -pub type DidMerkleProofOf = - MerkleProof, ProofLeaf, ::BlockNumber>>; +pub type DidMerkleProofOf = MerkleProof< + Vec, + ProofLeaf< + KeyIdOf, + ::BlockNumber, + ::Web3Name, + LinkableAccountId, + >, +>; #[derive(Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)] pub struct CompleteMerkleProof { @@ -41,9 +49,16 @@ pub struct CompleteMerkleProof { pub struct DidMerkleRootGenerator(PhantomData); +type ProofLeafOf = ProofLeaf< + KeyIdOf, + ::BlockNumber, + ::Web3Name, + LinkableAccountId, +>; + impl DidMerkleRootGenerator where - T: did::Config, + T: did::Config + pallet_did_lookup::Config + pallet_web3_names::Config, { // Calls the function in the `sp_trie` crate to generate the merkle root given // the provided `DidDetails`. @@ -56,66 +71,105 @@ where // A valid proof will contain a leaf with the key details for each reference // leaf, with multiple reference leaves potentially referring to the same // details leaf, as we already do with out `DidDetails` type. - fn calculate_root_with_db(identity: &DidDetails, db: &mut MemoryDB) -> Result { + fn calculate_root_with_db(identity: &LinkedDidInfoOf, db: &mut MemoryDB) -> Result { + // Fails if the DID details do not exist. + let (Some(did_details), web3_name, linked_accounts) = (&identity.a, &identity.b, &identity.c) else { return Err(()) }; let mut trie = TrieHash::>::default(); let mut trie_builder = TrieDBMutBuilder::>::new(db, &mut trie).build(); // Authentication key - let auth_leaf = ProofLeaf::<_, T::BlockNumber>::KeyReference( - KeyReferenceKey( - identity.authentication_key, + // TODO: No panic + let auth_key_details = did_details + .public_keys + .get(&did_details.authentication_key) + .expect("Authentication key should be part of the public keys."); + let auth_leaf = ProofLeafOf::::DidKey( + DidKeyMerkleKey( + did_details.authentication_key, DidVerificationKeyRelationship::Authentication.into(), ), - KeyReferenceValue, + DidKeyMerkleValue(auth_key_details.clone()), ); trie_builder .insert(auth_leaf.encoded_key().as_slice(), auth_leaf.encoded_value().as_slice()) .map_err(|_| ())?; // Attestation key, if present - if let Some(att_key_id) = identity.attestation_key { - let att_leaf = ProofLeaf::<_, T::BlockNumber>::KeyReference( - KeyReferenceKey(att_key_id, DidVerificationKeyRelationship::AssertionMethod.into()), - KeyReferenceValue, + if let Some(att_key_id) = did_details.attestation_key { + let att_key_details = did_details + .public_keys + .get(&att_key_id) + .expect("Attestation key should be part of the public keys."); + let att_leaf = ProofLeafOf::::DidKey( + (att_key_id, DidVerificationKeyRelationship::AssertionMethod.into()).into(), + att_key_details.clone().into(), ); trie_builder .insert(att_leaf.encoded_key().as_slice(), att_leaf.encoded_value().as_slice()) .map_err(|_| ())?; }; // Delegation key, if present - if let Some(del_key_id) = identity.delegation_key { - let del_leaf = ProofLeaf::<_, T::BlockNumber>::KeyReference( - KeyReferenceKey(del_key_id, DidVerificationKeyRelationship::CapabilityDelegation.into()), - KeyReferenceValue, + if let Some(del_key_id) = did_details.delegation_key { + let del_key_details = did_details + .public_keys + .get(&del_key_id) + .expect("Delegation key should be part of the public keys."); + let del_leaf = ProofLeafOf::::DidKey( + (del_key_id, DidVerificationKeyRelationship::CapabilityDelegation.into()).into(), + del_key_details.clone().into(), ); trie_builder .insert(del_leaf.encoded_key().as_slice(), del_leaf.encoded_value().as_slice()) .map_err(|_| ())?; }; // Key agreement keys - identity + did_details .key_agreement_keys .iter() .try_for_each(|id| -> Result<(), ()> { - let enc_leaf = ProofLeaf::<_, T::BlockNumber>::KeyReference( - KeyReferenceKey(*id, DidKeyRelationship::Encryption), - KeyReferenceValue, + let key_agreement_details = did_details + .public_keys + .get(id) + .expect("Key agreement key should be part of the public keys."); + let enc_leaf = ProofLeafOf::::DidKey( + (*id, DidKeyRelationship::Encryption).into(), + key_agreement_details.clone().into(), ); trie_builder .insert(enc_leaf.encoded_key().as_slice(), enc_leaf.encoded_value().as_slice()) .map_err(|_| ())?; Ok(()) })?; - // Public keys - identity - .public_keys - .iter() - .try_for_each(|(id, key_details)| -> Result<(), ()> { - let key_leaf = ProofLeaf::KeyDetails(KeyDetailsKey(*id), KeyDetailsValue(key_details.clone())); - trie_builder - .insert(key_leaf.encoded_key().as_slice(), key_leaf.encoded_value().as_slice()) - .map_err(|_| ())?; - Ok(()) - })?; + + // Linked accounts + if let Some(linked_accounts) = linked_accounts { + linked_accounts + .iter() + .try_for_each(|linked_account| -> Result<(), ()> { + let linked_account_leaf = ProofLeafOf::::LinkedAccount(linked_account.clone().into(), ().into()); + trie_builder + .insert( + linked_account_leaf.encoded_key().as_slice(), + linked_account_leaf.encoded_value().as_slice(), + ) + .map_err(|_| ())?; + Ok(()) + })?; + } + + // Web3name, if present + if let Some(web3name_details) = web3_name { + let web3_name_leaf = ProofLeafOf::::Web3Name( + web3name_details.web3_name.clone().into(), + web3name_details.claimed_at.into(), + ); + trie_builder + .insert( + web3_name_leaf.encoded_key().as_slice(), + web3_name_leaf.encoded_value().as_slice(), + ) + .map_err(|_| ())?; + } + trie_builder.commit(); Ok(trie_builder.root().to_owned()) } @@ -126,76 +180,86 @@ where // generates a merkle proof which only reveals the details of the provided key // IDs. #[allow(clippy::result_unit_err)] - pub fn generate_proof<'a, K>( - identity: &DidDetails, - mut key_ids: K, + pub fn generate_proof<'a, K, A>( + identity: &LinkedDidInfoOf, + key_ids: K, + should_include_web3_name: bool, + account_ids: A, ) -> Result>, ()> where K: Iterator>, + A: Iterator, { + // Fails if the DID details do not exist. + let (Some(did_details), linked_web3_name, linked_accounts) = (&identity.a, &identity.b, &identity.c) else { return Err(()) }; + let mut db = MemoryDB::default(); let root = Self::calculate_root_with_db(identity, &mut db)?; - #[allow(clippy::type_complexity)] - let leaves: BTreeSet, T::BlockNumber>> = - key_ids.try_fold(BTreeSet::new(), |mut set, key_id| -> Result<_, ()> { - let key_details = identity.public_keys.get(key_id).ok_or(())?; - // Adds a key reference leaf for each relationship the key ID is part of. - if *key_id == identity.authentication_key { - set.insert(ProofLeaf::KeyReference( - KeyReferenceKey(*key_id, DidVerificationKeyRelationship::Authentication.into()), - KeyReferenceValue, - )); - } - if Some(*key_id) == identity.attestation_key { - set.insert(ProofLeaf::KeyReference( - KeyReferenceKey(*key_id, DidVerificationKeyRelationship::AssertionMethod.into()), - KeyReferenceValue, - )); - } - if Some(*key_id) == identity.delegation_key { - set.insert(ProofLeaf::KeyReference( - KeyReferenceKey(*key_id, DidVerificationKeyRelationship::CapabilityDelegation.into()), - KeyReferenceValue, - )); + let mut leaves = key_ids + .map(|key_id| -> Result, ()> { + let key_details = did_details.public_keys.get(key_id).ok_or(())?; + // Create the merkle leaf key depending on the relationship of the key to the + // DID document. + let did_key_merkle_key: DidKeyMerkleKey> = if *key_id == did_details.authentication_key { + Ok((*key_id, DidVerificationKeyRelationship::Authentication.into()).into()) + } else if Some(*key_id) == did_details.attestation_key { + Ok((*key_id, DidVerificationKeyRelationship::AssertionMethod.into()).into()) + } else if Some(*key_id) == did_details.delegation_key { + Ok((*key_id, DidVerificationKeyRelationship::CapabilityDelegation.into()).into()) + } else if did_details.key_agreement_keys.contains(key_id) { + Ok((*key_id, DidKeyRelationship::Encryption).into()) + } else { + Err(()) + }?; + Ok(ProofLeaf::DidKey(did_key_merkle_key, key_details.clone().into())) + }) + .chain(account_ids.map(|account_id| -> Result, ()> { + let Some(linked_accounts) = linked_accounts else { return Err(()) }; + if linked_accounts.contains(account_id) { + Ok(ProofLeaf::LinkedAccount(account_id.clone().into(), ().into())) + } else { + Err(()) } - if identity.key_agreement_keys.contains(key_id) { - set.insert(ProofLeaf::KeyReference( - KeyReferenceKey(*key_id, DidKeyRelationship::Encryption), - KeyReferenceValue, - )); - }; - // Then adds the actual key details to the merkle proof. - // If the same key is specified twice, the old key is simply replaced with a new - // key of the same value. - let key_details_leaf = - ProofLeaf::KeyDetails(KeyDetailsKey(*key_id), KeyDetailsValue(key_details.clone())); - if !set.contains(&key_details_leaf) { - set.insert(key_details_leaf); - } - Ok(set) - })?; + })) + .collect::, _>>()?; + + match (should_include_web3_name, linked_web3_name) { + // If web3name should be included and it exists... + (true, Some(web3name_details)) => { + leaves.push(ProofLeaf::Web3Name( + web3name_details.web3_name.clone().into(), + web3name_details.claimed_at.into(), + )); + Ok(()) + } + // ...else if web3name should be included and it DOES NOT exist... + (true, None) => Err(()), + // ...else if web3name should NOT be included. + (false, _) => Ok(()), + }?; + let encoded_keys: Vec> = leaves.iter().map(|l| l.encoded_key()).collect(); let proof = generate_trie_proof::, _, _, _>(&db, root, &encoded_keys).map_err(|_| ())?; Ok(CompleteMerkleProof { root, proof: DidMerkleProofOf:: { blinded: proof, - revealed: leaves.into_iter().collect::>(), + revealed: leaves.into_iter().collect(), }, }) } } -impl IdentityProofGenerator> for DidMerkleRootGenerator +impl IdentityProofGenerator> for DidMerkleRootGenerator where - T: did::Config, + T: did::Config + pallet_did_lookup::Config + pallet_web3_names::Config, { // TODO: Proper error handling type Error = (); type Output = T::Hash; - fn generate_commitment(_identifier: &T::DidIdentifier, identity: &DidDetails) -> Result { + fn generate_commitment(_identifier: &DidIdentifier, identity: &LinkedDidInfoOf) -> Result { let mut db = MemoryDB::default(); Self::calculate_root_with_db(identity, &mut db) }