diff --git a/registry.json b/registry.json index 6117b5d844e..76a4ce09650 100644 --- a/registry.json +++ b/registry.json @@ -3943,7 +3943,7 @@ "coinId": 20009001, "symbol": "EVMOS", "decimals": 18, - "blockchain": "Cosmos", + "blockchain": "NativeEvmos", "chainId": "evmos_9001-2", "derivation": [ { diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 62a7eeabf34..2508ba241b6 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1449,6 +1449,7 @@ dependencies = [ "tw_any_coin", "tw_coin_entry", "tw_coin_registry", + "tw_cosmos_sdk", "tw_encoding", "tw_keypair", "tw_memory", @@ -1517,6 +1518,7 @@ dependencies = [ "tw_keypair", "tw_memory", "tw_misc", + "tw_native_evmos", "tw_ronin", ] @@ -1540,6 +1542,7 @@ dependencies = [ "serde_json", "tw_bech32_address", "tw_coin_entry", + "tw_cosmos_sdk", "tw_encoding", "tw_hash", "tw_keypair", @@ -1682,6 +1685,17 @@ dependencies = [ "tw_memory", ] +[[package]] +name = "tw_native_evmos" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_keypair", + "tw_memory", + "tw_proto", +] + [[package]] name = "tw_number" version = "0.1.0" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 9a56201089e..3257cf37364 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "chains/tw_cosmos", + "chains/tw_native_evmos", "tw_any_coin", "tw_bech32_address", "tw_bitcoin", diff --git a/rust/chains/tw_native_evmos/Cargo.toml b/rust/chains/tw_native_evmos/Cargo.toml new file mode 100644 index 00000000000..cc4762a7971 --- /dev/null +++ b/rust/chains/tw_native_evmos/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tw_native_evmos" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk", features = ["test-utils"] } diff --git a/rust/chains/tw_native_evmos/src/context.rs b/rust/chains/tw_native_evmos/src/context.rs new file mode 100644 index 00000000000..c4522fb37d1 --- /dev/null +++ b/rust/chains/tw_native_evmos/src/context.rs @@ -0,0 +1,20 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ethermint_public_key::EthermintEthSecp256PublicKey; +use tw_cosmos_sdk::address::Address; +use tw_cosmos_sdk::context::CosmosContext; +use tw_cosmos_sdk::hasher::keccak256_hasher::Keccak256Hasher; +use tw_cosmos_sdk::private_key::secp256k1::Secp256PrivateKey; + +pub struct NativeEvmosContext; + +impl CosmosContext for NativeEvmosContext { + type Address = Address; + type TxHasher = Keccak256Hasher; + type PrivateKey = Secp256PrivateKey; + type PublicKey = EthermintEthSecp256PublicKey; +} diff --git a/rust/chains/tw_native_evmos/src/entry.rs b/rust/chains/tw_native_evmos/src/entry.rs new file mode 100644 index 00000000000..54ff53b55f2 --- /dev/null +++ b/rust/chains/tw_native_evmos/src/entry.rs @@ -0,0 +1,74 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::NativeEvmosContext; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::AddressResult; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::prefix::NoPrefix; +use tw_cosmos_sdk::address::{Address, CosmosAddress}; +use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; +use tw_keypair::tw; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct NativeEvmosEntry; + +impl CoinEntry for NativeEvmosEntry { + type AddressPrefix = NoPrefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + Address::from_str_with_coin(coin, address) + } + + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: tw::PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + Address::with_public_key_coin_context(coin, &public_key) + } + + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + TWSigner::::sign(coin, input) + } + + fn preimage_hashes( + &self, + _coin: &dyn CoinContext, + _input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + todo!() + } + + fn compile( + &self, + _coin: &dyn CoinContext, + _input: Self::SigningInput<'_>, + _signatures: Vec, + _public_keys: Vec, + ) -> Self::SigningOutput { + todo!() + } +} diff --git a/rust/tw_cosmos_sdk/src/public_key/ethermint_eth_secp256k1.rs b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs similarity index 55% rename from rust/tw_cosmos_sdk/src/public_key/ethermint_eth_secp256k1.rs rename to rust/chains/tw_native_evmos/src/ethermint_public_key.rs index 4c25d79c581..5f53f261e5b 100644 --- a/rust/tw_cosmos_sdk/src/public_key/ethermint_eth_secp256k1.rs +++ b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs @@ -4,34 +4,44 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -use crate::proto::ethermint; -use crate::public_key::secp256k1::prepare_secp256k1_public_key; -use crate::public_key::{CosmosPublicKey, JsonPublicKey, ProtobufPublicKey}; use tw_coin_entry::coin_context::CoinContext; -use tw_keypair::tw; +use tw_cosmos_sdk::proto::ethermint; +use tw_cosmos_sdk::public_key::{CosmosPublicKey, JsonPublicKey, ProtobufPublicKey}; +use tw_keypair::ecdsa::secp256k1; use tw_keypair::KeyPairResult; +use tw_keypair::{tw, KeyPairError}; use tw_memory::Data; use tw_proto::{google, to_any}; -// TODO move to tw_ethermint blockchain pub struct EthermintEthSecp256PublicKey { public_key: Data, } +impl EthermintEthSecp256PublicKey { + pub fn new(public_key: &secp256k1::PublicKey) -> KeyPairResult { + Ok(EthermintEthSecp256PublicKey { + // NativeEvmos chain requires the public key to be compressed. + // This trick is needed because `registry.json` contains extended public key type. + public_key: public_key.compressed().to_vec(), + }) + } +} + impl CosmosPublicKey for EthermintEthSecp256PublicKey { fn from_private_key(coin: &dyn CoinContext, private_key: &tw::PrivateKey) -> KeyPairResult where Self: Sized, { - let public_key = private_key.get_public_key_by_type(coin.public_key_type())?; - Ok(EthermintEthSecp256PublicKey { - public_key: public_key.to_bytes(), - }) + let tw_public_key = private_key.get_public_key_by_type(coin.public_key_type())?; + let secp256k1_key = tw_public_key + .to_secp256k1() + .ok_or(KeyPairError::InvalidPublicKey)?; + EthermintEthSecp256PublicKey::new(secp256k1_key) } - fn from_bytes(coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult { - let public_key = prepare_secp256k1_public_key(coin, public_key_bytes)?; - Ok(EthermintEthSecp256PublicKey { public_key }) + fn from_bytes(_coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult { + let public_key = secp256k1::PublicKey::try_from(public_key_bytes)?; + EthermintEthSecp256PublicKey::new(&public_key) } fn to_bytes(&self) -> Data { diff --git a/rust/chains/tw_native_evmos/src/lib.rs b/rust/chains/tw_native_evmos/src/lib.rs new file mode 100644 index 00000000000..3a0db84a1a8 --- /dev/null +++ b/rust/chains/tw_native_evmos/src/lib.rs @@ -0,0 +1,9 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod context; +pub mod ethermint_public_key; +pub mod entry; diff --git a/rust/tw_any_coin/Cargo.toml b/rust/tw_any_coin/Cargo.toml index 7ea537e73c2..01824db63ed 100644 --- a/rust/tw_any_coin/Cargo.toml +++ b/rust/tw_any_coin/Cargo.toml @@ -15,6 +15,7 @@ test-utils = [] [dev-dependencies] tw_any_coin = { path = "./", features = ["test-utils"] } +tw_cosmos_sdk = { path = "../tw_cosmos_sdk", features = ["test-utils"] } tw_encoding = { path = "../tw_encoding" } tw_keypair = { path = "../tw_keypair", features = ["test-utils"] } tw_memory = { path = "../tw_memory", features = ["test-utils"] } diff --git a/rust/tw_any_coin/tests/chain_tests.rs b/rust/tw_any_coin/tests/chain_tests.rs new file mode 100644 index 00000000000..c1b24a82062 --- /dev/null +++ b/rust/tw_any_coin/tests/chain_tests.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod chains; diff --git a/rust/tw_any_coin/tests/chains/mod.rs b/rust/tw_any_coin/tests/chains/mod.rs new file mode 100644 index 00000000000..d376e463622 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod native_evmos; diff --git a/rust/tw_any_coin/tests/chains/native_evmos/mod.rs b/rust/tw_any_coin/tests/chains/native_evmos/mod.rs new file mode 100644 index 00000000000..4d62bbecabe --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_evmos/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod native_evmos_sign; diff --git a/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_sign.rs b/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_sign.rs new file mode 100644 index 00000000000..7c2bfcce848 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_sign.rs @@ -0,0 +1,122 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; +use tw_coin_entry::error::SigningErrorType; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_encoding::hex::DecodeHex; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; +use tw_proto::{deserialize, serialize}; + +const NATIVE_EVMOS_COIN_TYPE: u32 = 20009001; + +fn account_1037_private_key() -> Cow<'static, [u8]> { + "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005" + .decode_hex() + .unwrap() + .into() +} + +fn account_2139877_private_key() -> Cow<'static, [u8]> { + "79bcbded1a5678ab34e6d9db9ad78e4e480e7b22723cc5fbf59e843732e1a8e5" + .decode_hex() + .unwrap() + .into() +} + +#[test] +fn test_sign_native_evmos_tx_json() { + let send_msg = Proto::mod_Message::Send { + from_address: "evmos1hsk6jryyqjfhp5dhc55tc9jtckygx0ep4mur4z".into(), + to_address: "evmos1zt50azupanqlfam5afhv3hexwyutnuke45f6ye".into(), + amounts: vec![make_amount("muon", "1")], + ..Proto::mod_Message::Send::default() + }; + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::JSON, + account_number: 1037, + chain_id: "evmos_9001-2".into(), + sequence: 8, + fee: Some(make_fee(200000, make_amount("muon", "200"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::send_coins_message(send_msg))], + ..Proto::SigningInput::default() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = + TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), NATIVE_EVMOS_COIN_TYPE) }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + assert_eq!( + output.json, + r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"evmos1hsk6jryyqjfhp5dhc55tc9jtckygx0ep4mur4z","to_address":"evmos1zt50azupanqlfam5afhv3hexwyutnuke45f6ye"}}],"signatures":[{"pub_key":{"type":"ethermint/PubKeyEthSecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg=="}]}}"# + ); + assert_eq!( + output.signature_json, + r#"[{"pub_key":{"type":"ethermint/PubKeyEthSecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg=="}]"# + ); +} + +/// CompoundingAuthz +#[test] +fn test_sign_native_evmos_tx_protobuf() { + use Proto::mod_Message::mod_AuthGrant::OneOfgrant_type as ProtoGrantType; + use Proto::mod_Message::mod_StakeAuthorization::OneOfvalidators as ProtoValidatorsType; + + let allow_list = Proto::mod_Message::mod_StakeAuthorization::Validators { + address: vec!["evmosvaloper1umk407eed7af6anvut6llg2zevnf0dn0feqqny".into()], + }; + let stake_authorization = Proto::mod_Message::StakeAuthorization { + authorization_type: Proto::mod_Message::AuthorizationType::UNDELEGATE, + validators: ProtoValidatorsType::allow_list(allow_list), + ..Proto::mod_Message::StakeAuthorization::default() + }; + let auth_grant = Proto::mod_Message::AuthGrant { + granter: "evmos12m9grgas60yk0kult076vxnsrqz8xpjy9rpf3e".into(), + grantee: "evmos18fzq4nac28gfma6gqfvkpwrgpm5ctar2z9mxf3".into(), + grant_type: ProtoGrantType::grant_stake(stake_authorization), + expiration: 1692309600, + }; + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Protobuf, + account_number: 2139877, + chain_id: "evmos_9001-2".into(), + sequence: 3, + fee: Some(make_fee(180859, make_amount("aevmos", "4521475000000000"))), + private_key: account_2139877_private_key(), + messages: vec![make_message(MessageEnum::auth_grant(auth_grant))], + ..Proto::SigningInput::default() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = + TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), NATIVE_EVMOS_COIN_TYPE) }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + // Successfully broadcasted https://www.mintscan.io/evmos/txs/8D811CEC078420C41220F0B584EA0AC037763380FAC31E0E352E4BB4D1D18B79 + // TODO the signature has been updated according to the serialization of the `StakeAuthorization` message. + // Previous: CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5EjUKM2V2bW9zdmFsb3BlcjF1bWs0MDdlZWQ3YWY2YW52dXQ2bGxnMnpldm5mMGRuMGZlcXFueSABEgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkAm17CZgB7m+CPVlITnrHosklMTL9zrUeGRs8FL8N0GcRami9zdJ+e3xuXOtJmwP7G6QNh85CRYjFj8a8lpmmJM + assert_eq!( + output.serialized, + r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5IAISNQozZXZtb3N2YWxvcGVyMXVtazQwN2VlZDdhZjZhbnZ1dDZsbGcyemV2bmYwZG4wZmVxcW55EgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkDu0sBTTeGfvUm+W6opsAcvBPLU51CFCtQOc1NCMrwRfGrUm+UBDN0NF3p1V8Ucn1tV9eLv7sXH13t/whbe7sTc"}"# + ); +} diff --git a/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs b/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs index 7bfacef868c..a568c0b23b0 100644 --- a/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs +++ b/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs @@ -43,6 +43,7 @@ fn test_any_address_derive() { // TODO fix this when `CoinType` is generated by a codegen tool. BlockchainType::Cosmos => continue, BlockchainType::Ethereum => "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309", + BlockchainType::NativeEvmos => "evmos14s0vgnj0pjnazu4hsqlksdk7slah9vcfvt8ssm", BlockchainType::Ronin => "ronin:Ac1ec44E4f0ca7D172B7803f6836De87Fb72b309", BlockchainType::InternetComputer => { "290cc7c359f44c8516fc169c5ed4f0f3ae2e24bf5de0d4c51f5e7545b5474faa" @@ -83,6 +84,10 @@ fn test_any_address_normalize_eth() { "0xb16db98b365b1f89191996942612b14f1da4bd5f", "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f", ), + BlockchainType::NativeEvmos => ( + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34", + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34", + ), BlockchainType::Ronin => ( "0xb16db98b365b1f89191996942612b14f1da4bd5f", "ronin:b16Db98B365B1f89191996942612B14F1Da4Bd5f", @@ -130,6 +135,10 @@ fn test_any_address_is_valid_coin() { "0xb16db98b365b1f89191996942612b14f1da4bd5f", "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f", ], + BlockchainType::NativeEvmos => vec![ + "evmos14py36sx57ud82t9yrks9z6hdsrpn5x6k0r05np", + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34" + ], BlockchainType::Ronin => vec![ "0xb16db98b365b1f89191996942612b14f1da4bd5f", "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f", @@ -172,6 +181,7 @@ fn test_any_address_is_valid_coin_invalid() { "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278bce", "553357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278", ], + BlockchainType::NativeEvmos => vec!["evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw"], BlockchainType::Unsupported => unreachable!(), }; diff --git a/rust/tw_bech32_address/src/lib.rs b/rust/tw_bech32_address/src/lib.rs index 8c2ba00558e..f06d56eecb6 100644 --- a/rust/tw_bech32_address/src/lib.rs +++ b/rust/tw_bech32_address/src/lib.rs @@ -123,7 +123,7 @@ impl FromStr for Bech32Address { fn from_str(s: &str) -> Result { let bech32::Decoded { hrp, bytes } = - bech32::decode(&s).map_err(|_| AddressError::InvalidInput)?; + bech32::decode(s).map_err(|_| AddressError::InvalidInput)?; Ok(Bech32Address { hrp, key_hash: bytes, diff --git a/rust/tw_coin_registry/Cargo.toml b/rust/tw_coin_registry/Cargo.toml index 00626538cbc..a73a2963020 100644 --- a/rust/tw_coin_registry/Cargo.toml +++ b/rust/tw_coin_registry/Cargo.toml @@ -17,4 +17,5 @@ tw_internet_computer = { path = "../tw_internet_computer" } tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } tw_misc = { path = "../tw_misc" } +tw_native_evmos = { path = "../chains/tw_native_evmos" } tw_ronin = { path = "../tw_ronin" } diff --git a/rust/tw_coin_registry/src/blockchain_type.rs b/rust/tw_coin_registry/src/blockchain_type.rs index cec63b24e3b..a5d49efb885 100644 --- a/rust/tw_coin_registry/src/blockchain_type.rs +++ b/rust/tw_coin_registry/src/blockchain_type.rs @@ -17,6 +17,7 @@ pub enum BlockchainType { Cosmos, Ethereum, InternetComputer, + NativeEvmos, Ronin, Unsupported, } @@ -41,6 +42,7 @@ impl FromStr for BlockchainType { "Ethereum" => Ok(BlockchainType::Ethereum), "InternetComputer" => Ok(BlockchainType::InternetComputer), "Ronin" => Ok(BlockchainType::Ronin), + "NativeEvmos" => Ok(BlockchainType::NativeEvmos), _ => Ok(BlockchainType::Unsupported), } } diff --git a/rust/tw_coin_registry/src/dispatcher.rs b/rust/tw_coin_registry/src/dispatcher.rs index 916fa6660a1..59e30e497f1 100644 --- a/rust/tw_coin_registry/src/dispatcher.rs +++ b/rust/tw_coin_registry/src/dispatcher.rs @@ -15,6 +15,7 @@ use tw_cosmos::entry::CosmosEntry; use tw_ethereum::entry::EthereumEntry; use tw_evm::evm_entry::EvmEntryExt; use tw_internet_computer::entry::InternetComputerEntry; +use tw_native_evmos::entry::NativeEvmosEntry; use tw_ronin::entry::RoninEntry; pub type CoinEntryExtStaticRef = &'static dyn CoinEntryExt; @@ -24,6 +25,7 @@ const BITCOIN: BitcoinEntry = BitcoinEntry; const COSMOS: CosmosEntry = CosmosEntry; const ETHEREUM: EthereumEntry = EthereumEntry; const INTERNET_COMPUTER: InternetComputerEntry = InternetComputerEntry; +const NATIVE_EVMOS: NativeEvmosEntry = NativeEvmosEntry; const RONIN: RoninEntry = RoninEntry; pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult { @@ -32,6 +34,7 @@ pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult Ok(&COSMOS), BlockchainType::Ethereum => Ok(ÐEREUM), BlockchainType::InternetComputer => Ok(&INTERNET_COMPUTER), + BlockchainType::NativeEvmos => Ok(&NATIVE_EVMOS), BlockchainType::Ronin => Ok(&RONIN), BlockchainType::Unsupported => Err(RegistryError::Unsupported), } @@ -53,6 +56,7 @@ pub fn evm_dispatcher(coin: CoinType) -> RegistryResult { BlockchainType::Cosmos => Err(RegistryError::Unsupported), BlockchainType::Ethereum => Ok(ÐEREUM), BlockchainType::InternetComputer => Err(RegistryError::Unsupported), + BlockchainType::NativeEvmos => Err(RegistryError::Unsupported), BlockchainType::Ronin => Ok(&RONIN), BlockchainType::Unsupported => Err(RegistryError::Unsupported), } diff --git a/rust/tw_cosmos_sdk/Cargo.toml b/rust/tw_cosmos_sdk/Cargo.toml index 435f7527643..c2b1be4fd94 100644 --- a/rust/tw_cosmos_sdk/Cargo.toml +++ b/rust/tw_cosmos_sdk/Cargo.toml @@ -22,6 +22,7 @@ tw_proto = { path = "../tw_proto" } [dev-dependencies] tw_coin_entry = { path = "../tw_coin_entry", features = ["test-utils"] } +tw_cosmos_sdk = { path = "./", features = ["test-utils"] } tw_misc = { path = "../tw_misc", features = ["test-utils"] } [build-dependencies] diff --git a/rust/tw_cosmos_sdk/src/hasher/keccak256_hasher.rs b/rust/tw_cosmos_sdk/src/hasher/keccak256_hasher.rs new file mode 100644 index 00000000000..21ca81fb087 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/hasher/keccak256_hasher.rs @@ -0,0 +1,21 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::hasher::CosmosHasher; +use tw_hash::sha3::keccak256; +use tw_memory::Data; + +pub struct Keccak256Hasher; + +impl CosmosHasher for Keccak256Hasher { + fn hash_sign_doc(sign_doc: &[u8]) -> Data { + keccak256(sign_doc) + } + + fn hash_json_tx(json: &str) -> Data { + keccak256(json.as_bytes()) + } +} diff --git a/rust/tw_cosmos_sdk/src/hasher/mod.rs b/rust/tw_cosmos_sdk/src/hasher/mod.rs index c9ef347464f..7422d204961 100644 --- a/rust/tw_cosmos_sdk/src/hasher/mod.rs +++ b/rust/tw_cosmos_sdk/src/hasher/mod.rs @@ -6,6 +6,7 @@ use tw_memory::Data; +pub mod keccak256_hasher; pub mod sha256_hasher; pub trait CosmosHasher { diff --git a/rust/tw_cosmos_sdk/src/public_key/mod.rs b/rust/tw_cosmos_sdk/src/public_key/mod.rs index cc52689c604..c03a84f74b3 100644 --- a/rust/tw_cosmos_sdk/src/public_key/mod.rs +++ b/rust/tw_cosmos_sdk/src/public_key/mod.rs @@ -9,7 +9,6 @@ use tw_keypair::{tw, KeyPairResult}; use tw_memory::Data; use tw_proto::google; -pub mod ethermint_eth_secp256k1; pub mod injective_eth_secp256k1; pub mod secp256k1; diff --git a/rust/tw_cosmos_sdk/src/test_utils/proto_utils.rs b/rust/tw_cosmos_sdk/src/test_utils/proto_utils.rs index 323f626d18b..5bef94cc9eb 100644 --- a/rust/tw_cosmos_sdk/src/test_utils/proto_utils.rs +++ b/rust/tw_cosmos_sdk/src/test_utils/proto_utils.rs @@ -21,7 +21,7 @@ pub fn make_fee_none(gas: u64) -> Proto::Fee<'static> { } } -pub fn make_message<'a>(message_oneof: MessageEnum<'a>) -> Proto::Message<'a> { +pub fn make_message(message_oneof: MessageEnum) -> Proto::Message { Proto::Message { message_oneof } } diff --git a/rust/tw_cosmos_sdk/src/transaction/message/wasm_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/wasm_message.rs index 5db431f2018..47e8921658f 100644 --- a/rust/tw_cosmos_sdk/src/transaction/message/wasm_message.rs +++ b/rust/tw_cosmos_sdk/src/transaction/message/wasm_message.rs @@ -29,7 +29,7 @@ impl ExecuteMsg { /// Tries to convert [`ExecuteMsg::String`] to [`ExecuteMsg::Json`], otherwise returns the same object. pub fn try_to_json(&self) -> ExecuteMsg { if let ExecuteMsg::String(s) = self { - if let Ok(json) = serde_json::from_str(&s) { + if let Ok(json) = serde_json::from_str(s) { return ExecuteMsg::Json(json); } } diff --git a/rust/tw_cosmos_sdk/tests/sign.rs b/rust/tw_cosmos_sdk/tests/sign.rs index bba48c53f34..aed3750c834 100644 --- a/rust/tw_cosmos_sdk/tests/sign.rs +++ b/rust/tw_cosmos_sdk/tests/sign.rs @@ -18,7 +18,7 @@ use tw_proto::Common::Proto::SigningError; use tw_proto::Cosmos::Proto; use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; -fn account_1337_private_key() -> Cow<'static, [u8]> { +fn account_1037_private_key() -> Cow<'static, [u8]> { "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005" .decode_hex() .unwrap() @@ -81,7 +81,7 @@ fn test_sign_coin_send() { chain_id: "gaia-13003".into(), sequence: 8, fee: Some(make_fee(200000, make_amount("muon", "200"))), - private_key: account_1337_private_key(), + private_key: account_1037_private_key(), messages: vec![make_message(MessageEnum::send_coins_message(send_msg))], ..Proto::SigningInput::default() }; @@ -117,7 +117,7 @@ fn test_sign_raw_json() { chain_id: "gaia-13003".into(), sequence: 8, fee: Some(make_fee(200000, make_amount("muon", "200"))), - private_key: account_1337_private_key(), + private_key: account_1037_private_key(), messages: vec![make_message(MessageEnum::raw_json_message(raw_json_msg))], ..Proto::SigningInput::default() }; @@ -197,7 +197,7 @@ fn test_sign_direct() { let mut input = Proto::SigningInput { account_number: 1037, chain_id: "gaia-13003".into(), - private_key: account_1337_private_key(), + private_key: account_1037_private_key(), messages: vec![make_message(MessageEnum::sign_direct_message(sign_direct))], ..Proto::SigningInput::default() }; @@ -242,7 +242,7 @@ fn test_sign_direct_0a90010a() { let mut input = Proto::SigningInput { account_number: 1, chain_id: "cosmoshub-4".into(), - private_key: account_1337_private_key(), + private_key: account_1037_private_key(), messages: vec![make_message(MessageEnum::sign_direct_message(sign_direct))], ..Proto::SigningInput::default() }; diff --git a/rust/tw_cosmos_sdk/tests/sign_staking.rs b/rust/tw_cosmos_sdk/tests/sign_staking.rs index f7e46dc96f3..2464b507d3c 100644 --- a/rust/tw_cosmos_sdk/tests/sign_staking.rs +++ b/rust/tw_cosmos_sdk/tests/sign_staking.rs @@ -17,7 +17,7 @@ use tw_proto::Common::Proto::SigningError; use tw_proto::Cosmos::Proto; use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; -fn account_1337_private_key() -> Cow<'static, [u8]> { +fn account_1037_private_key() -> Cow<'static, [u8]> { "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005" .decode_hex() .unwrap() @@ -141,7 +141,7 @@ fn test_staking_delegate() { chain_id: "gaia-13003".into(), sequence: 7, fee: Some(make_fee(101721, make_amount("muon", "1018"))), - private_key: account_1337_private_key(), + private_key: account_1037_private_key(), messages: vec![make_message(MessageEnum::stake_message(delegate))], mode: Proto::BroadcastMode::ASYNC, ..Proto::SigningInput::default() @@ -181,7 +181,7 @@ fn test_staking_undelegate() { chain_id: "gaia-13003".into(), sequence: 7, fee: Some(make_fee(101721, make_amount("muon", "1018"))), - private_key: account_1337_private_key(), + private_key: account_1037_private_key(), messages: vec![make_message(MessageEnum::unstake_message(undelegate))], mode: Proto::BroadcastMode::SYNC, ..Proto::SigningInput::default() @@ -222,7 +222,7 @@ fn test_staking_restake() { chain_id: "gaia-13003".into(), sequence: 7, fee: Some(make_fee(101721, make_amount("muon", "1018"))), - private_key: account_1337_private_key(), + private_key: account_1037_private_key(), messages: vec![make_message(MessageEnum::restake_message(redelegate))], ..Proto::SigningInput::default() }; @@ -260,7 +260,7 @@ fn test_staking_withdraw_rewards() { chain_id: "gaia-13003".into(), sequence: 7, fee: Some(make_fee(101721, make_amount("muon", "1018"))), - private_key: account_1337_private_key(), + private_key: account_1037_private_key(), messages: vec![make_message(MessageEnum::withdraw_stake_reward_message( withdraw, ))], @@ -300,7 +300,7 @@ fn test_staking_set_withdraw_address() { chain_id: "gaia-13003".into(), sequence: 7, fee: Some(make_fee(101721, make_amount("muon", "1018"))), - private_key: account_1337_private_key(), + private_key: account_1037_private_key(), messages: vec![make_message(MessageEnum::set_withdraw_address_message( set_address, ))], diff --git a/tests/chains/Evmos/SignerTests.cpp b/tests/chains/Evmos/SignerTests.cpp index 02ae7c9b45e..2bae6596aa9 100644 --- a/tests/chains/Evmos/SignerTests.cpp +++ b/tests/chains/Evmos/SignerTests.cpp @@ -62,9 +62,9 @@ TEST(EvmosSigner, SignTxJsonEthermintKeyType) { "pub_key": { "type":"ethermint/PubKeyEthSecp256k1", - "value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" + "value":"BFcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FWj3JBJiUsav6oXsWRf05KLv9Yt53nXEmm6fjB8Zl0wI=" }, - "signature":"RWt8aaBxdMAeEjym8toWskJ6WaJpEF9Ciucz2lAHkvNnTicGpzxwTUzJbJXRirSnGkejhISaYtDw2RBiq0vg5w==" + "signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg==" } ]} })"_json; @@ -82,7 +82,7 @@ TEST(EvmosSigner, SignTxJsonEthermintKeyType) { "type":"ethermint/PubKeyEthSecp256k1", "value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" }, - "signature":"RWt8aaBxdMAeEjym8toWskJ6WaJpEF9Ciucz2lAHkvNnTicGpzxwTUzJbJXRirSnGkejhISaYtDw2RBiq0vg5w==" + "signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg==" } ])"_json; EXPECT_EQ(signatures, expectedSignatures); @@ -116,9 +116,11 @@ TEST(EvmosSigner, CompoundingAuthz) { input.set_private_key(privateKey.data(), privateKey.size()); auto output = Signer::sign(input, TWCoinTypeNativeEvmos); + // TODO the signature has been updated according to the serialization of the `StakeAuthorization` message. + // Previous: CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5EjUKM2V2bW9zdmFsb3BlcjF1bWs0MDdlZWQ3YWY2YW52dXQ2bGxnMnpldm5mMGRuMGZlcXFueSABEgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkAm17CZgB7m+CPVlITnrHosklMTL9zrUeGRs8FL8N0GcRami9zdJ+e3xuXOtJmwP7G6QNh85CRYjFj8a8lpmmJM auto expected = R"( { - "mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5EjUKM2V2bW9zdmFsb3BlcjF1bWs0MDdlZWQ3YWY2YW52dXQ2bGxnMnpldm5mMGRuMGZlcXFueSABEgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkAm17CZgB7m+CPVlITnrHosklMTL9zrUeGRs8FL8N0GcRami9zdJ+e3xuXOtJmwP7G6QNh85CRYjFj8a8lpmmJM" + "mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5IAISNQozZXZtb3N2YWxvcGVyMXVtazQwN2VlZDdhZjZhbnZ1dDZsbGcyemV2bmYwZG4wZmVxcW55EgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkDu0sBTTeGfvUm+W6opsAcvBPLU51CFCtQOc1NCMrwRfGrUm+UBDN0NF3p1V8Ucn1tV9eLv7sXH13t/whbe7sTc" })"; assertJSONEqual(output.serialized(), expected); }