diff --git a/Cargo.lock b/Cargo.lock index 9d53b7100..d2d0721ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5768,6 +5768,8 @@ dependencies = [ name = "pallet-did-lookup" version = "1.10.0-dev" dependencies = [ + "base58", + "blake2", "env_logger 0.10.0", "frame-benchmarking", "frame-support", diff --git a/pallets/pallet-did-lookup/Cargo.toml b/pallets/pallet-did-lookup/Cargo.toml index f3a603298..1bbd4637d 100644 --- a/pallets/pallet-did-lookup/Cargo.toml +++ b/pallets/pallet-did-lookup/Cargo.toml @@ -25,6 +25,8 @@ test-log = "0.2.11" [dependencies] # External dependencies +base58.workspace = true +blake2 = {version = "0.10.6", default-features = false} codec = {package = "parity-scale-codec", workspace = true, features = ["derive"]} hex.workspace = true libsecp256k1 = {workspace = true, features = ["hmac"]} @@ -59,6 +61,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", ] std = [ + "blake2/std", "codec/std", "frame-benchmarking?/std", "frame-support/std", @@ -75,8 +78,4 @@ std = [ "sp-runtime/std", "sp-std/std", ] -try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "kilt-support/try-runtime", -] +try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime", "kilt-support/try-runtime"] diff --git a/pallets/pallet-did-lookup/src/associate_account_request.rs b/pallets/pallet-did-lookup/src/associate_account_request.rs index 023f9570d..7ab93b42b 100644 --- a/pallets/pallet-did-lookup/src/associate_account_request.rs +++ b/pallets/pallet-did-lookup/src/associate_account_request.rs @@ -22,25 +22,31 @@ use crate::{ signature::get_wrapped_payload, }; +use base58::ToBase58; +use blake2::{Blake2b512, Digest}; use codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; +use scale_info::{ + prelude::{format, string::String}, + TypeInfo, +}; use sp_runtime::{traits::Verify, AccountId32, MultiSignature}; +use sp_std::{fmt::Debug, vec, vec::Vec}; #[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)] pub enum AssociateAccountRequest { - Dotsama(AccountId32, MultiSignature), + Polkadot(AccountId32, MultiSignature), Ethereum(AccountId20, EthereumSignature), } impl AssociateAccountRequest { - pub fn verify( + pub fn verify, BlockNumber: Debug>( &self, - did_identifier: ::DidIdentifier, - expiration: ::BlockNumber, + did_identifier: &DidIdentifier, + expiration: BlockNumber, ) -> bool { - let encoded_payload = (&did_identifier, expiration).encode(); + let encoded_payload = get_challenge(did_identifier, expiration).into_bytes(); match self { - AssociateAccountRequest::Dotsama(acc, proof) => proof.verify( + AssociateAccountRequest::Polkadot(acc, proof) => proof.verify( &get_wrapped_payload(&encoded_payload[..], crate::signature::WrapType::Substrate)[..], acc, ), @@ -53,8 +59,68 @@ impl AssociateAccountRequest { pub fn get_linkable_account(&self) -> LinkableAccountId { match self { - AssociateAccountRequest::Dotsama(acc, _) => LinkableAccountId::AccountId32(acc.clone()), + AssociateAccountRequest::Polkadot(acc, _) => LinkableAccountId::AccountId32(acc.clone()), AssociateAccountRequest::Ethereum(acc, _) => LinkableAccountId::AccountId20(*acc), } } } + +/// Build the challenge that must be signed to prove the consent for an +/// account to be linked to a DID. +pub fn get_challenge, BlockNumber: Debug>( + did_identifier: &DidIdentifier, + expiration: BlockNumber, +) -> String { + format!( + "Publicly link the signing address to did:kilt:{} before block number {:?}", + to_ss58(did_identifier.as_ref(), 38), + expiration + ) +} + +// Copied from /~https://github.com/paritytech/substrate/blob/ad5399644aebc54e32a107ac37ae08e6cd1f0cfb/primitives/core/src/crypto.rs#L324 +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 +fn to_ss58(public_key: &[u8], prefix: u16) -> String { + // We mask out the upper two bits of the ident - SS58 Prefix currently only + // supports 14-bits + let ident: u16 = prefix & 0b0011_1111_1111_1111; + let mut v = match ident { + 0..=63 => vec![ident as u8], + 64..=16_383 => { + // upper six bits of the lower byte(!) + let first = ((ident & 0b0000_0000_1111_1100) as u8) >> 2; + // lower two bits of the lower byte in the high pos, + // lower bits of the upper byte in the low pos + let second = ((ident >> 8) as u8) | ((ident & 0b0000_0000_0000_0011) as u8) << 6; + vec![first | 0b01000000, second] + } + _ => unreachable!("masked out the upper two bits; qed"), + }; + v.extend(public_key); + let r = ss58hash(&v); + v.extend(&r[0..2]); + v.to_base58() +} + +const PREFIX: &[u8] = b"SS58PRE"; + +fn ss58hash(data: &[u8]) -> Vec { + let mut ctx = Blake2b512::new(); + ctx.update(PREFIX); + ctx.update(data); + ctx.finalize().to_vec() +} + +#[cfg(test)] +mod tests { + use super::get_challenge; + + #[test] + fn test_get_challenge() { + assert_eq!( + get_challenge(&[1u8; 32], 5), + "Publicly link the signing address to did:kilt:4nwPAmtsK5toZfBM9WvmAe4Fa3LyZ3X3JHt7EUFfrcPPAZAm before block number 5" + ); + } +} diff --git a/pallets/pallet-did-lookup/src/benchmarking.rs b/pallets/pallet-did-lookup/src/benchmarking.rs index 3e015e1cb..5a5cf9617 100644 --- a/pallets/pallet-did-lookup/src/benchmarking.rs +++ b/pallets/pallet-did-lookup/src/benchmarking.rs @@ -19,7 +19,6 @@ //! Benchmarking -use codec::Encode; use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; use frame_support::{ crypto::ecdsa::ECDSAExt, @@ -38,7 +37,7 @@ use kilt_support::{deposit::Deposit, traits::GenerateBenchmarkOrigin}; use crate::{ account::AccountId20, - associate_account_request::AssociateAccountRequest, + associate_account_request::{get_challenge, AssociateAccountRequest}, linkable_account::LinkableAccountId, migrations::{add_legacy_association, get_mixed_storage_iterator, MixedStorageKey}, signature::get_wrapped_payload, @@ -71,13 +70,13 @@ benchmarks! { let connected_acc = sr25519_generate(KeyTypeId(*b"aura"), None); let connected_acc_id: T::AccountId = connected_acc.into(); let linkable_id: LinkableAccountId = connected_acc_id.clone().into(); - let bn: ::BlockNumber = 500_u32.into(); + let expire_at: ::BlockNumber = 500_u32.into(); let sig = sp_io::crypto::sr25519_sign( KeyTypeId(*b"aura"), &connected_acc, &get_wrapped_payload( - &Encode::encode(&(&did, bn))[..], + get_challenge(&did, expire_at).as_bytes(), crate::signature::WrapType::Substrate, )) .ok_or("Error while building signature.")?; @@ -89,8 +88,8 @@ benchmarks! { assert!(ConnectedAccounts::::get(&previous_did, linkable_id.clone()).is_some()); let origin = T::EnsureOrigin::generate_origin(caller, did.clone()); let id_arg = linkable_id.clone(); - let req = AssociateAccountRequest::Dotsama(connected_acc_id.into(), sig.into()); - }: associate_account(origin, req, bn) + let req = AssociateAccountRequest::Polkadot(connected_acc_id.into(), sig.into()); + }: associate_account(origin, req, expire_at) verify { assert!(ConnectedDids::::get(linkable_id.clone()).is_some()); assert!(ConnectedAccounts::::get(&previous_did, linkable_id.clone()).is_none()); @@ -104,13 +103,13 @@ benchmarks! { let connected_acc = ed25519_generate(KeyTypeId(*b"aura"), None); let connected_acc_id: T::AccountId = connected_acc.into(); let linkable_id: LinkableAccountId = connected_acc_id.clone().into(); - let bn: ::BlockNumber = 500_u32.into(); + let expire_at: ::BlockNumber = 500_u32.into(); let sig = sp_io::crypto::ed25519_sign( KeyTypeId(*b"aura"), &connected_acc, &get_wrapped_payload( - &Encode::encode(&(&did, bn))[..], + get_challenge(&did, expire_at).as_bytes(), crate::signature::WrapType::Substrate, )) .ok_or("Error while building signature.")?; @@ -122,8 +121,8 @@ benchmarks! { assert!(ConnectedAccounts::::get(&previous_did, linkable_id.clone()).is_some()); let origin = T::EnsureOrigin::generate_origin(caller, did.clone()); let id_arg = linkable_id.clone(); - let req = AssociateAccountRequest::Dotsama(connected_acc_id.into(), sig.into()); - }: associate_account(origin, req, bn) + let req = AssociateAccountRequest::Polkadot(connected_acc_id.into(), sig.into()); + }: associate_account(origin, req, expire_at) verify { assert!(ConnectedDids::::get(linkable_id.clone()).is_some()); assert!(ConnectedAccounts::::get(&previous_did, linkable_id.clone()).is_none()); @@ -137,13 +136,13 @@ benchmarks! { let connected_acc = ecdsa_generate(KeyTypeId(*b"aura"), None); let connected_acc_id = sp_runtime::MultiSigner::from(connected_acc).into_account(); let linkable_id: LinkableAccountId = connected_acc_id.clone().into(); - let bn: ::BlockNumber = 500_u32.into(); + let expire_at: ::BlockNumber = 500_u32.into(); let sig = sp_io::crypto::ecdsa_sign( KeyTypeId(*b"aura"), &connected_acc, &get_wrapped_payload( - &Encode::encode(&(&did, bn))[..], + get_challenge(&did, expire_at).as_bytes(), crate::signature::WrapType::Substrate, )) .ok_or("Error while building signature.")?; @@ -155,8 +154,8 @@ benchmarks! { assert!(ConnectedAccounts::::get(&previous_did, linkable_id.clone()).is_some()); let origin = T::EnsureOrigin::generate_origin(caller, did.clone()); let id_arg = linkable_id.clone(); - let req = AssociateAccountRequest::Dotsama(connected_acc_id, sig.into()); - }: associate_account(origin, req, bn) + let req = AssociateAccountRequest::Polkadot(connected_acc_id, sig.into()); + }: associate_account(origin, req, expire_at) verify { assert!(ConnectedDids::::get(linkable_id.clone()).is_some()); assert!(ConnectedAccounts::::get(&previous_did, linkable_id.clone()).is_none()); @@ -173,7 +172,7 @@ benchmarks! { let eth_account = AccountId20(eth_public_key.to_eth_address().unwrap()); let wrapped_payload = get_wrapped_payload( - &Encode::encode(&(&did, expire_at))[..], + get_challenge(&did, expire_at).as_bytes(), crate::signature::WrapType::Ethereum, ); diff --git a/pallets/pallet-did-lookup/src/lib.rs b/pallets/pallet-did-lookup/src/lib.rs index b84cd4129..d7802701d 100644 --- a/pallets/pallet-did-lookup/src/lib.rs +++ b/pallets/pallet-did-lookup/src/lib.rs @@ -34,10 +34,10 @@ mod connection_record; mod migration_state; mod signature; -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod tests; -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod mock; #[cfg(feature = "runtime-benchmarks")] @@ -95,7 +95,7 @@ pub mod pallet { type OriginSuccess: CallSources, DidIdentifierOf>; /// The identifier to which accounts can get associated. - type DidIdentifier: Parameter + MaxEncodedLen + MaybeSerializeDeserialize; + type DidIdentifier: Parameter + AsRef<[u8]> + MaxEncodedLen + MaybeSerializeDeserialize; /// The currency that is used to reserve funds for each did. type Currency: ReservableCurrency>; @@ -252,7 +252,7 @@ pub mod pallet { ); ensure!( - req.verify::(did_identifier.clone(), expiration), + req.verify::(&did_identifier, expiration), Error::::NotAuthorized ); diff --git a/pallets/pallet-did-lookup/src/migrations.rs b/pallets/pallet-did-lookup/src/migrations.rs index 4e5cd3d7d..bad1dca8f 100644 --- a/pallets/pallet-did-lookup/src/migrations.rs +++ b/pallets/pallet-did-lookup/src/migrations.rs @@ -256,7 +256,7 @@ pub(crate) fn add_legacy_association( ConnectedAccounts::::insert(&did_identifier, &account, ()); } -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod tests { use frame_support::assert_ok; use kilt_support::deposit::Deposit; diff --git a/pallets/pallet-did-lookup/src/tests.rs b/pallets/pallet-did-lookup/src/tests.rs index aa60f2b07..bede8ac86 100644 --- a/pallets/pallet-did-lookup/src/tests.rs +++ b/pallets/pallet-did-lookup/src/tests.rs @@ -28,7 +28,7 @@ use sp_runtime::{ use crate::{ account::{AccountId20, EthereumSignature}, - associate_account_request::AssociateAccountRequest, + associate_account_request::{get_challenge, AssociateAccountRequest}, linkable_account::LinkableAccountId, migration_state::MigrationState, migrations::{add_legacy_association, get_mixed_storage_iterator, MixedStorageKey}, @@ -98,16 +98,16 @@ fn test_add_association_account() { let expire_at: BlockNumber = 500; let account_hash_alice = MultiSigner::from(pair_alice.public()).into_account(); let sig_alice_0 = MultiSignature::from( - pair_alice.sign(&[b"", &Encode::encode(&(&DID_00, expire_at))[..], b""].concat()[..]), + pair_alice.sign(&[b"", get_challenge(&DID_00, expire_at).as_bytes(), b""].concat()[..]), ); let sig_alice_1 = MultiSignature::from( - pair_alice.sign(&[b"", &Encode::encode(&(&DID_01, expire_at))[..], b""].concat()[..]), + pair_alice.sign(&[b"", get_challenge(&DID_01, expire_at).as_bytes(), b""].concat()[..]), ); // new association. No overwrite assert!(DidLookup::associate_account( mock_origin::DoubleOrigin(ACCOUNT_00, DID_00).into(), - AssociateAccountRequest::Dotsama(account_hash_alice.clone(), sig_alice_0), + AssociateAccountRequest::Polkadot(account_hash_alice.clone(), sig_alice_0), expire_at, ) .is_ok()); @@ -132,7 +132,7 @@ fn test_add_association_account() { // overwrite existing association let res = DidLookup::associate_account( mock_origin::DoubleOrigin(ACCOUNT_00, DID_01).into(), - AssociateAccountRequest::Dotsama(account_hash_alice.clone(), sig_alice_1.clone()), + AssociateAccountRequest::Polkadot(account_hash_alice.clone(), sig_alice_1.clone()), expire_at, ); if let Err(err) = res { @@ -163,7 +163,7 @@ fn test_add_association_account() { // overwrite existing deposit assert!(DidLookup::associate_account( mock_origin::DoubleOrigin(ACCOUNT_01, DID_01).into(), - AssociateAccountRequest::Dotsama(account_hash_alice.clone(), sig_alice_1), + AssociateAccountRequest::Polkadot(account_hash_alice.clone(), sig_alice_1), expire_at, ) .is_ok()); @@ -203,7 +203,7 @@ fn test_add_eth_association() { let eth_account = AccountId20(eth_pair.public().to_eth_address().unwrap()); let wrapped_payload = get_wrapped_payload( - &Encode::encode(&(&DID_00, expire_at))[..], + get_challenge(&DID_00, expire_at).as_bytes(), crate::signature::WrapType::Ethereum, ); @@ -215,7 +215,7 @@ fn test_add_eth_association() { AssociateAccountRequest::Ethereum(eth_account, EthereumSignature::from(sig)), expire_at, ); - assert!(res.is_ok()); + assert_ok!(res); assert_eq!( ConnectedDids::::get(LinkableAccountId::from(eth_account)), Some(ConnectionRecord { @@ -252,7 +252,7 @@ fn test_add_association_account_invalid_signature() { assert_noop!( DidLookup::associate_account( mock_origin::DoubleOrigin(ACCOUNT_00, DID_01).into(), - AssociateAccountRequest::Dotsama(account_hash_alice, sig_alice_0), + AssociateAccountRequest::Polkadot(account_hash_alice, sig_alice_0), expire_at, ), Error::::NotAuthorized @@ -280,7 +280,7 @@ fn test_add_association_account_expired() { assert_noop!( DidLookup::associate_account( mock_origin::DoubleOrigin(ACCOUNT_00, DID_01).into(), - AssociateAccountRequest::Dotsama(account_hash_alice, sig_alice_0), + AssociateAccountRequest::Polkadot(account_hash_alice, sig_alice_0), expire_at, ), Error::::OutdatedProof diff --git a/runtimes/peregrine/src/tests.rs b/runtimes/peregrine/src/tests.rs index a50c57c12..10bfaf4ad 100644 --- a/runtimes/peregrine/src/tests.rs +++ b/runtimes/peregrine/src/tests.rs @@ -167,7 +167,7 @@ fn test_derive_did_key_web3name() { fn test_derive_did_key_lookup() { assert_eq!( RuntimeCall::DidLookup(pallet_did_lookup::Call::associate_account { - req: AssociateAccountRequest::Dotsama( + req: AssociateAccountRequest::Polkadot( AccountId::new([1u8; 32]), sp_runtime::MultiSignature::from(sp_core::ed25519::Signature([0; 64])) ), @@ -335,7 +335,7 @@ fn test_migration_filter_migrating() { // This should not work during migration: assert!(!MigrationFilter::contains(&RuntimeCall::DidLookup( pallet_did_lookup::Call::associate_account { - req: pallet_did_lookup::associate_account_request::AssociateAccountRequest::Dotsama( + req: pallet_did_lookup::associate_account_request::AssociateAccountRequest::Polkadot( AccountId32::from([0u8; 32]), sp_runtime::MultiSignature::Ecdsa(Signature([0u8; 65])) ), @@ -379,7 +379,7 @@ fn test_migration_filter_done() { // This should only work after the migration: assert!(MigrationFilter::contains(&RuntimeCall::DidLookup( pallet_did_lookup::Call::associate_account { - req: pallet_did_lookup::associate_account_request::AssociateAccountRequest::Dotsama( + req: pallet_did_lookup::associate_account_request::AssociateAccountRequest::Polkadot( AccountId32::from([0u8; 32]), sp_runtime::MultiSignature::Ecdsa(Signature([0u8; 65])) ), diff --git a/runtimes/spiritnet/src/tests.rs b/runtimes/spiritnet/src/tests.rs index a50c57c12..10bfaf4ad 100644 --- a/runtimes/spiritnet/src/tests.rs +++ b/runtimes/spiritnet/src/tests.rs @@ -167,7 +167,7 @@ fn test_derive_did_key_web3name() { fn test_derive_did_key_lookup() { assert_eq!( RuntimeCall::DidLookup(pallet_did_lookup::Call::associate_account { - req: AssociateAccountRequest::Dotsama( + req: AssociateAccountRequest::Polkadot( AccountId::new([1u8; 32]), sp_runtime::MultiSignature::from(sp_core::ed25519::Signature([0; 64])) ), @@ -335,7 +335,7 @@ fn test_migration_filter_migrating() { // This should not work during migration: assert!(!MigrationFilter::contains(&RuntimeCall::DidLookup( pallet_did_lookup::Call::associate_account { - req: pallet_did_lookup::associate_account_request::AssociateAccountRequest::Dotsama( + req: pallet_did_lookup::associate_account_request::AssociateAccountRequest::Polkadot( AccountId32::from([0u8; 32]), sp_runtime::MultiSignature::Ecdsa(Signature([0u8; 65])) ), @@ -379,7 +379,7 @@ fn test_migration_filter_done() { // This should only work after the migration: assert!(MigrationFilter::contains(&RuntimeCall::DidLookup( pallet_did_lookup::Call::associate_account { - req: pallet_did_lookup::associate_account_request::AssociateAccountRequest::Dotsama( + req: pallet_did_lookup::associate_account_request::AssociateAccountRequest::Polkadot( AccountId32::from([0u8; 32]), sp_runtime::MultiSignature::Ecdsa(Signature([0u8; 65])) ), diff --git a/support/src/mock.rs b/support/src/mock.rs index bc550b9e0..e214cbd86 100644 --- a/support/src/mock.rs +++ b/support/src/mock.rs @@ -132,3 +132,9 @@ impl From for SubjectId { SubjectId(acc.into()) } } + +impl AsRef<[u8]> for SubjectId { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +}