From d4a0c8c1552e78be364b96c04b84f0a964cbea99 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Wed, 24 Jan 2024 16:12:39 +0700 Subject: [PATCH 01/11] feat(ZetaChain): Add `new-cosmos-chain` command --- codegen-v2/src/codegen/cpp/mod.rs | 8 +++ .../src/codegen/cpp/new_cosmos_chain.rs | 20 ++++++ .../cpp/tw_coin_type_tests_generator.rs | 13 +++- .../codegen/rust/coin_integration_tests.rs | 33 +++++++-- codegen-v2/src/codegen/rust/mod.rs | 1 + .../src/codegen/rust/new_cosmos_chain.rs | 16 +++++ .../integration_tests/cosmos_address_tests.rs | 69 +++++++++++++++++++ .../integration_tests/mod_address.rs | 5 ++ codegen-v2/src/codegen/template_generator.rs | 1 + codegen-v2/src/main.rs | 14 ++++ codegen-v2/src/registry.rs | 2 + .../native_evmos/native_evmos_address.rs | 15 ++++ 12 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 codegen-v2/src/codegen/cpp/new_cosmos_chain.rs create mode 100644 codegen-v2/src/codegen/rust/new_cosmos_chain.rs create mode 100644 codegen-v2/src/codegen/rust/templates/integration_tests/cosmos_address_tests.rs create mode 100644 codegen-v2/src/codegen/rust/templates/integration_tests/mod_address.rs diff --git a/codegen-v2/src/codegen/cpp/mod.rs b/codegen-v2/src/codegen/cpp/mod.rs index 3ddf0131626..1ef7188d86f 100644 --- a/codegen-v2/src/codegen/cpp/mod.rs +++ b/codegen-v2/src/codegen/cpp/mod.rs @@ -9,6 +9,7 @@ use std::path::PathBuf; pub mod blockchain_dispatcher_generator; pub mod entry_generator; pub mod new_blockchain; +pub mod new_cosmos_chain; pub mod new_evmchain; pub mod tw_any_address_tests_generator; pub mod tw_any_signer_tests_generator; @@ -41,3 +42,10 @@ pub fn coin_integration_tests_directory(coin: &CoinItem) -> PathBuf { .join("chains") .join(coin.coin_type()) } + +pub fn cosmos_coin_integration_tests_directory(coin: &CoinItem) -> PathBuf { + integration_tests_directory() + .join("chains") + .join("Cosmos") + .join(coin.coin_type()) +} diff --git a/codegen-v2/src/codegen/cpp/new_cosmos_chain.rs b/codegen-v2/src/codegen/cpp/new_cosmos_chain.rs new file mode 100644 index 00000000000..08c4406f994 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/new_cosmos_chain.rs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::cpp::tw_coin_address_derivation_tests_generator::CoinAddressDerivationTestsGenerator; +use crate::codegen::cpp::tw_coin_type_generator::TWCoinTypeGenerator; +use crate::codegen::cpp::tw_coin_type_tests_generator::TWCoinTypeTestsGenerator; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_cosmos_chain(coin: &CoinItem) -> Result<()> { + // Add the new coin type to the `TWCoinType` enum. + TWCoinTypeGenerator::generate_coin_type_variant(coin)?; + + // Add integration tests. + TWCoinTypeTestsGenerator::generate(coin)?; + CoinAddressDerivationTestsGenerator::generate_new_coin_type_case(coin)?; + + Ok(()) +} diff --git a/codegen-v2/src/codegen/cpp/tw_coin_type_tests_generator.rs b/codegen-v2/src/codegen/cpp/tw_coin_type_tests_generator.rs index 67edddac4ff..a3f9f50c5da 100644 --- a/codegen-v2/src/codegen/cpp/tw_coin_type_tests_generator.rs +++ b/codegen-v2/src/codegen/cpp/tw_coin_type_tests_generator.rs @@ -2,7 +2,9 @@ // // Copyright © 2017 Trust Wallet. -use crate::codegen::cpp::coin_integration_tests_directory; +use crate::codegen::cpp::{ + coin_integration_tests_directory, cosmos_coin_integration_tests_directory, +}; use crate::codegen::template_generator::TemplateGenerator; use crate::registry::CoinItem; use crate::Result; @@ -20,6 +22,15 @@ pub struct TWCoinTypeTestsGenerator; impl TWCoinTypeTestsGenerator { pub fn generate(coin: &CoinItem) -> Result<()> { let coin_tests_dir = coin_integration_tests_directory(coin); + Self::generate_at(coin, coin_tests_dir) + } + + pub fn generate_cosmos(coin: &CoinItem) -> Result<()> { + let cosmos_coin_tests_dir = cosmos_coin_integration_tests_directory(coin); + Self::generate_at(coin, cosmos_coin_tests_dir) + } + + fn generate_at(coin: &CoinItem, coin_tests_dir: PathBuf) -> Result<()> { let tw_coin_type_tests_path = coin_tests_dir.join("TWCoinTypeTests.cpp"); fs::create_dir(coin_tests_dir)?; diff --git a/codegen-v2/src/codegen/rust/coin_integration_tests.rs b/codegen-v2/src/codegen/rust/coin_integration_tests.rs index 2743d546066..6c4ce2b4e0f 100644 --- a/codegen-v2/src/codegen/rust/coin_integration_tests.rs +++ b/codegen-v2/src/codegen/rust/coin_integration_tests.rs @@ -12,8 +12,11 @@ use std::fs; use std::path::PathBuf; const ADDRESS_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/address_tests.rs"); +const COSMOS_ADDRESS_TESTS_TEMPLATE: &str = + include_str!("templates/integration_tests/cosmos_address_tests.rs"); const COMPILE_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/compile_tests.rs"); const MOD_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/mod.rs"); +const MOD_ADDRESS_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/mod_address.rs"); const SIGN_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/sign_tests.rs"); pub fn chains_integration_tests_directory() -> PathBuf { @@ -49,10 +52,28 @@ impl CoinIntegrationTests { fs::create_dir_all(&blockchain_tests_path)?; self.list_blockchain_in_chains_mod()?; - self.create_address_tests()?; + self.create_address_tests(ADDRESS_TESTS_TEMPLATE)?; self.create_compile_tests()?; self.create_sign_tests()?; - self.create_chain_tests_mod_rs()?; + self.create_chain_tests_mod_rs(MOD_TESTS_TEMPLATE)?; + + Ok(blockchain_tests_path) + } + + /// For a Cosmos chain, it's enough to create address tests only, but with additional Bech32 prefix tests. + pub fn create_cosmos(self) -> Result { + let blockchain_tests_path = self.coin_tests_directory(); + if blockchain_tests_path.exists() { + println!("[SKIP] integration tests already exists: {blockchain_tests_path:?}"); + return Ok(blockchain_tests_path); + } + + fs::create_dir_all(&blockchain_tests_path)?; + + self.list_blockchain_in_chains_mod()?; + self.create_address_tests(COSMOS_ADDRESS_TESTS_TEMPLATE)?; + // `mod.rs` should contain `{COIN_TYPE}_address` module only. + self.create_chain_tests_mod_rs(MOD_ADDRESS_TESTS_TEMPLATE)?; Ok(blockchain_tests_path) } @@ -61,14 +82,14 @@ impl CoinIntegrationTests { coin_integration_tests_directory(&self.coin.id) } - fn create_address_tests(&self) -> Result<()> { + fn create_address_tests(&self, template: &'static str) -> Result<()> { let coin_id = self.coin.id.as_str(); let address_tests_path = self .coin_tests_directory() .join(format!("{coin_id}_address.rs")); println!("[ADD] {address_tests_path:?}"); - TemplateGenerator::new(ADDRESS_TESTS_TEMPLATE) + TemplateGenerator::new(template) .write_to(address_tests_path) .with_default_patterns(&self.coin) .write() @@ -100,11 +121,11 @@ impl CoinIntegrationTests { .write() } - fn create_chain_tests_mod_rs(&self) -> Result<()> { + fn create_chain_tests_mod_rs(&self, template: &'static str) -> Result<()> { let blockchain_tests_mod_path = self.coin_tests_directory().join("mod.rs"); println!("[ADD] {blockchain_tests_mod_path:?}"); - TemplateGenerator::new(MOD_TESTS_TEMPLATE) + TemplateGenerator::new(template) .write_to(blockchain_tests_mod_path) .with_default_patterns(&self.coin) .write() diff --git a/codegen-v2/src/codegen/rust/mod.rs b/codegen-v2/src/codegen/rust/mod.rs index 5364b415fee..8905e725a32 100644 --- a/codegen-v2/src/codegen/rust/mod.rs +++ b/codegen-v2/src/codegen/rust/mod.rs @@ -12,6 +12,7 @@ pub mod coin_crate; pub mod coin_integration_tests; pub mod coin_registry_manifest_generator; pub mod new_blockchain; +pub mod new_cosmos_chain; pub mod new_evmchain; pub mod toml_editor; diff --git a/codegen-v2/src/codegen/rust/new_cosmos_chain.rs b/codegen-v2/src/codegen/rust/new_cosmos_chain.rs new file mode 100644 index 00000000000..96151ff6446 --- /dev/null +++ b/codegen-v2/src/codegen/rust/new_cosmos_chain.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::rust::coin_address_derivation_test_generator::CoinAddressDerivationTestGenerator; +use crate::codegen::rust::coin_integration_tests::CoinIntegrationTests; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_cosmos_chain(coin: &CoinItem) -> Result<()> { + // Create integration tests. + CoinIntegrationTests::new(coin.clone()).create_cosmos()?; + CoinAddressDerivationTestGenerator::generate_new_coin_type_case(coin)?; + + Ok(()) +} diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/cosmos_address_tests.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/cosmos_address_tests.rs new file mode 100644 index 00000000000..6bf6079ac9d --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/cosmos_address_tests.rs @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_any_coin::test_utils::address_utils::{ + test_address_bech32_is_valid, test_address_create_bech32_with_public_key, + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, + AddressBech32IsValid, AddressCreateBech32WithPublicKey, +}; +use tw_coin_registry::coin_type::CoinType; +use tw_keypair::tw::PublicKeyType; + +#[test] +fn test_{COIN_ID}_address_normalization() { + test_address_normalization(CoinType::{COIN_TYPE}, "DENORMALIZED", "EXPECTED"); +} + +#[test] +fn test_{COIN_ID}_address_is_valid() { + test_address_valid(CoinType::{COIN_TYPE}, "VALID {COIN_TYPE} ADDRESS"); +} + +#[test] +fn test_{COIN_ID}_address_invalid() { + test_address_invalid(CoinType::{COIN_TYPE}, "INVALID ADDRESS"); + // Cosmos has a different `hrp`. + test_address_invalid(CoinType::Cosmos, "VALID {COIN_TYPE} ADDRESS"); +} + +#[test] +fn test_{COIN_ID}_address_get_data() { + test_address_get_data(CoinType::{COIN_TYPE}, "ADDRESS", "HEX(DATA)"); +} + +#[test] +fn test_{COIN_ID}_is_valid_bech32() { + // {COIN_TYPE} address must be valid if its Base32 prefix passed. + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::{COIN_TYPE}, + address: "{COIN_TYPE} ADDRESS", + hrp: "{HRP}", + }); + // {COIN_TYPE} address must be valid for the standard Cosmos hub if its Base32 prefix passed. + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::Cosmos, + address: "{COIN_TYPE} ADDRESS", + hrp: "{HRP}", + }); + // Cosmos address must be valid with "cosmos" hrp. + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::{COIN_TYPE}, + address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", + hrp: "cosmos", + }); +} + +#[test] +fn test_any_address_create_bech32_with_public_key() { + test_address_create_bech32_with_public_key(AddressCreateBech32WithPublicKey { + // TODO consider using `CoinType::{COIN_TYPE}` if the chain's `addressHasher` is different from Cosmos's. + coin: CoinType::Cosmos, + private_key: "PRIVATE_KEY", + // TODO consider using another `PublicKeyType` if the chain's `publicKeyType` is different from Cosmos's. + public_key_type: PublicKeyType::Secp256k1, + hrp: "{HRP}", + expected: "{COIN_TYPE} ADDRESS", + }); +} + diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/mod_address.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/mod_address.rs new file mode 100644 index 00000000000..6f501e180bc --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/mod_address.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +mod {COIN_ID}_address; diff --git a/codegen-v2/src/codegen/template_generator.rs b/codegen-v2/src/codegen/template_generator.rs index 684116c514d..724066d47ac 100644 --- a/codegen-v2/src/codegen/template_generator.rs +++ b/codegen-v2/src/codegen/template_generator.rs @@ -43,6 +43,7 @@ impl TemplateGenerator { .add_pattern("{DECIMALS}", coin.decimals) .add_pattern("{P2PKH_PREFIX}", coin.p2pkh_prefix) .add_pattern("{P2SH_PREFIX}", coin.p2sh_prefix) + .add_pattern("{HRP}", coin.hrp.as_str()) .add_pattern("{STATIC_PREFIX}", coin.static_prefix) .add_pattern("{EXPLORER_URL}", &coin.explorer.url) .add_pattern("{EXPLORER_TX_PATH}", &coin.explorer.tx_path) diff --git a/codegen-v2/src/main.rs b/codegen-v2/src/main.rs index 9bd35a8b9ee..53fd140cc65 100644 --- a/codegen-v2/src/main.rs +++ b/codegen-v2/src/main.rs @@ -21,6 +21,7 @@ fn main() -> Result<()> { "new-blockchain-rust" => new_blockchain_rust(&args[2..]), "new-blockchain" => new_blockchain(&args[2..]), "new-evmchain" => new_evmchain(&args[2..]), + "new-cosmos-chain" => new_cosmos_chain(&args[2..]), "swift" => generate_swift_bindings(), _ => Err(Error::InvalidCommand), } @@ -64,6 +65,19 @@ fn new_evmchain(args: &[String]) -> Result<()> { Ok(()) } +fn new_cosmos_chain(args: &[String]) -> Result<()> { + let coin_str = args.iter().next().ok_or_else(|| Error::InvalidCommand)?; + let coin_id = CoinId::new(coin_str.clone())?; + let coin_item = read_coin_from_registry(&coin_id)?; + + println!("New '{coin_str}' Cosmos chain template requested"); + + rust::new_cosmos_chain::new_cosmos_chain(&coin_item)?; + cpp::new_cosmos_chain::new_cosmos_chain(&coin_item)?; + + Ok(()) +} + fn generate_swift_bindings() -> Result<()> { // NOTE: The paths will be configurable, eventually. const OUT_DIR: &str = "bindings/"; diff --git a/codegen-v2/src/registry.rs b/codegen-v2/src/registry.rs index 17eda663c7c..853f0d2249e 100644 --- a/codegen-v2/src/registry.rs +++ b/codegen-v2/src/registry.rs @@ -47,6 +47,8 @@ pub struct CoinItem { #[serde(rename = "staticPrefix")] #[serde(default)] pub static_prefix: u8, + #[serde(default)] + pub hrp: String, pub explorer: CoinExplorer, } diff --git a/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_address.rs b/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_address.rs index 6800702b545..00b620ec8a3 100644 --- a/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_address.rs +++ b/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_address.rs @@ -37,6 +37,11 @@ fn test_native_evmos_address_invalid() { CoinType::NativeEvmos, "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw", ); + // Cosmos has a different `hrp`. + test_address_invalid( + CoinType::Cosmos, + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34", + ); } #[test] @@ -55,6 +60,16 @@ fn test_any_address_is_valid_bech32() { address: "evmos14py36sx57ud82t9yrks9z6hdsrpn5x6k0r05np", hrp: "evmos", }); + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::NativeEvmos, + address: "evmos14py36sx57ud82t9yrks9z6hdsrpn5x6k0r05np", + hrp: "evmos", + }); + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::NativeEvmos, + address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", + hrp: "cosmos", + }); } #[test] From 08d51e45793bf880c4e1dec6ad646e5a11e1c249 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Wed, 24 Jan 2024 17:45:35 +0700 Subject: [PATCH 02/11] feat(ZetaChain): Add `NativeZetaChain` mainnet --- docs/registry.md | 1 + include/TrustWalletCore/TWCoinType.h | 1 + registry.json | 30 +++++++ rust/tw_any_coin/tests/chains/mod.rs | 1 + .../tw_any_coin/tests/chains/zetachain/mod.rs | 5 ++ .../chains/zetachain/zetachain_address.rs | 87 +++++++++++++++++++ .../tests/coin_address_derivation_test.rs | 1 + .../NativeZetaChain/TWCoinTypeTests.cpp | 29 +++++++ tests/common/CoinAddressDerivationTests.cpp | 3 + 9 files changed, 158 insertions(+) create mode 100644 rust/tw_any_coin/tests/chains/zetachain/mod.rs create mode 100644 rust/tw_any_coin/tests/chains/zetachain/zetachain_address.rs create mode 100644 tests/chains/NativeZetaChain/TWCoinTypeTests.cpp diff --git a/docs/registry.md b/docs/registry.md index 490965bc8b1..558ee6febf3 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -123,6 +123,7 @@ This list is generated from [./registry.json](../registry.json) | 10002020 | Ronin | RON | | | | 10002222 | KavaEvm | KAVA | | | | 10004689 | IoTeX EVM | IOTX | | | +| 10007000 | NativeZetaChain | ZETA | | | | 10007700 | NativeCanto | CANTO | | | | 10008217 | Klaytn | KLAY | | | | 10009000 | Avalanche C-Chain | AVAX | | | diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index f18f8ff9c67..64e5b5f9225 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -178,6 +178,7 @@ enum TWCoinType { TWCoinTypeInternetComputer = 223, TWCoinTypeTia = 21000118, TWCoinTypeMantaPacific = 169, + TWCoinTypeNativeZetaChain = 10007000, // end_of_tw_coin_type_marker_do_not_modify }; diff --git a/registry.json b/registry.json index 1e33dfc115b..9adb374df71 100644 --- a/registry.json +++ b/registry.json @@ -4421,6 +4421,36 @@ "documentation": "https://docs.canto.io/" } }, + { + "id": "zetachain", + "name": "NativeZetaChain", + "displayName": "NativeZetaChain", + "coinId": 10007000, + "symbol": "ZETA", + "decimals": 18, + "blockchain": "Cosmos", + "chainId": "zetachain_7000", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "hrp": "zeta", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.zetachain.com/cosmos", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "2DBB071DDD47985F4470A21E5943CE95D371AE4BDE2267E201D3553FB2BDCFDE", + "sampleAccount": "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne" + }, + "info": { + "url": "https://www.zetachain.com/", + "documentation": "https://www.zetachain.com/docs/" + } + }, { "id": "ton", "name": "TON", diff --git a/rust/tw_any_coin/tests/chains/mod.rs b/rust/tw_any_coin/tests/chains/mod.rs index 38c46ed262e..7a9f7285560 100644 --- a/rust/tw_any_coin/tests/chains/mod.rs +++ b/rust/tw_any_coin/tests/chains/mod.rs @@ -13,3 +13,4 @@ mod native_evmos; mod native_injective; mod tbinance; mod thorchain; +mod zetachain; diff --git a/rust/tw_any_coin/tests/chains/zetachain/mod.rs b/rust/tw_any_coin/tests/chains/zetachain/mod.rs new file mode 100644 index 00000000000..1b26a3ca277 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/zetachain/mod.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +mod zetachain_address; diff --git a/rust/tw_any_coin/tests/chains/zetachain/zetachain_address.rs b/rust/tw_any_coin/tests/chains/zetachain/zetachain_address.rs new file mode 100644 index 00000000000..374fc39cda6 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/zetachain/zetachain_address.rs @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_any_coin::test_utils::address_utils::{ + test_address_bech32_is_valid, test_address_create_bech32_with_public_key, + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, + AddressBech32IsValid, AddressCreateBech32WithPublicKey, +}; +use tw_coin_registry::coin_type::CoinType; +use tw_keypair::tw::PublicKeyType; + +#[test] +fn test_zetachain_address_normalization() { + test_address_normalization( + CoinType::NativeZetaChain, + "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne", + "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne", + ); +} + +#[test] +fn test_zetachain_address_is_valid() { + test_address_valid( + CoinType::NativeZetaChain, + "zeta1em87eul072u3yz4vpgm9mdfszhu0clxekuw08f", + ); + test_address_valid( + CoinType::NativeZetaChain, + "zeta17xpfvakm2amg962yls6f84z3kell8c5lxad43d", + ); +} + +#[test] +fn test_zetachain_address_invalid() { + test_address_invalid( + CoinType::NativeZetaChain, + "zeta17xpfvakm2amg962yls6f84z3kell8c5lxad4", + ); + // Cosmos has a different `hrp`. + test_address_invalid( + CoinType::Cosmos, + "zeta17xpfvakm2amg962yls6f84z3kell8c5lxad43d", + ); +} + +#[test] +fn test_zetachain_address_get_data() { + test_address_get_data( + CoinType::NativeZetaChain, + "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne", + "A8491D40D4F71A752CA41DA0516AED80C33A1B56", + ); +} + +#[test] +fn test_zetachain_is_valid_bech32() { + // NativeZetaChain address must be valid if its Base32 prefix passed. + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::NativeZetaChain, + address: "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne", + hrp: "zeta", + }); + // NativeZetaChain address must be valid for the standard Cosmos hub if its Base32 prefix passed. + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::Cosmos, + address: "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne", + hrp: "zeta", + }); + // Cosmos address must be valid with "cosmos" hrp. + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::NativeZetaChain, + address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", + hrp: "cosmos", + }); +} + +#[test] +fn test_any_address_create_bech32_with_public_key() { + test_address_create_bech32_with_public_key(AddressCreateBech32WithPublicKey { + coin: CoinType::NativeZetaChain, + private_key: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed", + public_key_type: PublicKeyType::Secp256k1Extended, + hrp: "zeta", + expected: "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne", + }); +} diff --git a/rust/tw_any_coin/tests/coin_address_derivation_test.rs b/rust/tw_any_coin/tests/coin_address_derivation_test.rs index cd1c64283d2..09bd78cd283 100644 --- a/rust/tw_any_coin/tests/coin_address_derivation_test.rs +++ b/rust/tw_any_coin/tests/coin_address_derivation_test.rs @@ -145,6 +145,7 @@ fn test_coin_address_derivation() { CoinType::InternetComputer => "290cc7c359f44c8516fc169c5ed4f0f3ae2e24bf5de0d4c51f5e7545b5474faa", CoinType::Binance => "bnb1ten42eesehw0ktddcp0fws7d3ycsqez3aqvnpg", CoinType::TBinance => "tbnb1ten42eesehw0ktddcp0fws7d3ycsqez3n49hpe", + CoinType::NativeZetaChain => "zeta14s0vgnj0pjnazu4hsqlksdk7slah9vcfcwctsr", // end_of_coin_address_derivation_tests_marker_do_not_modify _ => panic!("{:?} must be covered", coin), }; diff --git a/tests/chains/NativeZetaChain/TWCoinTypeTests.cpp b/tests/chains/NativeZetaChain/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..1de95b3cd92 --- /dev/null +++ b/tests/chains/NativeZetaChain/TWCoinTypeTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TWNativeZetaChainCoinType, TWCoinType) { + const auto coin = TWCoinTypeNativeZetaChain; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("2DBB071DDD47985F4470A21E5943CE95D371AE4BDE2267E201D3553FB2BDCFDE")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "zetachain"); + assertStringsEqual(name, "NativeZetaChain"); + assertStringsEqual(symbol, "ZETA"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0); + assertStringsEqual(txUrl, "https://explorer.zetachain.com/cosmos/tx/2DBB071DDD47985F4470A21E5943CE95D371AE4BDE2267E201D3553FB2BDCFDE"); + assertStringsEqual(accUrl, "https://explorer.zetachain.com/cosmos/account/zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne"); +} diff --git a/tests/common/CoinAddressDerivationTests.cpp b/tests/common/CoinAddressDerivationTests.cpp index 77695e734eb..6aa4c4604b8 100644 --- a/tests/common/CoinAddressDerivationTests.cpp +++ b/tests/common/CoinAddressDerivationTests.cpp @@ -384,6 +384,9 @@ TEST(Coin, DeriveAddress) { case TWCoinTypeTia: EXPECT_EQ(address, "celestia1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0g3wnkv"); break; + case TWCoinTypeNativeZetaChain: + EXPECT_EQ(address, "zeta1nk9x9ajk4rgkzhqjjn7hr6w0k0jg2kj027x9uy"); + break; // end_of_coin_address_derivation_tests_marker_do_not_modify // no default branch here, intentionally, to better notice any missing coins } From 72dcf5289634a4f8fd6117db96f3f507ab9287c5 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Wed, 24 Jan 2024 21:39:42 +0700 Subject: [PATCH 03/11] feat(ZetaChain): Add support for custom Cosmos chain --- rust/chains/tw_binance/src/address.rs | 10 +- rust/chains/tw_greenfield/src/address.rs | 12 +- rust/chains/tw_greenfield/src/public_key.rs | 8 +- .../src/ethermint_public_key.rs | 8 +- rust/chains/tw_native_injective/src/entry.rs | 6 +- .../src/injective_public_key.rs | 8 +- rust/chains/tw_thorchain/src/entry.rs | 6 +- rust/tw_cosmos_sdk/src/address.rs | 19 +-- .../src/modules/compiler/tw_compiler.rs | 12 +- .../src/modules/signer/tw_signer.rs | 8 +- rust/tw_cosmos_sdk/src/modules/tx_builder.rs | 123 ++++++++++-------- rust/tw_cosmos_sdk/src/public_key/mod.rs | 7 + .../tw_cosmos_sdk/src/public_key/secp256k1.rs | 32 ++++- rust/tw_proto/src/lib.rs | 8 ++ src/proto/Cosmos.proto | 8 ++ 15 files changed, 167 insertions(+), 108 deletions(-) diff --git a/rust/chains/tw_binance/src/address.rs b/rust/chains/tw_binance/src/address.rs index ec4cffad8f3..3c718a97bd8 100644 --- a/rust/chains/tw_binance/src/address.rs +++ b/rust/chains/tw_binance/src/address.rs @@ -30,15 +30,7 @@ impl CoinAddress for BinanceAddress { } } -impl CosmosAddress for BinanceAddress { - fn from_str_with_coin(coin: &dyn CoinContext, addr: &str) -> AddressResult - where - Self: Sized, - { - let prefix = None; - Self::from_str_with_coin_and_prefix(coin, addr.to_string(), prefix) - } -} +impl CosmosAddress for BinanceAddress {} impl BinanceAddress { pub const VALIDATOR_HRP: &'static str = "bva"; diff --git a/rust/chains/tw_greenfield/src/address.rs b/rust/chains/tw_greenfield/src/address.rs index 9318c48cecc..d0d4d794448 100644 --- a/rust/chains/tw_greenfield/src/address.rs +++ b/rust/chains/tw_greenfield/src/address.rs @@ -5,9 +5,8 @@ use serde::Serialize; use std::fmt; use std::str::FromStr; -use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::coin_entry::CoinAddress; -use tw_coin_entry::error::{AddressError, AddressResult}; +use tw_coin_entry::error::AddressError; use tw_cosmos_sdk::address::CosmosAddress; use tw_evm::address::Address as EthereumAddress; use tw_keypair::ecdsa::secp256k1; @@ -23,14 +22,7 @@ impl GreenfieldAddress { } } -impl CosmosAddress for GreenfieldAddress { - fn from_str_with_coin(_coin: &dyn CoinContext, addr: &str) -> AddressResult - where - Self: Sized, - { - GreenfieldAddress::from_str(addr) - } -} +impl CosmosAddress for GreenfieldAddress {} impl CoinAddress for GreenfieldAddress { #[inline] diff --git a/rust/chains/tw_greenfield/src/public_key.rs b/rust/chains/tw_greenfield/src/public_key.rs index a10f6d32954..b03ca413ee3 100644 --- a/rust/chains/tw_greenfield/src/public_key.rs +++ b/rust/chains/tw_greenfield/src/public_key.rs @@ -3,7 +3,9 @@ // Copyright © 2017 Trust Wallet. use tw_coin_entry::coin_context::CoinContext; -use tw_cosmos_sdk::public_key::{CosmosPublicKey, JsonPublicKey, ProtobufPublicKey}; +use tw_cosmos_sdk::public_key::{ + CosmosPublicKey, CustomPublicKeyType, JsonPublicKey, ProtobufPublicKey, +}; use tw_keypair::ecdsa::secp256k1; use tw_keypair::tw::{PrivateKey, PublicKeyType}; use tw_keypair::{KeyPairError, KeyPairResult}; @@ -41,6 +43,10 @@ impl CosmosPublicKey for GreenfieldPublicKey { secp256k1::PublicKey::try_from(public_key_bytes).map(GreenfieldPublicKey) } + fn with_custom_public_key_type(&mut self, _custom_type: CustomPublicKeyType) { + // Do nothing. Greenfield does not support custom public key type. + } + fn to_bytes(&self) -> Data { self.0.compressed().to_vec() } diff --git a/rust/chains/tw_native_evmos/src/ethermint_public_key.rs b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs index 0059dc31871..40c7b44daa8 100644 --- a/rust/chains/tw_native_evmos/src/ethermint_public_key.rs +++ b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs @@ -4,7 +4,9 @@ use tw_coin_entry::coin_context::CoinContext; use tw_cosmos_sdk::proto::ethermint; -use tw_cosmos_sdk::public_key::{CosmosPublicKey, JsonPublicKey, ProtobufPublicKey}; +use tw_cosmos_sdk::public_key::{ + CosmosPublicKey, CustomPublicKeyType, JsonPublicKey, ProtobufPublicKey, +}; use tw_keypair::ecdsa::secp256k1; use tw_keypair::KeyPairResult; use tw_keypair::{tw, KeyPairError}; @@ -42,6 +44,10 @@ impl CosmosPublicKey for EthermintEthSecp256PublicKey { EthermintEthSecp256PublicKey::new(&public_key) } + fn with_custom_public_key_type(&mut self, _custom_type: CustomPublicKeyType) { + // Do nothing. NativeEvmos does not support custom public key type. + } + fn to_bytes(&self) -> Data { self.public_key.clone() } diff --git a/rust/chains/tw_native_injective/src/entry.rs b/rust/chains/tw_native_injective/src/entry.rs index caf1cbdf3bf..a93d89c294e 100644 --- a/rust/chains/tw_native_injective/src/entry.rs +++ b/rust/chains/tw_native_injective/src/entry.rs @@ -12,7 +12,7 @@ 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::modules::wallet_connector::NoWalletConnector; -use tw_cosmos_sdk::address::{Address, Bech32Prefix, CosmosAddress}; +use tw_cosmos_sdk::address::{Address, Bech32Prefix}; use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; use tw_keypair::tw; @@ -37,9 +37,9 @@ impl CoinEntry for NativeInjectiveEntry { &self, coin: &dyn CoinContext, address: &str, - _prefix: Option, + prefix: Option, ) -> AddressResult { - Address::from_str_with_coin(coin, address) + Address::from_str_with_coin_and_prefix(coin, address.to_string(), prefix) } #[inline] diff --git a/rust/chains/tw_native_injective/src/injective_public_key.rs b/rust/chains/tw_native_injective/src/injective_public_key.rs index 47b01c575fd..9647da7fd7f 100644 --- a/rust/chains/tw_native_injective/src/injective_public_key.rs +++ b/rust/chains/tw_native_injective/src/injective_public_key.rs @@ -5,7 +5,9 @@ use tw_coin_entry::coin_context::CoinContext; use tw_cosmos_sdk::proto::injective; use tw_cosmos_sdk::public_key::secp256k1::prepare_secp256k1_public_key; -use tw_cosmos_sdk::public_key::{CosmosPublicKey, JsonPublicKey, ProtobufPublicKey}; +use tw_cosmos_sdk::public_key::{ + CosmosPublicKey, CustomPublicKeyType, JsonPublicKey, ProtobufPublicKey, +}; use tw_keypair::tw::PrivateKey; use tw_keypair::KeyPairResult; use tw_memory::Data; @@ -31,6 +33,10 @@ impl CosmosPublicKey for InjectiveEthSecp256PublicKey { Ok(InjectiveEthSecp256PublicKey { public_key }) } + fn with_custom_public_key_type(&mut self, _custom_type: CustomPublicKeyType) { + // Do nothing. NativeEvmos does not support custom public key type. + } + fn to_bytes(&self) -> Data { self.public_key.clone() } diff --git a/rust/chains/tw_thorchain/src/entry.rs b/rust/chains/tw_thorchain/src/entry.rs index 7ec28508188..838c10bd526 100644 --- a/rust/chains/tw_thorchain/src/entry.rs +++ b/rust/chains/tw_thorchain/src/entry.rs @@ -13,7 +13,7 @@ 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::modules::wallet_connector::NoWalletConnector; -use tw_cosmos_sdk::address::{Address, Bech32Prefix, CosmosAddress}; +use tw_cosmos_sdk::address::{Address, Bech32Prefix}; use tw_keypair::tw; use tw_proto::Cosmos::Proto; use tw_proto::TxCompiler::Proto as CompilerProto; @@ -36,9 +36,9 @@ impl CoinEntry for ThorchainEntry { &self, coin: &dyn CoinContext, address: &str, - _prefix: Option, + prefix: Option, ) -> AddressResult { - Address::from_str_with_coin(coin, address) + Address::from_str_with_coin_and_prefix(coin, address.to_string(), prefix) } #[inline] diff --git a/rust/tw_cosmos_sdk/src/address.rs b/rust/tw_cosmos_sdk/src/address.rs index 4f15075115d..10cbca9a10c 100644 --- a/rust/tw_cosmos_sdk/src/address.rs +++ b/rust/tw_cosmos_sdk/src/address.rs @@ -4,24 +4,11 @@ use serde::Serialize; use std::str::FromStr; -use tw_coin_entry::coin_context::CoinContext; -use tw_coin_entry::error::{AddressError, AddressResult}; +use tw_coin_entry::error::AddressError; pub type Address = tw_bech32_address::Bech32Address; pub type Bech32Prefix = tw_bech32_address::bech32_prefix::Bech32Prefix; -pub trait CosmosAddress: FromStr + Serialize + ToString { - fn from_str_with_coin(coin: &dyn CoinContext, addr: &str) -> AddressResult - where - Self: Sized; -} +pub trait CosmosAddress: FromStr + Serialize + ToString {} -impl CosmosAddress for Address { - fn from_str_with_coin(coin: &dyn CoinContext, addr: &str) -> AddressResult - where - Self: Sized, - { - let prefix = None; - Address::from_str_with_coin_and_prefix(coin, addr.to_string(), prefix) - } -} +impl CosmosAddress for Address {} diff --git a/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs b/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs index 6dbdeb2ceaa..6427b086c83 100644 --- a/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs +++ b/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs @@ -104,7 +104,11 @@ impl TWTransactionCompiler { public_key, } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; let signature = Context::Signature::try_from(&signature)?; - let public_key = Context::PublicKey::from_bytes(coin, &public_key)?; + + let mut public_key = Context::PublicKey::from_bytes(coin, &public_key)?; + if let Some(custom_type) = TxBuilder::::custom_public_key_type_from_proto(&input) { + public_key.with_custom_public_key_type(custom_type); + } let signed_tx_raw = match TxBuilder::::try_sign_direct_args(&input) { // If there was a `SignDirect` message in the signing input, generate the `TxRaw` directly. @@ -150,7 +154,11 @@ impl TWTransactionCompiler { public_key, } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; let signature = Context::Signature::try_from(&signature)?; - let public_key = Context::PublicKey::from_bytes(coin, &public_key)?; + + let mut public_key = Context::PublicKey::from_bytes(coin, &public_key)?; + if let Some(custom_type) = TxBuilder::::custom_public_key_type_from_proto(&input) { + public_key.with_custom_public_key_type(custom_type); + } // Set the public key. It will be used to construct a signer info. input.public_key = Cow::from(public_key.to_bytes()); diff --git a/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs b/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs index d06f16d673c..1a04d09f026 100644 --- a/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs +++ b/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs @@ -4,6 +4,7 @@ use crate::context::CosmosContext; use crate::modules::compiler::tw_compiler::TWTransactionCompiler; +use crate::modules::tx_builder::TxBuilder; use crate::private_key::CosmosPrivateKey; use crate::public_key::CosmosPublicKey; use std::borrow::Cow; @@ -32,7 +33,12 @@ impl TWSigner { mut input: Proto::SigningInput<'_>, ) -> SigningResult> { let private_key = Context::PrivateKey::try_from(&input.private_key)?; - let public_key = Context::PublicKey::from_private_key(coin, private_key.as_ref())?; + + let mut public_key = Context::PublicKey::from_private_key(coin, private_key.as_ref())?; + if let Some(custom_type) = TxBuilder::::custom_public_key_type_from_proto(&input) { + public_key.with_custom_public_key_type(custom_type); + } + // Set the public key. It will be used to construct a signer info. input.public_key = Cow::from(public_key.to_bytes()); diff --git a/rust/tw_cosmos_sdk/src/modules/tx_builder.rs b/rust/tw_cosmos_sdk/src/modules/tx_builder.rs index e08d8c47ba4..2acf558b877 100644 --- a/rust/tw_cosmos_sdk/src/modules/tx_builder.rs +++ b/rust/tw_cosmos_sdk/src/modules/tx_builder.rs @@ -2,10 +2,10 @@ // // Copyright © 2017 Trust Wallet. -use crate::address::{Address, CosmosAddress}; +use crate::address::Address; use crate::context::CosmosContext; use crate::modules::serializer::protobuf_serializer::SignDirectArgs; -use crate::public_key::CosmosPublicKey; +use crate::public_key::{CosmosPublicKey, CustomPublicKeyType}; use crate::transaction::message::cosmos_generic_message::JsonRawMessage; use crate::transaction::message::{CosmosMessage, CosmosMessageBox}; use crate::transaction::{Coin, Fee, SignMode, SignerInfo, TxBody, UnsignedTransaction}; @@ -53,7 +53,11 @@ where coin: &dyn CoinContext, input: &Proto::SigningInput, ) -> SigningResult> { - let public_key = Context::PublicKey::from_bytes(coin, &input.public_key)?; + let mut public_key = Context::PublicKey::from_bytes(coin, &input.public_key)?; + if let Some(custom_type) = Self::custom_public_key_type_from_proto(input) { + public_key.with_custom_public_key_type(custom_type); + } + Ok(SignerInfo { public_key, sequence: input.sequence, @@ -62,6 +66,18 @@ where }) } + pub fn custom_public_key_type_from_proto( + input: &Proto::SigningInput, + ) -> Option { + input + .custom_public_key_type + .clone() + .map(|custom_type| CustomPublicKeyType { + json_type: custom_type.json_type.to_string(), + protobuf_type_url: custom_type.protobuf_type_url.to_string(), + }) + } + fn fee_from_proto(input: &Proto::Fee) -> SigningResult> { let amounts = input .amounts @@ -197,7 +213,7 @@ where } pub fn send_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, send: &Proto::mod_Message::Send<'_>, ) -> SigningResult { use crate::transaction::message::cosmos_bank_message::SendMessage; @@ -209,15 +225,15 @@ where .collect::>()?; let msg = SendMessage { custom_type_prefix: send.type_prefix.to_string().empty_or_some(), - from_address: Address::from_str_with_coin(coin, &send.from_address)?, - to_address: Address::from_str_with_coin(coin, &send.to_address)?, + from_address: Address::from_str(&send.from_address)?, + to_address: Address::from_str(&send.to_address)?, amount: amounts, }; Ok(msg.into_boxed()) } pub fn transfer_tokens_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, transfer: &Proto::mod_Message::Transfer<'_>, ) -> SigningResult { use crate::transaction::message::ibc_message::{Height, TransferTokensMessage}; @@ -236,8 +252,7 @@ where source_port: transfer.source_port.to_string(), source_channel: transfer.source_channel.to_string(), token, - sender: Address::from_str_with_coin(coin, &transfer.sender)?, - // Don't use `Address::from_str_with_coin` as the recipient address can belong to another Cosmos chain. + sender: Address::from_str(&transfer.sender)?, receiver: Address::from_str(&transfer.receiver)?, timeout_height: Height { revision_number: height.revision_number, @@ -249,7 +264,7 @@ where } pub fn delegate_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, delegate: &Proto::mod_Message::Delegate<'_>, ) -> SigningResult { use crate::transaction::message::cosmos_staking_message::DelegateMessage; @@ -262,14 +277,14 @@ where let msg = DelegateMessage { custom_type_prefix: delegate.type_prefix.to_string().empty_or_some(), amount, - delegator_address: Address::from_str_with_coin(coin, &delegate.delegator_address)?, - validator_address: Address::from_str_with_coin(coin, &delegate.validator_address)?, + delegator_address: Address::from_str(&delegate.delegator_address)?, + validator_address: Address::from_str(&delegate.validator_address)?, }; Ok(msg.into_boxed()) } pub fn undelegate_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, undelegate: &Proto::mod_Message::Undelegate<'_>, ) -> SigningResult { use crate::transaction::message::cosmos_staking_message::UndelegateMessage; @@ -283,42 +298,42 @@ where let msg = UndelegateMessage { custom_type_prefix: undelegate.type_prefix.to_string().empty_or_some(), amount, - delegator_address: Address::from_str_with_coin(coin, &undelegate.delegator_address)?, - validator_address: Address::from_str_with_coin(coin, &undelegate.validator_address)?, + delegator_address: Address::from_str(&undelegate.delegator_address)?, + validator_address: Address::from_str(&undelegate.validator_address)?, }; Ok(msg.into_boxed()) } pub fn withdraw_reward_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, withdraw: &Proto::mod_Message::WithdrawDelegationReward<'_>, ) -> SigningResult { use crate::transaction::message::cosmos_staking_message::WithdrawDelegationRewardMessage; let msg = WithdrawDelegationRewardMessage { custom_type_prefix: withdraw.type_prefix.to_string().empty_or_some(), - delegator_address: Address::from_str_with_coin(coin, &withdraw.delegator_address)?, - validator_address: Address::from_str_with_coin(coin, &withdraw.validator_address)?, + delegator_address: Address::from_str(&withdraw.delegator_address)?, + validator_address: Address::from_str(&withdraw.validator_address)?, }; Ok(msg.into_boxed()) } pub fn set_withdraw_address_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, set: &Proto::mod_Message::SetWithdrawAddress<'_>, ) -> SigningResult { use crate::transaction::message::cosmos_staking_message::SetWithdrawAddressMessage; let msg = SetWithdrawAddressMessage { custom_type_prefix: set.type_prefix.to_string().empty_or_some(), - delegator_address: Address::from_str_with_coin(coin, &set.delegator_address)?, - withdraw_address: Address::from_str_with_coin(coin, &set.withdraw_address)?, + delegator_address: Address::from_str(&set.delegator_address)?, + withdraw_address: Address::from_str(&set.withdraw_address)?, }; Ok(msg.into_boxed()) } pub fn redelegate_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, redelegate: &Proto::mod_Message::BeginRedelegate<'_>, ) -> SigningResult { use crate::transaction::message::cosmos_staking_message::BeginRedelegateMessage; @@ -328,15 +343,13 @@ where .as_ref() .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; let amount = Self::coin_from_proto(amount)?; - let validator_src_address = - Address::from_str_with_coin(coin, &redelegate.validator_src_address)?; - let validator_dst_address = - Address::from_str_with_coin(coin, &redelegate.validator_dst_address)?; + let validator_src_address = Address::from_str(&redelegate.validator_src_address)?; + let validator_dst_address = Address::from_str(&redelegate.validator_dst_address)?; let msg = BeginRedelegateMessage { custom_type_prefix: redelegate.type_prefix.to_string().empty_or_some(), amount, - delegator_address: Address::from_str_with_coin(coin, &redelegate.delegator_address)?, + delegator_address: Address::from_str(&redelegate.delegator_address)?, validator_src_address, validator_dst_address, }; @@ -358,7 +371,7 @@ where } pub fn wasm_terra_execute_contract_transfer_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, transfer: &Proto::mod_Message::WasmTerraExecuteContractTransfer<'_>, ) -> SigningResult { use crate::transaction::message::terra_wasm_message::TerraExecuteContractMessage; @@ -370,8 +383,8 @@ where }; let msg = TerraExecuteContractMessage { - sender: Address::from_str_with_coin(coin, &transfer.sender_address)?, - contract: Address::from_str_with_coin(coin, &transfer.contract_address)?, + sender: Address::from_str(&transfer.sender_address)?, + contract: Address::from_str(&transfer.contract_address)?, execute_msg: ExecuteMsg::json(execute_payload)?, // Used in case you are sending native tokens along with this message. coins: Vec::default(), @@ -380,7 +393,7 @@ where } pub fn wasm_terra_execute_contract_send_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, send: &Proto::mod_Message::WasmTerraExecuteContractSend<'_>, ) -> SigningResult { use crate::transaction::message::terra_wasm_message::TerraExecuteContractMessage; @@ -393,8 +406,8 @@ where }; let msg = TerraExecuteContractMessage { - sender: Address::from_str_with_coin(coin, &send.sender_address)?, - contract: Address::from_str_with_coin(coin, &send.contract_address)?, + sender: Address::from_str(&send.sender_address)?, + contract: Address::from_str(&send.contract_address)?, execute_msg: ExecuteMsg::json(execute_payload)?, // Used in case you are sending native tokens along with this message. coins: Vec::default(), @@ -403,7 +416,7 @@ where } pub fn wasm_terra_execute_contract_generic_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, generic: &Proto::mod_Message::WasmTerraExecuteContractGeneric<'_>, ) -> SigningResult { use crate::transaction::message::terra_wasm_message::TerraExecuteContractMessage; @@ -416,8 +429,8 @@ where .collect::>()?; let msg = TerraExecuteContractMessage { - sender: Address::from_str_with_coin(coin, &generic.sender_address)?, - contract: Address::from_str_with_coin(coin, &generic.contract_address)?, + sender: Address::from_str(&generic.sender_address)?, + contract: Address::from_str(&generic.contract_address)?, execute_msg: ExecuteMsg::String(generic.execute_msg.to_string()), coins, }; @@ -425,7 +438,7 @@ where } pub fn wasm_execute_contract_transfer_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, transfer: &Proto::mod_Message::WasmExecuteContractTransfer<'_>, ) -> SigningResult { use crate::transaction::message::wasm_message::{ @@ -438,8 +451,8 @@ where }; let msg = WasmExecuteContractMessage { - sender: Address::from_str_with_coin(coin, &transfer.sender_address)?, - contract: Address::from_str_with_coin(coin, &transfer.contract_address)?, + sender: Address::from_str(&transfer.sender_address)?, + contract: Address::from_str(&transfer.contract_address)?, msg: ExecuteMsg::json(transfer_payload)?, // Used in case you are sending native tokens along with this message. coins: Vec::default(), @@ -448,7 +461,7 @@ where } pub fn wasm_execute_contract_send_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, send: &Proto::mod_Message::WasmExecuteContractSend<'_>, ) -> SigningResult { use crate::transaction::message::wasm_message::{ @@ -462,8 +475,8 @@ where }; let msg = WasmExecuteContractMessage { - sender: Address::from_str_with_coin(coin, &send.sender_address)?, - contract: Address::from_str_with_coin(coin, &send.contract_address)?, + sender: Address::from_str(&send.sender_address)?, + contract: Address::from_str(&send.contract_address)?, msg: ExecuteMsg::json(execute_payload)?, // Used in case you are sending native tokens along with this message. coins: Vec::default(), @@ -472,7 +485,7 @@ where } pub fn wasm_execute_contract_generic_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, generic: &Proto::mod_Message::WasmExecuteContractGeneric<'_>, ) -> SigningResult { use crate::transaction::message::wasm_message::{ExecuteMsg, WasmExecuteContractMessage}; @@ -484,8 +497,8 @@ where .collect::>()?; let msg = WasmExecuteContractMessage { - sender: Address::from_str_with_coin(coin, &generic.sender_address)?, - contract: Address::from_str_with_coin(coin, &generic.contract_address)?, + sender: Address::from_str(&generic.sender_address)?, + contract: Address::from_str(&generic.contract_address)?, msg: ExecuteMsg::String(generic.execute_msg.to_string()), coins, }; @@ -513,7 +526,7 @@ where } pub fn auth_grant_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, auth: &Proto::mod_Message::AuthGrant<'_>, ) -> SigningResult { use crate::transaction::message::cosmos_auth_message::AuthGrantMessage; @@ -533,8 +546,8 @@ where }; let msg = AuthGrantMessage { - granter: Address::from_str_with_coin(coin, &auth.granter)?, - grantee: Address::from_str_with_coin(coin, &auth.grantee)?, + granter: Address::from_str(&auth.granter)?, + grantee: Address::from_str(&auth.grantee)?, grant_msg, expiration_secs: auth.expiration, }; @@ -542,21 +555,21 @@ where } pub fn auth_revoke_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, auth: &Proto::mod_Message::AuthRevoke<'_>, ) -> SigningResult { use crate::transaction::message::cosmos_auth_message::AuthRevokeMessage; let msg = AuthRevokeMessage { - granter: Address::from_str_with_coin(coin, &auth.granter)?, - grantee: Address::from_str_with_coin(coin, &auth.grantee)?, + granter: Address::from_str(&auth.granter)?, + grantee: Address::from_str(&auth.grantee)?, msg_type_url: auth.msg_type_url.to_string(), }; Ok(msg.into_boxed()) } pub fn vote_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, vote: &Proto::mod_Message::MsgVote<'_>, ) -> SigningResult { use crate::transaction::message::cosmos_gov_message::{VoteMessage, VoteOption}; @@ -572,20 +585,20 @@ where let msg = VoteMessage { proposal_id: vote.proposal_id, - voter: Address::from_str_with_coin(coin, &vote.voter)?, + voter: Address::from_str(&vote.voter)?, option, }; Ok(msg.into_boxed()) } pub fn stride_stake_msg_from_proto( - coin: &dyn CoinContext, + _coin: &dyn CoinContext, stake: &Proto::mod_Message::MsgStrideLiquidStakingStake<'_>, ) -> SigningResult { use crate::transaction::message::stride_message::StrideLiquidStakeMessage; let msg = StrideLiquidStakeMessage { - creator: Address::from_str_with_coin(coin, &stake.creator)?, + creator: Address::from_str(&stake.creator)?, amount: U256::from_str(&stake.amount)?, host_denom: stake.host_denom.to_string(), }; diff --git a/rust/tw_cosmos_sdk/src/public_key/mod.rs b/rust/tw_cosmos_sdk/src/public_key/mod.rs index f7382521b91..17f10f910f5 100644 --- a/rust/tw_cosmos_sdk/src/public_key/mod.rs +++ b/rust/tw_cosmos_sdk/src/public_key/mod.rs @@ -9,6 +9,11 @@ use tw_proto::google; pub mod secp256k1; +pub struct CustomPublicKeyType { + pub json_type: String, + pub protobuf_type_url: String, +} + pub trait CosmosPublicKey: JsonPublicKey + ProtobufPublicKey + Sized { fn from_private_key( coin: &dyn CoinContext, @@ -17,6 +22,8 @@ pub trait CosmosPublicKey: JsonPublicKey + ProtobufPublicKey + Sized { fn from_bytes(coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult; + fn with_custom_public_key_type(&mut self, custom_type: CustomPublicKeyType); + fn to_bytes(&self) -> Data; } diff --git a/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs b/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs index 1b1d2509194..5dd7061f95c 100644 --- a/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs +++ b/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs @@ -3,18 +3,23 @@ // Copyright © 2017 Trust Wallet. use crate::proto::cosmos; -use crate::public_key::{CosmosPublicKey, JsonPublicKey, ProtobufPublicKey}; +use crate::public_key::{CosmosPublicKey, CustomPublicKeyType, JsonPublicKey, ProtobufPublicKey}; +use quick_protobuf::MessageInfo; use tw_coin_entry::coin_context::CoinContext; use tw_keypair::ecdsa::secp256k1; use tw_keypair::tw::{self, PublicKeyType}; use tw_keypair::{KeyPairError, KeyPairResult}; use tw_memory::Data; use tw_misc::traits::ToBytesVec; -use tw_proto::{google, to_any}; +use tw_proto::{google, to_any_with_type_url}; + +const DEFAULT_JSON_PUBLIC_KEY_TYPE: &str = "tendermint/PubKeySecp256k1"; #[derive(Clone)] pub struct Secp256PublicKey { public_key: Data, + json_type: String, + protobuf_type_url: String, } impl Secp256PublicKey { @@ -23,7 +28,11 @@ impl Secp256PublicKey { public_key: &secp256k1::PublicKey, ) -> KeyPairResult { let public_key = prepare_secp256k1_public_key(coin, public_key.compressed().as_slice())?; - Ok(Secp256PublicKey { public_key }) + Ok(Secp256PublicKey { + public_key, + json_type: DEFAULT_JSON_PUBLIC_KEY_TYPE.to_string(), + protobuf_type_url: format!("/{}", cosmos::crypto::secp256k1::PubKey::PATH), + }) } } @@ -35,12 +44,23 @@ impl CosmosPublicKey for Secp256PublicKey { let public_key = private_key.get_public_key_by_type(coin.public_key_type())?; Ok(Secp256PublicKey { public_key: public_key.to_bytes(), + json_type: DEFAULT_JSON_PUBLIC_KEY_TYPE.to_string(), + protobuf_type_url: format!("/{}", cosmos::crypto::secp256k1::PubKey::PATH), }) } fn from_bytes(coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult { let public_key = prepare_secp256k1_public_key(coin, public_key_bytes)?; - Ok(Secp256PublicKey { public_key }) + Ok(Secp256PublicKey { + public_key, + json_type: DEFAULT_JSON_PUBLIC_KEY_TYPE.to_string(), + protobuf_type_url: format!("/{}", cosmos::crypto::secp256k1::PubKey::PATH), + }) + } + + fn with_custom_public_key_type(&mut self, custom_type: CustomPublicKeyType) { + self.json_type = custom_type.json_type; + self.protobuf_type_url = custom_type.protobuf_type_url; } fn to_bytes(&self) -> Data { @@ -53,13 +73,13 @@ impl ProtobufPublicKey for Secp256PublicKey { let proto = cosmos::crypto::secp256k1::PubKey { key: self.public_key.clone(), }; - to_any(&proto) + to_any_with_type_url(&proto, self.protobuf_type_url.clone()) } } impl JsonPublicKey for Secp256PublicKey { fn public_key_type(&self) -> String { - "tendermint/PubKeySecp256k1".to_string() + self.json_type.clone() } } diff --git a/rust/tw_proto/src/lib.rs b/rust/tw_proto/src/lib.rs index 23a061c2f47..e8c013bb125 100644 --- a/rust/tw_proto/src/lib.rs +++ b/rust/tw_proto/src/lib.rs @@ -50,6 +50,14 @@ where google::protobuf::Any { type_url, value } } +pub fn to_any_with_type_url(message: &T, type_url: String) -> google::protobuf::Any +where + T: MessageInfo + MessageWrite, +{ + let value = serialize(message).expect("Protobuf serialization should never fail"); + google::protobuf::Any { type_url, value } +} + pub fn type_url() -> String { format!("/{}", T::PATH) } diff --git a/src/proto/Cosmos.proto b/src/proto/Cosmos.proto index 323ac93bb08..6daa56c0c33 100644 --- a/src/proto/Cosmos.proto +++ b/src/proto/Cosmos.proto @@ -367,6 +367,11 @@ enum SigningMode { Protobuf = 1; // Protobuf-serialized (binary), Stargate } +message CustomPublicKeyType { + string json_type = 1; + string protobuf_type_url = 2; +} + // Input data necessary to create a signed transaction. message SigningInput { // Specify if protobuf (a.k.a. Stargate) or earlier JSON serialization is used @@ -397,6 +402,9 @@ message SigningInput { BroadcastMode mode = 9; bytes public_key = 10; + + // Optional. Public Key Type used to sign a transaction and generate a JSON broadcast message. + CustomPublicKeyType custom_public_key_type = 11; } // Result containing the signed and encoded transaction. From ab7f7e8e4b5abdf1dcfe8f4a38c2f73fb2f454de Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Thu, 25 Jan 2024 12:51:11 +0700 Subject: [PATCH 04/11] feat(ZetaChain): Add `SignerInfo` to the Cosmos.proto protocol --- rust/Cargo.lock | 2 + rust/chains/tw_binance/src/compiler.rs | 4 +- rust/chains/tw_binance/src/context.rs | 9 ++- rust/chains/tw_binance/src/signer.rs | 3 +- rust/chains/tw_greenfield/src/compiler.rs | 3 +- rust/chains/tw_greenfield/src/context.rs | 9 ++- .../tw_greenfield/src/modules/tx_builder.rs | 4 +- rust/chains/tw_greenfield/src/public_key.rs | 25 ++++--- rust/chains/tw_native_evmos/Cargo.toml | 1 + rust/chains/tw_native_evmos/src/context.rs | 7 +- .../src/ethermint_public_key.rs | 27 +++---- rust/chains/tw_native_injective/Cargo.toml | 1 + .../chains/tw_native_injective/src/context.rs | 7 +- .../src/injective_public_key.rs | 26 ++----- .../tw_any_coin/tests/chains/zetachain/mod.rs | 1 + .../tests/chains/zetachain/zetachain_sign.rs | 75 +++++++++++++++++++ rust/tw_cosmos_sdk/src/context.rs | 11 ++- .../src/hasher/keccak256_hasher.rs | 19 ----- rust/tw_cosmos_sdk/src/hasher/mod.rs | 14 ---- .../tw_cosmos_sdk/src/hasher/sha256_hasher.rs | 19 ----- rust/tw_cosmos_sdk/src/lib.rs | 1 - .../src/modules/compiler/json_preimager.rs | 9 ++- .../modules/compiler/protobuf_preimager.rs | 9 ++- .../src/modules/compiler/tw_compiler.rs | 20 +++-- .../src/modules/signer/tw_signer.rs | 6 +- rust/tw_cosmos_sdk/src/modules/tx_builder.rs | 39 ++++++---- rust/tw_cosmos_sdk/src/public_key/mod.rs | 21 ++++-- .../tw_cosmos_sdk/src/public_key/secp256k1.rs | 53 ++++++------- rust/tw_proto/src/lib.rs | 2 +- src/proto/Cosmos.proto | 42 ++++++++--- 30 files changed, 268 insertions(+), 201 deletions(-) create mode 100644 rust/tw_any_coin/tests/chains/zetachain/zetachain_sign.rs delete mode 100644 rust/tw_cosmos_sdk/src/hasher/keccak256_hasher.rs delete mode 100644 rust/tw_cosmos_sdk/src/hasher/mod.rs delete mode 100644 rust/tw_cosmos_sdk/src/hasher/sha256_hasher.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index efbc436e0d4..2f08868d5b9 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1940,6 +1940,7 @@ version = "0.1.0" dependencies = [ "tw_coin_entry", "tw_cosmos_sdk", + "tw_hash", "tw_keypair", "tw_memory", "tw_proto", @@ -1951,6 +1952,7 @@ version = "0.1.0" dependencies = [ "tw_coin_entry", "tw_cosmos_sdk", + "tw_hash", "tw_keypair", "tw_memory", "tw_proto", diff --git a/rust/chains/tw_binance/src/compiler.rs b/rust/chains/tw_binance/src/compiler.rs index 8c9012d2910..754cdd854ed 100644 --- a/rust/chains/tw_binance/src/compiler.rs +++ b/rust/chains/tw_binance/src/compiler.rs @@ -71,7 +71,9 @@ impl BinanceCompiler { public_key, } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; let signature = BinanceSignature::try_from(signature.as_slice())?; - let public_key = Secp256PublicKey::from_bytes(coin, public_key.as_slice())?; + let public_key_params = None; + let public_key = + Secp256PublicKey::from_bytes(coin, public_key.as_slice(), public_key_params)?; let signature_bytes = signature.to_vec(); let signature_json = JsonSerializer::::serialize_signature( diff --git a/rust/chains/tw_binance/src/context.rs b/rust/chains/tw_binance/src/context.rs index 4cd308db000..58952365b63 100644 --- a/rust/chains/tw_binance/src/context.rs +++ b/rust/chains/tw_binance/src/context.rs @@ -4,18 +4,21 @@ use crate::address::BinanceAddress; use tw_cosmos_sdk::context::CosmosContext; -use tw_cosmos_sdk::hasher::sha256_hasher::Sha256Hasher; use tw_cosmos_sdk::private_key::secp256k1::Secp256PrivateKey; use tw_cosmos_sdk::public_key::secp256k1::Secp256PublicKey; use tw_cosmos_sdk::signature::secp256k1::Secp256k1Signature; +use tw_hash::hasher::Hasher; pub struct BinanceContext; impl CosmosContext for BinanceContext { type Address = BinanceAddress; - /// Binance Beacon chain uses `sha256` hash. - type TxHasher = Sha256Hasher; type PrivateKey = Secp256PrivateKey; type PublicKey = Secp256PublicKey; type Signature = Secp256k1Signature; + + /// Binance Beacon chain uses `sha256` hash. + fn default_tx_hasher() -> Hasher { + Hasher::Sha256 + } } diff --git a/rust/chains/tw_binance/src/signer.rs b/rust/chains/tw_binance/src/signer.rs index 7f6a7809e33..b17553e067e 100644 --- a/rust/chains/tw_binance/src/signer.rs +++ b/rust/chains/tw_binance/src/signer.rs @@ -39,7 +39,8 @@ impl BinanceSigner { let key_pair = secp256k1::KeyPair::try_from(input.private_key.as_ref())?; let signature = BinanceSignature::from(key_pair.sign(tx_hash)?); - let public_key = Secp256PublicKey::from_secp256k1_public_key(coin, key_pair.public())?; + let public_key = + Secp256PublicKey::from_secp256k1_public_key(coin.public_key_type(), key_pair.public())?; let signature_bytes = signature.to_vec(); let signature_json = JsonSerializer::::serialize_signature( diff --git a/rust/chains/tw_greenfield/src/compiler.rs b/rust/chains/tw_greenfield/src/compiler.rs index 46485a5bfe1..a37ac39a7f2 100644 --- a/rust/chains/tw_greenfield/src/compiler.rs +++ b/rust/chains/tw_greenfield/src/compiler.rs @@ -71,7 +71,8 @@ impl GreenfieldCompiler { public_key, } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; - let public_key = GreenfieldPublicKey::from_bytes(coin, &public_key)?; + let public_key_params = None; + let public_key = GreenfieldPublicKey::from_bytes(coin, &public_key, public_key_params)?; let signature = GreenfieldSignature::try_from(raw_signature.as_slice())?; let signature_bytes = signature.to_vec(); diff --git a/rust/chains/tw_greenfield/src/context.rs b/rust/chains/tw_greenfield/src/context.rs index f4f9db90b25..888147cde48 100644 --- a/rust/chains/tw_greenfield/src/context.rs +++ b/rust/chains/tw_greenfield/src/context.rs @@ -5,17 +5,20 @@ use crate::address::GreenfieldAddress; use crate::public_key::GreenfieldPublicKey; use tw_cosmos_sdk::context::CosmosContext; -use tw_cosmos_sdk::hasher::keccak256_hasher::Keccak256Hasher; use tw_cosmos_sdk::private_key::secp256k1::Secp256PrivateKey; use tw_cosmos_sdk::signature::secp256k1::Secp256k1Signature; +use tw_hash::hasher::Hasher; pub struct GreenfieldContext; impl CosmosContext for GreenfieldContext { type Address = GreenfieldAddress; - /// Greenfield uses EIP712 message signing algorithm built upon `keccak256` hash. - type TxHasher = Keccak256Hasher; type PrivateKey = Secp256PrivateKey; type PublicKey = GreenfieldPublicKey; type Signature = Secp256k1Signature; + + /// Greenfield uses EIP712 message signing algorithm built upon `keccak256` hash. + fn default_tx_hasher() -> Hasher { + Hasher::Keccak256 + } } diff --git a/rust/chains/tw_greenfield/src/modules/tx_builder.rs b/rust/chains/tw_greenfield/src/modules/tx_builder.rs index 2fef62ccd1e..2e0951c8cce 100644 --- a/rust/chains/tw_greenfield/src/modules/tx_builder.rs +++ b/rust/chains/tw_greenfield/src/modules/tx_builder.rs @@ -56,7 +56,9 @@ impl TxBuilder { coin: &dyn CoinContext, input: &Proto::SigningInput, ) -> SigningResult { - let public_key = GreenfieldPublicKey::from_bytes(coin, &input.public_key)?; + let public_key_params = None; + let public_key = + GreenfieldPublicKey::from_bytes(coin, &input.public_key, public_key_params)?; let sign_mode = match input.signing_mode { Proto::SigningMode::Eip712 => GreenfieldSignMode::Eip712, }; diff --git a/rust/chains/tw_greenfield/src/public_key.rs b/rust/chains/tw_greenfield/src/public_key.rs index b03ca413ee3..41f4a3cced6 100644 --- a/rust/chains/tw_greenfield/src/public_key.rs +++ b/rust/chains/tw_greenfield/src/public_key.rs @@ -4,11 +4,10 @@ use tw_coin_entry::coin_context::CoinContext; use tw_cosmos_sdk::public_key::{ - CosmosPublicKey, CustomPublicKeyType, JsonPublicKey, ProtobufPublicKey, + CosmosPublicKey, JsonPublicKey, ProtobufPublicKey, PublicKeyParams, }; use tw_keypair::ecdsa::secp256k1; -use tw_keypair::tw::{PrivateKey, PublicKeyType}; -use tw_keypair::{KeyPairError, KeyPairResult}; +use tw_keypair::{tw, KeyPairError, KeyPairResult}; use tw_memory::Data; use tw_proto::{google, to_any}; @@ -30,23 +29,29 @@ impl ProtobufPublicKey for GreenfieldPublicKey { } impl CosmosPublicKey for GreenfieldPublicKey { - fn from_private_key(_coin: &dyn CoinContext, private_key: &PrivateKey) -> KeyPairResult { + fn from_private_key( + _coin: &dyn CoinContext, + private_key: &tw::PrivateKey, + // Ignore custom public key parameters. + _params: Option, + ) -> KeyPairResult { let public_key = private_key - .get_public_key_by_type(PublicKeyType::Secp256k1)? + .get_public_key_by_type(tw::PublicKeyType::Secp256k1)? .to_secp256k1() .ok_or(KeyPairError::InvalidPublicKey)? .clone(); Ok(GreenfieldPublicKey(public_key)) } - fn from_bytes(_coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult { + fn from_bytes( + _coin: &dyn CoinContext, + public_key_bytes: &[u8], + // Ignore custom public key parameters. + _params: Option, + ) -> KeyPairResult { secp256k1::PublicKey::try_from(public_key_bytes).map(GreenfieldPublicKey) } - fn with_custom_public_key_type(&mut self, _custom_type: CustomPublicKeyType) { - // Do nothing. Greenfield does not support custom public key type. - } - fn to_bytes(&self) -> Data { self.0.compressed().to_vec() } diff --git a/rust/chains/tw_native_evmos/Cargo.toml b/rust/chains/tw_native_evmos/Cargo.toml index cc4762a7971..87137a252cc 100644 --- a/rust/chains/tw_native_evmos/Cargo.toml +++ b/rust/chains/tw_native_evmos/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] tw_coin_entry = { path = "../../tw_coin_entry" } tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_hash = { path = "../../tw_hash" } tw_keypair = { path = "../../tw_keypair" } tw_memory = { path = "../../tw_memory" } tw_proto = { path = "../../tw_proto" } diff --git a/rust/chains/tw_native_evmos/src/context.rs b/rust/chains/tw_native_evmos/src/context.rs index 2f2352dde83..abebb7cd8f9 100644 --- a/rust/chains/tw_native_evmos/src/context.rs +++ b/rust/chains/tw_native_evmos/src/context.rs @@ -5,16 +5,19 @@ 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; use tw_cosmos_sdk::signature::secp256k1::Secp256k1Signature; +use tw_hash::hasher::Hasher; pub struct NativeEvmosContext; impl CosmosContext for NativeEvmosContext { type Address = Address; - type TxHasher = Keccak256Hasher; type PrivateKey = Secp256PrivateKey; type PublicKey = EthermintEthSecp256PublicKey; type Signature = Secp256k1Signature; + + fn default_tx_hasher() -> Hasher { + Hasher::Keccak256 + } } diff --git a/rust/chains/tw_native_evmos/src/ethermint_public_key.rs b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs index 40c7b44daa8..32accd79966 100644 --- a/rust/chains/tw_native_evmos/src/ethermint_public_key.rs +++ b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs @@ -5,11 +5,10 @@ use tw_coin_entry::coin_context::CoinContext; use tw_cosmos_sdk::proto::ethermint; use tw_cosmos_sdk::public_key::{ - CosmosPublicKey, CustomPublicKeyType, JsonPublicKey, ProtobufPublicKey, + CosmosPublicKey, JsonPublicKey, ProtobufPublicKey, PublicKeyParams, }; use tw_keypair::ecdsa::secp256k1; use tw_keypair::KeyPairResult; -use tw_keypair::{tw, KeyPairError}; use tw_memory::Data; use tw_proto::{google, to_any}; @@ -28,26 +27,18 @@ impl EthermintEthSecp256PublicKey { } impl CosmosPublicKey for EthermintEthSecp256PublicKey { - fn from_private_key(coin: &dyn CoinContext, private_key: &tw::PrivateKey) -> KeyPairResult - where - Self: Sized, - { - 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 { + fn from_bytes( + _coin: &dyn CoinContext, + public_key_bytes: &[u8], + // Ignore custom public key parameters. + _params: Option, + ) -> KeyPairResult { + // `NativeEvmos` requires the public key to be compressed, + // however the uncompressed public key is used to generate an address. let public_key = secp256k1::PublicKey::try_from(public_key_bytes)?; EthermintEthSecp256PublicKey::new(&public_key) } - fn with_custom_public_key_type(&mut self, _custom_type: CustomPublicKeyType) { - // Do nothing. NativeEvmos does not support custom public key type. - } - fn to_bytes(&self) -> Data { self.public_key.clone() } diff --git a/rust/chains/tw_native_injective/Cargo.toml b/rust/chains/tw_native_injective/Cargo.toml index d793539a241..b8d8a0bcb50 100644 --- a/rust/chains/tw_native_injective/Cargo.toml +++ b/rust/chains/tw_native_injective/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] tw_coin_entry = { path = "../../tw_coin_entry" } tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_hash = { path = "../../tw_hash" } tw_keypair = { path = "../../tw_keypair" } tw_memory = { path = "../../tw_memory" } tw_proto = { path = "../../tw_proto" } diff --git a/rust/chains/tw_native_injective/src/context.rs b/rust/chains/tw_native_injective/src/context.rs index a9ac9bf6bfc..c50dff8c44a 100644 --- a/rust/chains/tw_native_injective/src/context.rs +++ b/rust/chains/tw_native_injective/src/context.rs @@ -5,16 +5,19 @@ use crate::injective_public_key::InjectiveEthSecp256PublicKey; 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; use tw_cosmos_sdk::signature::secp256k1::Secp256k1Signature; +use tw_hash::hasher::Hasher; pub struct NativeInjectiveContext; impl CosmosContext for NativeInjectiveContext { type Address = Address; - type TxHasher = Keccak256Hasher; type PrivateKey = Secp256PrivateKey; type PublicKey = InjectiveEthSecp256PublicKey; type Signature = Secp256k1Signature; + + fn default_tx_hasher() -> Hasher { + Hasher::Keccak256 + } } diff --git a/rust/chains/tw_native_injective/src/injective_public_key.rs b/rust/chains/tw_native_injective/src/injective_public_key.rs index 9647da7fd7f..7b15c09bded 100644 --- a/rust/chains/tw_native_injective/src/injective_public_key.rs +++ b/rust/chains/tw_native_injective/src/injective_public_key.rs @@ -6,9 +6,8 @@ use tw_coin_entry::coin_context::CoinContext; use tw_cosmos_sdk::proto::injective; use tw_cosmos_sdk::public_key::secp256k1::prepare_secp256k1_public_key; use tw_cosmos_sdk::public_key::{ - CosmosPublicKey, CustomPublicKeyType, JsonPublicKey, ProtobufPublicKey, + CosmosPublicKey, JsonPublicKey, ProtobufPublicKey, PublicKeyParams, }; -use tw_keypair::tw::PrivateKey; use tw_keypair::KeyPairResult; use tw_memory::Data; use tw_proto::{google, to_any}; @@ -18,25 +17,16 @@ pub struct InjectiveEthSecp256PublicKey { } impl CosmosPublicKey for InjectiveEthSecp256PublicKey { - fn from_private_key(coin: &dyn CoinContext, private_key: &PrivateKey) -> KeyPairResult - where - Self: Sized, - { - let public_key = private_key.get_public_key_by_type(coin.public_key_type())?; - Ok(InjectiveEthSecp256PublicKey { - public_key: public_key.to_bytes(), - }) - } - - fn from_bytes(coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult { - let public_key = prepare_secp256k1_public_key(coin, public_key_bytes)?; + fn from_bytes( + coin: &dyn CoinContext, + public_key_bytes: &[u8], + // Ignore custom public key parameters. + _params: Option, + ) -> KeyPairResult { + let public_key = prepare_secp256k1_public_key(coin.public_key_type(), public_key_bytes)?; Ok(InjectiveEthSecp256PublicKey { public_key }) } - fn with_custom_public_key_type(&mut self, _custom_type: CustomPublicKeyType) { - // Do nothing. NativeEvmos does not support custom public key type. - } - fn to_bytes(&self) -> Data { self.public_key.clone() } diff --git a/rust/tw_any_coin/tests/chains/zetachain/mod.rs b/rust/tw_any_coin/tests/chains/zetachain/mod.rs index 1b26a3ca277..b742c655b68 100644 --- a/rust/tw_any_coin/tests/chains/zetachain/mod.rs +++ b/rust/tw_any_coin/tests/chains/zetachain/mod.rs @@ -3,3 +3,4 @@ // Copyright © 2017 Trust Wallet. mod zetachain_address; +mod zetachain_sign; diff --git a/rust/tw_any_coin/tests/chains/zetachain/zetachain_sign.rs b/rust/tw_any_coin/tests/chains/zetachain/zetachain_sign.rs new file mode 100644 index 00000000000..056f5858926 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/zetachain/zetachain_sign.rs @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; +use tw_coin_entry::error::SigningErrorType; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_proto::Cosmos::Proto::{self, mod_Message::OneOfmessage_oneof as MessageEnum}; +use tw_proto::{deserialize, serialize}; + +const PRIVATE_KEY: &str = "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed"; + +/// Successfully broadcasted: +/// https://explorer.zetachain.com/cosmos/tx/A2FC8816657856ED274C4418C3CAEAEE645561275F6C63AB5F8B1DCFB37341A0 +#[test] +fn test_zetachain_sign_msg_send() { + let send_msg = Proto::mod_Message::Send { + from_address: "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne".into(), + to_address: "zeta1cscf4ldnkkz7f0wpveur6dpd0d6p2zxnsuu70y".into(), + amounts: vec![Proto::Amount { + denom: "azeta".into(), + // 0.3 ZETA + amount: "300000000000000000".into(), + }], + ..Proto::mod_Message::Send::default() + }; + + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Protobuf, + account_number: 2726346, + chain_id: "athens_7001-1".into(), + sequence: 2, + fee: Some(Proto::Fee { + gas: 200000, + amounts: vec![], + }), + private_key: PRIVATE_KEY.decode_hex().unwrap().into(), + messages: vec![Proto::Message { + message_oneof: MessageEnum::send_coins_message(send_msg), + }], + tx_hasher: Proto::TxHasher::Keccak256, + signer_info: Some(Proto::SignerInfo { + // Zetachain requires a compressed public key to sign a transaction, + // however an uncompressed public key is used to generate address. + public_key_type: Proto::SignerPublicKeyType::Secp256k1, + json_type: "zetachain/PubKeyEthSecp256k1".into(), + protobuf_type: "/ethermint.crypto.v1.ethsecp256k1.PubKey".into(), + }), + ..Proto::SigningInput::default() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = TWDataHelper::wrap(unsafe { + tw_any_signer_sign(input_data.ptr(), CoinType::NativeZetaChain as u32) + }) + .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.serialized, + r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpoBCpcBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEncKK3pldGExNHB5MzZzeDU3dWQ4MnQ5eXJrczl6Nmhkc3JwbjV4NmtteHMwbmUSK3pldGExY3NjZjRsZG5ra3o3ZjB3cHZldXI2ZHBkMGQ2cDJ6eG5zdXU3MHkaGwoFYXpldGESEjMwMDAwMDAwMDAwMDAwMDAwMBJhClkKTwooL2V0aGVybWludC5jcnlwdG8udjEuZXRoc2VjcDI1NmsxLlB1YktleRIjCiECho5+FjRBfbKt/Z/jggW/oP6gGJin/TBWXRP3BWo3wGUSBAoCCAEYAhIEEMCaDBpAgGvqca0w2N9wnHnnxS9HiVud4aQ9lNCumzgNIW6wOR4kvPScacGS1G3kwCw7wyI2NJL8M1eVYjafFIt2FpKl3w=="}"# + ); + assert_eq!(output.signature.to_hex(), "806bea71ad30d8df709c79e7c52f47895b9de1a43d94d0ae9b380d216eb0391e24bcf49c69c192d46de4c02c3bc322363492fc33579562369f148b761692a5df"); + assert_eq!( + output.signature_json, + r#"[{"pub_key":{"type":"zetachain/PubKeyEthSecp256k1","value":"AoaOfhY0QX2yrf2f44IFv6D+oBiYp/0wVl0T9wVqN8Bl"},"signature":"gGvqca0w2N9wnHnnxS9HiVud4aQ9lNCumzgNIW6wOR4kvPScacGS1G3kwCw7wyI2NJL8M1eVYjafFIt2FpKl3w=="}]"# + ); +} diff --git a/rust/tw_cosmos_sdk/src/context.rs b/rust/tw_cosmos_sdk/src/context.rs index 0fc370bc39f..077c8c1ede4 100644 --- a/rust/tw_cosmos_sdk/src/context.rs +++ b/rust/tw_cosmos_sdk/src/context.rs @@ -3,21 +3,21 @@ // Copyright © 2017 Trust Wallet. use crate::address::{Address, CosmosAddress}; -use crate::hasher::sha256_hasher::Sha256Hasher; -use crate::hasher::CosmosHasher; use crate::private_key::secp256k1::Secp256PrivateKey; use crate::private_key::CosmosPrivateKey; use crate::public_key::secp256k1::Secp256PublicKey; use crate::public_key::CosmosPublicKey; use crate::signature::secp256k1::Secp256k1Signature; use crate::signature::CosmosSignature; +use tw_hash::hasher::Hasher; pub trait CosmosContext { type Address: CosmosAddress; - type TxHasher: CosmosHasher; type PrivateKey: CosmosPrivateKey; type PublicKey: CosmosPublicKey; type Signature: CosmosSignature; + + fn default_tx_hasher() -> Hasher; } #[derive(Default)] @@ -25,8 +25,11 @@ pub struct StandardCosmosContext; impl CosmosContext for StandardCosmosContext { type Address = Address; - type TxHasher = Sha256Hasher; type PrivateKey = Secp256PrivateKey; type PublicKey = Secp256PublicKey; type Signature = Secp256k1Signature; + + fn default_tx_hasher() -> Hasher { + Hasher::Sha256 + } } diff --git a/rust/tw_cosmos_sdk/src/hasher/keccak256_hasher.rs b/rust/tw_cosmos_sdk/src/hasher/keccak256_hasher.rs deleted file mode 100644 index a37dccb78b1..00000000000 --- a/rust/tw_cosmos_sdk/src/hasher/keccak256_hasher.rs +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -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 deleted file mode 100644 index 960940ef8c5..00000000000 --- a/rust/tw_cosmos_sdk/src/hasher/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -use tw_memory::Data; - -pub mod keccak256_hasher; -pub mod sha256_hasher; - -pub trait CosmosHasher { - fn hash_sign_doc(sign_doc: &[u8]) -> Data; - - fn hash_json_tx(json: &str) -> Data; -} diff --git a/rust/tw_cosmos_sdk/src/hasher/sha256_hasher.rs b/rust/tw_cosmos_sdk/src/hasher/sha256_hasher.rs deleted file mode 100644 index 33f0e42a824..00000000000 --- a/rust/tw_cosmos_sdk/src/hasher/sha256_hasher.rs +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -use crate::hasher::CosmosHasher; -use tw_hash::sha2::sha256; -use tw_memory::Data; - -pub struct Sha256Hasher; - -impl CosmosHasher for Sha256Hasher { - fn hash_sign_doc(sign_doc: &[u8]) -> Data { - sha256(sign_doc) - } - - fn hash_json_tx(json: &str) -> Data { - sha256(json.as_bytes()) - } -} diff --git a/rust/tw_cosmos_sdk/src/lib.rs b/rust/tw_cosmos_sdk/src/lib.rs index 3c6f16a7665..a0a49da4057 100644 --- a/rust/tw_cosmos_sdk/src/lib.rs +++ b/rust/tw_cosmos_sdk/src/lib.rs @@ -4,7 +4,6 @@ pub mod address; pub mod context; -pub mod hasher; pub mod modules; pub mod private_key; pub mod public_key; diff --git a/rust/tw_cosmos_sdk/src/modules/compiler/json_preimager.rs b/rust/tw_cosmos_sdk/src/modules/compiler/json_preimager.rs index 5c61af317a5..a8310883057 100644 --- a/rust/tw_cosmos_sdk/src/modules/compiler/json_preimager.rs +++ b/rust/tw_cosmos_sdk/src/modules/compiler/json_preimager.rs @@ -3,12 +3,12 @@ // Copyright © 2017 Trust Wallet. use crate::context::CosmosContext; -use crate::hasher::CosmosHasher; use crate::modules::serializer::json_serializer::JsonSerializer; use crate::public_key::JsonPublicKey; use crate::transaction::UnsignedTransaction; use std::marker::PhantomData; use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_hash::hasher::Hasher; use tw_memory::Data; pub struct JsonTxPreimage { @@ -24,11 +24,14 @@ impl JsonPreimager where Context::PublicKey: JsonPublicKey, { - pub fn preimage_hash(unsigned: &UnsignedTransaction) -> SigningResult { + pub fn preimage_hash( + unsigned: &UnsignedTransaction, + hasher: Hasher, + ) -> SigningResult { let tx_to_sign = JsonSerializer::build_unsigned_tx(unsigned)?; let encoded_tx = serde_json::to_string(&tx_to_sign) .map_err(|_| SigningError(SigningErrorType::Error_internal))?; - let tx_hash = Context::TxHasher::hash_json_tx(&encoded_tx); + let tx_hash = hasher.hash(encoded_tx.as_bytes()); Ok(JsonTxPreimage { encoded_tx, diff --git a/rust/tw_cosmos_sdk/src/modules/compiler/protobuf_preimager.rs b/rust/tw_cosmos_sdk/src/modules/compiler/protobuf_preimager.rs index 7aa71bbdb3e..06e5d2e7621 100644 --- a/rust/tw_cosmos_sdk/src/modules/compiler/protobuf_preimager.rs +++ b/rust/tw_cosmos_sdk/src/modules/compiler/protobuf_preimager.rs @@ -3,11 +3,11 @@ // Copyright © 2017 Trust Wallet. use crate::context::CosmosContext; -use crate::hasher::CosmosHasher; use crate::modules::serializer::protobuf_serializer::{ProtobufSerializer, SignDirectArgs}; use crate::transaction::UnsignedTransaction; use std::marker::PhantomData; use tw_coin_entry::error::SigningResult; +use tw_hash::hasher::Hasher; use tw_memory::Data; use tw_proto::serialize; @@ -23,10 +23,11 @@ pub struct ProtobufPreimager { impl ProtobufPreimager { pub fn preimage_hash( unsigned: &UnsignedTransaction, + hasher: Hasher, ) -> SigningResult { let tx_to_sign = ProtobufSerializer::build_sign_doc(unsigned)?; let encoded_tx = serialize(&tx_to_sign)?; - let tx_hash = Context::TxHasher::hash_sign_doc(&encoded_tx); + let tx_hash = hasher.hash(&encoded_tx); Ok(ProtobufTxPreimage { encoded_tx, @@ -34,10 +35,10 @@ impl ProtobufPreimager { }) } - pub fn preimage_hash_direct(args: &SignDirectArgs) -> SigningResult { + pub fn preimage_hash_direct(args: &SignDirectArgs, hasher: Hasher) -> SigningResult { let tx_to_sign = ProtobufSerializer::::build_direct_sign_doc(args); let encoded_tx = serialize(&tx_to_sign)?; - let tx_hash = Context::TxHasher::hash_sign_doc(&encoded_tx); + let tx_hash = hasher.hash(&encoded_tx); Ok(ProtobufTxPreimage { encoded_tx, diff --git a/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs b/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs index 6427b086c83..4059f09fbcc 100644 --- a/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs +++ b/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs @@ -58,16 +58,17 @@ impl TWTransactionCompiler { coin: &dyn CoinContext, input: Proto::SigningInput<'_>, ) -> SigningResult> { + let tx_hasher = TxBuilder::::tx_hasher_from_proto(&input); let preimage = match TxBuilder::::try_sign_direct_args(&input) { // If there was a `SignDirect` message in the signing input, generate the tx preimage directly. Ok(Some(sign_direct_args)) => { - ProtobufPreimager::::preimage_hash_direct(&sign_direct_args)? + ProtobufPreimager::::preimage_hash_direct(&sign_direct_args, tx_hasher)? }, // Otherwise, generate the tx preimage by using `TxBuilder`. _ => { // Please note the [`Proto::SigningInput::public_key`] should be set already. let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; - ProtobufPreimager::::preimage_hash(&unsigned_tx)? + ProtobufPreimager::::preimage_hash(&unsigned_tx, tx_hasher)? }, }; @@ -84,7 +85,8 @@ impl TWTransactionCompiler { ) -> SigningResult> { // Please note the [`Proto::SigningInput::public_key`] should be set already. let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; - let preimage = JsonPreimager::preimage_hash(&unsigned_tx)?; + let tx_hasher = TxBuilder::::tx_hasher_from_proto(&input); + let preimage = JsonPreimager::preimage_hash(&unsigned_tx, tx_hasher)?; Ok(CompilerProto::PreSigningOutput { data: Cow::from(preimage.encoded_tx.as_bytes().to_vec()), @@ -105,10 +107,8 @@ impl TWTransactionCompiler { } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; let signature = Context::Signature::try_from(&signature)?; - let mut public_key = Context::PublicKey::from_bytes(coin, &public_key)?; - if let Some(custom_type) = TxBuilder::::custom_public_key_type_from_proto(&input) { - public_key.with_custom_public_key_type(custom_type); - } + let params = TxBuilder::::public_key_params_from_proto(&input); + let public_key = Context::PublicKey::from_bytes(coin, &public_key, params)?; let signed_tx_raw = match TxBuilder::::try_sign_direct_args(&input) { // If there was a `SignDirect` message in the signing input, generate the `TxRaw` directly. @@ -155,10 +155,8 @@ impl TWTransactionCompiler { } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; let signature = Context::Signature::try_from(&signature)?; - let mut public_key = Context::PublicKey::from_bytes(coin, &public_key)?; - if let Some(custom_type) = TxBuilder::::custom_public_key_type_from_proto(&input) { - public_key.with_custom_public_key_type(custom_type); - } + let params = TxBuilder::::public_key_params_from_proto(&input); + let public_key = Context::PublicKey::from_bytes(coin, &public_key, params)?; // Set the public key. It will be used to construct a signer info. input.public_key = Cow::from(public_key.to_bytes()); diff --git a/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs b/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs index 1a04d09f026..4a6d4b276c0 100644 --- a/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs +++ b/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs @@ -34,10 +34,8 @@ impl TWSigner { ) -> SigningResult> { let private_key = Context::PrivateKey::try_from(&input.private_key)?; - let mut public_key = Context::PublicKey::from_private_key(coin, private_key.as_ref())?; - if let Some(custom_type) = TxBuilder::::custom_public_key_type_from_proto(&input) { - public_key.with_custom_public_key_type(custom_type); - } + let params = TxBuilder::::public_key_params_from_proto(&input); + let public_key = Context::PublicKey::from_private_key(coin, private_key.as_ref(), params)?; // Set the public key. It will be used to construct a signer info. input.public_key = Cow::from(public_key.to_bytes()); diff --git a/rust/tw_cosmos_sdk/src/modules/tx_builder.rs b/rust/tw_cosmos_sdk/src/modules/tx_builder.rs index 2acf558b877..c39d13f8c70 100644 --- a/rust/tw_cosmos_sdk/src/modules/tx_builder.rs +++ b/rust/tw_cosmos_sdk/src/modules/tx_builder.rs @@ -5,7 +5,7 @@ use crate::address::Address; use crate::context::CosmosContext; use crate::modules::serializer::protobuf_serializer::SignDirectArgs; -use crate::public_key::{CosmosPublicKey, CustomPublicKeyType}; +use crate::public_key::{CosmosPublicKey, PublicKeyParams}; use crate::transaction::message::cosmos_generic_message::JsonRawMessage; use crate::transaction::message::{CosmosMessage, CosmosMessageBox}; use crate::transaction::{Coin, Fee, SignMode, SignerInfo, TxBody, UnsignedTransaction}; @@ -13,6 +13,8 @@ use std::marker::PhantomData; use std::str::FromStr; use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_hash::hasher::Hasher; +use tw_keypair::tw; use tw_misc::traits::{OptionalEmpty, ToBytesVec}; use tw_number::U256; use tw_proto::Cosmos::Proto; @@ -53,10 +55,8 @@ where coin: &dyn CoinContext, input: &Proto::SigningInput, ) -> SigningResult> { - let mut public_key = Context::PublicKey::from_bytes(coin, &input.public_key)?; - if let Some(custom_type) = Self::custom_public_key_type_from_proto(input) { - public_key.with_custom_public_key_type(custom_type); - } + let params = Self::public_key_params_from_proto(input); + let public_key = Context::PublicKey::from_bytes(coin, &input.public_key, params)?; Ok(SignerInfo { public_key, @@ -66,16 +66,25 @@ where }) } - pub fn custom_public_key_type_from_proto( - input: &Proto::SigningInput, - ) -> Option { - input - .custom_public_key_type - .clone() - .map(|custom_type| CustomPublicKeyType { - json_type: custom_type.json_type.to_string(), - protobuf_type_url: custom_type.protobuf_type_url.to_string(), - }) + pub fn public_key_params_from_proto(input: &Proto::SigningInput) -> Option { + input.signer_info.clone().map(|params| PublicKeyParams { + public_key_type: match params.public_key_type { + Proto::SignerPublicKeyType::Secp256k1 => tw::PublicKeyType::Secp256k1, + Proto::SignerPublicKeyType::Secp256k1Extended => { + tw::PublicKeyType::Secp256k1Extended + }, + }, + json_type: params.json_type.to_string(), + protobuf_type_url: params.protobuf_type.to_string(), + }) + } + + pub fn tx_hasher_from_proto(input: &Proto::SigningInput) -> Hasher { + match input.tx_hasher { + Proto::TxHasher::UseDefault => Context::default_tx_hasher(), + Proto::TxHasher::Sha256 => Hasher::Sha256, + Proto::TxHasher::Keccak256 => Hasher::Keccak256, + } } fn fee_from_proto(input: &Proto::Fee) -> SigningResult> { diff --git a/rust/tw_cosmos_sdk/src/public_key/mod.rs b/rust/tw_cosmos_sdk/src/public_key/mod.rs index 17f10f910f5..746de80b584 100644 --- a/rust/tw_cosmos_sdk/src/public_key/mod.rs +++ b/rust/tw_cosmos_sdk/src/public_key/mod.rs @@ -9,7 +9,8 @@ use tw_proto::google; pub mod secp256k1; -pub struct CustomPublicKeyType { +pub struct PublicKeyParams { + pub public_key_type: tw::PublicKeyType, pub json_type: String, pub protobuf_type_url: String, } @@ -18,12 +19,22 @@ pub trait CosmosPublicKey: JsonPublicKey + ProtobufPublicKey + Sized { fn from_private_key( coin: &dyn CoinContext, private_key: &tw::PrivateKey, + params: Option, + ) -> KeyPairResult { + let public_key_type = match params { + Some(ref params) => params.public_key_type, + None => coin.public_key_type(), + }; + let public_key = private_key.get_public_key_by_type(public_key_type)?; + Self::from_bytes(coin, &public_key.to_bytes(), params) + } + + fn from_bytes( + coin: &dyn CoinContext, + public_key_bytes: &[u8], + params: Option, ) -> KeyPairResult; - fn from_bytes(coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult; - - fn with_custom_public_key_type(&mut self, custom_type: CustomPublicKeyType); - fn to_bytes(&self) -> Data; } diff --git a/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs b/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs index 5dd7061f95c..6b147ed4238 100644 --- a/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs +++ b/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs @@ -3,15 +3,14 @@ // Copyright © 2017 Trust Wallet. use crate::proto::cosmos; -use crate::public_key::{CosmosPublicKey, CustomPublicKeyType, JsonPublicKey, ProtobufPublicKey}; -use quick_protobuf::MessageInfo; +use crate::public_key::{CosmosPublicKey, JsonPublicKey, ProtobufPublicKey, PublicKeyParams}; use tw_coin_entry::coin_context::CoinContext; use tw_keypair::ecdsa::secp256k1; -use tw_keypair::tw::{self, PublicKeyType}; +use tw_keypair::tw; use tw_keypair::{KeyPairError, KeyPairResult}; use tw_memory::Data; use tw_misc::traits::ToBytesVec; -use tw_proto::{google, to_any_with_type_url}; +use tw_proto::{google, to_any_with_type_url, type_url}; const DEFAULT_JSON_PUBLIC_KEY_TYPE: &str = "tendermint/PubKeySecp256k1"; @@ -24,45 +23,39 @@ pub struct Secp256PublicKey { impl Secp256PublicKey { pub fn from_secp256k1_public_key( - coin: &dyn CoinContext, + public_key_type: tw::PublicKeyType, public_key: &secp256k1::PublicKey, ) -> KeyPairResult { - let public_key = prepare_secp256k1_public_key(coin, public_key.compressed().as_slice())?; + let public_key = + prepare_secp256k1_public_key(public_key_type, public_key.compressed().as_slice())?; Ok(Secp256PublicKey { public_key, json_type: DEFAULT_JSON_PUBLIC_KEY_TYPE.to_string(), - protobuf_type_url: format!("/{}", cosmos::crypto::secp256k1::PubKey::PATH), + protobuf_type_url: type_url::(), }) } } impl CosmosPublicKey for Secp256PublicKey { - 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(Secp256PublicKey { - public_key: public_key.to_bytes(), + fn from_bytes( + coin: &dyn CoinContext, + public_key_bytes: &[u8], + maybe_params: Option, + ) -> KeyPairResult { + let params = maybe_params.unwrap_or_else(|| PublicKeyParams { + public_key_type: coin.public_key_type(), json_type: DEFAULT_JSON_PUBLIC_KEY_TYPE.to_string(), - protobuf_type_url: format!("/{}", cosmos::crypto::secp256k1::PubKey::PATH), - }) - } + protobuf_type_url: type_url::(), + }); - fn from_bytes(coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult { - let public_key = prepare_secp256k1_public_key(coin, public_key_bytes)?; + let public_key = prepare_secp256k1_public_key(params.public_key_type, public_key_bytes)?; Ok(Secp256PublicKey { public_key, - json_type: DEFAULT_JSON_PUBLIC_KEY_TYPE.to_string(), - protobuf_type_url: format!("/{}", cosmos::crypto::secp256k1::PubKey::PATH), + json_type: params.json_type, + protobuf_type_url: params.protobuf_type_url, }) } - fn with_custom_public_key_type(&mut self, custom_type: CustomPublicKeyType) { - self.json_type = custom_type.json_type; - self.protobuf_type_url = custom_type.protobuf_type_url; - } - fn to_bytes(&self) -> Data { self.public_key.clone() } @@ -84,13 +77,13 @@ impl JsonPublicKey for Secp256PublicKey { } pub fn prepare_secp256k1_public_key( - coin: &dyn CoinContext, + public_key_type: tw::PublicKeyType, public_key_bytes: &[u8], ) -> KeyPairResult { let public_key = secp256k1::PublicKey::try_from(public_key_bytes)?; - match coin.public_key_type() { - PublicKeyType::Secp256k1 => Ok(public_key.compressed().to_vec()), - PublicKeyType::Secp256k1Extended => Ok(public_key.uncompressed().to_vec()), + match public_key_type { + tw::PublicKeyType::Secp256k1 => Ok(public_key.compressed().to_vec()), + tw::PublicKeyType::Secp256k1Extended => Ok(public_key.uncompressed().to_vec()), _ => Err(KeyPairError::InvalidPublicKey), } } diff --git a/rust/tw_proto/src/lib.rs b/rust/tw_proto/src/lib.rs index e8c013bb125..fd222ddbb4e 100644 --- a/rust/tw_proto/src/lib.rs +++ b/rust/tw_proto/src/lib.rs @@ -46,7 +46,7 @@ where T: MessageInfo + MessageWrite, { let value = serialize(message).expect("Protobuf serialization should never fail"); - let type_url = format!("/{}", T::PATH); + let type_url = type_url::(); google::protobuf::Any { type_url, value } } diff --git a/src/proto/Cosmos.proto b/src/proto/Cosmos.proto index 6daa56c0c33..6ac55b56570 100644 --- a/src/proto/Cosmos.proto +++ b/src/proto/Cosmos.proto @@ -103,8 +103,8 @@ message Message { string type_prefix = 5; } - // cosmos-sdk/MsgSetWithdrawAddress - message SetWithdrawAddress { + // cosmos-sdk/MsgSetWithdrawAddress + message SetWithdrawAddress { string delegator_address = 1; string withdraw_address = 2; string type_prefix = 3; @@ -295,8 +295,8 @@ message Message { // cosmos-sdk/MsgRevoke message AuthRevoke { - string granter = 1; - string grantee = 2; + string granter = 1; + string grantee = 2; string msg_type_url = 3; } @@ -317,8 +317,8 @@ message Message { // cosmos-sdk/MsgVote defines a message to cast a vote. message MsgVote { uint64 proposal_id = 1; - string voter = 2; - VoteOption option = 3; + string voter = 2; + VoteOption option = 3; } message MsgStrideLiquidStakingStake { @@ -367,9 +367,27 @@ enum SigningMode { Protobuf = 1; // Protobuf-serialized (binary), Stargate } -message CustomPublicKeyType { - string json_type = 1; - string protobuf_type_url = 2; +enum TxHasher { + // For Cosmos chain, `Sha256` is used by default. + UseDefault = 0; + Sha256 = 1; + Keccak256 = 2; +} + +enum SignerPublicKeyType { + // Default public key type. + Secp256k1 = 0; + // Mostly used in Cosmos chains with EVM support. + Secp256k1Extended = 1; +} + +// Custom Signer info required to sign a transaction and generate a broadcast JSON message. +message SignerInfo { + // Public key type used to sign a transaction. + // It can be different from the value from `registry.json`. + SignerPublicKeyType public_key_type = 1; + string json_type = 2; + string protobuf_type = 3; } // Input data necessary to create a signed transaction. @@ -403,8 +421,10 @@ message SigningInput { bytes public_key = 10; - // Optional. Public Key Type used to sign a transaction and generate a JSON broadcast message. - CustomPublicKeyType custom_public_key_type = 11; + TxHasher tx_hasher = 11; + + // Optional. If set, use a different Signer info when signing the transaction. + SignerInfo signer_info = 12; } // Result containing the signed and encoded transaction. From 086f5614ca76bd2af202e1dad2af64e6de297a24 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Thu, 25 Jan 2024 12:53:32 +0700 Subject: [PATCH 05/11] feat(ZetaChain): Fix zetachain params in registry.json --- registry.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/registry.json b/registry.json index 9adb374df71..bc735b2fae1 100644 --- a/registry.json +++ b/registry.json @@ -4429,7 +4429,7 @@ "symbol": "ZETA", "decimals": 18, "blockchain": "Cosmos", - "chainId": "zetachain_7000", + "chainId": "zetachain_7000-1", "derivation": [ { "path": "m/44'/60'/0'/0/0" @@ -4442,7 +4442,7 @@ "explorer": { "url": "https://explorer.zetachain.com/cosmos", "txPath": "/tx/", - "accountPath": "/account/", + "accountPath": "/address/", "sampleTx": "2DBB071DDD47985F4470A21E5943CE95D371AE4BDE2267E201D3553FB2BDCFDE", "sampleAccount": "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne" }, From 2bf15e4dfaea8af310f01ff444d87a88eefc2b65 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Thu, 25 Jan 2024 13:33:34 +0700 Subject: [PATCH 06/11] feat(ZetaChain): Add mobile tests --- .../blockchains/CoinAddressDerivationTests.kt | 1 + .../TestNativeZetaChainAddress.kt | 28 ++++++++ .../TestNativeZetaChainSigner.kt | 71 +++++++++++++++++++ .../core/test/CoinAddressDerivationTests.kt | 1 + .../tests/chains/zetachain/zetachain_sign.rs | 4 +- .../Blockchains/NativeZetaChainTests.swift | 58 +++++++++++++++ swift/Tests/CoinAddressDerivationTests.swift | 3 + 7 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainAddress.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainSigner.kt create mode 100644 swift/Tests/Blockchains/NativeZetaChainTests.swift diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt index 1436bb783cf..ed19bae335c 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt @@ -151,5 +151,6 @@ class CoinAddressDerivationTests { SEI -> assertEquals("sei142j9u5eaduzd7faumygud6ruhdwme98qagm0sj", address) INTERNETCOMPUTER -> assertEquals("b9a13d974ee9db036d5abc5b66ace23e513cb5676f3996626c7717c339a3ee87", address) TIA -> assertEquals("celestia142j9u5eaduzd7faumygud6ruhdwme98qpwmfv7", address) + NATIVEZETACHAIN -> assertEquals("zeta13u6g7vqgw074mgmf2ze2cadzvkz9snlwywj304", address) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainAddress.kt new file mode 100644 index 00000000000..93a2fbd2a3a --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainAddress.kt @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.nativezetachain + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestNativeZetaChainAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed".toHexByteArray()) + val pubKey = key.getPublicKeySecp256k1(false) + val address = AnyAddress(pubKey, CoinType.NATIVEZETACHAIN) + val expected = AnyAddress("zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne", CoinType.NATIVEZETACHAIN) + + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainSigner.kt new file mode 100644 index 00000000000..13d22c48aa2 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainSigner.kt @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.nativezetachain + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.AnyAddress +import wallet.core.jni.CoinType +import wallet.core.jni.PrivateKey +import wallet.core.jni.proto.Cosmos + +class TestNativeZetaChainSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun NativeZetaChainTransactionSigning() { + val key = PrivateKey("8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(false) + val from = AnyAddress(publicKey, CoinType.NATIVEZETACHAIN).description() + + val transferAmount = Cosmos.Amount.newBuilder().apply { + // 0.3 ZETA + amount = "300000000000000000" + denom = "azeta" + }.build() + + val message = Cosmos.Message.newBuilder().apply { + sendCoinsMessage = Cosmos.Message.Send.newBuilder().apply { + fromAddress = from + toAddress = "zeta1cscf4ldnkkz7f0wpveur6dpd0d6p2zxnsuu70y" + addAllAmounts(listOf(transferAmount)) + }.build() + }.build() + + val transferFee = Cosmos.Fee.newBuilder().apply { + gas = 200000 + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = Cosmos.SigningMode.Protobuf + accountNumber = 2726346 + chainId = "athens_7001-1" + sequence = 2 + fee = transferFee + privateKey = ByteString.copyFrom(key.data()) + txHasher = Cosmos.TxHasher.Keccak256 + signerInfo = Cosmos.SignerInfo.newBuilder().apply { + // Zetachain requires a compressed public key to sign a transaction, + // however an uncompressed public key is used to generate address. + publicKeyType = Cosmos.SignerPublicKeyType.Secp256k1 + jsonType = "zetachain/PubKeyEthSecp256k1" + protobufType = "/ethermint.crypto.v1.ethsecp256k1.PubKey" + }.build() + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, CoinType.NATIVEZETACHAIN, Cosmos.SigningOutput.parser()) + + // Successfully broadcasted (testnet): + // https://explorer.zetachain.com/cosmos/tx/A2FC8816657856ED274C4418C3CAEAEE645561275F6C63AB5F8B1DCFB37341A0 + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpoBCpcBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEncKK3pldGExNHB5MzZzeDU3dWQ4MnQ5eXJrczl6Nmhkc3JwbjV4NmtteHMwbmUSK3pldGExY3NjZjRsZG5ra3o3ZjB3cHZldXI2ZHBkMGQ2cDJ6eG5zdXU3MHkaGwoFYXpldGESEjMwMDAwMDAwMDAwMDAwMDAwMBJhClkKTwooL2V0aGVybWludC5jcnlwdG8udjEuZXRoc2VjcDI1NmsxLlB1YktleRIjCiECho5+FjRBfbKt/Z/jggW/oP6gGJin/TBWXRP3BWo3wGUSBAoCCAEYAhIEEMCaDBpAgGvqca0w2N9wnHnnxS9HiVud4aQ9lNCumzgNIW6wOR4kvPScacGS1G3kwCw7wyI2NJL8M1eVYjafFIt2FpKl3w==\"}") + } +} diff --git a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt index db177dc1b16..6a2a7f54b05 100644 --- a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt +++ b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt @@ -144,5 +144,6 @@ class CoinAddressDerivationTests { Sei -> "sei142j9u5eaduzd7faumygud6ruhdwme98qagm0sj" InternetComputer -> "b9a13d974ee9db036d5abc5b66ace23e513cb5676f3996626c7717c339a3ee87" Tia -> "celestia142j9u5eaduzd7faumygud6ruhdwme98qpwmfv7" + NativeZetaChain -> "zeta13u6g7vqgw074mgmf2ze2cadzvkz9snlwywj304" } } diff --git a/rust/tw_any_coin/tests/chains/zetachain/zetachain_sign.rs b/rust/tw_any_coin/tests/chains/zetachain/zetachain_sign.rs index 056f5858926..6fd458f0a6c 100644 --- a/rust/tw_any_coin/tests/chains/zetachain/zetachain_sign.rs +++ b/rust/tw_any_coin/tests/chains/zetachain/zetachain_sign.rs @@ -12,10 +12,10 @@ use tw_proto::{deserialize, serialize}; const PRIVATE_KEY: &str = "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed"; -/// Successfully broadcasted: +/// Successfully broadcasted (testnet): /// https://explorer.zetachain.com/cosmos/tx/A2FC8816657856ED274C4418C3CAEAEE645561275F6C63AB5F8B1DCFB37341A0 #[test] -fn test_zetachain_sign_msg_send() { +fn test_zetachain_sign_msg_send_testnet() { let send_msg = Proto::mod_Message::Send { from_address: "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne".into(), to_address: "zeta1cscf4ldnkkz7f0wpveur6dpd0d6p2zxnsuu70y".into(), diff --git a/swift/Tests/Blockchains/NativeZetaChainTests.swift b/swift/Tests/Blockchains/NativeZetaChainTests.swift new file mode 100644 index 00000000000..88ead0bc866 --- /dev/null +++ b/swift/Tests/Blockchains/NativeZetaChainTests.swift @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class NativeZetaChainTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .nativeZetaChain) + let addressFromString = AnyAddress(string: "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne", coin: .nativeZetaChain)! + + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSign() { + let privateKey = PrivateKey(data: Data(hexString: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .nativeZetaChain) + + let message = CosmosMessage.with { + $0.sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress.description + $0.toAddress = "zeta1cscf4ldnkkz7f0wpveur6dpd0d6p2zxnsuu70y" + $0.amounts = [CosmosAmount.with { + $0.amount = "300000000000000000" + $0.denom = "azeta" + }] + } + } + + let fee = CosmosFee.with { + $0.gas = 200000 + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 2726346 + $0.chainID = "athens_7001-1" + $0.sequence = 2 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + $0.txHasher = CosmosTxHasher.keccak256 + $0.signerInfo = CosmosSignerInfo.with { + $0.publicKeyType = CosmosSignerPublicKeyType.secp256K1 + $0.jsonType = "zetachain/PubKeyEthSecp256k1" + $0.protobufType = "/ethermint.crypto.v1.ethsecp256k1.PubKey" + } + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .nativeZetaChain) + + XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpoBCpcBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEncKK3pldGExNHB5MzZzeDU3dWQ4MnQ5eXJrczl6Nmhkc3JwbjV4NmtteHMwbmUSK3pldGExY3NjZjRsZG5ra3o3ZjB3cHZldXI2ZHBkMGQ2cDJ6eG5zdXU3MHkaGwoFYXpldGESEjMwMDAwMDAwMDAwMDAwMDAwMBJhClkKTwooL2V0aGVybWludC5jcnlwdG8udjEuZXRoc2VjcDI1NmsxLlB1YktleRIjCiECho5+FjRBfbKt/Z/jggW/oP6gGJin/TBWXRP3BWo3wGUSBAoCCAEYAhIEEMCaDBpAgGvqca0w2N9wnHnnxS9HiVud4aQ9lNCumzgNIW6wOR4kvPScacGS1G3kwCw7wyI2NJL8M1eVYjafFIt2FpKl3w==\"}") + } +} diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 5c04bb7bd01..2e4ce928ecd 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -388,6 +388,9 @@ class CoinAddressDerivationTests: XCTestCase { case .tia: let expectedResult = "celestia142j9u5eaduzd7faumygud6ruhdwme98qpwmfv7" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .nativeZetaChain: + let expectedResult = "zeta13u6g7vqgw074mgmf2ze2cadzvkz9snlwywj304" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) @unknown default: fatalError() } From b1504a5f4e49415cb9dd207ab5ce9b07f81c2495 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Thu, 25 Jan 2024 13:35:15 +0700 Subject: [PATCH 07/11] [CI] Trigger CI From c45753b22cd9be2a868a0fbffede865517206f05 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Thu, 25 Jan 2024 13:38:30 +0700 Subject: [PATCH 08/11] feat(ZetaChain): Update JSON Public Key Type --- .../blockchains/nativezetachain/TestNativeZetaChainSigner.kt | 2 +- rust/tw_any_coin/tests/chains/zetachain/zetachain_sign.rs | 4 ++-- swift/Tests/Blockchains/NativeZetaChainTests.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainSigner.kt index 13d22c48aa2..2dd00e4f2f8 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainSigner.kt @@ -56,7 +56,7 @@ class TestNativeZetaChainSigner { // Zetachain requires a compressed public key to sign a transaction, // however an uncompressed public key is used to generate address. publicKeyType = Cosmos.SignerPublicKeyType.Secp256k1 - jsonType = "zetachain/PubKeyEthSecp256k1" + jsonType = "ethermint/PubKeyEthSecp256k1" protobufType = "/ethermint.crypto.v1.ethsecp256k1.PubKey" }.build() addAllMessages(listOf(message)) diff --git a/rust/tw_any_coin/tests/chains/zetachain/zetachain_sign.rs b/rust/tw_any_coin/tests/chains/zetachain/zetachain_sign.rs index 6fd458f0a6c..d2fa28fc914 100644 --- a/rust/tw_any_coin/tests/chains/zetachain/zetachain_sign.rs +++ b/rust/tw_any_coin/tests/chains/zetachain/zetachain_sign.rs @@ -45,7 +45,7 @@ fn test_zetachain_sign_msg_send_testnet() { // Zetachain requires a compressed public key to sign a transaction, // however an uncompressed public key is used to generate address. public_key_type: Proto::SignerPublicKeyType::Secp256k1, - json_type: "zetachain/PubKeyEthSecp256k1".into(), + json_type: "ethermint/PubKeyEthSecp256k1".into(), protobuf_type: "/ethermint.crypto.v1.ethsecp256k1.PubKey".into(), }), ..Proto::SigningInput::default() @@ -70,6 +70,6 @@ fn test_zetachain_sign_msg_send_testnet() { assert_eq!(output.signature.to_hex(), "806bea71ad30d8df709c79e7c52f47895b9de1a43d94d0ae9b380d216eb0391e24bcf49c69c192d46de4c02c3bc322363492fc33579562369f148b761692a5df"); assert_eq!( output.signature_json, - r#"[{"pub_key":{"type":"zetachain/PubKeyEthSecp256k1","value":"AoaOfhY0QX2yrf2f44IFv6D+oBiYp/0wVl0T9wVqN8Bl"},"signature":"gGvqca0w2N9wnHnnxS9HiVud4aQ9lNCumzgNIW6wOR4kvPScacGS1G3kwCw7wyI2NJL8M1eVYjafFIt2FpKl3w=="}]"# + r#"[{"pub_key":{"type":"ethermint/PubKeyEthSecp256k1","value":"AoaOfhY0QX2yrf2f44IFv6D+oBiYp/0wVl0T9wVqN8Bl"},"signature":"gGvqca0w2N9wnHnnxS9HiVud4aQ9lNCumzgNIW6wOR4kvPScacGS1G3kwCw7wyI2NJL8M1eVYjafFIt2FpKl3w=="}]"# ); } diff --git a/swift/Tests/Blockchains/NativeZetaChainTests.swift b/swift/Tests/Blockchains/NativeZetaChainTests.swift index 88ead0bc866..5c5a8650098 100644 --- a/swift/Tests/Blockchains/NativeZetaChainTests.swift +++ b/swift/Tests/Blockchains/NativeZetaChainTests.swift @@ -46,7 +46,7 @@ class NativeZetaChainTests: XCTestCase { $0.txHasher = CosmosTxHasher.keccak256 $0.signerInfo = CosmosSignerInfo.with { $0.publicKeyType = CosmosSignerPublicKeyType.secp256K1 - $0.jsonType = "zetachain/PubKeyEthSecp256k1" + $0.jsonType = "ethermint/PubKeyEthSecp256k1" $0.protobufType = "/ethermint.crypto.v1.ethsecp256k1.PubKey" } } From 741c6007267b13c081065ecf9288a30dff8ee8b1 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Thu, 25 Jan 2024 13:46:47 +0700 Subject: [PATCH 09/11] feat(ZetaChain): Fix `accountPath` in `registry.json` --- registry.json | 4 ++-- tests/chains/NativeZetaChain/TWCoinTypeTests.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/registry.json b/registry.json index bc735b2fae1..2df0f2c23c6 100644 --- a/registry.json +++ b/registry.json @@ -4440,8 +4440,8 @@ "hrp": "zeta", "addressHasher": "keccak256", "explorer": { - "url": "https://explorer.zetachain.com/cosmos", - "txPath": "/tx/", + "url": "https://explorer.zetachain.com", + "txPath": "/cosmos/tx/", "accountPath": "/address/", "sampleTx": "2DBB071DDD47985F4470A21E5943CE95D371AE4BDE2267E201D3553FB2BDCFDE", "sampleAccount": "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne" diff --git a/tests/chains/NativeZetaChain/TWCoinTypeTests.cpp b/tests/chains/NativeZetaChain/TWCoinTypeTests.cpp index 1de95b3cd92..89e2cdafc2d 100644 --- a/tests/chains/NativeZetaChain/TWCoinTypeTests.cpp +++ b/tests/chains/NativeZetaChain/TWCoinTypeTests.cpp @@ -25,5 +25,5 @@ TEST(TWNativeZetaChainCoinType, TWCoinType) { ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0); ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0); assertStringsEqual(txUrl, "https://explorer.zetachain.com/cosmos/tx/2DBB071DDD47985F4470A21E5943CE95D371AE4BDE2267E201D3553FB2BDCFDE"); - assertStringsEqual(accUrl, "https://explorer.zetachain.com/cosmos/account/zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne"); + assertStringsEqual(accUrl, "https://explorer.zetachain.com/address/zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne"); } From b5beab872f22fe5c4a39d5eb755a158c591e5783 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Thu, 25 Jan 2024 13:47:33 +0700 Subject: [PATCH 10/11] feat(ZetaChain): Fix rustfmt --- .../tw_cosmos_sdk/src/modules/compiler/protobuf_preimager.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rust/tw_cosmos_sdk/src/modules/compiler/protobuf_preimager.rs b/rust/tw_cosmos_sdk/src/modules/compiler/protobuf_preimager.rs index 06e5d2e7621..dc82a15920a 100644 --- a/rust/tw_cosmos_sdk/src/modules/compiler/protobuf_preimager.rs +++ b/rust/tw_cosmos_sdk/src/modules/compiler/protobuf_preimager.rs @@ -35,7 +35,10 @@ impl ProtobufPreimager { }) } - pub fn preimage_hash_direct(args: &SignDirectArgs, hasher: Hasher) -> SigningResult { + pub fn preimage_hash_direct( + args: &SignDirectArgs, + hasher: Hasher, + ) -> SigningResult { let tx_to_sign = ProtobufSerializer::::build_direct_sign_doc(args); let encoded_tx = serialize(&tx_to_sign)?; let tx_hash = hasher.hash(&encoded_tx); From 14dfabdcbf4bf2dca981ab01d683a83a6bd97810 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Thu, 25 Jan 2024 15:44:53 +0700 Subject: [PATCH 11/11] feat(ZetaChain): Slightly refactor NativeEvmos and NativeInjective public key types --- .../src/ethermint_public_key.rs | 49 +++++++------- .../src/injective_public_key.rs | 42 +++++++----- .../tests/chains/cosmos/cosmos_sign.rs | 65 +++++++++++++++++++ 3 files changed, 113 insertions(+), 43 deletions(-) diff --git a/rust/chains/tw_native_evmos/src/ethermint_public_key.rs b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs index 32accd79966..c2bf2d8f0ad 100644 --- a/rust/chains/tw_native_evmos/src/ethermint_public_key.rs +++ b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs @@ -4,59 +4,56 @@ use tw_coin_entry::coin_context::CoinContext; use tw_cosmos_sdk::proto::ethermint; +use tw_cosmos_sdk::public_key::secp256k1::Secp256PublicKey; use tw_cosmos_sdk::public_key::{ CosmosPublicKey, JsonPublicKey, ProtobufPublicKey, PublicKeyParams, }; -use tw_keypair::ecdsa::secp256k1; -use tw_keypair::KeyPairResult; +use tw_keypair::{tw, KeyPairResult}; use tw_memory::Data; -use tw_proto::{google, to_any}; +use tw_proto::{google, type_url}; -pub struct EthermintEthSecp256PublicKey { - public_key: Data, -} +const ETHERMINT_SECP256K1_PUBLIC_KEY_TYPE: &str = "ethermint/PubKeyEthSecp256k1"; + +pub struct EthermintEthSecp256PublicKey(Secp256PublicKey); 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(), - }) + fn default_public_key_params() -> PublicKeyParams { + PublicKeyParams { + // `NativeEvmos` requires the public key to be compressed, + // however the uncompressed public key is used to generate an address. + public_key_type: tw::PublicKeyType::Secp256k1, + json_type: ETHERMINT_SECP256K1_PUBLIC_KEY_TYPE.to_string(), + protobuf_type_url: type_url::(), + } } } impl CosmosPublicKey for EthermintEthSecp256PublicKey { fn from_bytes( - _coin: &dyn CoinContext, + coin: &dyn CoinContext, public_key_bytes: &[u8], - // Ignore custom public key parameters. - _params: Option, + maybe_params: Option, ) -> KeyPairResult { - // `NativeEvmos` requires the public key to be compressed, + // Use default Ethermint public key parameters if otherwise is not specified, // however the uncompressed public key is used to generate an address. - let public_key = secp256k1::PublicKey::try_from(public_key_bytes)?; - EthermintEthSecp256PublicKey::new(&public_key) + let params = maybe_params.unwrap_or_else(Self::default_public_key_params); + Secp256PublicKey::from_bytes(coin, public_key_bytes, Some(params)) + .map(EthermintEthSecp256PublicKey) } fn to_bytes(&self) -> Data { - self.public_key.clone() + self.0.to_bytes() } } impl JsonPublicKey for EthermintEthSecp256PublicKey { fn public_key_type(&self) -> String { - const ETHERMINT_SECP256K1_PUBLIC_KEY_TYPE: &str = "ethermint/PubKeyEthSecp256k1"; - - ETHERMINT_SECP256K1_PUBLIC_KEY_TYPE.to_string() + self.0.public_key_type() } } impl ProtobufPublicKey for EthermintEthSecp256PublicKey { fn to_proto(&self) -> google::protobuf::Any { - let proto = ethermint::crypto::v1::ethsecp256k1::PubKey { - key: self.public_key.clone(), - }; - to_any(&proto) + self.0.to_proto() } } diff --git a/rust/chains/tw_native_injective/src/injective_public_key.rs b/rust/chains/tw_native_injective/src/injective_public_key.rs index 7b15c09bded..c405867f6e8 100644 --- a/rust/chains/tw_native_injective/src/injective_public_key.rs +++ b/rust/chains/tw_native_injective/src/injective_public_key.rs @@ -4,48 +4,56 @@ use tw_coin_entry::coin_context::CoinContext; use tw_cosmos_sdk::proto::injective; -use tw_cosmos_sdk::public_key::secp256k1::prepare_secp256k1_public_key; +use tw_cosmos_sdk::public_key::secp256k1::Secp256PublicKey; use tw_cosmos_sdk::public_key::{ CosmosPublicKey, JsonPublicKey, ProtobufPublicKey, PublicKeyParams, }; use tw_keypair::KeyPairResult; use tw_memory::Data; -use tw_proto::{google, to_any}; +use tw_proto::{google, type_url}; -pub struct InjectiveEthSecp256PublicKey { - public_key: Data, +/// https://github.com/cosmostation/cosmostation-chrome-extension/blob/e2fd27d71a17993f8eef07ce30f7a04a32e52788/src/constants/cosmos.ts#L4 +const INJECTIVE_SECP256K1_PUBLIC_KEY_TYPE: &str = "injective/PubKeyEthSecp256k1"; + +pub struct InjectiveEthSecp256PublicKey(Secp256PublicKey); + +impl InjectiveEthSecp256PublicKey { + fn default_public_key_params(coin: &dyn CoinContext) -> PublicKeyParams { + PublicKeyParams { + // `NativeInjective` uses the same public key type as specified in `registry.json`. + public_key_type: coin.public_key_type(), + json_type: INJECTIVE_SECP256K1_PUBLIC_KEY_TYPE.to_string(), + protobuf_type_url: type_url::(), + } + } } impl CosmosPublicKey for InjectiveEthSecp256PublicKey { fn from_bytes( coin: &dyn CoinContext, public_key_bytes: &[u8], - // Ignore custom public key parameters. - _params: Option, + maybe_params: Option, ) -> KeyPairResult { - let public_key = prepare_secp256k1_public_key(coin.public_key_type(), public_key_bytes)?; - Ok(InjectiveEthSecp256PublicKey { public_key }) + // Use default Ethermint public key parameters if otherwise is not specified, + // however the uncompressed public key is used to generate an address. + let params = maybe_params.unwrap_or_else(|| Self::default_public_key_params(coin)); + Secp256PublicKey::from_bytes(coin, public_key_bytes, Some(params)) + .map(InjectiveEthSecp256PublicKey) } fn to_bytes(&self) -> Data { - self.public_key.clone() + self.0.to_bytes() } } impl JsonPublicKey for InjectiveEthSecp256PublicKey { fn public_key_type(&self) -> String { - /// https://github.com/cosmostation/cosmostation-chrome-extension/blob/e2fd27d71a17993f8eef07ce30f7a04a32e52788/src/constants/cosmos.ts#L4 - const INJECTIVE_SECP256K1_PUBLIC_KEY_TYPE: &str = "injective/PubKeyEthSecp256k1"; - - INJECTIVE_SECP256K1_PUBLIC_KEY_TYPE.to_string() + self.0.public_key_type() } } impl ProtobufPublicKey for InjectiveEthSecp256PublicKey { fn to_proto(&self) -> google::protobuf::Any { - let proto = injective::crypto::v1beta1::ethsecp256k1::PubKey { - key: self.public_key.clone(), - }; - to_any(&proto) + self.0.to_proto() } } diff --git a/rust/tw_any_coin/tests/chains/cosmos/cosmos_sign.rs b/rust/tw_any_coin/tests/chains/cosmos/cosmos_sign.rs index 5daa370bf10..384b8b14cfb 100644 --- a/rust/tw_any_coin/tests/chains/cosmos/cosmos_sign.rs +++ b/rust/tw_any_coin/tests/chains/cosmos/cosmos_sign.rs @@ -61,3 +61,68 @@ fn test_any_signer_sign_cosmos() { let expected = r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX"}"#; assert_eq!(output.serialized, expected); } + +/// Tests if it is possible to sign a transaction for a custom chain using `CoinType::Cosmos`. +/// Successfully broadcasted (testnet): +/// https://explorer.zetachain.com/cosmos/tx/A2FC8816657856ED274C4418C3CAEAEE645561275F6C63AB5F8B1DCFB37341A0 +#[test] +fn test_any_signer_sign_custom_chain() { + use tw_proto::Cosmos::Proto; + use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + + let send_msg = Proto::mod_Message::Send { + from_address: "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne".into(), + to_address: "zeta1cscf4ldnkkz7f0wpveur6dpd0d6p2zxnsuu70y".into(), + amounts: vec![Proto::Amount { + denom: "azeta".into(), + // 0.3 ZETA + amount: "300000000000000000".into(), + }], + ..Proto::mod_Message::Send::default() + }; + + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Protobuf, + account_number: 2726346, + chain_id: "athens_7001-1".into(), + sequence: 2, + fee: Some(Proto::Fee { + gas: 200000, + amounts: vec![], + }), + private_key: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed" + .decode_hex() + .unwrap() + .into(), + messages: vec![Proto::Message { + message_oneof: MessageEnum::send_coins_message(send_msg), + }], + // Use a different Transaction hashing algorithm. + tx_hasher: Proto::TxHasher::Keccak256, + signer_info: Some(Proto::SignerInfo { + // Zetachain requires a compressed public key to sign a transaction, + // however an uncompressed public key is used to generate address. + public_key_type: Proto::SignerPublicKeyType::Secp256k1, + json_type: "ethermint/PubKeyEthSecp256k1".into(), + protobuf_type: "/ethermint.crypto.v1.ethsecp256k1.PubKey".into(), + }), + ..Proto::SigningInput::default() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = TWDataHelper::wrap(unsafe { + tw_any_signer_sign(input_data.ptr(), CoinType::Cosmos as u32) + }) + .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.serialized, + r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpoBCpcBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEncKK3pldGExNHB5MzZzeDU3dWQ4MnQ5eXJrczl6Nmhkc3JwbjV4NmtteHMwbmUSK3pldGExY3NjZjRsZG5ra3o3ZjB3cHZldXI2ZHBkMGQ2cDJ6eG5zdXU3MHkaGwoFYXpldGESEjMwMDAwMDAwMDAwMDAwMDAwMBJhClkKTwooL2V0aGVybWludC5jcnlwdG8udjEuZXRoc2VjcDI1NmsxLlB1YktleRIjCiECho5+FjRBfbKt/Z/jggW/oP6gGJin/TBWXRP3BWo3wGUSBAoCCAEYAhIEEMCaDBpAgGvqca0w2N9wnHnnxS9HiVud4aQ9lNCumzgNIW6wOR4kvPScacGS1G3kwCw7wyI2NJL8M1eVYjafFIt2FpKl3w=="}"# + ); +}