From a795c0e745651da7a89904527eaa5d609fe74ac2 Mon Sep 17 00:00:00 2001 From: tuxmain <tuxmain@zettascript.org> Date: Tue, 18 Jan 2022 22:30:35 +0100 Subject: [PATCH] feat(identity): revoke_identity --- pallets/identity/src/lib.rs | 105 ++++++++++++++++++++++----- pallets/identity/src/mock.rs | 4 +- pallets/identity/src/tests.rs | 66 +++++++++++++++++ pallets/identity/src/types.rs | 8 ++ runtime/common/src/pallets_config.rs | 2 + 5 files changed, 165 insertions(+), 20 deletions(-) diff --git a/pallets/identity/src/lib.rs b/pallets/identity/src/lib.rs index 567ebc9df..a40f726a4 100644 --- a/pallets/identity/src/lib.rs +++ b/pallets/identity/src/lib.rs @@ -36,7 +36,7 @@ use crate::traits::*; use codec::Codec; use frame_support::dispatch::Weight; use frame_system::RawOrigin; -use sp_runtime::traits::{AtLeast32BitUnsigned, One, Saturating, Zero}; +use sp_runtime::traits::{AtLeast32BitUnsigned, IdentifyAccount, One, Saturating, Zero}; use sp_std::fmt::Debug; use sp_std::prelude::*; @@ -47,6 +47,7 @@ pub mod pallet { use frame_support::traits::StorageVersion; use frame_system::pallet_prelude::*; use sp_membership::traits::MembershipAction as _; + use sp_runtime::traits::Verify; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); @@ -102,6 +103,10 @@ pub mod pallet { type MaxNoRightPeriod: Get<Self::BlockNumber>; /// type Membership: sp_membership::traits::MembershipAction<Self::IdtyIndex, Self::Origin>; + /// Signing key of revocation payload + type RevocationSigner: IdentifyAccount<AccountId = Self::AccountId>; + /// Signature of revocation payload + type RevocationSignature: Parameter + Verify<Signer = Self::RevocationSigner>; } // GENESIS STUFFĂ‚ // @@ -510,15 +515,13 @@ pub mod pallet { .rights .binary_search_by(|(right_, _)| right_.cmp(&right)) { - let name = idty_value.name.clone(); - let old_key_opt = if let Some(ref subkey) = idty_value.rights[index].1 { - Some(subkey.clone()) - } else if right.allow_owner_key() { - Some(idty_value.owner_key.clone()) - } else { - None - }; - idty_value.rights.remove(index); + <Pallet<T>>::do_del_right( + idty_index, + idty_value.name.clone(), + &idty_value.owner_key, + right, + idty_value.rights.remove(index).1, + ); if idty_value.rights.is_empty() { let block_number = frame_system::pallet::Pallet::<T>::block_number(); @@ -531,15 +534,6 @@ pub mod pallet { } <Identities<T>>::insert(idty_index, idty_value); - Self::deposit_event(Event::<T>::IdtyLostRight(name, right)); - if old_key_opt.is_some() { - T::OnRightKeyChange::on_right_key_change( - idty_index, - right, - old_key_opt, - None, - ); - } Ok(().into()) } else { Err(Error::<T>::RightNotExist.into()) @@ -549,6 +543,55 @@ pub mod pallet { } } #[pallet::weight(0)] + pub fn revoke_identity( + origin: OriginFor<T>, + payload: RevocationPayload<T::AccountId, T::IdtyIndex, T::Hash>, + payload_sig: T::RevocationSignature, + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + if payload.genesis_hash != frame_system::Pallet::<T>::block_hash(T::BlockNumber::zero()) + { + return Err(Error::<T>::BadGenesisHash.into()); + } + if !payload.using_encoded(|bytes| payload_sig.verify(bytes, &payload.owner_key)) { + return Err(Error::<T>::BadProof.into()); + } + let idty_index = payload.idty; + + if let Ok(mut idty_value) = <Identities<T>>::try_get(idty_index) { + if idty_value.owner_key != payload.owner_key { + return Err(Error::<T>::RequireToBeOwner.into()); + } + if idty_value.status != IdtyStatus::Validated { + return Err(Error::<T>::IdtyNotValidated.into()); + } + + let idty_name = idty_value.name.clone(); + idty_value.rights.drain(..).for_each(|(right, subkey_opt)| { + <Pallet<T>>::do_del_right( + idty_index, + idty_name.clone(), + &payload.owner_key, + right, + subkey_opt, + ) + }); + + let block_number = frame_system::pallet::Pallet::<T>::block_number(); + let removable_on = block_number + T::MaxNoRightPeriod::get(); + idty_value.removable_on = removable_on; + <IdentitiesRemovableOn<T>>::append( + removable_on, + (idty_index, IdtyStatus::Validated), + ); + <Identities<T>>::insert(idty_index, idty_value); + + Ok(().into()) + } else { + Err(Error::<T>::IdtyNotFound.into()) + } + } + #[pallet::weight(0)] pub fn set_right_subkey( origin: OriginFor<T>, idty_index: T::IdtyIndex, @@ -608,6 +651,10 @@ pub mod pallet { #[pallet::error] pub enum Error<T> { + /// Genesis hash does not match + BadGenesisHash, + /// Signature is invalid + BadProof, /// Creator not exist CreatorNotExist, /// Creator not allowed to create identities @@ -727,6 +774,26 @@ pub mod pallet { total_weight } + pub(super) fn do_del_right( + idty_index: T::IdtyIndex, + idty_name: IdtyName, + owner_key: &T::AccountId, + right: T::IdtyRight, + subkey_opt: Option<T::AccountId>, + ) { + let old_key_opt = if let Some(ref subkey) = subkey_opt { + Some(subkey.clone()) + } else if right.allow_owner_key() { + Some(owner_key.clone()) + } else { + None + }; + + Self::deposit_event(Event::<T>::IdtyLostRight(idty_name, right)); + if old_key_opt.is_some() { + T::OnRightKeyChange::on_right_key_change(idty_index, right, old_key_opt, None); + } + } } } diff --git a/pallets/identity/src/mock.rs b/pallets/identity/src/mock.rs index 51e77fff1..d67bff40c 100644 --- a/pallets/identity/src/mock.rs +++ b/pallets/identity/src/mock.rs @@ -27,7 +27,7 @@ use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_core::H256; use sp_runtime::{ - testing::Header, + testing::{Header, TestSignature, UintAuthorityId}, traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; @@ -145,6 +145,8 @@ impl pallet_identity::Config for Test { type OnRightKeyChange = (); type MaxNoRightPeriod = MaxNoRightPeriod; type Membership = (); + type RevocationSigner = UintAuthorityId; + type RevocationSignature = TestSignature; } // Build genesis storage according to the mock runtime. diff --git a/pallets/identity/src/tests.rs b/pallets/identity/src/tests.rs index 264b324c2..68f0ccca4 100644 --- a/pallets/identity/src/tests.rs +++ b/pallets/identity/src/tests.rs @@ -19,7 +19,9 @@ use crate::mock::*; use crate::{Error, IdtyName, IdtyValue}; use frame_support::assert_err; use frame_support::assert_ok; +use frame_support::pallet_prelude::Encode; use frame_system::{EventRecord, Phase}; +use sp_runtime::testing::TestSignature; type IdtyVal = IdtyValue<u64, u64, (), Right>; @@ -247,3 +249,67 @@ fn test_two_identities() { assert_eq!(idty2.removable_on, 7); }); } + +#[test] +fn test_revoke_identity_ok() { + let identities = vec![crate::IdtyValue { + name: IdtyName(vec![0]), + owner_key: 1, + next_creatable_identity_on: 0, + removable_on: 0, + rights: vec![(Right::Right1, None), (Right::Right2, Some(10))], + status: crate::IdtyStatus::Validated, + data: (), + }]; + + new_test_ext(IdentityConfig { identities }).execute_with(|| { + // Should have one identity + assert_eq!(Identity::identities_count(), 1); + + // We need to initialize at least one block before any call + run_to_block(1); + + let revocation_payload = crate::RevocationPayload { + owner_key: 1, + idty: 1, + genesis_hash: System::block_hash(0), + }; + let revocation_signature = TestSignature(1, revocation_payload.encode()); + // Delete right Right1 for IdtyName(vec![1]) + // Should succes and trigger the correct event + assert_ok!(Identity::revoke_identity( + Origin::signed(2), + revocation_payload, + revocation_signature + )); + let events = System::events(); + assert_eq!(events.len(), 2); + assert_eq!( + events[0], + EventRecord { + phase: Phase::Initialization, + event: Event::Identity(crate::Event::IdtyLostRight( + IdtyName(vec![0]), + Right::Right1 + )), + topics: vec![], + } + ); + assert_eq!( + events[1], + EventRecord { + phase: Phase::Initialization, + event: Event::Identity(crate::Event::IdtyLostRight( + IdtyName(vec![0]), + Right::Right2 + )), + topics: vec![], + } + ); + + // The identity has no more rights, the inactivity period must start to run + let idty = Identity::identity(1).expect("idty not found"); + assert!(idty.rights.is_empty()); + assert_eq!(idty.removable_on, 5); + }); +} diff --git a/pallets/identity/src/types.rs b/pallets/identity/src/types.rs index b4231157b..cb3f7b986 100644 --- a/pallets/identity/src/types.rs +++ b/pallets/identity/src/types.rs @@ -113,3 +113,11 @@ where } } } + +#[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo, RuntimeDebug)] +pub struct RevocationPayload<AccountId, IdtyIndex: Decode + Encode + TypeInfo, Hash> { + pub owner_key: AccountId, + pub idty: IdtyIndex, + // Avoid replay attack between blockchains + pub genesis_hash: Hash, +} diff --git a/runtime/common/src/pallets_config.rs b/runtime/common/src/pallets_config.rs index 8fb5add39..22fa48996 100644 --- a/runtime/common/src/pallets_config.rs +++ b/runtime/common/src/pallets_config.rs @@ -201,6 +201,8 @@ macro_rules! pallets_config { type OnRightKeyChange = OnRightKeyChangeHandler<Runtime>; type MaxNoRightPeriod = MaxNoRightPeriod; type Membership = Membership; + type RevocationSigner = <Signature as sp_runtime::traits::Verify>::Signer; + type RevocationSignature = Signature; } impl pallet_membership::Config<frame_support::instances::Instance1> for Runtime { -- GitLab