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