From b5990f2e45d18a0c9ace772eee44756047b3c367 Mon Sep 17 00:00:00 2001 From: Tomas Tauber <2410580+tomtau@users.noreply.github.com> Date: Tue, 17 Aug 2021 10:23:32 +0800 Subject: [PATCH 1/7] added Ethermint support (fixes #1267 #1071) - collected changes from https://github.com/informalsystems/ibc-rs/issues/1267#issuecomment-896459781 - EthAccount definition was directly pasted into the proto library (as different chains the same proto definition, but under a different package path) - added a new configuration option that allows specifying the address derivation as well as the proto type of public keys (e.g. "/injective.crypto.v1beta1.ethsecp256k1.PubKey" or "/ethermint.crypto.v1alpha1.ethsecp256k1.PubKey") --- .../features/1267-ethermint-support.md | 4 + Cargo.lock | 16 ++++ config.toml | 15 ++++ proto/src/lib.rs | 8 ++ relayer-cli/src/commands/keys/restore.rs | 2 +- relayer/Cargo.toml | 1 + relayer/src/chain/cosmos.rs | 69 +++++++++------ relayer/src/chain/mock.rs | 3 +- relayer/src/config.rs | 29 +++++++ relayer/src/error.rs | 10 +++ relayer/src/keyring.rs | 86 ++++++++++++++----- relayer/src/keyring/errors.rs | 4 + relayer/src/keyring/pub_key.rs | 6 +- .../config/fixtures/relayer_conf_example.toml | 3 +- 14 files changed, 202 insertions(+), 54 deletions(-) create mode 100644 .changelog/unreleased/features/1267-ethermint-support.md diff --git a/.changelog/unreleased/features/1267-ethermint-support.md b/.changelog/unreleased/features/1267-ethermint-support.md new file mode 100644 index 0000000000..c6a3641c3f --- /dev/null +++ b/.changelog/unreleased/features/1267-ethermint-support.md @@ -0,0 +1,4 @@ +- Added Ethermint support ([#1267] [#1071]) + +[#1267]: https://github.com/informalsystems/ibc-rs/issues/1267 +[#1071]: https://github.com/informalsystems/ibc-rs/issues/1071 diff --git a/Cargo.lock b/Cargo.lock index e110eaa065..8895f2e205 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -553,6 +553,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-bigint" version = "0.2.2" @@ -1422,6 +1428,7 @@ dependencies = [ "test-env-log", "thiserror", "tiny-bip39", + "tiny-keccak", "tokio", "toml", "tonic", @@ -3259,6 +3266,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tiny_http" version = "0.8.2" diff --git a/config.toml b/config.toml index 6e887b5740..e37626ab9e 100644 --- a/config.toml +++ b/config.toml @@ -131,6 +131,20 @@ trusting_period = '14days' # Warning: This is an advanced feature! Modify with caution. trust_threshold = { numerator = '1', denominator = '3' } +# Specify the address type which determines: +# 1) address derivation; +# 2) how to retrieve and decode accounts and pubkeys; +# 3) the message signing method. +# The current configuration options are for Cosmos SDK and Ethermint. +# +# Example configuration for Ethermint: +# +# address_type = { derivation = 'ethermint', proto_type = { pk_type = '/injective.crypto.v1beta1.ethsecp256k1.PubKey' } } +# +# Default: { derivation = 'cosmos' }, i.e. address derivation as in Cosmos SDK +# Warning: This is an advanced feature! Modify with caution. +address_type = { derivation = 'cosmos' } + # This section specifies the filters for policy based relaying. # Default: no policy/ filters # The section is ignored if the global 'filter' option is set to 'false'. @@ -168,3 +182,4 @@ max_tx_size = 2097152 clock_drift = '5s' trusting_period = '14days' trust_threshold = { numerator = '1', denominator = '3' } +address_type = { derivation = 'cosmos' } diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 62903f113a..6475d5869e 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -29,6 +29,14 @@ pub mod cosmos { pub mod auth { pub mod v1beta1 { include!("prost/cosmos.auth.v1beta1.rs"); + /// EthAccount defines an Ethermint account. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct EthAccount { + #[prost(message, optional, tag = "1")] + pub base_account: ::core::option::Option, + #[prost(bytes = "vec", tag = "2")] + pub code_hash: ::prost::alloc::vec::Vec, + } } } pub mod staking { diff --git a/relayer-cli/src/commands/keys/restore.rs b/relayer-cli/src/commands/keys/restore.rs index ae2f0208c2..12e08ee6c3 100644 --- a/relayer-cli/src/commands/keys/restore.rs +++ b/relayer-cli/src/commands/keys/restore.rs @@ -93,7 +93,7 @@ pub fn restore_key( config: &ChainConfig, ) -> Result> { let mut keyring = KeyRing::new(Store::Test, &config.account_prefix, &config.id)?; - let key_entry = keyring.key_from_mnemonic(mnemonic, hdpath)?; + let key_entry = keyring.key_from_mnemonic(mnemonic, hdpath, &config.address_type)?; keyring.add_key(key_name, key_entry.clone())?; Ok(key_entry) diff --git a/relayer/Cargo.toml b/relayer/Cargo.toml index 74de846e17..fb9954213e 100644 --- a/relayer/Cargo.toml +++ b/relayer/Cargo.toml @@ -49,6 +49,7 @@ bitcoin = { version = "=0.27", features = ["use-serde"] } tiny-bip39 = "0.8.0" hdpath = { version = "0.6.0", features = ["with-bitcoin"] } sha2 = "0.9.3" +tiny-keccak = { version = "2.0.2", features = ["keccak"], default-features = false } ripemd160 = "0.9.1" bech32 = "0.8.1" itertools = "0.10.1" diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index dabcef014a..e893fcffdc 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -50,7 +50,7 @@ use ibc::ics24_host::{ClientUpgradePath, Path, IBC_QUERY_PATH, SDK_UPGRADE_QUERY use ibc::query::{QueryTxHash, QueryTxRequest}; use ibc::signer::Signer; use ibc::Height as ICSHeight; -use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, QueryAccountRequest}; +use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, EthAccount, QueryAccountRequest}; use ibc_proto::cosmos::base::tendermint::v1beta1::service_client::ServiceClient; use ibc_proto::cosmos::base::tendermint::v1beta1::GetNodeInfoRequest; use ibc_proto::cosmos::base::v1beta1::Coin; @@ -71,7 +71,7 @@ use ibc_proto::ibc::core::connection::v1::{ QueryClientConnectionsRequest, QueryConnectionsRequest, }; -use crate::config::{ChainConfig, GasPrice}; +use crate::config::{AddressType, ChainConfig, GasPrice}; use crate::error::Error; use crate::event::monitor::{EventMonitor, EventReceiver}; use crate::keyring::{KeyEntry, KeyRing, Store}; @@ -523,15 +523,15 @@ impl CosmosSdkChain { fn account(&mut self) -> Result<&mut BaseAccount, Error> { if self.account == None { let account = self.block_on(query_account(self, self.key()?.account))?; - - debug!( - sequence = %account.sequence, - number = %account.account_number, - "[{}] send_tx: retrieved account", - self.id() - ); - - self.account = Some(account); + if let Some(acc) = account.as_ref() { + debug!( + sequence = %acc.sequence, + number = %acc.account_number, + "[{}] send_tx: retrieved account", + self.id() + ); + } + self.account = account; } Ok(self @@ -555,9 +555,13 @@ impl CosmosSdkChain { fn signer(&self, sequence: u64) -> Result { let (_key, pk_buf) = self.key_and_bytes()?; + let pk_type = match &self.config.address_type { + AddressType::Cosmos => "/cosmos.crypto.secp256k1.PubKey".to_string(), + AddressType::Ethermint { pk_type } => pk_type.clone(), + }; // Create a MsgSend proto Any message let pk_any = Any { - type_url: "/cosmos.crypto.secp256k1.PubKey".to_string(), + type_url: pk_type, value: pk_buf, }; @@ -610,7 +614,11 @@ impl CosmosSdkChain { // Sign doc let signed = self .keybase - .sign_msg(&self.config.key_name, signdoc_buf) + .sign_msg( + &self.config.key_name, + signdoc_buf, + &self.config.address_type, + ) .map_err(Error::key_base)?; Ok(signed) @@ -1888,7 +1896,10 @@ async fn broadcast_tx_sync(chain: &CosmosSdkChain, data: Vec) -> Result Result { +async fn query_account( + chain: &CosmosSdkChain, + address: String, +) -> Result, Error> { let mut client = ibc_proto::cosmos::auth::v1beta1::query_client::QueryClient::connect( chain.grpc_addr.clone(), ) @@ -1898,19 +1909,23 @@ async fn query_account(chain: &CosmosSdkChain, address: String) -> Result Result { diff --git a/relayer/src/chain/mock.rs b/relayer/src/chain/mock.rs index 0bcb430c41..84525de977 100644 --- a/relayer/src/chain/mock.rs +++ b/relayer/src/chain/mock.rs @@ -391,7 +391,7 @@ pub mod test_utils { use ibc::ics24_host::identifier::ChainId; - use crate::config::{ChainConfig, GasPrice, PacketFilter}; + use crate::config::{AddressType, ChainConfig, GasPrice, PacketFilter}; /// Returns a very minimal chain configuration, to be used in initializing `MockChain`s. pub fn get_basic_chain_config(id: &str) -> ChainConfig { @@ -413,6 +413,7 @@ pub mod test_utils { trusting_period: Duration::from_secs(14 * 24 * 60 * 60), // 14 days trust_threshold: Default::default(), packet_filter: PacketFilter::default(), + address_type: AddressType::default(), } } } diff --git a/relayer/src/config.rs b/relayer/src/config.rs index cd91cb90fb..bb54c65c66 100644 --- a/relayer/src/config.rs +++ b/relayer/src/config.rs @@ -262,6 +262,33 @@ impl Default for RestConfig { } } +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde( + rename_all = "lowercase", + tag = "derivation", + content = "proto_type", + deny_unknown_fields +)] +pub enum AddressType { + Cosmos, + Ethermint { pk_type: String }, +} + +impl Default for AddressType { + fn default() -> Self { + AddressType::Cosmos + } +} + +impl fmt::Display for AddressType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AddressType::Cosmos => write!(f, "cosmos"), + AddressType::Ethermint { .. } => write!(f, "ethermint"), + } + } +} + #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] pub struct ChainConfig { @@ -291,6 +318,8 @@ pub struct ChainConfig { pub gas_price: GasPrice, #[serde(default)] pub packet_filter: PacketFilter, + #[serde(default)] + pub address_type: AddressType, } /// Attempt to load and parse the TOML config file as a `Config`. diff --git a/relayer/src/error.rs b/relayer/src/error.rs index 4af2417ba5..17fb1a8668 100644 --- a/relayer/src/error.rs +++ b/relayer/src/error.rs @@ -422,6 +422,16 @@ define_error! { format!("Hermes health check failed while verifying the application compatibility for chain {0}:{1}; caused by: {2}", e.chain_id, e.address, e.cause) }, + + UnknownAccountType + { + type_url: String + } + |e| { + format!("Failed to deserialize account of an unknown protobuf type: {0}", + e.type_url) + }, + } } diff --git a/relayer/src/keyring.rs b/relayer/src/keyring.rs index 3ae33576e3..fba32cf0b8 100644 --- a/relayer/src/keyring.rs +++ b/relayer/src/keyring.rs @@ -1,13 +1,9 @@ -use std::collections::HashMap; -use std::ffi::OsStr; -use std::fs::{self, File}; -use std::path::{Path, PathBuf}; - +use crate::config::AddressType; use bech32::{ToBase32, Variant}; use bip39::{Language, Mnemonic, Seed}; use bitcoin::{ network::constants::Network, - secp256k1::Secp256k1, + secp256k1::{Message, Secp256k1, SecretKey}, util::bip32::{DerivationPath, ExtendedPrivKey, ExtendedPubKey}, }; use hdpath::StandardHDPath; @@ -16,6 +12,11 @@ use k256::ecdsa::{signature::Signer, Signature, SigningKey}; use ripemd160::Ripemd160; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; +use std::collections::HashMap; +use std::ffi::OsStr; +use std::fs::{self, File}; +use std::path::{Path, PathBuf}; +use tiny_keccak::{Hasher, Keccak}; use errors::Error; pub use pub_key::EncodedPubKey; @@ -320,6 +321,7 @@ impl KeyRing { &self, mnemonic_words: &str, hd_path: &HDPath, + at: &AddressType, ) -> Result { // Get the private key from the mnemonic let private_key = private_key_from_mnemonic(mnemonic_words, hd_path)?; @@ -328,7 +330,7 @@ impl KeyRing { let public_key = ExtendedPubKey::from_private(&Secp256k1::new(), &private_key); // Get address from the public Key - let address = get_address(public_key); + let address = get_address(public_key, at); // Compute Bech32 account let account = bech32::encode(self.account_prefix(), address.to_base32(), Variant::Bech32) @@ -343,15 +345,33 @@ impl KeyRing { } /// Sign a message - pub fn sign_msg(&self, key_name: &str, msg: Vec) -> Result, Error> { + pub fn sign_msg( + &self, + key_name: &str, + msg: Vec, + address_type: &AddressType, + ) -> Result, Error> { let key = self.get_key(key_name)?; let private_key_bytes = key.private_key.private_key.to_bytes(); - let signing_key = - SigningKey::from_bytes(private_key_bytes.as_slice()).map_err(Error::invalid_key)?; - - let signature: Signature = signing_key.sign(&msg); - Ok(signature.as_ref().to_vec()) + match address_type { + AddressType::Cosmos => { + let signing_key = SigningKey::from_bytes(private_key_bytes.as_slice()) + .map_err(Error::invalid_key)?; + let signature: Signature = signing_key.sign(&msg); + Ok(signature.as_ref().to_vec()) + } + AddressType::Ethermint { .. } => { + let hash = keccak256_hash(msg.as_slice()); + let s = Secp256k1::signing_only(); + // SAFETY: hash is 32 bytes, as expected in `Message::from_slice` -- see `keccak256_hash`, hence `unwrap` + let sign_msg = Message::from_slice(hash.as_slice()).unwrap(); + let key = SecretKey::from_slice(private_key_bytes.as_slice()) + .map_err(Error::invalid_key_raw)?; + let (_, sig_bytes) = s.sign_recoverable(&sign_msg, &key).serialize_compact(); + Ok(sig_bytes.to_vec()) + } + } } pub fn account_prefix(&self) -> &str { @@ -380,19 +400,31 @@ fn private_key_from_mnemonic( } /// Return an address from a Public Key -fn get_address(pk: ExtendedPubKey) -> Vec { - let mut hasher = Sha256::new(); - hasher.update(pk.public_key.to_bytes().as_slice()); +fn get_address(pk: ExtendedPubKey, at: &AddressType) -> Vec { + match at { + AddressType::Cosmos => { + let mut hasher = Sha256::new(); + hasher.update(pk.public_key.to_bytes().as_slice()); + + // Read hash digest over the public key bytes & consume hasher + let pk_hash = hasher.finalize(); + + // Plug the hash result into the next crypto hash function. + let mut rip_hasher = Ripemd160::new(); + rip_hasher.update(pk_hash); + let rip_result = rip_hasher.finalize(); - // Read hash digest over the public key bytes & consume hasher - let pk_hash = hasher.finalize(); + rip_result.to_vec() + } + AddressType::Ethermint { .. } => { + let public_key = pk.public_key.key.serialize_uncompressed(); + debug_assert_eq!(public_key[0], 0x04); - // Plug the hash result into the next crypto hash function. - let mut rip_hasher = Ripemd160::new(); - rip_hasher.update(pk_hash); - let rip_result = rip_hasher.finalize(); + let output = keccak256_hash(&public_key[1..]); - rip_result.to_vec() + output[12..].to_vec() + } + } } fn decode_bech32(input: &str) -> Result, Error> { @@ -415,3 +447,11 @@ fn disk_store_path(folder_name: &str) -> Result { Ok(folder) } + +fn keccak256_hash(bytes: &[u8]) -> Vec { + let mut hasher = Keccak::v256(); + hasher.update(bytes); + let mut resp = vec![0u8; 32]; + hasher.finalize(&mut resp); + resp +} diff --git a/relayer/src/keyring/errors.rs b/relayer/src/keyring/errors.rs index b5abaf5bf8..4e06f876d4 100644 --- a/relayer/src/keyring/errors.rs +++ b/relayer/src/keyring/errors.rs @@ -7,6 +7,10 @@ define_error! { [ TraceError ] |_| { "invalid key: could not build signing key from private key bytes" }, + InvalidKeyRaw + [ TraceError ] + |_| { "invalid key: could not build signing key from private key bytes" }, + KeyNotFound |_| { "key not found" }, diff --git a/relayer/src/keyring/pub_key.rs b/relayer/src/keyring/pub_key.rs index 97ba01bc09..11d0794dcb 100644 --- a/relayer/src/keyring/pub_key.rs +++ b/relayer/src/keyring/pub_key.rs @@ -63,7 +63,11 @@ impl FromStr for EncodedPubKey { proto.tpe ); - if proto.tpe != "/cosmos.crypto.secp256k1.PubKey" { + // Ethermint pubkey types: + // "/ethermint.crypto.v1alpha1.ethsecp256k1.PubKey", "/injective.crypto.v1beta1.ethsecp256k1.PubKey" + if proto.tpe != "/cosmos.crypto.secp256k1.PubKey" + && !proto.tpe.ends_with(".ethsecp256k1.PubKey") + { Err(Error::unsupported_public_key(proto.tpe)) } else { Ok(EncodedPubKey::Proto(proto)) diff --git a/relayer/tests/config/fixtures/relayer_conf_example.toml b/relayer/tests/config/fixtures/relayer_conf_example.toml index bd3d322a39..44d001bcc5 100644 --- a/relayer/tests/config/fixtures/relayer_conf_example.toml +++ b/relayer/tests/config/fixtures/relayer_conf_example.toml @@ -18,6 +18,7 @@ max_tx_size = 1048576 clock_drift = '5s' trusting_period = '14days' trust_threshold = { numerator = '1', denominator = '3' } +address_type = { derivation = 'cosmos' } [[chains]] id = 'chain_B' @@ -32,4 +33,4 @@ gas_price = { price = 0.001, denom = 'stake' } clock_drift = '5s' trusting_period = '14days' trust_threshold = { numerator = '1', denominator = '3' } - +address_type = { derivation = 'ethermint', proto_type = { pk_type = '/injective.crypto.v1beta1.ethsecp256k1.PubKey' } } From 16bd81843f19f46a58ae72df7f29f9ff83bf81a9 Mon Sep 17 00:00:00 2001 From: Tomas Tauber <2410580+tomtau@users.noreply.github.com> Date: Tue, 24 Aug 2021 10:39:04 +0800 Subject: [PATCH 2/7] added a comment for eth address and change query_account return type back to BaseAccount --- relayer/src/chain/cosmos.rs | 30 ++++++++++++------------------ relayer/src/error.rs | 3 +++ relayer/src/keyring.rs | 4 +++- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index e893fcffdc..ab63ea0658 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -523,15 +523,13 @@ impl CosmosSdkChain { fn account(&mut self) -> Result<&mut BaseAccount, Error> { if self.account == None { let account = self.block_on(query_account(self, self.key()?.account))?; - if let Some(acc) = account.as_ref() { - debug!( - sequence = %acc.sequence, - number = %acc.account_number, - "[{}] send_tx: retrieved account", - self.id() - ); - } - self.account = account; + debug!( + sequence = %account.sequence, + number = %account.account_number, + "[{}] send_tx: retrieved account", + self.id() + ); + self.account = Some(account); } Ok(self @@ -1896,10 +1894,7 @@ async fn broadcast_tx_sync(chain: &CosmosSdkChain, data: Vec) -> Result Result, Error> { +async fn query_account(chain: &CosmosSdkChain, address: String) -> Result { let mut client = ibc_proto::cosmos::auth::v1beta1::query_client::QueryClient::connect( chain.grpc_addr.clone(), ) @@ -1915,14 +1910,13 @@ async fn query_account( .account .unwrap(); if resp_account.type_url == "/cosmos.auth.v1beta1.BaseAccount" { - Ok(Some( - BaseAccount::decode(resp_account.value.as_slice()) - .map_err(|e| Error::protobuf_decode("BaseAccount".to_string(), e))?, - )) + Ok(BaseAccount::decode(resp_account.value.as_slice()) + .map_err(|e| Error::protobuf_decode("BaseAccount".to_string(), e))?) } else if resp_account.type_url.ends_with(".EthAccount") { Ok(EthAccount::decode(resp_account.value.as_slice()) .map_err(|e| Error::protobuf_decode("EthAccount".to_string(), e))? - .base_account) + .base_account + .ok_or_else(Error::empty_base_account)?) } else { Err(Error::unknown_account_type(resp_account.type_url)) } diff --git a/relayer/src/error.rs b/relayer/src/error.rs index 17fb1a8668..308add8994 100644 --- a/relayer/src/error.rs +++ b/relayer/src/error.rs @@ -432,6 +432,9 @@ define_error! { e.type_url) }, + EmptyBaseAccount + |_| { "Empty BaseAccount within EthAccount" }, + } } diff --git a/relayer/src/keyring.rs b/relayer/src/keyring.rs index fba32cf0b8..7847ba7a7d 100644 --- a/relayer/src/keyring.rs +++ b/relayer/src/keyring.rs @@ -418,10 +418,12 @@ fn get_address(pk: ExtendedPubKey, at: &AddressType) -> Vec { } AddressType::Ethermint { .. } => { let public_key = pk.public_key.key.serialize_uncompressed(); + // 0x04 is [SECP256K1_TAG_PUBKEY_UNCOMPRESSED](https://github.com/bitcoin-core/secp256k1/blob/d7ec49a6893751f068275cc8ddf4993ef7f31756/include/secp256k1.h#L196) debug_assert_eq!(public_key[0], 0x04); let output = keccak256_hash(&public_key[1..]); - + // right-most 20-bytes from the 32-byte keccak hash + // (see https://kobl.one/blog/create-full-ethereum-keypair-and-address/) output[12..].to_vec() } } From 042c69b938ed1a72763397fd3961c927f5e29121 Mon Sep 17 00:00:00 2001 From: Tomas Tauber <2410580+tomtau@users.noreply.github.com> Date: Fri, 27 Aug 2021 11:28:44 +0800 Subject: [PATCH 3/7] check the public key type in ethermint address generation --- relayer/src/keyring.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/relayer/src/keyring.rs b/relayer/src/keyring.rs index 7847ba7a7d..49f8e3d5e9 100644 --- a/relayer/src/keyring.rs +++ b/relayer/src/keyring.rs @@ -402,7 +402,17 @@ fn private_key_from_mnemonic( /// Return an address from a Public Key fn get_address(pk: ExtendedPubKey, at: &AddressType) -> Vec { match at { - AddressType::Cosmos => { + AddressType::Ethermint { ref pk_type } if pk_type.ends_with(".ethsecp256k1.PubKey") => { + let public_key = pk.public_key.key.serialize_uncompressed(); + // 0x04 is [SECP256K1_TAG_PUBKEY_UNCOMPRESSED](https://github.com/bitcoin-core/secp256k1/blob/d7ec49a6893751f068275cc8ddf4993ef7f31756/include/secp256k1.h#L196) + debug_assert_eq!(public_key[0], 0x04); + + let output = keccak256_hash(&public_key[1..]); + // right-most 20-bytes from the 32-byte keccak hash + // (see https://kobl.one/blog/create-full-ethereum-keypair-and-address/) + output[12..].to_vec() + } + AddressType::Cosmos | AddressType::Ethermint { .. } => { let mut hasher = Sha256::new(); hasher.update(pk.public_key.to_bytes().as_slice()); @@ -416,16 +426,6 @@ fn get_address(pk: ExtendedPubKey, at: &AddressType) -> Vec { rip_result.to_vec() } - AddressType::Ethermint { .. } => { - let public_key = pk.public_key.key.serialize_uncompressed(); - // 0x04 is [SECP256K1_TAG_PUBKEY_UNCOMPRESSED](https://github.com/bitcoin-core/secp256k1/blob/d7ec49a6893751f068275cc8ddf4993ef7f31756/include/secp256k1.h#L196) - debug_assert_eq!(public_key[0], 0x04); - - let output = keccak256_hash(&public_key[1..]); - // right-most 20-bytes from the 32-byte keccak hash - // (see https://kobl.one/blog/create-full-ethereum-keypair-and-address/) - output[12..].to_vec() - } } } From 1668937481a989f43dfe19438c4f47f10dab7502 Mon Sep 17 00:00:00 2001 From: Tomas Tauber <2410580+tomtau@users.noreply.github.com> Date: Tue, 31 Aug 2021 14:21:48 +0800 Subject: [PATCH 4/7] added a check on `sign_msg` --- relayer/src/keyring.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/relayer/src/keyring.rs b/relayer/src/keyring.rs index 49f8e3d5e9..6adb920f53 100644 --- a/relayer/src/keyring.rs +++ b/relayer/src/keyring.rs @@ -355,13 +355,7 @@ impl KeyRing { let private_key_bytes = key.private_key.private_key.to_bytes(); match address_type { - AddressType::Cosmos => { - let signing_key = SigningKey::from_bytes(private_key_bytes.as_slice()) - .map_err(Error::invalid_key)?; - let signature: Signature = signing_key.sign(&msg); - Ok(signature.as_ref().to_vec()) - } - AddressType::Ethermint { .. } => { + AddressType::Ethermint { ref pk_type } if pk_type.ends_with(".ethsecp256k1.PubKey") => { let hash = keccak256_hash(msg.as_slice()); let s = Secp256k1::signing_only(); // SAFETY: hash is 32 bytes, as expected in `Message::from_slice` -- see `keccak256_hash`, hence `unwrap` @@ -371,6 +365,12 @@ impl KeyRing { let (_, sig_bytes) = s.sign_recoverable(&sign_msg, &key).serialize_compact(); Ok(sig_bytes.to_vec()) } + AddressType::Cosmos | AddressType::Ethermint { .. } => { + let signing_key = SigningKey::from_bytes(private_key_bytes.as_slice()) + .map_err(Error::invalid_key)?; + let signature: Signature = signing_key.sign(&msg); + Ok(signature.as_ref().to_vec()) + } } } From e07d221e83e6c9fb98421494cd477669fe57b168 Mon Sep 17 00:00:00 2001 From: Tomas Tauber <2410580+tomtau@users.noreply.github.com> Date: Tue, 7 Sep 2021 17:24:33 +0800 Subject: [PATCH 5/7] added comments + reordered example config --- .../features/1267-ethermint-support.md | 2 +- config.toml | 28 +++++++++---------- proto/src/lib.rs | 2 ++ relayer/src/config.rs | 4 +++ relayer/src/keyring/pub_key.rs | 6 +++- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/.changelog/unreleased/features/1267-ethermint-support.md b/.changelog/unreleased/features/1267-ethermint-support.md index c6a3641c3f..602c516bee 100644 --- a/.changelog/unreleased/features/1267-ethermint-support.md +++ b/.changelog/unreleased/features/1267-ethermint-support.md @@ -1,4 +1,4 @@ -- Added Ethermint support ([#1267] [#1071]) +- Added post-Stargate (v0.5+) Ethermint support ([#1267] [#1071]) [#1267]: https://github.com/informalsystems/ibc-rs/issues/1267 [#1071]: https://github.com/informalsystems/ibc-rs/issues/1071 diff --git a/config.toml b/config.toml index e37626ab9e..72b9a0c973 100644 --- a/config.toml +++ b/config.toml @@ -90,6 +90,20 @@ account_prefix = 'cosmos' # https://hermes.informal.systems/commands/keys/index.html#adding-keys key_name = 'testkey' +# Specify the address type which determines: +# 1) address derivation; +# 2) how to retrieve and decode accounts and pubkeys; +# 3) the message signing method. +# The current configuration options are for Cosmos SDK and Ethermint. +# +# Example configuration for Ethermint: +# +# address_type = { derivation = 'ethermint', proto_type = { pk_type = '/injective.crypto.v1beta1.ethsecp256k1.PubKey' } } +# +# Default: { derivation = 'cosmos' }, i.e. address derivation as in Cosmos SDK +# Warning: This is an advanced feature! Modify with caution. +address_type = { derivation = 'cosmos' } + # Specify the store prefix used by the on-chain IBC modules. Required # Recommended value for Cosmos SDK: 'ibc' store_prefix = 'ibc' @@ -131,20 +145,6 @@ trusting_period = '14days' # Warning: This is an advanced feature! Modify with caution. trust_threshold = { numerator = '1', denominator = '3' } -# Specify the address type which determines: -# 1) address derivation; -# 2) how to retrieve and decode accounts and pubkeys; -# 3) the message signing method. -# The current configuration options are for Cosmos SDK and Ethermint. -# -# Example configuration for Ethermint: -# -# address_type = { derivation = 'ethermint', proto_type = { pk_type = '/injective.crypto.v1beta1.ethsecp256k1.PubKey' } } -# -# Default: { derivation = 'cosmos' }, i.e. address derivation as in Cosmos SDK -# Warning: This is an advanced feature! Modify with caution. -address_type = { derivation = 'cosmos' } - # This section specifies the filters for policy based relaying. # Default: no policy/ filters # The section is ignored if the global 'filter' option is set to 'false'. diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 6475d5869e..b375d92131 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -30,6 +30,8 @@ pub mod cosmos { pub mod v1beta1 { include!("prost/cosmos.auth.v1beta1.rs"); /// EthAccount defines an Ethermint account. + /// TODO: remove when https://github.com/cosmos/cosmos-sdk/pull/9981 + /// lands in the next Cosmos SDK release #[derive(Clone, PartialEq, ::prost::Message)] pub struct EthAccount { #[prost(message, optional, tag = "1")] diff --git a/relayer/src/config.rs b/relayer/src/config.rs index bb54c65c66..4e8220791b 100644 --- a/relayer/src/config.rs +++ b/relayer/src/config.rs @@ -262,6 +262,10 @@ impl Default for RestConfig { } } +/// It defines the address generation method +/// TODO: Ethermint `pk_type` to be restricted +/// after the Cosmos SDK release with ethsecp256k1 +/// https://github.com/cosmos/cosmos-sdk/pull/9981 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] #[serde( rename_all = "lowercase", diff --git a/relayer/src/keyring/pub_key.rs b/relayer/src/keyring/pub_key.rs index 11d0794dcb..445401f39f 100644 --- a/relayer/src/keyring/pub_key.rs +++ b/relayer/src/keyring/pub_key.rs @@ -64,7 +64,11 @@ impl FromStr for EncodedPubKey { ); // Ethermint pubkey types: - // "/ethermint.crypto.v1alpha1.ethsecp256k1.PubKey", "/injective.crypto.v1beta1.ethsecp256k1.PubKey" + // e.g. "/ethermint.crypto.v1alpha1.ethsecp256k1.PubKey", "/injective.crypto.v1beta1.ethsecp256k1.PubKey" + // "/ethermint.crypto.v1beta1.ethsecp256k1.PubKey", "/ethermint.crypto.v1.ethsecp256k1.PubKey", + // "/cosmos.crypto.ethsecp256k1.PubKey" + // TODO: to be restricted after the Cosmos SDK release with ethsecp256k1 + // https://github.com/cosmos/cosmos-sdk/pull/9981 if proto.tpe != "/cosmos.crypto.secp256k1.PubKey" && !proto.tpe.ends_with(".ethsecp256k1.PubKey") { From 06aee03a1db57937e00232f6bf2a5258ffad4c44 Mon Sep 17 00:00:00 2001 From: Tomas Tauber <2410580+tomtau@users.noreply.github.com> Date: Wed, 8 Sep 2021 11:06:39 +0800 Subject: [PATCH 6/7] added links with information for testing Ethermint --- ci/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ci/README.md b/ci/README.md index bea8098dcc..184e096a17 100644 --- a/ci/README.md +++ b/ci/README.md @@ -6,6 +6,13 @@ This folder contains the files required to run the End to end testing in [Github The [End to end (e2e) testing workflow](https://github.com/informalsystems/ibc-rs/actions?query=workflow%3A%22End+to+End+testing%22) spins up two `gaia` chains (`ibc-0` and `ibc-1`) in Docker containers and one container that runs the relayer. There's a script that configures the relayer (e.g. configure light clients and add keys) and runs transactions and queries. A successful run of this script ensures that the relayer is working properly with two chains that support `IBC`. +### Testing Ethermint-based networks +At this moment, the automated E2E workflow does not spin up a network with the (post-Stargate) Ethermint module. In the meantime, you can test it manually by following one of the resources below: + +- [the official documentation on ethermint.dev](https://ethermint.dev/quickstart/run_node.html) +- [using the tweaked E2E scripts from the Injective's fork](https://github.com/InjectiveLabs/ibc-rs/commit/669535617a6e45be9916387e292d45a77e7d23d2) +- [using the nix-based integration test scripts in the Cronos project](https://github.com/crypto-org-chain/cronos#quitck-start) + ### Running an End to end (e2e) test locally If you want to run the end to end test locally, you will need [Docker](https://www.docker.com/) installed on your machine. From 8f2a7371039593294cf77a358ace594f4868c4b3 Mon Sep 17 00:00:00 2001 From: Tomas Tauber <2410580+tomtau@users.noreply.github.com> Date: Fri, 10 Sep 2021 09:17:29 +0800 Subject: [PATCH 7/7] adjusted a comment for `EthAccount` --- proto/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/proto/src/lib.rs b/proto/src/lib.rs index b375d92131..f2b1d59703 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -30,8 +30,10 @@ pub mod cosmos { pub mod v1beta1 { include!("prost/cosmos.auth.v1beta1.rs"); /// EthAccount defines an Ethermint account. - /// TODO: remove when https://github.com/cosmos/cosmos-sdk/pull/9981 + /// TODO: remove when/if a canonical `EthAccount` /// lands in the next Cosmos SDK release + /// (note https://github.com/cosmos/cosmos-sdk/pull/9981 + /// only adds the PubKey type) #[derive(Clone, PartialEq, ::prost::Message)] pub struct EthAccount { #[prost(message, optional, tag = "1")]