Skip to content

Commit b261ccd

Browse files
agryaznovatheibkchr
authored andcommitted
Add frame_support::crypto::ecdsa::Public.to_eth_address() (k256-based) and use it in pallets (paritytech#11087)
* `ecdsa::Public::to_eth_address` + test, beefy-mmr `convert()` to use it, contracts Ext interface * `seal_ecdsa_to_eth_address` all but benchmark done * `seal_ecdsa_to_eth_address` + wasm test * `seal_ecdsa_to_eth_address` + benchmark * fixed dependencies * Apply suggestions from code review Co-authored-by: Alexander Theißen <[email protected]> * fixes from review #1 * ecdsa::Public(*pk).to_eth_address() moved to frame_support and contracts to use it * beefy-mmr to use newly added frame_support function for convertion * a doc fix * import fix * benchmark fix-1 (still fails) * benchmark fixed * Apply suggestions from code review Co-authored-by: Alexander Theißen <[email protected]> * fixes on Alex T feedback * to_eth_address() put into extension trait for sp-core::ecdsa::Public * Update frame/support/src/crypto/ecdsa.rs Co-authored-by: Alexander Theißen <[email protected]> * Update frame/contracts/src/wasm/mod.rs Co-authored-by: Alexander Theißen <[email protected]> * fixes on issues pointed out in review * benchmark errors fixed * fmt fix * EcdsaRecoverFailed err docs updated * Apply suggestions from code review Co-authored-by: Bastian Köcher <[email protected]> * make applied suggestions compile * get rid of unwrap() in runtime * Remove expect Co-authored-by: Alexander Theißen <[email protected]> Co-authored-by: Bastian Köcher <[email protected]> Co-authored-by: Bastian Köcher <[email protected]>
1 parent e58860f commit b261ccd

File tree

13 files changed

+283
-48
lines changed

13 files changed

+283
-48
lines changed

Cargo.lock

Lines changed: 14 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frame/beefy-mmr/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ repository = "https://github.com/paritytech/substrate"
1010
[dependencies]
1111
hex = { version = "0.4", optional = true }
1212
codec = { version = "3.0.0", package = "parity-scale-codec", default-features = false, features = ["derive"] }
13-
k256 = { version = "0.10.2", default-features = false, features = ["arithmetic"] }
1413
log = { version = "0.4.13", default-features = false }
1514
scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
1615
serde = { version = "1.0.136", optional = true }
@@ -42,7 +41,6 @@ std = [
4241
"frame-support/std",
4342
"frame-system/std",
4443
"hex",
45-
"k256/std",
4644
"log/std",
4745
"pallet-beefy/std",
4846
"pallet-mmr/std",

frame/beefy-mmr/src/lib.rs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use sp_std::prelude::*;
3939
use beefy_primitives::mmr::{BeefyDataProvider, BeefyNextAuthoritySet, MmrLeaf, MmrLeafVersion};
4040
use pallet_mmr::{LeafDataProvider, ParentNumberAndHash};
4141

42-
use frame_support::traits::Get;
42+
use frame_support::{crypto::ecdsa::ECDSAExt, traits::Get};
4343

4444
pub use pallet::*;
4545

@@ -71,19 +71,16 @@ where
7171
pub struct BeefyEcdsaToEthereum;
7272
impl Convert<beefy_primitives::crypto::AuthorityId, Vec<u8>> for BeefyEcdsaToEthereum {
7373
fn convert(a: beefy_primitives::crypto::AuthorityId) -> Vec<u8> {
74-
use k256::{elliptic_curve::sec1::ToEncodedPoint, PublicKey};
75-
use sp_core::crypto::ByteArray;
76-
77-
PublicKey::from_sec1_bytes(a.as_slice())
78-
.map(|pub_key| {
79-
// uncompress the key
80-
let uncompressed = pub_key.to_encoded_point(false);
81-
// convert to ETH address
82-
sp_io::hashing::keccak_256(&uncompressed.as_bytes()[1..])[12..].to_vec()
83-
})
74+
sp_core::ecdsa::Public::try_from(a.as_ref())
8475
.map_err(|_| {
8576
log::error!(target: "runtime::beefy", "Invalid BEEFY PublicKey format!");
8677
})
78+
.unwrap_or(sp_core::ecdsa::Public::from_raw([0u8; 33]))
79+
.to_eth_address()
80+
.map(|v| v.to_vec())
81+
.map_err(|_| {
82+
log::error!(target: "runtime::beefy", "Failed to convert BEEFY PublicKey to ETH address!");
83+
})
8784
.unwrap_or_default()
8885
}
8986
}

frame/contracts/src/benchmarking/mod.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1963,6 +1963,44 @@ benchmarks! {
19631963
let origin = RawOrigin::Signed(instance.caller.clone());
19641964
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
19651965

1966+
// Only calling the function itself for the list of
1967+
// generated different ECDSA keys.
1968+
seal_ecdsa_to_eth_address {
1969+
let r in 0 .. API_BENCHMARK_BATCHES;
1970+
let key_type = sp_core::crypto::KeyTypeId(*b"code");
1971+
let pub_keys_bytes = (0..r * API_BENCHMARK_BATCH_SIZE)
1972+
.map(|_| {
1973+
sp_io::crypto::ecdsa_generate(key_type, None).0
1974+
})
1975+
.flatten()
1976+
.collect::<Vec<_>>();
1977+
let pub_keys_bytes_len = pub_keys_bytes.len() as i32;
1978+
let code = WasmModule::<T>::from(ModuleDefinition {
1979+
memory: Some(ImportedMemory::max::<T>()),
1980+
imported_functions: vec![ImportedFunction {
1981+
module: "__unstable__",
1982+
name: "seal_ecdsa_to_eth_address",
1983+
params: vec![ValueType::I32, ValueType::I32],
1984+
return_type: Some(ValueType::I32),
1985+
}],
1986+
data_segments: vec![
1987+
DataSegment {
1988+
offset: 0,
1989+
value: pub_keys_bytes,
1990+
},
1991+
],
1992+
call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
1993+
Counter(0, 33), // pub_key_ptr
1994+
Regular(Instruction::I32Const(pub_keys_bytes_len)), // out_ptr
1995+
Regular(Instruction::Call(0)),
1996+
Regular(Instruction::Drop),
1997+
])),
1998+
.. Default::default()
1999+
});
2000+
let instance = Contract::<T>::new(code, vec![])?;
2001+
let origin = RawOrigin::Signed(instance.caller.clone());
2002+
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
2003+
19662004
seal_set_code_hash {
19672005
let r in 0 .. API_BENCHMARK_BATCHES;
19682006
let code_hashes = (0..r * API_BENCHMARK_BATCH_SIZE)

frame/contracts/src/exec.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use crate::{
2222
Pallet as Contracts, Schedule,
2323
};
2424
use frame_support::{
25+
crypto::ecdsa::ECDSAExt,
2526
dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable},
2627
storage::{with_transaction, TransactionOutcome},
2728
traits::{Contains, Currency, ExistenceRequirement, OriginTrait, Randomness, Time},
@@ -30,7 +31,7 @@ use frame_support::{
3031
use frame_system::RawOrigin;
3132
use pallet_contracts_primitives::ExecReturnValue;
3233
use smallvec::{Array, SmallVec};
33-
use sp_core::crypto::UncheckedFrom;
34+
use sp_core::{crypto::UncheckedFrom, ecdsa::Public as ECDSAPublic};
3435
use sp_io::crypto::secp256k1_ecdsa_recover_compressed;
3536
use sp_runtime::traits::Convert;
3637
use sp_std::{marker::PhantomData, mem, prelude::*};
@@ -232,6 +233,9 @@ pub trait Ext: sealing::Sealed {
232233
/// Recovers ECDSA compressed public key based on signature and message hash.
233234
fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()>;
234235

236+
/// Returns Ethereum address from the ECDSA compressed public key.
237+
fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()>;
238+
235239
/// Tests sometimes need to modify and inspect the contract info directly.
236240
#[cfg(test)]
237241
fn contract_info(&mut self) -> &mut ContractInfo<Self::T>;
@@ -1204,6 +1208,10 @@ where
12041208
secp256k1_ecdsa_recover_compressed(&signature, &message_hash).map_err(|_| ())
12051209
}
12061210

1211+
fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()> {
1212+
ECDSAPublic(*pk).to_eth_address()
1213+
}
1214+
12071215
#[cfg(test)]
12081216
fn contract_info(&mut self) -> &mut ContractInfo<Self::T> {
12091217
self.top_frame_mut().contract_info()
@@ -1267,6 +1275,7 @@ mod tests {
12671275
use codec::{Decode, Encode};
12681276
use frame_support::{assert_err, assert_ok};
12691277
use frame_system::{EventRecord, Phase};
1278+
use hex_literal::hex;
12701279
use pallet_contracts_primitives::ReturnFlags;
12711280
use pretty_assertions::assert_eq;
12721281
use sp_core::Bytes;
@@ -2718,4 +2727,36 @@ mod tests {
27182727
));
27192728
});
27202729
}
2730+
#[test]
2731+
fn ecdsa_to_eth_address_returns_proper_value() {
2732+
let bob_ch = MockLoader::insert(Call, |ctx, _| {
2733+
let pubkey_compressed: [u8; 33] =
2734+
hex!("028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd91")[..]
2735+
.try_into()
2736+
.unwrap();
2737+
assert_eq!(
2738+
ctx.ext.ecdsa_to_eth_address(&pubkey_compressed).unwrap(),
2739+
hex!("09231da7b19A016f9e576d23B16277062F4d46A8")[..]
2740+
);
2741+
exec_success()
2742+
});
2743+
2744+
ExtBuilder::default().build().execute_with(|| {
2745+
let schedule = <Test as Config>::Schedule::get();
2746+
place_contract(&BOB, bob_ch);
2747+
2748+
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap();
2749+
let result = MockStack::run_call(
2750+
ALICE,
2751+
BOB,
2752+
&mut GasMeter::<Test>::new(GAS_LIMIT),
2753+
&mut storage_meter,
2754+
&schedule,
2755+
0,
2756+
vec![],
2757+
None,
2758+
);
2759+
assert_matches!(result, Ok(_));
2760+
});
2761+
}
27212762
}

frame/contracts/src/schedule.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,9 @@ pub struct HostFnWeights<T: Config> {
418418
/// Weight of calling `seal_ecdsa_recover`.
419419
pub ecdsa_recover: Weight,
420420

421+
/// Weight of calling `seal_ecdsa_to_eth_address`.
422+
pub ecdsa_to_eth_address: Weight,
423+
421424
/// The type parameter is used in the default implementation.
422425
#[codec(skip)]
423426
pub _phantom: PhantomData<T>,
@@ -653,6 +656,7 @@ impl<T: Config> Default for HostFnWeights<T> {
653656
hash_blake2_128: cost_batched!(seal_hash_blake2_128),
654657
hash_blake2_128_per_byte: cost_byte_batched!(seal_hash_blake2_128_per_kb),
655658
ecdsa_recover: cost_batched!(seal_ecdsa_recover),
659+
ecdsa_to_eth_address: cost_batched!(seal_ecdsa_to_eth_address),
656660
_phantom: PhantomData,
657661
}
658662
}

frame/contracts/src/wasm/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,9 @@ mod tests {
503503
fn contract_info(&mut self) -> &mut crate::ContractInfo<Self::T> {
504504
unimplemented!()
505505
}
506+
fn ecdsa_to_eth_address(&self, _pk: &[u8; 33]) -> Result<[u8; 20], ()> {
507+
Ok([2u8; 20])
508+
}
506509
}
507510

508511
fn execute<E: BorrowMut<MockExt>>(wat: &str, input_data: Vec<u8>, mut ext: E) -> ExecResult {
@@ -1085,6 +1088,42 @@ mod tests {
10851088
assert_eq!(mock_ext.ecdsa_recover.into_inner(), [([1; 65], [1; 32])]);
10861089
}
10871090

1091+
#[test]
1092+
#[cfg(feature = "unstable-interface")]
1093+
fn contract_ecdsa_to_eth_address() {
1094+
/// calls `seal_ecdsa_to_eth_address` for the contstant and ensures the result equals the
1095+
/// expected one.
1096+
const CODE_ECDSA_TO_ETH_ADDRESS: &str = r#"
1097+
(module
1098+
(import "__unstable__" "seal_ecdsa_to_eth_address" (func $seal_ecdsa_to_eth_address (param i32 i32) (result i32)))
1099+
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
1100+
(import "env" "memory" (memory 1 1))
1101+
1102+
(func (export "call")
1103+
;; fill the buffer with the eth address.
1104+
(call $seal_ecdsa_to_eth_address (i32.const 0) (i32.const 0))
1105+
1106+
;; Return the contents of the buffer
1107+
(call $seal_return
1108+
(i32.const 0)
1109+
(i32.const 0)
1110+
(i32.const 20)
1111+
)
1112+
1113+
;; seal_return doesn't return, so this is effectively unreachable.
1114+
(unreachable)
1115+
)
1116+
(func (export "deploy"))
1117+
)
1118+
"#;
1119+
1120+
let output = execute(CODE_ECDSA_TO_ETH_ADDRESS, vec![], MockExt::default()).unwrap();
1121+
assert_eq!(
1122+
output,
1123+
ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes([0x02; 20].to_vec()) }
1124+
);
1125+
}
1126+
10881127
const CODE_GET_STORAGE: &str = r#"
10891128
(module
10901129
(import "seal0" "seal_get_storage" (func $seal_get_storage (param i32 i32 i32) (result i32)))

0 commit comments

Comments
 (0)