Skip to content

Commit

Permalink
feat: add DID key verification relationship logic (#502)
Browse files Browse the repository at this point in the history
Fixes KILTprotocol/ticket#2587.

This PR adds a new trait `DipCallOriginFilter` that exposes a `fn
check_proof(call: Call, proof: Self::Proof) -> Result<Self::Success,
Self::Error>` function, called by the runtime to verify if a call can be
dispatched with the provided proof or not.
  • Loading branch information
ntn-x2 committed Apr 19, 2023
1 parent 5a5d8f7 commit f6e447b
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 19 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions dip-template/runtimes/dip-receiver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ 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
Expand All @@ -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
Expand Down Expand Up @@ -73,6 +75,7 @@ default = [
std = [
"parity-scale-codec/std",
"scale-info/std",
"did/std",
"pallet-did-lookup/std",
"pallet-dip-receiver/std",
"runtime-common/std",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
]
95 changes: 94 additions & 1 deletion dip-template/runtimes/dip-receiver/src/dip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u8>>;
type DipCallOriginFilter = DipCallFilter;
type Identifier = DidIdentifier;
type ProofLeaf = ProofLeaf<Hash, BlockNumber>;
type ProofDigest = Hash;
Expand All @@ -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<DidVerificationKeyRelationship> {
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<DidVerificationKeyRelationship, ()> {
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<RuntimeCall> for DipCallFilter {
type Error = ();
type Proof = VerificationResult<BlockNumber>;
type Success = ();

// Accepts only a DipOrigin for the DidLookup pallet calls.
fn check_proof(call: RuntimeCall, proof: Self::Proof) -> Result<Self::Success, Self::Error> {
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]), ());
}
}
12 changes: 10 additions & 2 deletions dip-template/runtimes/dip-receiver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -358,8 +366,8 @@ impl pallet_did_lookup::Config for Runtime {
type Currency = Balances;
type Deposit = ConstU128<UNIT>;
type DidIdentifier = DidIdentifier;
type EnsureOrigin = EnsureDipOrigin<DidIdentifier, AccountId>;
type OriginSuccess = DipOrigin<DidIdentifier, AccountId>;
type EnsureOrigin = EnsureDipOrigin<DidIdentifier, AccountId, ()>;
type OriginSuccess = DipOrigin<DidIdentifier, AccountId, ()>;
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
}
Expand Down
18 changes: 15 additions & 3 deletions pallets/pallet-dip-receiver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = <<T as Config>::ProofVerifier as IdentityProofVerifier>::VerificationResult;
pub type VersionedIdentityProofOf<T> =
VersionedIdentityProof<<T as Config>::BlindedValue, <T as Config>::ProofLeaf>;

Expand All @@ -52,6 +53,7 @@ pub mod pallet {
#[pallet::config]
pub trait Config: frame_system::Config {
type BlindedValue: Parameter;
type DipCallOriginFilter: DipCallOriginFilter<<Self as Config>::RuntimeCall, Proof = VerificationResultOf<Self>>;
type Identifier: Parameter + MaxEncodedLen;
type ProofLeaf: Parameter;
type ProofDigest: Parameter + MaxEncodedLen;
Expand Down Expand Up @@ -81,6 +83,7 @@ pub mod pallet {

#[pallet::error]
pub enum Error<T> {
BadOrigin,
Dispatch,
IdentityNotFound,
InvalidProof,
Expand All @@ -89,7 +92,11 @@ pub mod pallet {

// The new origin other pallets can use.
#[pallet::origin]
pub type Origin<T> = DipOrigin<<T as Config>::Identifier, <T as frame_system::Config>::AccountId>;
pub type Origin<T> = DipOrigin<
<T as Config>::Identifier,
<T as frame_system::Config>::AccountId,
<<T as Config>::DipCallOriginFilter as DipCallOriginFilter<<T as Config>::RuntimeCall>>::Success,
>;

// TODO: Benchmarking
#[pallet::call]
Expand Down Expand Up @@ -130,12 +137,17 @@ pub mod pallet {
) -> DispatchResult {
let submitter = ensure_signed(origin)?;
let proof_digest = IdentityProofs::<T>::get(&identifier).ok_or(Error::<T>::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::<T>::InvalidProof)?;
// TODO: Better error handling
// TODO: Avoid cloning `call`
let proof_result = T::DipCallOriginFilter::check_proof(*call.clone(), proof_verification_result)
.map_err(|_| Error::<T>::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::<T>::Dispatch)?;
Expand Down
27 changes: 17 additions & 10 deletions pallets/pallet-dip-receiver/src/origin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,40 @@ use scale_info::TypeInfo;
use sp_std::marker::PhantomData;

#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct DipOrigin<Identifier, AccountId> {
pub struct DipOrigin<Identifier, AccountId, Details> {
pub identifier: Identifier,
pub account_address: AccountId,
pub details: Details,
}

pub struct EnsureDipOrigin<Identifier, AccountId>(PhantomData<(Identifier, AccountId)>);
pub struct EnsureDipOrigin<Identifier, AccountId, Details>(PhantomData<(Identifier, AccountId, Details)>);

#[cfg(not(feature = "runtime-benchmarks"))]
impl<OuterOrigin, Identifier, AccountId> EnsureOrigin<OuterOrigin> for EnsureDipOrigin<Identifier, AccountId>
impl<OuterOrigin, Identifier, AccountId, Details> EnsureOrigin<OuterOrigin>
for EnsureDipOrigin<Identifier, AccountId, Details>
where
OuterOrigin: From<DipOrigin<Identifier, AccountId>> + Into<Result<DipOrigin<Identifier, AccountId>, OuterOrigin>>,
OuterOrigin: From<DipOrigin<Identifier, AccountId, Details>>
+ Into<Result<DipOrigin<Identifier, AccountId, Details>, OuterOrigin>>,
{
type Success = DipOrigin<Identifier, AccountId>;
type Success = DipOrigin<Identifier, AccountId, Details>;

fn try_origin(o: OuterOrigin) -> Result<Self::Success, OuterOrigin> {
o.into()
}
}

#[cfg(feature = "runtime-benchmarks")]
impl<OuterOrigin, Identifier, AccountId> EnsureOrigin<OuterOrigin> for EnsureDipOrigin<Identifier, AccountId>
impl<OuterOrigin, Identifier, AccountId, Details> EnsureOrigin<OuterOrigin>
for EnsureDipOrigin<Identifier, AccountId, Details>
where
OuterOrigin: From<DipOrigin<Identifier, AccountId>> + Into<Result<DipOrigin<Identifier, AccountId>, OuterOrigin>>,
OuterOrigin: From<DipOrigin<Identifier, AccountId, Details>>
+ Into<Result<DipOrigin<Identifier, AccountId, Details>, OuterOrigin>>,
// Additional trait bounds only valid when benchmarking
Identifier: From<[u8; 32]>,
AccountId: From<[u8; 32]>,
Details: Default,
{
type Success = DipOrigin<Identifier, AccountId>;
type Success = DipOrigin<Identifier, AccountId, Details>;

fn try_origin(o: OuterOrigin) -> Result<Self::Success, OuterOrigin> {
o.into()
Expand All @@ -59,12 +65,13 @@ where
Ok(OuterOrigin::from(DipOrigin {
identifier: Identifier::from([0u8; 32]),
account_address: AccountId::from([0u8; 32]),
details: Default::default(),
}))
}
}

impl<Identifier, AccountId> kilt_support::traits::CallSources<AccountId, Identifier>
for DipOrigin<Identifier, AccountId>
impl<Identifier, AccountId, Details> kilt_support::traits::CallSources<AccountId, Identifier>
for DipOrigin<Identifier, AccountId, Details>
where
Identifier: Clone,
AccountId: Clone,
Expand Down
8 changes: 8 additions & 0 deletions pallets/pallet-dip-receiver/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,11 @@ impl<ProofDigest, Leaf, BlindedValue> IdentityProofVerifier
Ok(())
}
}

pub trait DipCallOriginFilter<Call> {
type Error;
type Proof;
type Success;

fn check_proof(call: Call, proof: Self::Proof) -> Result<Self::Success, Self::Error>;
}

0 comments on commit f6e447b

Please sign in to comment.