feat: Generate Cosmos address for Injective (#4618)

### Description

Injective uses different logic compared to Neutron and Osmosis to
generate account addresses from public key. It is implemented in this PR
so that sender address is correctly generated from signer info of
transaction.

Depending on the type of public key communicated in the transaction, we
shall generate either Bitcoin-like or Ethereum-like address for
Injective.

We can also specify the type of account id for the signer. Bitcoin-like
will be chosen by default, so, we don't need to change the configuration
of Relayer.

### Drive-by changes

Made some errors to be warnings. We'll add metrics to measure how
frequent the issues are and decide how to fix them:
1. transaction contains multiple contract execution messages
2. transaction contains fees in unsupported denominations

Made Scraper to refrain from recording transaction which it cannot parse
properly.

### Backward compatibility

Yes

### Testing

Added unit tests
Manual testing with Injective blockchain

---------

Co-authored-by: Danil Nemirovsky <4614623+ameten@users.noreply.github.com>
pull/4598/head
Danil Nemirovsky 2 months ago committed by GitHub
parent b314d3e4d1
commit e490e77445
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 81
      rust/main/Cargo.lock
  2. 7
      rust/main/Cargo.toml
  3. 1
      rust/main/chains/hyperlane-cosmos/Cargo.toml
  4. 11
      rust/main/chains/hyperlane-cosmos/src/error.rs
  5. 40
      rust/main/chains/hyperlane-cosmos/src/libs/account.rs
  6. 74
      rust/main/chains/hyperlane-cosmos/src/libs/account/tests.rs
  7. 41
      rust/main/chains/hyperlane-cosmos/src/libs/address.rs
  8. 8
      rust/main/chains/hyperlane-cosmos/src/libs/mod.rs
  9. 3
      rust/main/chains/hyperlane-cosmos/src/mailbox/contract.rs
  10. 105
      rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs
  11. 14
      rust/main/chains/hyperlane-cosmos/src/providers/grpc.rs
  12. 3
      rust/main/chains/hyperlane-cosmos/src/providers/grpc/tests.rs
  13. 3
      rust/main/chains/hyperlane-cosmos/src/providers/rpc/provider.rs
  14. 3
      rust/main/chains/hyperlane-cosmos/src/routing_ism.rs
  15. 14
      rust/main/chains/hyperlane-cosmos/src/signers.rs
  16. 7
      rust/main/hyperlane-base/src/settings/parser/mod.rs
  17. 12
      rust/main/hyperlane-base/src/settings/signers.rs
  18. 11
      rust/main/hyperlane-core/src/types/account_address_type.rs
  19. 6
      rust/main/hyperlane-core/src/types/mod.rs
  20. 14
      rust/main/utils/crypto/Cargo.toml
  21. 51
      rust/main/utils/crypto/src/key.rs
  22. 4
      rust/main/utils/crypto/src/lib.rs

81
rust/main/Cargo.lock generated

@ -515,7 +515,7 @@ dependencies = [
"bitflags 1.3.2",
"bytes",
"futures-util",
"http 0.2.11",
"http 0.2.12",
"http-body",
"hyper",
"itoa",
@ -545,7 +545,7 @@ dependencies = [
"async-trait",
"bytes",
"futures-util",
"http 0.2.11",
"http 0.2.12",
"http-body",
"mime",
"rustversion",
@ -1256,7 +1256,7 @@ dependencies = [
"coins-core 0.8.7",
"digest 0.10.7",
"hmac 0.12.1",
"k256 0.13.3",
"k256 0.13.4",
"serde",
"sha2 0.10.8",
"thiserror",
@ -1564,7 +1564,7 @@ dependencies = [
"ecdsa 0.16.9",
"eyre",
"getrandom 0.2.15",
"k256 0.13.3",
"k256 0.13.4",
"rand_core 0.6.4",
"serde",
"serde_json",
@ -1589,7 +1589,7 @@ checksum = "0f862b355f7e47711e0acfe6af92cb3fd8fd5936b66a9eaa338b51edabd1e77d"
dependencies = [
"digest 0.10.7",
"ed25519-zebra 3.1.0",
"k256 0.13.3",
"k256 0.13.4",
"rand_core 0.6.4",
"thiserror",
]
@ -1608,7 +1608,7 @@ dependencies = [
"digest 0.10.7",
"ecdsa 0.16.9",
"ed25519-zebra 4.0.3",
"k256 0.13.3",
"k256 0.13.4",
"num-traits",
"p256",
"rand_core 0.6.4",
@ -1798,6 +1798,16 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto"
version = "0.1.0"
dependencies = [
"elliptic-curve 0.13.8",
"hex 0.4.3",
"k256 0.13.4",
"thiserror",
]
[[package]]
name = "crypto-bigint"
version = "0.4.9"
@ -3078,7 +3088,7 @@ dependencies = [
"getrandom 0.2.15",
"hashers",
"hex 0.4.3",
"http 0.2.11",
"http 0.2.12",
"once_cell",
"parking_lot 0.11.2",
"pin-project",
@ -3484,7 +3494,7 @@ dependencies = [
"ecdsa 0.16.9",
"ed25519-dalek 2.1.1",
"fuel-types",
"k256 0.13.3",
"k256 0.13.4",
"lazy_static",
"p256",
"rand 0.8.5",
@ -3979,7 +3989,7 @@ dependencies = [
"futures-core",
"futures-sink",
"futures-util",
"http 0.2.11",
"http 0.2.12",
"indexmap 2.5.0",
"slab",
"tokio",
@ -4074,7 +4084,7 @@ dependencies = [
"base64 0.21.7",
"bytes",
"headers-core",
"http 0.2.11",
"http 0.2.12",
"httpdate",
"mime",
"sha1",
@ -4086,7 +4096,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
dependencies = [
"http 0.2.11",
"http 0.2.12",
]
[[package]]
@ -4230,9 +4240,9 @@ dependencies = [
[[package]]
name = "http"
version = "0.2.11"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
@ -4257,7 +4267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http 0.2.11",
"http 0.2.12",
"pin-project-lite",
]
@ -4300,7 +4310,7 @@ dependencies = [
"futures-core",
"futures-util",
"h2",
"http 0.2.11",
"http 0.2.12",
"http-body",
"httparse",
"httpdate",
@ -4322,7 +4332,7 @@ dependencies = [
"bytes",
"futures",
"headers",
"http 0.2.11",
"http 0.2.12",
"hyper",
"hyper-rustls 0.22.1",
"rustls-native-certs 0.5.0",
@ -4357,7 +4367,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
dependencies = [
"futures-util",
"http 0.2.11",
"http 0.2.12",
"hyper",
"log",
"rustls 0.21.12",
@ -4502,10 +4512,11 @@ dependencies = [
"bech32 0.9.1",
"cosmrs",
"cosmwasm-std 2.1.3",
"crypto",
"derive-new",
"futures",
"hex 0.4.3",
"http 0.2.11",
"http 0.2.12",
"hyper",
"hyper-tls",
"hyperlane-core",
@ -5091,9 +5102,9 @@ dependencies = [
[[package]]
name = "k256"
version = "0.13.3"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b"
checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
dependencies = [
"cfg-if",
"ecdsa 0.16.9",
@ -5480,7 +5491,7 @@ dependencies = [
"bytes",
"encoding_rs",
"futures-util",
"http 0.2.11",
"http 0.2.12",
"httparse",
"log",
"memchr",
@ -7027,7 +7038,7 @@ dependencies = [
"futures-core",
"futures-util",
"h2",
"http 0.2.11",
"http 0.2.12",
"http-body",
"hyper",
"hyper-rustls 0.24.2",
@ -7228,7 +7239,7 @@ dependencies = [
"hyperlane-cosmos",
"hyperlane-cosmwasm-interface",
"jobserver",
"k256 0.13.3",
"k256 0.13.4",
"macro_rules_attribute",
"maplit",
"nix 0.26.4",
@ -7258,7 +7269,7 @@ dependencies = [
"bytes",
"crc32fast",
"futures",
"http 0.2.11",
"http 0.2.12",
"hyper",
"hyper-tls",
"lazy_static",
@ -7330,7 +7341,7 @@ dependencies = [
"futures",
"hex 0.4.3",
"hmac 0.11.0",
"http 0.2.11",
"http 0.2.12",
"hyper",
"log",
"md-5 0.9.1",
@ -9391,7 +9402,7 @@ dependencies = [
"base64 0.13.1",
"bytes",
"chrono",
"http 0.2.11",
"http 0.2.12",
"percent-encoding",
"serde",
"serde_json",
@ -9430,7 +9441,7 @@ dependencies = [
"ed25519-consensus",
"flex-error",
"futures",
"k256 0.13.3",
"k256 0.13.4",
"num-traits",
"once_cell",
"prost 0.11.9",
@ -9507,7 +9518,7 @@ dependencies = [
"flex-error",
"futures",
"getrandom 0.2.15",
"http 0.2.11",
"http 0.2.12",
"hyper",
"hyper-proxy",
"hyper-rustls 0.22.1",
@ -9891,7 +9902,7 @@ dependencies = [
"futures-core",
"futures-util",
"h2",
"http 0.2.11",
"http 0.2.12",
"http-body",
"hyper",
"hyper-timeout",
@ -9921,7 +9932,7 @@ dependencies = [
"base64 0.21.7",
"bytes",
"h2",
"http 0.2.11",
"http 0.2.12",
"http-body",
"hyper",
"hyper-timeout",
@ -10103,7 +10114,7 @@ dependencies = [
"base64 0.13.1",
"byteorder",
"bytes",
"http 0.2.11",
"http 0.2.12",
"httparse",
"log",
"rand 0.8.5",
@ -10376,7 +10387,7 @@ dependencies = [
"hyperlane-cosmos",
"hyperlane-ethereum",
"hyperlane-test",
"k256 0.13.3",
"k256 0.13.4",
"mockall",
"prometheus",
"reqwest",
@ -10460,7 +10471,7 @@ dependencies = [
"futures-channel",
"futures-util",
"headers",
"http 0.2.11",
"http 0.2.12",
"hyper",
"log",
"mime",
@ -10950,7 +10961,7 @@ checksum = "acaf2e321fc6f853572b372962fa253cba1b62a0025116bb463ce3c00b4394dc"
dependencies = [
"cfg-if",
"futures",
"http 0.2.11",
"http 0.2.12",
"humantime-serde",
"hyper",
"hyper-rustls 0.24.2",
@ -10996,7 +11007,7 @@ dependencies = [
"async-trait",
"base64 0.13.1",
"futures",
"http 0.2.11",
"http 0.2.12",
"hyper",
"hyper-rustls 0.24.2",
"itertools 0.10.5",

@ -13,6 +13,7 @@ members = [
"hyperlane-test",
"utils/abigen",
"utils/backtrace-oneline",
"utils/crypto",
"utils/hex",
"utils/run-locally",
]
@ -69,10 +70,10 @@ futures-util = "0.3"
generic-array = { version = "0.14", features = ["serde", "more_lengths"] }
# Required for WASM support https://docs.rs/getrandom/latest/getrandom/#webassembly-support
bech32 = "0.9.1"
elliptic-curve = "0.12.3"
elliptic-curve = "0.13.8"
getrandom = { version = "0.2", features = ["js"] }
hex = "0.4.3"
http = "*"
http = "0.2.12"
hyper = "0.14"
hyper-tls = "0.5.0"
hyperlane-cosmwasm-interface = "=0.0.6-rc6"
@ -81,7 +82,7 @@ injective-std = "=0.1.5"
itertools = "*"
jobserver = "=0.1.26"
jsonrpc-core = "18.0"
k256 = { version = "0.13.1", features = ["std", "ecdsa"] }
k256 = { version = "0.13.4", features = ["arithmetic", "std", "ecdsa"] }
log = "0.4"
macro_rules_attribute = "0.2"
maplit = "1.0"

@ -14,6 +14,7 @@ base64 = { workspace = true }
bech32 = { workspace = true }
cosmrs = { workspace = true, features = ["cosmwasm", "tokio", "grpc", "rpc"] }
cosmwasm-std = { workspace = true }
crypto = { path = "../../utils/crypto" }
derive-new = { workspace = true }
futures = { workspace = true }
hex = { workspace = true }

@ -1,6 +1,9 @@
use std::fmt::Debug;
use cosmrs::proto::prost;
use crypto::PublicKeyError;
use hyperlane_core::ChainCommunicationError;
use std::fmt::Debug;
/// Errors from the crates specific to the hyperlane-cosmos
/// implementation.
@ -60,3 +63,9 @@ impl From<HyperlaneCosmosError> for ChainCommunicationError {
ChainCommunicationError::from_other(value)
}
}
impl From<PublicKeyError> for HyperlaneCosmosError {
fn from(value: PublicKeyError) -> Self {
HyperlaneCosmosError::PublicKeyError(value.to_string())
}
}

@ -1,9 +1,11 @@
use cosmrs::{crypto::PublicKey, AccountId};
use hyperlane_cosmwasm_interface::types::keccak256_hash;
use tendermint::account::Id as TendermintAccountId;
use tendermint::public_key::PublicKey as TendermintPublicKey;
use crypto::decompress_public_key;
use hyperlane_core::Error::Overflow;
use hyperlane_core::{ChainCommunicationError, ChainResult, H256};
use hyperlane_core::{AccountAddressType, ChainCommunicationError, ChainResult, H256};
use crate::HyperlaneCosmosError;
@ -16,7 +18,21 @@ impl<'a> CosmosAccountId<'a> {
Self { account_id }
}
pub fn account_id_from_pubkey(pub_key: PublicKey, prefix: &str) -> ChainResult<AccountId> {
/// Calculate AccountId from public key depending on provided prefix
pub fn account_id_from_pubkey(
pub_key: PublicKey,
prefix: &str,
account_address_type: &AccountAddressType,
) -> ChainResult<AccountId> {
match account_address_type {
AccountAddressType::Bitcoin => Self::bitcoin_style(pub_key, prefix),
AccountAddressType::Ethereum => Self::ethereum_style(pub_key, prefix),
}
}
/// Returns a Bitcoin style address: RIPEMD160(SHA256(pubkey))
/// Source: `<https://github.com/cosmos/cosmos-sdk/blob/177e7f45959215b0b4e85babb7c8264eaceae052/crypto/keys/secp256k1/secp256k1.go#L154>`
fn bitcoin_style(pub_key: PublicKey, prefix: &str) -> ChainResult<AccountId> {
// Get the inner type
let tendermint_pub_key = TendermintPublicKey::from(pub_key);
// Get the RIPEMD160(SHA256(pub_key))
@ -27,6 +43,23 @@ impl<'a> CosmosAccountId<'a> {
Ok(account_id)
}
/// Returns an Ethereum style address: KECCAK256(pubkey)[20]
/// Parameter `pub_key` is a compressed public key.
fn ethereum_style(pub_key: PublicKey, prefix: &str) -> ChainResult<AccountId> {
let decompressed_public_key = decompress_public_key(&pub_key.to_bytes())
.map_err(Into::<HyperlaneCosmosError>::into)?;
let hash = keccak256_hash(&decompressed_public_key[1..]);
let mut bytes = [0u8; 20];
bytes.copy_from_slice(&hash.as_slice()[12..]);
let account_id =
AccountId::new(prefix, bytes.as_slice()).map_err(Into::<HyperlaneCosmosError>::into)?;
Ok(account_id)
}
}
impl TryFrom<&CosmosAccountId<'_>> for H256 {
@ -55,3 +88,6 @@ impl TryFrom<CosmosAccountId<'_>> for H256 {
(&account_id).try_into()
}
}
#[cfg(test)]
mod tests;

@ -0,0 +1,74 @@
use cosmrs::crypto::PublicKey;
use cosmwasm_std::HexBinary;
use crypto::decompress_public_key;
use hyperlane_core::AccountAddressType;
use AccountAddressType::{Bitcoin, Ethereum};
use crate::CosmosAccountId;
const COMPRESSED_PUBLIC_KEY: &str =
"02962d010010b6eec66846322704181570d89e28236796579c535d2e44d20931f4";
const INJECTIVE_ADDRESS: &str = "inj1m6ada382hfuxvuke4h9p4uswhn2qcca7mlg0dr";
const NEUTRON_ADDRESS: &str = "neutron1mydju5alsmhnfsawy0j4lyns70l7qukgdgy45w";
#[test]
fn test_account_id() {
// given
let pub_key = compressed_public_key();
// when
let neutron_account_id =
CosmosAccountId::account_id_from_pubkey(pub_key, "neutron", &Bitcoin).unwrap();
let injective_account_id =
CosmosAccountId::account_id_from_pubkey(pub_key, "inj", &Ethereum).unwrap();
// then
assert_eq!(neutron_account_id.as_ref(), NEUTRON_ADDRESS);
assert_eq!(injective_account_id.as_ref(), INJECTIVE_ADDRESS);
}
#[test]
fn test_bitcoin_style() {
// given
let compressed = compressed_public_key();
let decompressed = decompressed_public_key();
// when
let from_compressed = CosmosAccountId::bitcoin_style(compressed, "neutron").unwrap();
let from_decompressed = CosmosAccountId::bitcoin_style(decompressed, "neutron").unwrap();
// then
assert_eq!(from_compressed.as_ref(), NEUTRON_ADDRESS);
assert_eq!(from_decompressed.as_ref(), NEUTRON_ADDRESS);
}
#[test]
fn test_ethereum_style() {
// given
let compressed = compressed_public_key();
let decompressed = decompressed_public_key();
// when
let from_compressed = CosmosAccountId::ethereum_style(compressed, "inj").unwrap();
let from_decompressed = CosmosAccountId::ethereum_style(decompressed, "inj").unwrap();
// then
assert_eq!(from_compressed.as_ref(), INJECTIVE_ADDRESS);
assert_eq!(from_decompressed.as_ref(), INJECTIVE_ADDRESS);
}
fn compressed_public_key() -> PublicKey {
let hex = hex::decode(COMPRESSED_PUBLIC_KEY).unwrap();
let tendermint = tendermint::PublicKey::from_raw_secp256k1(&hex).unwrap();
let pub_key = PublicKey::from(tendermint);
pub_key
}
fn decompressed_public_key() -> PublicKey {
let hex = hex::decode(COMPRESSED_PUBLIC_KEY).unwrap();
let decompressed = decompress_public_key(&hex).unwrap();
let tendermint = tendermint::PublicKey::from_raw_secp256k1(&decompressed).unwrap();
let pub_key = PublicKey::from(tendermint);
pub_key
}

@ -5,10 +5,11 @@ use cosmrs::{
AccountId,
};
use derive_new::new;
use hyperlane_core::{ChainCommunicationError, ChainResult, Error::Overflow, H256};
use hyperlane_core::{
AccountAddressType, ChainCommunicationError, ChainResult, Error::Overflow, H256,
};
use crate::libs::account::CosmosAccountId;
use crate::HyperlaneCosmosError;
use crate::{CosmosAccountId, HyperlaneCosmosError};
/// Wrapper around the cosmrs AccountId type that abstracts bech32 encoding
#[derive(new, Debug, Clone)]
@ -20,23 +21,19 @@ pub struct CosmosAddress {
}
impl CosmosAddress {
/// Returns a Bitcoin style address: RIPEMD160(SHA256(pubkey))
/// Source: `<https://github.com/cosmos/cosmos-sdk/blob/177e7f45959215b0b4e85babb7c8264eaceae052/crypto/keys/secp256k1/secp256k1.go#L154>`
pub fn from_pubkey(pubkey: PublicKey, prefix: &str) -> ChainResult<Self> {
let account_id = CosmosAccountId::account_id_from_pubkey(pubkey, prefix)?;
Self::from_account_id(account_id)
}
/// Creates a wrapper around a cosmrs AccountId from a private key byte array
pub fn from_privkey(priv_key: &[u8], prefix: &str) -> ChainResult<Self> {
pub fn from_privkey(
priv_key: &[u8],
prefix: &str,
account_address_type: &AccountAddressType,
) -> ChainResult<Self> {
let pubkey = SigningKey::from_slice(priv_key)
.map_err(Into::<HyperlaneCosmosError>::into)?
.public_key();
Self::from_pubkey(pubkey, prefix)
Self::from_pubkey(pubkey, prefix, account_address_type)
}
/// Returns a Bitcoin style address calculated from Bech32 encoding
/// Source: `<https://github.com/cosmos/cosmos-sdk/blob/177e7f45959215b0b4e85babb7c8264eaceae052/crypto/keys/secp256k1/secp256k1.go#L154>`
/// Returns an account address calculated from Bech32 encoding
pub fn from_account_id(account_id: AccountId) -> ChainResult<Self> {
// Hex digest
let digest = H256::try_from(&CosmosAccountId::new(&account_id))?;
@ -77,6 +74,17 @@ impl CosmosAddress {
pub fn digest(&self) -> H256 {
self.digest
}
/// Calculates an account address depending on prefix and account address type
fn from_pubkey(
pubkey: PublicKey,
prefix: &str,
account_address_type: &AccountAddressType,
) -> ChainResult<Self> {
let account_id =
CosmosAccountId::account_id_from_pubkey(pubkey, prefix, account_address_type)?;
Self::from_account_id(account_id)
}
}
impl TryFrom<&CosmosAddress> for H256 {
@ -119,8 +127,9 @@ pub mod test {
let hex_key = "0x5486418967eabc770b0fcb995f7ef6d9a72f7fc195531ef76c5109f44f51af26";
let key = hex_or_base58_to_h256(hex_key).unwrap();
let prefix = "neutron";
let addr = CosmosAddress::from_privkey(key.as_bytes(), prefix)
.expect("Cosmos address creation failed");
let addr =
CosmosAddress::from_privkey(key.as_bytes(), prefix, &AccountAddressType::Bitcoin)
.expect("Cosmos address creation failed");
assert_eq!(
addr.address(),
"neutron1kknekjxg0ear00dky5ykzs8wwp2gz62z9s6aaj"

@ -1,4 +1,8 @@
pub(crate) use account::CosmosAccountId;
pub(crate) use address::CosmosAddress;
/// This module contains conversions from Cosmos AccountId to H56
pub(crate) mod account;
mod account;
/// This module contains all the verification variables the libraries used by the Hyperlane Cosmos chain.
pub mod address;
mod address;

@ -11,7 +11,6 @@ use hyperlane_core::{
TxCostEstimate, TxOutcome, H256, U256,
};
use crate::address::CosmosAddress;
use crate::grpc::WasmProvider;
use crate::payloads::general;
use crate::payloads::mailbox::{
@ -19,7 +18,7 @@ use crate::payloads::mailbox::{
};
use crate::types::tx_response_to_outcome;
use crate::utils::get_block_height_for_lag;
use crate::{payloads, ConnectionConf, CosmosProvider, Signer};
use crate::{payloads, ConnectionConf, CosmosAddress, CosmosProvider, Signer};
#[derive(Clone, Debug)]
/// A reference to a Mailbox contract on some Cosmos chain

@ -1,8 +1,10 @@
use std::str::FromStr;
use async_trait::async_trait;
use cosmrs::cosmwasm::MsgExecuteContract;
use cosmrs::crypto::PublicKey;
use cosmrs::tx::{MessageExt, SequenceNumber, SignerInfo};
use cosmrs::{AccountId, Any, Coin, Tx};
use cosmrs::tx::{MessageExt, SequenceNumber, SignerInfo, SignerPublicKey};
use cosmrs::{proto, AccountId, Any, Coin, Tx};
use itertools::Itertools;
use once_cell::sync::Lazy;
use tendermint::hash::Algorithm;
@ -11,20 +13,25 @@ use tendermint_rpc::{client::CompatMode, Client, HttpClient};
use time::OffsetDateTime;
use tracing::{error, warn};
use crypto::decompress_public_key;
use hyperlane_core::{
BlockInfo, ChainCommunicationError, ChainInfo, ChainResult, ContractLocator, HyperlaneChain,
HyperlaneDomain, HyperlaneProvider, TxnInfo, TxnReceiptInfo, H256, U256,
AccountAddressType, BlockInfo, ChainCommunicationError, ChainInfo, ChainResult,
ContractLocator, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, TxnReceiptInfo,
H256, U256,
};
use crate::address::CosmosAddress;
use crate::grpc::{WasmGrpcProvider, WasmProvider};
use crate::libs::account::CosmosAccountId;
use crate::providers::rpc::CosmosRpcClient;
use crate::{ConnectionConf, CosmosAmount, HyperlaneCosmosError, Signer};
use crate::{
ConnectionConf, CosmosAccountId, CosmosAddress, CosmosAmount, HyperlaneCosmosError, Signer,
};
/// Exponent value for atto units (10^-18).
const ATTO_EXPONENT: u32 = 18;
/// Injective public key type URL for protobuf Any
const INJECTIVE_PUBLIC_KEY_TYPE_URL: &str = "/injective.crypto.v1beta1.ethsecp256k1.PubKey";
/// Abstraction over a connection to a Cosmos chain
#[derive(Debug, Clone)]
pub struct CosmosProvider {
@ -93,16 +100,74 @@ impl CosmosProvider {
HyperlaneCosmosError::PublicKeyError("no public key for default signer".to_owned())
})?;
let public_key = PublicKey::try_from(signer_public_key)?;
let (key, account_address_type) = self.normalize_public_key(signer_public_key)?;
let public_key = PublicKey::try_from(key)?;
let account_id = CosmosAccountId::account_id_from_pubkey(
public_key,
&self.connection_conf.get_bech32_prefix(),
&account_address_type,
)?;
Ok((account_id, signer_info.sequence))
}
fn normalize_public_key(
&self,
signer_public_key: SignerPublicKey,
) -> ChainResult<(SignerPublicKey, AccountAddressType)> {
let public_key_and_account_address_type = match signer_public_key {
SignerPublicKey::Single(pk) => (SignerPublicKey::from(pk), AccountAddressType::Bitcoin),
SignerPublicKey::LegacyAminoMultisig(pk) => {
(SignerPublicKey::from(pk), AccountAddressType::Bitcoin)
}
SignerPublicKey::Any(pk) => {
if pk.type_url != PublicKey::ED25519_TYPE_URL
&& pk.type_url != PublicKey::SECP256K1_TYPE_URL
&& pk.type_url != INJECTIVE_PUBLIC_KEY_TYPE_URL
{
let msg = format!(
"can only normalize public keys with a known TYPE_URL: {}, {}, {}",
PublicKey::ED25519_TYPE_URL,
PublicKey::SECP256K1_TYPE_URL,
INJECTIVE_PUBLIC_KEY_TYPE_URL
);
warn!(pk.type_url, msg);
Err(HyperlaneCosmosError::PublicKeyError(msg.to_owned()))?
}
let (pub_key, account_address_type) =
if pk.type_url == INJECTIVE_PUBLIC_KEY_TYPE_URL {
let any = Any {
type_url: PublicKey::SECP256K1_TYPE_URL.to_owned(),
value: pk.value,
};
let proto = proto::cosmos::crypto::secp256k1::PubKey::from_any(&any)
.map_err(Into::<HyperlaneCosmosError>::into)?;
let decompressed = decompress_public_key(&proto.key)
.map_err(|e| HyperlaneCosmosError::PublicKeyError(e.to_string()))?;
let tendermint = tendermint::PublicKey::from_raw_secp256k1(&decompressed)
.ok_or_else(|| {
HyperlaneCosmosError::PublicKeyError(
"cannot create tendermint public key".to_owned(),
)
})?;
(PublicKey::from(tendermint), AccountAddressType::Ethereum)
} else {
(PublicKey::try_from(pk)?, AccountAddressType::Bitcoin)
};
(SignerPublicKey::Single(pub_key), account_address_type)
}
};
Ok(public_key_and_account_address_type)
}
/// Calculates the sender and the nonce for the transaction.
/// We use `payer` of the fees as the sender of the transaction, and we search for `payer`
/// signature information to find the nonce.
@ -146,14 +211,15 @@ impl CosmosProvider {
let contract_execution_messages_len = contract_execution_messages.len();
if contract_execution_messages_len > 1 {
error!(
?tx_hash,
?contract_execution_messages,
"transaction contains multiple contract execution messages, we are indexing the first entry only");
let msg = "transaction contains multiple contract execution messages, we are indexing the first entry only";
warn!(?tx_hash, ?contract_execution_messages, msg);
Err(ChainCommunicationError::CustomError(msg.to_owned()))?
}
let any = contract_execution_messages.first().ok_or_else(|| {
ChainCommunicationError::from_other_str("could not find contract execution message")
let msg = "could not find contract execution message";
warn!(?tx_hash, msg);
ChainCommunicationError::from_other_str(msg)
})?;
let proto =
ProtoMsgExecuteContract::from_any(any).map_err(Into::<HyperlaneCosmosError>::into)?;
@ -166,7 +232,7 @@ impl CosmosProvider {
/// The only denomination we support at the moment is the one we express gas minimum price
/// in the configuration of a chain. If fees contain an entry in a different denomination,
/// we report it in the logs.
fn report_unsupported_denominations(&self, tx: &Tx, tx_hash: &H256) {
fn report_unsupported_denominations(&self, tx: &Tx, tx_hash: &H256) -> ChainResult<()> {
let supported_denomination = self.connection_conf.get_minimum_gas_price().denom;
let unsupported_denominations = tx
.auth_info
@ -178,12 +244,17 @@ impl CosmosProvider {
.fold("".to_string(), |acc, denom| acc + ", " + denom);
if !unsupported_denominations.is_empty() {
error!(
let msg = "transaction contains fees in unsupported denominations, manual intervention is required";
warn!(
?tx_hash,
?supported_denomination,
?unsupported_denominations,
"transaction contains fees in unsupported denominations, manual intervention is required");
msg,
);
Err(ChainCommunicationError::CustomError(msg.to_owned()))?
}
Ok(())
}
/// Converts fees to a common denomination if necessary.
@ -278,7 +349,7 @@ impl HyperlaneProvider for CosmosProvider {
let (sender, nonce) = self.sender_and_nonce(&tx)?;
// TODO support multiple denominations for amount
self.report_unsupported_denominations(&tx, hash);
self.report_unsupported_denominations(&tx, hash)?;
let gas_limit = U256::from(tx.auth_info.fee.gas_limit);
let fee = tx

@ -1,3 +1,5 @@
use std::fmt::Debug;
use async_trait::async_trait;
use cosmrs::{
proto::{
@ -25,13 +27,8 @@ use cosmrs::{
Any, Coin,
};
use derive_new::new;
use hyperlane_core::{
rpc_clients::{BlockNumberGetter, FallbackProvider},
ChainCommunicationError, ChainResult, ContractLocator, FixedPointNumber, HyperlaneDomain, U256,
};
use protobuf::Message as _;
use serde::Serialize;
use std::fmt::Debug;
use tonic::{
transport::{Channel, Endpoint},
GrpcMethod, IntoRequest,
@ -39,9 +36,14 @@ use tonic::{
use tracing::{debug, instrument};
use url::Url;
use crate::{address::CosmosAddress, CosmosAmount};
use hyperlane_core::{
rpc_clients::{BlockNumberGetter, FallbackProvider},
ChainCommunicationError, ChainResult, ContractLocator, FixedPointNumber, HyperlaneDomain, U256,
};
use crate::{rpc_clients::CosmosFallbackProvider, HyperlaneCosmosError};
use crate::{signers::Signer, ConnectionConf};
use crate::{CosmosAddress, CosmosAmount};
/// A multiplier applied to a simulated transaction's gas usage to
/// calculate the estimated gas.

@ -5,9 +5,8 @@ use url::Url;
use hyperlane_core::config::OperationBatchConfig;
use hyperlane_core::{ContractLocator, HyperlaneDomain, KnownHyperlaneDomain};
use crate::address::CosmosAddress;
use crate::grpc::{WasmGrpcProvider, WasmProvider};
use crate::{ConnectionConf, CosmosAmount, NativeToken, RawCosmosAmount};
use crate::{ConnectionConf, CosmosAddress, CosmosAmount, NativeToken, RawCosmosAmount};
#[ignore]
#[tokio::test]

@ -20,9 +20,8 @@ use hyperlane_core::{
ChainCommunicationError, ChainResult, ContractLocator, HyperlaneDomain, LogMeta, H256, U256,
};
use crate::address::CosmosAddress;
use crate::rpc::CosmosRpcClient;
use crate::{ConnectionConf, CosmosProvider, HyperlaneCosmosError};
use crate::{ConnectionConf, CosmosAddress, CosmosProvider, HyperlaneCosmosError};
#[async_trait]
/// Trait for wasm indexer. Use rpc provider

@ -8,13 +8,12 @@ use hyperlane_core::{
};
use crate::{
address::CosmosAddress,
grpc::WasmProvider,
payloads::ism_routes::{
IsmRouteRequest, IsmRouteRequestInner, IsmRouteRespnose, QueryRoutingIsmGeneralRequest,
},
signers::Signer,
ConnectionConf, CosmosProvider,
ConnectionConf, CosmosAddress, CosmosProvider,
};
/// A reference to a RoutingIsm contract on some Cosmos chain

@ -1,7 +1,7 @@
use cosmrs::crypto::{secp256k1::SigningKey, PublicKey};
use hyperlane_core::ChainResult;
use hyperlane_core::{AccountAddressType, ChainResult};
use crate::{address::CosmosAddress, HyperlaneCosmosError};
use crate::{CosmosAddress, HyperlaneCosmosError};
#[derive(Clone, Debug)]
/// Signer for cosmos chain
@ -22,8 +22,14 @@ impl Signer {
/// # Arguments
/// * `private_key` - private key for signer
/// * `prefix` - prefix for signer address
pub fn new(private_key: Vec<u8>, prefix: String) -> ChainResult<Self> {
let address = CosmosAddress::from_privkey(&private_key, &prefix)?.address();
/// * `account_address_type` - the type of account address used for signer
pub fn new(
private_key: Vec<u8>,
prefix: String,
account_address_type: &AccountAddressType,
) -> ChainResult<Self> {
let address =
CosmosAddress::from_privkey(&private_key, &prefix, account_address_type)?.address();
let signing_key = Self::build_signing_key(&private_key)?;
let public_key = signing_key.public_key();
Ok(Self {

@ -332,9 +332,16 @@ fn parse_signer(signer: ValueParser) -> ConfigResult<SignerConf> {
.get_key("prefix")
.parse_string()
.unwrap_or_default();
let account_address_type = signer
.chain(&mut err)
.get_opt_key("accountAddressType")
.parse_from_str("Expected Account Address Type")
.end()
.unwrap_or_default();
err.into_result(SignerConf::CosmosKey {
key,
prefix: prefix.to_string(),
account_address_type,
})
}};
}

@ -3,7 +3,7 @@ use ed25519_dalek::SecretKey;
use ethers::prelude::{AwsSigner, LocalWallet};
use ethers::utils::hex::ToHex;
use eyre::{bail, Context, Report};
use hyperlane_core::H256;
use hyperlane_core::{AccountAddressType, H256};
use hyperlane_sealevel::Keypair;
use rusoto_core::Region;
use rusoto_kms::KmsClient;
@ -34,6 +34,8 @@ pub enum SignerConf {
key: H256,
/// Prefix for cosmos address
prefix: String,
/// Account address type for cosmos address
account_address_type: AccountAddressType,
},
/// Assume node will sign on RPC calls
#[default]
@ -143,10 +145,16 @@ impl ChainSigner for Keypair {
#[async_trait]
impl BuildableWithSignerConf for hyperlane_cosmos::Signer {
async fn build(conf: &SignerConf) -> Result<Self, Report> {
if let SignerConf::CosmosKey { key, prefix } = conf {
if let SignerConf::CosmosKey {
key,
prefix,
account_address_type,
} = conf
{
Ok(hyperlane_cosmos::Signer::new(
key.as_bytes().to_vec(),
prefix.clone(),
account_address_type,
)?)
} else {
bail!(format!("{conf:?} key is not supported by cosmos"));

@ -0,0 +1,11 @@
/// Specifies the account address type
#[derive(
Clone, Debug, Default, strum::Display, strum::EnumString, strum::IntoStaticStr, strum::EnumIter,
)]
pub enum AccountAddressType {
/// Bitcoin style address: RIPEMD160(SHA256(pubkey))
#[default]
Bitcoin,
/// Ethereum style address: KECCAK256(pubkey)[20]
Ethereum,
}

@ -1,11 +1,13 @@
use serde::{Deserialize, Serialize};
use std::fmt;
use std::io::{Read, Write};
use std::ops::Add;
use serde::{Deserialize, Serialize};
pub use self::primitive_types::*;
#[cfg(feature = "ethers")]
pub use ::primitive_types as ethers_core_types;
pub use account_address_type::AccountAddressType;
pub use announcement::*;
pub use chain_data::*;
pub use checkpoint::*;
@ -18,6 +20,8 @@ pub use transaction::*;
use crate::{Decode, Encode, HyperlaneProtocolError};
/// This module contains enum for account address type
mod account_address_type;
mod announcement;
mod chain_data;
mod checkpoint;

@ -0,0 +1,14 @@
[package]
name = "crypto"
documentation.workspace = true
edition.workspace = true
homepage.workspace = true
license-file.workspace = true
publish.workspace = true
version.workspace = true
[dependencies]
elliptic-curve = { workspace = true, features = ["sec1"] }
hex = { workspace = true }
k256 = { workspace = true }
thiserror = { workspace = true }

@ -0,0 +1,51 @@
use std::fmt::{Display, Formatter};
use elliptic_curve::sec1::ToEncodedPoint;
#[derive(Debug, thiserror::Error)]
pub enum PublicKeyError {
Decode(String),
}
impl Display for PublicKeyError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
/// Decompresses public key of secp256k1 if it was compressed
///
/// Public key can be expressed in compressed or decompressed forms.
/// Compressed form contains one byte as prefix and x component of the public key.
/// Decompressed form contains one byte as prefix, x and y components of the public key.
pub fn decompress_public_key(public_key: &[u8]) -> Result<Vec<u8>, PublicKeyError> {
let elliptic: elliptic_curve::PublicKey<k256::Secp256k1> =
elliptic_curve::PublicKey::from_sec1_bytes(public_key)
.map_err(|e| PublicKeyError::Decode(e.to_string()))?;
// if public key was compressed, encoding into the point will decompress it.
let point = elliptic.to_encoded_point(false);
let decompressed = point.to_bytes().to_vec();
Ok(decompressed)
}
#[cfg(test)]
mod tests {
use crate::key::decompress_public_key;
#[test]
fn test_decompress_public_key() {
// given
let compressed = "02962d010010b6eec66846322704181570d89e28236796579c535d2e44d20931f4";
let hex = hex::decode(compressed).unwrap();
// when
let decompressed = hex::encode(decompress_public_key(&hex).unwrap());
// then
assert_eq!(
"04962d010010b6eec66846322704181570d89e28236796579c535d2e44d20931f40cb1152fb9e61ec7493a0d9a35d2e8a57198e109613854abdd3be5603d504008",
decompressed
);
}
}

@ -0,0 +1,4 @@
pub use key::decompress_public_key;
pub use key::PublicKeyError;
mod key;
Loading…
Cancel
Save