From a366115b1698381bb753c8d11c2a90e9c162671a Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Tue, 7 Nov 2023 01:11:57 +0000 Subject: [PATCH] fix: cosmos address building --- rust/Cargo.lock | 1 + rust/Cargo.toml | 1 + rust/chains/hyperlane-cosmos/Cargo.toml | 1 + .../hyperlane-cosmos/src/aggregation_ism.rs | 11 +- .../hyperlane-cosmos/src/libs/address.rs | 101 ++++++++++++------ rust/chains/hyperlane-cosmos/src/mailbox.rs | 7 +- .../hyperlane-cosmos/src/providers/rpc.rs | 12 +-- .../hyperlane-cosmos/src/routing_ism.rs | 6 +- rust/utils/run-locally/src/cosmos/link.rs | 1 - 9 files changed, 95 insertions(+), 46 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 67577b6b92..2f6b6e0731 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -4167,6 +4167,7 @@ dependencies = [ "serde_json", "sha2 0.10.8", "sha256", + "tendermint", "thiserror", "tokio", "tonic", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index e7a6e3a9cb..3345fe9887 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -149,6 +149,7 @@ static_assertions = "1.1" strum = "0.25.0" strum_macros = "0.25.2" tempfile = "3.3" +tendermint = "0.32.2" thiserror = "1.0" time = "0.3" tiny-keccak = "2.0.2" diff --git a/rust/chains/hyperlane-cosmos/Cargo.toml b/rust/chains/hyperlane-cosmos/Cargo.toml index a7a2fec414..e3ff363ae9 100644 --- a/rust/chains/hyperlane-cosmos/Cargo.toml +++ b/rust/chains/hyperlane-cosmos/Cargo.toml @@ -28,6 +28,7 @@ hyper = { workspace = true } hyper-tls = { workspace = true } sha256 = { workspace = true } hex = { workspace = true } +tendermint = { workspace = true, features = ["rust-crypto", "secp256k1"]} hpl-interface = { version = "0.0.2" } hyperlane-core = { path = "../../hyperlane-core" } diff --git a/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs b/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs index 6ac1dd5240..373d71a1fc 100644 --- a/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs @@ -1,5 +1,7 @@ +use std::str::FromStr; + use crate::{ - address::bech32_decode, + address::CosmosAddress, grpc::{WasmGrpcProvider, WasmProvider}, payloads::aggregate_ism::{ModulesAndThresholdRequest, ModulesAndThresholdResponse}, ConnectionConf, CosmosProvider, Signer, @@ -60,8 +62,11 @@ impl AggregationIsm for CosmosAggregationIsm { let data = self.provider.wasm_query(payload, None).await?; let response: ModulesAndThresholdResponse = serde_json::from_slice(&data)?; - let modules: ChainResult> = - response.modules.into_iter().map(bech32_decode).collect(); + let modules: ChainResult> = response + .modules + .into_iter() + .map(|module| CosmosAddress::from_str(&module).map(|ca| ca.digest())) + .collect(); Ok((modules?, response.threshold)) } diff --git a/rust/chains/hyperlane-cosmos/src/libs/address.rs b/rust/chains/hyperlane-cosmos/src/libs/address.rs index 5fef0c7b48..b5170ed9f7 100644 --- a/rust/chains/hyperlane-cosmos/src/libs/address.rs +++ b/rust/chains/hyperlane-cosmos/src/libs/address.rs @@ -1,23 +1,13 @@ use std::str::FromStr; -use cosmrs::{crypto::secp256k1::SigningKey, AccountId}; +use cosmrs::{ + crypto::{secp256k1::SigningKey, PublicKey}, + AccountId, +}; use derive_new::new; -use hyperlane_core::{ChainResult, Error::Overflow, H256}; - -/// decode bech32 address to H256 -pub fn bech32_decode(addr: String) -> ChainResult { - let account_id = AccountId::from_str(&addr)?; - let bytes = account_id.to_bytes(); - let h256_len = H256::len_bytes(); - let Some(start_point) = h256_len.checked_sub(bytes.len()) else { - // input is too large to fit in a H256 - return Err(Overflow.into()); - }; - let mut empty_hash = H256::default(); - let result = empty_hash.as_bytes_mut(); - result[start_point..].copy_from_slice(bytes.as_slice()); - Ok(H256::from_slice(result)) -} +use hyperlane_core::{ChainCommunicationError, ChainResult, Error::Overflow, H256}; +use tendermint::account::Id as TendermintAccountId; +use tendermint::public_key::PublicKey as TendermintPublicKey; /// Wrapper around the cosmrs AccountId type that abstracts keypair conversions and /// bech32 encoding @@ -30,22 +20,46 @@ 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(pub_key: &[u8], prefix: &str) -> ChainResult { - let account_id = AccountId::new(prefix, pub_key)?; - let digest = bech32_decode(account_id.to_string())?; - Ok(Self { account_id, digest }) + pub fn from_pubkey(pubkey: PublicKey, prefix: &str) -> ChainResult { + // Get the inner type + let tendermint_pubkey = TendermintPublicKey::from(pubkey); + // Get the RIPEMD160(SHA256(pubkey)) + let tendermint_id = TendermintAccountId::from(tendermint_pubkey); + // Bech32 encoding + let account_id = AccountId::new(prefix, tendermint_id.as_bytes())?; + // Hex digest + let digest = Self::bech32_decode(&account_id)?; + Ok(CosmosAddress::new(account_id, digest)) } /// Creates a wrapper arround a cosmrs AccountId from a private key byte array pub fn from_privkey(priv_key: &[u8], prefix: &str) -> ChainResult { - let pubkey = SigningKey::from_slice(priv_key)?.public_key().to_bytes(); - Self::from_pubkey(&pubkey, prefix) + let pubkey = SigningKey::from_slice(priv_key)?.public_key(); + Self::from_pubkey(pubkey, prefix) } /// Creates a wrapper arround a cosmrs AccountId from a H256 digest + /// + /// - digest: H256 digest (hex version of address) + /// - prefix: Bech32 prefix pub fn from_h256(digest: H256, prefix: &str) -> ChainResult { let bytes = digest.as_bytes(); - CosmosAddress::from_pubkey(bytes, prefix) + // Bech32 encoding + let account_id = AccountId::new(prefix, bytes)?; + Ok(CosmosAddress::new(account_id, digest)) + } + + fn bech32_decode(account_id: &AccountId) -> ChainResult { + let bytes = account_id.to_bytes(); + let h256_len = H256::len_bytes(); + let Some(start_point) = h256_len.checked_sub(bytes.len()) else { + // input is too large to fit in a H256 + return Err(Overflow.into()); + }; + let mut empty_hash = H256::default(); + let result = empty_hash.as_bytes_mut(); + result[start_point..].copy_from_slice(bytes.as_slice()); + Ok(H256::from_slice(result)) } /// String representation of a cosmos AccountId @@ -59,9 +73,34 @@ impl CosmosAddress { } } -/// encode H256 to bech32 address -pub fn pub_to_addr(pub_key: Vec, prefix: &str) -> ChainResult { - Ok(CosmosAddress::from_pubkey(&pub_key, prefix)?.address()) +impl TryFrom<&CosmosAddress> for H256 { + type Error = ChainCommunicationError; + + fn try_from(cosmos_address: &CosmosAddress) -> Result { + let bytes = cosmos_address.account_id.to_bytes(); + let h256_len = H256::len_bytes(); + let Some(start_point) = h256_len.checked_sub(bytes.len()) else { + // input is too large to fit in a H256 + return Err(Overflow.into()); + }; + let mut empty_hash = H256::default(); + let result = empty_hash.as_bytes_mut(); + result[start_point..].copy_from_slice(bytes.as_slice()); + Ok(H256::from_slice(result)) + } +} + +impl FromStr for CosmosAddress { + type Err = ChainCommunicationError; + + fn from_str(s: &str) -> Result { + let account_id = AccountId::from_str(s)?; + // Temporarily set the digest to a default value. + // Can implement H256::try_from for AccountId to avoid this. + let mut cosmos_address = CosmosAddress::new(account_id, Default::default()); + cosmos_address.digest = H256::try_from(&cosmos_address)?; + Ok(cosmos_address) + } } #[cfg(test)] @@ -73,10 +112,10 @@ pub mod test { #[test] fn test_bech32_decode() { let addr = "dual1pk99xge6q94qtu3568x3qhp68zzv0mx7za4ct008ks36qhx5tvss3qawfh"; - let decoded = - bech32_decode(addr.to_string()).expect("decoding of a valid address shouldn't panic"); + let cosmos_address = CosmosAddress::from_str(addr).unwrap(); assert_eq!( - decoded, + CosmosAddress::bech32_decode(&cosmos_address.account_id) + .expect("decoding of a valid address shouldn't panic"), H256::from_str("0d8a53233a016a05f234d1cd105c3a3884c7ecde176b85bde7b423a05cd45b21") .unwrap() ); @@ -91,7 +130,7 @@ pub mod test { .expect("Cosmos address creation failed"); assert_eq!( addr.address(), - "neutron1qvxyspfhvy6xth4w240lvxngp6k0ytskd9w4uxpve4lrzjdm050uqxvtda6" + "neutron1kknekjxg0ear00dky5ykzs8wwp2gz62z9s6aaj" ); } } diff --git a/rust/chains/hyperlane-cosmos/src/mailbox.rs b/rust/chains/hyperlane-cosmos/src/mailbox.rs index feb2088f26..73ed319c7f 100644 --- a/rust/chains/hyperlane-cosmos/src/mailbox.rs +++ b/rust/chains/hyperlane-cosmos/src/mailbox.rs @@ -4,6 +4,7 @@ use std::{ io::Cursor, num::NonZeroU64, ops::RangeInclusive, + str::FromStr, }; use crate::address::CosmosAddress; @@ -14,7 +15,7 @@ use crate::payloads::mailbox::{ use crate::payloads::{general, mailbox}; use crate::rpc::{CosmosWasmIndexer, ParsedEvent, WasmIndexer}; use crate::CosmosProvider; -use crate::{address, signers::Signer, utils::get_block_height_for_lag, ConnectionConf}; +use crate::{signers::Signer, utils::get_block_height_for_lag, ConnectionConf}; use async_trait::async_trait; use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; use cosmrs::proto::cosmos::tx::v1beta1::SimulateResponse; @@ -150,8 +151,8 @@ impl Mailbox for CosmosMailbox { let response: mailbox::RecipientIsmResponse = serde_json::from_slice(&data)?; // convert Hex to H256 - let ism = address::bech32_decode(response.ism)?; - Ok(ism) + let ism = CosmosAddress::from_str(&response.ism)?; + Ok(ism.digest()) } #[instrument(err, ret, skip(self))] diff --git a/rust/chains/hyperlane-cosmos/src/providers/rpc.rs b/rust/chains/hyperlane-cosmos/src/providers/rpc.rs index bf11c3b17b..111ed4881d 100644 --- a/rust/chains/hyperlane-cosmos/src/providers/rpc.rs +++ b/rust/chains/hyperlane-cosmos/src/providers/rpc.rs @@ -27,17 +27,17 @@ pub trait WasmIndexer: Send + Sync { parser: for<'a> fn(&'a Vec) -> ChainResult>, ) -> ChainResult> where - T: Send + Sync + 'static; + T: Send + Sync + PartialEq + 'static; } #[derive(Debug, Eq, PartialEq)] /// An event parsed from the RPC response. -pub struct ParsedEvent { +pub struct ParsedEvent { contract_address: String, event: T, } -impl ParsedEvent { +impl ParsedEvent { /// Create a new ParsedEvent. pub fn new(contract_address: String, event: T) -> Self { Self { @@ -99,7 +99,7 @@ impl CosmosWasmIndexer { parser: for<'a> fn(&'a Vec) -> ChainResult>, ) -> ChainResult + '_> where - T: 'static, + T: PartialEq + 'static, { let logs_iter = txs .into_iter() @@ -127,7 +127,7 @@ impl CosmosWasmIndexer { parser: for<'a> fn(&'a Vec) -> ChainResult>, ) -> impl Iterator + '_ where - T: 'static, + T: PartialEq + 'static, { tx.tx_result.events.into_iter().enumerate().filter_map(move |(log_idx, event)| { if event.kind.as_str() != self.target_event_kind { @@ -189,7 +189,7 @@ impl WasmIndexer for CosmosWasmIndexer { parser: for<'a> fn(&'a Vec) -> ChainResult>, ) -> ChainResult> where - T: Send + Sync + 'static, + T: PartialEq + Send + Sync + 'static, { // Page starts from 1 let query = Query::default() diff --git a/rust/chains/hyperlane-cosmos/src/routing_ism.rs b/rust/chains/hyperlane-cosmos/src/routing_ism.rs index bb8c58ea00..497aedcc87 100644 --- a/rust/chains/hyperlane-cosmos/src/routing_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/routing_ism.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use async_trait::async_trait; use hyperlane_core::{ @@ -6,7 +8,7 @@ use hyperlane_core::{ }; use crate::{ - address::bech32_decode, + address::CosmosAddress, grpc::{WasmGrpcProvider, WasmProvider}, payloads::ism_routes::{ IsmRouteRequest, IsmRouteRequestInner, IsmRouteRespnose, QueryRoutingIsmGeneralRequest, @@ -72,6 +74,6 @@ impl RoutingIsm for CosmosRoutingIsm { .await?; let response: IsmRouteRespnose = serde_json::from_slice(&data)?; - Ok(bech32_decode(response.ism)?) + Ok(CosmosAddress::from_str(&response.ism)?.digest()) } } diff --git a/rust/utils/run-locally/src/cosmos/link.rs b/rust/utils/run-locally/src/cosmos/link.rs index 46bc5cabcc..1e65aa4b25 100644 --- a/rust/utils/run-locally/src/cosmos/link.rs +++ b/rust/utils/run-locally/src/cosmos/link.rs @@ -92,7 +92,6 @@ fn link_network( target_domain: u32, ) { let validator_addr = validator.addr(hrp); - let _validator_pubkey = validator.pub_key_to_binary(); let dest_domain = if network.domain == 26657 { 26658