diff --git a/Cargo.toml b/Cargo.toml index ded10b8e9da..154fbd1a1ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ members = [ "crates/primitives", "crates/engine", "crates/env", - "crates/eth_compatibility", "crates/storage", "crates/storage/derive", ] diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 8f5b0193624..a39c348924c 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -511,6 +511,34 @@ pub fn ecdsa_recover( }) } +/// Returns an Ethereum address from the ECDSA compressed public key. +/// +/// # Example +/// +/// ``` +/// let pub_key = [ +/// 2, 141, 181, 91, 5, 219, 134, 192, 177, 120, 108, 164, 159, 9, 93, 118, +/// 52, 76, 158, 96, 86, 178, 240, 39, 1, 167, 231, 243, 194, 10, 171, 253, +/// 145, +/// ]; +/// let EXPECTED_ETH_ADDRESS = [ +/// 9, 35, 29, 167, 177, 154, 1, 111, 158, 87, 109, 35, 177, 98, 119, 6, 47, +/// 77, 70, 168, +/// ]; +/// let mut output = [0; 20]; +/// ink_env::ecdsa_to_eth_address(&pub_key, &mut output); +/// assert_eq!(output, EXPECTED_ETH_ADDRESS); +/// ``` +/// +/// # Errors +/// +/// - If the ECDSA public key cannot be recovered from the provided public key. +pub fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result<()> { + ::on_instance(|instance| { + instance.ecdsa_to_eth_address(pubkey, output) + }) +} + /// Checks whether the specified account is a contract. /// /// # Errors diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 70f5601c3ff..43ee3252332 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -253,6 +253,14 @@ pub trait EnvBackend { output: &mut [u8; 33], ) -> Result<()>; + /// Retrieves an Ethereum address from the ECDSA compressed `pubkey` + /// and stores the result in `output`. + fn ecdsa_to_eth_address( + &mut self, + pubkey: &[u8; 33], + output: &mut [u8; 20], + ) -> Result<()>; + /// Low-level interface to call a chain extension method. /// /// Returns the output of the chain extension of the specified type. diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index d948759a8e2..63e312b9f58 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -293,6 +293,20 @@ impl EnvBackend for EnvInstance { } } + fn ecdsa_to_eth_address( + &mut self, + pubkey: &[u8; 33], + output: &mut [u8; 20], + ) -> Result<()> { + let pk = secp256k1::PublicKey::from_slice(pubkey) + .map_err(|_| Error::EcdsaRecoveryFailed)?; + let uncompressed = pk.serialize_uncompressed(); + let mut hash = ::Type::default(); + ::hash(&uncompressed[1..], &mut hash); + output.as_mut().copy_from_slice(&hash[12..]); + Ok(()) + } + fn call_chain_extension( &mut self, func_id: u32, diff --git a/crates/env/src/engine/on_chain/ext.rs b/crates/env/src/engine/on_chain/ext.rs index e42a7dd8642..75cf404e0f1 100644 --- a/crates/env/src/engine/on_chain/ext.rs +++ b/crates/env/src/engine/on_chain/ext.rs @@ -378,6 +378,11 @@ mod sys { message_hash_ptr: Ptr32<[u8]>, output_ptr: Ptr32Mut<[u8]>, ) -> ReturnCode; + + pub fn seal_ecdsa_to_eth_address( + public_key_ptr: Ptr32<[u8]>, + output_ptr: Ptr32Mut<[u8]>, + ) -> ReturnCode; } } @@ -704,6 +709,16 @@ pub fn ecdsa_recover( ret_code.into() } +pub fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result { + let ret_code = unsafe { + sys::seal_ecdsa_to_eth_address( + Ptr32::from_slice(pubkey), + Ptr32Mut::from_slice(output), + ) + }; + ret_code.into() +} + pub fn is_contract(account_id: &[u8]) -> bool { let ret_val = unsafe { sys::seal_is_contract(Ptr32::from_slice(account_id)) }; ret_val.into_bool() diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index 940054eb380..e8a98d71c6f 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -295,6 +295,14 @@ impl EnvBackend for EnvInstance { ext::ecdsa_recover(signature, message_hash, output).map_err(Into::into) } + fn ecdsa_to_eth_address( + &mut self, + pubkey: &[u8; 33], + output: &mut [u8; 20], + ) -> Result<()> { + ext::ecdsa_to_eth_address(pubkey, output).map_err(Into::into) + } + fn call_chain_extension( &mut self, func_id: u32, diff --git a/crates/eth_compatibility/Cargo.toml b/crates/eth_compatibility/Cargo.toml deleted file mode 100644 index 2c30a464b5f..00000000000 --- a/crates/eth_compatibility/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "ink_eth_compatibility" -version = "3.0.1" -authors = ["Parity Technologies "] -edition = "2021" - -license = "Apache-2.0" -readme = "README.md" -repository = "https://github.com/paritytech/ink" -documentation = "https://docs.rs/ink_eth_compatibility/" -homepage = "https://www.parity.io/" -description = "[ink!] Ethereum related stuff." -keywords = ["wasm", "parity", "webassembly", "blockchain", "ethereum"] -categories = ["no-std", "embedded"] -include = ["Cargo.toml", "src/**/*.rs", "/README.md", "/LICENSE"] - -[dependencies] -ink_env = { version = "3.0.1", path = "../env", default-features = false } - -[target.'cfg(not(target_os = "windows"))'.dependencies] -# We do not include `libsecp256k1` on Windows, since it's incompatible. -# We have https://github.com/paritytech/ink/issues/1068 for removing -# this dependency altogether. -libsecp256k1 = { version = "0.7.0", default-features = false } - -[features] -default = ["std"] -std = [ - "ink_env/std", -] diff --git a/crates/eth_compatibility/LICENSE b/crates/eth_compatibility/LICENSE deleted file mode 120000 index 30cff7403da..00000000000 --- a/crates/eth_compatibility/LICENSE +++ /dev/null @@ -1 +0,0 @@ -../../LICENSE \ No newline at end of file diff --git a/crates/eth_compatibility/README.md b/crates/eth_compatibility/README.md deleted file mode 120000 index fe840054137..00000000000 --- a/crates/eth_compatibility/README.md +++ /dev/null @@ -1 +0,0 @@ -../../README.md \ No newline at end of file diff --git a/crates/eth_compatibility/src/lib.rs b/crates/eth_compatibility/src/lib.rs deleted file mode 100644 index f08dba141bc..00000000000 --- a/crates/eth_compatibility/src/lib.rs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2018-2022 Parity Technologies (UK) Ltd. -// -// 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. - -#![no_std] - -use ink_env::{ - DefaultEnvironment, - Environment, -}; - -/// The ECDSA compressed public key. -#[derive(Debug, Copy, Clone)] -pub struct ECDSAPublicKey([u8; 33]); - -impl Default for ECDSAPublicKey { - fn default() -> Self { - // Default is not implemented for [u8; 33], so we can't derive it for ECDSAPublicKey - // But clippy thinks that it is possible. So it is workaround for clippy. - let empty = [0; 33]; - Self(empty) - } -} - -impl AsRef<[u8; 33]> for ECDSAPublicKey { - fn as_ref(&self) -> &[u8; 33] { - &self.0 - } -} - -impl AsMut<[u8; 33]> for ECDSAPublicKey { - fn as_mut(&mut self) -> &mut [u8; 33] { - &mut self.0 - } -} - -impl From<[u8; 33]> for ECDSAPublicKey { - fn from(bytes: [u8; 33]) -> Self { - Self(bytes) - } -} - -/// The address of an Ethereum account. -#[derive(Debug, Default, Copy, Clone)] -pub struct EthereumAddress([u8; 20]); - -impl AsRef<[u8; 20]> for EthereumAddress { - fn as_ref(&self) -> &[u8; 20] { - &self.0 - } -} - -impl AsMut<[u8; 20]> for EthereumAddress { - fn as_mut(&mut self) -> &mut [u8; 20] { - &mut self.0 - } -} - -impl From<[u8; 20]> for EthereumAddress { - fn from(bytes: [u8; 20]) -> Self { - Self(bytes) - } -} - -impl ECDSAPublicKey { - /// Returns Ethereum address from the ECDSA compressed public key. - /// - /// # Example - /// - /// ``` - /// use ink_eth_compatibility::{ECDSAPublicKey, EthereumAddress}; - /// let pub_key: ECDSAPublicKey = [ - /// 2, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, - /// 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, - /// 152, - /// ].into(); - /// - /// let EXPECTED_ETH_ADDRESS: EthereumAddress = [ - /// 126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223 - /// ].into(); - /// - /// assert_eq!(pub_key.to_eth_address().as_ref(), EXPECTED_ETH_ADDRESS.as_ref()); - /// ``` - // We do not include this function on Windows, since it depends on `libsecp256k1`, - // which is incompatible with Windows. - // We have https://github.com/paritytech/ink/issues/1068 for removing this - // dependency altogether. - #[cfg(not(target_os = "windows"))] - pub fn to_eth_address(&self) -> EthereumAddress { - use ink_env::hash; - use libsecp256k1::PublicKey; - - // Transform compressed public key into uncompressed. - let pub_key = PublicKey::parse_compressed(&self.0) - .expect("Unable to parse the compressed ECDSA public key"); - let uncompressed = pub_key.serialize(); - - // Hash the uncompressed public key by Keccak256 algorithm. - let mut hash = ::Type::default(); - // The first byte indicates that the public key is uncompressed. - // Let's skip it for hashing the public key directly. - ink_env::hash_bytes::(&uncompressed[1..], &mut hash); - - // Take the last 20 bytes as an Address - let mut result = EthereumAddress::default(); - result.as_mut().copy_from_slice(&hash[12..]); - - result - } - - /// Returns the default Substrate's `AccountId` from the ECDSA compressed public key. - /// It hashes the compressed public key with the `blake2b_256` algorithm like in substrate. - /// - /// # Example - /// - /// ``` - /// use ink_eth_compatibility::ECDSAPublicKey; - /// let pub_key: ECDSAPublicKey = [ - /// 2, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, - /// 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, - /// 152, - /// ].into(); - /// - /// const EXPECTED_ACCOUNT_ID: [u8; 32] = [ - /// 41, 117, 241, 210, 139, 146, 182, 232, 68, 153, 184, 59, 7, 151, 239, 82, - /// 53, 85, 62, 235, 126, 218, 160, 206, 162, 67, 193, 18, 140, 47, 231, 55, - /// ]; - /// - /// assert_eq!(pub_key.to_default_account_id(), EXPECTED_ACCOUNT_ID.into()); - pub fn to_default_account_id( - &self, - ) -> ::AccountId { - use ink_env::hash; - - let mut output = ::Type::default(); - ink_env::hash_bytes::(&self.0[..], &mut output); - - output.into() - } -} diff --git a/crates/lang/Cargo.toml b/crates/lang/Cargo.toml index f5a57d3dbee..a79f9a7aba1 100644 --- a/crates/lang/Cargo.toml +++ b/crates/lang/Cargo.toml @@ -20,7 +20,6 @@ ink_storage = { version = "3.0.1", path = "../storage", default-features = false ink_primitives = { version = "3.0.1", path = "../primitives", default-features = false } ink_metadata = { version = "3.0.1", path = "../metadata", default-features = false, optional = true } ink_prelude = { version = "3.0.1", path = "../prelude", default-features = false } -ink_eth_compatibility = { version = "3.0.1", path = "../eth_compatibility", default-features = false } ink_lang_macro = { version = "3.0.1", path = "macro", default-features = false } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "full"] } diff --git a/crates/lang/src/env_access.rs b/crates/lang/src/env_access.rs index fa843c6826a..cc01db9cb51 100644 --- a/crates/lang/src/env_access.rs +++ b/crates/lang/src/env_access.rs @@ -29,7 +29,6 @@ use ink_env::{ Error, Result, }; -use ink_eth_compatibility::ECDSAPublicKey; /// The API behind the `self.env()` and `Self::env()` syntax in ink!. /// @@ -831,10 +830,59 @@ where self, signature: &[u8; 65], message_hash: &[u8; 32], - ) -> Result { + ) -> Result<[u8; 33]> { let mut output = [0; 33]; ink_env::ecdsa_recover(signature, message_hash, &mut output) - .map(|_| output.into()) + .map(|_| output) + .map_err(|_| Error::EcdsaRecoveryFailed) + } + + /// Returns an Ethereum address from the ECDSA compressed public key. + /// + /// # Example + /// + /// ``` + /// # use ink_lang as ink; + /// # #[ink::contract] + /// # pub mod my_contract { + /// # #[ink(storage)] + /// # pub struct MyContract { } + /// # + /// # impl MyContract { + /// # #[ink(constructor)] + /// # pub fn new() -> Self { + /// # Self {} + /// # } + /// # + /// #[ink(message)] + /// pub fn ecdsa_to_eth_address(&self) { + /// let pub_key = [ + /// 2, 141, 181, 91, 5, 219, 134, 192, 177, 120, 108, 164, 159, 9, 93, 118, + /// 52, 76, 158, 96, 86, 178, 240, 39, 1, 167, 231, 243, 194, 10, 171, 253, + /// 145, + /// ]; + /// let EXPECTED_ETH_ADDRESS = [ + /// 9, 35, 29, 167, 177, 154, 1, 111, 158, 87, 109, 35, 177, 98, 119, 6, 47, + /// 77, 70, 168, + /// ]; + /// let output = self + /// .env() + /// .ecdsa_to_eth_address(&pub_key) + /// .expect("must return an Ethereum address for the compressed public key"); + /// assert_eq!(output, EXPECTED_ETH_ADDRESS); + /// } + /// # + /// # } + /// # } + /// ``` + /// + /// # Note + /// + /// For more details visit: [`ink_env::ecdsa_to_eth_address`] + pub fn ecdsa_to_eth_address(self, pubkey: &[u8; 33]) -> Result<[u8; 20]> { + let mut output = [0; 20]; + ink_env::ecdsa_to_eth_address(pubkey, &mut output) + .map(|_| output) .map_err(|_| Error::EcdsaRecoveryFailed) }