diff --git a/Cargo.lock b/Cargo.lock index 440d549af1a35..3ca6fd499b45a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -456,12 +456,6 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" -[[package]] -name = "base64ct" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874f8444adcb4952a8bc51305c8be95c8ec8237bb0d2e78d2e039f771f8828a0" - [[package]] name = "beef" version = "0.5.1" @@ -1794,6 +1788,7 @@ checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" dependencies = [ "der", "elliptic-curve", + "rfc6979", "signature", ] @@ -2291,6 +2286,7 @@ dependencies = [ "frame-support-procedural", "frame-system", "impl-trait-for-tuples", + "k256", "log 0.4.14", "once_cell", "parity-scale-codec", @@ -5646,7 +5642,6 @@ dependencies = [ "frame-system", "hex", "hex-literal", - "k256", "log 0.4.14", "pallet-beefy", "pallet-mmr", @@ -7037,17 +7032,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs8" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" -dependencies = [ - "der", - "spki", - "zeroize", -] - [[package]] name = "pkg-config" version = "0.3.19" @@ -7812,6 +7796,17 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "448296241d034b96c11173591deaa1302f2c17b56092106c1f92c1bc0183a8c9" +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint", + "hmac 0.11.0", + "zeroize", +] + [[package]] name = "ring" version = "0.16.20" @@ -9304,7 +9299,6 @@ checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der", "generic-array 0.14.4", - "pkcs8", "subtle", "zeroize", ] @@ -9589,6 +9583,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ + "digest 0.9.0", "rand_core 0.6.2", ] @@ -10515,16 +10510,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "spki" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "ss58-registry" version = "1.11.0" diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml index a32b1dc6dbd64..9da8198b405f7 100644 --- a/frame/beefy-mmr/Cargo.toml +++ b/frame/beefy-mmr/Cargo.toml @@ -10,7 +10,6 @@ repository = "https://github.com/paritytech/substrate" [dependencies] hex = { version = "0.4", optional = true } codec = { version = "3.0.0", package = "parity-scale-codec", default-features = false, features = ["derive"] } -k256 = { version = "0.10.2", default-features = false, features = ["arithmetic"] } log = { version = "0.4.13", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } @@ -42,7 +41,6 @@ std = [ "frame-support/std", "frame-system/std", "hex", - "k256/std", "log/std", "pallet-beefy/std", "pallet-mmr/std", diff --git a/frame/beefy-mmr/src/lib.rs b/frame/beefy-mmr/src/lib.rs index 640ebeb7d49cc..8b904d6aefd5f 100644 --- a/frame/beefy-mmr/src/lib.rs +++ b/frame/beefy-mmr/src/lib.rs @@ -39,7 +39,7 @@ use sp_std::prelude::*; use beefy_primitives::mmr::{BeefyDataProvider, BeefyNextAuthoritySet, MmrLeaf, MmrLeafVersion}; use pallet_mmr::{LeafDataProvider, ParentNumberAndHash}; -use frame_support::traits::Get; +use frame_support::{crypto::ecdsa::ECDSAExt, traits::Get}; pub use pallet::*; @@ -71,19 +71,16 @@ where pub struct BeefyEcdsaToEthereum; impl Convert> for BeefyEcdsaToEthereum { fn convert(a: beefy_primitives::crypto::AuthorityId) -> Vec { - use k256::{elliptic_curve::sec1::ToEncodedPoint, PublicKey}; - use sp_core::crypto::ByteArray; - - PublicKey::from_sec1_bytes(a.as_slice()) - .map(|pub_key| { - // uncompress the key - let uncompressed = pub_key.to_encoded_point(false); - // convert to ETH address - sp_io::hashing::keccak_256(&uncompressed.as_bytes()[1..])[12..].to_vec() - }) + sp_core::ecdsa::Public::try_from(a.as_ref()) .map_err(|_| { log::error!(target: "runtime::beefy", "Invalid BEEFY PublicKey format!"); }) + .unwrap_or(sp_core::ecdsa::Public::from_raw([0u8; 33])) + .to_eth_address() + .map(|v| v.to_vec()) + .map_err(|_| { + log::error!(target: "runtime::beefy", "Failed to convert BEEFY PublicKey to ETH address!"); + }) .unwrap_or_default() } } diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index de83f51a01528..e67436fbc9d37 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1963,6 +1963,44 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + // Only calling the function itself for the list of + // generated different ECDSA keys. + seal_ecdsa_to_eth_address { + let r in 0 .. API_BENCHMARK_BATCHES; + let key_type = sp_core::crypto::KeyTypeId(*b"code"); + let pub_keys_bytes = (0..r * API_BENCHMARK_BATCH_SIZE) + .map(|_| { + sp_io::crypto::ecdsa_generate(key_type, None).0 + }) + .flatten() + .collect::>(); + let pub_keys_bytes_len = pub_keys_bytes.len() as i32; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "__unstable__", + name: "seal_ecdsa_to_eth_address", + params: vec![ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: pub_keys_bytes, + }, + ], + call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, 33), // pub_key_ptr + Regular(Instruction::I32Const(pub_keys_bytes_len)), // out_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + seal_set_code_hash { let r in 0 .. API_BENCHMARK_BATCHES; let code_hashes = (0..r * API_BENCHMARK_BATCH_SIZE) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 7aa5c0b731fad..54a5223b53d21 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -22,6 +22,7 @@ use crate::{ Pallet as Contracts, Schedule, }; use frame_support::{ + crypto::ecdsa::ECDSAExt, dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable}, storage::{with_transaction, TransactionOutcome}, traits::{Contains, Currency, ExistenceRequirement, OriginTrait, Randomness, Time}, @@ -30,7 +31,7 @@ use frame_support::{ use frame_system::RawOrigin; use pallet_contracts_primitives::ExecReturnValue; use smallvec::{Array, SmallVec}; -use sp_core::crypto::UncheckedFrom; +use sp_core::{crypto::UncheckedFrom, ecdsa::Public as ECDSAPublic}; use sp_io::crypto::secp256k1_ecdsa_recover_compressed; use sp_runtime::traits::Convert; use sp_std::{marker::PhantomData, mem, prelude::*}; @@ -232,6 +233,9 @@ pub trait Ext: sealing::Sealed { /// Recovers ECDSA compressed public key based on signature and message hash. fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()>; + /// Returns Ethereum address from the ECDSA compressed public key. + fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()>; + /// Tests sometimes need to modify and inspect the contract info directly. #[cfg(test)] fn contract_info(&mut self) -> &mut ContractInfo; @@ -1204,6 +1208,10 @@ where secp256k1_ecdsa_recover_compressed(&signature, &message_hash).map_err(|_| ()) } + fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()> { + ECDSAPublic(*pk).to_eth_address() + } + #[cfg(test)] fn contract_info(&mut self) -> &mut ContractInfo { self.top_frame_mut().contract_info() @@ -1267,6 +1275,7 @@ mod tests { use codec::{Decode, Encode}; use frame_support::{assert_err, assert_ok}; use frame_system::{EventRecord, Phase}; + use hex_literal::hex; use pallet_contracts_primitives::ReturnFlags; use pretty_assertions::assert_eq; use sp_core::Bytes; @@ -2718,4 +2727,36 @@ mod tests { )); }); } + #[test] + fn ecdsa_to_eth_address_returns_proper_value() { + let bob_ch = MockLoader::insert(Call, |ctx, _| { + let pubkey_compressed: [u8; 33] = + hex!("028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd91")[..] + .try_into() + .unwrap(); + assert_eq!( + ctx.ext.ecdsa_to_eth_address(&pubkey_compressed).unwrap(), + hex!("09231da7b19A016f9e576d23B16277062F4d46A8")[..] + ); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&BOB, bob_ch); + + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let result = MockStack::run_call( + ALICE, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + 0, + vec![], + None, + ); + assert_matches!(result, Ok(_)); + }); + } } diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index b0c58d721d578..bbbeb72f2cdf6 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -418,6 +418,9 @@ pub struct HostFnWeights { /// Weight of calling `seal_ecdsa_recover`. pub ecdsa_recover: Weight, + /// Weight of calling `seal_ecdsa_to_eth_address`. + pub ecdsa_to_eth_address: Weight, + /// The type parameter is used in the default implementation. #[codec(skip)] pub _phantom: PhantomData, @@ -653,6 +656,7 @@ impl Default for HostFnWeights { hash_blake2_128: cost_batched!(seal_hash_blake2_128), hash_blake2_128_per_byte: cost_byte_batched!(seal_hash_blake2_128_per_kb), ecdsa_recover: cost_batched!(seal_ecdsa_recover), + ecdsa_to_eth_address: cost_batched!(seal_ecdsa_to_eth_address), _phantom: PhantomData, } } diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index c38613cb68102..f12df06b938c8 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -503,6 +503,9 @@ mod tests { fn contract_info(&mut self) -> &mut crate::ContractInfo { unimplemented!() } + fn ecdsa_to_eth_address(&self, _pk: &[u8; 33]) -> Result<[u8; 20], ()> { + Ok([2u8; 20]) + } } fn execute>(wat: &str, input_data: Vec, mut ext: E) -> ExecResult { @@ -1085,6 +1088,42 @@ mod tests { assert_eq!(mock_ext.ecdsa_recover.into_inner(), [([1; 65], [1; 32])]); } + #[test] + #[cfg(feature = "unstable-interface")] + fn contract_ecdsa_to_eth_address() { + /// calls `seal_ecdsa_to_eth_address` for the contstant and ensures the result equals the + /// expected one. + const CODE_ECDSA_TO_ETH_ADDRESS: &str = r#" +(module + (import "__unstable__" "seal_ecdsa_to_eth_address" (func $seal_ecdsa_to_eth_address (param i32 i32) (result i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func (export "call") + ;; fill the buffer with the eth address. + (call $seal_ecdsa_to_eth_address (i32.const 0) (i32.const 0)) + + ;; Return the contents of the buffer + (call $seal_return + (i32.const 0) + (i32.const 0) + (i32.const 20) + ) + + ;; seal_return doesn't return, so this is effectively unreachable. + (unreachable) + ) + (func (export "deploy")) +) +"#; + + let output = execute(CODE_ECDSA_TO_ETH_ADDRESS, vec![], MockExt::default()).unwrap(); + assert_eq!( + output, + ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes([0x02; 20].to_vec()) } + ); + } + const CODE_GET_STORAGE: &str = r#" (module (import "seal0" "seal_get_storage" (func $seal_get_storage (param i32 i32 i32) (result i32))) diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 975cfcdd12db8..876bd8848b0b1 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -71,7 +71,9 @@ pub enum ReturnCode { /// The call dispatched by `seal_call_runtime` was executed but returned an error. #[cfg(feature = "unstable-interface")] CallRuntimeReturnedError = 10, - /// ECDSA pubkey recovery failed. Most probably wrong recovery id or signature. + /// ECDSA pubkey recovery failed (most probably wrong recovery id or signature), or + /// ECDSA compressed pubkey conversion into Ethereum address failed (most probably + /// wrong pubkey provided). #[cfg(feature = "unstable-interface")] EcdsaRecoverFailed = 11, } @@ -225,6 +227,9 @@ pub enum RuntimeCosts { /// Weight of calling `seal_set_code_hash` #[cfg(feature = "unstable-interface")] SetCodeHash, + /// Weight of calling `ecdsa_to_eth_address` + #[cfg(feature = "unstable-interface")] + EcdsaToEthAddress, } impl RuntimeCosts { @@ -309,6 +314,8 @@ impl RuntimeCosts { CallRuntime(weight) => weight, #[cfg(feature = "unstable-interface")] SetCodeHash => s.set_code_hash, + #[cfg(feature = "unstable-interface")] + EcdsaToEthAddress => s.ecdsa_to_eth_address, }; RuntimeToken { #[cfg(test)] @@ -1984,12 +1991,12 @@ define_env!(Env, , // # Parameters // // - `signature_ptr`: the pointer into the linear memory where the signature - // is placed. Should be decodable as a 65 bytes. Traps otherwise. + // is placed. Should be decodable as a 65 bytes. Traps otherwise. // - `message_hash_ptr`: the pointer into the linear memory where the message // hash is placed. Should be decodable as a 32 bytes. Traps otherwise. // - `output_ptr`: the pointer into the linear memory where the output - // data is placed. The buffer should be 33 bytes. Traps otherwise. - // The function will write the result directly into this buffer. + // data is placed. The buffer should be 33 bytes. The function + // will write the result directly into this buffer. // // # Errors // @@ -2036,7 +2043,7 @@ define_env!(Env, , // // # Parameters // - // - code_hash_ptr: A pointer to the buffer that contains the new code hash. + // - `code_hash_ptr`: A pointer to the buffer that contains the new code hash. // // # Errors // @@ -2052,4 +2059,35 @@ define_env!(Env, , Ok(()) => Ok(ReturnCode::Success) } }, + + // Calculates Ethereum address from the ECDSA compressed public key and stores + // it into the supplied buffer. + // + // # Parameters + // + // - `key_ptr`: a pointer to the ECDSA compressed public key. Should be decodable as a 33 bytes value. + // Traps otherwise. + // - `out_ptr`: the pointer into the linear memory where the output + // data is placed. The function will write the result + // directly into this buffer. + // + // The value is stored to linear memory at the address pointed to by `out_ptr`. + // If the available space at `out_ptr` is less than the size of the value a trap is triggered. + // + // # Errors + // + // `ReturnCode::EcdsaRecoverFailed` + [__unstable__] seal_ecdsa_to_eth_address(ctx, key_ptr: u32, out_ptr: u32) -> ReturnCode => { + ctx.charge_gas(RuntimeCosts::EcdsaToEthAddress)?; + let mut compressed_key: [u8; 33] = [0;33]; + ctx.read_sandbox_memory_into_buf(key_ptr, &mut compressed_key)?; + let result = ctx.ext.ecdsa_to_eth_address(&compressed_key); + match result { + Ok(eth_address) => { + ctx.write_sandbox_memory(out_ptr, eth_address.as_ref())?; + Ok(ReturnCode::Success) + }, + Err(_) => Ok(ReturnCode::EcdsaRecoverFailed), + } + }, ); diff --git a/frame/contracts/src/weights.rs b/frame/contracts/src/weights.rs index 43f00196ab3b2..85ff2548ca698 100644 --- a/frame/contracts/src/weights.rs +++ b/frame/contracts/src/weights.rs @@ -103,6 +103,7 @@ pub trait WeightInfo { fn seal_hash_blake2_128(r: u32, ) -> Weight; fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight; fn seal_ecdsa_recover(r: u32, ) -> Weight; + fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight; fn seal_set_code_hash(r: u32, ) -> Weight; fn instr_i64const(r: u32, ) -> Weight; fn instr_i64load(r: u32, ) -> Weight; @@ -805,6 +806,17 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { + (272_893_000 as Weight) + // Standard Error: 1_438_000 + .saturating_add((15_412_877_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) // Storage: Contracts OwnerInfoOf (r:36 w:36) fn seal_set_code_hash(r: u32, ) -> Weight { (0 as Weight) @@ -1717,6 +1729,17 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { + (272_893_000 as Weight) + // Standard Error: 1_438_000 + .saturating_add((15_412_877_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) // Storage: Contracts OwnerInfoOf (r:36 w:36) fn seal_set_code_hash(r: u32, ) -> Weight { (0 as Weight) diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index ec596494c601b..ce04fddb8521f 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -35,6 +35,7 @@ impl-trait-for-tuples = "0.2.2" smallvec = "1.8.0" log = { version = "0.4.14", default-features = false } sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/hashing/proc-macro" } +k256 = { version = "0.10.2", default-features = false, features = ["ecdsa"] } [dev-dependencies] assert_matches = "1.3.0" diff --git a/frame/support/src/crypto.rs b/frame/support/src/crypto.rs new file mode 100644 index 0000000000000..182a784649d02 --- /dev/null +++ b/frame/support/src/crypto.rs @@ -0,0 +1,21 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +//! Utilities for dealing with crypto primitives. Sometimes we need to use these from inside WASM +//! contracts, where crypto calculations have weak performance. + +pub mod ecdsa; diff --git a/frame/support/src/crypto/ecdsa.rs b/frame/support/src/crypto/ecdsa.rs new file mode 100644 index 0000000000000..a4a04acabe1df --- /dev/null +++ b/frame/support/src/crypto/ecdsa.rs @@ -0,0 +1,49 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) 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. + +//! Simple ECDSA secp256k1 API. +//! +//! Provides an extension trait for [`sp_core::ecdsa::Public`] to do certain operations. + +use sp_core::{crypto::ByteArray, ecdsa::Public}; + +/// Extension trait for [`Public`] to be used from inside the runtime. +/// +/// # Note +/// +/// This is needed because host functions cannot be called from within +/// `sp_core` due to cyclic dependencies on `sp_io`. +pub trait ECDSAExt { + /// Returns Ethereum address calculated from this ECDSA public key. + fn to_eth_address(&self) -> Result<[u8; 20], ()>; +} + +impl ECDSAExt for Public { + fn to_eth_address(&self) -> Result<[u8; 20], ()> { + use k256::{elliptic_curve::sec1::ToEncodedPoint, PublicKey}; + + PublicKey::from_sec1_bytes(self.as_slice()).map_err(drop).and_then(|pub_key| { + // uncompress the key + let uncompressed = pub_key.to_encoded_point(false); + // convert to ETH address + <[u8; 20]>::try_from( + sp_io::hashing::keccak_256(&uncompressed.as_bytes()[1..])[12..].as_ref(), + ) + .map_err(drop) + }) + } +} diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 714449eec7847..4484ff6b6b295 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -64,6 +64,7 @@ pub mod event; pub mod inherent; #[macro_use] pub mod error; +pub mod crypto; pub mod instances; pub mod migrations; pub mod traits;