diff --git a/Cargo.lock b/Cargo.lock index 3011a81f2..fa4ce4b82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2289,6 +2289,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "hex-literal 0.3.4", "kilt-support", "log", "pallet-balances", diff --git a/pallets/did/Cargo.toml b/pallets/did/Cargo.toml index 0440f4a98..ef930349e 100644 --- a/pallets/did/Cargo.toml +++ b/pallets/did/Cargo.toml @@ -15,6 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dev-dependencies] env_logger.workspace = true +hex-literal.workspace = true ctype = {workspace = true, features = ["mock"]} kilt-support = {workspace = true, features = ["mock", "try-runtime"]} diff --git a/pallets/did/src/benchmarking.rs b/pallets/did/src/benchmarking.rs index 14f676ece..b97e8ee80 100644 --- a/pallets/did/src/benchmarking.rs +++ b/pallets/did/src/benchmarking.rs @@ -17,7 +17,7 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org use super::*; -use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, Zero}; +use frame_benchmarking::{account, benchmarks, Zero}; use frame_support::{ assert_ok, traits::fungible::{Inspect, Mutate, MutateHold}, @@ -130,6 +130,7 @@ benchmarks! { ::RuntimeOrigin: From>, ::AccountId: From, ::Currency: Mutate, + T::AccountId: AsRef<[u8; 32]> + From<[u8; 32]>, } /* create extrinsic */ @@ -342,7 +343,9 @@ benchmarks! { let did_public_auth_key = get_ed25519_public_authentication_key(); let did_subject: DidIdentifierOf = MultiSigner::from(did_public_auth_key).into_account().into(); - let did_details = generate_base_did_details::(DidVerificationKey::from(did_public_auth_key), None); + let mut did_details = generate_base_did_details::(DidVerificationKey::from(did_public_auth_key), None); + did_details.deposit.amount = did_details.calculate_deposit(c); + let service_endpoints = get_service_endpoints::( c, T::MaxServiceIdLength::get(), @@ -352,7 +355,10 @@ benchmarks! { T::MaxServiceUrlLength::get(), ); - Did::::insert(&did_subject, did_details); + let deposit_owner = did_details.deposit.owner.clone(); + make_free_for_did::(&deposit_owner); + Pallet::::try_insert_did(did_subject.clone(), did_details, deposit_owner).expect("DID should be created!"); + save_service_endpoints(&did_subject, &service_endpoints); let origin = RawOrigin::Signed(did_subject.clone()); }: _(origin, c) @@ -375,7 +381,8 @@ benchmarks! { let did_public_auth_key = get_ed25519_public_authentication_key(); let did_subject: DidIdentifierOf = MultiSigner::from(did_public_auth_key).into_account().into(); - let did_details = generate_base_did_details::(DidVerificationKey::from(did_public_auth_key), None); + let mut did_details = generate_base_did_details::(DidVerificationKey::from(did_public_auth_key), None); + did_details.deposit.amount = did_details.calculate_deposit(c); let service_endpoints = get_service_endpoints::( c, T::MaxServiceIdLength::get(), @@ -385,7 +392,10 @@ benchmarks! { T::MaxServiceUrlLength::get(), ); - Did::::insert(&did_subject, did_details.clone()); + let deposit_owner = did_details.deposit.owner.clone(); + make_free_for_did::(&deposit_owner); + Pallet::::try_insert_did(did_subject.clone(), did_details.clone(), deposit_owner).expect("DID should be created!"); + save_service_endpoints(&did_subject, &service_endpoints); let origin = RawOrigin::Signed(did_details.deposit.owner); let subject_clone = did_subject.clone(); @@ -1188,10 +1198,40 @@ benchmarks! { }, ) } -} -impl_benchmark_test_suite! { - Pallet, - crate::mock::ExtBuilder::default().build_with_keystore(), - crate::mock::Test + dispatch_as { + // ecdsa keys are the most expensive since they require an additional hashing step + let did_public_auth_key = get_ecdsa_public_authentication_key(); + let did_subject: DidIdentifierOf = MultiSigner::from(did_public_auth_key).into_account().into(); + let did_account: AccountIdOf = MultiSigner::from(did_public_auth_key).into_account().into(); + + let did_details = generate_base_did_details::(DidVerificationKey::from(did_public_auth_key), None); + + Did::::insert(&did_subject, did_details.clone()); + make_free_for_did::(&did_account); + CurrencyOf::::hold(&HoldReason::Deposit.into(), &did_account, did_details.deposit.amount).expect("should reserve currency"); + + let test_call = ::RuntimeCall::get_call_for_did_call_benchmark(); + let origin = RawOrigin::Signed(did_subject.clone()); + }: _(origin, did_subject, Box::new(test_call)) + + create_from_account { + // ecdsa keys are the most expensive since they require an additional hashing step + let did_public_auth_key = get_ecdsa_public_authentication_key(); + let did_subject: DidIdentifierOf = MultiSigner::from(did_public_auth_key).into_account().into(); + let did_account: AccountIdOf = MultiSigner::from(did_public_auth_key).into_account().into(); + + let authentication_key = DidVerificationKey::from(did_public_auth_key); + make_free_for_did::(&did_account); + let origin = RawOrigin::Signed(did_subject.clone()); + }: _(origin, authentication_key) + verify { + Did::::get(&did_subject).expect("DID entry should be created"); + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::ExtBuilder::default().build_with_keystore(), + crate::mock::Test + ) } diff --git a/pallets/did/src/default_weights.rs b/pallets/did/src/default_weights.rs index a53bd3dc4..dc27e93c1 100644 --- a/pallets/did/src/default_weights.rs +++ b/pallets/did/src/default_weights.rs @@ -1,45 +1,27 @@ -// KILT Blockchain – https://botlabs.org -// Copyright (C) 2019-2023 BOTLabs GmbH - -// The KILT Blockchain is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The KILT Blockchain is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -// If you feel like getting in touch with us, you can do so at info@botlabs.org //! Autogenerated weights for did //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-08-04 +//! DATE: 2023-09-07 //! STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `eyrie-7`, CPU: `Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz` +//! HOSTNAME: `rust-2`, CPU: `12th Gen Intel(R) Core(TM) i9-12900K` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/release/kilt-parachain +// target/debug/kilt-parachain // benchmark // pallet -// --template=.maintain/weight-template.hbs -// --header=HEADER-GPL -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 +// --chain=dev // --steps=50 // --repeat=20 -// --chain=dev // --pallet=did -// --extrinsic=* -// --output=./pallets/did/src/default_weights.rs +// --extrinsic=dispatch_as,create_from_account +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=pallets/did/src/default_weights.rs +// --template=.maintain/weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -86,6 +68,8 @@ pub trait WeightInfo { fn signature_verification_ecdsa(l: u32, ) -> Weight; fn change_deposit_owner() -> Weight; fn update_deposit() -> Weight; + fn dispatch_as() -> Weight; + fn create_from_account() -> Weight; } /// Weights for did using the Substrate node and recommended hardware. @@ -728,6 +712,36 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } + + /// Storage: Did Did (r:1 w:0) + /// Proof: Did Did (max_values: None, max_size: Some(2312), added: 4787, mode: MaxEncodedLen) + fn dispatch_as() -> Weight { + // Proof Size summary in bytes: + // Measured: `353` + // Estimated: `5777` + // Minimum execution time: 187_140 nanoseconds. + Weight::from_parts(190_030_000, 5777) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: Did DidBlacklist (r:1 w:0) + /// Proof: Did DidBlacklist (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Did Did (r:1 w:1) + /// Proof: Did Did (max_values: None, max_size: Some(2312), added: 4787, mode: MaxEncodedLen) + /// Storage: Did DidEndpointsCount (r:1 w:0) + /// Proof: Did DidEndpointsCount (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(949), added: 3424, mode: MaxEncodedLen) + fn create_from_account() -> Weight { + // Proof Size summary in bytes: + // Measured: `1097` + // Estimated: `6204` + // Minimum execution time: 970_542 nanoseconds. + Weight::from_parts(983_257_000, 6204) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } } // For backwards compatibility and tests @@ -1369,4 +1383,34 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } + + /// Storage: Did Did (r:1 w:0) + /// Proof: Did Did (max_values: None, max_size: Some(2312), added: 4787, mode: MaxEncodedLen) + fn dispatch_as() -> Weight { + // Proof Size summary in bytes: + // Measured: `353` + // Estimated: `5777` + // Minimum execution time: 187_140 nanoseconds. + Weight::from_parts(190_030_000, 5777) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: Did DidBlacklist (r:1 w:0) + /// Proof: Did DidBlacklist (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Did Did (r:1 w:1) + /// Proof: Did Did (max_values: None, max_size: Some(2312), added: 4787, mode: MaxEncodedLen) + /// Storage: Did DidEndpointsCount (r:1 w:0) + /// Proof: Did DidEndpointsCount (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(949), added: 3424, mode: MaxEncodedLen) + fn create_from_account() -> Weight { + // Proof Size summary in bytes: + // Measured: `1097` + // Estimated: `6204` + // Minimum execution time: 970_542 nanoseconds. + Weight::from_parts(983_257_000, 6204) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } } diff --git a/pallets/did/src/did_details.rs b/pallets/did/src/did_details.rs index 34a55c86f..304c6bc0f 100644 --- a/pallets/did/src/did_details.rs +++ b/pallets/did/src/did_details.rs @@ -16,41 +16,42 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -use core::cmp::Ordering; use frame_support::{ ensure, storage::{bounded_btree_map::BoundedBTreeMap, bounded_btree_set::BoundedBTreeSet}, traits::Get, RuntimeDebug, }; -use kilt_support::{traits::StorageDepositCollector, Deposit}; +use kilt_support::Deposit; use parity_scale_codec::{Decode, Encode, MaxEncodedLen, WrapperTypeEncode}; use scale_info::TypeInfo; use sp_core::{ecdsa, ed25519, sr25519}; use sp_runtime::{ - traits::{Verify, Zero}, - DispatchError, MultiSignature, SaturatedConversion, Saturating, + traits::{IdentifyAccount, Verify, Zero}, + MultiSignature, SaturatedConversion, Saturating, }; use sp_std::{convert::TryInto, vec::Vec}; use crate::{ errors::{self, DidError}, - utils, AccountIdOf, BalanceOf, BlockNumberOf, Config, DidAuthorizedCallOperationOf, DidCreationDetailsOf, - DidDepositCollector, DidEndpointsCount, DidIdentifierOf, KeyIdOf, Payload, + utils, AccountIdOf, BalanceOf, BlockNumberOf, Config, DidAuthorizedCallOperationOf, DidCreationDetailsOf, KeyIdOf, + Payload, }; -/// Types of verification keys a DID can control. +/// Public verification key that a DID can control. #[derive(Clone, Decode, RuntimeDebug, Encode, Eq, Ord, PartialEq, PartialOrd, TypeInfo, MaxEncodedLen)] -pub enum DidVerificationKey { +pub enum DidVerificationKey { /// An Ed25519 public key. Ed25519(ed25519::Public), /// A Sr25519 public key. Sr25519(sr25519::Public), /// An ECDSA public key. Ecdsa(ecdsa::Public), + /// Account Identifier + Account(AccountId), } -impl DidVerificationKey { +impl DidVerificationKey { /// Verify a DID signature using one of the DID keys. pub fn verify_signature(&self, payload: &Payload, signature: &DidSignature) -> Result<(), errors::SignatureError> { match (self, signature) { @@ -72,19 +73,38 @@ impl DidVerificationKey { } } -impl From for DidVerificationKey { +impl IdentifyAccount for DidVerificationKey +where + AccountId: From<[u8; 32]> + AsRef<[u8; 32]>, +{ + type AccountId = AccountId; + + fn into_account(self) -> Self::AccountId { + let bytes = match self { + DidVerificationKey::Ed25519(pub_key) => pub_key.0, + DidVerificationKey::Sr25519(pub_key) => pub_key.0, + // Hash the Ecdsa key the same way it's done in substrate (the ecdsa key is 33 bytes, one byte too long) + DidVerificationKey::Ecdsa(pub_key) => sp_io::hashing::blake2_256(pub_key.as_ref()), + DidVerificationKey::Account(acc_id) => *acc_id.as_ref(), + }; + + bytes.into() + } +} + +impl From for DidVerificationKey { fn from(key: ed25519::Public) -> Self { DidVerificationKey::Ed25519(key) } } -impl From for DidVerificationKey { +impl From for DidVerificationKey { fn from(key: sr25519::Public) -> Self { DidVerificationKey::Sr25519(key) } } -impl From for DidVerificationKey { +impl From for DidVerificationKey { fn from(key: ecdsa::Public) -> Self { DidVerificationKey::Ecdsa(key) } @@ -99,20 +119,20 @@ pub enum DidEncryptionKey { /// A general public key under the control of the DID. #[derive(Clone, Decode, RuntimeDebug, Encode, Eq, Ord, PartialEq, PartialOrd, TypeInfo, MaxEncodedLen)] -pub enum DidPublicKey { +pub enum DidPublicKey { /// A verification key, used to generate and verify signatures. - PublicVerificationKey(DidVerificationKey), + PublicVerificationKey(DidVerificationKey), /// An encryption key, used to encrypt and decrypt payloads. PublicEncryptionKey(DidEncryptionKey), } -impl From for DidPublicKey { - fn from(verification_key: DidVerificationKey) -> Self { +impl From> for DidPublicKey { + fn from(verification_key: DidVerificationKey) -> Self { Self::PublicVerificationKey(verification_key) } } -impl From for DidPublicKey { +impl From for DidPublicKey { fn from(encryption_key: DidEncryptionKey) -> Self { Self::PublicEncryptionKey(encryption_key) } @@ -171,7 +191,7 @@ impl From for DidSignature { } } -pub trait DidVerifiableIdentifier { +pub trait DidVerifiableIdentifier { /// Allows a verifiable identifier to verify a signature it produces and /// return the public key /// associated with the identifier. @@ -179,15 +199,15 @@ pub trait DidVerifiableIdentifier { &self, payload: &Payload, signature: &DidSignature, - ) -> Result; + ) -> Result, errors::SignatureError>; } -impl> DidVerifiableIdentifier for I { +impl, AccountId> DidVerifiableIdentifier for I { fn verify_and_recover_signature( &self, payload: &Payload, signature: &DidSignature, - ) -> Result { + ) -> Result, errors::SignatureError> { // So far, either the raw Ed25519/Sr25519 public key or the Blake2-256 hashed // ECDSA public key. let raw_public_key: &[u8; 32] = self.as_ref(); @@ -238,15 +258,15 @@ impl> DidVerifiableIdentifier for I { /// It is currently used to keep track of all the past and current /// attestation keys a DID might control. #[derive(Clone, RuntimeDebug, Decode, Encode, PartialEq, Ord, PartialOrd, Eq, TypeInfo, MaxEncodedLen)] -pub struct DidPublicKeyDetails { +pub struct DidPublicKeyDetails { /// A public key the DID controls. - pub key: DidPublicKey, + pub key: DidPublicKey, /// The block number in which the verification key was added to the DID. pub block_number: BlockNumber, } /// The details associated to a DID identity. -#[derive(Clone, Decode, Encode, PartialEq, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Decode, Encode, PartialEq, TypeInfo, MaxEncodedLen, Debug)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] @@ -288,9 +308,9 @@ impl DidDetails { /// /// The tx counter is automatically set to 0. pub fn new( - authentication_key: DidVerificationKey, + authentication_key: DidVerificationKey>, block_number: BlockNumberOf, - deposit: Deposit, BalanceOf>, + deposit_owner: AccountIdOf, ) -> Result { let mut public_keys = DidPublicKeyMapOf::::default(); let authentication_key_id = utils::calculate_key_id::(&authentication_key.clone().into()); @@ -304,7 +324,13 @@ impl DidDetails { ) .map_err(|_| errors::StorageError::MaxPublicKeysExceeded)?; - Ok(Self { + let deposit = Deposit { + owner: deposit_owner, + // set deposit for the moment to zero. We will update it, when all keys are set. + amount: Zero::zero(), + }; + + let mut new_did_details = Self { authentication_key: authentication_key_id, key_agreement_keys: DidKeyAgreementKeySetOf::::default(), attestation_key: None, @@ -312,13 +338,21 @@ impl DidDetails { public_keys, last_tx_counter: 0u64, deposit, - }) + }; + + let deposit_amount = new_did_details.calculate_deposit(0); + new_did_details.deposit.amount = deposit_amount; + + Ok(new_did_details) } - pub fn calculate_deposit(&self, did_subject: &DidIdentifierOf) -> BalanceOf { + /// Calculate the deposit that should be secured for the DID based on the number of keys and service endpoints. + /// + /// Since service endpoints are not stored inside the DidDetails, the number of endpoints need to be provided. + pub fn calculate_deposit(&self, endpoint_count: u32) -> BalanceOf { let mut deposit: BalanceOf = T::BaseDeposit::get(); - let endpoint_count: BalanceOf = DidEndpointsCount::::get(did_subject).into(); + let endpoint_count: BalanceOf = endpoint_count.into(); deposit = deposit.saturating_add(endpoint_count.saturating_mul(T::ServiceEndpointDeposit::get())); let key_agreement_count: BalanceOf = self.key_agreement_keys.len().saturated_into(); @@ -337,35 +371,11 @@ impl DidDetails { deposit } - pub fn update_deposit(&mut self, did_subject: &DidIdentifierOf) -> Result<(), DispatchError> { - let new_required_deposit = self.calculate_deposit(did_subject); - - match new_required_deposit.cmp(&self.deposit.amount) { - Ordering::Greater => { - let deposit_to_reserve = new_required_deposit.saturating_sub(self.deposit.amount); - DidDepositCollector::::create_deposit(self.deposit.clone().owner, deposit_to_reserve)?; - self.deposit.amount = self.deposit.amount.saturating_add(deposit_to_reserve); - } - Ordering::Less => { - let deposit_to_release = self.deposit.amount.saturating_sub(new_required_deposit); - - DidDepositCollector::::free_deposit(Deposit { - owner: self.deposit.owner.clone(), - amount: deposit_to_release, - })?; - self.deposit.amount = self.deposit.amount.saturating_sub(deposit_to_release); - } - _ => (), - }; - Ok(()) - } - - // Creates a new DID entry from some [DidCreationDetails] and a given - // authentication key. - pub fn from_creation_details( + /// Creates a new DID entry from some [DidCreationDetails] and a given + /// authentication key. + pub fn new_with_creation_details( details: DidCreationDetailsOf, - new_auth_key: DidVerificationKey, - did_subject: &DidIdentifierOf, + new_auth_key: DidVerificationKey>, ) -> Result { ensure!( details.new_key_agreement_keys.len() @@ -375,30 +385,22 @@ impl DidDetails { let current_block_number = frame_system::Pallet::::block_number(); - let deposit = Deposit { - owner: details.clone().submitter, - // set deposit for the moment to zero. We will update it, when all keys are set. - amount: Zero::zero(), - }; - // Creates a new DID with the given authentication key. - let mut new_did_details = DidDetails::new(new_auth_key, current_block_number, deposit)?; + let mut new_did_details = DidDetails::new(new_auth_key, current_block_number, details.clone().submitter)?; new_did_details.add_key_agreement_keys(details.clone().new_key_agreement_keys, current_block_number)?; - if let Some(attesation_key) = details.clone().new_attestation_key { - new_did_details.update_attestation_key(attesation_key, current_block_number)?; + if let Some(attestation_key) = details.clone().new_attestation_key { + new_did_details.update_attestation_key(attestation_key, current_block_number)?; } - if let Some(delegation_key) = details.clone().new_delegation_key { + if let Some(delegation_key) = details.new_delegation_key { new_did_details.update_delegation_key(delegation_key, current_block_number)?; } - let deposit_amount = new_did_details.calculate_deposit(did_subject); + let deposit_amount = new_did_details.calculate_deposit(0); new_did_details.deposit.amount = deposit_amount; - DidDepositCollector::::create_deposit(details.submitter, deposit_amount)?; - Ok(new_did_details) } @@ -409,7 +411,7 @@ impl DidDetails { /// set of public keys. pub fn update_authentication_key( &mut self, - new_authentication_key: DidVerificationKey, + new_authentication_key: DidVerificationKey>, block_number: BlockNumberOf, ) -> Result<(), errors::StorageError> { let old_authentication_key_id = self.authentication_key; @@ -487,7 +489,7 @@ impl DidDetails { /// set of public keys. pub fn update_attestation_key( &mut self, - new_attestation_key: DidVerificationKey, + new_attestation_key: DidVerificationKey>, block_number: BlockNumberOf, ) -> Result<(), errors::StorageError> { let new_attestation_key_id = utils::calculate_key_id::(&new_attestation_key.clone().into()); @@ -530,7 +532,7 @@ impl DidDetails { /// set of public keys. pub fn update_delegation_key( &mut self, - new_delegation_key: DidVerificationKey, + new_delegation_key: DidVerificationKey>, block_number: BlockNumberOf, ) -> Result<(), errors::StorageError> { let new_delegation_key_id = utils::calculate_key_id::(&new_delegation_key.clone().into()); @@ -566,8 +568,8 @@ impl DidDetails { Ok(()) } - // Remove a key from the map of public keys if none of the other keys, i.e., - // authentication, key agreement, attestation, or delegation, is referencing it. + /// Remove a key from the map of public keys if none of the other keys, i.e., + /// authentication, key agreement, attestation, or delegation, is referencing it. pub fn remove_key_if_unused(&mut self, key_id: KeyIdOf) { if self.authentication_key != key_id && self.attestation_key != Some(key_id) @@ -583,7 +585,7 @@ impl DidDetails { pub fn get_verification_key_for_key_type( &self, key_type: DidVerificationKeyRelationship, - ) -> Option<&DidVerificationKey> { + ) -> Option<&DidVerificationKey>> { let key_id = match key_type { DidVerificationKeyRelationship::AssertionMethod => self.attestation_key, DidVerificationKeyRelationship::Authentication => Some(self.authentication_key), @@ -612,8 +614,11 @@ pub(crate) type DidNewKeyAgreementKeySet = pub(crate) type DidKeyAgreementKeySetOf = BoundedBTreeSet, ::MaxTotalKeyAgreementKeys>; -pub(crate) type DidPublicKeyMapOf = - BoundedBTreeMap, DidPublicKeyDetails>, ::MaxPublicKeysPerDid>; +pub(crate) type DidPublicKeyMapOf = BoundedBTreeMap< + KeyIdOf, + DidPublicKeyDetails, AccountIdOf>, + ::MaxPublicKeysPerDid, +>; /// The details of a new DID to create. #[derive(Clone, RuntimeDebug, Decode, Encode, PartialEq, TypeInfo)] @@ -628,9 +633,9 @@ where /// The new key agreement keys. pub new_key_agreement_keys: DidNewKeyAgreementKeySet, /// \[OPTIONAL\] The new attestation key. - pub new_attestation_key: Option, + pub new_attestation_key: Option>, /// \[OPTIONAL\] The new delegation key. - pub new_delegation_key: Option, + pub new_delegation_key: Option>, /// The service endpoints details. pub new_service_details: Vec, } diff --git a/pallets/did/src/lib.rs b/pallets/did/src/lib.rs index 42ae39c2d..eedf0fd4c 100644 --- a/pallets/did/src/lib.rs +++ b/pallets/did/src/lib.rs @@ -79,6 +79,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::unused_unit)] +#![recursion_limit = "256"] #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; @@ -137,7 +138,6 @@ use frame_system::RawOrigin; #[frame_support::pallet] pub mod pallet { use super::*; - use crate::service_endpoints::utils as service_endpoints_utils; use did_details::DidCreationDetails; use frame_support::{ pallet_prelude::*, @@ -153,14 +153,14 @@ pub mod pallet { Deposit, }; use service_endpoints::DidEndpoint; - use sp_runtime::traits::BadOrigin; + use sp_runtime::traits::{BadOrigin, IdentifyAccount}; use crate::{ did_details::{ DeriveDidCallAuthorizationVerificationKeyRelationship, DidAuthorizedCallOperation, DidDetails, DidEncryptionKey, DidSignature, DidVerifiableIdentifier, DidVerificationKey, RelationshipDeriveError, }, - service_endpoints::ServiceEndpointId, + service_endpoints::{utils as service_endpoints_utils, ServiceEndpointId}, }; /// The current storage version. @@ -213,7 +213,10 @@ pub mod pallet { + DeriveDidCallAuthorizationVerificationKeyRelationship; /// Type for a DID subject identifier. - type DidIdentifier: Parameter + DidVerifiableIdentifier + MaxEncodedLen; + type DidIdentifier: Parameter + + DidVerifiableIdentifier> + + MaxEncodedLen + + From>; /// Origin type expected by the proxied dispatchable calls. #[cfg(not(feature = "runtime-benchmarks"))] @@ -506,7 +509,10 @@ pub mod pallet { } #[pallet::call] - impl Pallet { + impl Pallet + where + T::AccountId: AsRef<[u8; 32]> + From<[u8; 32]>, + { /// Store a new DID on chain, after verifying that the creation /// operation has been signed by the KILT account associated with the /// identifier of the DID being created and that a DID with the same @@ -565,21 +571,9 @@ pub mod pallet { signature: DidSignature, ) -> DispatchResult { let sender = ensure_signed(origin)?; - - ensure!(sender == details.submitter, BadOrigin); let did_identifier = details.did.clone(); - // Make sure that DIDs cannot be created again after they have been deleted. - ensure!( - !DidBlacklist::::contains_key(&did_identifier), - Error::::AlreadyDeleted - ); - - // There has to be no other DID with the same identifier already saved on chain, - // otherwise generate a AlreadyExists error. - ensure!(!Did::::contains_key(&did_identifier), Error::::AlreadyExists); - - log::debug!("Creating DID {:?}", &did_identifier); + ensure!(sender == details.submitter, BadOrigin); let account_did_auth_key = did_identifier .verify_and_recover_signature(&details.encode(), &signature) @@ -595,22 +589,14 @@ pub mod pallet { }); DidEndpointsCount::::insert(&did_identifier, input_service_endpoints.len().saturated_into::()); - let did_entry = DidDetails::from_creation_details(*details, account_did_auth_key, &did_identifier) - .map_err(Error::::from)?; - - Did::::insert(&did_identifier, did_entry.clone()); + let mut did_entry = + DidDetails::new_with_creation_details(*details, account_did_auth_key).map_err(Error::::from)?; + did_entry.deposit.amount = + did_entry.calculate_deposit(input_service_endpoints.len().saturated_into::()); - let imbalance: CreditOf = >>::withdraw( - &did_entry.deposit.owner, - T::Fee::get(), - Precision::Exact, - Preservation::Preserve, - Fortitude::Polite, - )?; - - T::FeeCollector::on_unbalanced(imbalance); + log::debug!("Creating DID {:?}", &did_identifier); - Self::deposit_event(Event::DidCreated(sender, did_identifier)); + Self::try_insert_did(did_identifier, did_entry, sender)?; Ok(()) } @@ -633,7 +619,10 @@ pub mod pallet { /// # #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::set_ed25519_authentication_key().max(::WeightInfo::set_sr25519_authentication_key()).max(::WeightInfo::set_ecdsa_authentication_key()))] - pub fn set_authentication_key(origin: OriginFor, new_key: DidVerificationKey) -> DispatchResult { + pub fn set_authentication_key( + origin: OriginFor, + new_key: DidVerificationKey>, + ) -> DispatchResult { let did_subject = T::EnsureOrigin::ensure_origin(origin)?.subject(); let mut did_details = Did::::get(&did_subject).ok_or(Error::::NotFound)?; @@ -647,8 +636,7 @@ pub mod pallet { .update_authentication_key(new_key, frame_system::Pallet::::block_number()) .map_err(Error::::from)?; - did_details.update_deposit(&did_subject)?; - Did::::insert(&did_subject, did_details); + Self::try_update_did(&did_subject, did_details)?; log::debug!("Authentication key set"); Self::deposit_event(Event::DidUpdated(did_subject)); @@ -673,7 +661,7 @@ pub mod pallet { /// # #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::set_ed25519_delegation_key().max(::WeightInfo::set_sr25519_delegation_key()).max(::WeightInfo::set_ecdsa_delegation_key()))] - pub fn set_delegation_key(origin: OriginFor, new_key: DidVerificationKey) -> DispatchResult { + pub fn set_delegation_key(origin: OriginFor, new_key: DidVerificationKey>) -> DispatchResult { let did_subject = T::EnsureOrigin::ensure_origin(origin)?.subject(); let mut did_details = Did::::get(&did_subject).ok_or(Error::::NotFound)?; @@ -682,8 +670,7 @@ pub mod pallet { .update_delegation_key(new_key, frame_system::Pallet::::block_number()) .map_err(Error::::from)?; - did_details.update_deposit(&did_subject)?; - Did::::insert(&did_subject, did_details); + Self::try_update_did(&did_subject, did_details)?; log::debug!("Delegation key set"); Self::deposit_event(Event::DidUpdated(did_subject)); @@ -714,8 +701,7 @@ pub mod pallet { log::debug!("Removing delegation key for DID {:?}", &did_subject); did_details.remove_delegation_key().map_err(Error::::from)?; - did_details.update_deposit(&did_subject)?; - Did::::insert(&did_subject, did_details); + Self::try_update_did(&did_subject, did_details)?; log::debug!("Delegation key removed"); Self::deposit_event(Event::DidUpdated(did_subject)); @@ -740,7 +726,10 @@ pub mod pallet { /// # #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::set_ed25519_attestation_key().max(::WeightInfo::set_sr25519_attestation_key()).max(::WeightInfo::set_ecdsa_attestation_key()))] - pub fn set_attestation_key(origin: OriginFor, new_key: DidVerificationKey) -> DispatchResult { + pub fn set_attestation_key( + origin: OriginFor, + new_key: DidVerificationKey>, + ) -> DispatchResult { let did_subject = T::EnsureOrigin::ensure_origin(origin)?.subject(); let mut did_details = Did::::get(&did_subject).ok_or(Error::::NotFound)?; @@ -749,8 +738,7 @@ pub mod pallet { .update_attestation_key(new_key, frame_system::Pallet::::block_number()) .map_err(Error::::from)?; - did_details.update_deposit(&did_subject)?; - Did::::insert(&did_subject, did_details); + Self::try_update_did(&did_subject, did_details)?; log::debug!("Attestation key set"); Self::deposit_event(Event::DidUpdated(did_subject)); @@ -781,8 +769,7 @@ pub mod pallet { log::debug!("Removing attestation key for DID {:?}", &did_subject); did_details.remove_attestation_key().map_err(Error::::from)?; - did_details.update_deposit(&did_subject)?; - Did::::insert(&did_subject, did_details); + Self::try_update_did(&did_subject, did_details)?; log::debug!("Attestation key removed"); Self::deposit_event(Event::DidUpdated(did_subject)); @@ -814,8 +801,7 @@ pub mod pallet { .add_key_agreement_key(new_key, frame_system::Pallet::::block_number()) .map_err(Error::::from)?; - did_details.update_deposit(&did_subject)?; - Did::::insert(&did_subject, did_details); + Self::try_update_did(&did_subject, did_details)?; log::debug!("Key agreement key set"); Self::deposit_event(Event::DidUpdated(did_subject)); @@ -844,8 +830,7 @@ pub mod pallet { log::debug!("Removing key agreement key for DID {:?}", &did_subject); did_details.remove_key_agreement_key(key_id).map_err(Error::::from)?; - did_details.update_deposit(&did_subject)?; - Did::::insert(&did_subject, did_details); + Self::try_update_did(&did_subject, did_details)?; log::debug!("Key agreement key removed"); Self::deposit_event(Event::DidUpdated(did_subject)); @@ -874,7 +859,7 @@ pub mod pallet { .map_err(Error::::from)?; // Verify that the DID is present. - let mut did = Did::::get(&did_subject).ok_or(Error::::NotFound)?; + let did_details = Did::::get(&did_subject).ok_or(Error::::NotFound)?; let currently_stored_endpoints_count = DidEndpointsCount::::get(&did_subject); @@ -894,9 +879,8 @@ pub mod pallet { }, )?; DidEndpointsCount::::insert(&did_subject, currently_stored_endpoints_count.saturating_add(1)); - did.update_deposit(&did_subject)?; - Did::::insert(&did_subject, did); + Self::try_update_did(&did_subject, did_details)?; Self::deposit_event(Event::DidUpdated(did_subject)); @@ -920,7 +904,7 @@ pub mod pallet { pub fn remove_service_endpoint(origin: OriginFor, service_id: ServiceEndpointId) -> DispatchResult { let did_subject = T::EnsureOrigin::ensure_origin(origin)?.subject(); - let mut did_details = Did::::get(&did_subject).ok_or(Error::::NotFound)?; + let did_details = Did::::get(&did_subject).ok_or(Error::::NotFound)?; ensure!( ServiceEndpoints::::take(&did_subject, &service_id).is_some(), @@ -937,8 +921,7 @@ pub mod pallet { } }); - did_details.update_deposit(&did_subject)?; - Did::::insert(&did_subject, did_details); + Self::try_update_did(&did_subject, did_details)?; Self::deposit_event(Event::DidUpdated(did_subject)); @@ -1134,24 +1117,208 @@ pub mod pallet { #[pallet::weight(::WeightInfo::update_deposit())] pub fn update_deposit(origin: OriginFor, did: DidIdentifierOf) -> DispatchResult { let sender = ensure_signed(origin)?; - let mut did_entry = Did::::get(&did).ok_or(Error::::NotFound)?; + let did_entry = Did::::get(&did).ok_or(Error::::NotFound)?; ensure!(did_entry.deposit.owner == sender, Error::::BadDidOrigin); - did_entry.update_deposit(&did)?; - Did::::insert(&did, did_entry); + + Self::try_update_did(&did, did_entry)?; + Ok(()) + } + + /// Proxy a dispatchable call of another runtime extrinsic that + /// supports a DID origin. + /// + /// The referenced DID identifier must be present on chain before the + /// operation is dispatched. + /// + /// A call submitted through this extrinsic must be signed with the + /// right DID key, depending on the call. In contrast to the + /// `submit_did_call` extrinsic, this call doesn't separate the sender + /// from the DID subject. The key that must be used for this DID call + /// is required to also be a valid account with enough balance to pay + /// for fees. + /// + /// The dispatch origin must be a KILT account with enough funds to + /// execute the extrinsic and must correspond to the required DID + /// Verification Key. + /// + /// Emits `DidCallDispatched`. + #[allow(clippy::boxed_local)] + #[pallet::call_index(15)] + #[pallet::weight({ + let dispatch_info = call.get_dispatch_info(); + + (::WeightInfo::dispatch_as().saturating_add(dispatch_info.weight), dispatch_info.class) + })] + pub fn dispatch_as( + origin: OriginFor, + did_identifier: DidIdentifierOf, + call: Box>, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + let verification_key_relationship = + call.derive_verification_key_relationship().map_err(Error::::from)?; + + Pallet::::verify_account_authorization(&did_identifier, &who, verification_key_relationship) + .map_err(Error::::from)?; + + log::debug!("Dispatch call from DID {:?}", did_identifier); + + #[cfg(not(feature = "runtime-benchmarks"))] + let result = call.dispatch( + DidRawOrigin { + id: did_identifier.clone(), + submitter: who, + } + .into(), + ); + #[cfg(feature = "runtime-benchmarks")] + let result = call.dispatch(RawOrigin::Signed(did_identifier.clone()).into()); + + let dispatch_event_payload = result.map(|_| ()).map_err(|e| e.error); + + Self::deposit_event(Event::DidCallDispatched(did_identifier, dispatch_event_payload)); + + result + } + + /// Store a new DID on chain. + /// + /// The DID identifier is derived from the account ID that submits this + /// call. The authentication key must correspond to the account ID that + /// submitted this call. For accounts that use the ed25519 and sr25519 + /// schema, the authentication key must be of the + /// `DidVerificationKey::Ed25519` or `DidVerificationKey::Sr25519` + /// variant and contains the public key. For Ecdsa accounts, the + /// `DidVerificationKey::Ecdsa` variant is calculated by hashing the + /// Ecdsa public key. + /// + /// If this call is dispatched by an account id that doesn't correspond + /// to a public private key pair, the `DidVerificationKey::Account` + /// variant shall be used (Multisig, Pure Proxy, Governance origins). + /// The resulting DID can NOT be used for signing data and is therefore + /// limited to onchain activities. + /// + /// There must be no DID information stored on chain under the same DID + /// identifier. This call will fail if there exists a DID with the same + /// identifier or if a DID with the same identifier existed and was + /// deleted. + /// + /// The origin for this account must be funded and provide the required + /// deposit and fee. + /// + /// Emits `DidCreated`. + #[pallet::call_index(16)] + #[pallet::weight(::WeightInfo::create_from_account())] + pub fn create_from_account( + origin: OriginFor, + authentication_key: DidVerificationKey>, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + let did_identifier: DidIdentifierOf = sender.clone().into(); + + log::debug!("Creating DID {:?}", &did_identifier); + + let current_block_number = frame_system::Pallet::::block_number(); + let did_entry = + DidDetails::new(authentication_key, current_block_number, sender.clone()).map_err(Error::::from)?; + + Self::try_insert_did(did_identifier, did_entry, sender)?; + Ok(()) } } - impl Pallet { + impl Pallet + where + T::AccountId: AsRef<[u8; 32]> + From<[u8; 32]>, + { + /// Try creating a DID. + /// + /// # Errors + /// + /// * When the DID was deleted, this function returns a `AlreadyDeleted` + /// error. + /// * When the DID already exists, this function returns a + /// `AlreadyExists` error. + /// * When the [sender] doesn't have enough free balance, this function + /// returns a `UnableToPayFees` error. + pub fn try_insert_did( + did_identifier: DidIdentifierOf, + did_entry: DidDetails, + sender: AccountIdOf, + ) -> DispatchResult { + // Make sure that DIDs cannot be created again after they have been deleted. + ensure!( + !DidBlacklist::::contains_key(&did_identifier), + Error::::AlreadyDeleted + ); + + // There has to be no other DID with the same identifier already saved on chain, + // otherwise generate a AlreadyExists error. + ensure!(!Did::::contains_key(&did_identifier), Error::::AlreadyExists); + + // Collect fee + let imbalance: CreditOf = >>::withdraw( + &did_entry.deposit.owner, + T::Fee::get(), + Precision::Exact, + Preservation::Preserve, + Fortitude::Polite, + ) + .map_err(|_| Error::::UnableToPayFees)?; + T::FeeCollector::on_unbalanced(imbalance); + + DidDepositCollector::::create_deposit(sender.clone(), did_entry.deposit.amount) + .map_err(|_| Error::::UnableToPayFees)?; + + Did::::insert(&did_identifier, did_entry); + + Pallet::::deposit_event(Event::DidCreated(sender, did_identifier)); + + Ok(()) + } + + /// Try updating the DID. + /// + /// # Errors + /// + /// This can fail when the deposit owner doesn't have enough free + /// balance. + pub fn try_update_did(did_identifier: &DidIdentifierOf, mut did_details: DidDetails) -> DispatchResult { + Self::try_update_deposit(&mut did_details, did_identifier)?; + Did::::insert(did_identifier, did_details); + + Ok(()) + } + + fn try_update_deposit(did_details: &mut DidDetails, did_subject: &DidIdentifierOf) -> DispatchResult { + let endpoint_count = DidEndpointsCount::::get(did_subject); + let new_required_deposit = did_details.calculate_deposit(endpoint_count); + + match new_required_deposit.cmp(&did_details.deposit.amount) { + core::cmp::Ordering::Greater => { + let deposit_to_reserve = new_required_deposit.saturating_sub(did_details.deposit.amount); + DidDepositCollector::::create_deposit(did_details.deposit.clone().owner, deposit_to_reserve)?; + did_details.deposit.amount = did_details.deposit.amount.saturating_add(deposit_to_reserve); + } + core::cmp::Ordering::Less => { + let deposit_to_release = did_details.deposit.amount.saturating_sub(new_required_deposit); + + DidDepositCollector::::free_deposit(Deposit { + owner: did_details.deposit.owner.clone(), + amount: deposit_to_release, + })?; + did_details.deposit.amount = did_details.deposit.amount.saturating_sub(deposit_to_release); + } + _ => (), + }; + Ok(()) + } + /// Verify the validity (i.e., nonce, signature and mortality) of a /// DID-authorized operation and, if valid, update the DID state with /// the latest nonce. - /// - /// # - /// Weight: O(1) - /// - Reads: Did - /// - Writes: Did - /// # pub fn verify_did_operation_signature_and_increase_nonce( operation: &DidAuthorizedCallOperationWithVerificationRelationship, signature: &DidSignature, @@ -1178,6 +1345,36 @@ pub mod pallet { Ok(()) } + /// Verify that [account] is authorized to dispatch DID calls on behave + /// of [did_identifier]. + /// + /// # Errors + /// + /// This function returns an error if the did was not found, the + /// verification key was not found or the account didn't match the + /// verification key. + pub fn verify_account_authorization( + did_identifier: &DidIdentifierOf, + submitter_account: &AccountIdOf, + verification_key_relationship: DidVerificationKeyRelationship, + ) -> Result<(), DidError> { + let did_details = Did::::get(did_identifier).ok_or(StorageError::NotFound(errors::NotFoundKind::Did))?; + + let verification_key = did_details + .get_verification_key_for_key_type(verification_key_relationship) + .ok_or_else(|| { + DidError::Storage(StorageError::NotFound(errors::NotFoundKind::Key( + verification_key_relationship.into(), + ))) + })?; + + if submitter_account == &verification_key.clone().into_account() { + Ok(()) + } else { + Err(DidError::Signature(SignatureError::InvalidData)) + } + } + /// Check if the provided block number is valid, /// i.e., if the current blockchain block is in the inclusive range /// [operation_block_number, operation_block_number + @@ -1236,7 +1433,7 @@ pub mod pallet { /// Deletes DID details from storage, including its linked service /// endpoints, adds the identifier to the blacklisted DIDs and frees the /// deposit. - fn delete_did(did_subject: DidIdentifierOf, endpoints_to_remove: u32) -> DispatchResult { + pub(crate) fn delete_did(did_subject: DidIdentifierOf, endpoints_to_remove: u32) -> DispatchResult { let current_endpoints_count = DidEndpointsCount::::get(&did_subject); ensure!( current_endpoints_count <= endpoints_to_remove, @@ -1293,7 +1490,10 @@ pub mod pallet { fn deposit_amount(key: &DidIdentifierOf) -> >>::Balance { let did_entry = Did::::get(key); match did_entry { - Some(entry) => entry.calculate_deposit(key), + Some(entry) => { + let endpoint_count = DidEndpointsCount::::get(key); + entry.calculate_deposit(endpoint_count) + } // If there is no entry return 0 _ => Zero::zero(), } diff --git a/pallets/did/src/mock.rs b/pallets/did/src/mock.rs index b144af7e0..9d92561c2 100644 --- a/pallets/did/src/mock.rs +++ b/pallets/did/src/mock.rs @@ -235,6 +235,7 @@ pub(crate) const DEFAULT_BALANCE: Balance = 10 * KILT; pub(crate) const ACCOUNT_00: AccountId = AccountId::new([1u8; 32]); pub(crate) const ACCOUNT_01: AccountId = AccountId::new([2u8; 32]); +pub(crate) const ACCOUNT_02: AccountId = AccountId::new([3u8; 32]); pub(crate) const ACCOUNT_FEE: AccountId = AccountId::new([u8::MAX; 32]); pub(crate) const AUTH_SEED_0: [u8; 32] = [4u8; 32]; @@ -317,7 +318,7 @@ pub fn get_ecdsa_delegation_key(seed: &[u8; 32]) -> ecdsa::Pair { ecdsa::Pair::from_seed(seed) } -pub fn generate_key_id(key: &DidPublicKey) -> KeyIdOf { +pub fn generate_key_id(key: &DidPublicKey) -> KeyIdOf { crate_utils::calculate_key_id::(key) } diff --git a/pallets/did/src/mock_utils.rs b/pallets/did/src/mock_utils.rs index 09a1a89a6..d78618441 100644 --- a/pallets/did/src/mock_utils.rs +++ b/pallets/did/src/mock_utils.rs @@ -17,8 +17,7 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org use frame_support::storage::bounded_btree_set::BoundedBTreeSet; -use kilt_support::Deposit; -use sp_runtime::{traits::Zero, AccountId32, SaturatedConversion}; +use sp_runtime::{AccountId32, SaturatedConversion}; use sp_std::{ collections::btree_set::BTreeSet, convert::{TryFrom, TryInto}, @@ -98,7 +97,7 @@ pub fn generate_base_did_creation_details( } pub fn generate_base_did_details( - authentication_key: DidVerificationKey, + authentication_key: DidVerificationKey>, deposit_owner: Option>, ) -> DidDetails where @@ -108,10 +107,7 @@ where DidDetails::new( authentication_key, BlockNumberOf::::default(), - Deposit { - owner: deposit_owner.unwrap_or(AccountId32::new([0u8; 32]).into()), - amount: Zero::zero(), - }, + deposit_owner.unwrap_or(AccountId32::new([0u8; 32]).into()), ) .expect("Failed to generate new DidDetails from auth_key due to BoundedBTreeSet bound") } diff --git a/pallets/did/src/signature.rs b/pallets/did/src/signature.rs index b365c63d2..2a02e4377 100644 --- a/pallets/did/src/signature.rs +++ b/pallets/did/src/signature.rs @@ -28,7 +28,10 @@ use crate::{ }; pub struct DidSignatureVerify(PhantomData); -impl VerifySignature for DidSignatureVerify { +impl VerifySignature for DidSignatureVerify +where + T::AccountId: AsRef<[u8; 32]> + From<[u8; 32]>, +{ type SignerId = ::DidIdentifier; type Payload = Vec; type Signature = DidSignature; diff --git a/pallets/did/src/tests/create_from_account.rs b/pallets/did/src/tests/create_from_account.rs new file mode 100644 index 000000000..3dc8f04e4 --- /dev/null +++ b/pallets/did/src/tests/create_from_account.rs @@ -0,0 +1,171 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use frame_support::{ + assert_noop, assert_ok, + traits::fungible::{Inspect, InspectHold}, +}; +use sp_core::{ecdsa, ed25519, sr25519}; + +use crate::{ + did_details::{DidPublicKey, DidVerificationKey}, + mock::*, + AccountIdOf, Config, Error, HoldReason, +}; + +/// Tests the creation of a DID. +/// This assumes that the `account` can be derived from the `verification_key` +/// and the creation is successful. +fn blueprint_test_successful(account_id: AccountIdOf, verification_key: DidVerificationKey>) { + let balance = ::BaseDeposit::get() + + ::Fee::get() + + <::Currency as Inspect>>::minimum_balance(); + + ExtBuilder::default() + .with_balances(vec![(account_id.clone(), balance)]) + .build_and_execute_with_sanity_tests(None, || { + assert!(Did::get_did(&account_id).is_none()); + + assert_ok!(Did::create_from_account( + RuntimeOrigin::signed(account_id.clone()), + verification_key.clone(), + )); + + let stored_did = Did::get_did(&account_id).expect("DID should be present on chain."); + assert_eq!(stored_did.key_agreement_keys.len(), 0); + assert_eq!(stored_did.delegation_key, None); + assert_eq!(stored_did.attestation_key, None); + assert_eq!(stored_did.public_keys.len(), 1); + assert!(stored_did + .public_keys + .contains_key(&generate_key_id(&verification_key.clone().into()))); + assert_eq!(stored_did.last_tx_counter, 0u64); + assert_eq!( + stored_did + .public_keys + .values() + .next() + .map(|details| details.key.clone()), + Some(DidPublicKey::PublicVerificationKey(verification_key)) + ); + assert_eq!( + Balances::balance_on_hold(&HoldReason::Deposit.into(), &account_id), + ::BaseDeposit::get() + ); + }); +} + +#[test] +fn successful_ed25519() { + let verification_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_test_successful(ACCOUNT_00, verification_key); +} + +#[test] +fn successful_sr25519() { + let verification_key = DidVerificationKey::Sr25519(sr25519::Public(*ACCOUNT_00.as_ref())); + blueprint_test_successful(ACCOUNT_00, verification_key); +} + +#[test] +fn successful_ecdsa() { + // these values where generated with `subkey generate -n kilt --scheme ecdsa` + let verification_key = DidVerificationKey::Ecdsa(ecdsa::Public(hex_literal::hex!( + "02484c08122e16f2cbce7697b5a9393280ca67dd8b91a907c1bc4b93451ebf4093" + ))); + let account_id: AccountIdOf = + hex_literal::hex!("375df6416958de6cb384516d3dead111c3a932c9e658ec1afd776e71bd2303b3").into(); + blueprint_test_successful(account_id, verification_key); +} + +#[test] +fn successful_account() { + let verification_key = DidVerificationKey::Account(ACCOUNT_00); + blueprint_test_successful(ACCOUNT_00, verification_key); +} + +#[test] +fn should_not_overwrite() { + let verification_key = DidVerificationKey::Sr25519(sr25519::Public(*ACCOUNT_00.as_ref())); + let account_id = ACCOUNT_00; + + let balance = ::BaseDeposit::get() + + ::Fee::get() + + <::Currency as Inspect>>::minimum_balance(); + + ExtBuilder::default() + // we take twice the amount of balance so that we can create two DIDs + .with_balances(vec![(account_id.clone(), balance * 2)]) + .build_and_execute_with_sanity_tests(None, || { + assert!(Did::get_did(&account_id).is_none()); + + assert_ok!(Did::create_from_account( + RuntimeOrigin::signed(account_id.clone()), + verification_key.clone(), + )); + + assert_noop!( + Did::create_from_account(RuntimeOrigin::signed(account_id.clone()), verification_key.clone(),), + Error::::AlreadyExists + ); + }); +} + +#[test] +fn should_not_recreate_deleted_did() { + let verification_key = DidVerificationKey::Sr25519(sr25519::Public(*ACCOUNT_00.as_ref())); + let account_id = ACCOUNT_00; + + let balance = ::BaseDeposit::get() + + ::Fee::get() + + <::Currency as Inspect>>::minimum_balance(); + + ExtBuilder::default() + .with_balances(vec![(account_id.clone(), balance)]) + .build_and_execute_with_sanity_tests(None, || { + assert!(Did::get_did(&account_id).is_none()); + + assert_ok!(Did::create_from_account( + RuntimeOrigin::signed(account_id.clone()), + verification_key.clone(), + )); + + let origin = build_test_origin(account_id.clone(), account_id.clone()); + assert_ok!(Did::delete(origin, 0)); + + assert_noop!( + Did::create_from_account(RuntimeOrigin::signed(account_id.clone()), verification_key.clone(),), + Error::::AlreadyDeleted + ); + }); +} + +#[test] +fn should_not_create_without_funds() { + let verification_key = DidVerificationKey::Sr25519(sr25519::Public(*ACCOUNT_00.as_ref())); + let account_id = ACCOUNT_00; + + ExtBuilder::default().build_and_execute_with_sanity_tests(None, || { + assert!(Did::get_did(&account_id).is_none()); + + assert_noop!( + Did::create_from_account(RuntimeOrigin::signed(account_id.clone()), verification_key.clone(),), + Error::::UnableToPayFees + ); + }); +} diff --git a/pallets/did/src/tests/dispatch_as.rs b/pallets/did/src/tests/dispatch_as.rs new file mode 100644 index 000000000..6300e454b --- /dev/null +++ b/pallets/did/src/tests/dispatch_as.rs @@ -0,0 +1,86 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use frame_support::{assert_noop, assert_ok}; + +use crate::{ + did_details::DidDetails, + mock::{Did, ExtBuilder, RuntimeCall, RuntimeOrigin, Test, DEFAULT_BALANCE}, + AccountIdOf, Did as DidStorage, DidIdentifierOf, Error, +}; + +mod attestation; +mod authentication; +mod delegation; +mod error_cases; + +fn blueprint_successful_dispatch (), FA: FnOnce() -> ()>( + did_identifier: DidIdentifierOf, + caller: AccountIdOf, + did_details: DidDetails, + call: RuntimeCall, + before: FB, + after: FA, +) { + ExtBuilder::default() + .with_balances(vec![(did_details.deposit.owner.clone(), DEFAULT_BALANCE)]) + .with_dids(vec![(did_identifier.clone(), did_details)]) + .build_and_execute_with_sanity_tests(None, || { + before(); + let did_details_before = DidStorage::::get(did_identifier.clone()).expect("Did must exists"); + + assert_ok!(Did::dispatch_as( + RuntimeOrigin::signed(caller), + did_identifier.clone(), + Box::new(call), + )); + + let did_details_after = DidStorage::::get(did_identifier.clone()).expect("Did must not be deleted"); + assert_eq!(did_details_before, did_details_after, "Did details must not be changed"); + + after(); + }); +} + +fn blueprint_failed_dispatch ()>( + did_identifier: DidIdentifierOf, + caller: AccountIdOf, + did_details: Option>, + call: RuntimeCall, + before: F, + error: Error, +) { + let (balances, dids) = if let Some(did_details) = did_details { + ( + vec![(did_details.deposit.owner.clone(), DEFAULT_BALANCE)], + vec![(did_identifier.clone(), did_details)], + ) + } else { + (Vec::new(), Vec::new()) + }; + ExtBuilder::default() + .with_balances(balances) + .with_dids(dids) + .build_and_execute_with_sanity_tests(None, || { + before(); + assert_noop!( + Did::dispatch_as(RuntimeOrigin::signed(caller), did_identifier.clone(), Box::new(call),), + error + ); + }); +} diff --git a/pallets/did/src/tests/dispatch_as/attestation.rs b/pallets/did/src/tests/dispatch_as/attestation.rs new file mode 100644 index 000000000..99229be7f --- /dev/null +++ b/pallets/did/src/tests/dispatch_as/attestation.rs @@ -0,0 +1,162 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use frame_support::assert_ok; +use sp_core::{ecdsa, ed25519, sr25519}; +use sp_runtime::traits::Hash; + +use crate::{ + did_details::DidVerificationKey, mock::*, mock_utils::*, tests::dispatch_as::blueprint_failed_dispatch, + AccountIdOf, DidIdentifierOf, Error, +}; + +use super::blueprint_successful_dispatch; + +fn blueprint_successful_dispatch_with_key( + did_identifier: DidIdentifierOf, + caller: AccountIdOf, + authentication_key: DidVerificationKey>, + attestation_key: DidVerificationKey>, + deposit_owner: AccountIdOf, +) { + let mut did_details = generate_base_did_details(authentication_key, Some(deposit_owner)); + assert_ok!(did_details.update_attestation_key(attestation_key, 0)); + + let ctype_hash = ::Hashing::hash(&get_attestation_key_test_input()[..]); + + blueprint_successful_dispatch( + did_identifier, + caller, + did_details, + get_attestation_key_call(), + || { + assert!( + ctype::Ctypes::::get(ctype_hash).is_none(), + "Ctype should not exists before the call" + ); + }, + || { + assert!( + ctype::Ctypes::::get(ctype_hash).is_some(), + "CType must exist after the call" + ); + }, + ); +} + +#[test] +fn successful_key_dispatch_ed25519() { + let deposit_owner = ACCOUNT_01; + let caller = ACCOUNT_00; + let did_identifier = ACCOUNT_02; + let verification_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_02.as_ref())); + let attestation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_successful_dispatch_with_key(did_identifier, caller, verification_key, attestation_key, deposit_owner); +} + +#[test] +fn successful_key_dispatch_sr25519() { + let deposit_owner = ACCOUNT_01; + let caller = ACCOUNT_00; + let did_identifier = ACCOUNT_02; + let attestation_key = DidVerificationKey::Sr25519(sr25519::Public(*ACCOUNT_00.as_ref())); + let verification_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_successful_dispatch_with_key(did_identifier, caller, verification_key, attestation_key, deposit_owner); +} + +#[test] +fn successful_key_dispatch_ecdsa() { + // these values where generated with `subkey generate -n kilt --scheme ecdsa` + let attestation_key = DidVerificationKey::Ecdsa(ecdsa::Public(hex_literal::hex!( + "02484c08122e16f2cbce7697b5a9393280ca67dd8b91a907c1bc4b93451ebf4093" + ))); + let caller: AccountIdOf = + hex_literal::hex!("375df6416958de6cb384516d3dead111c3a932c9e658ec1afd776e71bd2303b3").into(); + + let deposit_owner = ACCOUNT_01; + let did_identifier = ACCOUNT_02; + let verification_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_successful_dispatch_with_key(did_identifier, caller, verification_key, attestation_key, deposit_owner); +} + +#[test] +fn successful_key_dispatch_account() { + let deposit_owner = ACCOUNT_01; + let caller = ACCOUNT_00; + let did_identifier = ACCOUNT_02; + let attestation_key = DidVerificationKey::Account(ACCOUNT_00); + let verification_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_successful_dispatch_with_key(did_identifier, caller, verification_key, attestation_key, deposit_owner); +} + +fn blueprint_failed_dispatch_with_key( + caller: AccountIdOf, + authentication_key: DidVerificationKey>, + attestation_key: DidVerificationKey>, + delegation_key: DidVerificationKey>, +) { + let deposit_owner = ACCOUNT_02; + let did_identifier = ACCOUNT_02; + + let mut did_details = generate_base_did_details(authentication_key, Some(deposit_owner)); + assert_ok!(did_details.update_attestation_key(attestation_key, 0)); + assert_ok!(did_details.update_delegation_key(delegation_key, 0)); + + blueprint_failed_dispatch( + did_identifier, + caller, + Some(did_details), + get_attestation_key_call(), + || {}, + Error::::InvalidSignature, + ); +} + +#[test] +fn failed_no_match_ed25519() { + let attestation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_01.as_ref())); + let delegation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + let authentication_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_failed_dispatch_with_key(ACCOUNT_00, authentication_key, attestation_key, delegation_key); +} + +#[test] +fn failed_no_match_sr25519() { + let attestation_key = DidVerificationKey::Sr25519(sr25519::Public(*ACCOUNT_01.as_ref())); + let delegation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + let authentication_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_failed_dispatch_with_key(ACCOUNT_00, authentication_key, attestation_key, delegation_key); +} + +#[test] +fn failed_no_match_ecdsa() { + let attestation_key = DidVerificationKey::Ecdsa(ecdsa::Public(hex_literal::hex!( + "02484c08122e16f2cbce7697b5a9393280ca67dd8b91a907c1bc4b93451ebf4093" + ))); + let delegation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + let authentication_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_failed_dispatch_with_key(ACCOUNT_00, authentication_key, attestation_key, delegation_key); +} + +#[test] +fn failed_no_match_account() { + let attestation_key = DidVerificationKey::Account(ACCOUNT_01); + let delegation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + let authentication_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_failed_dispatch_with_key(ACCOUNT_00, authentication_key, attestation_key, delegation_key); +} diff --git a/pallets/did/src/tests/dispatch_as/authentication.rs b/pallets/did/src/tests/dispatch_as/authentication.rs new file mode 100644 index 000000000..ff6a13a69 --- /dev/null +++ b/pallets/did/src/tests/dispatch_as/authentication.rs @@ -0,0 +1,155 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use frame_support::assert_ok; +use sp_core::{ecdsa, ed25519, sr25519}; +use sp_runtime::traits::Hash; + +use crate::{ + did_details::DidVerificationKey, mock::*, mock_utils::*, tests::dispatch_as::blueprint_failed_dispatch, + AccountIdOf, DidIdentifierOf, Error, +}; + +use super::blueprint_successful_dispatch; + +fn blueprint_successful_dispatch_with_key( + did_identifier: DidIdentifierOf, + caller: AccountIdOf, + verification_key: DidVerificationKey>, + deposit_owner: AccountIdOf, +) { + let did_details = generate_base_did_details(verification_key, Some(deposit_owner)); + let ctype_hash = ::Hashing::hash(&get_authentication_key_test_input()[..]); + + blueprint_successful_dispatch( + did_identifier, + caller, + did_details, + get_authentication_key_call(), + || { + assert!( + ctype::Ctypes::::get(ctype_hash).is_none(), + "Ctype should not exists before the call" + ); + }, + || { + assert!( + ctype::Ctypes::::get(ctype_hash).is_some(), + "CType must exist after the call" + ); + }, + ); +} + +#[test] +fn successful_key_dispatch_ed25519() { + let deposit_owner = ACCOUNT_01; + let caller = ACCOUNT_00; + let did_identifier = ACCOUNT_02; + let verification_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_successful_dispatch_with_key(did_identifier, caller, verification_key, deposit_owner); +} + +#[test] +fn successful_key_dispatch_sr25519() { + let deposit_owner = ACCOUNT_01; + let caller = ACCOUNT_00; + let did_identifier = ACCOUNT_02; + let verification_key = DidVerificationKey::Sr25519(sr25519::Public(*ACCOUNT_00.as_ref())); + blueprint_successful_dispatch_with_key(did_identifier, caller, verification_key, deposit_owner) +} + +#[test] +fn successful_key_dispatch_ecdsa() { + // these values where generated with `subkey generate -n kilt --scheme ecdsa` + let verification_key = DidVerificationKey::Ecdsa(ecdsa::Public(hex_literal::hex!( + "02484c08122e16f2cbce7697b5a9393280ca67dd8b91a907c1bc4b93451ebf4093" + ))); + let caller: AccountIdOf = + hex_literal::hex!("375df6416958de6cb384516d3dead111c3a932c9e658ec1afd776e71bd2303b3").into(); + + let deposit_owner = ACCOUNT_01; + let did_identifier = ACCOUNT_02; + blueprint_successful_dispatch_with_key(did_identifier, caller, verification_key, deposit_owner) +} + +#[test] +fn successful_key_dispatch_account() { + let deposit_owner = ACCOUNT_01; + let caller = ACCOUNT_00; + let did_identifier = ACCOUNT_02; + let verification_key = DidVerificationKey::Account(ACCOUNT_00); + blueprint_successful_dispatch_with_key(did_identifier, caller, verification_key, deposit_owner) +} + +fn blueprint_failed_dispatch_with_key( + caller: AccountIdOf, + authentication_key: DidVerificationKey>, + attestation_key: DidVerificationKey>, + delegation_key: DidVerificationKey>, +) { + let deposit_owner = ACCOUNT_02; + let did_identifier = ACCOUNT_02; + + let mut did_details = generate_base_did_details(authentication_key, Some(deposit_owner)); + assert_ok!(did_details.update_attestation_key(attestation_key, 0)); + assert_ok!(did_details.update_delegation_key(delegation_key, 0)); + + blueprint_failed_dispatch( + did_identifier, + caller, + Some(did_details), + get_authentication_key_call(), + || {}, + Error::::InvalidSignature, + ); +} + +#[test] +fn failed_no_match_ed25519() { + let authentication_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_01.as_ref())); + let attestation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + let delegation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_failed_dispatch_with_key(ACCOUNT_00, authentication_key, attestation_key, delegation_key); +} + +#[test] +fn failed_no_match_sr25519() { + let authentication_key = DidVerificationKey::Sr25519(sr25519::Public(*ACCOUNT_01.as_ref())); + let attestation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + let delegation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_failed_dispatch_with_key(ACCOUNT_00, authentication_key, attestation_key, delegation_key); +} + +#[test] +fn failed_no_match_ecdsa() { + let authentication_key = DidVerificationKey::Ecdsa(ecdsa::Public(hex_literal::hex!( + "02484c08122e16f2cbce7697b5a9393280ca67dd8b91a907c1bc4b93451ebf4093" + ))); + let attestation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + let delegation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_failed_dispatch_with_key(ACCOUNT_00, authentication_key, attestation_key, delegation_key); +} + +#[test] +fn failed_no_match_account() { + let authentication_key = DidVerificationKey::Account(ACCOUNT_01); + let attestation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + let delegation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_failed_dispatch_with_key(ACCOUNT_00, authentication_key, attestation_key, delegation_key); +} diff --git a/pallets/did/src/tests/dispatch_as/delegation.rs b/pallets/did/src/tests/dispatch_as/delegation.rs new file mode 100644 index 000000000..4571c12d2 --- /dev/null +++ b/pallets/did/src/tests/dispatch_as/delegation.rs @@ -0,0 +1,162 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use frame_support::assert_ok; +use sp_core::{ecdsa, ed25519, sr25519}; +use sp_runtime::traits::Hash; + +use crate::{ + did_details::DidVerificationKey, mock::*, mock_utils::*, tests::dispatch_as::blueprint_failed_dispatch, + AccountIdOf, DidIdentifierOf, Error, +}; + +use super::blueprint_successful_dispatch; + +fn blueprint_successful_dispatch_with_key( + did_identifier: DidIdentifierOf, + caller: AccountIdOf, + authentication_key: DidVerificationKey>, + delegation_key: DidVerificationKey>, + deposit_owner: AccountIdOf, +) { + let mut did_details = generate_base_did_details(authentication_key, Some(deposit_owner)); + assert_ok!(did_details.update_delegation_key(delegation_key, 0)); + + let ctype_hash = ::Hashing::hash(&get_delegation_key_test_input()[..]); + + blueprint_successful_dispatch( + did_identifier, + caller, + did_details, + get_delegation_key_call(), + || { + assert!( + ctype::Ctypes::::get(ctype_hash).is_none(), + "Ctype should not exists before the call" + ); + }, + || { + assert!( + ctype::Ctypes::::get(ctype_hash).is_some(), + "CType must exist after the call" + ); + }, + ); +} + +#[test] +fn successful_dispatch_ed25519() { + let deposit_owner = ACCOUNT_01; + let caller = ACCOUNT_00; + let did_identifier = ACCOUNT_02; + let verification_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_02.as_ref())); + let delegation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_successful_dispatch_with_key(did_identifier, caller, verification_key, delegation_key, deposit_owner); +} + +#[test] +fn successful_dispatch_sr25519() { + let deposit_owner = ACCOUNT_01; + let caller = ACCOUNT_00; + let did_identifier = ACCOUNT_02; + let delegation_key = DidVerificationKey::Sr25519(sr25519::Public(*ACCOUNT_00.as_ref())); + let verification_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_successful_dispatch_with_key(did_identifier, caller, verification_key, delegation_key, deposit_owner); +} + +#[test] +fn successful_dispatch_ecdsa() { + // these values where generated with `subkey generate -n kilt --scheme ecdsa` + let delegation_key = DidVerificationKey::Ecdsa(ecdsa::Public(hex_literal::hex!( + "02484c08122e16f2cbce7697b5a9393280ca67dd8b91a907c1bc4b93451ebf4093" + ))); + let caller: AccountIdOf = + hex_literal::hex!("375df6416958de6cb384516d3dead111c3a932c9e658ec1afd776e71bd2303b3").into(); + + let deposit_owner = ACCOUNT_01; + let did_identifier = ACCOUNT_02; + let verification_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_successful_dispatch_with_key(did_identifier, caller, verification_key, delegation_key, deposit_owner); +} + +#[test] +fn successful_dispatch_account() { + let deposit_owner = ACCOUNT_01; + let caller = ACCOUNT_00; + let did_identifier = ACCOUNT_02; + let delegation_key = DidVerificationKey::Account(ACCOUNT_00); + let verification_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_successful_dispatch_with_key(did_identifier, caller, verification_key, delegation_key, deposit_owner); +} + +fn blueprint_failed_dispatch_with_key( + caller: AccountIdOf, + authentication_key: DidVerificationKey>, + attestation_key: DidVerificationKey>, + delegation_key: DidVerificationKey>, +) { + let deposit_owner = ACCOUNT_02; + let did_identifier = ACCOUNT_02; + + let mut did_details = generate_base_did_details(authentication_key, Some(deposit_owner)); + assert_ok!(did_details.update_attestation_key(attestation_key, 0)); + assert_ok!(did_details.update_delegation_key(delegation_key, 0)); + + blueprint_failed_dispatch( + did_identifier, + caller, + Some(did_details), + get_delegation_key_call(), + || {}, + Error::::InvalidSignature, + ); +} + +#[test] +fn failed_no_match_ed25519() { + let delegation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_01.as_ref())); + let attestation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + let authentication_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_failed_dispatch_with_key(ACCOUNT_00, authentication_key, attestation_key, delegation_key); +} + +#[test] +fn failed_no_match_sr25519() { + let delegation_key = DidVerificationKey::Sr25519(sr25519::Public(*ACCOUNT_01.as_ref())); + let attestation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + let authentication_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_failed_dispatch_with_key(ACCOUNT_00, authentication_key, attestation_key, delegation_key); +} + +#[test] +fn failed_no_match_ecdsa() { + let delegation_key = DidVerificationKey::Ecdsa(ecdsa::Public(hex_literal::hex!( + "02484c08122e16f2cbce7697b5a9393280ca67dd8b91a907c1bc4b93451ebf4093" + ))); + let attestation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + let authentication_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_failed_dispatch_with_key(ACCOUNT_00, authentication_key, attestation_key, delegation_key); +} + +#[test] +fn failed_no_match_account() { + let delegation_key = DidVerificationKey::Account(ACCOUNT_01); + let attestation_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + let authentication_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + blueprint_failed_dispatch_with_key(ACCOUNT_00, authentication_key, attestation_key, delegation_key); +} diff --git a/pallets/did/src/tests/dispatch_as/error_cases.rs b/pallets/did/src/tests/dispatch_as/error_cases.rs new file mode 100644 index 000000000..cb86907ee --- /dev/null +++ b/pallets/did/src/tests/dispatch_as/error_cases.rs @@ -0,0 +1,56 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use sp_core::ed25519; + +use crate::{ + did_details::DidVerificationKey, mock::*, mock_utils::generate_base_did_details, + tests::dispatch_as::blueprint_failed_dispatch, Error, Pallet, +}; + +#[test] +fn no_did() { + let did_identifier = ACCOUNT_02; + + blueprint_failed_dispatch( + did_identifier, + ACCOUNT_00, + None, + get_attestation_key_call(), + || {}, + Error::::NotFound, + ); +} + +#[test] +fn deleted_did() { + let did_identifier = ACCOUNT_02; + let authentication_key = DidVerificationKey::Ed25519(ed25519::Public(*ACCOUNT_00.as_ref())); + let did_details = generate_base_did_details(authentication_key, Some(ACCOUNT_01.clone())); + + blueprint_failed_dispatch( + did_identifier.clone(), + ACCOUNT_00, + Some(did_details), + get_attestation_key_call(), + || { + Pallet::::delete_did(did_identifier, 0).expect("DID should be removable"); + }, + Error::::NotFound, + ); +} diff --git a/pallets/did/src/tests/mod.rs b/pallets/did/src/tests/mod.rs index 450d558da..396961da2 100644 --- a/pallets/did/src/tests/mod.rs +++ b/pallets/did/src/tests/mod.rs @@ -17,7 +17,9 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org mod create; +mod create_from_account; mod delete; +mod dispatch_as; mod manage_keys; mod service_endpoint; mod submit; diff --git a/pallets/did/src/tests/submit.rs b/pallets/did/src/tests/submit.rs index afb5e1132..9af4f9b05 100644 --- a/pallets/did/src/tests/submit.rs +++ b/pallets/did/src/tests/submit.rs @@ -28,6 +28,33 @@ use crate::{ mock_utils::*, }; +#[test] +fn check_call_authentication_key_successful() { + let auth_key = get_sr25519_authentication_key(&AUTH_SEED_0); + let did = get_did_identifier_from_sr25519_key(auth_key.public()); + let caller = ACCOUNT_00; + + let mock_did = generate_base_did_details::(DidVerificationKey::from(auth_key.public()), Some(did.clone())); + + let call_operation = generate_test_did_call( + DidVerificationKeyRelationship::Authentication, + did.clone(), + caller.clone(), + ); + let signature = auth_key.sign(call_operation.encode().as_ref()); + + ExtBuilder::default() + .with_balances(vec![(did.clone(), DEFAULT_BALANCE)]) + .with_dids(vec![(did, mock_did)]) + .build_and_execute_with_sanity_tests(None, || { + assert_ok!(Did::submit_did_call( + RuntimeOrigin::signed(caller), + Box::new(call_operation.operation), + did::DidSignature::from(signature) + )); + }); +} + #[test] fn check_did_not_found_call_error() { let auth_key = get_sr25519_authentication_key(&AUTH_SEED_0); @@ -503,33 +530,6 @@ fn check_call_delegation_key_error() { }); } -#[test] -fn check_call_authentication_key_successful() { - let auth_key = get_sr25519_authentication_key(&AUTH_SEED_0); - let did = get_did_identifier_from_sr25519_key(auth_key.public()); - let caller = ACCOUNT_00; - - let mock_did = generate_base_did_details::(DidVerificationKey::from(auth_key.public()), Some(did.clone())); - - let call_operation = generate_test_did_call( - DidVerificationKeyRelationship::Authentication, - did.clone(), - caller.clone(), - ); - let signature = auth_key.sign(call_operation.encode().as_ref()); - - ExtBuilder::default() - .with_balances(vec![(did.clone(), DEFAULT_BALANCE)]) - .with_dids(vec![(did, mock_did)]) - .build_and_execute_with_sanity_tests(None, || { - assert_ok!(Did::submit_did_call( - RuntimeOrigin::signed(caller), - Box::new(call_operation.operation), - did::DidSignature::from(signature) - )); - }); -} - #[test] fn check_call_authentication_key_error() { let auth_key = get_sr25519_authentication_key(&AUTH_SEED_0); diff --git a/pallets/did/src/utils.rs b/pallets/did/src/utils.rs index 837bb6c26..b10ea4b07 100644 --- a/pallets/did/src/utils.rs +++ b/pallets/did/src/utils.rs @@ -22,12 +22,12 @@ use scale_info::prelude::format; use sp_runtime::traits::Hash; use sp_std::vec::Vec; -use crate::{did_details::DidPublicKey, Config, KeyIdOf}; +use crate::{did_details::DidPublicKey, AccountIdOf, Config, KeyIdOf}; // URI base used to test validity of provided service IDs (URI fragments). const TEST_URI_BASE: &str = "did:kilt:test-did"; -pub fn calculate_key_id(key: &DidPublicKey) -> KeyIdOf { +pub fn calculate_key_id(key: &DidPublicKey>) -> KeyIdOf { let hashed_values: Vec = key.encode(); T::Hashing::hash(&hashed_values) } diff --git a/runtime-api/did/src/did_details.rs b/runtime-api/did/src/did_details.rs index 82f6e2fd6..c4e672217 100644 --- a/runtime-api/did/src/did_details.rs +++ b/runtime-api/did/src/did_details.rs @@ -29,7 +29,7 @@ pub struct DidDetails pub key_agreement_keys: BTreeSet, pub delegation_key: Option, pub attestation_key: Option, - pub public_keys: BTreeMap>, + pub public_keys: BTreeMap>, pub last_tx_counter: u64, pub deposit: Deposit, } diff --git a/runtimes/peregrine/src/tests.rs b/runtimes/peregrine/src/tests.rs index 9f994c5db..5eab813d9 100644 --- a/runtimes/peregrine/src/tests.rs +++ b/runtimes/peregrine/src/tests.rs @@ -68,7 +68,7 @@ fn did_storage_sizes() { assert_eq!(max_did_endpoint_size, MAX_SERVICE_ENDPOINT_BYTE_LENGTH as usize); // DID key - let max_did_key_size = did::did_details::DidPublicKey::max_encoded_len(); + let max_did_key_size = did::did_details::DidPublicKey::::max_encoded_len(); assert_eq!(max_did_key_size, MAX_KEY_LENGTH as usize); } diff --git a/runtimes/peregrine/src/weights/did.rs b/runtimes/peregrine/src/weights/did.rs index a26e7895f..ace3fb317 100644 --- a/runtimes/peregrine/src/weights/did.rs +++ b/runtimes/peregrine/src/weights/did.rs @@ -19,26 +19,25 @@ //! Autogenerated weights for `did` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-08-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-09-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `eyrie-7`, CPU: `Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz` +//! HOSTNAME: `rust-2`, CPU: `12th Gen Intel(R) Core(TM) i9-12900K` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/release/kilt-parachain +// target/debug/kilt-parachain // benchmark // pallet -// --template=.maintain/runtime-weight-template.hbs -// --header=HEADER-GPL -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 +// --chain=dev // --steps=50 // --repeat=20 -// --chain=dev // --pallet=did -// --extrinsic=* +// --extrinsic=dispatch_as,create_from_account +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 // --output=./runtimes/peregrine/src/weights/did.rs +// --template=.maintain/runtime-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -723,6 +722,38 @@ impl did::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } + + /// Storage: Did Did (r:1 w:0) + /// Proof: Did Did (max_values: None, max_size: Some(2312), added: 4787, mode: MaxEncodedLen) + fn dispatch_as() -> Weight { + // Proof Size summary in bytes: + // Measured: `353` + // Estimated: `5777` + // Minimum execution time: 186_836_000 picoseconds. + Weight::from_parts(189_377_000, 0) + .saturating_add(Weight::from_parts(0, 5777)) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: Did DidBlacklist (r:1 w:0) + /// Proof: Did DidBlacklist (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Did Did (r:1 w:1) + /// Proof: Did Did (max_values: None, max_size: Some(2312), added: 4787, mode: MaxEncodedLen) + /// Storage: Did DidEndpointsCount (r:1 w:0) + /// Proof: Did DidEndpointsCount (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(949), added: 3424, mode: MaxEncodedLen) + fn create_from_account() -> Weight { + // Proof Size summary in bytes: + // Measured: `999` + // Estimated: `6204` + // Minimum execution time: 1_008_739_000 picoseconds. + Weight::from_parts(1_020_810_000, 0) + .saturating_add(Weight::from_parts(0, 6204)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } } #[cfg(test)] @@ -1159,4 +1190,29 @@ mod tests { > 5777 ); } + + #[test] + fn test_dispatch_as() { + assert!( + ::BlockWeights::get() + .per_class + .get(frame_support::dispatch::DispatchClass::Normal) + .max_extrinsic + .unwrap_or_else(::max_value) + .proof_size() + > 5777 + ); + } + #[test] + fn test_create_from_account() { + assert!( + ::BlockWeights::get() + .per_class + .get(frame_support::dispatch::DispatchClass::Normal) + .max_extrinsic + .unwrap_or_else(::max_value) + .proof_size() + > 6204 + ); + } } diff --git a/runtimes/spiritnet/src/tests.rs b/runtimes/spiritnet/src/tests.rs index 4a8adfcf9..2341c1b88 100644 --- a/runtimes/spiritnet/src/tests.rs +++ b/runtimes/spiritnet/src/tests.rs @@ -68,7 +68,7 @@ fn did_storage_sizes() { assert_eq!(max_did_endpoint_size, MAX_SERVICE_ENDPOINT_BYTE_LENGTH as usize); // DID key - let max_did_key_size = did::did_details::DidPublicKey::max_encoded_len(); + let max_did_key_size = did::did_details::DidPublicKey::::max_encoded_len(); assert_eq!(max_did_key_size, MAX_KEY_LENGTH as usize); } diff --git a/runtimes/spiritnet/src/weights/did.rs b/runtimes/spiritnet/src/weights/did.rs index b992230ae..a23b6ccce 100644 --- a/runtimes/spiritnet/src/weights/did.rs +++ b/runtimes/spiritnet/src/weights/did.rs @@ -723,6 +723,38 @@ impl did::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } + + /// Storage: Did Did (r:1 w:0) + /// Proof: Did Did (max_values: None, max_size: Some(2312), added: 4787, mode: MaxEncodedLen) + fn dispatch_as() -> Weight { + // Proof Size summary in bytes: + // Measured: `353` + // Estimated: `5777` + // Minimum execution time: 186_836_000 picoseconds. + Weight::from_parts(189_377_000, 0) + .saturating_add(Weight::from_parts(0, 5777)) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: Did DidBlacklist (r:1 w:0) + /// Proof: Did DidBlacklist (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Did Did (r:1 w:1) + /// Proof: Did Did (max_values: None, max_size: Some(2312), added: 4787, mode: MaxEncodedLen) + /// Storage: Did DidEndpointsCount (r:1 w:0) + /// Proof: Did DidEndpointsCount (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(949), added: 3424, mode: MaxEncodedLen) + fn create_from_account() -> Weight { + // Proof Size summary in bytes: + // Measured: `999` + // Estimated: `6204` + // Minimum execution time: 1_008_739_000 picoseconds. + Weight::from_parts(1_020_810_000, 0) + .saturating_add(Weight::from_parts(0, 6204)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } } #[cfg(test)]