diff --git a/pallets/identity/src/lib.rs b/pallets/identity/src/lib.rs index f76323b09451882266a99d11406ca50a9fd1da31..4a9b7d23c709b5a42bb9dffe2c5bb428681996c2 100644 --- a/pallets/identity/src/lib.rs +++ b/pallets/identity/src/lib.rs @@ -35,7 +35,7 @@ pub use types::*; use crate::traits::*; use codec::Codec; use frame_support::dispatch::Weight; -use sp_runtime::traits::{AtLeast32BitUnsigned, One, Saturating, Zero}; +use sp_runtime::traits::{AtLeast32BitUnsigned, IdentifyAccount, One, Saturating, Verify, Zero}; use sp_std::fmt::Debug; use sp_std::prelude::*; @@ -93,6 +93,10 @@ pub mod pallet { /// Handle the logic that remove all identity consumers. /// "identity consumers" mean all things that rely on the existence of the identity. type RemoveIdentityConsumers: RemoveIdentityConsumers<Self::IdtyIndex>; + /// Signing key of revocation payload + type RevocationSigner: IdentifyAccount<AccountId = Self::AccountId>; + /// Signature of revocation payload + type RevocationSignature: Parameter + Verify<Signer = Self::RevocationSigner>; } // GENESIS STUFFĂ‚ // @@ -362,6 +366,33 @@ pub mod pallet { Ok(().into()) } + #[pallet::weight(0)] + pub fn revoke_identity( + origin: OriginFor<T>, + payload: RevocationPayload<T::AccountId, 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()); + } + if let Some(idty_index) = <IdentityIndexOf<T>>::take(payload.owner_key) { + if let Ok(_idty_value) = <Identities<T>>::try_get(idty_index) { + T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Removed {}); + } else { + panic!("storage corrupted"); + } + Self::do_remove_identity(idty_index); + Ok(().into()) + } else { + Err(Error::<T>::IdtyNotFound.into()) + } + } + #[pallet::weight(0)] pub fn remove_identity( origin: OriginFor<T>, @@ -418,6 +449,10 @@ pub mod pallet { #[pallet::error] pub enum Error<T> { + /// Genesis hash does not match + BadGenesisHash, + /// Signature is invalid + BadProof, /// Creator not allowed to create identities CreatorNotAllowedToCreateIdty, /// Identity already confirmed diff --git a/pallets/identity/src/mock.rs b/pallets/identity/src/mock.rs index 7a19d32d3cc8849aa5b5795261c3c898f6fe12c5..566812369d9ba56a67a1022b4d5da184e3863ba0 100644 --- a/pallets/identity/src/mock.rs +++ b/pallets/identity/src/mock.rs @@ -110,6 +110,8 @@ impl pallet_identity::Config for Test { type OnIdtyChange = (); type MaxDisabledPeriod = MaxDisabledPeriod; type RemoveIdentityConsumers = (); + type RevocationSigner = (); + type RevocationSignature = (); } // Build genesis storage according to the mock runtime. diff --git a/pallets/identity/src/tests.rs b/pallets/identity/src/tests.rs index 885b1cd009f5d805d18585c3628467d6d528cbd4..1175e321b5bb2a3aba891ee812551be779d54e8c 100644 --- a/pallets/identity/src/tests.rs +++ b/pallets/identity/src/tests.rs @@ -15,7 +15,7 @@ // along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. use crate::mock::*; -use crate::{Error, GenesisIdty, IdtyName, IdtyValue}; +use crate::{Error, GenesisIdty, IdtyName, IdtyValue, RevocationPayload}; //use frame_support::assert_err; use frame_support::assert_ok; use frame_system::{EventRecord, Phase}; @@ -123,3 +123,34 @@ fn test_idty_creation_period() { ); }); } + +#[test] +fn test_idty_revocation() { + new_test_ext(IdentityConfig { + identities: vec![alice()], + }) + .execute_with(|| { + // We need to initialize at least one block before any call + run_to_block(1); + + // Alice should be able te create an identity + assert_ok!(Identity::revoke_identity( + Origin::signed(2), + RevocationPayload { + owner_key: 1, + genesis_hash: System::block_hash(0), + }, + () + )); + let events = System::events(); + assert_eq!(events.len(), 1); + assert_eq!( + events[0], + EventRecord { + phase: Phase::Initialization, + event: Event::Identity(crate::Event::IdtyRemoved { idty_index: 1 }), + topics: vec![], + } + ); + }); +} diff --git a/pallets/identity/src/types.rs b/pallets/identity/src/types.rs index d6991ce60acdfd98e60a775c278804cf9f2bfd0c..02931d2243441f804253657780a27a050a03cf37 100644 --- a/pallets/identity/src/types.rs +++ b/pallets/identity/src/types.rs @@ -85,3 +85,10 @@ pub struct IdtyValue<BlockNumber, AccountId> { pub removable_on: BlockNumber, pub status: IdtyStatus, } + +#[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo, RuntimeDebug)] +pub struct RevocationPayload<AccountId, Hash> { + pub owner_key: AccountId, + // 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 e1beb6b255d1dd2ef0402e6908d108b1df04e751..7b78f62dbec9dd55dbe7d375cd4b5fac793934a7 100644 --- a/runtime/common/src/pallets_config.rs +++ b/runtime/common/src/pallets_config.rs @@ -367,6 +367,8 @@ macro_rules! pallets_config { type OnIdtyChange = Wot; type MaxDisabledPeriod = MaxDisabledPeriod; type RemoveIdentityConsumers = RemoveIdentityConsumersImpl<Self>; + type RevocationSigner = <Signature as sp_runtime::traits::Verify>::Signer; + type RevocationSignature = Signature; } impl pallet_membership::Config<frame_support::instances::Instance1> for Runtime {