Skip to content

Commit

Permalink
fix: cosmos address building
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-savu committed Nov 7, 2023
1 parent c283034 commit a366115
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 46 deletions.
1 change: 1 addition & 0 deletions rust/Cargo.lock

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

1 change: 1 addition & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions rust/chains/hyperlane-cosmos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
11 changes: 8 additions & 3 deletions rust/chains/hyperlane-cosmos/src/aggregation_ism.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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<Vec<H256>> =
response.modules.into_iter().map(bech32_decode).collect();
let modules: ChainResult<Vec<H256>> = response
.modules
.into_iter()
.map(|module| CosmosAddress::from_str(&module).map(|ca| ca.digest()))
.collect();

Ok((modules?, response.threshold))
}
Expand Down
101 changes: 70 additions & 31 deletions rust/chains/hyperlane-cosmos/src/libs/address.rs
Original file line number Diff line number Diff line change
@@ -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<H256> {
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
Expand All @@ -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<Self> {
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<Self> {
// 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<Self> {
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<Self> {
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<H256> {
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
Expand All @@ -59,9 +73,34 @@ impl CosmosAddress {
}
}

/// encode H256 to bech32 address
pub fn pub_to_addr(pub_key: Vec<u8>, prefix: &str) -> ChainResult<String> {
Ok(CosmosAddress::from_pubkey(&pub_key, prefix)?.address())
impl TryFrom<&CosmosAddress> for H256 {
type Error = ChainCommunicationError;

fn try_from(cosmos_address: &CosmosAddress) -> Result<Self, Self::Error> {
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<Self, Self::Err> {
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)]
Expand All @@ -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()
);
Expand All @@ -91,7 +130,7 @@ pub mod test {
.expect("Cosmos address creation failed");
assert_eq!(
addr.address(),
"neutron1qvxyspfhvy6xth4w240lvxngp6k0ytskd9w4uxpve4lrzjdm050uqxvtda6"
"neutron1kknekjxg0ear00dky5ykzs8wwp2gz62z9s6aaj"
);
}
}
7 changes: 4 additions & 3 deletions rust/chains/hyperlane-cosmos/src/mailbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{
io::Cursor,
num::NonZeroU64,
ops::RangeInclusive,
str::FromStr,
};

use crate::address::CosmosAddress;
Expand All @@ -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;
Expand Down Expand Up @@ -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))]
Expand Down
12 changes: 6 additions & 6 deletions rust/chains/hyperlane-cosmos/src/providers/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,17 @@ pub trait WasmIndexer: Send + Sync {
parser: for<'a> fn(&'a Vec<EventAttribute>) -> ChainResult<ParsedEvent<T>>,
) -> ChainResult<Vec<(T, LogMeta)>>
where
T: Send + Sync + 'static;
T: Send + Sync + PartialEq + 'static;
}

#[derive(Debug, Eq, PartialEq)]
/// An event parsed from the RPC response.
pub struct ParsedEvent<T> {
pub struct ParsedEvent<T: PartialEq> {
contract_address: String,
event: T,
}

impl<T> ParsedEvent<T> {
impl<T: PartialEq> ParsedEvent<T> {
/// Create a new ParsedEvent.
pub fn new(contract_address: String, event: T) -> Self {
Self {
Expand Down Expand Up @@ -99,7 +99,7 @@ impl CosmosWasmIndexer {
parser: for<'a> fn(&'a Vec<EventAttribute>) -> ChainResult<ParsedEvent<T>>,
) -> ChainResult<impl Iterator<Item = (T, LogMeta)> + '_>
where
T: 'static,
T: PartialEq + 'static,
{
let logs_iter = txs
.into_iter()
Expand Down Expand Up @@ -127,7 +127,7 @@ impl CosmosWasmIndexer {
parser: for<'a> fn(&'a Vec<EventAttribute>) -> ChainResult<ParsedEvent<T>>,
) -> impl Iterator<Item = (T, LogMeta)> + '_
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 {
Expand Down Expand Up @@ -189,7 +189,7 @@ impl WasmIndexer for CosmosWasmIndexer {
parser: for<'a> fn(&'a Vec<EventAttribute>) -> ChainResult<ParsedEvent<T>>,
) -> ChainResult<Vec<(T, LogMeta)>>
where
T: Send + Sync + 'static,
T: PartialEq + Send + Sync + 'static,
{
// Page starts from 1
let query = Query::default()
Expand Down
6 changes: 4 additions & 2 deletions rust/chains/hyperlane-cosmos/src/routing_ism.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::str::FromStr;

use async_trait::async_trait;

use hyperlane_core::{
Expand All @@ -6,7 +8,7 @@ use hyperlane_core::{
};

use crate::{
address::bech32_decode,
address::CosmosAddress,
grpc::{WasmGrpcProvider, WasmProvider},
payloads::ism_routes::{
IsmRouteRequest, IsmRouteRequestInner, IsmRouteRespnose, QueryRoutingIsmGeneralRequest,
Expand Down Expand Up @@ -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())
}
}
1 change: 0 additions & 1 deletion rust/utils/run-locally/src/cosmos/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit a366115

Please sign in to comment.