diff --git a/Cargo.toml b/Cargo.toml index cc60736d..d989ee89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ serde = { version = "1.0.152", default-features = false, features = ["derive"] } serde_json = { version = "1.0.94", default-features = false } sha2 = { version = "0.10.6", default-features = false } substrate-bip39 = { version = "0.4.4" } +thiserror = "1.0.61" tiny-bip39 = "1.0.0" url = "2.5.0" zeroize = { version = "1.5.7", default-features = false } diff --git a/core-primitives/src/lib.rs b/core-primitives/src/lib.rs index a63b5ec6..bad107ae 100644 --- a/core-primitives/src/lib.rs +++ b/core-primitives/src/lib.rs @@ -35,7 +35,7 @@ pub type Hash = sp_core::H256; /// Balance of an account. pub type Balance = u128; /// Alias to 512-bit hash when used in the context of a transaction signature on the chain. -pub type Signature = np_runtime::UniversalSignature; +pub type Signature = np_runtime::AuthenticationProof; /// Some way of identifying an account on the chain. We intentionally make it equivalent /// to the public key of our transaction signing scheme. pub type AccountId = <::Signer as IdentifyAccount>::AccountId; diff --git a/frame/alias/src/lib.rs b/frame/alias/src/lib.rs index 28631052..4f25f604 100644 --- a/frame/alias/src/lib.rs +++ b/frame/alias/src/lib.rs @@ -26,10 +26,10 @@ pub use pallet::*; use crate::weights::WeightInfo; use np_crypto::ecdsa::EcdsaExt; -use parity_scale_codec::{Decode, Encode, FullCodec, MaxEncodedLen}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_runtime::{traits::Member, DispatchError}; -use sp_std::prelude::*; +use sp_runtime::{BoundedBTreeSet, DispatchError}; +use sp_std::{collections::btree_set::BTreeSet, prelude::*}; #[cfg_attr(feature = "std", derive(Hash))] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode, MaxEncodedLen, TypeInfo)] @@ -40,10 +40,8 @@ pub enum AccountAlias { #[frame_support::pallet] pub mod pallet { - use super::*; use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; /// The module's config trait. #[pallet::config] @@ -52,26 +50,10 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; - - type Origin: Member + FullCodec + MaxEncodedLen + TypeInfo; } #[pallet::pallet] - pub struct Pallet(PhantomData); - - #[pallet::call] - impl Pallet - where - T::AccountId: EcdsaExt, - { - #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::alias())] - pub fn alias(origin: OriginFor) -> DispatchResult { - let who = ensure_signed(origin)?; - Self::alias_secp256k1(&who)?; - Ok(()) - } - } + pub struct Pallet(_); #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -84,16 +66,6 @@ pub mod pallet { #[pallet::error] pub enum Error { - /// The account name already exists. - AlreadyExists, - /// The account name does not exists. - NotExists, - /// The account name is not available. - InUse, - /// Invalid name foramt. - InvalidNameFormat, - /// Tag generation failed. - TagGenerationFailed, /// Ethereum address conversion failed. EthereumAddressConversionFailed, /// Cosmos address conversion failed. @@ -103,42 +75,57 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn accountid)] pub type AccountIdOf = StorageMap<_, Blake2_128Concat, AccountAlias, T::AccountId>; + + #[pallet::storage] + #[pallet::getter(fn aliases)] + pub type AccountAliases = + StorageMap<_, Blake2_128Concat, T::AccountId, BoundedBTreeSet>>; } impl Pallet where T::AccountId: EcdsaExt, { - // PUBLIC IMMUTABLES - /// Lookup an AccountAlias to get an Id, if exists. pub fn lookup(alias: &AccountAlias) -> Option { AccountIdOf::::get(alias).map(|x| x) } pub fn alias_secp256k1(who: &T::AccountId) -> Result<(), DispatchError> { - let ethereum_address = who + let mut aliases = BTreeSet::new(); + let eth = who .to_eth_address() .map(|x| x.into()) .ok_or(Error::::EthereumAddressConversionFailed)?; - if AccountIdOf::::get(AccountAlias::EthereumAddress(ethereum_address)).is_none() { - AccountIdOf::::insert(AccountAlias::EthereumAddress(ethereum_address), who); + let eth_alias = AccountAlias::EthereumAddress(eth); + if AccountIdOf::::get(eth_alias).is_none() { + AccountIdOf::::insert(eth_alias, who.clone()); + aliases.insert(eth_alias); Self::deposit_event(Event::::EthereumAddressPublished { who: who.clone(), - address: ethereum_address, + address: eth, }); } - let cosmos_address = who + let cosm = who .to_cosm_address() .map(|x| x.into()) .ok_or(Error::::CosmosAddressConversionFailed)?; - if AccountIdOf::::get(AccountAlias::CosmosAddress(cosmos_address)).is_none() { - AccountIdOf::::insert(AccountAlias::CosmosAddress(cosmos_address), who); + let cosm_alias = AccountAlias::CosmosAddress(cosm); + if AccountIdOf::::get(cosm_alias).is_none() { + AccountIdOf::::insert(cosm_alias, who.clone()); + aliases.insert(cosm_alias); Self::deposit_event(Event::::CosmosAddressPublished { who: who.clone(), - address: cosmos_address, + address: cosm, }); } + if !aliases.is_empty() { + AccountAliases::::insert( + who, + BoundedBTreeSet::try_from(aliases) + .map_err(|_| DispatchError::Other("Too many aliases"))?, + ); + } Ok(()) } } diff --git a/primitives/crypto/src/webauthn.rs b/primitives/crypto/src/webauthn.rs index 6e97424b..d8590683 100644 --- a/primitives/crypto/src/webauthn.rs +++ b/primitives/crypto/src/webauthn.rs @@ -26,7 +26,7 @@ use sp_std::vec::Vec; use crate::p256; #[cfg(feature = "full_crypto")] -use base64ct::{Base64UrlUnpadded as Base64, Encoding}; +use base64ct::{Base64UrlUnpadded, Encoding}; #[cfg(feature = "full_crypto")] use sp_core::hashing::{blake2_256, sha2_256}; #[cfg(feature = "full_crypto")] @@ -65,7 +65,7 @@ impl ClientDataJson { // challenge should be same to the hash value of the message. fn check_message(&self, message_hash: &[u8]) -> bool { - match Base64::decode_vec(&self.challenge) { + match Base64UrlUnpadded::decode_vec(&self.challenge) { Ok(c) => c == message_hash, Err(_) => false, } @@ -101,7 +101,7 @@ impl TryFrom<&str> for ClientDataJson { type Error = (); fn try_from(s: &str) -> Result { - Self::try_from(Base64::decode_vec(s).map_err(|_| ())?.as_slice()) + Self::try_from(Base64UrlUnpadded::decode_vec(s).map_err(|_| ())?.as_slice()) } } @@ -151,10 +151,12 @@ impl Signature { .map_or(false, |recovered_pubkey| recovered_pubkey == *pubkey) } + /// Recover the public key from this signature and a message. pub fn recover>(&self, message: M) -> Option { self.recover_prehashed(&blake2_256(message.as_ref())) } + /// Recover the public key from this signature and a pre-hashed message. pub fn recover_prehashed(&self, message_hash: &[u8; 32]) -> Option { let client_data = match ClientDataJson::try_from(&self.client_data_json[..]) { Ok(c) => c, @@ -187,9 +189,17 @@ impl sp_std::fmt::Debug for Signature { #[cfg(feature = "std")] fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "WebAuthnSignature {{ ")?; - write!(f, "clientDataJSON: \"{}\", ", Base64::encode_string(&self.client_data_json))?; - write!(f, "authenticatorData: \"{}\", ", Base64::encode_string(&self.authenticator_data))?; - write!(f, "signature: \"{}\" }}", Base64::encode_string(&self.signature)) + write!( + f, + "clientDataJSON: \"{}\", ", + Base64UrlUnpadded::encode_string(&self.client_data_json) + )?; + write!( + f, + "authenticatorData: \"{}\", ", + Base64UrlUnpadded::encode_string(&self.authenticator_data) + )?; + write!(f, "signature: \"{}\" }}", Base64UrlUnpadded::encode_string(&self.signature)) } #[cfg(not(feature = "std"))] @@ -208,7 +218,7 @@ mod tests { let client_data = ClientDataJson::try_from(client_data_json); assert!(client_data.is_ok()); - let client_data_json = Base64::decode_vec(client_data_json).unwrap(); + let client_data_json = Base64UrlUnpadded::decode_vec(client_data_json).unwrap(); let client_data = ClientDataJson::try_from(client_data_json.as_slice()); assert!(client_data.is_ok()); @@ -224,7 +234,7 @@ mod tests { let client_data = ClientDataJson::try_from(client_data_json).unwrap(); let authenticator_data = "dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvABAAAAAg"; - let authenticator_data = Base64::decode_vec(authenticator_data).unwrap(); + let authenticator_data = Base64UrlUnpadded::decode_vec(authenticator_data).unwrap(); let rpid_hash = &authenticator_data[0..32]; assert!(client_data.check_rpid(rpid_hash)); } @@ -237,9 +247,9 @@ mod tests { ); let public = Public::try_from(public.as_ref()).unwrap(); let signature = Signature { - signature: Base64::decode_vec("MEQCIHjqOGStreQAKH44uq5lQL5afSdZAhaOKwvnPdpPPfZiAiB-piO5KWYcYDXbvHWIXQirbN1Ww5sLfIvCGGyE1qOdtg").unwrap(), - authenticator_data: Base64::decode_vec("dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvAFAAAABA").unwrap(), - client_data_json: Base64::decode_vec("eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQSIsIm9yaWdpbiI6Imh0dHBzOi8vd2ViYXV0aG4uaW8iLCJjcm9zc09yaWdpbiI6ZmFsc2V9").unwrap(), + signature: Base64UrlUnpadded::decode_vec("MEQCIHjqOGStreQAKH44uq5lQL5afSdZAhaOKwvnPdpPPfZiAiB-piO5KWYcYDXbvHWIXQirbN1Ww5sLfIvCGGyE1qOdtg").unwrap(), + authenticator_data: Base64UrlUnpadded::decode_vec("dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvAFAAAABA").unwrap(), + client_data_json: Base64UrlUnpadded::decode_vec("eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQSIsIm9yaWdpbiI6Imh0dHBzOi8vd2ViYXV0aG4uaW8iLCJjcm9zc09yaWdpbiI6ZmFsc2V9").unwrap(), }; assert!(signature.verify_prehashed(&[0u8; 32], &public)); } diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index b7c6e9fe..49759333 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -13,6 +13,7 @@ base64ct = { workspace = true, optional = true } parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } scale-info = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"], optional = true } +thiserror = { workspace = true, optional = true } # noir np-crypto = { workspace = true } @@ -42,6 +43,7 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "thiserror", ] serde = [ "dep:serde", diff --git a/primitives/runtime/src/accountid32.rs b/primitives/runtime/src/accountid32.rs index 09d6860e..6e7adeb2 100644 --- a/primitives/runtime/src/accountid32.rs +++ b/primitives/runtime/src/accountid32.rs @@ -16,12 +16,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{Derived, UniversalAddress}; +use crate::Multikey; use np_crypto::ecdsa::EcdsaExt; use parity_scale_codec::{Decode, Encode, EncodeLike, Error, Input, MaxEncodedLen}; use scale_info::{Type, TypeInfo}; #[cfg(feature = "serde")] -use sp_core::crypto::{PublicError, Ss58Codec}; +use sp_core::crypto::{PublicError, Ss58AddressFormat, Ss58Codec}; use sp_core::{ crypto::{AccountId32 as SubstrateAccountId32, FromEntropy, UncheckedFrom}, ByteArray, H160, H256, @@ -32,23 +32,33 @@ use sp_std::{ vec::Vec, }; +/// An opaque 32-byte cryptographic identifier. +/// +/// HACK: This type replaces Substrate AccountId32 to be passed keeping recovered public key. +/// `source` field should be ignored during serialization. #[derive(Clone, Eq)] pub struct AccountId32 { inner: [u8; 32], - pub origin: Option, + source: Option, } impl AccountId32 { + /// Create a new instance from its raw inner byte value. + /// + /// Equivalent to this types `From<[u8; 32]>` implementation. For the lack of const + /// support in traits we have this constructor. pub const fn new(inner: [u8; 32]) -> Self { - Self { inner, origin: None } + Self { inner, source: None } } -} -impl Derived for AccountId32 { - type Origin = UniversalAddress; + /// Returns the source of account id, if possible. + pub fn source(&self) -> Option<&Multikey> { + self.source.as_ref() + } - fn origin(&self) -> Option { - self.origin.clone() + /// Sets the source of account id. + pub fn set_source(&mut self, source: Multikey) { + self.source = Some(source); } } @@ -84,7 +94,7 @@ impl EncodeLike for AccountId32 {} impl Decode for AccountId32 { fn decode(input: &mut I) -> Result { - Ok(Self { inner: <[u8; 32]>::decode(input)?, origin: None }) + Ok(Self { inner: <[u8; 32]>::decode(input)?, source: None }) } } @@ -111,7 +121,7 @@ impl sp_std::hash::Hash for AccountId32 { impl UncheckedFrom for AccountId32 { fn unchecked_from(h: H256) -> Self { - Self { inner: h.into(), origin: None } + Self { inner: h.into(), source: None } } } @@ -121,12 +131,13 @@ impl ByteArray for AccountId32 { #[cfg(feature = "serde")] impl Ss58Codec for AccountId32 { - fn to_ss58check(&self) -> String { - SubstrateAccountId32::new(self.inner).to_ss58check() + fn from_ss58check_with_version(s: &str) -> Result<(Self, Ss58AddressFormat), PublicError> { + SubstrateAccountId32::from_ss58check_with_version(s) + .map(|(inner, format)| (Self { inner: inner.into(), source: None }, format)) } - fn from_ss58check(s: &str) -> Result { - Ok(Self { inner: SubstrateAccountId32::from_ss58check(s)?.into(), origin: None }) + fn to_ss58check_with_version(&self, version: Ss58AddressFormat) -> String { + SubstrateAccountId32::new(self.inner).to_ss58check_with_version(version) } } @@ -182,7 +193,7 @@ impl From for AccountId32 { impl From for AccountId32 { fn from(v: sp_core::ecdsa::Public) -> Self { - Self { inner: sp_core::blake2_256(v.as_ref()), origin: Some(v.into()) } + Self { inner: sp_core::blake2_256(v.as_ref()), source: Some(v.into()) } } } @@ -260,10 +271,10 @@ impl FromEntropy for AccountId32 { impl EcdsaExt for AccountId32 { fn to_eth_address(&self) -> Option { - self.origin.as_ref().and_then(EcdsaExt::to_eth_address) + self.source.as_ref().and_then(EcdsaExt::to_eth_address) } fn to_cosm_address(&self) -> Option { - self.origin.as_ref().and_then(EcdsaExt::to_cosm_address) + self.source.as_ref().and_then(EcdsaExt::to_cosm_address) } } diff --git a/primitives/runtime/src/generic/mod.rs b/primitives/runtime/src/generic/mod.rs new file mode 100644 index 00000000..e3757e8f --- /dev/null +++ b/primitives/runtime/src/generic/mod.rs @@ -0,0 +1,20 @@ +// This file is part of Noir. + +// Copyright (C) 2023 Haderech Pte. Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod unchecked_extrinsic; + +pub use unchecked_extrinsic::*; diff --git a/primitives/runtime/src/generic.rs b/primitives/runtime/src/generic/unchecked_extrinsic.rs similarity index 98% rename from primitives/runtime/src/generic.rs rename to primitives/runtime/src/generic/unchecked_extrinsic.rs index 53487f8f..03e8b7b1 100644 --- a/primitives/runtime/src/generic.rs +++ b/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -17,7 +17,7 @@ //! Generic implementation of an unchecked (pre-verification) extrinsic. -use crate::Verify; +use crate::traits::VerifyMut; use parity_scale_codec::{Compact, Decode, Encode, EncodeLike, Error, Input}; use scale_info::{build::Fields, meta_type, Path, StaticTypeInfo, Type, TypeInfo, TypeParameter}; use sp_io::hashing::blake2_256; @@ -153,8 +153,8 @@ impl Checkable where LookupSource: Member + MaybeDisplay, Call: Encode + Member, - Signature: Member + Verify, - ::Signer: IdentifyAccount, + Signature: Member + VerifyMut, + ::Signer: IdentifyAccount, Extra: SignedExtension, AccountId: Member + MaybeDisplay, Lookup: traits::Lookup, @@ -166,7 +166,8 @@ where Some((signed, signature, extra)) => { let mut signed = lookup.lookup(signed)?; let raw_payload = SignedPayload::new(self.function, extra)?; - if !raw_payload.using_encoded(|payload| signature.verify(payload, &mut signed)) { + if !raw_payload.using_encoded(|payload| signature.verify_mut(payload, &mut signed)) + { return Err(InvalidTransaction::BadProof.into()) } diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index efb02444..7cc2b22c 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -21,12 +21,16 @@ #![cfg_attr(not(feature = "std"), no_std)] mod accountid32; +/// Generic extrinsic implementation. pub mod generic; +mod multikey; +/// Self-contained (including verification proofs) types to handle extrinsics of foreign protocols. pub mod self_contained; -mod universaladdress; +/// Traits for runtime types. +pub mod traits; pub use accountid32::AccountId32; -pub use universaladdress::{UniversalAddress, UniversalAddressKind}; +pub use multikey::{Multikey, MultikeyKind}; #[cfg(feature = "serde")] pub use serde::{Deserialize, Serialize}; @@ -36,7 +40,7 @@ use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_core::{ecdsa, ed25519, sr25519}; use sp_runtime::{ - traits::{IdentifyAccount, Lazy, Verify as SubstrateVerify}, + traits::{Lazy, Verify}, RuntimeDebug, }; use sp_std::prelude::*; @@ -44,7 +48,7 @@ use sp_std::prelude::*; /// Signature verify that can work with any known signature types. #[derive(Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum UniversalSignature { +pub enum AuthenticationProof { /// A Ed25519 signature. Ed25519(ed25519::Signature), /// A Sr25519 signature. @@ -57,22 +61,8 @@ pub enum UniversalSignature { WebAuthn(webauthn::Signature), } -/// Means of signature verification. -pub trait Verify { - /// Type of the signer. - type Signer: IdentifyAccount; - /// Verify a signature. - /// - /// Return `true` if signature is valid for the value. - fn verify>( - &self, - msg: L, - signer: &mut ::AccountId, - ) -> bool; -} - -impl SubstrateVerify for UniversalSignature { - type Signer = UniversalAddress; +impl Verify for AuthenticationProof { + type Signer = Multikey; fn verify>(&self, mut msg: L, signer: &AccountId32) -> bool { match (self, signer) { @@ -114,10 +104,10 @@ impl SubstrateVerify for UniversalSignature { } } -impl Verify for UniversalSignature { - type Signer = UniversalAddress; +impl traits::VerifyMut for AuthenticationProof { + type Signer = Multikey; - fn verify>(&self, mut msg: L, signer: &mut AccountId32) -> bool { + fn verify_mut>(&self, mut msg: L, signer: &mut AccountId32) -> bool { match (self, signer.clone()) { (Self::Ed25519(ref sig), who) => match ed25519::Public::try_from(who.as_ref()) { Ok(signer) => sig.verify(msg, &signer), @@ -134,7 +124,7 @@ impl Verify for UniversalSignature { if &sp_io::hashing::blake2_256(pubkey.as_ref()) == AsRef::<[u8; 32]>::as_ref(&who) { - signer.origin = Some(ecdsa::Public(pubkey).into()); + signer.set_source(ecdsa::Public(pubkey).into()); true } else { false @@ -149,7 +139,7 @@ impl Verify for UniversalSignature { if &sp_io::hashing::blake2_256(pubkey.as_ref()) == AsRef::<[u8; 32]>::as_ref(&who) { - signer.origin = Some(p256::Public(pubkey).into()); + signer.set_source(p256::Public(pubkey).into()); true } else { false @@ -163,7 +153,7 @@ impl Verify for UniversalSignature { if &sp_io::hashing::blake2_256(pubkey.as_ref()) == AsRef::<[u8; 32]>::as_ref(&who) { - signer.origin = Some(p256::Public(pubkey).into()); + signer.set_source(webauthn::Public::from_raw(pubkey).into()); true } else { false @@ -174,9 +164,3 @@ impl Verify for UniversalSignature { } } } - -pub trait Derived { - type Origin; - - fn origin(&self) -> Option; -} diff --git a/primitives/runtime/src/universaladdress.rs b/primitives/runtime/src/multikey.rs similarity index 52% rename from primitives/runtime/src/universaladdress.rs rename to primitives/runtime/src/multikey.rs index 225a4a1d..8f630985 100644 --- a/primitives/runtime/src/universaladdress.rs +++ b/primitives/runtime/src/multikey.rs @@ -15,19 +15,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Universal account infrastructure. +//! Interoperable public key representation. use crate::AccountId32; use np_crypto::{ecdsa::EcdsaExt, p256}; -use parity_scale_codec::{Decode, Encode, EncodeLike, Error, Input, MaxEncodedLen}; +use parity_scale_codec::{Decode, Encode, EncodeLike, Error as CodecError, Input, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_core::{ecdsa, ed25519, sr25519, H160, H256}; +use sp_core::{ + crypto::{PublicError, UncheckedFrom}, + ecdsa, ed25519, sr25519, ByteArray, H160, H256, +}; use sp_runtime::traits::IdentifyAccount; #[cfg(not(feature = "std"))] use sp_std::vec::Vec; #[cfg(feature = "serde")] -use base64ct::{Base64UrlUnpadded as Base64, Encoding}; +use base64ct::{Base64UrlUnpadded, Encoding}; #[cfg(feature = "serde")] use serde::{ de::{Deserializer, Error as DeError, Visitor}, @@ -52,10 +55,10 @@ pub mod multicodec { pub const BLAKE2B_256: &[u8] = &[0xa0, 0xe4, 0x02, 0x20]; } -/// The type of public key that universal address contains. +/// The type of public key that multikey contains. #[cfg_attr(feature = "std", derive(Debug))] #[derive(Clone, PartialEq, Eq)] -pub enum UniversalAddressKind { +pub enum MultikeyKind { /// Unknown public key type. (Invalid) Unknown, /// Ed25519 public key type. @@ -70,49 +73,59 @@ pub enum UniversalAddressKind { Blake2b256, } +#[cfg_attr(feature = "std", derive(thiserror::Error))] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Error { + #[cfg_attr(feature = "std", error("invalid length"))] + BadLength, + #[cfg_attr(feature = "std", error("invalid multicodec prefix"))] + InvalidPrefix, +} + /// A universal representation of a public key encoded with multicodec. +/// +/// NOTE: https://www.w3.org/TR/vc-data-integrity/#multikey #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, TypeInfo)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "std", derive(Hash))] -pub struct UniversalAddress(pub Vec); +pub struct Multikey(Vec); -impl IdentifyAccount for UniversalAddress { +impl IdentifyAccount for Multikey { type AccountId = AccountId32; fn into_account(self) -> Self::AccountId { match self.kind() { - UniversalAddressKind::Ed25519 | UniversalAddressKind::Sr25519 => + MultikeyKind::Ed25519 | MultikeyKind::Sr25519 => <[u8; 32]>::try_from(&self.0[2..]).unwrap().into(), - UniversalAddressKind::Secp256k1 | UniversalAddressKind::P256 => + MultikeyKind::Secp256k1 | MultikeyKind::P256 => sp_io::hashing::blake2_256(&self.0[2..]).into(), - UniversalAddressKind::Blake2b256 => <[u8; 32]>::try_from(&self.0[4..]).unwrap().into(), - _ => panic!("invalid universal address"), + MultikeyKind::Blake2b256 => <[u8; 32]>::try_from(&self.0[4..]).unwrap().into(), + _ => panic!("invalid multikey"), } } } -/* #[cfg(feature = "serde")] -impl Serialize for UniversalAddress { +impl Serialize for Multikey { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let encoded = String::from("u") + &Base64::encode_string(&self.0); + let encoded = String::from("u") + &Base64UrlUnpadded::encode_string(&self.0); serializer.serialize_str(&encoded) } } +// TODO: Support other multibase formats other than base64url. #[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for UniversalAddress { +impl<'de> Deserialize<'de> for Multikey { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { - struct UniversalAddressVisitor; + struct MultikeyVisitor; - impl<'de> Visitor<'de> for UniversalAddressVisitor { - type Value = UniversalAddress; + impl<'de> Visitor<'de> for MultikeyVisitor { + type Value = Multikey; fn expecting(&self, formatter: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { formatter.write_str("a multibase (base64url) encoded string") @@ -124,34 +137,32 @@ impl<'de> Deserialize<'de> for UniversalAddress { { use sp_std::str::FromStr; - UniversalAddress::from_str(value) - .map_err(|_| E::custom("invalid universal address")) + Multikey::from_str(value).map_err(|_| E::custom("invalid multikey")) } } - deserializer.deserialize_str(UniversalAddressVisitor) + deserializer.deserialize_str(MultikeyVisitor) } } -*/ -impl UniversalAddress { +impl Multikey { /// Get the type of public key that contains. - pub fn kind(&self) -> UniversalAddressKind { + pub fn kind(&self) -> MultikeyKind { match &self.0[0..4] { - [0xe7, 0x01, ..] => UniversalAddressKind::Secp256k1, - [0xed, 0x01, ..] => UniversalAddressKind::Ed25519, - [0xef, 0x01, ..] => UniversalAddressKind::Sr25519, - [0x80, 0x24, ..] => UniversalAddressKind::P256, - [0xa0, 0xe4, 0x02, 0x20] => UniversalAddressKind::Blake2b256, - _ => UniversalAddressKind::Unknown, + [0xe7, 0x01, ..] => MultikeyKind::Secp256k1, + [0xed, 0x01, ..] => MultikeyKind::Ed25519, + [0xef, 0x01, ..] => MultikeyKind::Sr25519, + [0x80, 0x24, ..] => MultikeyKind::P256, + [0xa0, 0xe4, 0x02, 0x20] => MultikeyKind::Blake2b256, + _ => MultikeyKind::Unknown, } } } -impl EcdsaExt for UniversalAddress { +impl EcdsaExt for Multikey { fn to_eth_address(&self) -> Option { match self.kind() { - UniversalAddressKind::Secp256k1 => { + MultikeyKind::Secp256k1 => { let pubkey = np_io::crypto::secp256k1_pubkey_serialize(&self.0[2..].try_into().unwrap())?; Some(H160::from_slice(&sp_io::hashing::keccak_256(&pubkey)[12..])) @@ -162,7 +173,7 @@ impl EcdsaExt for UniversalAddress { fn to_cosm_address(&self) -> Option { match self.kind() { - UniversalAddressKind::Secp256k1 => { + MultikeyKind::Secp256k1 => { let hashed = sp_io::hashing::sha2_256(&self.0[2..]); Some(np_io::crypto::ripemd160(&hashed).into()) }, @@ -171,40 +182,69 @@ impl EcdsaExt for UniversalAddress { } } -impl AsRef<[u8]> for UniversalAddress { +impl AsRef<[u8]> for Multikey { fn as_ref(&self) -> &[u8] { self.0.as_ref() } } -impl AsMut<[u8]> for UniversalAddress { +impl AsMut<[u8]> for Multikey { fn as_mut(&mut self) -> &mut [u8] { self.0.as_mut() } } -impl TryFrom<&[u8]> for UniversalAddress { - type Error = (); +impl TryFrom<&[u8]> for Multikey { + type Error = Error; fn try_from(data: &[u8]) -> Result { - Ok(Self(Vec::try_from(data).map_err(|_| ())?)) + Ok(Self::try_from(Vec::from(data))?) } } -impl MaxEncodedLen for UniversalAddress { +impl TryFrom> for Multikey { + type Error = Error; + + fn try_from(v: Vec) -> Result { + if v.len() > 0 && v.len() < 34 { + return Err(Error::BadLength); + } + match &v[0..4] { + [0xe7, 0x01, ..] => + ecdsa::Public::try_from(&v[2..]).map_err(|_| Error::BadLength).map(Into::into), + [0xed, 0x01, ..] => + ed25519::Public::try_from(&v[2..]).map_err(|_| Error::BadLength).map(Into::into), + [0xef, 0x01, ..] => + sr25519::Public::try_from(&v[2..]).map_err(|_| Error::BadLength).map(Into::into), + [0x80, 0x24, ..] => + p256::Public::try_from(&v[2..]).map_err(|_| Error::BadLength).map(Into::into), + [0xa0, 0xe4, 0x02, 0x20] => + (v.len() == 36).then(|| Self(Vec::from(&v[4..]))).ok_or(Error::BadLength), + _ => Err(Error::InvalidPrefix), + } + } +} + +impl UncheckedFrom> for Multikey { + fn unchecked_from(v: Vec) -> Self { + Self(v) + } +} + +impl MaxEncodedLen for Multikey { fn max_encoded_len() -> usize { 36 } } -impl Encode for UniversalAddress { +impl Encode for Multikey { fn size_hint(&self) -> usize { match self.kind() { - UniversalAddressKind::Ed25519 => 34, - UniversalAddressKind::Sr25519 => 34, - UniversalAddressKind::Secp256k1 => 35, - UniversalAddressKind::P256 => 35, - UniversalAddressKind::Blake2b256 => 36, + MultikeyKind::Ed25519 => 34, + MultikeyKind::Sr25519 => 34, + MultikeyKind::Secp256k1 => 35, + MultikeyKind::P256 => 35, + MultikeyKind::Blake2b256 => 36, _ => 0, } } @@ -218,150 +258,165 @@ impl Encode for UniversalAddress { } } -impl EncodeLike for UniversalAddress {} +impl EncodeLike for Multikey {} -impl Decode for UniversalAddress { - fn decode(input: &mut I) -> Result { +impl Decode for Multikey { + fn decode(input: &mut I) -> Result { let byte = input.read_byte()?; let expected_len = match byte { 0xed | 0xef => 34, 0xe7 | 0x80 => 35, 0xa0 => 36, - _ => return Err("unexpected first byte decoding UniversalAddress".into()), + _ => return Err("unexpected first byte decoding Multikey".into()), }; let mut res = Vec::new(); res.resize(expected_len, 0); res[0] = byte; input.read(&mut res[1..])?; - let res = UniversalAddress(res); + let res = Multikey(res); match res.kind() { - UniversalAddressKind::Unknown => Err("Could not decode UniversalAddress".into()), + MultikeyKind::Unknown => Err("Could not decode Multikey".into()), _ => Ok(res), } } } -impl From for UniversalAddress { +impl From for Multikey { fn from(k: ed25519::Public) -> Self { - let mut v: Vec = Vec::new(); + let mut v: Vec = + Vec::with_capacity(multicodec::ED25519_PUB.len() + ed25519::Public::LEN); v.extend_from_slice(multicodec::ED25519_PUB); v.extend_from_slice(k.as_ref()); Self(v) } } -impl From for UniversalAddress { +impl From for Multikey { fn from(k: sr25519::Public) -> Self { - let mut v: Vec = Vec::new(); + let mut v: Vec = + Vec::with_capacity(multicodec::SR25519_PUB.len() + sr25519::Public::LEN); v.extend_from_slice(multicodec::SR25519_PUB); v.extend_from_slice(k.as_ref()); Self(v) } } -impl From for UniversalAddress { +impl From for Multikey { fn from(k: ecdsa::Public) -> Self { - let mut v: Vec = Vec::new(); + let mut v: Vec = + Vec::with_capacity(multicodec::SECP256K1_PUB.len() + ecdsa::Public::LEN); v.extend_from_slice(multicodec::SECP256K1_PUB); v.extend_from_slice(k.as_ref()); Self(v) } } -impl From for UniversalAddress { +impl From for Multikey { fn from(k: p256::Public) -> Self { - let mut v: Vec = Vec::new(); + let mut v: Vec = Vec::with_capacity(multicodec::P256_PUB.len() + p256::Public::LEN); v.extend_from_slice(multicodec::P256_PUB); v.extend_from_slice(k.as_ref()); Self(v) } } -impl From for UniversalAddress { +impl From for Multikey { fn from(hash: H256) -> Self { - let mut v: Vec = Vec::new(); + let mut v: Vec = Vec::with_capacity(multicodec::BLAKE2B_256.len() + H256::len_bytes()); v.extend_from_slice(multicodec::BLAKE2B_256); v.extend_from_slice(hash.as_ref()); Self(v) } } -impl TryInto for UniversalAddress { +impl TryFrom for Multikey { type Error = (); - fn try_into(self) -> Result { - match &self.0[0..2] { - multicodec::ED25519_PUB => Ok(ed25519::Public::try_from(&self.0[2..]).map_err(|_| ())?), - _ => Err(()), + fn try_from(v: AccountId32) -> Result { + v.source().ok_or(()).cloned() + } +} + +impl TryFrom for ed25519::Public { + type Error = PublicError; + + fn try_from(v: Multikey) -> Result { + if v.kind() == MultikeyKind::Ed25519 { + ed25519::Public::try_from(&v.0[2..]).map_err(|_| PublicError::BadLength) + } else { + Err(PublicError::InvalidPrefix) } } } -impl TryInto for UniversalAddress { - type Error = (); +impl TryFrom for sr25519::Public { + type Error = PublicError; - fn try_into(self) -> Result { - match &self.0[0..2] { - multicodec::SR25519_PUB => Ok(sr25519::Public::try_from(&self.0[2..]).map_err(|_| ())?), - _ => Err(()), + fn try_from(v: Multikey) -> Result { + if v.kind() == MultikeyKind::Sr25519 { + sr25519::Public::try_from(&v.0[2..]).map_err(|_| PublicError::BadLength) + } else { + Err(PublicError::InvalidPrefix) } } } -impl TryInto for UniversalAddress { - type Error = (); +impl TryFrom for ecdsa::Public { + type Error = PublicError; - fn try_into(self) -> Result { - match &self.0[0..2] { - multicodec::SECP256K1_PUB => Ok(ecdsa::Public::try_from(&self.0[2..]).map_err(|_| ())?), - _ => Err(()), + fn try_from(v: Multikey) -> Result { + if v.kind() == MultikeyKind::Secp256k1 { + ecdsa::Public::try_from(&v.0[2..]).map_err(|_| PublicError::BadLength) + } else { + Err(PublicError::InvalidPrefix) } } } -impl TryInto for UniversalAddress { - type Error = (); +impl TryFrom for p256::Public { + type Error = PublicError; - fn try_into(self) -> Result { - match &self.0[0..2] { - multicodec::P256_PUB => Ok(p256::Public::try_from(&self.0[2..]).map_err(|_| ())?), - _ => Err(()), + fn try_from(v: Multikey) -> Result { + if v.kind() == MultikeyKind::P256 { + p256::Public::try_from(&v.0[2..]).map_err(|_| PublicError::BadLength) + } else { + Err(PublicError::InvalidPrefix) } } } #[cfg(feature = "serde")] -impl sp_std::str::FromStr for UniversalAddress { +impl sp_std::str::FromStr for Multikey { type Err = (); fn from_str(s: &str) -> Result { let addr = if s.starts_with('u') { - UniversalAddress(Base64::decode_vec(&s[1..]).map_err(|_| ())?) + Multikey(Base64UrlUnpadded::decode_vec(&s[1..]).map_err(|_| ())?) } else if s.starts_with("0x") { - UniversalAddress(array_bytes::hex2bytes(&s[2..]).map_err(|_| ())?) + Multikey(array_bytes::hex2bytes(&s[2..]).map_err(|_| ())?) } else { return Err(()) }; match addr.kind() { - UniversalAddressKind::Unknown => Err(()), + MultikeyKind::Unknown => Err(()), _ => Ok(addr), } } } #[cfg(feature = "std")] -impl std::fmt::Display for UniversalAddress { +impl std::fmt::Display for Multikey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "u{}", Base64::encode_string(self.as_ref())) + write!(f, "u{}", Base64UrlUnpadded::encode_string(self.as_ref())) } } -impl sp_std::fmt::Debug for UniversalAddress { +impl sp_std::fmt::Debug for Multikey { #[cfg(feature = "std")] fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - write!(f, "u{}", Base64::encode_string(self.as_ref())) + write!(f, "u{}", Base64UrlUnpadded::encode_string(self.as_ref())) } #[cfg(not(feature = "std"))] @@ -383,7 +438,7 @@ mod tests { ) .unwrap(); - let addr = UniversalAddress::from_str("u5wECOvHh76TR4a1cueOWfpjpAdr803xEzwv7bCFpl_XuUd8"); + let addr = Multikey::from_str("u5wECOvHh76TR4a1cueOWfpjpAdr803xEzwv7bCFpl_XuUd8"); assert!(addr.is_ok()); let addr = addr.unwrap(); @@ -398,8 +453,8 @@ mod tests { ) .unwrap(); let pubkey = ecdsa::Public::try_from(&pubkey[..]).unwrap(); - let addr = UniversalAddress::from(pubkey); - assert_eq!(addr.kind(), UniversalAddressKind::Secp256k1); + let addr = Multikey::from(pubkey); + assert_eq!(addr.kind(), MultikeyKind::Secp256k1); } #[test] @@ -408,14 +463,14 @@ mod tests { "e701023af1e1efa4d1e1ad5cb9e3967e98e901dafcd37c44cf0bfb6c216997f5ee51df", ) .unwrap(); - let addr = UniversalAddress(raw.clone()); - assert_eq!(addr.kind(), UniversalAddressKind::Secp256k1); + let addr = Multikey(raw.clone()); + assert_eq!(addr.kind(), MultikeyKind::Secp256k1); let encoded = addr.encode(); assert_eq!(encoded, raw); let mut io = IoReader(&encoded[..]); - let decoded = UniversalAddress::decode(&mut io); + let decoded = Multikey::decode(&mut io); assert!(decoded.is_ok()); assert_eq!(decoded.unwrap(), addr); } diff --git a/primitives/runtime/src/self_contained/mod.rs b/primitives/runtime/src/self_contained/mod.rs new file mode 100644 index 00000000..e3757e8f --- /dev/null +++ b/primitives/runtime/src/self_contained/mod.rs @@ -0,0 +1,20 @@ +// This file is part of Noir. + +// Copyright (C) 2023 Haderech Pte. Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod unchecked_extrinsic; + +pub use unchecked_extrinsic::*; diff --git a/primitives/runtime/src/self_contained.rs b/primitives/runtime/src/self_contained/unchecked_extrinsic.rs similarity index 97% rename from primitives/runtime/src/self_contained.rs rename to primitives/runtime/src/self_contained/unchecked_extrinsic.rs index e07d1b34..cc1b13bd 100644 --- a/primitives/runtime/src/self_contained.rs +++ b/primitives/runtime/src/self_contained/unchecked_extrinsic.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{generic, Verify}; +use crate::{generic, traits::VerifyMut}; use frame_support::{ dispatch::{DispatchInfo, GetDispatchInfo}, traits::ExtrinsicCall, @@ -84,8 +84,8 @@ impl Checkable where Address: Member + MaybeDisplay, Call: Encode + Member + SelfContainedCall, - Signature: Member + Verify, - ::Signer: IdentifyAccount, + Signature: Member + VerifyMut, + ::Signer: IdentifyAccount, Extra: SignedExtension, AccountId: Member + MaybeDisplay, Lookup: traits::Lookup, diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs new file mode 100644 index 00000000..53578cf5 --- /dev/null +++ b/primitives/runtime/src/traits.rs @@ -0,0 +1,32 @@ +// This file is part of Noir. + +// Copyright (C) 2023 Haderech Pte. Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use sp_runtime::traits::{IdentifyAccount, Lazy}; + +/// Means of signature verification. +pub trait VerifyMut { + /// Type of the signer. + type Signer: IdentifyAccount; + /// Verify a signature. + /// + /// Return `true` if signature is valid for the value. + fn verify_mut>( + &self, + msg: L, + signer: &mut ::AccountId, + ) -> bool; +} diff --git a/runtime/src/compat/evm.rs b/runtime/src/compat/evm.rs index d0dd5f16..e2b79d70 100644 --- a/runtime/src/compat/evm.rs +++ b/runtime/src/compat/evm.rs @@ -20,7 +20,7 @@ use frame_support::dispatch::RawOrigin; use np_crypto::ecdsa::EcdsaExt; -use np_runtime::{Derived, UniversalAddress, UniversalAddressKind}; +use np_runtime::{Multikey, MultikeyKind}; use pallet_alias::AccountAlias; use pallet_evm::{AddressMapping, EnsureAddressOrigin}; use sp_core::{Hasher, H160, H256}; @@ -32,7 +32,7 @@ pub struct EnsureAddressHashed(PhantomData); impl EnsureAddressOrigin for EnsureAddressHashed where OuterOrigin: Into, OuterOrigin>> + From>, - AccountId: Derived + Clone, + AccountId: TryInto + Clone, { type Success = AccountId; @@ -42,9 +42,9 @@ where ) -> Result { origin.into().and_then(|o| match o { RawOrigin::Signed(who) => { - if let Some(origin) = who.clone().origin() { - if origin.kind() == UniversalAddressKind::Secp256k1 { - if let Some(hashed) = origin.to_eth_address() { + if let Ok(source) = who.clone().try_into() { + if source.kind() == MultikeyKind::Secp256k1 { + if let Some(hashed) = source.to_eth_address() { if &hashed == address { return Ok(who) } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 29e75e0f..51ab8ae9 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -47,7 +47,6 @@ use frame_support::{ }; pub use noir_core_primitives::{AccountId, Balance, BlockNumber, Hash, Nonce, Signature}; use np_crypto::ecdsa::EcdsaExt; -use np_runtime::UniversalAddress; use pallet_cosmos::handler::cosm::MsgHandler; use pallet_ethereum::{ Call::transact, PostLogContent, Transaction as EthereumTransaction, TransactionAction, @@ -390,8 +389,6 @@ impl pallet_alias::Config for Runtime { type RuntimeEvent = RuntimeEvent; /// Weight information for extrinsics in this pallet. type WeightInfo = pallet_alias::weights::SubstrateWeight; - - type Origin = UniversalAddress; } impl pallet_aura::Config for Runtime {