Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Add frame_support::crypto::ecdsa::Public.to_eth_address() (k256-based) and use it in pallets #11087

Merged
merged 33 commits into from
Apr 16, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4c5f9fe
`ecdsa::Public::to_eth_address` + test, beefy-mmr `convert()` to use …
agryaznov Mar 17, 2022
414c40e
`seal_ecdsa_to_eth_address` all but benchmark done
agryaznov Mar 21, 2022
a7453f4
`seal_ecdsa_to_eth_address` + wasm test
agryaznov Mar 21, 2022
7e040fd
`seal_ecdsa_to_eth_address` + benchmark
agryaznov Mar 21, 2022
f843bc8
Merge branch 'master' into ag-seal-to-eth-addr
agryaznov Mar 22, 2022
d3090c7
fixed dependencies
agryaznov Mar 22, 2022
869cf84
Apply suggestions from code review
agryaznov Mar 22, 2022
c4b6a29
fixes from review #1
agryaznov Mar 22, 2022
3dbda9f
Merge branch 'master' into ag-seal-to-eth-addr
agryaznov Mar 30, 2022
f5feb46
ecdsa::Public(*pk).to_eth_address() moved to frame_support and contra…
agryaznov Mar 30, 2022
b801972
beefy-mmr to use newly added frame_support function for convertion
agryaznov Mar 30, 2022
be3e8a9
a doc fix
agryaznov Mar 30, 2022
4037170
import fix
agryaznov Mar 30, 2022
c8ff0ef
benchmark fix-1 (still fails)
agryaznov Mar 30, 2022
976ed7e
benchmark fixed
agryaznov Mar 31, 2022
a26858b
Merge branch 'master' into ag-seal-to-eth-addr
agryaznov Mar 31, 2022
ec8d860
Apply suggestions from code review
agryaznov Apr 8, 2022
b5be45d
fixes on Alex T feedback
agryaznov Apr 8, 2022
2ff93cb
Merge branch 'master' into ag-seal-to-eth-addr
agryaznov Apr 8, 2022
877b57e
to_eth_address() put into extension trait for sp-core::ecdsa::Public
agryaznov Apr 8, 2022
b1ee31f
Update frame/support/src/crypto/ecdsa.rs
agryaznov Apr 11, 2022
16fde71
Update frame/contracts/src/wasm/mod.rs
agryaznov Apr 11, 2022
cdd6bdb
fixes on issues pointed out in review
agryaznov Apr 11, 2022
b51a7b0
benchmark errors fixed
agryaznov Apr 11, 2022
fb1fcc4
Merge branch 'master' into ag-seal-to-eth-addr
agryaznov Apr 11, 2022
41ba268
fmt fix
agryaznov Apr 11, 2022
34779f8
EcdsaRecoverFailed err docs updated
agryaznov Apr 11, 2022
10af685
Merge branch 'master' into ag-seal-to-eth-addr
agryaznov Apr 12, 2022
7c5fa91
Apply suggestions from code review
agryaznov Apr 14, 2022
ec3a2af
make applied suggestions compile
agryaznov Apr 14, 2022
dffce61
get rid of unwrap() in runtime
agryaznov Apr 14, 2022
55c2d06
Merge branch 'master' into ag-seal-to-eth-addr
agryaznov Apr 14, 2022
2997b7e
Remove expect
bkchr Apr 16, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 3 additions & 13 deletions frame/beefy-mmr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,9 @@ where
pub struct BeefyEcdsaToEthereum;
impl Convert<beefy_primitives::crypto::AuthorityId, Vec<u8>> for BeefyEcdsaToEthereum {
fn convert(a: beefy_primitives::crypto::AuthorityId) -> Vec<u8> {
use k256::{elliptic_curve::sec1::ToEncodedPoint, PublicKey};
athei marked this conversation as resolved.
Show resolved Hide resolved
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()
})
.map_err(|_| {
log::error!(target: "runtime::beefy", "Invalid BEEFY PublicKey format!");
})
sp_core::ecdsa::Public::from(a)
.to_eth_address()
.map(|v| v.to_vec())
agryaznov marked this conversation as resolved.
Show resolved Hide resolved
.unwrap_or_default()
}
}
Expand Down
38 changes: 38 additions & 0 deletions frame/contracts/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1910,6 +1910,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 = (0..r * API_BENCHMARK_BATCH_SIZE)
.map(|_| {
sp_io::crypto::ecdsa_generate(key_type, None).encode()
})
.collect::<Vec<_>>();
let pub_keys_bytes = pub_keys.iter().map(|a| a.encode()).flatten().collect::<Vec<_>>();
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "__unstable__",
name: "seal_ecdsa_to_eth_address",
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
return_type: None,
}],
data_segments: vec![
DataSegment {
offset: 24,
value: pub_keys_bytes,
},
],
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
Counter(20, 33), // pub_key_ptr
Regular(Instruction::I32Const(0)), // out_ptr
Regular(Instruction::I32Const(4)), // out_len_ptr
Regular(Instruction::Call(0)),
Regular(Instruction::Drop),
])),
.. Default::default()
});
let instance = Contract::<T>::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)
Expand Down
42 changes: 41 additions & 1 deletion frame/contracts/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,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};
use sp_io::crypto::secp256k1_ecdsa_recover_compressed;
use sp_runtime::traits::Convert;
use sp_std::{marker::PhantomData, mem, prelude::*};
Expand Down Expand Up @@ -224,6 +224,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<Self::T>;
Expand Down Expand Up @@ -1175,6 +1178,10 @@ where
secp256k1_ecdsa_recover_compressed(&signature, &message_hash).map_err(|_| ())
}

fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()> {
ecdsa::Public(*pk).to_eth_address()
}

#[cfg(test)]
fn contract_info(&mut self) -> &mut ContractInfo<Self::T> {
self.top_frame_mut().contract_info()
Expand Down Expand Up @@ -1238,6 +1245,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;
Expand Down Expand Up @@ -2633,4 +2641,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 = <Test as Config>::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::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![],
None,
);
assert_matches!(result, Ok(_));
});
}
}
4 changes: 4 additions & 0 deletions frame/contracts/src/schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,9 @@ pub struct HostFnWeights<T: Config> {
/// 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<T>,
Expand Down Expand Up @@ -645,6 +648,7 @@ impl<T: Config> Default for HostFnWeights<T> {
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,
}
}
Expand Down
51 changes: 51 additions & 0 deletions frame/contracts/src/wasm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,9 @@ mod tests {
fn contract_info(&mut self) -> &mut crate::ContractInfo<Self::T> {
unimplemented!()
}
fn ecdsa_to_eth_address(&self, _pk: &[u8; 33]) -> Result<[u8; 20], ()> {
Ok([0u8; 20])
athei marked this conversation as resolved.
Show resolved Hide resolved
}
}

fn execute<E: BorrowMut<MockExt>>(wat: &str, input_data: Vec<u8>, mut ext: E) -> ExecResult {
Expand Down Expand Up @@ -1078,6 +1081,54 @@ 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 i32)))
(import "env" "memory" (memory 1 1))

;; size of our buffer is 20 bytes
(data (i32.const 20) "\20")

(func $assert (param i32)
(block $ok
(br_if $ok
(get_local 0)
)
(unreachable)
)
)

(func (export "call")
;; fill the buffer with the eth address.
(call $seal_ecdsa_to_eth_address (i32.const 0) (i32.const 0) (i32.const 20))

;; assert out_len == 20
(call $assert
(i32.eq
(i32.load (i32.const 20))
(i32.const 20)
)
)
;; assert that the mock returned 20 zero-bytes
(call $assert
(i64.eq
(i64.load (i32.const 0))
(i64.const 0x000000000000000000000000000000000000000)
)
athei marked this conversation as resolved.
Show resolved Hide resolved
)
)
(func (export "deploy"))
)
"#;

assert_ok!(execute(CODE_ECDSA_TO_ETH_ADDRESS, vec![], MockExt::default()));
}

const CODE_GET_STORAGE: &str = r#"
(module
(import "seal0" "seal_get_storage" (func $seal_get_storage (param i32 i32 i32) (result i32)))
Expand Down
24 changes: 24 additions & 0 deletions frame/contracts/src/wasm/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,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 {
Expand Down Expand Up @@ -299,6 +302,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)]
Expand Down Expand Up @@ -2004,4 +2009,23 @@ define_env!(Env, <E: Ext>,
Ok(()) => Ok(ReturnCode::Success)
}
},

// Calculates Ethereum address from the ECDSA compressed public key and stores
// it into the supplied buffer
agryaznov marked this conversation as resolved.
Show resolved Hide resolved
//
// # Parameters
//
// - key_ptr: a pointer to the ECDSA compressed public key
//
// The value is stored to linear memory at the address pointed to by `out_ptr`.
// `out_len_ptr` must point to a u32 value that describes the available space at
// `out_ptr`. This call overwrites it with the size of the value. If the available
// space at `out_ptr` is less than the size of the value a trap is triggered.
[__unstable__] seal_ecdsa_to_eth_address(ctx, key_ptr: u32, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeCosts::EcdsaToEthAddress)?;
let compressed_key: [u8; 33] =
ctx.read_sandbox_memory_as(key_ptr)?;
let result = ctx.ext.ecdsa_to_eth_address(&compressed_key).unwrap();
Ok(ctx.write_sandbox_output(out_ptr, out_len_ptr, &result, false, already_charged)?)
},
);
23 changes: 23 additions & 0 deletions frame/contracts/src/weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,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;
Expand Down Expand Up @@ -782,6 +783,17 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// 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)
Expand Down Expand Up @@ -1673,6 +1685,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)
Expand Down
4 changes: 2 additions & 2 deletions primitives/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ futures = { version = "0.3.19", optional = true }
dyn-clonable = { version = "0.9.0", optional = true }
thiserror = { version = "1.0.30", optional = true }
bitflags = "1.3"
k256 = { version = "0.10.2", default-features = false, features = ["ecdsa"] }
sp-core-hashing = { version = "4.0.0", default-features = false, path = "./hashing" }
athei marked this conversation as resolved.
Show resolved Hide resolved

# full crypto
ed25519-dalek = { version = "1.0.1", default-features = false, features = ["u64_backend", "alloc"], optional = true }
Expand All @@ -58,7 +60,6 @@ libsecp256k1 = { version = "0.7", default-features = false, features = ["static-
merlin = { version = "2.0", default-features = false, optional = true }
secp256k1 = { version = "0.21.2", default-features = false, features = ["recovery", "alloc"], optional = true }
ss58-registry = { version = "1.11.0", default-features = false }
sp-core-hashing = { version = "4.0.0", path = "./hashing", default-features = false, optional = true }
sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../runtime-interface" }

[dev-dependencies]
Expand Down Expand Up @@ -133,7 +134,6 @@ full_crypto = [
"hex",
"libsecp256k1",
"secp256k1",
"sp-core-hashing",
"sp-runtime-interface/disable_target_static_assertions",
"merlin",
]
Loading