diff --git a/Cargo.lock b/Cargo.lock index 5b03ccc153..9aea81789f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2249,6 +2249,7 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-timestamp", "cumulus-primitives-utility", + "did", "frame-executive", "frame-support", "frame-system", @@ -2264,6 +2265,7 @@ dependencies = [ "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", + "pallet-utility", "pallet-xcm", "parachain-info", "parity-scale-codec", diff --git a/dip-template/runtimes/dip-receiver/Cargo.toml b/dip-template/runtimes/dip-receiver/Cargo.toml index 76e495ad63..0621478468 100644 --- a/dip-template/runtimes/dip-receiver/Cargo.toml +++ b/dip-template/runtimes/dip-receiver/Cargo.toml @@ -18,6 +18,7 @@ codec = {package = "parity-scale-codec", workspace = true, features = ["derive"] scale-info = {workspace = true, features = ["derive"]} # DIP +did.workspace = true pallet-did-lookup.workspace = true pallet-dip-receiver.workspace = true runtime-common.workspace = true @@ -35,6 +36,7 @@ pallet-sudo.workspace = true pallet-timestamp.workspace = true pallet-transaction-payment.workspace = true pallet-transaction-payment-rpc-runtime-api.workspace = true +pallet-utility.workspace = true polkadot-parachain.workspace = true sp-api.workspace = true sp-block-builder.workspace = true @@ -73,6 +75,7 @@ default = [ std = [ "codec/std", "scale-info/std", + "did/std", "pallet-did-lookup/std", "pallet-dip-receiver/std", "runtime-common/std", @@ -88,6 +91,7 @@ std = [ "pallet-timestamp/std", "pallet-transaction-payment/std", "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-utility/std", "polkadot-parachain/std", "sp-api/std", "sp-block-builder/std", @@ -117,10 +121,10 @@ std = [ ] runtime-benchmarks = [ - "frame-system/runtime-benchmarks", - "frame-support/runtime-benchmarks", "pallet-dip-receiver/runtime-benchmarks", "runtime-common/runtime-benchmarks", - "xcm-builder/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", ] diff --git a/dip-template/runtimes/dip-receiver/src/dip.rs b/dip-template/runtimes/dip-receiver/src/dip.rs index 18a5ff1b05..b170209347 100644 --- a/dip-template/runtimes/dip-receiver/src/dip.rs +++ b/dip-template/runtimes/dip-receiver/src/dip.rs @@ -16,13 +16,19 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -use runtime_common::dip::{receiver::DidMerkleProofVerifier, ProofLeaf}; +use did::DidVerificationKeyRelationship; +use pallet_dip_receiver::traits::DipCallOriginFilter; +use runtime_common::dip::{ + receiver::{DidMerkleProofVerifier, VerificationResult}, + ProofLeaf, +}; use sp_std::vec::Vec; use crate::{BlockNumber, DidIdentifier, Hash, Hasher, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin}; impl pallet_dip_receiver::Config for Runtime { type BlindedValue = Vec>; + type DipCallOriginFilter = DipCallFilter; type Identifier = DidIdentifier; type ProofLeaf = ProofLeaf; type ProofDigest = Hash; @@ -31,3 +37,90 @@ impl pallet_dip_receiver::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeOrigin = RuntimeOrigin; } + +fn derive_verification_key_relationship(call: &RuntimeCall) -> Option { + match call { + RuntimeCall::DidLookup { .. } => Some(DidVerificationKeyRelationship::Authentication), + RuntimeCall::Utility(pallet_utility::Call::batch { calls }) => single_key_relationship(calls).ok(), + RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => single_key_relationship(calls).ok(), + RuntimeCall::Utility(pallet_utility::Call::force_batch { calls }) => single_key_relationship(calls).ok(), + _ => None, + } +} + +// Taken and adapted from `impl +// did::DeriveDidCallAuthorizationVerificationKeyRelationship for RuntimeCall` +// in Spiritnet/Peregrine runtime. +fn single_key_relationship(calls: &[RuntimeCall]) -> Result { + let first_call_relationship = calls.get(0).and_then(derive_verification_key_relationship).ok_or(())?; + calls + .iter() + .skip(1) + .map(derive_verification_key_relationship) + .try_fold(first_call_relationship, |acc, next| { + if next == Some(acc) { + Ok(acc) + } else { + Err(()) + } + }) +} + +pub struct DipCallFilter; + +impl DipCallOriginFilter for DipCallFilter { + type Error = (); + type Proof = VerificationResult; + type Success = (); + + // Accepts only a DipOrigin for the DidLookup pallet calls. + fn check_proof(call: RuntimeCall, proof: Self::Proof) -> Result { + let key_relationship = single_key_relationship(&[call])?; + if proof.0.iter().any(|l| l.relationship == key_relationship.into()) { + Ok(()) + } else { + Err(()) + } + } +} + +#[cfg(test)] +mod dip_call_origin_filter_tests { + use super::*; + + use frame_support::assert_err; + + #[test] + fn test_key_relationship_derivation() { + // Can call DidLookup functions with an authentication key + let did_lookup_call = RuntimeCall::DidLookup(pallet_did_lookup::Call::associate_sender {}); + assert_eq!( + single_key_relationship(&[did_lookup_call]), + Ok(DidVerificationKeyRelationship::Authentication) + ); + // Can't call System functions with a DID key (hence a DIP origin) + let system_call = RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); + assert_err!(single_key_relationship(&[system_call]), ()); + // Can't call empty batch with a DID key + let empty_batch_call = RuntimeCall::Utility(pallet_utility::Call::batch_all { calls: vec![] }); + assert_err!(single_key_relationship(&[empty_batch_call]), ()); + // Can call batch with a DipLookup with an authentication key + let did_lookup_batch_call = RuntimeCall::Utility(pallet_utility::Call::batch_all { + calls: vec![pallet_did_lookup::Call::associate_sender {}.into()], + }); + assert_eq!( + single_key_relationship(&[did_lookup_batch_call]), + Ok(DidVerificationKeyRelationship::Authentication) + ); + // Can't call a batch with different required keys + let did_lookup_batch_call = RuntimeCall::Utility(pallet_utility::Call::batch_all { + calls: vec![ + // Authentication key + pallet_did_lookup::Call::associate_sender {}.into(), + // No key + frame_system::Call::remark { remark: vec![] }.into(), + ], + }); + assert_err!(single_key_relationship(&[did_lookup_batch_call]), ()); + } +} diff --git a/dip-template/runtimes/dip-receiver/src/lib.rs b/dip-template/runtimes/dip-receiver/src/lib.rs index c678bf0c2c..1037d471ba 100644 --- a/dip-template/runtimes/dip-receiver/src/lib.rs +++ b/dip-template/runtimes/dip-receiver/src/lib.rs @@ -119,6 +119,7 @@ construct_runtime!( Timestamp: pallet_timestamp = 2, ParachainInfo: parachain_info = 3, Sudo: pallet_sudo = 4, + Utility: pallet_utility = 5, // Money Balances: pallet_balances = 10, @@ -277,6 +278,13 @@ impl pallet_sudo::Config for Runtime { type RuntimeEvent = RuntimeEvent; } +impl pallet_utility::Config for Runtime { + type PalletsOrigin = OriginCaller; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + pub const EXISTENTIAL_DEPOSIT: Balance = MILLIUNIT; impl pallet_balances::Config for Runtime { @@ -358,8 +366,8 @@ impl pallet_did_lookup::Config for Runtime { type Currency = Balances; type Deposit = ConstU128; type DidIdentifier = DidIdentifier; - type EnsureOrigin = EnsureDipOrigin; - type OriginSuccess = DipOrigin; + type EnsureOrigin = EnsureDipOrigin; + type OriginSuccess = DipOrigin; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); } diff --git a/pallets/pallet-dip-receiver/src/lib.rs b/pallets/pallet-dip-receiver/src/lib.rs index cb93b72281..a351ae1b9a 100644 --- a/pallets/pallet-dip-receiver/src/lib.rs +++ b/pallets/pallet-dip-receiver/src/lib.rs @@ -36,8 +36,9 @@ pub mod pallet { use dip_support::{latest::IdentityProofAction, VersionedIdentityProof, VersionedIdentityProofAction}; - use crate::traits::IdentityProofVerifier; + use crate::traits::{DipCallOriginFilter, IdentityProofVerifier}; + pub type VerificationResultOf = <::ProofVerifier as IdentityProofVerifier>::VerificationResult; pub type VersionedIdentityProofOf = VersionedIdentityProof<::BlindedValue, ::ProofLeaf>; @@ -52,6 +53,7 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { type BlindedValue: Parameter; + type DipCallOriginFilter: DipCallOriginFilter<::RuntimeCall, Proof = VerificationResultOf>; type Identifier: Parameter + MaxEncodedLen; type ProofLeaf: Parameter; type ProofDigest: Parameter + MaxEncodedLen; @@ -81,6 +83,7 @@ pub mod pallet { #[pallet::error] pub enum Error { + BadOrigin, Dispatch, IdentityNotFound, InvalidProof, @@ -89,7 +92,11 @@ pub mod pallet { // The new origin other pallets can use. #[pallet::origin] - pub type Origin = DipOrigin<::Identifier, ::AccountId>; + pub type Origin = DipOrigin< + ::Identifier, + ::AccountId, + <::DipCallOriginFilter as DipCallOriginFilter<::RuntimeCall>>::Success, + >; // TODO: Benchmarking #[pallet::call] @@ -130,12 +137,17 @@ pub mod pallet { ) -> DispatchResult { let submitter = ensure_signed(origin)?; let proof_digest = IdentityProofs::::get(&identifier).ok_or(Error::::IdentityNotFound)?; - let _ = T::ProofVerifier::verify_proof_against_digest(proof, proof_digest) + let proof_verification_result = T::ProofVerifier::verify_proof_against_digest(proof, proof_digest) .map_err(|_| Error::::InvalidProof)?; + // TODO: Better error handling + // TODO: Avoid cloning `call` + let proof_result = T::DipCallOriginFilter::check_proof(*call.clone(), proof_verification_result) + .map_err(|_| Error::::BadOrigin)?; // TODO: Proper DID signature verification (and cross-chain replay protection) let did_origin = DipOrigin { identifier, account_address: submitter, + details: proof_result, }; // TODO: Use dispatch info for weight calculation let _ = call.dispatch(did_origin.into()).map_err(|_| Error::::Dispatch)?; diff --git a/pallets/pallet-dip-receiver/src/origin.rs b/pallets/pallet-dip-receiver/src/origin.rs index 84b73cceae..98b033791d 100644 --- a/pallets/pallet-dip-receiver/src/origin.rs +++ b/pallets/pallet-dip-receiver/src/origin.rs @@ -22,19 +22,22 @@ use scale_info::TypeInfo; use sp_std::marker::PhantomData; #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub struct DipOrigin { +pub struct DipOrigin { pub identifier: Identifier, pub account_address: AccountId, + pub details: Details, } -pub struct EnsureDipOrigin(PhantomData<(Identifier, AccountId)>); +pub struct EnsureDipOrigin(PhantomData<(Identifier, AccountId, Details)>); #[cfg(not(feature = "runtime-benchmarks"))] -impl EnsureOrigin for EnsureDipOrigin +impl EnsureOrigin + for EnsureDipOrigin where - OuterOrigin: From> + Into, OuterOrigin>>, + OuterOrigin: From> + + Into, OuterOrigin>>, { - type Success = DipOrigin; + type Success = DipOrigin; fn try_origin(o: OuterOrigin) -> Result { o.into() @@ -42,14 +45,17 @@ where } #[cfg(feature = "runtime-benchmarks")] -impl EnsureOrigin for EnsureDipOrigin +impl EnsureOrigin + for EnsureDipOrigin where - OuterOrigin: From> + Into, OuterOrigin>>, + OuterOrigin: From> + + Into, OuterOrigin>>, // Additional trait bounds only valid when benchmarking Identifier: From<[u8; 32]>, AccountId: From<[u8; 32]>, + Details: Default, { - type Success = DipOrigin; + type Success = DipOrigin; fn try_origin(o: OuterOrigin) -> Result { o.into() @@ -59,12 +65,13 @@ where Ok(OuterOrigin::from(DipOrigin { identifier: Identifier::from([0u8; 32]), account_address: AccountId::from([0u8; 32]), + details: Default::default(), })) } } -impl kilt_support::traits::CallSources - for DipOrigin +impl kilt_support::traits::CallSources + for DipOrigin where Identifier: Clone, AccountId: Clone, diff --git a/pallets/pallet-dip-receiver/src/traits.rs b/pallets/pallet-dip-receiver/src/traits.rs index ef7642fff9..c134a038f5 100644 --- a/pallets/pallet-dip-receiver/src/traits.rs +++ b/pallets/pallet-dip-receiver/src/traits.rs @@ -50,3 +50,11 @@ impl IdentityProofVerifier Ok(()) } } + +pub trait DipCallOriginFilter { + type Error; + type Proof; + type Success; + + fn check_proof(call: Call, proof: Self::Proof) -> Result; +}