diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt index ed829d1ce76..4de24f9b60f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt @@ -67,7 +67,7 @@ class TestCosmosTransactions { assertEquals( output.serialized, - "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI=\"}" + "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjogARI2CjRjb3Ntb3N2YWxvcGVyMWdqdHZseTlsZWw2enNrdnd0dmxnNXZod3B1OWM5d2F3N3N4end4EgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQEAN1nIfDawlHnep2bNEm14w+g7tYybJJT3htcGVS6s9D7va3ed1OUEIk9LZoc3G//VenJ+KLw26SRVBaRukgVI=\"}" ) assertEquals(output.errorMessage, "") } diff --git a/codegen-v2/Cargo.toml b/codegen-v2/Cargo.toml index a8906c0ae94..7598a3ad756 100644 --- a/codegen-v2/Cargo.toml +++ b/codegen-v2/Cargo.toml @@ -3,8 +3,6 @@ name = "codegen-v2" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [lib] name = "libparser" path = "src/lib.rs" diff --git a/include/TrustWalletCore/TWBlockchain.h b/include/TrustWalletCore/TWBlockchain.h index 06a88beba2e..75da789d240 100644 --- a/include/TrustWalletCore/TWBlockchain.h +++ b/include/TrustWalletCore/TWBlockchain.h @@ -65,6 +65,8 @@ enum TWBlockchain { TWBlockchainSui = 50, TWBlockchainGreenfield = 51, TWBlockchainInternetComputer = 52, + TWBlockchainNativeEvmos = 55, // Cosmos + TWBlockchainNativeInjective = 56, // Cosmos }; TW_EXTERN_C_END diff --git a/registry.json b/registry.json index 966fc0a2d71..65e637c59b4 100644 --- a/registry.json +++ b/registry.json @@ -3177,6 +3177,7 @@ "curve": "secp256k1", "publicKeyType": "secp256k1", "hrp": "thor", + "addressHasher": "sha256ripemd", "chainId": "thorchain-mainnet-v1", "explorer": { "url": "https://viewblock.io/thorchain", @@ -3973,7 +3974,7 @@ "coinId": 20009001, "symbol": "EVMOS", "decimals": 18, - "blockchain": "Cosmos", + "blockchain": "NativeEvmos", "chainId": "evmos_9001-2", "derivation": [ { @@ -4365,7 +4366,7 @@ "coinId": 10000060, "symbol": "INJ", "decimals": 18, - "blockchain": "Cosmos", + "blockchain": "NativeInjective", "derivation": [ { "path": "m/44'/60'/0'/0/0" diff --git a/rust/Cargo.lock b/rust/Cargo.lock index c6fdc8c55a6..d58c01c250c 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1599,7 +1599,9 @@ dependencies = [ "tw_any_coin", "tw_coin_entry", "tw_coin_registry", + "tw_cosmos_sdk", "tw_encoding", + "tw_hash", "tw_keypair", "tw_memory", "tw_misc", @@ -1624,6 +1626,18 @@ dependencies = [ "tw_proto", ] +[[package]] +name = "tw_bech32_address" +version = "0.1.0" +dependencies = [ + "serde", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", +] + [[package]] name = "tw_bitcoin" version = "0.1.0" @@ -1648,6 +1662,7 @@ version = "0.1.0" dependencies = [ "serde_json", "tw_encoding", + "tw_hash", "tw_keypair", "tw_memory", "tw_misc", @@ -1665,13 +1680,48 @@ dependencies = [ "tw_aptos", "tw_bitcoin", "tw_coin_entry", + "tw_cosmos", "tw_ethereum", "tw_evm", + "tw_hash", "tw_internet_computer", "tw_keypair", "tw_memory", "tw_misc", + "tw_native_evmos", + "tw_native_injective", "tw_ronin", + "tw_thorchain", +] + +[[package]] +name = "tw_cosmos" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_keypair", + "tw_proto", +] + +[[package]] +name = "tw_cosmos_sdk" +version = "0.1.0" +dependencies = [ + "pb-rs", + "quick-protobuf", + "serde", + "serde_json", + "tw_bech32_address", + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", ] [[package]] @@ -1680,6 +1730,7 @@ version = "0.1.0" dependencies = [ "arbitrary", "bcs", + "bech32", "bs58", "ciborium", "data-encoding", @@ -1797,6 +1848,28 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tw_native_evmos" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_keypair", + "tw_memory", + "tw_proto", +] + +[[package]] +name = "tw_native_injective" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_keypair", + "tw_memory", + "tw_proto", +] + [[package]] name = "tw_number" version = "0.1.0" @@ -1834,6 +1907,17 @@ dependencies = [ "tw_proto", ] +[[package]] +name = "tw_thorchain" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_keypair", + "tw_memory", + "tw_proto", +] + [[package]] name = "tw_utxo" version = "0.1.0" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 872986a6a6a..87d437eff4c 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,10 +1,16 @@ [workspace] members = [ + "chains/tw_cosmos", + "chains/tw_native_evmos", + "chains/tw_native_injective", + "chains/tw_thorchain", "tw_any_coin", "tw_aptos", + "tw_bech32_address", "tw_bitcoin", "tw_coin_entry", "tw_coin_registry", + "tw_cosmos_sdk", "tw_encoding", "tw_ethereum", "tw_evm", diff --git a/rust/chains/tw_cosmos/Cargo.toml b/rust/chains/tw_cosmos/Cargo.toml new file mode 100644 index 00000000000..cbeae311ec3 --- /dev/null +++ b/rust/chains/tw_cosmos/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "tw_cosmos" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_keypair = { path = "../../tw_keypair" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk", features = ["test-utils"] } diff --git a/rust/chains/tw_cosmos/src/entry.rs b/rust/chains/tw_cosmos/src/entry.rs new file mode 100644 index 00000000000..ca2d84efcf7 --- /dev/null +++ b/rust/chains/tw_cosmos/src/entry.rs @@ -0,0 +1,94 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::AddressResult; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_cosmos_sdk::address::{Address, Bech32Prefix}; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; +use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; +use tw_keypair::tw; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct CosmosEntry; + +impl CoinEntry for CosmosEntry { + type AddressPrefix = Bech32Prefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + prefix: Option, + ) -> AddressResult { + Address::from_str_with_coin_and_prefix(coin, address.to_string(), prefix) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: tw::PublicKey, + _derivation: Derivation, + prefix: Option, + ) -> AddressResult { + Address::with_public_key_coin_context(coin, &public_key, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + TWSigner::::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + TWTransactionCompiler::::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + TWTransactionCompiler::::compile( + coin, + input, + signatures, + public_keys, + ) + } +} diff --git a/rust/chains/tw_cosmos/src/lib.rs b/rust/chains/tw_cosmos/src/lib.rs new file mode 100644 index 00000000000..1afc00798db --- /dev/null +++ b/rust/chains/tw_cosmos/src/lib.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod entry; diff --git a/rust/chains/tw_native_evmos/Cargo.toml b/rust/chains/tw_native_evmos/Cargo.toml new file mode 100644 index 00000000000..cc4762a7971 --- /dev/null +++ b/rust/chains/tw_native_evmos/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tw_native_evmos" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk", features = ["test-utils"] } diff --git a/rust/chains/tw_native_evmos/src/context.rs b/rust/chains/tw_native_evmos/src/context.rs new file mode 100644 index 00000000000..edb431767ac --- /dev/null +++ b/rust/chains/tw_native_evmos/src/context.rs @@ -0,0 +1,22 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ethermint_public_key::EthermintEthSecp256PublicKey; +use tw_cosmos_sdk::address::Address; +use tw_cosmos_sdk::context::CosmosContext; +use tw_cosmos_sdk::hasher::keccak256_hasher::Keccak256Hasher; +use tw_cosmos_sdk::private_key::secp256k1::Secp256PrivateKey; +use tw_cosmos_sdk::signature::secp256k1::Secp256k1Signature; + +pub struct NativeEvmosContext; + +impl CosmosContext for NativeEvmosContext { + type Address = Address; + type TxHasher = Keccak256Hasher; + type PrivateKey = Secp256PrivateKey; + type PublicKey = EthermintEthSecp256PublicKey; + type Signature = Secp256k1Signature; +} diff --git a/rust/chains/tw_native_evmos/src/entry.rs b/rust/chains/tw_native_evmos/src/entry.rs new file mode 100644 index 00000000000..eb25d7ea816 --- /dev/null +++ b/rust/chains/tw_native_evmos/src/entry.rs @@ -0,0 +1,89 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::NativeEvmosContext; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::AddressResult; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_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; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct NativeEvmosEntry; + +impl CoinEntry for NativeEvmosEntry { + type AddressPrefix = Bech32Prefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + prefix: Option, + ) -> AddressResult { + Address::from_str_with_coin_and_prefix(coin, address.to_string(), prefix) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: tw::PublicKey, + _derivation: Derivation, + prefix: Option, + ) -> AddressResult { + Address::with_public_key_coin_context(coin, &public_key, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + TWSigner::::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + TWTransactionCompiler::::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + TWTransactionCompiler::::compile(coin, input, signatures, public_keys) + } +} diff --git a/rust/chains/tw_native_evmos/src/ethermint_public_key.rs b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs new file mode 100644 index 00000000000..5f53f261e5b --- /dev/null +++ b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs @@ -0,0 +1,67 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::coin_context::CoinContext; +use tw_cosmos_sdk::proto::ethermint; +use tw_cosmos_sdk::public_key::{CosmosPublicKey, JsonPublicKey, ProtobufPublicKey}; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::KeyPairResult; +use tw_keypair::{tw, KeyPairError}; +use tw_memory::Data; +use tw_proto::{google, to_any}; + +pub struct EthermintEthSecp256PublicKey { + public_key: Data, +} + +impl EthermintEthSecp256PublicKey { + pub fn new(public_key: &secp256k1::PublicKey) -> KeyPairResult { + Ok(EthermintEthSecp256PublicKey { + // NativeEvmos chain requires the public key to be compressed. + // This trick is needed because `registry.json` contains extended public key type. + public_key: public_key.compressed().to_vec(), + }) + } +} + +impl CosmosPublicKey for EthermintEthSecp256PublicKey { + fn from_private_key(coin: &dyn CoinContext, private_key: &tw::PrivateKey) -> KeyPairResult + where + Self: Sized, + { + let tw_public_key = private_key.get_public_key_by_type(coin.public_key_type())?; + let secp256k1_key = tw_public_key + .to_secp256k1() + .ok_or(KeyPairError::InvalidPublicKey)?; + EthermintEthSecp256PublicKey::new(secp256k1_key) + } + + fn from_bytes(_coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult { + let public_key = secp256k1::PublicKey::try_from(public_key_bytes)?; + EthermintEthSecp256PublicKey::new(&public_key) + } + + fn to_bytes(&self) -> Data { + self.public_key.clone() + } +} + +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() + } +} + +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) + } +} diff --git a/rust/chains/tw_native_evmos/src/lib.rs b/rust/chains/tw_native_evmos/src/lib.rs new file mode 100644 index 00000000000..d7aa4534c83 --- /dev/null +++ b/rust/chains/tw_native_evmos/src/lib.rs @@ -0,0 +1,9 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod context; +pub mod entry; +pub mod ethermint_public_key; diff --git a/rust/chains/tw_native_injective/Cargo.toml b/rust/chains/tw_native_injective/Cargo.toml new file mode 100644 index 00000000000..d793539a241 --- /dev/null +++ b/rust/chains/tw_native_injective/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tw_native_injective" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk", features = ["test-utils"] } diff --git a/rust/chains/tw_native_injective/src/context.rs b/rust/chains/tw_native_injective/src/context.rs new file mode 100644 index 00000000000..898c034d656 --- /dev/null +++ b/rust/chains/tw_native_injective/src/context.rs @@ -0,0 +1,22 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::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; + +pub struct NativeInjectiveContext; + +impl CosmosContext for NativeInjectiveContext { + type Address = Address; + type TxHasher = Keccak256Hasher; + type PrivateKey = Secp256PrivateKey; + type PublicKey = InjectiveEthSecp256PublicKey; + type Signature = Secp256k1Signature; +} diff --git a/rust/chains/tw_native_injective/src/entry.rs b/rust/chains/tw_native_injective/src/entry.rs new file mode 100644 index 00000000000..b11d0036c87 --- /dev/null +++ b/rust/chains/tw_native_injective/src/entry.rs @@ -0,0 +1,94 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::NativeInjectiveContext; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::AddressResult; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_cosmos_sdk::address::{Address, Bech32Prefix, CosmosAddress}; +use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; +use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; +use tw_keypair::tw; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct NativeInjectiveEntry; + +impl CoinEntry for NativeInjectiveEntry { + type AddressPrefix = Bech32Prefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + Address::from_str_with_coin(coin, address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: tw::PublicKey, + _derivation: Derivation, + prefix: Option, + ) -> AddressResult { + Address::with_public_key_coin_context(coin, &public_key, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + TWSigner::::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + TWTransactionCompiler::::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + TWTransactionCompiler::::compile( + coin, + input, + signatures, + public_keys, + ) + } +} diff --git a/rust/chains/tw_native_injective/src/injective_public_key.rs b/rust/chains/tw_native_injective/src/injective_public_key.rs new file mode 100644 index 00000000000..084d47e94f0 --- /dev/null +++ b/rust/chains/tw_native_injective/src/injective_public_key.rs @@ -0,0 +1,57 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use 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_keypair::tw::PrivateKey; +use tw_keypair::KeyPairResult; +use tw_memory::Data; +use tw_proto::{google, to_any}; + +pub struct InjectiveEthSecp256PublicKey { + public_key: Data, +} + +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)?; + Ok(InjectiveEthSecp256PublicKey { public_key }) + } + + fn to_bytes(&self) -> Data { + self.public_key.clone() + } +} + +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() + } +} + +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) + } +} diff --git a/rust/chains/tw_native_injective/src/lib.rs b/rust/chains/tw_native_injective/src/lib.rs new file mode 100644 index 00000000000..a61663bd72c --- /dev/null +++ b/rust/chains/tw_native_injective/src/lib.rs @@ -0,0 +1,9 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod context; +pub mod entry; +pub mod injective_public_key; diff --git a/rust/chains/tw_thorchain/Cargo.toml b/rust/chains/tw_thorchain/Cargo.toml new file mode 100644 index 00000000000..a39bc4d7c80 --- /dev/null +++ b/rust/chains/tw_thorchain/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tw_thorchain" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk", features = ["test-utils"] } diff --git a/rust/chains/tw_thorchain/src/compiler.rs b/rust/chains/tw_thorchain/src/compiler.rs new file mode 100644 index 00000000000..a6fd1b8bbc8 --- /dev/null +++ b/rust/chains/tw_thorchain/src/compiler.rs @@ -0,0 +1,40 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::signing_input::ThorchainSigningInput; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct ThorchainCompiler; + +impl ThorchainCompiler { + pub fn preimage_hashes( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + ThorchainSigningInput::prepare_signing_input(&mut input); + TWTransactionCompiler::::preimage_hashes(coin, input) + } + + pub fn compile( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + ThorchainSigningInput::prepare_signing_input(&mut input); + TWTransactionCompiler::::compile( + coin, + input, + signatures, + public_keys, + ) + } +} diff --git a/rust/chains/tw_thorchain/src/entry.rs b/rust/chains/tw_thorchain/src/entry.rs new file mode 100644 index 00000000000..81f8bf562f1 --- /dev/null +++ b/rust/chains/tw_thorchain/src/entry.rs @@ -0,0 +1,88 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::compiler::ThorchainCompiler; +use crate::signer::ThorchainSigner; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::AddressResult; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_cosmos_sdk::address::{Address, Bech32Prefix, CosmosAddress}; +use tw_keypair::tw; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct ThorchainEntry; + +impl CoinEntry for ThorchainEntry { + type AddressPrefix = Bech32Prefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + Address::from_str_with_coin(coin, address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: tw::PublicKey, + _derivation: Derivation, + prefix: Option, + ) -> AddressResult { + Address::with_public_key_coin_context(coin, &public_key, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + ThorchainSigner::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + ThorchainCompiler::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + ThorchainCompiler::compile(coin, input, signatures, public_keys) + } +} diff --git a/rust/chains/tw_thorchain/src/lib.rs b/rust/chains/tw_thorchain/src/lib.rs new file mode 100644 index 00000000000..be313341897 --- /dev/null +++ b/rust/chains/tw_thorchain/src/lib.rs @@ -0,0 +1,10 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod compiler; +pub mod entry; +pub mod signer; +pub mod signing_input; diff --git a/rust/chains/tw_thorchain/src/signer.rs b/rust/chains/tw_thorchain/src/signer.rs new file mode 100644 index 00000000000..d086eacd1c3 --- /dev/null +++ b/rust/chains/tw_thorchain/src/signer.rs @@ -0,0 +1,23 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::signing_input::ThorchainSigningInput; +use tw_coin_entry::coin_context::CoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; +use tw_proto::Cosmos::Proto; + +pub struct ThorchainSigner; + +impl ThorchainSigner { + pub fn sign( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + ) -> Proto::SigningOutput<'static> { + ThorchainSigningInput::prepare_signing_input(&mut input); + TWSigner::::sign(coin, input) + } +} diff --git a/rust/chains/tw_thorchain/src/signing_input.rs b/rust/chains/tw_thorchain/src/signing_input.rs new file mode 100644 index 00000000000..66863f7288f --- /dev/null +++ b/rust/chains/tw_thorchain/src/signing_input.rs @@ -0,0 +1,23 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +const THORCHAIN_PREFIX_MSG_SEND: &str = "thorchain/MsgSend"; + +pub struct ThorchainSigningInput; + +impl ThorchainSigningInput { + pub fn prepare_signing_input(input: &mut Proto::SigningInput) { + for message in input.messages.iter_mut() { + if let MessageEnum::send_coins_message(ref mut msg_send) = message.message_oneof { + msg_send.type_prefix = Cow::from(THORCHAIN_PREFIX_MSG_SEND); + } + } + } +} diff --git a/rust/tw_any_coin/Cargo.toml b/rust/tw_any_coin/Cargo.toml index 17ba71621f1..bb8e9314f3d 100644 --- a/rust/tw_any_coin/Cargo.toml +++ b/rust/tw_any_coin/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] tw_coin_entry = { path = "../tw_coin_entry" } tw_coin_registry = { path = "../tw_coin_registry" } +tw_hash = { path = "../tw_hash" } tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } tw_misc = { path = "../tw_misc" } @@ -17,6 +18,7 @@ test-utils = [] serde = { version = "1.0.163", features = ["derive"] } serde_json = { version = "1.0.96" } tw_any_coin = { path = "./", features = ["test-utils"] } +tw_cosmos_sdk = { path = "../tw_cosmos_sdk", features = ["test-utils"] } tw_encoding = { path = "../tw_encoding" } tw_keypair = { path = "../tw_keypair", features = ["test-utils"] } tw_memory = { path = "../tw_memory", features = ["test-utils"] } diff --git a/rust/tw_any_coin/src/any_address.rs b/rust/tw_any_coin/src/any_address.rs index 23fe3fe42e4..5d5098482a9 100644 --- a/rust/tw_any_coin/src/any_address.rs +++ b/rust/tw_any_coin/src/any_address.rs @@ -36,7 +36,19 @@ impl AnyAddress { prefix: Option, ) -> AddressResult { let (ctx, entry) = coin_dispatcher(coin).map_err(|_| AddressError::UnknownCoinType)?; - let address = entry.normalize_address(&ctx, address, prefix)?; + entry.validate_address(&ctx, address, prefix)?; + let address = entry.normalize_address(&ctx, address)?; + Ok(AnyAddress { coin, address }) + } + + /// Creates an address from a string representation and a coin type. + /// Please note that his function does not validate if the address belongs to the given chain. + pub(crate) fn with_string_unchecked( + coin: CoinType, + address: &str, + ) -> AddressResult { + let (ctx, entry) = coin_dispatcher(coin).map_err(|_| AddressError::UnknownCoinType)?; + let address = entry.normalize_address(&ctx, address)?; Ok(AnyAddress { coin, address }) } @@ -57,7 +69,7 @@ impl AnyAddress { #[inline] pub fn get_data(&self) -> AddressResult { let (ctx, entry) = coin_dispatcher(self.coin).map_err(|_| AddressError::UnknownCoinType)?; - entry.address_to_data(&ctx, &self.address, None) + entry.address_to_data(&ctx, &self.address) } /// Returns the address string representation. diff --git a/rust/tw_any_coin/src/ffi/tw_any_address.rs b/rust/tw_any_coin/src/ffi/tw_any_address.rs index 25aa2373a00..de69826f9f8 100644 --- a/rust/tw_any_coin/src/ffi/tw_any_address.rs +++ b/rust/tw_any_coin/src/ffi/tw_any_address.rs @@ -8,6 +8,7 @@ use crate::any_address::AnyAddress; use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::prefix::AddressPrefix; use tw_keypair::ffi::pubkey::TWPublicKey; use tw_memory::ffi::tw_data::TWData; use tw_memory::ffi::tw_string::TWString; @@ -32,6 +33,28 @@ pub unsafe extern "C" fn tw_any_address_is_valid(string: *const TWString, coin: AnyAddress::is_valid(coin, string, None) } +/// Determines if the string is a valid Any address with the given hrp. +/// +/// \param string address to validate. +/// \param coin coin type of the address. +/// \param hrp explicit given hrp of the given address. +/// \return bool indicating if the address is valid. +#[no_mangle] +pub unsafe extern "C" fn tw_any_address_is_valid_bech32( + string: *const TWString, + coin: u32, + hrp: *const TWString, +) -> bool { + let string = try_or_false!(TWString::from_ptr_as_ref(string)); + let string = try_or_false!(string.as_str()); + + let hrp = try_or_false!(TWString::from_ptr_as_ref(hrp)); + let hrp = try_or_false!(hrp.as_str()); + + let prefix = AddressPrefix::Hrp(hrp.to_string()); + AnyAddress::is_valid(coin, string, Some(prefix)) +} + /// Creates an address from a string representation and a coin type. Must be deleted with `TWAnyAddressDelete` after use. /// /// \param string address to create. @@ -70,6 +93,34 @@ pub unsafe extern "C" fn tw_any_address_create_with_public_key_derivation( .unwrap_or_else(|_| std::ptr::null_mut()) } +/// Creates an bech32 address from a public key and a given hrp. +/// +/// \param public_key derivates the address from the public key. +/// \param coin coin type of the address. +/// \param hrp hrp of the address. +/// \return TWAnyAddress pointer or nullptr if public key is invalid. +#[no_mangle] +pub unsafe extern "C" fn tw_any_address_create_bech32_with_public_key( + public_key: *mut TWPublicKey, + coin: u32, + hrp: *const TWString, +) -> *mut TWAnyAddress { + let public_key = try_or_else!(TWPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut); + + let hrp = try_or_else!(TWString::from_ptr_as_ref(hrp), std::ptr::null_mut); + let hrp = try_or_else!(hrp.as_str(), std::ptr::null_mut); + + let prefix = AddressPrefix::Hrp(hrp.to_string()); + AnyAddress::with_public_key( + coin, + public_key.as_ref().clone(), + Derivation::default(), + Some(prefix), + ) + .map(|any_address| TWAnyAddress(any_address).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + /// Deletes an address. /// /// \param address address to delete. @@ -102,3 +153,28 @@ pub unsafe extern "C" fn tw_any_address_data(address: *const TWAnyAddress) -> *m let data = try_or_else!(address.0.get_data(), std::ptr::null_mut); TWData::from(data).into_ptr() } + +/// Creates an address from a string representation and a coin type. Must be deleted with `TWAnyAddressDelete` after use. +/// This function does not check if the address belongs to the given chain. +/// +/// \param string address to create. +/// \param coin coin type of the address. +/// \return `TWAnyAddress` pointer or nullptr if address and coin are invalid. +/// +/// # Warning +/// +/// This function should only be used when address prefix is unavailable to be passed to this function. +/// Consider using `tw_any_address_create__with_string` if the prefix is known. +/// Please note that this function should be removed when all chains are migrated to Rust. +#[no_mangle] +pub unsafe extern "C" fn tw_any_address_create_with_string_unchecked( + string: *const TWString, + coin: u32, +) -> *mut TWAnyAddress { + let string = try_or_else!(TWString::from_ptr_as_ref(string), std::ptr::null_mut); + let string = try_or_else!(string.as_str(), std::ptr::null_mut); + + AnyAddress::with_string_unchecked(coin, string) + .map(|any_address| TWAnyAddress(any_address).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} diff --git a/rust/tw_any_coin/tests/chains/aptos/aptos_compile.rs b/rust/tw_any_coin/tests/chains/aptos/aptos_compile.rs index 9f21f68d317..114d7e9a46e 100644 --- a/rust/tw_any_coin/tests/chains/aptos/aptos_compile.rs +++ b/rust/tw_any_coin/tests/chains/aptos/aptos_compile.rs @@ -25,7 +25,7 @@ use tw_proto::TxCompiler::Proto as CompilerProto; use tw_proto::{deserialize, serialize}; #[test] -fn test_any_signer_sign_aptos() { +fn test_any_signer_compile_aptos() { let input = aptos_sign_transfer_input(); // Step 2: Obtain preimage hash diff --git a/rust/tw_any_coin/tests/chains/mod.rs b/rust/tw_any_coin/tests/chains/mod.rs index fa5b3a9718c..165ba051b71 100644 --- a/rust/tw_any_coin/tests/chains/mod.rs +++ b/rust/tw_any_coin/tests/chains/mod.rs @@ -5,3 +5,6 @@ // file LICENSE at the root of the source code distribution tree. mod aptos; +mod native_evmos; +mod native_injective; +mod thorchain; diff --git a/rust/tw_any_coin/tests/chains/native_evmos/mod.rs b/rust/tw_any_coin/tests/chains/native_evmos/mod.rs new file mode 100644 index 00000000000..4d62bbecabe --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_evmos/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod native_evmos_sign; diff --git a/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_sign.rs b/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_sign.rs new file mode 100644 index 00000000000..3615cfa3548 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_sign.rs @@ -0,0 +1,122 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; +use tw_coin_entry::error::SigningErrorType; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_encoding::hex::DecodeHex; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; +use tw_proto::{deserialize, serialize}; + +const NATIVE_EVMOS_COIN_TYPE: u32 = 20009001; + +fn account_1037_private_key() -> Cow<'static, [u8]> { + "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005" + .decode_hex() + .unwrap() + .into() +} + +fn account_2139877_private_key() -> Cow<'static, [u8]> { + "79bcbded1a5678ab34e6d9db9ad78e4e480e7b22723cc5fbf59e843732e1a8e5" + .decode_hex() + .unwrap() + .into() +} + +#[test] +fn test_sign_native_evmos_tx_json() { + let send_msg = Proto::mod_Message::Send { + from_address: "evmos1hsk6jryyqjfhp5dhc55tc9jtckygx0ep4mur4z".into(), + to_address: "evmos1zt50azupanqlfam5afhv3hexwyutnuke45f6ye".into(), + amounts: vec![make_amount("muon", "1")], + ..Proto::mod_Message::Send::default() + }; + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::JSON, + account_number: 1037, + chain_id: "evmos_9001-2".into(), + sequence: 8, + fee: Some(make_fee(200000, make_amount("muon", "200"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::send_coins_message(send_msg))], + ..Proto::SigningInput::default() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = + TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), NATIVE_EVMOS_COIN_TYPE) }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + assert_eq!( + output.json, + r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"evmos1hsk6jryyqjfhp5dhc55tc9jtckygx0ep4mur4z","to_address":"evmos1zt50azupanqlfam5afhv3hexwyutnuke45f6ye"}}],"signatures":[{"pub_key":{"type":"ethermint/PubKeyEthSecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg=="}]}}"# + ); + assert_eq!( + output.signature_json, + r#"[{"pub_key":{"type":"ethermint/PubKeyEthSecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg=="}]"# + ); +} + +/// CompoundingAuthz +#[test] +fn test_sign_native_evmos_tx_protobuf() { + use Proto::mod_Message::mod_AuthGrant::OneOfgrant_type as ProtoGrantType; + use Proto::mod_Message::mod_StakeAuthorization::OneOfvalidators as ProtoValidatorsType; + + let allow_list = Proto::mod_Message::mod_StakeAuthorization::Validators { + address: vec!["evmosvaloper1umk407eed7af6anvut6llg2zevnf0dn0feqqny".into()], + }; + let stake_authorization = Proto::mod_Message::StakeAuthorization { + authorization_type: Proto::mod_Message::AuthorizationType::DELEGATE, + validators: ProtoValidatorsType::allow_list(allow_list), + ..Proto::mod_Message::StakeAuthorization::default() + }; + let auth_grant = Proto::mod_Message::AuthGrant { + granter: "evmos12m9grgas60yk0kult076vxnsrqz8xpjy9rpf3e".into(), + grantee: "evmos18fzq4nac28gfma6gqfvkpwrgpm5ctar2z9mxf3".into(), + grant_type: ProtoGrantType::grant_stake(stake_authorization), + expiration: 1692309600, + }; + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Protobuf, + account_number: 2139877, + chain_id: "evmos_9001-2".into(), + sequence: 3, + fee: Some(make_fee(180859, make_amount("aevmos", "4521475000000000"))), + private_key: account_2139877_private_key(), + messages: vec![make_message(MessageEnum::auth_grant(auth_grant))], + ..Proto::SigningInput::default() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = + TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), NATIVE_EVMOS_COIN_TYPE) }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + // Original test: https://github.com/trustwallet/wallet-core/blob/a60033f797e33628e557af7c66be539c8d78bc61/tests/chains/Evmos/SignerTests.cpp#L91-L124 + // Please note the signature has been updated according to the serialization of the `StakeAuthorization` message. + // Previous: CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5EjUKM2V2bW9zdmFsb3BlcjF1bWs0MDdlZWQ3YWY2YW52dXQ2bGxnMnpldm5mMGRuMGZlcXFueSABEgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkAm17CZgB7m+CPVlITnrHosklMTL9zrUeGRs8FL8N0GcRami9zdJ+e3xuXOtJmwP7G6QNh85CRYjFj8a8lpmmJM + assert_eq!( + output.serialized, + r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5IAESNQozZXZtb3N2YWxvcGVyMXVtazQwN2VlZDdhZjZhbnZ1dDZsbGcyemV2bmYwZG4wZmVxcW55EgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkBXaTo3nk5EMFW9Euheez5ADx2bWo7XisNJ5vuGj1fKXh6CGNJGfJj/q1XUkBzaCvPNg+EcFHgtJdVSyF4cJZTg"}"# + ); +} diff --git a/rust/tw_any_coin/tests/chains/native_injective/mod.rs b/rust/tw_any_coin/tests/chains/native_injective/mod.rs new file mode 100644 index 00000000000..2781305c015 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_injective/mod.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod native_injective_compile; +pub mod native_injective_sign; diff --git a/rust/tw_any_coin/tests/chains/native_injective/native_injective_compile.rs b/rust/tw_any_coin/tests/chains/native_injective/native_injective_compile.rs new file mode 100644 index 00000000000..fbffdddfb91 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_injective/native_injective_compile.rs @@ -0,0 +1,96 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::ffi::tw_transaction_compiler::{ + tw_transaction_compiler_compile, tw_transaction_compiler_pre_image_hashes, +}; +use tw_coin_entry::error::SigningErrorType; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_data_vector_helper::TWDataVectorHelper; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; +use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_proto::{deserialize, serialize}; + +const NATIVE_INJECTIVE_COIN_TYPE: u32 = 10000060; +const ACCOUNT_88701_PUBLIC_KEY: &str = "04088ac2919987d927368cb2be2ade44cd0ed3616745a9699cae264b3fc5a7c3607d99f441b8340990ee990cb3eaf560f1f0bafe600c7e94a4be8392166984f728"; + +fn send_tx_input() -> Proto::SigningInput<'static> { + let send_msg = Proto::mod_Message::Send { + from_address: "inj1d0jkrsd09c7pule43y3ylrul43lwwcqaky8w8c".into(), + to_address: "inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd".into(), + amounts: vec![make_amount("inj", "10000000000")], + ..Proto::mod_Message::Send::default() + }; + Proto::SigningInput { + account_number: 88701, + chain_id: "injective-1".into(), + sequence: 0, + fee: Some(make_fee(110000, make_amount("inj", "100000000000000"))), + public_key: ACCOUNT_88701_PUBLIC_KEY.decode_hex().unwrap().into(), + messages: vec![make_message(MessageEnum::send_coins_message(send_msg))], + ..Proto::SigningInput::default() + } +} + +#[test] +fn test_compile_native_injective_tx_protobuf() { + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Protobuf, + ..send_tx_input() + }; + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + // Step 1: Obtain preimage hash + + let preimage_output_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_pre_image_hashes(NATIVE_INJECTIVE_COIN_TYPE, input_data.ptr()) + }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let preimage_output: CompilerProto::PreSigningOutput = + deserialize(&preimage_output_data).unwrap(); + assert_eq!(preimage_output.error, SigningErrorType::OK); + assert!(preimage_output.error_message.is_empty()); + + let expected_preimage = "0a8f010a8c010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e64126c0a2a696e6a3164306a6b7273643039633770756c6534337933796c72756c34336c77776371616b7938773863122a696e6a31786d706b6d78723461733030656d32337463327a676d7579793267723468337767636c3676641a120a03696e6a120b3130303030303030303030129c010a7c0a740a2d2f696e6a6563746976652e63727970746f2e763162657461312e657468736563703235366b312e5075624b657912430a4104088ac2919987d927368cb2be2ade44cd0ed3616745a9699cae264b3fc5a7c3607d99f441b8340990ee990cb3eaf560f1f0bafe600c7e94a4be8392166984f72812040a020801121c0a160a03696e6a120f31303030303030303030303030303010b0db061a0b696e6a6563746976652d3120fdb405"; + assert_eq!(preimage_output.data.to_hex(), expected_preimage); + let expected_prehash = "57dc10c3d1893ff16e1f5a47fa4b2e96f37b9c57d98a42d88c971debb4947ec9"; + assert_eq!(preimage_output.data_hash.to_hex(), expected_prehash); + + // Step 2: Compile transaction info + + // Simulate signature, normally obtained from signature server + let signature = "f7a9ec0a521170bb5566ca973d3c73a1b69b162d99ce022059189991ec440637333394ff1c9e75fad84eb114393969f20989b036f1dfed28949e906dc0077421".decode_hex().unwrap(); + let public_key = ACCOUNT_88701_PUBLIC_KEY.decode_hex().unwrap(); + + let signatures = TWDataVectorHelper::create([signature.clone()]); + let public_keys = TWDataVectorHelper::create([public_key]); + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let output_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_compile( + NATIVE_INJECTIVE_COIN_TYPE, + input_data.ptr(), + signatures.ptr(), + public_keys.ptr(), + ) + }) + .to_vec() + .expect("!tw_transaction_compiler_compile returned nullptr"); + + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + let expected_encoded = r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Co8BCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKKmluajFkMGprcnNkMDljN3B1bGU0M3kzeWxydWw0M2x3d2NxYWt5OHc4YxIqaW5qMXhtcGtteHI0YXMwMGVtMjN0YzJ6Z211eXkyZ3I0aDN3Z2NsNnZkGhIKA2luahILMTAwMDAwMDAwMDASnAEKfAp0Ci0vaW5qZWN0aXZlLmNyeXB0by52MWJldGExLmV0aHNlY3AyNTZrMS5QdWJLZXkSQwpBBAiKwpGZh9knNoyyvireRM0O02FnRalpnK4mSz/Fp8NgfZn0Qbg0CZDumQyz6vVg8fC6/mAMfpSkvoOSFmmE9ygSBAoCCAESHAoWCgNpbmoSDzEwMDAwMDAwMDAwMDAwMBCw2wYaQPep7ApSEXC7VWbKlz08c6G2mxYtmc4CIFkYmZHsRAY3MzOU/xyedfrYTrEUOTlp8gmJsDbx3+0olJ6QbcAHdCE="}"#; + assert_eq!(output.serialized, expected_encoded); + assert_eq!(output.signature.to_hex(), signature.to_hex()); +} diff --git a/rust/tw_any_coin/tests/chains/native_injective/native_injective_sign.rs b/rust/tw_any_coin/tests/chains/native_injective/native_injective_sign.rs new file mode 100644 index 00000000000..f5d7e448a27 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_injective/native_injective_sign.rs @@ -0,0 +1,97 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; +use tw_coin_entry::error::SigningErrorType; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; +use tw_proto::{deserialize, serialize}; + +const NATIVE_INJECTIVE_COIN_TYPE: u32 = 10000060; + +fn account_17396_private_key() -> Cow<'static, [u8]> { + "9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1" + .decode_hex() + .unwrap() + .into() +} + +fn send_tx_input() -> Proto::SigningInput<'static> { + let send_msg = Proto::mod_Message::Send { + from_address: "inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a".into(), + to_address: "inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd".into(), + amounts: vec![make_amount("inj", "10000000000")], + ..Proto::mod_Message::Send::default() + }; + Proto::SigningInput { + account_number: 17396, + chain_id: "injective-1".into(), + sequence: 1, + fee: Some(make_fee(110000, make_amount("inj", "100000000000000"))), + private_key: account_17396_private_key(), + messages: vec![make_message(MessageEnum::send_coins_message(send_msg))], + ..Proto::SigningInput::default() + } +} + +#[test] +fn test_sign_native_injective_tx_protobuf() { + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Protobuf, + ..send_tx_input() + }; + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = TWDataHelper::wrap(unsafe { + tw_any_signer_sign(input_data.ptr(), NATIVE_INJECTIVE_COIN_TYPE) + }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + // https://www.mintscan.io/injective/txs/135DD2C4A1910E4334A9C0F15125DA992E724EBF23FEB9638FCB71218BB064A5 + assert_eq!( + output.serialized, + r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Co8BCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKKmluajEzdTZnN3ZxZ3cwNzRtZ21mMnplMmNhZHp2a3o5c25sd2NydHE4YRIqaW5qMXhtcGtteHI0YXMwMGVtMjN0YzJ6Z211eXkyZ3I0aDN3Z2NsNnZkGhIKA2luahILMTAwMDAwMDAwMDASngEKfgp0Ci0vaW5qZWN0aXZlLmNyeXB0by52MWJldGExLmV0aHNlY3AyNTZrMS5QdWJLZXkSQwpBBFoMa4O4vZgn5QcnDK20mbfjqQlSRvaiITKB94PYd8mLJWdCdBsGOfMXdo/k9MJ2JmDCESKDp2hdgVUH3uMikXMSBAoCCAEYARIcChYKA2luahIPMTAwMDAwMDAwMDAwMDAwELDbBhpAx2vkplmzeK7n3puCFGPWhLd0l/ZC/CYkGl+stH+3S3hiCvIe7uwwMpUlNaSwvT8HwF1kNUp+Sx2m0Uo1x5xcFw=="}"# + ); + assert_eq!(output.signature.to_hex(), "c76be4a659b378aee7de9b821463d684b77497f642fc26241a5facb47fb74b78620af21eeeec3032952535a4b0bd3f07c05d64354a7e4b1da6d14a35c79c5c17"); +} + +#[test] +fn test_sign_native_injective_tx_json() { + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::JSON, + ..send_tx_input() + }; + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = TWDataHelper::wrap(unsafe { + tw_any_signer_sign(input_data.ptr(), NATIVE_INJECTIVE_COIN_TYPE) + }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + // This transaction hasn't been broadcasted. + assert_eq!( + output.json, + r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"100000000000000","denom":"inj"}],"gas":"110000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"10000000000","denom":"inj"}],"from_address":"inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a","to_address":"inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd"}}],"signatures":[{"pub_key":{"type":"injective/PubKeyEthSecp256k1","value":"BFoMa4O4vZgn5QcnDK20mbfjqQlSRvaiITKB94PYd8mLJWdCdBsGOfMXdo/k9MJ2JmDCESKDp2hdgVUH3uMikXM="},"signature":"7wwedceebL95DwbClz5AzEp2Z74itHC7raiV976DcacfjrJ58oDfjbAO5UOZQAlihiYBP7PpyISFQ72FPRhdZA=="}]}}"# + ); + assert_eq!( + output.signature_json, + r#"[{"pub_key":{"type":"injective/PubKeyEthSecp256k1","value":"BFoMa4O4vZgn5QcnDK20mbfjqQlSRvaiITKB94PYd8mLJWdCdBsGOfMXdo/k9MJ2JmDCESKDp2hdgVUH3uMikXM="},"signature":"7wwedceebL95DwbClz5AzEp2Z74itHC7raiV976DcacfjrJ58oDfjbAO5UOZQAlihiYBP7PpyISFQ72FPRhdZA=="}]"# + ); +} diff --git a/rust/tw_any_coin/tests/chains/thorchain/mod.rs b/rust/tw_any_coin/tests/chains/thorchain/mod.rs new file mode 100644 index 00000000000..3f38961fcbb --- /dev/null +++ b/rust/tw_any_coin/tests/chains/thorchain/mod.rs @@ -0,0 +1,11 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod test_cases; +mod thorchain_compile; +mod thorchain_sign; + +const THORCHAIN_COIN_TYPE: u32 = 931; diff --git a/rust/tw_any_coin/tests/chains/thorchain/test_cases.rs b/rust/tw_any_coin/tests/chains/thorchain/test_cases.rs new file mode 100644 index 00000000000..e626e3466c7 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/thorchain/test_cases.rs @@ -0,0 +1,41 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +/// https://viewblock.io/thorchain/tx/FD0445AFFC4ED9ACCB7B5D3ADE361DAE4596EA096340F1360F1020381EA454AF +pub(super) mod send_fd0445af { + use super::*; + + pub const PRIVATE_KEY: &str = + "7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"; + pub const JSON_TX_PREIMAGE: &str = r#"{"account_number":"593","chain_id":"thorchain","fee":{"amount":[{"amount":"2000000","denom":"rune"}],"gas":"200000"},"memo":"","msgs":[{"type":"thorchain/MsgSend","value":{"amount":[{"amount":"10000000","denom":"rune"}],"from_address":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","to_address":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"}}],"sequence":"3"}"#; + /// Expected `json` value. + pub const JSON_TX: &str = r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"2000000","denom":"rune"}],"gas":"200000"},"memo":"","msg":[{"type":"thorchain/MsgSend","value":{"amount":[{"amount":"10000000","denom":"rune"}],"from_address":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","to_address":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"qgpMX3WNq4DsNBnYtdmBD4ejiailK4uI/m3/YVqCSNF8AtkUOTmP48ztqCbpkWTFvw1/9S8/ivsFxOcK6AI0jA=="}]}}"#; + /// Expected `signature` for JSON signing mode. + pub const JSON_SIGNING_SIGNATURE: &str = "aa0a4c5f758dab80ec3419d8b5d9810f87a389a8a52b8b88fe6dff615a8248d17c02d91439398fe3cceda826e99164c5bf0d7ff52f3f8afb05c4e70ae802348c"; + /// Expected `signature_json` for JSON signing mode. + pub const JSON_SIGNING_SIGNATURE_JSON: &str = r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"qgpMX3WNq4DsNBnYtdmBD4ejiailK4uI/m3/YVqCSNF8AtkUOTmP48ztqCbpkWTFvw1/9S8/ivsFxOcK6AI0jA=="}]"#; + + pub fn signing_input() -> Proto::SigningInput<'static> { + let send_msg = Proto::mod_Message::Send { + from_address: "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r".into(), + to_address: "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn".into(), + amounts: vec![make_amount("rune", "10000000")], + ..Proto::mod_Message::Send::default() + }; + Proto::SigningInput { + account_number: 593, + chain_id: "thorchain".into(), + sequence: 3, + fee: Some(make_fee(200000, make_amount("rune", "2000000"))), + messages: vec![make_message(MessageEnum::send_coins_message(send_msg))], + ..Proto::SigningInput::default() + } + } +} diff --git a/rust/tw_any_coin/tests/chains/thorchain/thorchain_compile.rs b/rust/tw_any_coin/tests/chains/thorchain/thorchain_compile.rs new file mode 100644 index 00000000000..ed5a2eb354c --- /dev/null +++ b/rust/tw_any_coin/tests/chains/thorchain/thorchain_compile.rs @@ -0,0 +1,97 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::chains::thorchain::test_cases::send_fd0445af::{ + signing_input, JSON_SIGNING_SIGNATURE, JSON_SIGNING_SIGNATURE_JSON, JSON_TX, JSON_TX_PREIMAGE, + PRIVATE_KEY, +}; +use crate::chains::thorchain::THORCHAIN_COIN_TYPE; +use tw_any_coin::ffi::tw_transaction_compiler::{ + tw_transaction_compiler_compile, tw_transaction_compiler_pre_image_hashes, +}; +use tw_coin_entry::error::SigningErrorType; +use tw_encoding::hex::ToHex; +use tw_hash::H256; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_data_vector_helper::TWDataVectorHelper; +use tw_misc::traits::ToBytesVec; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_proto::{deserialize, serialize}; + +#[test] +fn test_any_signer_compile_thorchain() { + let private_key = secp256k1::KeyPair::try_from(PRIVATE_KEY).unwrap(); + let public_key = private_key.public().to_vec(); + + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::JSON, + public_key: public_key.clone().into(), + ..signing_input() + }; + + // Step 2: Obtain preimage hash + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let preimage_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_pre_image_hashes(THORCHAIN_COIN_TYPE, input_data.ptr()) + }) + .to_vec() + .expect("!tw_transaction_compiler_pre_image_hashes returned nullptr"); + + let preimage: CompilerProto::PreSigningOutput = + deserialize(&preimage_data).expect("Coin entry returned an invalid output"); + + assert_eq!(preimage.error, SigningErrorType::OK); + assert!(preimage.error_message.is_empty()); + let tx_preimage = String::from_utf8(preimage.data.to_vec()) + .expect("Invalid transaction preimage. Expected a JSON object"); + assert_eq!(tx_preimage, JSON_TX_PREIMAGE); + + // Step 3: Sign the data "externally" + + let tx_hash = H256::try_from(preimage.data_hash.as_ref()).expect("Invalid Transaction Hash"); + + let signature = private_key + .sign(tx_hash) + .expect("Error signing data") + .to_vec(); + assert!( + signature.to_hex().contains(JSON_SIGNING_SIGNATURE), + "{} must contain {}", + signature.to_hex(), + JSON_SIGNING_SIGNATURE + ); + + // Step 4: Compile transaction info + + let signatures = TWDataVectorHelper::create([signature]); + let public_keys = TWDataVectorHelper::create([public_key]); + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let output_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_compile( + THORCHAIN_COIN_TYPE, + input_data.ptr(), + signatures.ptr(), + public_keys.ptr(), + ) + }) + .to_vec() + .expect("!tw_transaction_compiler_compile returned nullptr"); + + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + // https://viewblock.io/thorchain/tx/FD0445AFFC4ED9ACCB7B5D3ADE361DAE4596EA096340F1360F1020381EA454AF + assert_eq!(output.json, JSON_TX); + assert_eq!(output.signature.to_hex(), JSON_SIGNING_SIGNATURE); + assert_eq!(output.signature_json, JSON_SIGNING_SIGNATURE_JSON); +} diff --git a/rust/tw_any_coin/tests/chains/thorchain/thorchain_sign.rs b/rust/tw_any_coin/tests/chains/thorchain/thorchain_sign.rs new file mode 100644 index 00000000000..585166daa76 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/thorchain/thorchain_sign.rs @@ -0,0 +1,41 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::chains::thorchain::test_cases::send_fd0445af::{ + signing_input, JSON_SIGNING_SIGNATURE, JSON_SIGNING_SIGNATURE_JSON, JSON_TX, PRIVATE_KEY, +}; +use crate::chains::thorchain::THORCHAIN_COIN_TYPE; +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; +use tw_coin_entry::error::SigningErrorType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_proto::Cosmos::Proto; +use tw_proto::{deserialize, serialize}; + +#[test] +fn test_any_signer_sign_thorchain() { + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::JSON, + private_key: PRIVATE_KEY.decode_hex().unwrap().into(), + ..signing_input() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = + TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), THORCHAIN_COIN_TYPE) }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + // https://viewblock.io/thorchain/tx/FD0445AFFC4ED9ACCB7B5D3ADE361DAE4596EA096340F1360F1020381EA454AF + assert_eq!(output.json, JSON_TX); + assert_eq!(output.signature.to_hex(), JSON_SIGNING_SIGNATURE); + assert_eq!(output.signature_json, JSON_SIGNING_SIGNATURE_JSON); +} diff --git a/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs b/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs index 10013b4e6d3..0f4a6e9d928 100644 --- a/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs +++ b/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs @@ -5,8 +5,9 @@ // file LICENSE at the root of the source code distribution tree. use tw_any_coin::ffi::tw_any_address::{ - tw_any_address_create_with_public_key_derivation, tw_any_address_create_with_string, - tw_any_address_data, tw_any_address_description, tw_any_address_is_valid, + tw_any_address_create_bech32_with_public_key, tw_any_address_create_with_public_key_derivation, + tw_any_address_create_with_string, tw_any_address_data, tw_any_address_description, + tw_any_address_is_valid, tw_any_address_is_valid_bech32, }; use tw_any_coin::test_utils::TWAnyAddressHelper; use tw_coin_entry::derivation::Derivation; @@ -16,10 +17,12 @@ use tw_encoding::hex::DecodeHex; use tw_keypair::ffi::privkey::tw_private_key_get_public_key_by_type; use tw_keypair::test_utils::tw_private_key_helper::TWPrivateKeyHelper; use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; +use tw_keypair::tw::PublicKeyType; use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_memory::test_utils::tw_string_helper::TWStringHelper; const ETHEREUM_COIN_TYPE: u32 = 60; +const OSMOSIS_COIN_TYPE: u32 = 10000118; #[test] fn test_any_address_derive() { @@ -39,11 +42,20 @@ fn test_any_address_derive() { }, // By default, Bitcoin will return a P2PKH address. BlockchainType::Bitcoin => "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", + BlockchainType::Cosmos if coin.id == "cosmos" => { + "cosmos1ten42eesehw0ktddcp0fws7d3ycsqez3lynlqx" + }, + // Skip other Cosmos chains as they have different addresses. + // TODO fix this when `CoinType` is generated by a codegen tool. + BlockchainType::Cosmos => continue, BlockchainType::Ethereum => "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309", - BlockchainType::Ronin => "ronin:Ac1ec44E4f0ca7D172B7803f6836De87Fb72b309", BlockchainType::InternetComputer => { "290cc7c359f44c8516fc169c5ed4f0f3ae2e24bf5de0d4c51f5e7545b5474faa" }, + BlockchainType::NativeEvmos => "evmos14s0vgnj0pjnazu4hsqlksdk7slah9vcfvt8ssm", + BlockchainType::NativeInjective => "inj14s0vgnj0pjnazu4hsqlksdk7slah9vcfyrp6ct", + BlockchainType::Ronin => "ronin:Ac1ec44E4f0ca7D172B7803f6836De87Fb72b309", + BlockchainType::Thorchain => "thor1ten42eesehw0ktddcp0fws7d3ycsqez3er2y4e", BlockchainType::Unsupported => unreachable!(), }; @@ -64,6 +76,7 @@ fn test_any_address_derive() { #[test] fn test_any_address_normalize_eth() { for coin in supported_coin_items() { + // TODO match `CoinType` when it's generated. let (denormalized, expected_normalized) = match coin.blockchain { BlockchainType::Aptos => ( "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", @@ -73,17 +86,35 @@ fn test_any_address_normalize_eth() { "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", ), + BlockchainType::Cosmos if coin.id == "cosmos" => ( + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", + ), + // Skip other Cosmos chains until `CoinType` is not generated by a codegen tool. + BlockchainType::Cosmos => continue, BlockchainType::Ethereum => ( "0xb16db98b365b1f89191996942612b14f1da4bd5f", "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f", ), + BlockchainType::InternetComputer => ( + "290CC7C359F44C8516FC169C5ED4F0F3AE2E24BF5DE0D4C51F5E7545B5474FAA", + "290cc7c359f44c8516fc169c5ed4f0f3ae2e24bf5de0d4c51f5e7545b5474faa", + ), + BlockchainType::NativeEvmos => ( + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34", + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34", + ), + BlockchainType::NativeInjective => ( + "inj14py36sx57ud82t9yrks9z6hdsrpn5x6k8tf7m3", + "inj14py36sx57ud82t9yrks9z6hdsrpn5x6k8tf7m3", + ), BlockchainType::Ronin => ( "0xb16db98b365b1f89191996942612b14f1da4bd5f", "ronin:b16Db98B365B1f89191996942612B14F1Da4Bd5f", ), - BlockchainType::InternetComputer => ( - "290CC7C359F44C8516FC169C5ED4F0F3AE2E24BF5DE0D4C51F5E7545B5474FAA", - "290cc7c359f44c8516fc169c5ed4f0f3ae2e24bf5de0d4c51f5e7545b5474faa", + BlockchainType::Thorchain => ( + "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", + "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", ), BlockchainType::Unsupported => unreachable!(), }; @@ -121,21 +152,40 @@ fn test_any_address_is_valid_coin() { "bc1qunq74p3h8425hr6wllevlvqqr6sezfxj262rff", "bc1pwse34zfpvt344rvlt7tw0ngjtfh9xasc4q03avf0lk74jzjpzjuqaz7ks5", ], + BlockchainType::Cosmos if coin.id == "cosmos" => vec![ + "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", + "cosmosvalconspub1zcjduepqjnnwe2jsywv0kfc97pz04zkm7tc9k2437cde2my3y5js9t7cw9mstfg3sa", + ], + // Skip other Cosmos chains until `CoinType` is not generated by a codegen tool. + BlockchainType::Cosmos => continue, BlockchainType::Ethereum => vec![ "0xb16db98b365b1f89191996942612b14f1da4bd5f", "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f", ], + BlockchainType::InternetComputer => vec![ + "fb257577279ecac604d4780214af95aa6adc3a814f6f3d6d7ac844d1deca500a", + "e90c48d54847f4758f1d6b589a1db2500757a49a6722d4f775e050107b4b752d", + "a7c5baf393aed527ef6fb3869fbf84dd4e562edf9b04bd8f9bfbd6b8e6a22776", + "4cb2ca5cfcfa1d952f8cd7f0ec46c96e1023ab057b83a2c7ce236b9e71ccca0b", + ], + BlockchainType::NativeEvmos => vec![ + "evmos14py36sx57ud82t9yrks9z6hdsrpn5x6k0r05np", + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34" + ], + BlockchainType::NativeInjective => vec![ + "inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a", + "inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd" + ], BlockchainType::Ronin => vec![ "0xb16db98b365b1f89191996942612b14f1da4bd5f", "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f", "ronin:b16db98b365b1f89191996942612b14f1da4bd5f", "ronin:b16Db98B365B1f89191996942612B14F1Da4Bd5f", ], - BlockchainType::InternetComputer => vec![ - "fb257577279ecac604d4780214af95aa6adc3a814f6f3d6d7ac844d1deca500a", - "e90c48d54847f4758f1d6b589a1db2500757a49a6722d4f775e050107b4b752d", - "a7c5baf393aed527ef6fb3869fbf84dd4e562edf9b04bd8f9bfbd6b8e6a22776", - "4cb2ca5cfcfa1d952f8cd7f0ec46c96e1023ab057b83a2c7ce236b9e71ccca0b", + BlockchainType::Thorchain => vec![ + "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", + "thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65", ], _ => unreachable!(), }; @@ -162,6 +212,11 @@ fn test_any_address_is_valid_coin_invalid() { BlockchainType::Bitcoin => { vec!["0xb16db98b365b1f89191996942612b14f1da4bd5f"] }, + BlockchainType::Cosmos => vec![ + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax6", + "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe", + "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", + ], BlockchainType::Ethereum | BlockchainType::Ronin => { vec!["b16Db98B365B1f89191996942612B14F1Da4Bd5f"] }, @@ -170,6 +225,12 @@ fn test_any_address_is_valid_coin_invalid() { "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278bce", "553357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278", ], + BlockchainType::NativeEvmos => vec!["evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw"], + BlockchainType::NativeInjective => vec!["ini13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a"], + BlockchainType::Thorchain => vec![ + "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", + "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0e", + ], BlockchainType::Unsupported => unreachable!(), }; @@ -191,3 +252,36 @@ fn test_any_address_get_data_eth() { let data = TWDataHelper::wrap(unsafe { tw_any_address_data(any_address.ptr()) }); assert_eq!(data.to_vec(), Some(addr.decode_hex().unwrap())); } + +#[test] +fn test_any_address_is_valid_bech32() { + let addr = "juno1mry47pkga5tdswtluy0m8teslpalkdq0gnn4mf"; + + let address_str = TWStringHelper::create(addr); + let hrp = TWStringHelper::create("juno"); + // Should be valid even though Osmosis chain has `osmo` default hrp. + let result = + unsafe { tw_any_address_is_valid_bech32(address_str.ptr(), OSMOSIS_COIN_TYPE, hrp.ptr()) }; + assert!(result); +} + +#[test] +fn test_any_address_create_bech32_with_public_key() { + let private_key = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ); + let public_key = TWPublicKeyHelper::wrap(unsafe { + tw_private_key_get_public_key_by_type(private_key.ptr(), PublicKeyType::Secp256k1 as u32) + }); + let hrp = TWStringHelper::create("juno"); + + // Should be valid even though Osmosis chain has `osmo` default hrp. + let any_address = TWAnyAddressHelper::wrap(unsafe { + tw_any_address_create_bech32_with_public_key(public_key.ptr(), OSMOSIS_COIN_TYPE, hrp.ptr()) + }); + + let description = + TWStringHelper::wrap(unsafe { tw_any_address_description(any_address.ptr()) }); + let expected = "juno1ten42eesehw0ktddcp0fws7d3ycsqez3fksy86"; + assert_eq!(description.to_string(), Some(expected.to_string())); +} diff --git a/rust/tw_any_coin/tests/tw_any_signer_ffi_tests.rs b/rust/tw_any_coin/tests/tw_any_signer_ffi_tests.rs index 8af80dbc39f..7677f6c838b 100644 --- a/rust/tw_any_coin/tests/tw_any_signer_ffi_tests.rs +++ b/rust/tw_any_coin/tests/tw_any_signer_ffi_tests.rs @@ -12,6 +12,7 @@ use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_number::U256; use tw_proto::{deserialize, serialize}; +const COSMOS_COIN_TYPE: u32 = 118; const ETHEREUM_COIN_TYPE: u32 = 60; #[test] @@ -55,6 +56,58 @@ fn test_any_signer_sign_eth() { assert_eq!(output.encoded.to_hex(), expected); } +#[test] +fn test_any_signer_sign_cosmos() { + use tw_proto::Cosmos::Proto; + use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + + let private_key = "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af" + .decode_hex() + .unwrap(); + + let send_msg = Proto::mod_Message::Send { + from_address: "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx".into(), + to_address: "cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp".into(), + amounts: vec![Proto::Amount { + denom: "uatom".into(), + amount: "400000".into(), + }], + ..Proto::mod_Message::Send::default() + }; + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Protobuf, + account_number: 546179, + chain_id: "cosmoshub-4".into(), + sequence: 0, + fee: Some(Proto::Fee { + gas: 200000, + amounts: vec![Proto::Amount { + denom: "uatom".into(), + amount: "1000".into(), + }], + }), + private_key: private_key.into(), + messages: vec![Proto::Message { + message_oneof: MessageEnum::send_coins_message(send_msg), + }], + ..Proto::SigningInput::default() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = + TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), COSMOS_COIN_TYPE) }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX"}"#; + assert_eq!(output.serialized, expected); +} + #[test] fn test_any_signer_sign_unknown_coin() { let unsupported_coin = u32::MAX; diff --git a/rust/tw_aptos/fuzz/Cargo.toml b/rust/tw_aptos/fuzz/Cargo.toml index 2d3506a0ea6..721a84b3ab9 100644 --- a/rust/tw_aptos/fuzz/Cargo.toml +++ b/rust/tw_aptos/fuzz/Cargo.toml @@ -9,7 +9,6 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.4" -tw_number = { path = "../../tw_number" } tw_proto = { path = "../../tw_proto", features = ["fuzz"] } [dependencies.tw_aptos] diff --git a/rust/tw_aptos/src/entry.rs b/rust/tw_aptos/src/entry.rs index 4438a15ae2b..e2a16cd0ac8 100644 --- a/rust/tw_aptos/src/entry.rs +++ b/rust/tw_aptos/src/entry.rs @@ -44,6 +44,15 @@ impl CoinEntry for AptosEntry { Address::from_str(address) } + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + fn derive_address( &self, _coin: &dyn CoinContext, diff --git a/rust/tw_bech32_address/Cargo.toml b/rust/tw_bech32_address/Cargo.toml new file mode 100644 index 00000000000..1ca0cfa493f --- /dev/null +++ b/rust/tw_bech32_address/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "tw_bech32_address" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0.163", features = [ "derive" ] } +tw_coin_entry = { path = "../tw_coin_entry" } +tw_encoding = { path = "../tw_encoding" } +tw_hash = { path = "../tw_hash" } +tw_keypair = { path = "../tw_keypair" } +tw_memory = { path = "../tw_memory" } diff --git a/rust/tw_bech32_address/src/bech32_prefix.rs b/rust/tw_bech32_address/src/bech32_prefix.rs new file mode 100644 index 00000000000..8f6f418ed2c --- /dev/null +++ b/rust/tw_bech32_address/src/bech32_prefix.rs @@ -0,0 +1,22 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::error::AddressError; +use tw_coin_entry::prefix::AddressPrefix; + +pub struct Bech32Prefix { + pub hrp: String, +} + +impl TryFrom for Bech32Prefix { + type Error = AddressError; + + fn try_from(prefix: AddressPrefix) -> Result { + match prefix { + AddressPrefix::Hrp(hrp) => Ok(Bech32Prefix { hrp }), + } + } +} diff --git a/rust/tw_bech32_address/src/lib.rs b/rust/tw_bech32_address/src/lib.rs new file mode 100644 index 00000000000..67c361f90a7 --- /dev/null +++ b/rust/tw_bech32_address/src/lib.rs @@ -0,0 +1,441 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::bech32_prefix::Bech32Prefix; +use serde::{Serialize, Serializer}; +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_encoding::bech32; +use tw_hash::hasher::Hasher; +use tw_hash::H160; +use tw_keypair::tw::{PrivateKey, PublicKey, PublicKeyType}; +use tw_memory::Data; + +pub mod bech32_prefix; + +pub struct Bech32Address { + hrp: String, + key_hash: Data, + /// An address string created from this `hrp` and `key_hash`. + address_str: String, +} + +impl Bech32Address { + pub fn new(hrp: String, key_hash: Data) -> AddressResult { + let address_str = bech32::encode(&hrp, &key_hash).map_err(|_| AddressError::InvalidHrp)?; + Ok(Bech32Address { + hrp, + key_hash, + address_str, + }) + } + + pub fn with_public_key_hasher( + hrp: String, + public_key: &PublicKey, + hasher: Hasher, + ) -> AddressResult { + let public_key_bytes = match public_key.public_key_type() { + // If the public key is extended, skips the first byte (Evmos specific). + // https://github.com/trustwallet/wallet-core/blob/d67078daa580b37063c97be66a625aaee9664882/src/Bech32Address.cpp#L61-L64 + PublicKeyType::Secp256k1Extended => public_key + .to_secp256k1() + .ok_or(AddressError::PublicKeyTypeMismatch)? + .uncompressed_without_prefix() + .to_vec(), + _ => public_key.to_bytes(), + }; + + let public_key_hash = hasher.hash(&public_key_bytes); + if public_key_hash.len() < H160::LEN { + return Err(AddressError::UnexpectedHasher); + } + + let (_skipped_bytes, key_bytes) = + public_key_hash.split_at(public_key_hash.len() - H160::LEN); + Bech32Address::new(hrp, key_bytes.to_vec()) + } + + pub fn with_public_key_coin_context( + coin: &dyn CoinContext, + public_key: &PublicKey, + prefix: Option, + ) -> AddressResult { + let hrp = match prefix { + Some(Bech32Prefix { hrp }) => hrp, + None => coin.hrp().ok_or(AddressError::InvalidHrp)?, + }; + let address_hasher = coin + .address_hasher() + .ok_or(AddressError::UnexpectedHasher)?; + Self::with_public_key_hasher(hrp, public_key, address_hasher) + } + + pub fn with_private_key_coin_context( + coin: &dyn CoinContext, + private_key: &PrivateKey, + ) -> AddressResult { + let hrp = coin.hrp().ok_or(AddressError::InvalidHrp)?; + let public_key_type = coin.public_key_type(); + let public_key = private_key + .get_public_key_by_type(public_key_type) + .map_err(|_| AddressError::PublicKeyTypeMismatch)?; + let address_hasher = coin + .address_hasher() + .ok_or(AddressError::UnexpectedHasher)?; + Bech32Address::with_public_key_hasher(hrp, &public_key, address_hasher) + } + + pub fn from_str_checked( + expected_hrp: &str, + address_str: String, + ) -> AddressResult { + let bech32::Decoded { hrp, bytes } = + bech32::decode(&address_str).map_err(|_| AddressError::InvalidInput)?; + // Copied from the legacy Bech32Address.cpp: + // https://github.com/trustwallet/wallet-core/blob/d67078daa580b37063c97be66a625aaee9664882/src/Bech32Address.cpp#L21 + if !hrp.starts_with(expected_hrp) { + return Err(AddressError::InvalidHrp); + } + Ok(Bech32Address { + hrp, + key_hash: bytes, + address_str, + }) + } + + pub fn from_str_with_coin_and_prefix( + coin: &dyn CoinContext, + address_str: String, + prefix: Option, + ) -> AddressResult + where + Self: Sized, + { + let hrp = match prefix { + Some(Bech32Prefix { hrp }) => hrp, + None => coin.hrp().ok_or(AddressError::InvalidHrp)?, + }; + Self::from_str_checked(&hrp, address_str) + } + + pub fn key_hash(&self) -> &[u8] { + &self.key_hash + } + + pub fn hrp(&self) -> &str { + &self.hrp + } +} + +impl CoinAddress for Bech32Address { + fn data(&self) -> Data { + self.key_hash.to_vec() + } +} + +impl FromStr for Bech32Address { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + let bech32::Decoded { hrp, bytes } = + bech32::decode(s).map_err(|_| AddressError::InvalidInput)?; + Ok(Bech32Address { + hrp, + key_hash: bytes, + address_str: s.to_string(), + }) + } +} + +impl fmt::Display for Bech32Address { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.address_str) + } +} + +impl fmt::Debug for Bech32Address { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} + +impl Serialize for Bech32Address { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_encoding::hex::{DecodeHex, ToHex}; + + struct FromPublicKeyTestInput<'a> { + hrp: &'a str, + private_key: &'a str, + public_key_type: PublicKeyType, + hasher: Hasher, + expected: &'a str, + } + + fn test_from_public_key(input: FromPublicKeyTestInput<'_>) { + let private_key = PrivateKey::new(input.private_key.decode_hex().unwrap()).unwrap(); + let public_key = private_key + .get_public_key_by_type(input.public_key_type) + .unwrap(); + let actual = + Bech32Address::with_public_key_hasher(input.hrp.to_string(), &public_key, input.hasher) + .unwrap(); + assert_eq!(actual.to_string(), input.expected); + } + + #[test] + fn test_address_from_str_checked_valid() { + fn test_impl(addr: &str, hrp: &str) { + Bech32Address::from_str_checked(hrp, addr.to_string()) + .unwrap_or_else(|e| panic!("ERROR={:?}: hrp={} addr={}", e, hrp, addr)); + } + + test_impl("bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", "bnb"); + + test_impl("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", "cosmos"); + test_impl( + "cosmospub1addwnpepqftjsmkr7d7nx4tmhw4qqze8w39vjq364xt8etn45xqarlu3l2wu2n7pgrq", + "cosmos", + ); + test_impl( + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", + "cosmos", + ); + test_impl( + "cosmosvalconspub1zcjduepqjnnwe2jsywv0kfc97pz04zkm7tc9k2437cde2my3y5js9t7cw9mstfg3sa", + "cosmos", + ); + + test_impl("one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe", "one"); + test_impl("one1tp7xdd9ewwnmyvws96au0e7e7mz6f8hjqr3g3p", "one"); + + test_impl("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j", "io"); + + test_impl("zil1fwh4ltdguhde9s7nysnp33d5wye6uqpugufkz7", "zil"); + + test_impl( + "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz", + "erd", + ); + test_impl( + "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", + "erd", + ); + test_impl( + "erd19nu5t7hszckwah5nlcadmk5rlchtugzplznskffpwecygcu0520s9tnyy0", + "erd", + ); + + // uppercase version + test_impl("BNB1GRPF0955H0YKZQ3AR5NMUM7Y6GDFL6LXFN46H2", "bnb"); + } + + #[test] + fn test_address_from_str_checked_invalid() { + fn test_impl(addr: &str, hrp: &str) { + Bech32Address::from_str_checked(hrp, addr.to_string()) + .expect_err(&format!("hrp={} addr={}", hrp, addr)); + } + + // 1-char diff + test_impl("bnb1grpf0955h0ykzq3ar6nmum7y6gdfl6lxfn46h2", "bnb"); + // mixed case + test_impl("bnb1grPF0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", "bnb"); + + test_impl("cosmos1xsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", "cosmos"); + test_impl( + "cosmospub1xddwnpepqftjsmkr7d7nx4tmhw4qqze8w39vjq364xt8etn45xqarlu3l2wu2n7pgrq", + "cosmos", + ); + test_impl( + "cosmosvaloper1xxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", + "cosmos", + ); + test_impl( + "cosmosvalconspub1xcjduepqjnnwe2jsywv0kfc97pz04zkm7tc9k2437cde2my3y5js9t7cw9mstfg3sa", + "cosmos", + ); + + test_impl("one1a50tun737ulcvwy0yvve0pe", "one"); + test_impl("oe1tp7xdd9ewwnmyvws96au0ee7e7mz6f8hjqr3g3p", "one"); + + test_impl("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8", "io"); + test_impl("io187wzp08vnhjpkydnr97qlh8kh0dpkkytfam8j", "io"); + test_impl("it187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j", "io"); + + test_impl("", "erd"); + test_impl( + "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35!", + "erd", + ); + test_impl( + "xerd19nu5t7hszckwah5nlcadmk5rlchtugzplznskffpwecygcu0520s9tnyy0", + "erd", + ); + } + + #[test] + fn test_decode() { + fn test_impl(addr: &str, hrp: &str, expected_hash: &str) { + let actual = Bech32Address::from_str_checked(hrp, addr.to_string()).unwrap(); + assert_eq!(actual.key_hash.to_hex(), expected_hash); + } + + test_impl( + "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe", + "one", + "ed1ebe4fd1f73f86388f231997859ca42c07da5d", + ); + + test_impl( + "io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j", + "io", + "3f9c20bcec9de520d88d98cbe07ee7b5ded0dac4", + ); + } + + #[test] + fn test_from_hrp_and_hash() { + fn test_impl(hrp: &str, key_hash: &str, expected_addr: &str) { + let actual = + Bech32Address::new(hrp.to_string(), key_hash.decode_hex().unwrap()).unwrap(); + assert_eq!(actual.to_string(), expected_addr); + } + + test_impl( + "bnb", + "b6561dcc104130059a7c08f48c64610c1f6f9064", + "bnb1ketpmnqsgycqtxnupr6gcerpps0klyryuudz05", + ); + test_impl( + "one", + "587c66b4b973a7b231d02ebbc7e7d9f6c5a49ef2", + "one1tp7xdd9ewwnmyvws96au0e7e7mz6f8hjqr3g3p", + ); + test_impl( + "zil", + "0x91cdDcEBE846ce4d47712287EEe53cF17c2cfB77", + "zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg", + ); + test_impl( + "zil", + "1d19918a737306218b5cbb3241fcdcbd998c3a72", + "zil1r5verznnwvrzrz6uhveyrlxuhkvccwnju4aehf", + ); + } + + #[test] + fn test_from_hrp_and_public_key_hasher() { + test_from_public_key(FromPublicKeyTestInput { + hrp: "bnb", + private_key: "95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256ripemd, + expected: "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "cosmos", + private_key: "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256ripemd, + expected: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "one", + private_key: "e2f88b4974ae763ca1c2db49218802c2e441293a09eaa9ab681779e05d1b7b94", + public_key_type: PublicKeyType::Secp256k1Extended, + hasher: Hasher::Keccak256, + expected: "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "io", + private_key: "0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f", + public_key_type: PublicKeyType::Secp256k1Extended, + hasher: Hasher::Keccak256, + expected: "io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "zil", + private_key: "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256, + expected: "zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg", + }); + } + + /// From same public key, but different hashes: different results. + #[test] + fn test_different_hashes() { + test_from_public_key(FromPublicKeyTestInput { + hrp: "hrp", + private_key: "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256ripemd, + expected: "hrp186zwn9h0z9fyvwfqs4jl92cw3kexusm4xw6ptp", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "hrp", + private_key: "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256, + expected: "hrp1j8xae6lggm8y63m3y2r7aefu797ze7mhgfetvu", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "hrp", + private_key: "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + public_key_type: PublicKeyType::Secp256k1Extended, + hasher: Hasher::Keccak256, + expected: "hrp17hff3s97m5uxpjcdq3nzqxxatt8cmumnsf03su", + }); + } + + /// From same public key, but different prefixes: different results (checksum). + #[test] + fn test_different_hrp() { + test_from_public_key(FromPublicKeyTestInput { + hrp: "hrpone", + private_key: "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256ripemd, + expected: "hrpone186zwn9h0z9fyvwfqs4jl92cw3kexusm47das6p", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "hrptwo", + private_key: "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256ripemd, + expected: "hrptwo186zwn9h0z9fyvwfqs4jl92cw3kexusm4qzr8p7", + }); + + test_from_public_key(FromPublicKeyTestInput { + hrp: "hrpthree", + private_key: "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + public_key_type: PublicKeyType::Secp256k1, + hasher: Hasher::Sha256ripemd, + expected: "hrpthree186zwn9h0z9fyvwfqs4jl92cw3kexusm4wuqkvd", + }); + } +} diff --git a/rust/tw_bitcoin/Cargo.toml b/rust/tw_bitcoin/Cargo.toml index 24771b92ac7..451491eb345 100644 --- a/rust/tw_bitcoin/Cargo.toml +++ b/rust/tw_bitcoin/Cargo.toml @@ -3,8 +3,6 @@ name = "tw_bitcoin" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] bitcoin = "0.30.0" secp256k1 = { version = "0.27.0", features = [ "global-context", "rand-std" ] } diff --git a/rust/tw_bitcoin/src/entry.rs b/rust/tw_bitcoin/src/entry.rs index 0809b39f315..a847c4a0690 100644 --- a/rust/tw_bitcoin/src/entry.rs +++ b/rust/tw_bitcoin/src/entry.rs @@ -49,12 +49,23 @@ impl CoinEntry for BitcoinEntry { #[inline] fn parse_address( &self, - _coin: &dyn CoinContext, + coin: &dyn CoinContext, address: &str, _prefix: Option, + ) -> AddressResult { + self.parse_address_unchecked(coin, address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, ) -> AddressResult { let address = bitcoin::address::Address::from_str(address) .map_err(|_| AddressError::FromHexError)? + // At this moment, we support mainnet only. + // This check will be removed in coming PRs. .require_network(bitcoin::Network::Bitcoin) .map_err(|_| AddressError::InvalidInput)?; diff --git a/rust/tw_bitcoin/src/modules/legacy/build_and_sign.rs b/rust/tw_bitcoin/src/modules/legacy/build_and_sign.rs index 04cbe0d8d92..097f6b15772 100644 --- a/rust/tw_bitcoin/src/modules/legacy/build_and_sign.rs +++ b/rust/tw_bitcoin/src/modules/legacy/build_and_sign.rs @@ -5,7 +5,7 @@ use bitcoin::taproot::{LeafVersion, NodeInfo, TaprootSpendInfo}; use bitcoin::{Network, PrivateKey, PublicKey, ScriptBuf}; use secp256k1::XOnlyPublicKey; use tw_coin_entry::coin_entry::CoinEntry; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_encoding::hex; use tw_misc::traits::ToBytesVec; use tw_proto::Bitcoin::Proto as LegacyProto; @@ -19,6 +19,8 @@ use tw_proto::Utxo::Proto as UtxoProto; pub fn taproot_build_and_sign_transaction( legacy: LegacyProto::SigningInput, ) -> Result { + let coin = TestCoinContext::default(); + // Convert the appropriate lock time. let native_lock_time = LockTime::from_consensus(legacy.lock_time); let lock_time = match native_lock_time { @@ -91,7 +93,7 @@ pub fn taproot_build_and_sign_transaction( }; // Build and sign the Bitcoin transaction. - let signed = crate::entry::BitcoinEntry.sign(&EmptyCoinContext, signing_input); + let signed = crate::entry::BitcoinEntry.sign(&coin, signing_input); // Check for error. if signed.error != Proto::Error::OK { diff --git a/rust/tw_bitcoin/tests/brc20.rs b/rust/tw_bitcoin/tests/brc20.rs index 055452b5942..67c5a2678b8 100644 --- a/rust/tw_bitcoin/tests/brc20.rs +++ b/rust/tw_bitcoin/tests/brc20.rs @@ -4,13 +4,13 @@ use common::hex; use tw_bitcoin::aliases::*; use tw_bitcoin::BitcoinEntry; use tw_coin_entry::coin_entry::CoinEntry; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_proto::BitcoinV2::Proto; use tw_proto::Utxo::Proto as UtxoProto; #[test] fn coin_entry_sign_brc20_commit_reveal_transfer() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); let alice_pubkey = hex("030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb"); diff --git a/rust/tw_bitcoin/tests/free_estimate.rs b/rust/tw_bitcoin/tests/free_estimate.rs index 3e86653ce6d..bc64fbfa429 100644 --- a/rust/tw_bitcoin/tests/free_estimate.rs +++ b/rust/tw_bitcoin/tests/free_estimate.rs @@ -4,7 +4,7 @@ use common::{hex, ONE_BTC}; use tw_bitcoin::aliases::*; use tw_bitcoin::entry::BitcoinEntry; use tw_coin_entry::coin_entry::CoinEntry; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_proto::BitcoinV2::Proto; use tw_proto::Utxo::Proto as UtxoProto; @@ -12,7 +12,7 @@ const SAT_VB: u64 = 20; #[test] fn p2pkh_fee_estimate() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); @@ -66,7 +66,7 @@ fn p2pkh_fee_estimate() { #[test] fn p2wpkh_fee_estimate() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); @@ -124,7 +124,7 @@ fn p2wpkh_fee_estimate() { #[test] fn p2tr_key_path_fee_estimate() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); @@ -182,7 +182,7 @@ fn p2tr_key_path_fee_estimate() { #[test] fn brc20_inscribe_fee_estimate() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); diff --git a/rust/tw_bitcoin/tests/ordinal_nft.rs b/rust/tw_bitcoin/tests/ordinal_nft.rs index 23ff1694a04..d72c8450372 100644 --- a/rust/tw_bitcoin/tests/ordinal_nft.rs +++ b/rust/tw_bitcoin/tests/ordinal_nft.rs @@ -4,13 +4,13 @@ use common::hex; use tw_bitcoin::aliases::*; use tw_bitcoin::entry::BitcoinEntry; use tw_coin_entry::coin_entry::CoinEntry; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_proto::BitcoinV2::Proto; use tw_proto::Utxo::Proto as UtxoProto; #[test] fn coin_entry_sign_ordinal_nft_commit_reveal_transfer() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); let alice_pubkey = hex("030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb"); diff --git a/rust/tw_bitcoin/tests/p2pkh.rs b/rust/tw_bitcoin/tests/p2pkh.rs index 05f99c8d438..cb7a49829a4 100644 --- a/rust/tw_bitcoin/tests/p2pkh.rs +++ b/rust/tw_bitcoin/tests/p2pkh.rs @@ -4,13 +4,13 @@ use common::{hex, MINER_FEE, ONE_BTC}; use tw_bitcoin::aliases::*; use tw_bitcoin::entry::BitcoinEntry; use tw_coin_entry::coin_entry::CoinEntry; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_proto::BitcoinV2::Proto; use tw_proto::Utxo::Proto as UtxoProto; #[test] fn coin_entry_emtpy() { - let _coin = EmptyCoinContext; + let _coin = TestCoinContext::default(); let alice_private_key = hex("56429688a1a6b00b90ccd22a0de0a376b6569d8684022ae92229a28478bfb657"); let signing = Proto::SigningInput { @@ -25,7 +25,7 @@ fn coin_entry_emtpy() { #[test] fn coin_entry_sign_input_p2pkh_output_p2pkh() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("56429688a1a6b00b90ccd22a0de0a376b6569d8684022ae92229a28478bfb657"); let alice_pubkey = hex("036666dd712e05a487916384bfcd5973eb53e8038eccbbf97f7eed775b87389536"); diff --git a/rust/tw_bitcoin/tests/p2sh.rs b/rust/tw_bitcoin/tests/p2sh.rs index d09a15165ad..9d8323e4e5b 100644 --- a/rust/tw_bitcoin/tests/p2sh.rs +++ b/rust/tw_bitcoin/tests/p2sh.rs @@ -7,13 +7,13 @@ use tw_bitcoin::aliases::*; use tw_bitcoin::entry::BitcoinEntry; use tw_bitcoin::modules::signer::Signer; use tw_coin_entry::coin_entry::CoinEntry; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_proto::BitcoinV2::Proto; use tw_proto::Utxo::Proto as UtxoProto; #[test] fn coin_entry_sign_input_p2pkh_output_p2sh() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("56429688a1a6b00b90ccd22a0de0a376b6569d8684022ae92229a28478bfb657"); let alice_pubkey = hex("036666dd712e05a487916384bfcd5973eb53e8038eccbbf97f7eed775b87389536"); diff --git a/rust/tw_bitcoin/tests/p2tr_key_path.rs b/rust/tw_bitcoin/tests/p2tr_key_path.rs index c32c230c396..8a4c617f345 100644 --- a/rust/tw_bitcoin/tests/p2tr_key_path.rs +++ b/rust/tw_bitcoin/tests/p2tr_key_path.rs @@ -4,13 +4,13 @@ use common::{hex, MINER_FEE, ONE_BTC}; use tw_bitcoin::aliases::*; use tw_bitcoin::entry::BitcoinEntry; use tw_coin_entry::coin_entry::CoinEntry; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_proto::BitcoinV2::Proto; use tw_proto::Utxo::Proto as UtxoProto; #[test] fn coin_entry_sign_input_p2pkh_output_p2tr_key_path() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("12ce558df23528f1aa86f1f51ac7e13a197a06bda27610fa89e13b04c40ee999"); let alice_pubkey = hex("0351e003fdc48e7f31c9bc94996c91f6c3273b7ef4208a1686021bedf7673bb058"); diff --git a/rust/tw_bitcoin/tests/p2tr_script_path.rs b/rust/tw_bitcoin/tests/p2tr_script_path.rs index b2f01b8f606..7671b1dd739 100644 --- a/rust/tw_bitcoin/tests/p2tr_script_path.rs +++ b/rust/tw_bitcoin/tests/p2tr_script_path.rs @@ -7,7 +7,7 @@ use tw_bitcoin::aliases::*; use tw_bitcoin::entry::BitcoinEntry; use tw_bitcoin::modules::transactions::{BRC20TransferInscription, Brc20Ticker}; use tw_coin_entry::coin_entry::CoinEntry; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_misc::traits::ToBytesVec; use tw_proto::BitcoinV2::Proto; use tw_proto::Utxo::Proto as UtxoProto; @@ -17,7 +17,7 @@ use tw_proto::Utxo::Proto as UtxoProto; /// reconstruct the BRC20 transfer tests, but without using the convenience /// builders. fn coin_entry_custom_script_path() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); let alice_pubkey = hex("030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb"); diff --git a/rust/tw_bitcoin/tests/p2wpkh.rs b/rust/tw_bitcoin/tests/p2wpkh.rs index 5dfedc04959..ac97b68f2d9 100644 --- a/rust/tw_bitcoin/tests/p2wpkh.rs +++ b/rust/tw_bitcoin/tests/p2wpkh.rs @@ -4,13 +4,13 @@ use common::{hex, MINER_FEE, ONE_BTC}; use tw_bitcoin::aliases::*; use tw_bitcoin::entry::BitcoinEntry; use tw_coin_entry::coin_entry::CoinEntry; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_proto::BitcoinV2::Proto; use tw_proto::Utxo::Proto as UtxoProto; #[test] fn coin_entry_sign_input_p2pkh_output_p2wpkh() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); diff --git a/rust/tw_bitcoin/tests/p2wsh.rs b/rust/tw_bitcoin/tests/p2wsh.rs index fd50fd831d5..15d844e0c7d 100644 --- a/rust/tw_bitcoin/tests/p2wsh.rs +++ b/rust/tw_bitcoin/tests/p2wsh.rs @@ -7,13 +7,13 @@ use tw_bitcoin::aliases::*; use tw_bitcoin::entry::BitcoinEntry; use tw_bitcoin::modules::signer::Signer; use tw_coin_entry::coin_entry::CoinEntry; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_proto::BitcoinV2::Proto; use tw_proto::Utxo::Proto as UtxoProto; #[test] fn coin_entry_sign_input_p2pkh_output_p2wsh() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("56429688a1a6b00b90ccd22a0de0a376b6569d8684022ae92229a28478bfb657"); let alice_pubkey = hex("036666dd712e05a487916384bfcd5973eb53e8038eccbbf97f7eed775b87389536"); diff --git a/rust/tw_bitcoin/tests/plan_builder.rs b/rust/tw_bitcoin/tests/plan_builder.rs index 935c5ada511..3d1ea2025f1 100644 --- a/rust/tw_bitcoin/tests/plan_builder.rs +++ b/rust/tw_bitcoin/tests/plan_builder.rs @@ -5,13 +5,13 @@ use tw_bitcoin::aliases::*; use tw_bitcoin::BitcoinEntry; use tw_coin_entry::coin_entry::CoinEntry; use tw_coin_entry::modules::plan_builder::PlanBuilder; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_proto::BitcoinV2::Proto; use tw_proto::Utxo::Proto as UtxoProto; #[test] fn transaction_plan_compose_brc20() { - let _coin = EmptyCoinContext; + let _coin = TestCoinContext::default(); let alice_private_key = hex("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); let alice_pubkey = hex("030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb"); diff --git a/rust/tw_bitcoin/tests/send_to_address.rs b/rust/tw_bitcoin/tests/send_to_address.rs index 030148ea415..f8cddd60cb0 100644 --- a/rust/tw_bitcoin/tests/send_to_address.rs +++ b/rust/tw_bitcoin/tests/send_to_address.rs @@ -6,13 +6,13 @@ use secp256k1::XOnlyPublicKey; use tw_bitcoin::aliases::*; use tw_bitcoin::entry::BitcoinEntry; use tw_coin_entry::coin_entry::CoinEntry; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_proto::BitcoinV2::Proto; use tw_proto::Utxo::Proto as UtxoProto; #[test] fn send_to_p2sh_address() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); @@ -73,7 +73,7 @@ fn send_to_p2sh_address() { #[test] fn send_to_p2pkh_address() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); @@ -133,7 +133,7 @@ fn send_to_p2pkh_address() { #[test] fn send_to_p2wsh_address() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); @@ -194,7 +194,7 @@ fn send_to_p2wsh_address() { #[test] fn send_to_p2wpkh_address() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); @@ -254,7 +254,7 @@ fn send_to_p2wpkh_address() { #[test] fn send_to_p2tr_key_path_address() { - let coin = EmptyCoinContext; + let coin = TestCoinContext::default(); let alice_private_key = hex("57a64865bce5d4855e99b1cce13327c46171434f2d72eeaf9da53ee075e7f90a"); let alice_pubkey = hex("028d7dce6d72fb8f7af9566616c6436349c67ad379f2404dd66fe7085fe0fba28f"); diff --git a/rust/tw_coin_entry/Cargo.toml b/rust/tw_coin_entry/Cargo.toml index 41ab8b48576..d3df7dd56da 100644 --- a/rust/tw_coin_entry/Cargo.toml +++ b/rust/tw_coin_entry/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] serde_json = "1.0.95" tw_encoding = { path = "../tw_encoding" } +tw_hash = { path = "../tw_hash" } tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } tw_misc = { path = "../tw_misc" } diff --git a/rust/tw_coin_entry/src/coin_context.rs b/rust/tw_coin_entry/src/coin_context.rs index 8d6ccc92e35..f6f4a9dbb83 100644 --- a/rust/tw_coin_entry/src/coin_context.rs +++ b/rust/tw_coin_entry/src/coin_context.rs @@ -4,12 +4,17 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +use tw_hash::hasher::Hasher; use tw_keypair::tw::PublicKeyType; /// Extend the trait with methods required for blockchain additions. pub trait CoinContext { + /// Necessary chain property. fn public_key_type(&self) -> PublicKeyType; - // Example: - // fn ss58_prefix(&self) -> Option; + /// Optional chain property. + fn address_hasher(&self) -> Option; + + /// Optional chain property. + fn hrp(&self) -> Option; } diff --git a/rust/tw_coin_entry/src/coin_entry.rs b/rust/tw_coin_entry/src/coin_entry.rs index 195efe7263b..70c9d24077d 100644 --- a/rust/tw_coin_entry/src/coin_entry.rs +++ b/rust/tw_coin_entry/src/coin_entry.rs @@ -46,6 +46,14 @@ pub trait CoinEntry { prefix: Option, ) -> AddressResult; + /// Tries to parse `Self::Address` from the given `address` string by `coin` type. + /// Please note that this method does not check if the address belongs to the given chain. + fn parse_address_unchecked( + &self, + coin: &dyn CoinContext, + address: &str, + ) -> AddressResult; + /// Derives an address associated with the given `public_key` by `coin` context, `derivation` and address `prefix`. fn derive_address( &self, diff --git a/rust/tw_coin_entry/src/coin_entry_ext.rs b/rust/tw_coin_entry/src/coin_entry_ext.rs index 68caa9fe6e7..d3daf007c4a 100644 --- a/rust/tw_coin_entry/src/coin_entry_ext.rs +++ b/rust/tw_coin_entry/src/coin_entry_ext.rs @@ -29,12 +29,7 @@ pub trait CoinEntryExt { ) -> AddressResult<()>; /// Validates and normalizes the given `address`. - fn normalize_address( - &self, - coin: &dyn CoinContext, - address: &str, - prefix: Option, - ) -> AddressResult; + fn normalize_address(&self, coin: &dyn CoinContext, address: &str) -> AddressResult; /// Derives an address associated with the given `public_key` by `coin` context, `derivation` and address `prefix`. fn derive_address( @@ -46,12 +41,7 @@ pub trait CoinEntryExt { ) -> AddressResult; /// Returns underlying data (public key or key hash). - fn address_to_data( - &self, - coin: &dyn CoinContext, - address: &str, - prefix: Option, - ) -> AddressResult; + fn address_to_data(&self, coin: &dyn CoinContext, address: &str) -> AddressResult; /// Signs a transaction declared as the given `input`. fn sign(&self, coin: &dyn CoinContext, input: &[u8]) -> ProtoResult; @@ -110,16 +100,11 @@ where self.parse_address(coin, address, prefix).map(|_| ()) } - fn normalize_address( - &self, - coin: &dyn CoinContext, - address: &str, - prefix: Option, - ) -> AddressResult { - let prefix = prefix.map(T::AddressPrefix::try_from).transpose()?; + fn normalize_address(&self, coin: &dyn CoinContext, address: &str) -> AddressResult { // Parse the address and display it. // Please note that `Self::Address::to_string()` returns a normalize address. - ::parse_address(self, coin, address, prefix).map(|addr| addr.to_string()) + ::parse_address_unchecked(self, coin, address) + .map(|addr| addr.to_string()) } fn derive_address( @@ -136,15 +121,8 @@ where .map(|addr| addr.to_string()) } - fn address_to_data( - &self, - coin: &dyn CoinContext, - address: &str, - prefix: Option, - ) -> AddressResult { - let prefix = prefix.map(T::AddressPrefix::try_from).transpose()?; - - self.parse_address(coin, address, prefix) + fn address_to_data(&self, coin: &dyn CoinContext, address: &str) -> AddressResult { + self.parse_address_unchecked(coin, address) .map(|addr| addr.data()) } diff --git a/rust/tw_coin_entry/src/common/compile_input.rs b/rust/tw_coin_entry/src/common/compile_input.rs index 317c1f3b5f7..fc3dfa5f5fb 100644 --- a/rust/tw_coin_entry/src/common/compile_input.rs +++ b/rust/tw_coin_entry/src/common/compile_input.rs @@ -6,18 +6,13 @@ use crate::coin_entry::{PublicKeyBytes, SignatureBytes}; use crate::error::{SigningError, SigningErrorType, SigningResult}; -use tw_keypair::KeyPairError; -pub struct SingleSignaturePubkey { - pub signature: Signature, - pub public_key: PublicKey, +pub struct SingleSignaturePubkey { + pub signature: SignatureBytes, + pub public_key: PublicKeyBytes, } -impl SingleSignaturePubkey -where - Signature: for<'a> TryFrom<&'a [u8], Error = KeyPairError>, - PublicKey: for<'a> TryFrom<&'a [u8], Error = KeyPairError>, -{ +impl SingleSignaturePubkey { pub fn from_sign_pubkey_list( signatures: Vec, public_keys: Vec, @@ -26,18 +21,15 @@ where return Err(SigningError(SigningErrorType::Error_no_support_n2n)); } - let signature_data = signatures + let signature = signatures .into_iter() .next() .ok_or(SigningError(SigningErrorType::Error_signatures_count))?; - let public_key_data = public_keys + let public_key = public_keys .into_iter() .next() .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; - let signature = Signature::try_from(signature_data.as_slice())?; - let public_key = PublicKey::try_from(public_key_data.as_slice())?; - Ok(SingleSignaturePubkey { signature, public_key, diff --git a/rust/tw_coin_entry/src/derivation.rs b/rust/tw_coin_entry/src/derivation.rs index 3eb0f6c0b70..29378ffa176 100644 --- a/rust/tw_coin_entry/src/derivation.rs +++ b/rust/tw_coin_entry/src/derivation.rs @@ -5,9 +5,11 @@ // file LICENSE at the root of the source code distribution tree. /// Extend this enum. +#[derive(Default)] #[repr(u32)] pub enum Derivation { /// Default derivation. + #[default] Default = 0, } diff --git a/rust/tw_coin_entry/src/error.rs b/rust/tw_coin_entry/src/error.rs index 0e39825ce3b..179e8b73b3f 100644 --- a/rust/tw_coin_entry/src/error.rs +++ b/rust/tw_coin_entry/src/error.rs @@ -34,6 +34,8 @@ pub enum AddressError { FromHexError, PublicKeyTypeMismatch, UnexpectedAddressPrefix, + UnexpectedHasher, + InvalidHrp, InvalidInput, } diff --git a/rust/tw_coin_entry/src/test_utils/empty_context.rs b/rust/tw_coin_entry/src/test_utils/empty_context.rs deleted file mode 100644 index 423081ba7e1..00000000000 --- a/rust/tw_coin_entry/src/test_utils/empty_context.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -use crate::coin_context::CoinContext; -use tw_keypair::tw::PublicKeyType; - -/// Test coin context that panics on any `CoinContext` method call. -pub struct EmptyCoinContext; - -impl CoinContext for EmptyCoinContext { - fn public_key_type(&self) -> PublicKeyType { - panic!() - } -} diff --git a/rust/tw_coin_entry/src/test_utils/mod.rs b/rust/tw_coin_entry/src/test_utils/mod.rs index 805b39f4b3e..4cbcc23d463 100644 --- a/rust/tw_coin_entry/src/test_utils/mod.rs +++ b/rust/tw_coin_entry/src/test_utils/mod.rs @@ -4,4 +4,4 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -pub mod empty_context; +pub mod test_context; diff --git a/rust/tw_coin_entry/src/test_utils/test_context.rs b/rust/tw_coin_entry/src/test_utils/test_context.rs new file mode 100644 index 00000000000..50d31585a22 --- /dev/null +++ b/rust/tw_coin_entry/src/test_utils/test_context.rs @@ -0,0 +1,44 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::coin_context::CoinContext; +use tw_hash::hasher::Hasher; +use tw_keypair::tw::PublicKeyType; + +/// Test coin context that panics on any `CoinContext` method call. +#[derive(Default)] +pub struct TestCoinContext { + pub public_key_type: Option, + pub address_hasher: Option, + pub hrp: Option, +} + +impl TestCoinContext { + pub fn with_public_key_type(mut self, public_key_type: PublicKeyType) -> TestCoinContext { + self.public_key_type = Some(public_key_type); + self + } + + pub fn with_hrp(mut self, hrp: &str) -> TestCoinContext { + self.hrp = Some(hrp.to_string()); + self + } +} + +impl CoinContext for TestCoinContext { + fn public_key_type(&self) -> PublicKeyType { + self.public_key_type + .expect("EmptyCoinContext::public_key_type was not set") + } + + fn address_hasher(&self) -> Option { + self.address_hasher + } + + fn hrp(&self) -> Option { + self.hrp.clone() + } +} diff --git a/rust/tw_coin_registry/Cargo.toml b/rust/tw_coin_registry/Cargo.toml index b45a573c5a1..e8a4155f09b 100644 --- a/rust/tw_coin_registry/Cargo.toml +++ b/rust/tw_coin_registry/Cargo.toml @@ -10,10 +10,15 @@ serde_json = "1.0.96" tw_aptos = { path = "../tw_aptos" } tw_bitcoin = { path = "../tw_bitcoin" } tw_coin_entry = { path = "../tw_coin_entry" } +tw_cosmos = { path = "../chains/tw_cosmos" } tw_ethereum = { path = "../tw_ethereum" } tw_evm = { path = "../tw_evm" } +tw_hash = { path = "../tw_hash" } tw_internet_computer = { path = "../tw_internet_computer" } tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } tw_misc = { path = "../tw_misc" } +tw_native_evmos = { path = "../chains/tw_native_evmos" } +tw_native_injective = { path = "../chains/tw_native_injective" } tw_ronin = { path = "../tw_ronin" } +tw_thorchain = { path = "../chains/tw_thorchain" } diff --git a/rust/tw_coin_registry/src/blockchain_type.rs b/rust/tw_coin_registry/src/blockchain_type.rs index 2139a373eb3..c344d6b16b5 100644 --- a/rust/tw_coin_registry/src/blockchain_type.rs +++ b/rust/tw_coin_registry/src/blockchain_type.rs @@ -15,9 +15,13 @@ use std::str::FromStr; pub enum BlockchainType { Aptos, Bitcoin, + Cosmos, Ethereum, InternetComputer, + NativeEvmos, + NativeInjective, Ronin, + Thorchain, Unsupported, } @@ -38,9 +42,13 @@ impl FromStr for BlockchainType { match s { "Aptos" => Ok(BlockchainType::Aptos), "Bitcoin" => Ok(BlockchainType::Bitcoin), + "Cosmos" => Ok(BlockchainType::Cosmos), "Ethereum" => Ok(BlockchainType::Ethereum), "InternetComputer" => Ok(BlockchainType::InternetComputer), + "NativeEvmos" => Ok(BlockchainType::NativeEvmos), + "NativeInjective" => Ok(BlockchainType::NativeInjective), "Ronin" => Ok(BlockchainType::Ronin), + "Thorchain" => Ok(BlockchainType::Thorchain), _ => Ok(BlockchainType::Unsupported), } } diff --git a/rust/tw_coin_registry/src/coin_context.rs b/rust/tw_coin_registry/src/coin_context.rs index 41c090a7194..2185e7615e6 100644 --- a/rust/tw_coin_registry/src/coin_context.rs +++ b/rust/tw_coin_registry/src/coin_context.rs @@ -6,6 +6,7 @@ use crate::registry::CoinItem; use tw_coin_entry::coin_context::CoinContext; +use tw_hash::hasher::Hasher; use tw_keypair::tw::PublicKeyType; pub struct CoinRegistryContext { @@ -24,4 +25,14 @@ impl CoinContext for CoinRegistryContext { fn public_key_type(&self) -> PublicKeyType { self.item.public_key_type } + + #[inline] + fn address_hasher(&self) -> Option { + self.item.address_hasher + } + + #[inline] + fn hrp(&self) -> Option { + self.item.hrp.clone() + } } diff --git a/rust/tw_coin_registry/src/dispatcher.rs b/rust/tw_coin_registry/src/dispatcher.rs index 68970653371..0f3c36e2b70 100644 --- a/rust/tw_coin_registry/src/dispatcher.rs +++ b/rust/tw_coin_registry/src/dispatcher.rs @@ -12,27 +12,39 @@ use crate::registry::get_coin_item; use tw_aptos::entry::AptosEntry; use tw_bitcoin::entry::BitcoinEntry; use tw_coin_entry::coin_entry_ext::CoinEntryExt; +use tw_cosmos::entry::CosmosEntry; use tw_ethereum::entry::EthereumEntry; use tw_evm::evm_entry::EvmEntryExt; use tw_internet_computer::entry::InternetComputerEntry; +use tw_native_evmos::entry::NativeEvmosEntry; +use tw_native_injective::entry::NativeInjectiveEntry; use tw_ronin::entry::RoninEntry; +use tw_thorchain::entry::ThorchainEntry; pub type CoinEntryExtStaticRef = &'static dyn CoinEntryExt; pub type EvmEntryExtStaticRef = &'static dyn EvmEntryExt; const APTOS: AptosEntry = AptosEntry; const BITCOIN: BitcoinEntry = BitcoinEntry; +const COSMOS: CosmosEntry = CosmosEntry; const ETHEREUM: EthereumEntry = EthereumEntry; const INTERNET_COMPUTER: InternetComputerEntry = InternetComputerEntry; +const NATIVE_EVMOS: NativeEvmosEntry = NativeEvmosEntry; +const NATIVE_INJECTIVE: NativeInjectiveEntry = NativeInjectiveEntry; const RONIN: RoninEntry = RoninEntry; +const THORCHAIN: ThorchainEntry = ThorchainEntry; pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult { match blockchain { BlockchainType::Aptos => Ok(&APTOS), BlockchainType::Bitcoin => Ok(&BITCOIN), + BlockchainType::Cosmos => Ok(&COSMOS), BlockchainType::Ethereum => Ok(ÐEREUM), BlockchainType::InternetComputer => Ok(&INTERNET_COMPUTER), + BlockchainType::NativeEvmos => Ok(&NATIVE_EVMOS), + BlockchainType::NativeInjective => Ok(&NATIVE_INJECTIVE), BlockchainType::Ronin => Ok(&RONIN), + BlockchainType::Thorchain => Ok(&THORCHAIN), BlockchainType::Unsupported => Err(RegistryError::Unsupported), } } @@ -51,9 +63,13 @@ pub fn evm_dispatcher(coin: CoinType) -> RegistryResult { match item.blockchain { BlockchainType::Aptos => Err(RegistryError::Unsupported), BlockchainType::Bitcoin => Err(RegistryError::Unsupported), + BlockchainType::Cosmos => Err(RegistryError::Unsupported), BlockchainType::Ethereum => Ok(ÐEREUM), BlockchainType::InternetComputer => Err(RegistryError::Unsupported), + BlockchainType::NativeEvmos => Err(RegistryError::Unsupported), + BlockchainType::NativeInjective => Err(RegistryError::Unsupported), BlockchainType::Ronin => Ok(&RONIN), + BlockchainType::Thorchain => Err(RegistryError::Unsupported), BlockchainType::Unsupported => Err(RegistryError::Unsupported), } } diff --git a/rust/tw_coin_registry/src/registry.rs b/rust/tw_coin_registry/src/registry.rs index 422dd38ae6f..e438210c278 100644 --- a/rust/tw_coin_registry/src/registry.rs +++ b/rust/tw_coin_registry/src/registry.rs @@ -10,6 +10,7 @@ use crate::error::{RegistryError, RegistryResult}; use lazy_static::lazy_static; use serde::Deserialize; use std::collections::HashMap; +use tw_hash::hasher::Hasher; use tw_keypair::tw::PublicKeyType; type RegistryMap = HashMap; @@ -26,9 +27,13 @@ lazy_static! { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct CoinItem { + pub id: String, + pub name: String, pub coin_id: CoinType, pub blockchain: BlockchainType, pub public_key_type: PublicKeyType, + pub address_hasher: Option, + pub hrp: Option, } #[inline] diff --git a/rust/tw_cosmos_sdk/Cargo.toml b/rust/tw_cosmos_sdk/Cargo.toml new file mode 100644 index 00000000000..c2b1be4fd94 --- /dev/null +++ b/rust/tw_cosmos_sdk/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "tw_cosmos_sdk" +version = "0.1.0" +edition = "2021" + +[features] +test-utils = [] + +[dependencies] +quick-protobuf = "0.8.1" +serde = { version = "1.0.163", features = ["derive"] } +serde_json = "1.0.96" +tw_bech32_address = { path = "../tw_bech32_address" } +tw_coin_entry = { path = "../tw_coin_entry" } +tw_encoding = { path = "../tw_encoding" } +tw_hash = { path = "../tw_hash" } +tw_keypair = { path = "../tw_keypair" } +tw_memory = { path = "../tw_memory" } +tw_misc = { path = "../tw_misc" } +tw_number = { path = "../tw_number", features = ["serde"] } +tw_proto = { path = "../tw_proto" } + +[dev-dependencies] +tw_coin_entry = { path = "../tw_coin_entry", features = ["test-utils"] } +tw_cosmos_sdk = { path = "./", features = ["test-utils"] } +tw_misc = { path = "../tw_misc", features = ["test-utils"] } + +[build-dependencies] +pb-rs = "0.10.0" diff --git a/rust/tw_cosmos_sdk/build.rs b/rust/tw_cosmos_sdk/build.rs new file mode 100644 index 00000000000..a434de70fb2 --- /dev/null +++ b/rust/tw_cosmos_sdk/build.rs @@ -0,0 +1,71 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use pb_rs::types::FileDescriptor; +use pb_rs::ConfigBuilder; +use std::path::{Path, PathBuf}; +use std::{env, fs}; + +fn main() { + let cargo_manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let proto_ext = Some(Path::new("proto").as_os_str()); + + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()).join("proto"); + + let proto_dir = cargo_manifest_dir + .join("..") + .join("..") + .join("src") + .join("Cosmos") + .join("Protobuf"); + let proto_dir_str = proto_dir.to_str().expect("Invalid proto directory path"); + // Re-run this build.rs if the `proto` directory has been changed (i.e. a new file is added). + println!("cargo:rerun-if-changed={}", proto_dir_str); + + let protos: Vec<_> = fs::read_dir(&proto_dir) + .expect("Expected a valid directory with proto files") + .filter_map(|file| { + let file = file.ok()?; + if file.path().extension() != proto_ext { + return None; + } + + let path = file.path(); + let path_str = path.to_str().expect("Invalid Proto file name"); + println!("cargo:rerun-if-changed={}", path_str); + Some(path) + }) + .collect(); + + // Delete all old generated files before re-generating new ones + if out_dir.exists() { + fs::remove_dir_all(&out_dir).expect("Error removing out directory"); + } + fs::DirBuilder::new() + .create(&out_dir) + .expect("Error creating out directory"); + + // `tw_proto/common_proto` contains google.protobuf proto files that are used in Cosmos protocol. + let common_proto_dir = cargo_manifest_dir + .join("..") + .join("tw_proto") + .join("src") + .join("common") + .canonicalize() + .expect("Cannot find common proto directory"); + + let out_protos = ConfigBuilder::new( + &protos, + None, + Some(&out_dir), + &[common_proto_dir, proto_dir], + ) + .expect("Error configuring pb-rs builder") + .gen_info(true) + .dont_use_cow(true) + .build(); + FileDescriptor::run(&out_protos).expect("Error generating proto files"); +} diff --git a/rust/tw_cosmos_sdk/fuzz/.gitignore b/rust/tw_cosmos_sdk/fuzz/.gitignore new file mode 100644 index 00000000000..5c404b9583f --- /dev/null +++ b/rust/tw_cosmos_sdk/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +Cargo.lock diff --git a/rust/tw_cosmos_sdk/fuzz/Cargo.toml b/rust/tw_cosmos_sdk/fuzz/Cargo.toml new file mode 100644 index 00000000000..519510a0a49 --- /dev/null +++ b/rust/tw_cosmos_sdk/fuzz/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "tw_cosmos_sdk-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +tw_coin_entry = { path = "../../tw_coin_entry", features = ["test-utils"] } +tw_keypair = { path = "../../tw_keypair" } +tw_proto = { path = "../../tw_proto", features = ["fuzz"] } + +[dependencies.tw_cosmos_sdk] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "sign" +path = "fuzz_targets/sign.rs" +test = false +doc = false diff --git a/rust/tw_cosmos_sdk/fuzz/fuzz_targets/sign.rs b/rust/tw_cosmos_sdk/fuzz/fuzz_targets/sign.rs new file mode 100644 index 00000000000..af6f1bca403 --- /dev/null +++ b/rust/tw_cosmos_sdk/fuzz/fuzz_targets/sign.rs @@ -0,0 +1,16 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; +use tw_keypair::tw::PublicKeyType; +use tw_proto::Cosmos::Proto; + +fuzz_target!(|input: Proto::SigningInput<'_>| { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let _ = TWSigner::::sign(&coin, input); +}); diff --git a/rust/tw_cosmos_sdk/src/address.rs b/rust/tw_cosmos_sdk/src/address.rs new file mode 100644 index 00000000000..4f141b2a1dd --- /dev/null +++ b/rust/tw_cosmos_sdk/src/address.rs @@ -0,0 +1,29 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde::Serialize; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::{AddressError, AddressResult}; + +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; +} + +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) + } +} diff --git a/rust/tw_cosmos_sdk/src/context.rs b/rust/tw_cosmos_sdk/src/context.rs new file mode 100644 index 00000000000..f7da7218f25 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/context.rs @@ -0,0 +1,34 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::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; + +pub trait CosmosContext { + type Address: CosmosAddress; + type TxHasher: CosmosHasher; + type PrivateKey: CosmosPrivateKey; + type PublicKey: CosmosPublicKey; + type Signature: CosmosSignature; +} + +#[derive(Default)] +pub struct StandardCosmosContext; + +impl CosmosContext for StandardCosmosContext { + type Address = Address; + type TxHasher = Sha256Hasher; + type PrivateKey = Secp256PrivateKey; + type PublicKey = Secp256PublicKey; + type Signature = Secp256k1Signature; +} diff --git a/rust/tw_cosmos_sdk/src/hasher/keccak256_hasher.rs b/rust/tw_cosmos_sdk/src/hasher/keccak256_hasher.rs new file mode 100644 index 00000000000..21ca81fb087 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/hasher/keccak256_hasher.rs @@ -0,0 +1,21 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::hasher::CosmosHasher; +use tw_hash::sha3::keccak256; +use tw_memory::Data; + +pub struct Keccak256Hasher; + +impl CosmosHasher for Keccak256Hasher { + fn hash_sign_doc(sign_doc: &[u8]) -> Data { + keccak256(sign_doc) + } + + fn hash_json_tx(json: &str) -> Data { + keccak256(json.as_bytes()) + } +} diff --git a/rust/tw_cosmos_sdk/src/hasher/mod.rs b/rust/tw_cosmos_sdk/src/hasher/mod.rs new file mode 100644 index 00000000000..7422d204961 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/hasher/mod.rs @@ -0,0 +1,16 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use 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 new file mode 100644 index 00000000000..3b2310cdbec --- /dev/null +++ b/rust/tw_cosmos_sdk/src/hasher/sha256_hasher.rs @@ -0,0 +1,21 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::hasher::CosmosHasher; +use tw_hash::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 new file mode 100644 index 00000000000..52cba46ef65 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/lib.rs @@ -0,0 +1,25 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod address; +pub mod context; +pub mod hasher; +pub mod modules; +pub mod private_key; +pub mod public_key; +pub mod signature; +pub mod transaction; + +#[cfg(feature = "test-utils")] +pub mod test_utils; + +#[allow(non_snake_case)] +#[rustfmt::skip] +pub mod proto { + use tw_proto::google; + + include!(concat!(env!("OUT_DIR"), "/proto/mod.rs")); +} diff --git a/rust/tw_cosmos_sdk/src/modules/broadcast_msg.rs b/rust/tw_cosmos_sdk/src/modules/broadcast_msg.rs new file mode 100644 index 00000000000..3c2b636ea0a --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/broadcast_msg.rs @@ -0,0 +1,60 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use quick_protobuf::MessageWrite; +use serde::Serialize; +use serde_json::Value as Json; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_encoding::base64::Base64Encoded; +use tw_proto::serialize; + +pub enum BroadcastMode { + Block, + Async, + Sync, +} + +#[derive(Serialize)] +#[serde(untagged)] +pub enum BroadcastMsg { + /// Binary representation of the transaction. + Raw { + mode: String, + tx_bytes: Base64Encoded, + }, + /// JSON encoded transaction. + Json { mode: String, tx: Json }, +} + +impl BroadcastMsg { + pub fn raw(mode: BroadcastMode, tx: &Tx) -> BroadcastMsg { + let mode = match mode { + BroadcastMode::Block => "BROADCAST_MODE_BLOCK", + BroadcastMode::Async => "BROADCAST_MODE_ASYNC", + BroadcastMode::Sync => "BROADCAST_MODE_SYNC", + } + .to_string(); + let tx_bytes = Base64Encoded(serialize(tx).expect("Error on serializing transaction")); + BroadcastMsg::Raw { mode, tx_bytes } + } + + pub fn json(mode: BroadcastMode, tx: Tx) -> SigningResult { + let mode = match mode { + BroadcastMode::Block => "block", + BroadcastMode::Async => "async", + BroadcastMode::Sync => "sync", + } + .to_string(); + let tx = + serde_json::to_value(tx).map_err(|_| SigningError(SigningErrorType::Error_internal))?; + Ok(BroadcastMsg::Json { mode, tx }) + } + + pub fn to_json_string(&self) -> String { + // It's safe to unwrap here because `BroadcastMsg` consists of checked fields only. + serde_json::to_string(self).expect("Unexpected error on serializing a BroadcastMsg") + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/compiler/json_preimager.rs b/rust/tw_cosmos_sdk/src/modules/compiler/json_preimager.rs new file mode 100644 index 00000000000..188e4c3d377 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/compiler/json_preimager.rs @@ -0,0 +1,40 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::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_memory::Data; + +pub struct JsonTxPreimage { + pub encoded_tx: String, + pub tx_hash: Data, +} + +pub struct JsonPreimager { + _phantom: PhantomData, +} + +impl JsonPreimager +where + Context::PublicKey: JsonPublicKey, +{ + pub fn preimage_hash(unsigned: &UnsignedTransaction) -> 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); + + Ok(JsonTxPreimage { + encoded_tx, + tx_hash, + }) + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/compiler/mod.rs b/rust/tw_cosmos_sdk/src/modules/compiler/mod.rs new file mode 100644 index 00000000000..a5d5ef61389 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/compiler/mod.rs @@ -0,0 +1,9 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod json_preimager; +pub mod protobuf_preimager; +pub mod tw_compiler; diff --git a/rust/tw_cosmos_sdk/src/modules/compiler/protobuf_preimager.rs b/rust/tw_cosmos_sdk/src/modules/compiler/protobuf_preimager.rs new file mode 100644 index 00000000000..591d56a6aba --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/compiler/protobuf_preimager.rs @@ -0,0 +1,49 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::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_memory::Data; +use tw_proto::serialize; + +pub struct ProtobufTxPreimage { + pub encoded_tx: Data, + pub tx_hash: Data, +} + +pub struct ProtobufPreimager { + _phantom: PhantomData, +} + +impl ProtobufPreimager { + pub fn preimage_hash( + unsigned: &UnsignedTransaction, + ) -> 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); + + Ok(ProtobufTxPreimage { + encoded_tx, + tx_hash, + }) + } + + pub fn preimage_hash_direct(args: &SignDirectArgs) -> 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); + + Ok(ProtobufTxPreimage { + encoded_tx, + tx_hash, + }) + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs b/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs new file mode 100644 index 00000000000..897276b8c90 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs @@ -0,0 +1,185 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::CosmosContext; +use crate::modules::broadcast_msg::{BroadcastMode, BroadcastMsg}; +use crate::modules::compiler::json_preimager::JsonPreimager; +use crate::modules::compiler::protobuf_preimager::ProtobufPreimager; +use crate::modules::serializer::json_serializer::JsonSerializer; +use crate::modules::serializer::protobuf_serializer::ProtobufSerializer; +use crate::modules::tx_builder::TxBuilder; +use crate::public_key::CosmosPublicKey; +use crate::signature::CosmosSignature; +use std::borrow::Cow; +use std::marker::PhantomData; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::common::compile_input::SingleSignaturePubkey; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_coin_entry::signing_output_error; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct TWTransactionCompiler { + _phantom: PhantomData, +} + +impl TWTransactionCompiler { + #[inline] + pub fn preimage_hashes( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + match input.signing_mode { + Proto::SigningMode::JSON => Self::preimage_hashes_as_json(coin, input), + Proto::SigningMode::Protobuf => Self::preimage_hashes_as_protobuf(coin, input), + } + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + #[inline] + pub fn compile( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + match input.signing_mode { + Proto::SigningMode::JSON => Self::compile_as_json(coin, input, signatures, public_keys), + Proto::SigningMode::Protobuf => { + Self::compile_as_protobuf(coin, input, signatures, public_keys) + }, + } + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + pub fn preimage_hashes_as_protobuf( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + 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)? + }, + // 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)? + }, + }; + + Ok(CompilerProto::PreSigningOutput { + data: Cow::from(preimage.encoded_tx), + data_hash: Cow::from(preimage.tx_hash), + ..CompilerProto::PreSigningOutput::default() + }) + } + + pub fn preimage_hashes_as_json( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> 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)?; + + Ok(CompilerProto::PreSigningOutput { + data: Cow::from(preimage.encoded_tx.as_bytes().to_vec()), + data_hash: Cow::from(preimage.tx_hash), + ..CompilerProto::PreSigningOutput::default() + }) + } + + pub fn compile_as_protobuf( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> SigningResult> { + let SingleSignaturePubkey { + signature, + public_key, + } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; + let signature = Context::Signature::from_bytes(&signature)?; + let public_key = Context::PublicKey::from_bytes(coin, &public_key)?; + + 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. + Ok(Some(sign_direct_args)) => ProtobufSerializer::::build_direct_signed_tx( + &sign_direct_args, + signature.to_bytes(), + ), + // Otherwise, generate the `TxRaw` by using `TxBuilder`. + _ => { + // Set the public key. It will be used to construct a signer info. + input.public_key = Cow::from(public_key.to_bytes()); + let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; + let signed_tx = unsigned_tx.into_signed(signature.to_bytes()); + + ProtobufSerializer::build_signed_tx(&signed_tx)? + }, + }; + + let broadcast_mode = Self::broadcast_mode(input.mode); + let broadcast_tx = BroadcastMsg::raw(broadcast_mode, &signed_tx_raw).to_json_string(); + + let signature_json = + JsonSerializer::::serialize_signature(&public_key, signature.to_bytes()); + let signature_json = serde_json::to_string(&[signature_json]) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?; + + Ok(Proto::SigningOutput { + signature: Cow::from(signature.to_bytes()), + signature_json: Cow::from(signature_json), + serialized: Cow::from(broadcast_tx), + ..Proto::SigningOutput::default() + }) + } + + pub fn compile_as_json( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> SigningResult> { + let SingleSignaturePubkey { + signature, + public_key, + } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; + let signature = Context::Signature::from_bytes(&signature)?; + let public_key = Context::PublicKey::from_bytes(coin, &public_key)?; + + // Set the public key. It will be used to construct a signer info. + input.public_key = Cow::from(public_key.to_bytes()); + let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; + let signed_tx = unsigned_tx.into_signed(signature.to_bytes()); + + let signed_tx_json = JsonSerializer::build_signed_tx(&signed_tx)?; + + let broadcast_mode = Self::broadcast_mode(input.mode); + let broadcast_tx = BroadcastMsg::json(broadcast_mode, &signed_tx_json)?.to_json_string(); + + let signature_json = serde_json::to_string(&signed_tx_json.signatures) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?; + + Ok(Proto::SigningOutput { + signature: Cow::from(signature.to_bytes()), + signature_json: Cow::from(signature_json), + json: Cow::from(broadcast_tx), + ..Proto::SigningOutput::default() + }) + } + + fn broadcast_mode(input: Proto::BroadcastMode) -> BroadcastMode { + match input { + Proto::BroadcastMode::BLOCK => BroadcastMode::Block, + Proto::BroadcastMode::SYNC => BroadcastMode::Sync, + Proto::BroadcastMode::ASYNC => BroadcastMode::Async, + } + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/mod.rs b/rust/tw_cosmos_sdk/src/modules/mod.rs new file mode 100644 index 00000000000..2a667107a85 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/mod.rs @@ -0,0 +1,11 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod broadcast_msg; +pub mod compiler; +pub mod serializer; +pub mod signer; +pub mod tx_builder; diff --git a/rust/tw_cosmos_sdk/src/modules/serializer/json_serializer.rs b/rust/tw_cosmos_sdk/src/modules/serializer/json_serializer.rs new file mode 100644 index 00000000000..073f7de68cb --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/serializer/json_serializer.rs @@ -0,0 +1,125 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::CosmosContext; +use crate::private_key::SignatureData; +use crate::public_key::{CosmosPublicKey, JsonPublicKey}; +use crate::transaction::{Coin, Fee, SignedTransaction, UnsignedTransaction}; +use serde::Serialize; +use serde_json::Value as Json; +use std::marker::PhantomData; +use tw_coin_entry::error::SigningResult; +use tw_encoding::base64::Base64Encoded; + +#[derive(Serialize)] +pub struct SignedTxJson { + pub fee: FeeJson, + pub memo: String, + pub msg: Vec>, + pub signatures: Vec, +} + +#[derive(Serialize)] +pub struct UnsignedTxJson { + pub account_number: String, + pub chain_id: String, + pub fee: FeeJson, + pub memo: String, + pub msgs: Vec>, + pub sequence: String, +} + +#[derive(Serialize)] +pub struct FeeJson { + pub amount: Vec, + pub gas: String, +} + +#[derive(Clone, Serialize)] +pub struct AnyMsg { + #[serde(rename = "type")] + pub msg_type: String, + pub value: Value, +} + +#[derive(Clone, Serialize)] +pub struct SignatureJson { + pub pub_key: AnyMsg, + pub signature: Base64Encoded, +} + +/// `JsonSerializer` serializes transaction to JSON in Cosmos specific way. +pub struct JsonSerializer { + _phantom: PhantomData, +} + +impl JsonSerializer +where + Context: CosmosContext, + Context::PublicKey: JsonPublicKey, +{ + pub fn build_signed_tx(signed: &SignedTransaction) -> SigningResult { + let msg = signed + .tx_body + .messages + .iter() + .map(|msg| msg.to_json()) + .collect::>()?; + let signature = + Self::serialize_signature(&signed.signer.public_key, signed.signature.clone()); + + Ok(SignedTxJson { + fee: Self::build_fee(&signed.fee), + memo: signed.tx_body.memo.clone(), + msg, + signatures: vec![signature], + }) + } + + pub fn build_unsigned_tx( + unsigned: &UnsignedTransaction, + ) -> SigningResult { + let msgs = unsigned + .tx_body + .messages + .iter() + .map(|msg| msg.to_json()) + .collect::>()?; + + Ok(UnsignedTxJson { + account_number: unsigned.account_number.to_string(), + chain_id: unsigned.chain_id.clone(), + fee: Self::build_fee(&unsigned.fee), + memo: unsigned.tx_body.memo.clone(), + msgs, + sequence: unsigned.signer.sequence.to_string(), + }) + } + + pub fn serialize_signature( + public_key: &Context::PublicKey, + signature: SignatureData, + ) -> SignatureJson { + SignatureJson { + pub_key: Self::serialize_public_key(public_key), + signature: Base64Encoded(signature), + } + } + + pub fn serialize_public_key(public_key: &Context::PublicKey) -> AnyMsg { + AnyMsg { + msg_type: public_key.public_key_type(), + value: Base64Encoded(public_key.to_bytes()), + } + } + + pub fn build_fee(fee: &Fee) -> FeeJson { + FeeJson { + gas: fee.gas_limit.to_string(), + amount: fee.amounts.clone(), + } + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/serializer/mod.rs b/rust/tw_cosmos_sdk/src/modules/serializer/mod.rs new file mode 100644 index 00000000000..7361a31450a --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/serializer/mod.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod json_serializer; +pub mod protobuf_serializer; diff --git a/rust/tw_cosmos_sdk/src/modules/serializer/protobuf_serializer.rs b/rust/tw_cosmos_sdk/src/modules/serializer/protobuf_serializer.rs new file mode 100644 index 00000000000..ee67300b747 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/serializer/protobuf_serializer.rs @@ -0,0 +1,165 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::CosmosContext; +use crate::proto::cosmos::base::v1beta1 as base_proto; +use crate::proto::cosmos::signing::v1beta1 as signing_proto; +use crate::proto::cosmos::tx::v1beta1 as tx_proto; +use crate::public_key::ProtobufPublicKey; +use crate::transaction::{ + Coin, Fee, SignMode, SignedTransaction, SignerInfo, TxBody, UnsignedTransaction, +}; +use std::marker::PhantomData; +use tw_coin_entry::error::SigningResult; +use tw_memory::Data; +use tw_proto::serialize; + +pub fn build_coin(coin: &Coin) -> base_proto::Coin { + base_proto::Coin { + amount: coin.amount.to_string(), + denom: coin.denom.clone(), + } +} + +/// `ProtobufSerializer` serializes Cosmos specific Protobuf messages. +pub struct ProtobufSerializer { + _phantom: PhantomData, +} + +pub struct SignDirectArgs { + pub tx_body: Data, + pub auth_info: Data, + pub chain_id: String, + pub account_number: u64, +} + +impl ProtobufSerializer { + /// Serializes a signed transaction into the Cosmos [`tx_proto::TxRaw`] message. + /// [`tx_proto::TxRaw`] can be broadcasted to the network. + pub fn build_signed_tx(signed: &SignedTransaction) -> SigningResult { + let tx_body = Self::build_tx_body(&signed.tx_body)?; + let body_bytes = serialize(&tx_body).expect("Unexpected error on tx_body serialization"); + + let auth_info = Self::build_auth_info(&signed.signer, &signed.fee); + let auth_info_bytes = + serialize(&auth_info).expect("Unexpected error on auth_info serialization"); + + Ok(tx_proto::TxRaw { + body_bytes, + auth_info_bytes, + signatures: vec![signed.signature.clone()], + }) + } + + pub fn build_direct_signed_tx(args: &SignDirectArgs, signature: Data) -> tx_proto::TxRaw { + tx_proto::TxRaw { + body_bytes: args.tx_body.clone(), + auth_info_bytes: args.auth_info.clone(), + signatures: vec![signature], + } + } + + /// Serializes an unsigned transaction into the Cosmos [`tx_proto::SignDoc`] message. + /// [`tx_proto::SignDoc`] is used to generate a transaction prehash and sign it. + pub fn build_sign_doc( + unsigned: &UnsignedTransaction, + ) -> SigningResult { + let tx_body = Self::build_tx_body(&unsigned.tx_body)?; + let body_bytes = serialize(&tx_body).expect("Unexpected error on tx_body serialization"); + + let auth_info = Self::build_auth_info(&unsigned.signer, &unsigned.fee); + let auth_info_bytes = + serialize(&auth_info).expect("Unexpected error on auth_info serialization"); + + Ok(tx_proto::SignDoc { + body_bytes, + auth_info_bytes, + chain_id: unsigned.chain_id.clone(), + account_number: unsigned.account_number, + }) + } + + pub fn build_direct_sign_doc(args: &SignDirectArgs) -> tx_proto::SignDoc { + tx_proto::SignDoc { + body_bytes: args.tx_body.clone(), + auth_info_bytes: args.auth_info.clone(), + chain_id: args.chain_id.clone(), + account_number: args.account_number, + } + } + + pub fn build_auth_info( + signer: &SignerInfo, + fee: &Fee, + ) -> tx_proto::AuthInfo { + tx_proto::AuthInfo { + signer_infos: vec![Self::build_signer_info(signer)], + fee: Some(Self::build_fee(fee)), + // At this moment, we do not support transaction tip. + tip: None, + } + } + + pub fn build_tx_body(tx_body: &TxBody) -> SigningResult { + let messages: Vec<_> = tx_body + .messages + .iter() + .map(|msg| msg.to_proto()) + .collect::>()?; + + Ok(tx_proto::TxBody { + messages, + memo: tx_body.memo.clone(), + timeout_height: tx_body.timeout_height, + extension_options: Vec::default(), + non_critical_extension_options: Vec::default(), + }) + } + + pub fn build_signer_info(signer: &SignerInfo) -> tx_proto::SignerInfo { + use tx_proto::mod_ModeInfo::{self as mode_info, OneOfsum as SumEnum}; + + // Single is the mode info for a single signer. It is structured as a message + // to allow for additional fields such as locale for SIGN_MODE_TEXTUAL in the future. + let mode_info = tx_proto::ModeInfo { + sum: SumEnum::single(mode_info::Single { + mode: Self::build_sign_mode(signer.sign_mode), + }), + }; + + tx_proto::SignerInfo { + public_key: Some(signer.public_key.to_proto()), + mode_info: Some(mode_info), + sequence: signer.sequence, + } + } + + fn build_fee(fee: &Fee) -> tx_proto::Fee { + let payer = fee + .payer + .as_ref() + .map(Context::Address::to_string) + .unwrap_or_default(); + let granter = fee + .granter + .as_ref() + .map(Context::Address::to_string) + .unwrap_or_default(); + + tx_proto::Fee { + amount: fee.amounts.iter().map(build_coin).collect(), + gas_limit: fee.gas_limit, + payer, + granter, + } + } + + fn build_sign_mode(sign_mode: SignMode) -> signing_proto::SignMode { + match sign_mode { + SignMode::Direct => signing_proto::SignMode::SIGN_MODE_DIRECT, + } + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/signer/mod.rs b/rust/tw_cosmos_sdk/src/modules/signer/mod.rs new file mode 100644 index 00000000000..c4071fb11ff --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/signer/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod tw_signer; diff --git a/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs b/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs new file mode 100644 index 00000000000..8f63310be5a --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/signer/tw_signer.rs @@ -0,0 +1,61 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::CosmosContext; +use crate::modules::compiler::tw_compiler::TWTransactionCompiler; +use crate::private_key::CosmosPrivateKey; +use crate::public_key::CosmosPublicKey; +use std::borrow::Cow; +use std::marker::PhantomData; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_coin_entry::signing_output_error; +use tw_proto::Cosmos::Proto; + +pub struct TWSigner { + _phantom: PhantomData, +} + +impl TWSigner { + #[inline] + pub fn sign( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> Proto::SigningOutput<'static> { + Self::sign_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_impl( + coin: &dyn CoinContext, + 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())?; + // Set the public key. It will be used to construct a signer info. + input.public_key = Cow::from(public_key.to_bytes()); + + let preimage_output = + TWTransactionCompiler::::preimage_hashes(coin, input.clone()); + if preimage_output.error != SigningErrorType::OK { + return Err(SigningError(preimage_output.error)); + } + + let signature_data = private_key.sign_tx_hash(&preimage_output.data_hash)?; + let compile_output = TWTransactionCompiler::::compile( + coin, + input, + vec![signature_data], + vec![public_key.to_bytes()], + ); + + if compile_output.error != SigningErrorType::OK { + return Err(SigningError(preimage_output.error)); + } + + Ok(compile_output) + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/tx_builder.rs b/rust/tw_cosmos_sdk/src/modules/tx_builder.rs new file mode 100644 index 00000000000..da5e87a94d1 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/tx_builder.rs @@ -0,0 +1,655 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::{Address, CosmosAddress}; +use crate::context::CosmosContext; +use crate::modules::serializer::protobuf_serializer::SignDirectArgs; +use crate::public_key::CosmosPublicKey; +use crate::transaction::message::cosmos_generic_message::JsonRawMessage; +use crate::transaction::message::{CosmosMessage, CosmosMessageBox}; +use crate::transaction::{Coin, Fee, SignMode, SignerInfo, TxBody, UnsignedTransaction}; +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_misc::traits::ToBytesVec; +use tw_number::U256; +use tw_proto::Cosmos::Proto; +use tw_proto::{google, serialize}; + +const DEFAULT_TIMEOUT_HEIGHT: u64 = 0; + +pub struct TxBuilder { + _phantom: PhantomData, +} + +impl TxBuilder +where + Context: CosmosContext, +{ + /// Please note that [`Proto::SigningInput::public_key`] must be set. + /// If the public key should be derived from a private key, please do it before this method is called. + pub fn unsigned_tx_from_proto( + coin: &dyn CoinContext, + input: &Proto::SigningInput<'_>, + ) -> SigningResult> { + let fee = input + .fee + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_wrong_fee))?; + let signer = Self::signer_info_from_proto(coin, input)?; + + Ok(UnsignedTransaction { + signer, + fee: Self::fee_from_proto(fee)?, + chain_id: input.chain_id.to_string(), + account_number: input.account_number, + tx_body: Self::tx_body_from_proto(coin, input)?, + }) + } + + pub fn signer_info_from_proto( + coin: &dyn CoinContext, + input: &Proto::SigningInput, + ) -> SigningResult> { + let public_key = Context::PublicKey::from_bytes(coin, &input.public_key)?; + Ok(SignerInfo { + public_key, + sequence: input.sequence, + // At this moment, we support the Direct signing mode only. + sign_mode: SignMode::Direct, + }) + } + + fn fee_from_proto(input: &Proto::Fee) -> SigningResult> { + let amounts = input + .amounts + .iter() + .map(Self::coin_from_proto) + .collect::>()?; + Ok(Fee { + amounts, + gas_limit: input.gas, + payer: None, + granter: None, + }) + } + + fn coin_from_proto(input: &Proto::Amount<'_>) -> SigningResult { + let amount = U256::from_str(&input.amount)?; + Ok(Coin { + amount, + denom: input.denom.to_string(), + }) + } + + fn tx_body_from_proto( + coin: &dyn CoinContext, + input: &Proto::SigningInput<'_>, + ) -> SigningResult { + if input.messages.is_empty() { + return Err(SigningError(SigningErrorType::Error_invalid_params)); + } + + let messages = input + .messages + .iter() + .map(|msg| Self::tx_message(coin, msg)) + .collect::>()?; + + Ok(TxBody { + messages, + memo: input.memo.to_string(), + timeout_height: DEFAULT_TIMEOUT_HEIGHT, + }) + } + + pub fn try_sign_direct_args( + input: &Proto::SigningInput<'_>, + ) -> SigningResult> { + use Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + + let Some(msg) = input.messages.first() else { + return Ok(None); + }; + + match msg.message_oneof { + MessageEnum::sign_direct_message(ref direct) => Ok(Some(SignDirectArgs { + tx_body: direct.body_bytes.to_vec(), + auth_info: direct.auth_info_bytes.to_vec(), + chain_id: input.chain_id.to_string(), + account_number: input.account_number, + })), + _ => Ok(None), + } + } + + pub fn tx_message( + coin: &dyn CoinContext, + input: &Proto::Message, + ) -> SigningResult { + use Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + + match input.message_oneof { + MessageEnum::send_coins_message(ref send) => Self::send_msg_from_proto(coin, send), + MessageEnum::transfer_tokens_message(ref transfer) => { + Self::transfer_tokens_msg_from_proto(coin, transfer) + }, + MessageEnum::stake_message(ref delegate) => { + Self::delegate_msg_from_proto(coin, delegate) + }, + MessageEnum::unstake_message(ref undelegate) => { + Self::undelegate_msg_from_proto(coin, undelegate) + }, + MessageEnum::withdraw_stake_reward_message(ref withdraw) => { + Self::withdraw_reward_msg_from_proto(coin, withdraw) + }, + MessageEnum::set_withdraw_address_message(ref set) => { + Self::set_withdraw_address_msg_from_proto(coin, set) + }, + MessageEnum::restake_message(ref redelegate) => { + Self::redelegate_msg_from_proto(coin, redelegate) + }, + MessageEnum::raw_json_message(ref raw_json) => { + Self::wasm_raw_msg_from_proto(coin, raw_json) + }, + MessageEnum::wasm_terra_execute_contract_transfer_message(ref transfer) => { + Self::wasm_terra_execute_contract_transfer_msg_from_proto(coin, transfer) + }, + MessageEnum::wasm_terra_execute_contract_send_message(ref send) => { + Self::wasm_terra_execute_contract_send_msg_from_proto(coin, send) + }, + MessageEnum::thorchain_send_message(ref send) => { + Self::thorchain_send_msg_from_proto(coin, send) + }, + MessageEnum::wasm_terra_execute_contract_generic(ref generic) => { + Self::wasm_terra_execute_contract_generic_msg_from_proto(coin, generic) + }, + MessageEnum::wasm_execute_contract_transfer_message(ref transfer) => { + Self::wasm_execute_contract_transfer_msg_from_proto(coin, transfer) + }, + MessageEnum::wasm_execute_contract_send_message(ref send) => { + Self::wasm_execute_contract_send_msg_from_proto(coin, send) + }, + MessageEnum::wasm_execute_contract_generic(ref generic) => { + Self::wasm_execute_contract_generic_msg_from_proto(coin, generic) + }, + MessageEnum::sign_direct_message(ref _sign) => { + // `SignDirect` message must be handled before this function is called. + // Consider using `Self::try_sign_direct_args` instead. + Err(SigningError(SigningErrorType::Error_not_supported)) + }, + MessageEnum::auth_grant(ref grant) => Self::auth_grant_msg_from_proto(coin, grant), + MessageEnum::auth_revoke(ref revoke) => Self::auth_revoke_msg_from_proto(coin, revoke), + MessageEnum::msg_vote(ref vote) => Self::vote_msg_from_proto(coin, vote), + MessageEnum::msg_stride_liquid_staking_stake(ref stake) => { + Self::stride_stake_msg_from_proto(coin, stake) + }, + MessageEnum::msg_stride_liquid_staking_redeem(ref redeem) => { + Self::stride_redeem_msg_from_proto(coin, redeem) + }, + MessageEnum::thorchain_deposit_message(ref deposit) => { + Self::thorchain_deposit_msg_from_proto(coin, deposit) + }, + MessageEnum::None => Err(SigningError(SigningErrorType::Error_invalid_params)), + } + } + + pub fn send_msg_from_proto( + coin: &dyn CoinContext, + send: &Proto::mod_Message::Send<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_bank_message::SendMessage; + + let amounts = send + .amounts + .iter() + .map(Self::coin_from_proto) + .collect::>()?; + let msg = SendMessage { + custom_type_prefix: Self::custom_msg_type(&send.type_prefix), + from_address: Address::from_str_with_coin(coin, &send.from_address)?, + to_address: Address::from_str_with_coin(coin, &send.to_address)?, + amount: amounts, + }; + Ok(msg.into_boxed()) + } + + pub fn transfer_tokens_msg_from_proto( + coin: &dyn CoinContext, + transfer: &Proto::mod_Message::Transfer<'_>, + ) -> SigningResult { + use crate::transaction::message::ibc_message::{Height, TransferTokensMessage}; + + let token = transfer + .token + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + let token = Self::coin_from_proto(token)?; + let height = transfer + .timeout_height + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + + let msg = TransferTokensMessage { + 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. + receiver: Address::from_str(&transfer.receiver)?, + timeout_height: Height { + revision_number: height.revision_number, + revision_height: height.revision_height, + }, + timeout_timestamp: transfer.timeout_timestamp, + }; + Ok(msg.into_boxed()) + } + + pub fn delegate_msg_from_proto( + coin: &dyn CoinContext, + delegate: &Proto::mod_Message::Delegate<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_staking_message::DelegateMessage; + + let amount = delegate + .amount + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + let amount = Self::coin_from_proto(amount)?; + let msg = DelegateMessage { + custom_type_prefix: Self::custom_msg_type(&delegate.type_prefix), + amount, + delegator_address: Address::from_str_with_coin(coin, &delegate.delegator_address)?, + validator_address: Address::from_str_with_coin(coin, &delegate.validator_address)?, + }; + Ok(msg.into_boxed()) + } + + pub fn undelegate_msg_from_proto( + coin: &dyn CoinContext, + undelegate: &Proto::mod_Message::Undelegate<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_staking_message::UndelegateMessage; + + let amount = undelegate + .amount + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + let amount = Self::coin_from_proto(amount)?; + + let msg = UndelegateMessage { + custom_type_prefix: Self::custom_msg_type(&undelegate.type_prefix), + amount, + delegator_address: Address::from_str_with_coin(coin, &undelegate.delegator_address)?, + validator_address: Address::from_str_with_coin(coin, &undelegate.validator_address)?, + }; + Ok(msg.into_boxed()) + } + + pub fn withdraw_reward_msg_from_proto( + coin: &dyn CoinContext, + withdraw: &Proto::mod_Message::WithdrawDelegationReward<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_staking_message::WithdrawDelegationRewardMessage; + + let msg = WithdrawDelegationRewardMessage { + custom_type_prefix: Self::custom_msg_type(&withdraw.type_prefix), + delegator_address: Address::from_str_with_coin(coin, &withdraw.delegator_address)?, + validator_address: Address::from_str_with_coin(coin, &withdraw.validator_address)?, + }; + Ok(msg.into_boxed()) + } + + pub fn set_withdraw_address_msg_from_proto( + coin: &dyn CoinContext, + set: &Proto::mod_Message::SetWithdrawAddress<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_staking_message::SetWithdrawAddressMessage; + + let msg = SetWithdrawAddressMessage { + custom_type_prefix: Self::custom_msg_type(&set.type_prefix), + delegator_address: Address::from_str_with_coin(coin, &set.delegator_address)?, + withdraw_address: Address::from_str_with_coin(coin, &set.withdraw_address)?, + }; + Ok(msg.into_boxed()) + } + + pub fn redelegate_msg_from_proto( + coin: &dyn CoinContext, + redelegate: &Proto::mod_Message::BeginRedelegate<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_staking_message::BeginRedelegateMessage; + + let amount = redelegate + .amount + .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 msg = BeginRedelegateMessage { + custom_type_prefix: Self::custom_msg_type(&redelegate.type_prefix), + amount, + delegator_address: Address::from_str_with_coin(coin, &redelegate.delegator_address)?, + validator_src_address, + validator_dst_address, + }; + Ok(msg.into_boxed()) + } + + pub fn wasm_raw_msg_from_proto( + _coin: &dyn CoinContext, + raw: &Proto::mod_Message::RawJSON<'_>, + ) -> SigningResult { + let value = serde_json::from_str(&raw.value) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?; + + let msg = JsonRawMessage { + msg_type: raw.type_pb.to_string(), + value, + }; + Ok(msg.into_boxed()) + } + + pub fn wasm_terra_execute_contract_transfer_msg_from_proto( + coin: &dyn CoinContext, + transfer: &Proto::mod_Message::WasmTerraExecuteContractTransfer<'_>, + ) -> SigningResult { + use crate::transaction::message::terra_wasm_message::TerraExecuteContractMessage; + use crate::transaction::message::wasm_message::{ExecuteMsg, WasmExecutePayload}; + + let execute_payload = WasmExecutePayload::Transfer { + amount: U256::from_big_endian_slice(&transfer.amount)?, + recipient: transfer.recipient_address.to_string(), + }; + + let msg = TerraExecuteContractMessage { + sender: Address::from_str_with_coin(coin, &transfer.sender_address)?, + contract: Address::from_str_with_coin(coin, &transfer.contract_address)?, + execute_msg: ExecuteMsg::json(execute_payload)?, + // Used in case you are sending native tokens along with this message. + coins: Vec::default(), + }; + Ok(msg.into_boxed()) + } + + pub fn wasm_terra_execute_contract_send_msg_from_proto( + coin: &dyn CoinContext, + send: &Proto::mod_Message::WasmTerraExecuteContractSend<'_>, + ) -> SigningResult { + use crate::transaction::message::terra_wasm_message::TerraExecuteContractMessage; + use crate::transaction::message::wasm_message::{ExecuteMsg, WasmExecutePayload}; + + let execute_payload = WasmExecutePayload::Send { + amount: U256::from_big_endian_slice(&send.amount)?, + contract: send.recipient_contract_address.to_string(), + msg: send.msg.to_string(), + }; + + let msg = TerraExecuteContractMessage { + sender: Address::from_str_with_coin(coin, &send.sender_address)?, + contract: Address::from_str_with_coin(coin, &send.contract_address)?, + execute_msg: ExecuteMsg::json(execute_payload)?, + // Used in case you are sending native tokens along with this message. + coins: Vec::default(), + }; + Ok(msg.into_boxed()) + } + + pub fn wasm_terra_execute_contract_generic_msg_from_proto( + coin: &dyn CoinContext, + generic: &Proto::mod_Message::WasmTerraExecuteContractGeneric<'_>, + ) -> SigningResult { + use crate::transaction::message::terra_wasm_message::TerraExecuteContractMessage; + use crate::transaction::message::wasm_message::ExecuteMsg; + + let coins = generic + .coins + .iter() + .map(Self::coin_from_proto) + .collect::>()?; + + let msg = TerraExecuteContractMessage { + sender: Address::from_str_with_coin(coin, &generic.sender_address)?, + contract: Address::from_str_with_coin(coin, &generic.contract_address)?, + execute_msg: ExecuteMsg::String(generic.execute_msg.to_string()), + coins, + }; + Ok(msg.into_boxed()) + } + + pub fn wasm_execute_contract_transfer_msg_from_proto( + coin: &dyn CoinContext, + transfer: &Proto::mod_Message::WasmExecuteContractTransfer<'_>, + ) -> SigningResult { + use crate::transaction::message::wasm_message::{ + ExecuteMsg, WasmExecuteContractMessage, WasmExecutePayload, + }; + + let transfer_payload = WasmExecutePayload::Transfer { + amount: U256::from_big_endian_slice(&transfer.amount)?, + recipient: transfer.recipient_address.to_string(), + }; + + let msg = WasmExecuteContractMessage { + sender: Address::from_str_with_coin(coin, &transfer.sender_address)?, + contract: Address::from_str_with_coin(coin, &transfer.contract_address)?, + msg: ExecuteMsg::json(transfer_payload)?, + // Used in case you are sending native tokens along with this message. + coins: Vec::default(), + }; + Ok(msg.into_boxed()) + } + + pub fn wasm_execute_contract_send_msg_from_proto( + coin: &dyn CoinContext, + send: &Proto::mod_Message::WasmExecuteContractSend<'_>, + ) -> SigningResult { + use crate::transaction::message::wasm_message::{ + ExecuteMsg, WasmExecuteContractMessage, WasmExecutePayload, + }; + + let execute_payload = WasmExecutePayload::Send { + amount: U256::from_big_endian_slice(&send.amount)?, + contract: send.recipient_contract_address.to_string(), + msg: send.msg.to_string(), + }; + + let msg = WasmExecuteContractMessage { + sender: Address::from_str_with_coin(coin, &send.sender_address)?, + contract: Address::from_str_with_coin(coin, &send.contract_address)?, + msg: ExecuteMsg::json(execute_payload)?, + // Used in case you are sending native tokens along with this message. + coins: Vec::default(), + }; + Ok(msg.into_boxed()) + } + + pub fn wasm_execute_contract_generic_msg_from_proto( + coin: &dyn CoinContext, + generic: &Proto::mod_Message::WasmExecuteContractGeneric<'_>, + ) -> SigningResult { + use crate::transaction::message::wasm_message::{ExecuteMsg, WasmExecuteContractMessage}; + + let coins = generic + .coins + .iter() + .map(Self::coin_from_proto) + .collect::>()?; + + let msg = WasmExecuteContractMessage { + sender: Address::from_str_with_coin(coin, &generic.sender_address)?, + contract: Address::from_str_with_coin(coin, &generic.contract_address)?, + msg: ExecuteMsg::String(generic.execute_msg.to_string()), + coins, + }; + Ok(msg.into_boxed()) + } + + pub fn thorchain_send_msg_from_proto( + _coin: &dyn CoinContext, + send: &Proto::mod_Message::THORChainSend<'_>, + ) -> SigningResult { + use crate::transaction::message::thorchain_message::ThorchainSendMessage; + + let amount = send + .amounts + .iter() + .map(Self::coin_from_proto) + .collect::>()?; + + let msg = ThorchainSendMessage { + from_address: send.from_address.to_vec(), + to_address: send.to_address.to_vec(), + amount, + }; + Ok(msg.into_boxed()) + } + + pub fn auth_grant_msg_from_proto( + coin: &dyn CoinContext, + auth: &Proto::mod_Message::AuthGrant<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_auth_message::AuthGrantMessage; + use Proto::mod_Message::mod_AuthGrant::OneOfgrant_type as ProtoGrantType; + + const STAKE_AUTHORIZATION_MSG_TYPE: &str = "/cosmos.staking.v1beta1.StakeAuthorization"; + + let grant_msg = match auth.grant_type { + ProtoGrantType::grant_stake(ref stake) => google::protobuf::Any { + type_url: STAKE_AUTHORIZATION_MSG_TYPE.to_string(), + value: serialize(stake) + .map_err(|_| SigningError(SigningErrorType::Error_invalid_params))?, + }, + ProtoGrantType::None => { + return Err(SigningError(SigningErrorType::Error_invalid_params)) + }, + }; + + let msg = AuthGrantMessage { + granter: Address::from_str_with_coin(coin, &auth.granter)?, + grantee: Address::from_str_with_coin(coin, &auth.grantee)?, + grant_msg, + expiration_secs: auth.expiration, + }; + Ok(msg.into_boxed()) + } + + pub fn auth_revoke_msg_from_proto( + 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)?, + msg_type_url: auth.msg_type_url.to_string(), + }; + Ok(msg.into_boxed()) + } + + pub fn vote_msg_from_proto( + coin: &dyn CoinContext, + vote: &Proto::mod_Message::MsgVote<'_>, + ) -> SigningResult { + use crate::transaction::message::cosmos_gov_message::{VoteMessage, VoteOption}; + use Proto::mod_Message::VoteOption as ProtoVoteOption; + + let option = match vote.option { + ProtoVoteOption::_UNSPECIFIED => VoteOption::Unspecified, + ProtoVoteOption::YES => VoteOption::Yes, + ProtoVoteOption::ABSTAIN => VoteOption::Abstain, + ProtoVoteOption::NO => VoteOption::No, + ProtoVoteOption::NO_WITH_VETO => VoteOption::NoWithVeto, + }; + + let msg = VoteMessage { + proposal_id: vote.proposal_id, + voter: Address::from_str_with_coin(coin, &vote.voter)?, + option, + }; + Ok(msg.into_boxed()) + } + + pub fn stride_stake_msg_from_proto( + 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)?, + amount: U256::from_str(&stake.amount)?, + host_denom: stake.host_denom.to_string(), + }; + Ok(msg.into_boxed()) + } + + pub fn stride_redeem_msg_from_proto( + _coin: &dyn CoinContext, + redeem: &Proto::mod_Message::MsgStrideLiquidStakingRedeem<'_>, + ) -> SigningResult { + use crate::transaction::message::stride_message::StrideLiquidRedeemMessage; + + let msg = StrideLiquidRedeemMessage { + creator: redeem.creator.to_string(), + amount: U256::from_str(&redeem.amount)?, + receiver: redeem.receiver.to_string(), + host_zone: redeem.host_zone.to_string(), + }; + Ok(msg.into_boxed()) + } + + pub fn thorchain_deposit_msg_from_proto( + _coin: &dyn CoinContext, + deposit: &Proto::mod_Message::THORChainDeposit<'_>, + ) -> SigningResult { + use crate::transaction::message::thorchain_message::{ + ThorchainAsset, ThorchainCoin, ThorchainDepositMessage, + }; + + let mut coins = Vec::with_capacity(deposit.coins.len()); + for coin_proto in deposit.coins.iter() { + let asset_proto = coin_proto + .asset + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + + let asset = ThorchainAsset { + chain: asset_proto.chain.to_string(), + symbol: asset_proto.symbol.to_string(), + ticker: asset_proto.ticker.to_string(), + synth: asset_proto.synth, + }; + coins.push(ThorchainCoin { + asset, + amount: U256::from_str(&coin_proto.amount)?, + decimals: coin_proto.decimals, + }); + } + + let msg = ThorchainDepositMessage { + coins, + memo: deposit.memo.to_string(), + signer: deposit.signer.to_vec(), + }; + Ok(msg.into_boxed()) + } + + fn custom_msg_type(type_prefix: &str) -> Option { + if type_prefix.is_empty() { + None + } else { + Some(type_prefix.to_string()) + } + } +} diff --git a/rust/tw_cosmos_sdk/src/private_key/mod.rs b/rust/tw_cosmos_sdk/src/private_key/mod.rs new file mode 100644 index 00000000000..30045cc6549 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/private_key/mod.rs @@ -0,0 +1,18 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::error::SigningResult; +use tw_keypair::{tw, KeyPairError}; +use tw_memory::Data; +use tw_misc::traits::FromSlice; + +pub mod secp256k1; + +pub type SignatureData = Data; + +pub trait CosmosPrivateKey: AsRef + FromSlice { + fn sign_tx_hash(&self, hash: &[u8]) -> SigningResult; +} diff --git a/rust/tw_cosmos_sdk/src/private_key/secp256k1.rs b/rust/tw_cosmos_sdk/src/private_key/secp256k1.rs new file mode 100644 index 00000000000..921409f4cd4 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/private_key/secp256k1.rs @@ -0,0 +1,36 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::private_key::CosmosPrivateKey; +use tw_coin_entry::error::{SigningError, SigningResult}; +use tw_keypair::tw; +use tw_keypair::tw::Curve; +use tw_keypair::KeyPairError; +use tw_memory::Data; + +pub struct Secp256PrivateKey(tw::PrivateKey); + +impl AsRef for Secp256PrivateKey { + fn as_ref(&self) -> &tw::PrivateKey { + &self.0 + } +} + +impl<'a> TryFrom<&'a [u8]> for Secp256PrivateKey { + type Error = KeyPairError; + + fn try_from(value: &'a [u8]) -> Result { + tw::PrivateKey::new(value.to_vec()).map(Secp256PrivateKey) + } +} + +impl CosmosPrivateKey for Secp256PrivateKey { + fn sign_tx_hash(&self, hash: &[u8]) -> SigningResult { + self.0 + .sign(hash, Curve::Secp256k1) + .map_err(SigningError::from) + } +} diff --git a/rust/tw_cosmos_sdk/src/public_key/mod.rs b/rust/tw_cosmos_sdk/src/public_key/mod.rs new file mode 100644 index 00000000000..d3a4e20f5e8 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/public_key/mod.rs @@ -0,0 +1,32 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_coin_entry::coin_context::CoinContext; +use tw_keypair::{tw, KeyPairResult}; +use tw_memory::Data; +use tw_proto::google; + +pub mod secp256k1; + +pub trait CosmosPublicKey: JsonPublicKey + ProtobufPublicKey + Sized { + fn from_private_key( + coin: &dyn CoinContext, + private_key: &tw::PrivateKey, + ) -> KeyPairResult; + + fn from_bytes(coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult; + + fn to_bytes(&self) -> Data; +} + +pub trait ProtobufPublicKey { + fn to_proto(&self) -> google::protobuf::Any; +} + +pub trait JsonPublicKey { + /// In most cases, [`JsonPublicKey::public_key_type`] returns a corresponding protobuf message type. + fn public_key_type(&self) -> String; +} diff --git a/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs b/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs new file mode 100644 index 00000000000..3558aba8626 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs @@ -0,0 +1,67 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::proto::cosmos; +use crate::public_key::{CosmosPublicKey, JsonPublicKey, ProtobufPublicKey}; +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}; + +pub struct Secp256PublicKey { + public_key: Data, +} + +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]) -> KeyPairResult { + let public_key = prepare_secp256k1_public_key(coin, public_key_bytes)?; + Ok(Secp256PublicKey { public_key }) + } + + fn to_bytes(&self) -> Data { + self.public_key.clone() + } +} + +impl ProtobufPublicKey for Secp256PublicKey { + fn to_proto(&self) -> google::protobuf::Any { + let proto = cosmos::crypto::secp256k1::PubKey { + key: self.public_key.clone(), + }; + to_any(&proto) + } +} + +impl JsonPublicKey for Secp256PublicKey { + fn public_key_type(&self) -> String { + "tendermint/PubKeySecp256k1".to_string() + } +} + +pub fn prepare_secp256k1_public_key( + coin: &dyn CoinContext, + 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()), + _ => Err(KeyPairError::InvalidPublicKey), + } +} diff --git a/rust/tw_cosmos_sdk/src/signature/mod.rs b/rust/tw_cosmos_sdk/src/signature/mod.rs new file mode 100644 index 00000000000..d2c5ba2169e --- /dev/null +++ b/rust/tw_cosmos_sdk/src/signature/mod.rs @@ -0,0 +1,16 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_keypair::KeyPairResult; +use tw_memory::Data; + +pub mod secp256k1; + +pub trait CosmosSignature: Sized { + fn from_bytes(signature_bytes: &[u8]) -> KeyPairResult; + + fn to_bytes(&self) -> Data; +} diff --git a/rust/tw_cosmos_sdk/src/signature/secp256k1.rs b/rust/tw_cosmos_sdk/src/signature/secp256k1.rs new file mode 100644 index 00000000000..f07ec5957ff --- /dev/null +++ b/rust/tw_cosmos_sdk/src/signature/secp256k1.rs @@ -0,0 +1,34 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::signature::CosmosSignature; +use tw_hash::{H512, H520}; +use tw_keypair::{KeyPairError, KeyPairResult}; +use tw_memory::Data; +use tw_misc::traits::ToBytesVec; + +pub struct Secp256k1Signature { + signature: H512, +} + +impl CosmosSignature for Secp256k1Signature { + fn from_bytes(signature_bytes: &[u8]) -> KeyPairResult { + let signature_slice = if signature_bytes.len() == H520::len() { + // Discard the last `v` recovery byte. + &signature_bytes[0..H512::len()] + } else { + signature_bytes + }; + + let signature = + H512::try_from(signature_slice).map_err(|_| KeyPairError::InvalidSignature)?; + Ok(Secp256k1Signature { signature }) + } + + fn to_bytes(&self) -> Data { + self.signature.to_vec() + } +} diff --git a/rust/tw_cosmos_sdk/src/test_utils/mod.rs b/rust/tw_cosmos_sdk/src/test_utils/mod.rs new file mode 100644 index 00000000000..334b4aa821d --- /dev/null +++ b/rust/tw_cosmos_sdk/src/test_utils/mod.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod proto_utils; +pub mod sign_utils; diff --git a/rust/tw_cosmos_sdk/src/test_utils/proto_utils.rs b/rust/tw_cosmos_sdk/src/test_utils/proto_utils.rs new file mode 100644 index 00000000000..5bef94cc9eb --- /dev/null +++ b/rust/tw_cosmos_sdk/src/test_utils/proto_utils.rs @@ -0,0 +1,33 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +pub fn make_fee(gas: u64, amount: Proto::Amount<'_>) -> Proto::Fee<'_> { + Proto::Fee { + amounts: vec![amount], + gas, + } +} + +pub fn make_fee_none(gas: u64) -> Proto::Fee<'static> { + Proto::Fee { + amounts: Vec::default(), + gas, + } +} + +pub fn make_message(message_oneof: MessageEnum) -> Proto::Message { + Proto::Message { message_oneof } +} + +pub fn make_amount<'a>(denom: &'a str, amount: &'a str) -> Proto::Amount<'a> { + Proto::Amount { + denom: denom.into(), + amount: amount.into(), + } +} diff --git a/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs b/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs new file mode 100644 index 00000000000..3e439ad4dd1 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/test_utils/sign_utils.rs @@ -0,0 +1,167 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::CosmosContext; +use crate::modules::compiler::tw_compiler::TWTransactionCompiler; +use crate::modules::signer::tw_signer::TWSigner; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::SigningErrorType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Cosmos::Proto; + +#[derive(Clone)] +pub struct TestInput<'a> { + pub coin: &'a dyn CoinContext, + pub input: Proto::SigningInput<'a>, + /// Stringified JSON object. + pub tx: &'a str, + /// Signature hex-encoded. + pub signature: &'a str, + /// Stringified signature JSON object. + pub signature_json: &'a str, +} + +#[derive(Clone)] +pub struct TestCompileInput<'a> { + pub coin: &'a dyn CoinContext, + pub input: Proto::SigningInput<'a>, + /// Either a stringified JSON object or a hex-encoded serialzied `SignDoc`. + pub tx_preimage: &'a str, + /// Expected transaction preimage hash. + pub tx_prehash: &'a str, + /// Stringified JSON object. + pub tx: &'a str, + /// Signature hex-encoded. + pub signature: &'a str, + /// Stringified signature JSON object. + pub signature_json: &'a str, +} + +#[derive(Clone)] +pub struct TestErrorInput<'a> { + pub coin: &'a dyn CoinContext, + pub input: Proto::SigningInput<'a>, + pub error: SigningErrorType, +} + +#[track_caller] +pub fn test_compile_protobuf(mut test_input: TestCompileInput<'_>) { + test_input.input.signing_mode = Proto::SigningMode::Protobuf; + test_compile_impl::(test_input); +} + +#[track_caller] +pub fn test_compile_json(mut test_input: TestCompileInput<'_>) { + test_input.input.signing_mode = Proto::SigningMode::JSON; + test_compile_impl::(test_input); +} + +#[track_caller] +pub fn test_sign_protobuf(mut test_input: TestInput<'_>) { + test_input.input.signing_mode = Proto::SigningMode::Protobuf; + test_sign_impl::(test_input); +} + +#[track_caller] +pub fn test_sign_protobuf_error(mut test_input: TestErrorInput<'_>) { + test_input.input.signing_mode = Proto::SigningMode::Protobuf; + let output = TWSigner::::sign(test_input.coin, test_input.input); + assert_eq!(output.error, test_input.error); +} + +#[track_caller] +pub fn test_sign_json(mut test_input: TestInput<'_>) { + test_input.input.signing_mode = Proto::SigningMode::JSON; + test_sign_impl::(test_input); +} + +#[track_caller] +pub fn test_sign_json_error(mut test_input: TestErrorInput<'_>) { + test_input.input.signing_mode = Proto::SigningMode::JSON; + let output = TWSigner::::sign(test_input.coin, test_input.input); + assert_eq!(output.error, test_input.error); +} + +#[track_caller] +fn test_sign_impl(test_input: TestInput<'_>) { + let output = TWSigner::::sign(test_input.coin, test_input.input); + assert_eq!(output.error, SigningError::OK); + assert!(output.error_message.is_empty()); + + let result = if output.serialized.is_empty() { + output.json + } else { + output.serialized + }; + assert_eq!(result, test_input.tx, "Unexpected result transaction"); + + assert_eq!( + output.signature.to_hex(), + test_input.signature, + "Unexpected signature" + ); + assert_eq!( + output.signature_json, test_input.signature_json, + "Unexpected signature JSON" + ); +} + +#[track_caller] +fn test_compile_impl(test_input: TestCompileInput<'_>) { + // First step - generate the preimage hashes. + let preimage_output = TWTransactionCompiler::::preimage_hashes( + test_input.coin, + test_input.input.clone(), + ); + assert_eq!(preimage_output.error, SigningError::OK); + assert!(preimage_output.error_message.is_empty()); + + let actual_str = match String::from_utf8(preimage_output.data.to_vec()) { + Ok(actual_tx) => actual_tx, + Err(_) => preimage_output.data.to_hex(), + }; + + assert_eq!( + actual_str, test_input.tx_preimage, + "Unexpected preimage transaction" + ); + assert_eq!( + preimage_output.data_hash.to_hex(), + test_input.tx_prehash, + "Unexpected preimage hash" + ); + + // Second step - Compile the transaction. + + let public_key = test_input.input.public_key.to_vec(); + let compile_output = TWTransactionCompiler::::compile( + test_input.coin, + test_input.input, + vec![test_input.signature.decode_hex().unwrap()], + vec![public_key], + ); + + assert_eq!(compile_output.error, SigningError::OK); + assert!(compile_output.error_message.is_empty()); + + let result_tx = if compile_output.serialized.is_empty() { + compile_output.json + } else { + compile_output.serialized + }; + assert_eq!(result_tx, test_input.tx, "Unexpected result transaction"); + + assert_eq!( + compile_output.signature.to_hex(), + test_input.signature, + "Unexpected signature" + ); + assert_eq!( + compile_output.signature_json, test_input.signature_json, + "Unexpected signature JSON" + ); +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_auth_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_auth_message.rs new file mode 100644 index 00000000000..7e892dd7f33 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_auth_message.rs @@ -0,0 +1,57 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::proto::cosmos; +use crate::transaction::message::{CosmosMessage, ProtobufMessage}; +use tw_coin_entry::error::SigningResult; +use tw_proto::{google, to_any}; + +/// Supports Protobuf serialization only. +pub struct AuthGrantMessage { + pub granter: Address, + pub grantee: Address, + pub grant_msg: google::protobuf::Any, + pub expiration_secs: i64, +} + +impl CosmosMessage for AuthGrantMessage
{ + fn to_proto(&self) -> SigningResult { + let expiration = google::protobuf::Timestamp { + seconds: self.expiration_secs, + ..google::protobuf::Timestamp::default() + }; + let grant = cosmos::authz::v1beta1::Grant { + authorization: Some(self.grant_msg.clone()), + expiration: Some(expiration), + }; + + let proto_msg = cosmos::authz::v1beta1::MsgGrant { + granter: self.granter.to_string(), + grantee: self.grantee.to_string(), + grant: Some(grant), + }; + Ok(to_any(&proto_msg)) + } +} + +/// Supports Protobuf serialization only. +pub struct AuthRevokeMessage { + pub granter: Address, + pub grantee: Address, + pub msg_type_url: String, +} + +impl CosmosMessage for AuthRevokeMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmos::authz::v1beta1::MsgRevoke { + granter: self.granter.to_string(), + grantee: self.grantee.to_string(), + msg_type_url: self.msg_type_url.clone(), + }; + Ok(to_any(&proto_msg)) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_bank_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_bank_message.rs new file mode 100644 index 00000000000..dddfb065839 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_bank_message.rs @@ -0,0 +1,45 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::modules::serializer::protobuf_serializer::build_coin; +use crate::proto::cosmos; +use crate::transaction::message::{message_to_json, CosmosMessage, JsonMessage, ProtobufMessage}; +use crate::transaction::Coin; +use serde::Serialize; +use tw_coin_entry::error::SigningResult; +use tw_proto::to_any; + +const DEFAULT_JSON_SEND_TYPE: &str = "cosmos-sdk/MsgSend"; + +/// cosmos-sdk/MsgSend +#[derive(Serialize)] +pub struct SendMessage { + #[serde(skip)] + pub custom_type_prefix: Option, + pub from_address: Address, + pub to_address: Address, + pub amount: Vec, +} + +impl CosmosMessage for SendMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmos::bank::v1beta1::MsgSend { + from_address: self.from_address.to_string(), + to_address: self.to_address.to_string(), + amount: self.amount.iter().map(build_coin).collect(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + let msg_type = self + .custom_type_prefix + .as_deref() + .unwrap_or(DEFAULT_JSON_SEND_TYPE); + message_to_json(msg_type, self) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_generic_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_generic_message.rs new file mode 100644 index 00000000000..6d64926930e --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_generic_message.rs @@ -0,0 +1,25 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::transaction::message::{CosmosMessage, JsonMessage}; +use serde_json::Value as Json; +use tw_coin_entry::error::SigningResult; + +/// Any raw JSON message. +/// Supports JSON serialization only. +pub struct JsonRawMessage { + pub msg_type: String, + pub value: Json, +} + +impl CosmosMessage for JsonRawMessage { + fn to_json(&self) -> SigningResult { + Ok(JsonMessage { + msg_type: self.msg_type.clone(), + value: self.value.clone(), + }) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_gov_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_gov_message.rs new file mode 100644 index 00000000000..2054761a6b5 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_gov_message.rs @@ -0,0 +1,46 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::proto::cosmos; +use crate::transaction::message::{CosmosMessage, ProtobufMessage}; +use tw_coin_entry::error::SigningResult; +use tw_proto::to_any; + +pub enum VoteOption { + Unspecified, + Yes, + Abstain, + No, + NoWithVeto, +} + +pub struct VoteMessage { + pub proposal_id: u64, + pub voter: Address, + pub option: VoteOption, +} + +impl CosmosMessage for VoteMessage
{ + fn to_proto(&self) -> SigningResult { + use cosmos::gov::v1beta1::VoteOption as ProtoVoteOption; + + let option = match self.option { + VoteOption::Unspecified => ProtoVoteOption::VOTE_OPTION_UNSPECIFIED, + VoteOption::Yes => ProtoVoteOption::VOTE_OPTION_YES, + VoteOption::Abstain => ProtoVoteOption::VOTE_OPTION_ABSTAIN, + VoteOption::No => ProtoVoteOption::VOTE_OPTION_NO, + VoteOption::NoWithVeto => ProtoVoteOption::VOTE_OPTION_NO_WITH_VETO, + }; + + let proto_msg = cosmos::gov::v1beta1::MsgVote { + proposal_id: self.proposal_id, + voter: self.voter.to_string(), + option, + }; + Ok(to_any(&proto_msg)) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_staking_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_staking_message.rs new file mode 100644 index 00000000000..353fd780547 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_staking_message.rs @@ -0,0 +1,163 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::modules::serializer::protobuf_serializer::build_coin; +use crate::proto::cosmos; +use crate::transaction::message::{message_to_json, CosmosMessage, JsonMessage, ProtobufMessage}; +use crate::transaction::Coin; +use serde::Serialize; +use tw_coin_entry::error::SigningResult; +use tw_proto::to_any; + +const DEFAULT_JSON_SET_WITHDRAW_ADDRESS_TYPE: &str = "cosmos-sdk/MsgSetWithdrawAddress"; +const DEFAULT_JSON_WITHDRAW_REWARDS_TYPE: &str = "cosmos-sdk/MsgWithdrawDelegationReward"; +const DEFAULT_JSON_BEGIN_REDELEGATE_TYPE: &str = "cosmos-sdk/MsgBeginRedelegate"; +const DEFAULT_JSON_UNDELEGATE_TYPE: &str = "cosmos-sdk/MsgUndelegate"; +const DEFAULT_JSON_DELEGATE_TYPE: &str = "cosmos-sdk/MsgDelegate"; + +/// cosmos-sdk/MsgDelegate +#[derive(Serialize)] +pub struct DelegateMessage { + #[serde(skip)] + pub custom_type_prefix: Option, + pub amount: Coin, + pub delegator_address: Address, + pub validator_address: Address, +} + +impl CosmosMessage for DelegateMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmos::staking::v1beta1::MsgDelegate { + amount: Some(build_coin(&self.amount)), + delegator_address: self.delegator_address.to_string(), + validator_address: self.validator_address.to_string(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + let msg_type = self + .custom_type_prefix + .as_deref() + .unwrap_or(DEFAULT_JSON_DELEGATE_TYPE); + message_to_json(msg_type, self) + } +} + +/// cosmos-sdk/MsgUndelegate +#[derive(Serialize)] +pub struct UndelegateMessage { + #[serde(skip)] + pub custom_type_prefix: Option, + pub amount: Coin, + pub delegator_address: Address, + pub validator_address: Address, +} + +impl CosmosMessage for UndelegateMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmos::staking::v1beta1::MsgUndelegate { + amount: Some(build_coin(&self.amount)), + delegator_address: self.delegator_address.to_string(), + validator_address: self.validator_address.to_string(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + let msg_type = self + .custom_type_prefix + .as_deref() + .unwrap_or(DEFAULT_JSON_UNDELEGATE_TYPE); + message_to_json(msg_type, self) + } +} + +/// cosmos-sdk/MsgBeginRedelegate +#[derive(Serialize)] +pub struct BeginRedelegateMessage { + #[serde(skip)] + pub custom_type_prefix: Option, + pub amount: Coin, + pub delegator_address: Address, + pub validator_src_address: Address, + pub validator_dst_address: Address, +} + +impl CosmosMessage for BeginRedelegateMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmos::staking::v1beta1::MsgBeginRedelegate { + amount: Some(build_coin(&self.amount)), + delegator_address: self.delegator_address.to_string(), + validator_src_address: self.validator_src_address.to_string(), + validator_dst_address: self.validator_dst_address.to_string(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + let msg_type = self + .custom_type_prefix + .as_deref() + .unwrap_or(DEFAULT_JSON_BEGIN_REDELEGATE_TYPE); + message_to_json(msg_type, self) + } +} + +/// cosmos-sdk/MsgWithdrawDelegationReward +#[derive(Serialize)] +pub struct WithdrawDelegationRewardMessage { + #[serde(skip)] + pub custom_type_prefix: Option, + pub delegator_address: Address, + pub validator_address: Address, +} + +impl CosmosMessage for WithdrawDelegationRewardMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmos::distribution::v1beta1::MsgWithdrawDelegatorReward { + delegator_address: self.delegator_address.to_string(), + validator_address: self.validator_address.to_string(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + let msg_type = self + .custom_type_prefix + .as_deref() + .unwrap_or(DEFAULT_JSON_WITHDRAW_REWARDS_TYPE); + message_to_json(msg_type, self) + } +} + +/// cosmos-sdk/MsgSetWithdrawAddress +#[derive(Serialize)] +pub struct SetWithdrawAddressMessage { + #[serde(skip)] + pub custom_type_prefix: Option, + pub delegator_address: Address, + pub withdraw_address: Address, +} + +impl CosmosMessage for SetWithdrawAddressMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmos::distribution::v1beta1::MsgSetWithdrawAddress { + delegator_address: self.delegator_address.to_string(), + withdraw_address: self.withdraw_address.to_string(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + let msg_type = self + .custom_type_prefix + .as_deref() + .unwrap_or(DEFAULT_JSON_SET_WITHDRAW_ADDRESS_TYPE); + message_to_json(msg_type, self) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/ibc_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/ibc_message.rs new file mode 100644 index 00000000000..71d51e4b9fe --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/ibc_message.rs @@ -0,0 +1,53 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::modules::serializer::protobuf_serializer::build_coin; +use crate::proto::ibc; +use crate::transaction::message::{CosmosMessage, ProtobufMessage}; +use crate::transaction::Coin; +use tw_coin_entry::error::SigningResult; +use tw_proto::to_any; + +pub struct Height { + pub revision_number: u64, + pub revision_height: u64, +} + +pub struct TransferTokensMessage { + /// IBC port, e.g. "transfer". + pub source_port: String, + /// IBC connection channel, e.g. "channel-141", see apis /ibc/applications/transfer/v1beta1/denom_traces (connections) or /node_info (own channel). + pub source_channel: String, + pub token: Coin, + pub sender: Address, + pub receiver: Address, + /// Timeout block height. Either timeout height or timestamp should be set. + /// Recommendation is to set height, to rev. 1 and block current + 1000 (see api /blocks/latest). + pub timeout_height: Height, + // Timeout timestamp (in nanoseconds) relative to the current block timestamp. Either timeout height or timestamp should be set. + pub timeout_timestamp: u64, +} + +impl CosmosMessage for TransferTokensMessage
{ + fn to_proto(&self) -> SigningResult { + let height = ibc::core::client::v1::Height { + revision_number: self.timeout_height.revision_number, + revision_height: self.timeout_height.revision_height, + }; + + let proto_msg = ibc::applications::transfer::v1::MsgTransfer { + source_port: self.source_port.clone(), + source_channel: self.source_channel.clone(), + token: Some(build_coin(&self.token)), + sender: self.sender.to_string(), + receiver: self.receiver.to_string(), + timeout_height: Some(height), + timeout_timestamp: self.timeout_timestamp, + }; + Ok(to_any(&proto_msg)) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/mod.rs b/rust/tw_cosmos_sdk/src/transaction/message/mod.rs new file mode 100644 index 00000000000..bcc1d103bee --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/mod.rs @@ -0,0 +1,56 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::modules::serializer::json_serializer::AnyMsg; +use serde::Serialize; +use serde_json::Value as Json; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_proto::google; + +pub mod cosmos_auth_message; +pub mod cosmos_bank_message; +pub mod cosmos_generic_message; +pub mod cosmos_gov_message; +pub mod cosmos_staking_message; +pub mod ibc_message; +pub mod stride_message; +pub mod terra_wasm_message; +pub mod thorchain_message; +pub mod wasm_message; + +pub type ProtobufMessage = google::protobuf::Any; +pub type CosmosMessageBox = Box; +pub type JsonMessage = AnyMsg; + +pub trait CosmosMessage { + fn into_boxed(self) -> CosmosMessageBox + where + Self: 'static + Sized, + { + Box::new(self) + } + + /// Override the method if the message can be represented as a Protobuf message. + fn to_proto(&self) -> SigningResult { + Err(SigningError(SigningErrorType::Error_not_supported)) + } + + /// Override the method if the message can be represented as a JSON object. + fn to_json(&self) -> SigningResult { + Err(SigningError(SigningErrorType::Error_not_supported)) + } +} + +/// A standard implementation of the [`CosmosMessage::to_json`] method. +/// This suits any message type that implements the `serialize` trait. +pub fn message_to_json(msg_type: &str, msg: &T) -> SigningResult { + let value = + serde_json::to_value(msg).map_err(|_| SigningError(SigningErrorType::Error_internal))?; + Ok(JsonMessage { + msg_type: msg_type.to_string(), + value, + }) +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/stride_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/stride_message.rs new file mode 100644 index 00000000000..fe99e32c155 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/stride_message.rs @@ -0,0 +1,48 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::proto::stride; +use crate::transaction::message::{CosmosMessage, ProtobufMessage}; +use tw_coin_entry::error::SigningResult; +use tw_number::U256; +use tw_proto::to_any; + +pub struct StrideLiquidStakeMessage { + pub creator: Address, + pub amount: U256, + pub host_denom: String, +} + +impl CosmosMessage for StrideLiquidStakeMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = stride::stakeibc::MsgLiquidStake { + creator: self.creator.to_string(), + amount: self.amount.to_string(), + host_denom: self.host_denom.clone(), + }; + Ok(to_any(&proto_msg)) + } +} + +pub struct StrideLiquidRedeemMessage { + pub creator: String, + pub amount: U256, + pub receiver: String, + pub host_zone: String, +} + +impl CosmosMessage for StrideLiquidRedeemMessage { + fn to_proto(&self) -> SigningResult { + let proto_msg = stride::stakeibc::MsgRedeemStake { + creator: self.creator.clone(), + amount: self.amount.to_string(), + receiver: self.receiver.clone(), + host_zone: self.host_zone.clone(), + }; + Ok(to_any(&proto_msg)) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/terra_wasm_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/terra_wasm_message.rs new file mode 100644 index 00000000000..617f563d930 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/terra_wasm_message.rs @@ -0,0 +1,54 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::modules::serializer::protobuf_serializer::build_coin; +use crate::proto::terra; +use crate::transaction::message::wasm_message::ExecuteMsg; +use crate::transaction::message::{CosmosMessage, JsonMessage, ProtobufMessage}; +use crate::transaction::Coin; +use serde::Serialize; +use serde_json::json; +use tw_coin_entry::error::SigningResult; +use tw_proto::to_any; + +const DEFAULT_JSON_MSG_TYPE: &str = "wasm/MsgExecuteContract"; + +/// This method not only support token transfer, but also support all other types of contract call. +/// https://docs.terra.money/Tutorials/Smart-contracts/Manage-CW20-tokens.html#interacting-with-cw20-contract +#[derive(Serialize)] +pub struct TerraExecuteContractMessage { + pub sender: Address, + pub contract: Address, + pub execute_msg: ExecuteMsg, + pub coins: Vec, +} + +impl CosmosMessage for TerraExecuteContractMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = terra::wasm::v1beta1::MsgExecuteContract { + sender: self.sender.to_string(), + contract: self.contract.to_string(), + execute_msg: self.execute_msg.to_bytes(), + coins: self.coins.iter().map(build_coin).collect(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + // Don't use `message_to_json` because we need to try to convert [`ExecuteMsg::String`] to [`ExecuteMsg::Json`] if possible. + let value = json!({ + "coins": self.coins, + "contract": self.contract, + "execute_msg": self.execute_msg.try_to_json(), + "sender": self.sender, + }); + Ok(JsonMessage { + msg_type: DEFAULT_JSON_MSG_TYPE.to_string(), + value, + }) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/thorchain_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/thorchain_message.rs new file mode 100644 index 00000000000..2d33174bcee --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/thorchain_message.rs @@ -0,0 +1,82 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::modules::serializer::protobuf_serializer::build_coin; +use crate::proto::types; +use crate::transaction::message::{CosmosMessage, ProtobufMessage}; +use crate::transaction::Coin; +use tw_coin_entry::error::SigningResult; +use tw_memory::Data; +use tw_number::U256; +use tw_proto::to_any; + +pub struct ThorchainAsset { + pub chain: String, + pub symbol: String, + pub ticker: String, + pub synth: bool, +} + +impl ThorchainAsset { + pub fn to_proto(&self) -> types::Asset { + types::Asset { + chain: self.chain.clone(), + symbol: self.symbol.clone(), + ticker: self.ticker.clone(), + synth: self.synth, + } + } +} + +pub struct ThorchainCoin { + pub asset: ThorchainAsset, + pub amount: U256, + pub decimals: i64, +} + +impl ThorchainCoin { + pub fn to_proto(&self) -> types::Coin { + types::Coin { + asset: Some(self.asset.to_proto()), + amount: self.amount.to_string(), + decimals: self.decimals, + } + } +} + +pub struct ThorchainSendMessage { + pub from_address: Data, + pub to_address: Data, + pub amount: Vec, +} + +impl CosmosMessage for ThorchainSendMessage { + fn to_proto(&self) -> SigningResult { + let proto_msg = types::MsgSend { + from_address: self.from_address.clone(), + to_address: self.to_address.clone(), + amount: self.amount.iter().map(build_coin).collect(), + }; + Ok(to_any(&proto_msg)) + } +} + +pub struct ThorchainDepositMessage { + pub coins: Vec, + pub memo: String, + pub signer: Data, +} + +impl CosmosMessage for ThorchainDepositMessage { + fn to_proto(&self) -> SigningResult { + let proto_msg = types::MsgDeposit { + coins: self.coins.iter().map(ThorchainCoin::to_proto).collect(), + memo: self.memo.clone(), + signer: self.signer.clone(), + }; + Ok(to_any(&proto_msg)) + } +} diff --git a/rust/tw_cosmos_sdk/src/transaction/message/wasm_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/wasm_message.rs new file mode 100644 index 00000000000..fc24afc2b1b --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/message/wasm_message.rs @@ -0,0 +1,105 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::CosmosAddress; +use crate::modules::serializer::protobuf_serializer::build_coin; +use crate::proto::cosmwasm; +use crate::transaction::message::{CosmosMessage, JsonMessage, ProtobufMessage}; +use crate::transaction::Coin; +use serde::Serialize; +use serde_json::{json, Value as Json}; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_memory::Data; +use tw_number::U256; +use tw_proto::to_any; + +const DEFAULT_JSON_MSG_TYPE: &str = "wasm/MsgExecuteContract"; + +#[derive(Clone, Serialize)] +#[serde(untagged)] +pub enum ExecuteMsg { + /// Either a regular string or a stringified JSON object. + String(String), + /// JSON object with a type. + Json(Json), +} + +impl ExecuteMsg { + /// Tries to convert [`ExecuteMsg::String`] to [`ExecuteMsg::Json`], otherwise returns the same object. + pub fn try_to_json(&self) -> ExecuteMsg { + if let ExecuteMsg::String(s) = self { + if let Ok(json) = serde_json::from_str(s) { + return ExecuteMsg::Json(json); + } + } + self.clone() + } + + pub fn json(payload: Payload) -> SigningResult { + let payload = serde_json::to_value(payload) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?; + Ok(ExecuteMsg::Json(payload)) + } + + pub fn to_bytes(&self) -> Data { + match self { + ExecuteMsg::String(ref s) => s.as_bytes().to_vec(), + ExecuteMsg::Json(ref j) => j.to_string().as_bytes().to_vec(), + } + } +} + +/// This method not only support token transfer, but also support all other types of contract call. +#[derive(Serialize)] +pub struct WasmExecuteContractMessage { + pub sender: Address, + pub contract: Address, + pub msg: ExecuteMsg, + pub coins: Vec, +} + +impl CosmosMessage for WasmExecuteContractMessage
{ + fn to_proto(&self) -> SigningResult { + let proto_msg = cosmwasm::wasm::v1::MsgExecuteContract { + sender: self.sender.to_string(), + contract: self.contract.to_string(), + msg: self.msg.to_bytes(), + funds: self.coins.iter().map(build_coin).collect(), + }; + Ok(to_any(&proto_msg)) + } + + fn to_json(&self) -> SigningResult { + // Don't use `message_to_json` because we need to try to convert [`ExecuteMsg::String`] to [`ExecuteMsg::Json`] if possible. + let value = json!({ + "coins": self.coins, + "contract": self.contract, + "msg": self.msg.try_to_json(), + "sender": self.sender, + }); + Ok(JsonMessage { + msg_type: DEFAULT_JSON_MSG_TYPE.to_string(), + value, + }) + } +} + +#[derive(Serialize)] +pub enum WasmExecutePayload { + #[serde(rename = "transfer")] + Transfer { + #[serde(serialize_with = "U256::as_decimal_str")] + amount: U256, + recipient: String, + }, + #[serde(rename = "send")] + Send { + #[serde(serialize_with = "U256::as_decimal_str")] + amount: U256, + contract: String, + msg: String, + }, +} diff --git a/rust/tw_cosmos_sdk/src/transaction/mod.rs b/rust/tw_cosmos_sdk/src/transaction/mod.rs new file mode 100644 index 00000000000..0d3d02812de --- /dev/null +++ b/rust/tw_cosmos_sdk/src/transaction/mod.rs @@ -0,0 +1,72 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::context::CosmosContext; +use crate::private_key::SignatureData; +use serde::Serialize; +use tw_number::U256; + +pub mod message; + +use message::CosmosMessageBox; + +/// At this moment, TW only supports the Direct signing mode. +#[derive(Clone, Copy)] +pub enum SignMode { + Direct, +} + +pub struct Fee
{ + pub amounts: Vec, + pub gas_limit: u64, + pub payer: Option
, + pub granter: Option
, +} + +#[derive(Clone, Serialize)] +pub struct Coin { + #[serde(serialize_with = "U256::as_decimal_str")] + pub amount: U256, + pub denom: String, +} + +pub struct SignerInfo { + pub public_key: PublicKey, + pub sequence: u64, + pub sign_mode: SignMode, +} + +pub struct TxBody { + pub messages: Vec, + pub memo: String, + pub timeout_height: u64, +} + +pub struct UnsignedTransaction { + pub signer: SignerInfo, + pub fee: Fee, + pub chain_id: String, + pub account_number: u64, + pub tx_body: TxBody, +} + +impl UnsignedTransaction { + pub fn into_signed(self, signature: SignatureData) -> SignedTransaction { + SignedTransaction { + signer: self.signer, + fee: self.fee, + tx_body: self.tx_body, + signature, + } + } +} + +pub struct SignedTransaction { + pub signer: SignerInfo, + pub fee: Fee, + pub tx_body: TxBody, + pub signature: SignatureData, +} diff --git a/rust/tw_cosmos_sdk/tests/compile.rs b/rust/tw_cosmos_sdk/tests/compile.rs new file mode 100644 index 00000000000..78dd632c4a8 --- /dev/null +++ b/rust/tw_cosmos_sdk/tests/compile.rs @@ -0,0 +1,101 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_cosmos_sdk::test_utils::sign_utils::{ + test_compile_json, test_compile_protobuf, TestCompileInput, +}; +use tw_encoding::hex::DecodeHex; +use tw_keypair::tw::PublicKeyType; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +#[test] +fn test_compile_with_signatures() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let send_msg = Proto::mod_Message::Send { + from_address: "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx".into(), + to_address: "cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp".into(), + amounts: vec![make_amount("uatom", "400000")], + ..Proto::mod_Message::Send::default() + }; + + let input = Proto::SigningInput { + account_number: 546179, + chain_id: "cosmoshub-4".into(), + sequence: 0, + fee: Some(make_fee(200000, make_amount("uatom", "1000"))), + public_key: "02ecef5ce437a302c67f95468de4b31f36e911f467d7e6a52b41c1e13e1d563649" + .decode_hex() + .unwrap() + .into(), + messages: vec![make_message(MessageEnum::send_coins_message(send_msg))], + ..Proto::SigningInput::default() + }; + + test_compile_protobuf::(TestCompileInput { + coin: &coin, + input: input.clone(), + tx_preimage: "0a92010a8f010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e64126f0a2d636f736d6f73316d6b793639636e38656b74777930383435766563397570736470686b7478743033676b776c78122d636f736d6f733138733068646e736c6c6763636c7765753961796d77346e676b7472326b30726b7967647a64701a0f0a057561746f6d120634303030303012650a4e0a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a2102ecef5ce437a302c67f95468de4b31f36e911f467d7e6a52b41c1e13e1d56364912040a02080112130a0d0a057561746f6d12043130303010c09a0c1a0b636f736d6f736875622d342083ab21", + tx_prehash: "fa7990e1814c900efaedf1bdbedba22c22336675befe0ae39974130fc204f3de", + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX"}"#, + signature: "afbd513a776f4fdf470ef7f9675f21ae9d630fc4d635d8dbaa0dc0a716434cd07e02510765d4673dfa880825bae8e67cb367396ff6b976fc6b19a31fc95e8097", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJ"},"signature":"r71ROndvT99HDvf5Z18hrp1jD8TWNdjbqg3ApxZDTNB+AlEHZdRnPfqICCW66OZ8s2c5b/a5dvxrGaMfyV6Alw=="}]"#, + }); + + test_compile_json::(TestCompileInput { + coin: &coin, + input: input.clone(), + tx_preimage: r#"{"account_number":"546179","chain_id":"cosmoshub-4","fee":{"amount":[{"amount":"1000","denom":"uatom"}],"gas":"200000"},"memo":"","msgs":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"400000","denom":"uatom"}],"from_address":"cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx","to_address":"cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp"}}],"sequence":"0"}"#, + tx_prehash: "0a31f6cd50f1a5c514929ba68a977e222a7df2dc11e8470e93118cc3545e6b37", + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"1000","denom":"uatom"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"400000","denom":"uatom"}],"from_address":"cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx","to_address":"cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJ"},"signature":"tTyOrburrHEHa14qiw78e9StoZyyGmoku98IxYrWCmtN8Qo5mTeKa0BKKDfgG4LmmNdwYcrXtqQQ7F4dL3c26g=="}]}}"#, + signature: "b53c8eadbbabac71076b5e2a8b0efc7bd4ada19cb21a6a24bbdf08c58ad60a6b4df10a3999378a6b404a2837e01b82e698d77061cad7b6a410ec5e1d2f7736ea", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJ"},"signature":"tTyOrburrHEHa14qiw78e9StoZyyGmoku98IxYrWCmtN8Qo5mTeKa0BKKDfgG4LmmNdwYcrXtqQQ7F4dL3c26g=="}]"#, + }); +} + +#[test] +fn test_compile_with_signatures_direct() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let body_bytes = "0a89010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a090a046d756f6e120131".decode_hex().unwrap(); + let auth_info_bytes = "0a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc512040a020801180812110a0b0a046d756f6e120332303010c09a0c".decode_hex().unwrap(); + let sign_direct = Proto::mod_Message::SignDirect { + body_bytes: Cow::from(body_bytes), + auth_info_bytes: Cow::from(auth_info_bytes), + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + public_key: "0257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc5" + .decode_hex() + .unwrap() + .into(), + messages: vec![make_message(MessageEnum::sign_direct_message(sign_direct))], + ..Proto::SigningInput::default() + }; + + // real-world tx: https://www.mintscan.io/cosmos/txs/817101F3D96314AD028733248B28BAFAD535024D7D2C8875D3FE31DC159F096B + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Cr4BCr...1yKOU=", "mode": "BROADCAST_MODE_BLOCK"}' https://api.cosmos.network/cosmos/tx/v1beta1/txs + // also similar TX: BCDAC36B605576C8182C2829C808B30A69CAD4959D5ED1E6FF9984ABF280D603 + test_compile_protobuf::(TestCompileInput { + coin: &coin, + input: input.clone(), + tx_preimage: "0a8c010a89010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a090a046d756f6e12013112650a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc512040a020801180812110a0b0a046d756f6e120332303010c09a0c1a0a676169612d3133303033208d08", + tx_prehash: "8a6e6f74625fd39707843360120874853cc0c1d730b087f3939f4b187c75b907", + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H"}"#, + signature: "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"+eH0ABZXpCAJxOtoWWJdLkHpYfxy79KEKQnImOQ5/B9UmRbk7KxnbuNTx9VMWuMKKbQhC4v/Dr/cs3XhBQAvRw=="}]"#, + }); +} diff --git a/rust/tw_cosmos_sdk/tests/sign.rs b/rust/tw_cosmos_sdk/tests/sign.rs new file mode 100644 index 00000000000..3be2015cc9c --- /dev/null +++ b/rust/tw_cosmos_sdk/tests/sign.rs @@ -0,0 +1,361 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::tx_builder::TxBuilder; +use tw_cosmos_sdk::test_utils::sign_utils::{ + test_sign_json, test_sign_json_error, test_sign_protobuf, test_sign_protobuf_error, + TestErrorInput, TestInput, +}; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_keypair::tw::PublicKeyType; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +fn account_1037_private_key() -> Cow<'static, [u8]> { + "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005" + .decode_hex() + .unwrap() + .into() +} + +fn account_546179_private_key() -> Cow<'static, [u8]> { + "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af" + .decode_hex() + .unwrap() + .into() +} + +fn account_1366160_private_key() -> Cow<'static, [u8]> { + "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433" + .decode_hex() + .unwrap() + .into() +} + +fn make_fee(gas: u64, amount: Proto::Amount<'_>) -> Proto::Fee<'_> { + Proto::Fee { + amounts: vec![amount], + gas, + } +} + +fn make_message(message_oneof: MessageEnum) -> Proto::Message { + Proto::Message { message_oneof } +} + +fn make_amount<'a>(denom: &'a str, amount: &'a str) -> Proto::Amount<'a> { + Proto::Amount { + denom: denom.into(), + amount: amount.into(), + } +} + +fn make_height(revision_number: u64, revision_height: u64) -> Proto::Height { + Proto::Height { + revision_number, + revision_height, + } +} + +#[test] +fn test_sign_coin_send() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let send_msg = Proto::mod_Message::Send { + from_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + to_address: "cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573".into(), + amounts: vec![make_amount("muon", "1")], + ..Proto::mod_Message::Send::default() + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 8, + fee: Some(make_fee(200000, make_amount("muon", "200"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::send_coins_message(send_msg))], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H"}"#, + signature: "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"+eH0ABZXpCAJxOtoWWJdLkHpYfxy79KEKQnImOQ5/B9UmRbk7KxnbuNTx9VMWuMKKbQhC4v/Dr/cs3XhBQAvRw=="}]"#, + }); + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}}"#, + signature: "fc3ef899d206c88077fec42f21ba0b4df4bd3fd115fdf606ae01d9136fef363f57e9e33a7b9ec6ddab658cd07e3c0067470de94e4e75b979a1085a29f0efd926", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]"#, + }); +} + +#[test] +fn test_sign_raw_json() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let raw_json_msg = Proto::mod_Message::RawJSON { + type_pb: "test".into(), + value: r#"{"test":"hello"}"#.into(), + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 8, + fee: Some(make_fee(200000, make_amount("muon", "200"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::raw_json_message(raw_json_msg))], + ..Proto::SigningInput::default() + }; + + // `RawJSON` doesn't support Protobuf serialization and signing. + test_sign_protobuf_error::(TestErrorInput { + coin: &coin, + input: input.clone(), + error: SigningError::Error_not_supported, + }); + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"test","value":{"test":"hello"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"qhxxCOMiVhP7e7Mx+98HUZI0t5DNOFXwzIqNQz+fT6hDKR/ebW0uocsYnE5CiBNEalmBcs5gSIJegNkHhgyEmA=="}]}}"#, + signature: "aa1c7108e3225613fb7bb331fbdf07519234b790cd3855f0cc8a8d433f9f4fa843291fde6d6d2ea1cb189c4e428813446a598172ce6048825e80d907860c8498", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"qhxxCOMiVhP7e7Mx+98HUZI0t5DNOFXwzIqNQz+fT6hDKR/ebW0uocsYnE5CiBNEalmBcs5gSIJegNkHhgyEmA=="}]"#, + }); +} + +#[test] +fn test_sign_ibc_transfer() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let transfer_tokens = Proto::mod_Message::Transfer { + source_port: "transfer".into(), + source_channel: "channel-141".into(), + token: Some(make_amount("uatom", "100000")), + sender: "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx".into(), + receiver: "osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn".into(), + timeout_height: Some(make_height(1, 8800000)), + ..Proto::mod_Message::Transfer::default() + }; + let input = Proto::SigningInput { + account_number: 546179, + chain_id: "cosmoshub-4".into(), + sequence: 2, + fee: Some(make_fee(500000, make_amount("uatom", "12500"))), + private_key: account_546179_private_key(), + messages: vec![make_message(MessageEnum::transfer_tokens_message( + transfer_tokens, + ))], + ..Proto::SigningInput::default() + }; + + // real-world tx: https://www.mintscan.io/cosmos/txs/817101F3D96314AD028733248B28BAFAD535024D7D2C8875D3FE31DC159F096B + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Cr4BCr...1yKOU=", "mode": "BROADCAST_MODE_BLOCK"}' https://api.cosmos.network/cosmos/tx/v1beta1/txs + // also similar TX: BCDAC36B605576C8182C2829C808B30A69CAD4959D5ED1E6FF9984ABF280D603 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Cr4BCrsBCikvaWJjLmFwcGxpY2F0aW9ucy50cmFuc2Zlci52MS5Nc2dUcmFuc2ZlchKNAQoIdHJhbnNmZXISC2NoYW5uZWwtMTQxGg8KBXVhdG9tEgYxMDAwMDAiLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseCorb3NtbzE4czBoZG5zbGxnY2Nsd2V1OWF5bXc0bmdrdHIyazBya3ZuN2ptbjIHCAEQgI6ZBBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEC7O9c5DejAsZ/lUaN5LMfNukR9GfX5qUrQcHhPh1WNkkSBAoCCAEYAhIUCg4KBXVhdG9tEgUxMjUwMBCgwh4aQK0HIWdFMk+C6Gi1KG/vELe1ffcc1aEWUIqz2t/ZhwqNNHxUUSp27wteiugHEMVTEIOBhs84t2gIcT/nD/1yKOU="}"#, + signature: "ad07216745324f82e868b5286fef10b7b57df71cd5a116508ab3dadfd9870a8d347c54512a76ef0b5e8ae80710c55310838186cf38b76808713fe70ffd7228e5", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJ"},"signature":"rQchZ0UyT4LoaLUob+8Qt7V99xzVoRZQirPa39mHCo00fFRRKnbvC16K6AcQxVMQg4GGzzi3aAhxP+cP/XIo5Q=="}]"#, + }); + // `Transfer` doesn't support JSON serialization and signing. + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_sign_direct() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let body_bytes = "0a89010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a090a046d756f6e120131".decode_hex().unwrap(); + let auth_info_bytes = "0a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc512040a020801180812110a0b0a046d756f6e120332303010c09a0c".decode_hex().unwrap(); + let sign_direct = Proto::mod_Message::SignDirect { + body_bytes: Cow::from(body_bytes), + auth_info_bytes: Cow::from(auth_info_bytes), + }; + let mut input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::sign_direct_message(sign_direct))], + ..Proto::SigningInput::default() + }; + + // real-world tx: https://www.mintscan.io/cosmos/txs/817101F3D96314AD028733248B28BAFAD535024D7D2C8875D3FE31DC159F096B + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Cr4BCr...1yKOU=", "mode": "BROADCAST_MODE_BLOCK"}' https://api.cosmos.network/cosmos/tx/v1beta1/txs + // also similar TX: BCDAC36B605576C8182C2829C808B30A69CAD4959D5ED1E6FF9984ABF280D603 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H"}"#, + signature: "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"+eH0ABZXpCAJxOtoWWJdLkHpYfxy79KEKQnImOQ5/B9UmRbk7KxnbuNTx9VMWuMKKbQhC4v/Dr/cs3XhBQAvRw=="}]"#, + }); + + // `Transfer` doesn't support JSON serialization and signing. + // Set the default fee to get `SigningError::Error_not_supported`. + input.fee = Some(Proto::Fee::default()); + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_sign_direct_0a90010a() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + // Transaction body has the following content: + // https://github.com/trustwallet/wallet-core/blob/1382e3c8ac6d8e956e25c0475039f6c3988f9355/tests/chains/Cosmos/SignerTests.cpp#L327-L340 + let body_bytes = "0a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d120731323334353637".decode_hex().unwrap(); + let auth_info_bytes = "0a0a0a0012040a020801180112130a0d0a0575636f736d12043230303010c09a0c" + .decode_hex() + .unwrap(); + let sign_direct = Proto::mod_Message::SignDirect { + body_bytes: Cow::from(body_bytes), + auth_info_bytes: Cow::from(auth_info_bytes), + }; + let mut input = Proto::SigningInput { + account_number: 1, + chain_id: "cosmoshub-4".into(), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::sign_direct_message(sign_direct))], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpMBCpABChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnAKLWNvc21vczFwa3B0cmU3ZmRrbDZnZnJ6bGVzamp2aHhobGMzcjRnbW1rOHJzNhItY29zbW9zMXF5cHF4cHE5cWNyc3N6ZzJwdnhxNnJzMHpxZzN5eWM1bHp2N3h1GhAKBXVjb3NtEgcxMjM0NTY3EiEKCgoAEgQKAggBGAESEwoNCgV1Y29zbRIEMjAwMBDAmgwaQEgXmSAlm4M5bz+OX1GtvvZ3fBV2wrZrp4A/Imd55KM7ASivB/siYJegmYiOKzQ82uwoEmFalNnG2BrHHDwDR2Y="}"#, + signature: "48179920259b83396f3f8e5f51adbef6777c1576c2b66ba7803f226779e4a33b0128af07fb226097a099888e2b343cdaec2812615a94d9c6d81ac71c3c034766", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"SBeZICWbgzlvP45fUa2+9nd8FXbCtmungD8iZ3nkozsBKK8H+yJgl6CZiI4rNDza7CgSYVqU2cbYGsccPANHZg=="}]"#, + }); + + // `Transfer` doesn't support JSON serialization and signing. + // Set the default fee to get `SigningError::Error_not_supported`. + input.fee = Some(Proto::Fee::default()); + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_sign_vote() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let vote_msg = Proto::mod_Message::MsgVote { + proposal_id: 77, + voter: "cosmos1mry47pkga5tdswtluy0m8teslpalkdq07pswu4".into(), + option: Proto::mod_Message::VoteOption::YES, + }; + let input = Proto::SigningInput { + account_number: 1366160, + chain_id: "cosmoshub-4".into(), + sequence: 0, + fee: Some(make_fee(97681, make_amount("uatom", "2418"))), + private_key: account_1366160_private_key(), + messages: vec![make_message(MessageEnum::msg_vote(vote_msg))], + ..Proto::SigningInput::default() + }; + + // Successfully broadcasted https://www.mintscan.io/cosmos/txs/2EFA054B842B1641B131137B13360F95164C6C1D51BB4A4AC6DE8F75F504AA4C + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"ClQKUgobL2Nvc21vcy5nb3YudjFiZXRhMS5Nc2dWb3RlEjMITRItY29zbW9zMW1yeTQ3cGtnYTV0ZHN3dGx1eTBtOHRlc2xwYWxrZHEwN3Bzd3U0GAESZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBEhMKDQoFdWF0b20SBDI0MTgQkfsFGkA+Nb3NULc38quGC1x+8ZXry4w9mMX3IA7wUjFboTv7kVOwPlleIc8UqIsjVvKTUFnUuW8dlGQzNR1KkvbvZ1NA"}"#, + signature: "3e35bdcd50b737f2ab860b5c7ef195ebcb8c3d98c5f7200ef052315ba13bfb9153b03e595e21cf14a88b2356f2935059d4b96f1d946433351d4a92f6ef675340", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Asv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarl"},"signature":"PjW9zVC3N/KrhgtcfvGV68uMPZjF9yAO8FIxW6E7+5FTsD5ZXiHPFKiLI1byk1BZ1LlvHZRkMzUdSpL272dTQA=="}]"#, + }); + + // `MsgVote` doesn't support JSON serialization and signing. + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_vote_payload() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let mut vote_msg = Proto::mod_Message::MsgVote { + proposal_id: 123, + voter: "cosmos1mry47pkga5tdswtluy0m8teslpalkdq07pswu4".into(), + option: Proto::mod_Message::VoteOption::_UNSPECIFIED, + }; + + let tests = [ + (Proto::mod_Message::VoteOption::ABSTAIN, "087b122d636f736d6f73316d72793437706b67613574647377746c7579306d387465736c70616c6b6471303770737775341802"), + (Proto::mod_Message::VoteOption::NO, "087b122d636f736d6f73316d72793437706b67613574647377746c7579306d387465736c70616c6b6471303770737775341803"), + (Proto::mod_Message::VoteOption::NO_WITH_VETO, "087b122d636f736d6f73316d72793437706b67613574647377746c7579306d387465736c70616c6b6471303770737775341804"), + (Proto::mod_Message::VoteOption::_UNSPECIFIED, "087b122d636f736d6f73316d72793437706b67613574647377746c7579306d387465736c70616c6b647130377073777534"), + ]; + + for (option, expected) in tests { + vote_msg.option = option; + let payload = + TxBuilder::::vote_msg_from_proto(&coin, &vote_msg).unwrap(); + let actual = payload.to_proto().unwrap(); + assert_eq!(actual.value.to_hex(), expected); + } +} + +#[test] +fn test_error_missing_message() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 8, + fee: Some(make_fee(200000, make_amount("muon", "200"))), + private_key: account_1037_private_key(), + messages: Vec::default(), + ..Proto::SigningInput::default() + }; + + test_sign_protobuf_error::(TestErrorInput { + coin: &coin, + input: input.clone(), + error: SigningError::Error_invalid_params, + }); + + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_invalid_params, + }); +} diff --git a/rust/tw_cosmos_sdk/tests/sign_staking.rs b/rust/tw_cosmos_sdk/tests/sign_staking.rs new file mode 100644 index 00000000000..deaf265a136 --- /dev/null +++ b/rust/tw_cosmos_sdk/tests/sign_staking.rs @@ -0,0 +1,418 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_cosmos_sdk::test_utils::sign_utils::{ + test_sign_json, test_sign_json_error, test_sign_protobuf, TestErrorInput, TestInput, +}; +use tw_encoding::hex::DecodeHex; +use tw_keypair::tw::PublicKeyType; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +fn account_1037_private_key() -> Cow<'static, [u8]> { + "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005" + .decode_hex() + .unwrap() + .into() +} + +fn account_1290826_private_key() -> Cow<'static, [u8]> { + "c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc" + .decode_hex() + .unwrap() + .into() +} + +fn account_1932898_private_key() -> Cow<'static, [u8]> { + "d142e036ceebe70c4e61e3909d6c16bab518edfeac8bdf91000463ce0b4a6156" + .decode_hex() + .unwrap() + .into() +} + +#[test] +fn test_staking_compounding_authz() { + use Proto::mod_Message::mod_AuthGrant::OneOfgrant_type as ProtoGrantType; + use Proto::mod_Message::mod_StakeAuthorization::OneOfvalidators as ProtoValidatorsType; + + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let allow_list = Proto::mod_Message::mod_StakeAuthorization::Validators { + address: vec!["cosmosvaloper1gjtvly9lel6zskvwtvlg5vhwpu9c9waw7sxzwx".into()], + }; + let stake_authorization = Proto::mod_Message::StakeAuthorization { + authorization_type: Proto::mod_Message::AuthorizationType::DELEGATE, + validators: ProtoValidatorsType::allow_list(allow_list), + ..Proto::mod_Message::StakeAuthorization::default() + }; + let auth_grant = Proto::mod_Message::AuthGrant { + granter: "cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd".into(), + grantee: "cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf".into(), + grant_type: ProtoGrantType::grant_stake(stake_authorization), + expiration: 1692309600, + }; + let input = Proto::SigningInput { + account_number: 1290826, + chain_id: "cosmoshub-4".into(), + sequence: 5, + fee: Some(make_fee(96681, make_amount("uatom", "2418"))), + private_key: account_1290826_private_key(), + messages: vec![make_message(MessageEnum::auth_grant(auth_grant))], + ..Proto::SigningInput::default() + }; + + // Original test: https://github.com/trustwallet/wallet-core/blob/a60033f797e33628e557af7c66be539c8d78bc61/tests/chains/Cosmos/StakingTests.cpp#L18-L52 + // Please note the signature has been updated according to the serialization of the `StakeAuthorization` message. + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + // Previous: CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI= + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjogARI2CjRjb3Ntb3N2YWxvcGVyMWdqdHZseTlsZWw2enNrdnd0dmxnNXZod3B1OWM5d2F3N3N4end4EgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQEAN1nIfDawlHnep2bNEm14w+g7tYybJJT3htcGVS6s9D7va3ed1OUEIk9LZoc3G//VenJ+KLw26SRVBaRukgVI="}"#, + // Previous: 81727ee8a318a7fcec7cfad59ab16ac7cb2353d99cd81ee110eafed47207fb5923aa7cd22191a8779ca15ebef3811091cf464e535140e4a5029442b22bd3f572 + signature: "400dd6721f0dac251e77a9d9b3449b5e30fa0eed6326c9253de1b5c1954bab3d0fbbdadde77539410893d2d9a1cdc6fff55e9c9f8a2f0dba491541691ba48152", + // Previous: gXJ+6KMYp/zsfPrVmrFqx8sjU9mc2B7hEOr+1HIH+1kjqnzSIZGod5yhXr7zgRCRz0ZOU1FA5KUClEKyK9P1cg== + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1K"},"signature":"QA3Wch8NrCUed6nZs0SbXjD6Du1jJsklPeG1wZVLqz0Pu9rd53U5QQiT0tmhzcb/9V6cn4ovDbpJFUFpG6SBUg=="}]"#, + }); + + // `AuthGrant` doesn't support JSON serialization and signing. + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_staking_compounding_authz_f355e659() { + use Proto::mod_Message::mod_AuthGrant::OneOfgrant_type as ProtoGrantType; + use Proto::mod_Message::mod_StakeAuthorization::OneOfvalidators as ProtoValidatorsType; + + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let allow_list = Proto::mod_Message::mod_StakeAuthorization::Validators { + address: vec!["cosmosvaloper1gjtvly9lel6zskvwtvlg5vhwpu9c9waw7sxzwx".into()], + }; + let stake_authorization = Proto::mod_Message::StakeAuthorization { + authorization_type: Proto::mod_Message::AuthorizationType::DELEGATE, + validators: ProtoValidatorsType::allow_list(allow_list), + ..Proto::mod_Message::StakeAuthorization::default() + }; + let auth_grant = Proto::mod_Message::AuthGrant { + granter: "cosmos1wd0hdkzq68nmwzpprcugx82msj3l2y3wh8g5vv".into(), + grantee: "cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf".into(), + grant_type: ProtoGrantType::grant_stake(stake_authorization), + expiration: 1733011200, + }; + let input = Proto::SigningInput { + account_number: 1932898, + chain_id: "cosmoshub-4".into(), + sequence: 0, + fee: Some(make_fee(96681, make_amount("uatom", "2418"))), + private_key: account_1932898_private_key(), + messages: vec![make_message(MessageEnum::auth_grant(auth_grant))], + ..Proto::SigningInput::default() + }; + + // Successfully broadcasted https://www.mintscan.io/cosmos/tx/F355E659CBB8C0191213415E8F3EC6FD0AD1541F96FF192855147F6C0872A98B?height=17879293 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input, + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczF3ZDBoZGt6cTY4bm13enBwcmN1Z3g4Mm1zajNsMnkzd2g4ZzV2dhItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjogARI2CjRjb3Ntb3N2YWxvcGVyMWdqdHZseTlsZWw2enNrdnd0dmxnNXZod3B1OWM5d2F3N3N4end4EgYIgM6uugYSZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA1yVadHrd1uQDhwasOzMBbg6zarM8PjyhRmDVY97HiX5EgQKAggBEhMKDQoFdWF0b20SBDI0MTgQqfMFGkCcDzlVwE+RUkWhjH0PiBrKDzqGgIczmj9fuMI0umrOFTqmKm/IQGol0eo4XZOIcahSYlqJ+1MOptAZM8Csqoay"}"#, + signature: "9c0f3955c04f915245a18c7d0f881aca0f3a868087339a3f5fb8c234ba6ace153aa62a6fc8406a25d1ea385d938871a852625a89fb530ea6d01933c0acaa86b2", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A1yVadHrd1uQDhwasOzMBbg6zarM8PjyhRmDVY97HiX5"},"signature":"nA85VcBPkVJFoYx9D4gayg86hoCHM5o/X7jCNLpqzhU6pipvyEBqJdHqOF2TiHGoUmJaiftTDqbQGTPArKqGsg=="}]"#, + }); +} + +#[test] +fn test_staking_remove_compounding_authz() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let auth_revoke = Proto::mod_Message::AuthRevoke { + granter: "cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd".into(), + grantee: "cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf".into(), + msg_type_url: "/cosmos.staking.v1beta1.MsgDelegate".into(), + }; + let input = Proto::SigningInput { + account_number: 1290826, + chain_id: "cosmoshub-4".into(), + sequence: 4, + fee: Some(make_fee(87735, make_amount("uatom", "2194"))), + private_key: account_1290826_private_key(), + messages: vec![make_message(MessageEnum::auth_revoke(auth_revoke))], + ..Proto::SigningInput::default() + }; + + // Successfully broadcasted: https://www.mintscan.io/cosmos/txs/E3218F634BB6A1BE256545EBE38275D5B02D41E88F504A43F97CD9CD2B624D44 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CqoBCqcBCh8vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnUmV2b2tlEoMBCi1jb3Ntb3MxM2swcTBsN2xnMmtyMzJrdnQ3bHkyMzZwcGxkeTh2OWR6d2gzZ2QSLWNvc21vczFmczdsdTI4aHg1bTlha203cnAwYzI0MjJjbjhyMmY3Z3VydWpoZhojL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnRGVsZWdhdGUSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAQSEwoNCgV1YXRvbRIEMjE5NBC3rQUaQI7K+W7MMBoD6FbFZxRBqs9VTjErztjWTy57+fvrLaTCIZ+eBs7CuaKqfUZdSN8otjubSHVTQID3k9DpPAX0yDo="}"#, + signature: "8ecaf96ecc301a03e856c5671441aacf554e312bced8d64f2e7bf9fbeb2da4c2219f9e06cec2b9a2aa7d465d48df28b63b9b4875534080f793d0e93c05f4c83a", + // Previous: gXJ+6KMYp/zsfPrVmrFqx8sjU9mc2B7hEOr+1HIH+1kjqnzSIZGod5yhXr7zgRCRz0ZOU1FA5KUClEKyK9P1cg== + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1K"},"signature":"jsr5bswwGgPoVsVnFEGqz1VOMSvO2NZPLnv5++stpMIhn54GzsK5oqp9Rl1I3yi2O5tIdVNAgPeT0Ok8BfTIOg=="}]"#, + }); + + // `AuthRevoke` doesn't support JSON serialization and signing. + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_staking_delegate() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let delegate = Proto::mod_Message::Delegate { + delegator_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + validator_address: "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp".into(), + amount: Some(make_amount("muon", "10")), + ..Proto::mod_Message::Delegate::default() + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 7, + fee: Some(make_fee(101721, make_amount("muon", "1018"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::stake_message(delegate))], + mode: Proto::BroadcastMode::ASYNC, + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_ASYNC","tx_bytes":"CpsBCpgBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJxCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3AaCgoEbXVvbhICMTASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpA8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA=="}"#, + signature: "f0ef499bf90be996b6237a680ece6fa4ca3060980dbd808905153fbf1023b3494d658b2ae34aa94dbc0e4db3918c903952343a6ae738d2feae0854f8ab8cfeb8", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA=="}]"#, + }); + + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"async","tx":{"fee":{"amount":[{"amount":"1018","denom":"muon"}],"gas":"101721"},"memo":"","msg":[{"type":"cosmos-sdk/MsgDelegate","value":{"amount":{"amount":"10","denom":"muon"},"delegator_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","validator_address":"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"wIvfbCsLRCjzeXXoXTKfHLGXRbAAmUp0O134HVfVc6pfdVNJvvzISMHRUHgYcjsSiFlLyR32heia/yLgMDtIYQ=="}]}}"#, + signature: "c08bdf6c2b0b4428f37975e85d329f1cb19745b000994a743b5df81d57d573aa5f755349befcc848c1d1507818723b1288594bc91df685e89aff22e0303b4861", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"wIvfbCsLRCjzeXXoXTKfHLGXRbAAmUp0O134HVfVc6pfdVNJvvzISMHRUHgYcjsSiFlLyR32heia/yLgMDtIYQ=="}]"#, + }); +} + +#[test] +fn test_staking_delegate_custom_msg_type() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let delegate = Proto::mod_Message::Delegate { + delegator_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + validator_address: "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp".into(), + amount: Some(make_amount("muon", "10")), + type_prefix: "unreal/MsgDelegate".into(), + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 7, + fee: Some(make_fee(101721, make_amount("muon", "1018"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::stake_message(delegate))], + mode: Proto::BroadcastMode::ASYNC, + ..Proto::SigningInput::default() + }; + + // This transaction hasn't been broadcasted. + // Check if the custom type_prefix doesn't affect Protobuf serialization. + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_ASYNC","tx_bytes":"CpsBCpgBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJxCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3AaCgoEbXVvbhICMTASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpA8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA=="}"#, + signature: "f0ef499bf90be996b6237a680ece6fa4ca3060980dbd808905153fbf1023b3494d658b2ae34aa94dbc0e4db3918c903952343a6ae738d2feae0854f8ab8cfeb8", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"async","tx":{"fee":{"amount":[{"amount":"1018","denom":"muon"}],"gas":"101721"},"memo":"","msg":[{"type":"unreal/MsgDelegate","value":{"amount":{"amount":"10","denom":"muon"},"delegator_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","validator_address":"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"D9OufIm3RGkoGU/OO7eNHe17Yg4q0OBU0pHnqtULWXIm3J1eSUxsVI6OCbGAMYEqHUB9i5b1YGrueaDYFOH9xQ=="}]}}"#, + signature: "0fd3ae7c89b7446928194fce3bb78d1ded7b620e2ad0e054d291e7aad50b597226dc9d5e494c6c548e8e09b18031812a1d407d8b96f5606aee79a0d814e1fdc5", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"D9OufIm3RGkoGU/OO7eNHe17Yg4q0OBU0pHnqtULWXIm3J1eSUxsVI6OCbGAMYEqHUB9i5b1YGrueaDYFOH9xQ=="}]"#, + }); +} + +#[test] +fn test_staking_undelegate() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let undelegate = Proto::mod_Message::Undelegate { + delegator_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + validator_address: "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp".into(), + amount: Some(make_amount("muon", "10")), + ..Proto::mod_Message::Undelegate::default() + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 7, + fee: Some(make_fee(101721, make_amount("muon", "1018"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::unstake_message(undelegate))], + mode: Proto::BroadcastMode::SYNC, + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_SYNC","tx_bytes":"Cp0BCpoBCiUvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dVbmRlbGVnYXRlEnEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBoKCgRtdW9uEgIxMBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBhlxHFnjBERxLtjLbMCKXcrDctaSZ9djtWCa3ely1bpV6m+6aAFjpr8aEZH+q2AtjJSEdgpQRJxP+9/gQsRTnZ"}"#, + signature: "619711c59e30444712ed8cb6cc08a5dcac372d69267d763b5609adde972d5ba55ea6fba680163a6bf1a1191feab602d8c9484760a50449c4ffbdfe042c4539d9", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"YZcRxZ4wREcS7Yy2zAil3Kw3LWkmfXY7Vgmt3pctW6VepvumgBY6a/GhGR/qtgLYyUhHYKUEScT/vf4ELEU52Q=="}]"#, + }); + + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"sync","tx":{"fee":{"amount":[{"amount":"1018","denom":"muon"}],"gas":"101721"},"memo":"","msg":[{"type":"cosmos-sdk/MsgUndelegate","value":{"amount":{"amount":"10","denom":"muon"},"delegator_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","validator_address":"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"j4WpUVohGIHa6/s0bCvuyjq1wtQGqbOtQCz92qPQjisTN44Tz++Ozx1lAP6F0M4+eTA03XerqQ8hZCeAfL/3nw=="}]}}"#, + signature: "8f85a9515a211881daebfb346c2beeca3ab5c2d406a9b3ad402cfddaa3d08e2b13378e13cfef8ecf1d6500fe85d0ce3e793034dd77aba90f216427807cbff79f", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"j4WpUVohGIHa6/s0bCvuyjq1wtQGqbOtQCz92qPQjisTN44Tz++Ozx1lAP6F0M4+eTA03XerqQ8hZCeAfL/3nw=="}]"#, + }); +} + +#[test] +fn test_staking_restake() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let redelegate = Proto::mod_Message::BeginRedelegate { + delegator_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + validator_src_address: "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp".into(), + validator_dst_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + amount: Some(make_amount("muon", "10")), + ..Proto::mod_Message::BeginRedelegate::default() + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 7, + fee: Some(make_fee(101721, make_amount("muon", "1018"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::restake_message(redelegate))], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CtIBCs8BCiovY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dCZWdpblJlZGVsZWdhdGUSoAEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBotY29zbW9zMWhzazZqcnl5cWpmaHA1ZGhjNTV0YzlqdGNreWd4MGVwaDZkZDAyIgoKBG11b24SAjEwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQJXKG7D830zVXu7qgALJ3RKyQI6qZZ8rnWhgdH/kfqdxRIECgIIARgHEhIKDAoEbXVvbhIEMTAxOBDZmgYaQJ52qO5xdtBkNUeFeWrnqUXkngyHFKCXnOPPClyVI0HrULdp5jbwGra2RujEOn4BrbFCb3JFnpc2o1iuLXbKQxg="}"#, + signature: "9e76a8ee7176d064354785796ae7a945e49e0c8714a0979ce3cf0a5c952341eb50b769e636f01ab6b646e8c43a7e01adb1426f72459e9736a358ae2d76ca4318", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"nnao7nF20GQ1R4V5auepReSeDIcUoJec488KXJUjQetQt2nmNvAatrZG6MQ6fgGtsUJvckWelzajWK4tdspDGA=="}]"#, + }); + + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"1018","denom":"muon"}],"gas":"101721"},"memo":"","msg":[{"type":"cosmos-sdk/MsgBeginRedelegate","value":{"amount":{"amount":"10","denom":"muon"},"delegator_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","validator_dst_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","validator_src_address":"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"5k03Yb0loovvzagMCg4gjQJP2woriZVRcOZaXF1FSros6B1X4B8MEm3lpZwrWBJMEJVgyYA9ZaF6FLVI3WxQ2w=="}]}}"#, + signature: "e64d3761bd25a28befcda80c0a0e208d024fdb0a2b89955170e65a5c5d454aba2ce81d57e01f0c126de5a59c2b58124c109560c9803d65a17a14b548dd6c50db", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"5k03Yb0loovvzagMCg4gjQJP2woriZVRcOZaXF1FSros6B1X4B8MEm3lpZwrWBJMEJVgyYA9ZaF6FLVI3WxQ2w=="}]"#, + }); +} + +#[test] +fn test_staking_withdraw_rewards() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let withdraw = Proto::mod_Message::WithdrawDelegationReward { + delegator_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + validator_address: "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp".into(), + ..Proto::mod_Message::WithdrawDelegationReward::default() + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 7, + fee: Some(make_fee(101721, make_amount("muon", "1018"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::withdraw_stake_reward_message( + withdraw, + ))], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CqMBCqABCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmUKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBW1Cd+0pNfMPEVXQtqG1VIijDjZP2UOiDlvUF478axnxlF8PaOAsY0S5OdUE3Wz7+nu8YVmrLZQS/8mlqLaK05"}"#, + signature: "56d4277ed2935f30f1155d0b6a1b55488a30e364fd943a20e5bd4178efc6b19f1945f0f68e02c6344b939d504dd6cfbfa7bbc6159ab2d9412ffc9a5a8b68ad39", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"VtQnftKTXzDxFV0LahtVSIow42T9lDog5b1BeO/GsZ8ZRfD2jgLGNEuTnVBN1s+/p7vGFZqy2UEv/Jpai2itOQ=="}]"#, + }); + + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"1018","denom":"muon"}],"gas":"101721"},"memo":"","msg":[{"type":"cosmos-sdk/MsgWithdrawDelegationReward","value":{"delegator_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","validator_address":"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"VG8NZzVvavlM+1qyK5dOSZwzEj8sLCkvTw5kh44Oco9GQxBf13FVC+s/I3HwiICqo4+o8jNMEDp3nx2C0tuY1g=="}]}}"#, + signature: "546f0d67356f6af94cfb5ab22b974e499c33123f2c2c292f4f0e64878e0e728f4643105fd771550beb3f2371f08880aaa38fa8f2334c103a779f1d82d2db98d6", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"VG8NZzVvavlM+1qyK5dOSZwzEj8sLCkvTw5kh44Oco9GQxBf13FVC+s/I3HwiICqo4+o8jNMEDp3nx2C0tuY1g=="}]"#, + }); +} + +#[test] +fn test_staking_set_withdraw_address() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("cosmos"); + + let set_address = Proto::mod_Message::SetWithdrawAddress { + delegator_address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02".into(), + withdraw_address: "cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp".into(), + ..Proto::mod_Message::SetWithdrawAddress::default() + }; + let input = Proto::SigningInput { + account_number: 1037, + chain_id: "gaia-13003".into(), + sequence: 7, + fee: Some(make_fee(101721, make_amount("muon", "1018"))), + private_key: account_1037_private_key(), + messages: vec![make_message(MessageEnum::set_withdraw_address_message( + set_address, + ))], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Cp4BCpsBCjIvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1NldFdpdGhkcmF3QWRkcmVzcxJlCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3ASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpAkm2TJLw4FcIwN5bkqVaGbmAgkTSHeYD8sUkIyJHLa89cPvThkFO/lKlxBMl2UAMs06hL6cYcl4Px+B6rpFdBpA=="}"#, + signature: "926d9324bc3815c2303796e4a956866e60209134877980fcb14908c891cb6bcf5c3ef4e19053bf94a97104c97650032cd3a84be9c61c9783f1f81eaba45741a4", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"km2TJLw4FcIwN5bkqVaGbmAgkTSHeYD8sUkIyJHLa89cPvThkFO/lKlxBMl2UAMs06hL6cYcl4Px+B6rpFdBpA=="}]"#, + }); + + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"1018","denom":"muon"}],"gas":"101721"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSetWithdrawAddress","value":{"delegator_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","withdraw_address":"cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"Is+87DPQbtQmIyZASdEdb7hlZhA9ViGiOxREAi6xqs46B5ChxGtIwCGGiWFtr5f5mucsNYmWYgXeRbVxlPutog=="}]}}"#, + signature: "22cfbcec33d06ed42623264049d11d6fb86566103d5621a23b1444022eb1aace3a0790a1c46b48c0218689616daf97f99ae72c3589966205de45b57194fbada2", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"Is+87DPQbtQmIyZASdEdb7hlZhA9ViGiOxREAi6xqs46B5ChxGtIwCGGiWFtr5f5mucsNYmWYgXeRbVxlPutog=="}]"#, + }); +} diff --git a/rust/tw_cosmos_sdk/tests/sign_stride.rs b/rust/tw_cosmos_sdk/tests/sign_stride.rs new file mode 100644 index 00000000000..a38706a35c4 --- /dev/null +++ b/rust/tw_cosmos_sdk/tests/sign_stride.rs @@ -0,0 +1,104 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_cosmos_sdk::test_utils::sign_utils::{ + test_sign_json_error, test_sign_protobuf, TestErrorInput, TestInput, +}; +use tw_encoding::hex::DecodeHex; +use tw_keypair::tw::PublicKeyType; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +fn account_136412_private_key() -> Cow<'static, [u8]> { + "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433" + .decode_hex() + .unwrap() + .into() +} + +#[test] +fn test_stride_liquid_staking_stake() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("stride"); + + let stake = Proto::mod_Message::MsgStrideLiquidStakingStake { + creator: "stride1mry47pkga5tdswtluy0m8teslpalkdq0a2sjge".into(), + amount: "100000".into(), + host_denom: "uatom".into(), + }; + let input = Proto::SigningInput { + account_number: 136412, + chain_id: "stride-1".into(), + sequence: 0, + fee: Some(make_fee(500000, make_amount("ustrd", "0"))), + private_key: account_136412_private_key(), + messages: vec![make_message(MessageEnum::msg_stride_liquid_staking_stake( + stake, + ))], + ..Proto::SigningInput::default() + }; + + // Successfully broadcasted: https://www.mintscan.io/stride/txs/48E51A2571D99453C4581B30CECA2A1156C0D1EBACCD3619729B5A35AD67CC94?height=3485243 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CmMKYQofL3N0cmlkZS5zdGFrZWliYy5Nc2dMaXF1aWRTdGFrZRI+Ci1zdHJpZGUxbXJ5NDdwa2dhNXRkc3d0bHV5MG04dGVzbHBhbGtkcTBhMnNqZ2USBjEwMDAwMBoFdWF0b20SYgpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBEhAKCgoFdXN0cmQSATAQoMIeGkCDaZHV5/Z3CAQC5DXkaHmF6OKUiS5XKDsl3ZnBaaVuJjlSWV2vA7MPwGbC17P6jbVJt58ZLcxIWFt76UO3y1ix"}"#, + signature: "836991d5e7f677080402e435e4687985e8e294892e57283b25dd99c169a56e263952595daf03b30fc066c2d7b3fa8db549b79f192dcc48585b7be943b7cb58b1", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Asv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarl"},"signature":"g2mR1ef2dwgEAuQ15Gh5hejilIkuVyg7Jd2ZwWmlbiY5UlldrwOzD8Bmwtez+o21SbefGS3MSFhbe+lDt8tYsQ=="}]"#, + }); + + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_stride_liquid_staking_redeem() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("stride"); + + let redeem = Proto::mod_Message::MsgStrideLiquidStakingRedeem { + creator: "stride1mry47pkga5tdswtluy0m8teslpalkdq0a2sjge".into(), + amount: "40000".into(), + receiver: "cosmos1mry47pkga5tdswtluy0m8teslpalkdq07pswu4".into(), + host_zone: "cosmoshub-4".into(), + }; + let input = Proto::SigningInput { + account_number: 136412, + chain_id: "stride-1".into(), + sequence: 1, + fee: Some(make_fee(1000000, make_amount("ustrd", "0"))), + private_key: account_136412_private_key(), + messages: vec![make_message(MessageEnum::msg_stride_liquid_staking_redeem( + redeem, + ))], + ..Proto::SigningInput::default() + }; + + // Successfully broadcasted: https://www.mintscan.io/stride/txs/B3D3A92A2FFB92A480A4B547A4303E6932204972A965D687DB4FB6B4E16B2C42?height=3485343 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpgBCpUBCh8vc3RyaWRlLnN0YWtlaWJjLk1zZ1JlZGVlbVN0YWtlEnIKLXN0cmlkZTFtcnk0N3BrZ2E1dGRzd3RsdXkwbTh0ZXNscGFsa2RxMGEyc2pnZRIFNDAwMDAaC2Nvc21vc2h1Yi00Ii1jb3Ntb3MxbXJ5NDdwa2dhNXRkc3d0bHV5MG04dGVzbHBhbGtkcTA3cHN3dTQSZApQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBGAESEAoKCgV1c3RyZBIBMBDAhD0aQKf84TYoPqwnXw22r0dok2fYplUFu003TlIfpoT+wqTZF1lHPC+RTAoJob6x50CnfvGlgJFBEQYPD+Ccv659VVA="}"#, + signature: "a7fce136283eac275f0db6af47689367d8a65505bb4d374e521fa684fec2a4d91759473c2f914c0a09a1beb1e740a77ef1a580914111060f0fe09cbfae7d5550", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Asv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarl"},"signature":"p/zhNig+rCdfDbavR2iTZ9imVQW7TTdOUh+mhP7CpNkXWUc8L5FMCgmhvrHnQKd+8aWAkUERBg8P4Jy/rn1VUA=="}]"#, + }); + + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} diff --git a/rust/tw_cosmos_sdk/tests/sign_terra_wasm.rs b/rust/tw_cosmos_sdk/tests/sign_terra_wasm.rs new file mode 100644 index 00000000000..f3ac72bbf8f --- /dev/null +++ b/rust/tw_cosmos_sdk/tests/sign_terra_wasm.rs @@ -0,0 +1,272 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde_json::json; +use std::borrow::Cow; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::tx_builder::TxBuilder; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_cosmos_sdk::test_utils::sign_utils::{test_sign_json, test_sign_protobuf, TestInput}; +use tw_encoding::base64; +use tw_encoding::hex::DecodeHex; +use tw_keypair::tw::PublicKeyType; +use tw_number::U256; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +fn account_3407705_private_key() -> Cow<'static, [u8]> { + "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616" + .decode_hex() + .unwrap() + .into() +} + +#[test] +fn test_terra_wasm_transfer_protobuf_9ff3f0() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let transfer = Proto::mod_Message::WasmTerraExecuteContractTransfer { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + amount: U256::encode_be_compact(250000), + recipient_address: "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp".into(), + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "columbus-5".into(), + sequence: 3, + fee: Some(make_fee(200000, make_amount("uluna", "3000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message( + MessageEnum::wasm_terra_execute_contract_transfer_message(transfer), + )], + ..Proto::SigningInput::default() + }; + + // https://finder.terra.money/mainnet/tx/9FF3F0A16879254C22EB90D8B4D6195467FE5014381FD36BD3C23CA6698FE94B + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CucBCuQBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBK5AQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Glt7InRyYW5zZmVyIjp7ImFtb3VudCI6IjI1MDAwMCIsInJlY2lwaWVudCI6InRlcnJhMWpsZ2FxeTludm4yaGY1dDJzcmE5eWN6OHM3N3duZjlsMGttZ2NwIn19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgDEhMKDQoFdWx1bmESBDMwMDAQwJoMGkAaprIEMLPH2HmFdwFGoaipb2GIyhXt6ombz+WMnG2mORBI6gFt0M+IymYgzZz6w1SW52R922yafDnn7yXfutRw"}"#, + signature: "1aa6b20430b3c7d87985770146a1a8a96f6188ca15edea899bcfe58c9c6da6391048ea016dd0cf88ca6620cd9cfac35496e7647ddb6c9a7c39e7ef25dfbad470", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"GqayBDCzx9h5hXcBRqGoqW9hiMoV7eqJm8/ljJxtpjkQSOoBbdDPiMpmIM2c+sNUludkfdtsmnw55+8l37rUcA=="}]"#, + }); +} + +#[test] +fn test_terra_wasm_transfer_json_078e90() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let transfer = Proto::mod_Message::WasmTerraExecuteContractTransfer { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + amount: U256::encode_be_compact(250000), + recipient_address: "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp".into(), + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "columbus-5".into(), + sequence: 2, + fee: Some(make_fee(200000, make_amount("uluna", "3000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message( + MessageEnum::wasm_terra_execute_contract_transfer_message(transfer), + )], + ..Proto::SigningInput::default() + }; + + // https://finder.terra.money/mainnet/tx/078E90458061611F6FD8B708882B55FF5C1FFB3FCE61322107A0A0DE39FC0F3E + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"3000","denom":"uluna"}],"gas":"200000"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[],"contract":"terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76","execute_msg":{"transfer":{"amount":"250000","recipient":"terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp"}},"sender":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"BjETdtbA97Wv1zvcsCV1tM+bdYKC8O3uGTk4mMRv6pBJB2y/Ds7qoS7s/zrkhYak1YChklQetHsI30XRXzGIkg=="}]}}"#, + signature: "06311376d6c0f7b5afd73bdcb02575b4cf9b758282f0edee19393898c46fea9049076cbf0eceeaa12eecff3ae48586a4d580a192541eb47b08df45d15f318892", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"BjETdtbA97Wv1zvcsCV1tM+bdYKC8O3uGTk4mMRv6pBJB2y/Ds7qoS7s/zrkhYak1YChklQetHsI30XRXzGIkg=="}]"#, + }); +} + +#[test] +fn test_terra_wasm_generic() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let generic = Proto::mod_Message::WasmTerraExecuteContractGeneric { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + execute_msg: r#"{"transfer": { "amount": "250000", "recipient": "terra1d7048csap4wzcv5zm7z6tdqem2agyp9647vdyj" } }"#.into(), + coins: Vec::default(), + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "columbus-5".into(), + sequence: 7, + fee: Some(make_fee(200000, make_amount("uluna", "3000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message( + MessageEnum::wasm_terra_execute_contract_generic(generic), + )], + ..Proto::SigningInput::default() + }; + + // https://finder.terra.money/mainnet/tx/EC4F8532847E4D6AF016E6F6D3F027AE7FB6FF0B533C5132B01382D83B214A6F + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Cu4BCusBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLAAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2GmJ7InRyYW5zZmVyIjogeyAiYW1vdW50IjogIjI1MDAwMCIsICJyZWNpcGllbnQiOiAidGVycmExZDcwNDhjc2FwNHd6Y3Y1em03ejZ0ZHFlbTJhZ3lwOTY0N3ZkeWoiIH0gfRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYBxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAkPsS7xlSng2LMc9KiD1soN5NLaDcUh8I9okPmsdJN3le1B7yxRGNB4aQfhaRl/8Z0r5vitRT0AWuxDasd8wcFw=="}"#, + signature: "90fb12ef19529e0d8b31cf4a883d6ca0de4d2da0dc521f08f6890f9ac74937795ed41ef2c5118d0786907e169197ff19d2be6f8ad453d005aec436ac77cc1c17", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"kPsS7xlSng2LMc9KiD1soN5NLaDcUh8I9okPmsdJN3le1B7yxRGNB4aQfhaRl/8Z0r5vitRT0AWuxDasd8wcFw=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"3000","denom":"uluna"}],"gas":"200000"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[],"contract":"terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76","execute_msg":{"transfer":{"amount":"250000","recipient":"terra1d7048csap4wzcv5zm7z6tdqem2agyp9647vdyj"}},"sender":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"2Ph/0CdoT/lUn4fvQUp0mN+uhQ80W/9YWS6B8gmwWP5pgdFWDvkvWB4ytGRQ5M8XxJnLi32qZvsPW3Nbv9YElw=="}]}}"#, + signature: "d8f87fd027684ff9549f87ef414a7498dfae850f345bff58592e81f209b058fe6981d1560ef92f581e32b46450e4cf17c499cb8b7daa66fb0f5b735bbfd60497", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"2Ph/0CdoT/lUn4fvQUp0mN+uhQ80W/9YWS6B8gmwWP5pgdFWDvkvWB4ytGRQ5M8XxJnLi32qZvsPW3Nbv9YElw=="}]"#, + }); +} + +#[test] +fn test_terra_wasm_generic_with_coins() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let generic = Proto::mod_Message::WasmTerraExecuteContractGeneric { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC Market + contract_address: "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s".into(), + execute_msg: r#"{ "deposit_stable": {} }"#.into(), + coins: vec![make_amount("uusd", "1000")], + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "columbus-5".into(), + sequence: 9, + fee: Some(make_fee(600000, make_amount("uluna", "7000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message( + MessageEnum::wasm_terra_execute_contract_generic(generic), + )], + ..Proto::SigningInput::default() + }; + + // https://finder.terra.money/mainnet/tx/6651FCE0EE5C6D6ACB655CC49A6FD5E939FB082862854616EA0642475BCDD0C9 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CrIBCq8BCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBKEAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMXNlcGZqN3MwYWVnNTk2N3V4bmZrNHRoemxlcnJza3RrcGVsbTVzGhh7ICJkZXBvc2l0X3N0YWJsZSI6IHt9IH0qDAoEdXVzZBIEMTAwMBJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYCRITCg0KBXVsdW5hEgQ3MDAwEMDPJBpAGyi7f1ioY8XV6pjFq1s86Om4++CIUnd3rLHif2iopCcYvX0mLkTlQ6NUERg8nWTYgXcj6fOTO/ptgPuAtv0NWg=="}"#, + signature: "1b28bb7f58a863c5d5ea98c5ab5b3ce8e9b8fbe088527777acb1e27f68a8a42718bd7d262e44e543a35411183c9d64d8817723e9f3933bfa6d80fb80b6fd0d5a", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"Gyi7f1ioY8XV6pjFq1s86Om4++CIUnd3rLHif2iopCcYvX0mLkTlQ6NUERg8nWTYgXcj6fOTO/ptgPuAtv0NWg=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"7000","denom":"uluna"}],"gas":"600000"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[{"amount":"1000","denom":"uusd"}],"contract":"terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s","execute_msg":{"deposit_stable":{}},"sender":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"mmfWOFq/9WaQ3PfZZ3C2dgU6S7PH+p/WzVruTpx9UPEHA8dwlTm5rk47YwUbhwKWmpQoUq0fON+GR1VE+rFHqA=="}]}}"#, + signature: "9a67d6385abff56690dcf7d96770b676053a4bb3c7fa9fd6cd5aee4e9c7d50f10703c7709539b9ae4e3b63051b8702969a942852ad1f38df86475544fab147a8", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"mmfWOFq/9WaQ3PfZZ3C2dgU6S7PH+p/WzVruTpx9UPEHA8dwlTm5rk47YwUbhwKWmpQoUq0fON+GR1VE+rFHqA=="}]"#, + }); +} + +#[test] +fn test_terra_wasm_send() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let encoded_msg = base64::encode(r#"{"some_message":{}}"#.as_bytes(), false); + assert_eq!(encoded_msg, "eyJzb21lX21lc3NhZ2UiOnt9fQ=="); + + let send = Proto::mod_Message::WasmTerraExecuteContractSend { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + amount: U256::encode_be_compact(250000), + recipient_contract_address: "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp".into(), + msg: encoded_msg.into(), + coin: Vec::default(), + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "columbus-5".into(), + sequence: 4, + fee: Some(make_fee(200000, make_amount("uluna", "3000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message( + MessageEnum::wasm_terra_execute_contract_send_message(send), + )], + ..Proto::SigningInput::default() + }; + + // https://finder.terra.money/mainnet/tx/9FF3F0A16879254C22EB90D8B4D6195467FE5014381FD36BD3C23CA6698FE94B + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CocCCoQCCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLZAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Gnt7InNlbmQiOnsiYW1vdW50IjoiMjUwMDAwIiwiY29udHJhY3QiOiJ0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcCIsIm1zZyI6ImV5SnpiMjFsWDIxbGMzTmhaMlVpT250OWZRPT0ifX0SZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awAEgQKAggBGAQSEwoNCgV1bHVuYRIEMzAwMBDAmgwaQL6NByKeRZsyq5g6CTMdmPqiM77nOe9uLO8FjpetFgkBFiG3Le7ieZZ+4vCMhD1bcFgMwSHibFI/uPil847U/+g="}"#, + signature: "be8d07229e459b32ab983a09331d98faa233bee739ef6e2cef058e97ad1609011621b72deee279967ee2f08c843d5b70580cc121e26c523fb8f8a5f38ed4ffe8", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"vo0HIp5FmzKrmDoJMx2Y+qIzvuc5724s7wWOl60WCQEWIbct7uJ5ln7i8IyEPVtwWAzBIeJsUj+4+KXzjtT/6A=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"3000","denom":"uluna"}],"gas":"200000"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[],"contract":"terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76","execute_msg":{"send":{"amount":"250000","contract":"terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp","msg":"eyJzb21lX21lc3NhZ2UiOnt9fQ=="}},"sender":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"17wYg45V0rjwCxMdlvcKliFnPdE7LcBoN83lfZaHB3R4XxaBqUv3IGEl3wrIjGo2OBf5SD1HkQywloPvoI6ENA=="}]}}"#, + signature: "d7bc18838e55d2b8f00b131d96f70a9621673dd13b2dc06837cde57d96870774785f1681a94bf7206125df0ac88c6a363817f9483d47910cb09683efa08e8434", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"17wYg45V0rjwCxMdlvcKliFnPdE7LcBoN83lfZaHB3R4XxaBqUv3IGEl3wrIjGo2OBf5SD1HkQywloPvoI6ENA=="}]"#, + }); +} + +#[test] +fn test_terra_transfer_payload() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let transfer = Proto::mod_Message::WasmTerraExecuteContractTransfer { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + recipient_address: "recipient=address".into(), + amount: U256::encode_be_compact(250000), + }; + + let payload = + TxBuilder::::wasm_terra_execute_contract_transfer_msg_from_proto( + &coin, &transfer, + ) + .unwrap(); + let actual = payload.to_json().unwrap(); + let expected = json!({ + "coins": [], + "contract": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", + "execute_msg": { + "transfer": { + "amount": "250000", + "recipient": "recipient=address" + } + }, + "sender": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf" + }); + assert_eq!(actual.value, expected); +} diff --git a/rust/tw_cosmos_sdk/tests/sign_thorchain.rs b/rust/tw_cosmos_sdk/tests/sign_thorchain.rs new file mode 100644 index 00000000000..967ce2e48e9 --- /dev/null +++ b/rust/tw_cosmos_sdk/tests/sign_thorchain.rs @@ -0,0 +1,135 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use std::str::FromStr; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::address::Address; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_fee_none, make_message}; +use tw_cosmos_sdk::test_utils::sign_utils::{ + test_sign_json_error, test_sign_protobuf, TestErrorInput, TestInput, +}; +use tw_encoding::hex::DecodeHex; +use tw_keypair::tw::PublicKeyType; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +fn account_593_private_key() -> Cow<'static, [u8]> { + "7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e" + .decode_hex() + .unwrap() + .into() +} + +fn account_75247_private_key() -> Cow<'static, [u8]> { + "2659e41d54ebd449d68b9d58510d8eeeb837ee00d6ecc760b7a731238d8c3113" + .decode_hex() + .unwrap() + .into() +} + +fn address_to_key_hash(addr: &str) -> Cow<'static, [u8]> { + Address::from_str(addr).unwrap().key_hash().to_vec().into() +} + +fn make_thorchain_coin<'a>( + amount: &str, + decimals: i64, + asset: Proto::THORChainAsset<'a>, +) -> Proto::THORChainCoin<'a> { + Proto::THORChainCoin { + amount: amount.to_string().into(), + decimals, + asset: Some(asset), + } +} + +#[test] +fn test_thorchain_send() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("thor"); + + let send = Proto::mod_Message::THORChainSend { + from_address: address_to_key_hash("thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r"), + to_address: address_to_key_hash("thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"), + amounts: vec![make_amount("rune", "38000000")], + }; + let input = Proto::SigningInput { + account_number: 593, + chain_id: "thorchain-mainnet-v1".into(), + sequence: 21, + fee: Some(make_fee(2500000, make_amount("rune", "200"))), + private_key: account_593_private_key(), + messages: vec![make_message(MessageEnum::thorchain_send_message(send))], + ..Proto::SigningInput::default() + }; + + // https://viewblock.io/thorchain/tx/7E480FA163F6C6AFA17593F214C7BBC218F69AE3BC72366E39042AF381BFE105 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"ClIKUAoOL3R5cGVzLk1zZ1NlbmQSPgoUFSLnZ9tusZcIsAOAKb+9YHvJvQ4SFMqGRZ+wBVHH30JUDF54aRksgzrbGhAKBHJ1bmUSCDM4MDAwMDAwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQPtmX45bPQpL1/OWkK7pBWZzNXZbjExVKfJ6nBJ3jF8dxIECgIIARgVEhIKCwoEcnVuZRIDMjAwEKDLmAEaQKZtS3ATa26OOGvqdKm14ZbHeNfkPtIajXi5MkZ5XaX2SWOeX+YnCPZ9TxF9Jj5cVIo71m55xq4hVL3yDbRe89g="}"#, + signature: "a66d4b70136b6e8e386bea74a9b5e196c778d7e43ed21a8d78b93246795da5f649639e5fe62708f67d4f117d263e5c548a3bd66e79c6ae2154bdf20db45ef3d8", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"pm1LcBNrbo44a+p0qbXhlsd41+Q+0hqNeLkyRnldpfZJY55f5icI9n1PEX0mPlxUijvWbnnGriFUvfINtF7z2A=="}]"#, + }); + + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} + +#[test] +fn test_thorchain_deposit() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("thor"); + + let deposit = Proto::mod_Message::THORChainDeposit { + memo: "=:DOGE.DOGE:DNhRF1h8J4ZnB1bxp9kaqhVLYetkx1nSJ5::tr:0".into(), + signer: address_to_key_hash("thor14j5lwl8ulexrqp5x39kmkctv2937694z3jn2dz"), + coins: vec![make_thorchain_coin( + "150000000", + 0, + Proto::THORChainAsset { + chain: "THOR".into(), + symbol: "RUNE".into(), + ticker: "RUNE".into(), + ..Proto::THORChainAsset::default() + }, + )], + }; + let input = Proto::SigningInput { + account_number: 75247, + chain_id: "thorchain-mainnet-v1".into(), + sequence: 7, + fee: Some(make_fee_none(50000000)), + private_key: account_75247_private_key(), + messages: vec![make_message(MessageEnum::thorchain_deposit_message( + deposit, + ))], + ..Proto::SigningInput::default() + }; + + // https://viewblock.io/thorchain/tx/0162213E7F9D85965B1C57FA3BF9603C655B542F358318303A7B00661AE42510 + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CoUBCoIBChEvdHlwZXMuTXNnRGVwb3NpdBJtCh8KEgoEVEhPUhIEUlVORRoEUlVORRIJMTUwMDAwMDAwEjQ9OkRPR0UuRE9HRTpETmhSRjFoOEo0Wm5CMWJ4cDlrYXFoVkxZZXRreDFuU0o1Ojp0cjowGhSsqfd8/P5MMAaGiW27YWxRY+0WohJZClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDuZVDlIFW3DtSEBa6aUBJ0DrQHlQ+2g7lIt5ekAM25SkSBAoCCAEYBxIFEIDh6xcaQAxKMZMKbM8gdLwn23GDXfbwyCkgqWzFMFlnrqFm0u54F8T32wmsoJQAdoLIyOskYmi7nb1rhryfabeeULwRhiw="}"#, + signature: "0c4a31930a6ccf2074bc27db71835df6f0c82920a96cc5305967aea166d2ee7817c4f7db09aca094007682c8c8eb246268bb9dbd6b86bc9f69b79e50bc11862c", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A7mVQ5SBVtw7UhAWumlASdA60B5UPtoO5SLeXpADNuUp"},"signature":"DEoxkwpszyB0vCfbcYNd9vDIKSCpbMUwWWeuoWbS7ngXxPfbCayglAB2gsjI6yRiaLudvWuGvJ9pt55QvBGGLA=="}]"#, + }); + + test_sign_json_error::(TestErrorInput { + coin: &coin, + input, + error: SigningError::Error_not_supported, + }); +} diff --git a/rust/tw_cosmos_sdk/tests/sign_wasm_contract.rs b/rust/tw_cosmos_sdk/tests/sign_wasm_contract.rs new file mode 100644 index 00000000000..7e0d375ea48 --- /dev/null +++ b/rust/tw_cosmos_sdk/tests/sign_wasm_contract.rs @@ -0,0 +1,253 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde_json::json; +use std::borrow::Cow; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::tx_builder::TxBuilder; +use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message}; +use tw_cosmos_sdk::test_utils::sign_utils::{test_sign_json, test_sign_protobuf, TestInput}; +use tw_encoding::base64; +use tw_encoding::hex::DecodeHex; +use tw_keypair::tw::PublicKeyType; +use tw_number::U256; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +fn account_336_private_key() -> Cow<'static, [u8]> { + "37f0af5bc20adb6832d39368a15492cd1e9e0cc1556d4317a5f75f9ccdf525ee" + .decode_hex() + .unwrap() + .into() +} + +fn account_3407705_private_key() -> Cow<'static, [u8]> { + "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616" + .decode_hex() + .unwrap() + .into() +} + +/// Airdrop Neutron +#[test] +fn test_wasm_execute_generic() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("neutron"); + + let execute_msg = r#"{"claim":{"address":"neutron19h42zjnls2tpmg6yylcg6nr56cjxcx35q6xt57", "proof":["404ae2093edcca979ccb6ae4a36689cebc9c2c6a2b00b106c5396b079bf6dcf5","282fee30a25a60904f54d4f74aee8fcf8dd2822799c43be733e18e15743d4ece","e10de4202fe6532329d0d463d9669f1b659920868b9ea87d6715bfd223a86a40","564b4122c6f98653153d8e09d5a5f659fa7ebea740aa6b689c94211f8a11cc4b"], "amount":"2000000"}}"#; + let contract = Proto::mod_Message::WasmExecuteContractGeneric { + sender_address: "neutron19h42zjnls2tpmg6yylcg6nr56cjxcx35q6xt57".into(), + // ANC + contract_address: "neutron1465d8udjudl6cd8kgdlh2s37p7q0cf9x7yveumqwqk6ng94qwnmq7n79qn" + .into(), + execute_msg: execute_msg.into(), + ..Proto::mod_Message::WasmExecuteContractGeneric::default() + }; + + let input = Proto::SigningInput { + account_number: 336, + chain_id: "pion-1".into(), + sequence: 0, + fee: Some(make_fee(666666, make_amount("untrn", "1000"))), + private_key: account_336_private_key(), + messages: vec![make_message(MessageEnum::wasm_execute_contract_generic( + contract, + ))], + ..Proto::SigningInput::default() + }; + + // Successfully broadcasted: https://explorer.rs-testnet.polypore.xyz/pion-1/tx/28F25164B1E2556844C227819B1D5437960B7E91181B37460EC6792588FF7E4E + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpQECpEECiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QS6AMKLm5ldXRyb24xOWg0MnpqbmxzMnRwbWc2eXlsY2c2bnI1NmNqeGN4MzVxNnh0NTcSQm5ldXRyb24xNDY1ZDh1ZGp1ZGw2Y2Q4a2dkbGgyczM3cDdxMGNmOXg3eXZldW1xd3FrNm5nOTRxd25tcTduNzlxbhrxAnsiY2xhaW0iOnsiYWRkcmVzcyI6Im5ldXRyb24xOWg0MnpqbmxzMnRwbWc2eXlsY2c2bnI1NmNqeGN4MzVxNnh0NTciLCAicHJvb2YiOlsiNDA0YWUyMDkzZWRjY2E5NzljY2I2YWU0YTM2Njg5Y2ViYzljMmM2YTJiMDBiMTA2YzUzOTZiMDc5YmY2ZGNmNSIsIjI4MmZlZTMwYTI1YTYwOTA0ZjU0ZDRmNzRhZWU4ZmNmOGRkMjgyMjc5OWM0M2JlNzMzZTE4ZTE1NzQzZDRlY2UiLCJlMTBkZTQyMDJmZTY1MzIzMjlkMGQ0NjNkOTY2OWYxYjY1OTkyMDg2OGI5ZWE4N2Q2NzE1YmZkMjIzYTg2YTQwIiwiNTY0YjQxMjJjNmY5ODY1MzE1M2Q4ZTA5ZDVhNWY2NTlmYTdlYmVhNzQwYWE2YjY4OWM5NDIxMWY4YTExY2M0YiJdLCAiYW1vdW50IjoiMjAwMDAwMCJ9fRJlCk4KRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECqPwhojhpWpB3vDr8R+qyUnDkcK3BPxS35F8OrHPq5WwSBAoCCAESEwoNCgV1bnRybhIEMTAwMBCq2CgaQMIEXC8zyuuXWuIeX7dZBBzxMjmheOP1ONitBrVZdwmuQUgClmwhOdW0JwRe8CJ5NUKqtDYZjKFAPKGEWQ2veDs="}"#, + signature: "c2045c2f33caeb975ae21e5fb759041cf13239a178e3f538d8ad06b5597709ae414802966c2139d5b427045ef022793542aab436198ca1403ca184590daf783b", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Aqj8IaI4aVqQd7w6/EfqslJw5HCtwT8Ut+RfDqxz6uVs"},"signature":"wgRcLzPK65da4h5ft1kEHPEyOaF44/U42K0GtVl3Ca5BSAKWbCE51bQnBF7wInk1Qqq0NhmMoUA8oYRZDa94Ow=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"1000","denom":"untrn"}],"gas":"666666"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[],"contract":"neutron1465d8udjudl6cd8kgdlh2s37p7q0cf9x7yveumqwqk6ng94qwnmq7n79qn","msg":{"claim":{"address":"neutron19h42zjnls2tpmg6yylcg6nr56cjxcx35q6xt57","amount":"2000000","proof":["404ae2093edcca979ccb6ae4a36689cebc9c2c6a2b00b106c5396b079bf6dcf5","282fee30a25a60904f54d4f74aee8fcf8dd2822799c43be733e18e15743d4ece","e10de4202fe6532329d0d463d9669f1b659920868b9ea87d6715bfd223a86a40","564b4122c6f98653153d8e09d5a5f659fa7ebea740aa6b689c94211f8a11cc4b"]}},"sender":"neutron19h42zjnls2tpmg6yylcg6nr56cjxcx35q6xt57"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Aqj8IaI4aVqQd7w6/EfqslJw5HCtwT8Ut+RfDqxz6uVs"},"signature":"R0zmQ4RCZ+UL+dTxgCHjK3IRklnLDWIRn6ZYDT9CZzUThcJdxlwxog0zCAAWhzH6HDv1T6LvdATlm7p93o+jzA=="}]}}"#, + signature: "474ce643844267e50bf9d4f18021e32b72119259cb0d62119fa6580d3f4267351385c25dc65c31a20d330800168731fa1c3bf54fa2ef7404e59bba7dde8fa3cc", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Aqj8IaI4aVqQd7w6/EfqslJw5HCtwT8Ut+RfDqxz6uVs"},"signature":"R0zmQ4RCZ+UL+dTxgCHjK3IRklnLDWIRn6ZYDT9CZzUThcJdxlwxog0zCAAWhzH6HDv1T6LvdATlm7p93o+jzA=="}]"#, + }); +} + +/// TerraV2 DepositStable +#[test] +fn test_wasm_execute_generic_with_coins() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let execute_msg = r#"{ "deposit_stable": {} }"#; + let contract = Proto::mod_Message::WasmExecuteContractGeneric { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + contract_address: "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s".into(), + execute_msg: execute_msg.into(), + coins: vec![make_amount("uusd", "1000")], + ..Proto::mod_Message::WasmExecuteContractGeneric::default() + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "phoenix-1".into(), + sequence: 9, + fee: Some(make_fee(600000, make_amount("uluna", "7000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message(MessageEnum::wasm_execute_contract_generic( + contract, + ))], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CrABCq0BCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QShAEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTFzZXBmajdzMGFlZzU5Njd1eG5mazR0aHpsZXJyc2t0a3BlbG01cxoYeyAiZGVwb3NpdF9zdGFibGUiOiB7fSB9KgwKBHV1c2QSBDEwMDASZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awAEgQKAggBGAkSEwoNCgV1bHVuYRIENzAwMBDAzyQaQEDA2foXegF+rslj6o8bX2HPJfn+q/6Ezbq2iAd0SFOTQqS8aAyywQkdZJRToXcaby1HOYL1WvmsMPgrFzChiY4="}"#, + signature: "40c0d9fa177a017eaec963ea8f1b5f61cf25f9feabfe84cdbab688077448539342a4bc680cb2c1091d649453a1771a6f2d473982f55af9ac30f82b1730a1898e", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"QMDZ+hd6AX6uyWPqjxtfYc8l+f6r/oTNuraIB3RIU5NCpLxoDLLBCR1klFOhdxpvLUc5gvVa+aww+CsXMKGJjg=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"7000","denom":"uluna"}],"gas":"600000"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[{"amount":"1000","denom":"uusd"}],"contract":"terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s","msg":{"deposit_stable":{}},"sender":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"ohL3xBbHumPGwz7nCyocMmS9n08bq27bOlV3hRSduNZzwsaxq5IktzizeYTRmv5uLvAhKHsrsMwWvJWU0J0nvw=="}]}}"#, + signature: "a212f7c416c7ba63c6c33ee70b2a1c3264bd9f4f1bab6edb3a557785149db8d673c2c6b1ab9224b738b37984d19afe6e2ef021287b2bb0cc16bc9594d09d27bf", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"ohL3xBbHumPGwz7nCyocMmS9n08bq27bOlV3hRSduNZzwsaxq5IktzizeYTRmv5uLvAhKHsrsMwWvJWU0J0nvw=="}]"#, + }); +} + +/// TerraV2 Transfer +#[test] +fn test_wasm_execute_transfer() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let transfer = Proto::mod_Message::WasmExecuteContractTransfer { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + recipient_address: "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp".into(), + amount: U256::encode_be_compact(250000), + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "phoenix-1".into(), + sequence: 3, + fee: Some(make_fee(200000, make_amount("uluna", "3000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message( + MessageEnum::wasm_execute_contract_transfer_message(transfer), + )], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CuUBCuIBCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QSuQEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3NhpbeyJ0cmFuc2ZlciI6eyJhbW91bnQiOiIyNTAwMDAiLCJyZWNpcGllbnQiOiJ0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcCJ9fRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYAxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAiBGbQaj+jsXE6/FssD3fC77QOxpli9GqsPea+KoNyMIEgVj89Hii+oU1bAEQS4qV0SaE2V6RNy24uCcFTIRbcQ=="}"#, + signature: "88119b41a8fe8ec5c4ebf16cb03ddf0bbed03b1a658bd1aab0f79af8aa0dc8c2048158fcf478a2fa85356c01104b8a95d12684d95e91372db8b827054c845b71", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"iBGbQaj+jsXE6/FssD3fC77QOxpli9GqsPea+KoNyMIEgVj89Hii+oU1bAEQS4qV0SaE2V6RNy24uCcFTIRbcQ=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"3000","denom":"uluna"}],"gas":"200000"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[],"contract":"terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76","msg":{"transfer":{"amount":"250000","recipient":"terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp"}},"sender":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"fuuH43BvZGyTHb7Eaw0QDSbnOm11qRWj7HljhEmvKbFJztaSpLJ0cDmENksmt4lmPtIE26EZ1SZ6XriGX6LS0A=="}]}}"#, + signature: "7eeb87e3706f646c931dbec46b0d100d26e73a6d75a915a3ec79638449af29b149ced692a4b274703984364b26b789663ed204dba119d5267a5eb8865fa2d2d0", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"fuuH43BvZGyTHb7Eaw0QDSbnOm11qRWj7HljhEmvKbFJztaSpLJ0cDmENksmt4lmPtIE26EZ1SZ6XriGX6LS0A=="}]"#, + }); +} + +/// TerraV2 Transfer +#[test] +fn test_wasm_execute_send() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let encoded_msg = base64::encode(r#"{"some_message":{}}"#.as_bytes(), false); + let send = Proto::mod_Message::WasmExecuteContractSend { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + recipient_contract_address: "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp".into(), + amount: U256::encode_be_compact(250000), + msg: encoded_msg.into(), + coin: Vec::default(), + }; + + let input = Proto::SigningInput { + account_number: 3407705, + chain_id: "phoenix-1".into(), + sequence: 4, + fee: Some(make_fee(200000, make_amount("uluna", "3000"))), + private_key: account_3407705_private_key(), + messages: vec![make_message( + MessageEnum::wasm_execute_contract_send_message(send), + )], + ..Proto::SigningInput::default() + }; + + test_sign_protobuf::(TestInput { + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CoUCCoICCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QS2QEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3Nhp7eyJzZW5kIjp7ImFtb3VudCI6IjI1MDAwMCIsImNvbnRyYWN0IjoidGVycmExamxnYXF5OW52bjJoZjV0MnNyYTl5Y3o4czc3d25mOWwwa21nY3AiLCJtc2ciOiJleUp6YjIxbFgyMWxjM05oWjJVaU9udDlmUT09In19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgEEhMKDQoFdWx1bmESBDMwMDAQwJoMGkBKJbW1GDrv9j2FIckm7MtpDZzP2RjgDjU84oYmOHNHsxEBPLjtt3YAjsKWBCAsjbnbVoJ3s2XFG08nxQXS9xBK"}"#, + signature: "4a25b5b5183aeff63d8521c926eccb690d9ccfd918e00e353ce28626387347b311013cb8edb776008ec29604202c8db9db568277b365c51b4f27c505d2f7104a", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"SiW1tRg67/Y9hSHJJuzLaQ2cz9kY4A41POKGJjhzR7MRATy47bd2AI7ClgQgLI2521aCd7NlxRtPJ8UF0vcQSg=="}]"#, + }); + + // This transaction hasn't been broadcasted. + test_sign_json::(TestInput { + coin: &coin, + input, + tx: r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"3000","denom":"uluna"}],"gas":"200000"},"memo":"","msg":[{"type":"wasm/MsgExecuteContract","value":{"coins":[],"contract":"terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76","msg":{"send":{"amount":"250000","contract":"terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp","msg":"eyJzb21lX21lc3NhZ2UiOnt9fQ=="}},"sender":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"EXQcMfD4507CcIKeOgkPWY92EUpQ7NJ802bf1IoMfgk69iUs6iEYucTo0MJo/Igys0yfbIHiLt4oJMetwNpqmA=="}]}}"#, + signature: "11741c31f0f8e74ec270829e3a090f598f76114a50ecd27cd366dfd48a0c7e093af6252cea2118b9c4e8d0c268fc8832b34c9f6c81e22ede2824c7adc0da6a98", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA"},"signature":"EXQcMfD4507CcIKeOgkPWY92EUpQ7NJ802bf1IoMfgk69iUs6iEYucTo0MJo/Igys0yfbIHiLt4oJMetwNpqmA=="}]"#, + }); +} + +#[test] +fn test_terra_transfer_payload() { + let coin = TestCoinContext::default() + .with_public_key_type(PublicKeyType::Secp256k1) + .with_hrp("terra"); + + let transfer = Proto::mod_Message::WasmExecuteContractTransfer { + sender_address: "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf".into(), + // ANC + contract_address: "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76".into(), + recipient_address: "recipient=address".into(), + amount: U256::encode_be_compact(250000), + }; + + let payload = + TxBuilder::::wasm_execute_contract_transfer_msg_from_proto( + &coin, &transfer, + ) + .unwrap(); + let actual = payload.to_json().unwrap(); + let expected = json!({ + "coins": [], + "contract": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", + "msg": { + "transfer": { + "amount": "250000", + "recipient": "recipient=address" + } + }, + "sender": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf" + }); + assert_eq!(actual.value, expected); +} diff --git a/rust/tw_encoding/Cargo.toml b/rust/tw_encoding/Cargo.toml index 4a1b666a551..88008908278 100644 --- a/rust/tw_encoding/Cargo.toml +++ b/rust/tw_encoding/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] arbitrary = { version = "1", features = ["derive"], optional = true } bcs = "0.1.6" +bech32 = "0.9.1" bs58 = "0.4.0" ciborium = "0.2.1" data-encoding = "2.3.3" diff --git a/rust/tw_encoding/src/base64.rs b/rust/tw_encoding/src/base64.rs index dd202dec66d..5b83eda01a6 100644 --- a/rust/tw_encoding/src/base64.rs +++ b/rust/tw_encoding/src/base64.rs @@ -5,6 +5,8 @@ // file LICENSE at the root of the source code distribution tree. use crate::{EncodingError, EncodingResult}; +use serde::{Serialize, Serializer}; +use tw_memory::Data; pub fn encode(data: &[u8], is_url: bool) -> String { if is_url { @@ -22,3 +24,16 @@ pub fn decode(data: &str, is_url: bool) -> EncodingResult> { } .map_err(|_| EncodingError::InvalidInput) } + +#[derive(Clone, Debug)] +pub struct Base64Encoded(pub Data); + +impl Serialize for Base64Encoded { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let is_url = false; + serializer.serialize_str(&encode(&self.0, is_url)) + } +} diff --git a/rust/tw_encoding/src/bech32.rs b/rust/tw_encoding/src/bech32.rs new file mode 100644 index 00000000000..397f6e213ee --- /dev/null +++ b/rust/tw_encoding/src/bech32.rs @@ -0,0 +1,31 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use bech32::{FromBase32, ToBase32, Variant}; +use tw_memory::Data; + +pub use bech32::Error as Bech32Error; + +pub type Bech32Result = Result; + +pub struct Decoded { + pub hrp: String, + pub bytes: Data, +} + +pub fn encode(hrp: &str, data: &[u8]) -> Bech32Result { + bech32::encode(hrp, data.to_base32(), Variant::Bech32) +} + +pub fn decode(s: &str) -> Bech32Result { + let (hrp, base32_bytes, variant) = bech32::decode(s)?; + let bytes = Data::from_base32(&base32_bytes)?; + + if matches!(variant, Variant::Bech32) { + return Ok(Decoded { hrp, bytes }); + } + Err(Bech32Error::InvalidChecksum) +} diff --git a/rust/tw_encoding/src/lib.rs b/rust/tw_encoding/src/lib.rs index f658d9b019f..871a6a42887 100644 --- a/rust/tw_encoding/src/lib.rs +++ b/rust/tw_encoding/src/lib.rs @@ -8,6 +8,7 @@ pub mod base32; pub mod base58; pub mod base64; pub mod bcs; +pub mod bech32; pub mod cbor; pub mod ffi; pub mod hex; diff --git a/rust/tw_ethereum/src/entry.rs b/rust/tw_ethereum/src/entry.rs index cb9a1b4fb48..f69893fd63f 100644 --- a/rust/tw_ethereum/src/entry.rs +++ b/rust/tw_ethereum/src/entry.rs @@ -46,6 +46,16 @@ impl CoinEntry for EthereumEntry { Address::from_str(address) } + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] fn derive_address( &self, _coin: &dyn CoinContext, diff --git a/rust/tw_ethereum/tests/compiler.rs b/rust/tw_ethereum/tests/compiler.rs index 48b0df2b256..27cce260850 100644 --- a/rust/tw_ethereum/tests/compiler.rs +++ b/rust/tw_ethereum/tests/compiler.rs @@ -7,7 +7,7 @@ use std::borrow::Cow; use tw_coin_entry::coin_entry_ext::CoinEntryExt; use tw_coin_entry::error::SigningErrorType; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_encoding::hex; use tw_ethereum::entry::EthereumEntry; use tw_keypair::ecdsa::secp256k1; @@ -19,6 +19,8 @@ use tw_proto::{deserialize, serialize}; #[test] fn test_external_signature_sign() { + let coin = TestCoinContext::default(); + let transfer = Proto::mod_Transaction::Transfer { amount: U256::encode_be_compact(1_000_000_000_000_000_000), data: Cow::default(), @@ -38,7 +40,7 @@ fn test_external_signature_sign() { // Step 1: Obtain preimage hash let input_data = serialize(&input).unwrap(); let preimage_data = EthereumEntry - .preimage_hashes(&EmptyCoinContext, &input_data) + .preimage_hashes(&coin, &input_data) .expect("!preimage_hashes"); let preimage: CompilerProto::PreSigningOutput = deserialize(&preimage_data).expect("Coin entry returned an invalid output"); @@ -62,7 +64,7 @@ fn test_external_signature_sign() { let input_data = serialize(&input).unwrap(); let output_data = EthereumEntry .compile( - &EmptyCoinContext, + &coin, &input_data, vec![signature], vec![public_key.to_bytes()], @@ -88,7 +90,7 @@ fn test_external_signature_sign() { let input_data = serialize(&input).unwrap(); let output_data = EthereumEntry - .sign(&EmptyCoinContext, &input_data) + .sign(&coin, &input_data) .expect("!output_data"); let output: Proto::SigningOutput = deserialize(&output_data).expect("Coin entry returned an invalid output"); diff --git a/rust/tw_ethereum/tests/signer.rs b/rust/tw_ethereum/tests/signer.rs index d880744d540..8e0acb0e2ae 100644 --- a/rust/tw_ethereum/tests/signer.rs +++ b/rust/tw_ethereum/tests/signer.rs @@ -5,19 +5,21 @@ // file LICENSE at the root of the source code distribution tree. use tw_coin_entry::coin_entry_ext::CoinEntryExt; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_encoding::hex::DecodeHex; use tw_ethereum::entry::EthereumEntry; #[test] fn test_sign_json() { + let coin = TestCoinContext::default(); + let input_json = r#"{"chainId":"AQ==","gasPrice":"1pOkAA==","gasLimit":"Ugg=","toAddress":"0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1","transaction":{"transfer":{"amount":"A0i8paFgAA=="}}})"#; let private_key = "17209af590a86462395d5881e60d11c7fa7d482cfb02b5a01b93c2eeef243543" .decode_hex() .unwrap(); EthereumEntry - .sign_json(&EmptyCoinContext, input_json, private_key) + .sign_json(&coin, input_json, private_key) .expect_err("'EthEntry::sign_json' is not supported yet"); // Expected result - "f86a8084d693a400825208947d8bf18c7ce84b3e175b339c4ca93aed1dd166f1870348bca5a160008025a0fe5802b49e04c6b1705088310e133605ed8b549811a18968ad409ea02ad79f21a05bf845646fb1e1b9365f63a7fd5eb5e984094e3ed35c3bed7361aebbcbf41f10" diff --git a/rust/tw_evm/src/modules/compiler.rs b/rust/tw_evm/src/modules/compiler.rs index 4d5066874b4..23598eca7aa 100644 --- a/rust/tw_evm/src/modules/compiler.rs +++ b/rust/tw_evm/src/modules/compiler.rs @@ -64,7 +64,8 @@ impl Compiler { let SingleSignaturePubkey { signature, public_key: _, - } = SingleSignaturePubkey::::from_sign_pubkey_list(signatures, public_keys)?; + } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; + let signature = secp256k1::Signature::from_bytes(&signature)?; let chain_id = U256::from_big_endian_slice(&input.chain_id)?; diff --git a/rust/tw_evm/tests/message_signer.rs b/rust/tw_evm/tests/message_signer.rs index 7be25764c2a..2f3b8327d92 100644 --- a/rust/tw_evm/tests/message_signer.rs +++ b/rust/tw_evm/tests/message_signer.rs @@ -6,7 +6,7 @@ use tw_coin_entry::error::SigningErrorType; use tw_coin_entry::modules::message_signer::MessageSigner; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_encoding::hex::{DecodeHex, ToHex}; use tw_evm::modules::message_signer::EthMessageSigner; use tw_keypair::ecdsa::secp256k1; @@ -30,6 +30,8 @@ struct SignVerifyTestInput { } fn test_message_signer_sign_verify(test_input: SignVerifyTestInput) { + let coin = TestCoinContext::default(); + let private_key = test_input.private_key.decode_hex().unwrap(); let chain_id = test_input .chain_id @@ -42,7 +44,7 @@ fn test_message_signer_sign_verify(test_input: SignVerifyTestInput) { ..Proto::MessageSigningInput::default() }; - let output = EthMessageSigner.sign_message(&EmptyCoinContext, signing_input); + let output = EthMessageSigner.sign_message(&coin, signing_input); assert_eq!(output.error, SigningErrorType::OK); assert!(output.error_message.is_empty()); assert_eq!(output.signature, test_input.signature); @@ -57,7 +59,7 @@ fn test_message_signer_sign_verify(test_input: SignVerifyTestInput) { signature: test_input.signature.into(), }; assert!( - EthMessageSigner.verify_message(&EmptyCoinContext, verifying_input), + EthMessageSigner.verify_message(&coin, verifying_input), "!verify_message: {}", test_input.signature ); @@ -72,6 +74,8 @@ struct SignErrorTestInput { } fn test_message_signer_sign_err(test_input: SignErrorTestInput) { + let coin = TestCoinContext::default(); + let private_key = test_input.private_key.decode_hex().unwrap(); let signing_input = Proto::MessageSigningInput { private_key: private_key.into(), @@ -83,7 +87,7 @@ fn test_message_signer_sign_err(test_input: SignErrorTestInput) { ..Proto::MessageSigningInput::default() }; - let output = EthMessageSigner.sign_message(&EmptyCoinContext, signing_input); + let output = EthMessageSigner.sign_message(&coin, signing_input); assert_eq!(output.error, test_input.error); } @@ -95,6 +99,8 @@ struct PreimageTestInput { } fn test_message_signer_preimage_hashes(test_input: PreimageTestInput) { + let coin = TestCoinContext::default(); + let signing_input = Proto::MessageSigningInput { message: test_input.msg.into(), message_type: test_input.msg_type, @@ -104,7 +110,7 @@ fn test_message_signer_preimage_hashes(test_input: PreimageTestInput) { ..Proto::MessageSigningInput::default() }; - let output = EthMessageSigner.message_preimage_hashes(&EmptyCoinContext, signing_input); + let output = EthMessageSigner.message_preimage_hashes(&coin, signing_input); assert_eq!(output.error, SigningErrorType::OK); assert!(output.error_message.is_empty()); assert_eq!(output.data_hash.to_hex(), test_input.data_hash); @@ -175,13 +181,15 @@ fn test_message_signer_hash_with_custom_array() { #[test] fn test_message_signer_hash_unequal_array_len() { + let coin = TestCoinContext::default(); + let signing_input = Proto::MessageSigningInput { message: EIP712_UNEQUAL_ARRAY_LEN.into(), message_type: Proto::MessageType::MessageType_typed_eip155, ..Proto::MessageSigningInput::default() }; - let output = EthMessageSigner.message_preimage_hashes(&EmptyCoinContext, signing_input); + let output = EthMessageSigner.message_preimage_hashes(&coin, signing_input); assert_eq!(output.error, SigningErrorType::Error_invalid_params); } diff --git a/rust/tw_hash/src/hasher.rs b/rust/tw_hash/src/hasher.rs new file mode 100644 index 00000000000..086c9915f0c --- /dev/null +++ b/rust/tw_hash/src/hasher.rs @@ -0,0 +1,40 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ripemd::ripemd_160; +use crate::sha2::sha256; +use crate::sha3::keccak256; +use serde::Deserialize; +use tw_memory::Data; + +// keccak256 + +/// Enum selector for the supported hash functions. +/// Add hash types if necessary. For example, when add a new hasher to `registry.json`. +#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] +pub enum Hasher { + #[serde(rename = "sha256")] + Sha256, + #[serde(rename = "keccak256")] + Keccak256, + /// SHA256 hash of the SHA256 hash + #[serde(rename = "sha256d")] + Sha256d, + /// ripemd hash of the SHA256 hash + #[serde(rename = "sha256ripemd")] + Sha256ripemd, +} + +impl Hasher { + pub fn hash(&self, data: &[u8]) -> Data { + match self { + Hasher::Sha256 => sha256(data), + Hasher::Keccak256 => keccak256(data), + Hasher::Sha256d => sha256(&sha256(data)), + Hasher::Sha256ripemd => ripemd_160(&sha256(data)), + } + } +} diff --git a/rust/tw_hash/src/lib.rs b/rust/tw_hash/src/lib.rs index f7cfe16d911..8020e6cde19 100644 --- a/rust/tw_hash/src/lib.rs +++ b/rust/tw_hash/src/lib.rs @@ -9,6 +9,7 @@ pub mod blake2; pub mod crc32; pub mod ffi; pub mod groestl; +pub mod hasher; pub mod hmac; pub mod ripemd; pub mod sha1; diff --git a/rust/tw_internet_computer/Cargo.toml b/rust/tw_internet_computer/Cargo.toml index d774d4946ae..7fd375e1071 100644 --- a/rust/tw_internet_computer/Cargo.toml +++ b/rust/tw_internet_computer/Cargo.toml @@ -3,8 +3,6 @@ name = "tw_internet_computer" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] quick-protobuf = "0.8.1" serde = { version = "1.0.136", features = ["derive"] } diff --git a/rust/tw_internet_computer/src/entry.rs b/rust/tw_internet_computer/src/entry.rs index a9542fbb8ea..6fb47ba161c 100644 --- a/rust/tw_internet_computer/src/entry.rs +++ b/rust/tw_internet_computer/src/entry.rs @@ -53,6 +53,15 @@ impl CoinEntry for InternetComputerEntry { Self::Address::from_str(address) } + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Self::Address::from_str(address) + } + #[inline] fn derive_address( &self, diff --git a/rust/tw_keypair/src/traits.rs b/rust/tw_keypair/src/traits.rs index fa9216daa91..7b32076f7fe 100644 --- a/rust/tw_keypair/src/traits.rs +++ b/rust/tw_keypair/src/traits.rs @@ -5,7 +5,7 @@ // file LICENSE at the root of the source code distribution tree. use crate::KeyPairResult; -use tw_misc::traits::{ToBytesVec, ToBytesZeroizing}; +use tw_misc::traits::{FromSlice, ToBytesVec, ToBytesZeroizing}; pub trait KeyPairTrait: FromSlice + SigningKeyTrait + VerifyingKeyTrait { type Private: FromSlice + ToBytesZeroizing; @@ -33,7 +33,3 @@ pub trait VerifyingKeyTrait { /// Verifies if the given `hash` was signed using the private key. fn verify(&self, signature: Self::VerifySignature, message: Self::SigningMessage) -> bool; } - -pub trait FromSlice: for<'a> TryFrom<&'a [u8]> {} - -impl FromSlice for T where for<'a> T: TryFrom<&'a [u8]> {} diff --git a/rust/tw_keypair/src/tw/public.rs b/rust/tw_keypair/src/tw/public.rs index 8b527050944..754ed022870 100644 --- a/rust/tw_keypair/src/tw/public.rs +++ b/rust/tw_keypair/src/tw/public.rs @@ -142,4 +142,19 @@ impl PublicKey { _ => None, } } + + /// Returns a public key type. + pub fn public_key_type(&self) -> PublicKeyType { + match self { + PublicKey::Secp256k1(_) => PublicKeyType::Secp256k1, + PublicKey::Secp256k1Extended(_) => PublicKeyType::Secp256k1Extended, + PublicKey::Nist256p1(_) => PublicKeyType::Nist256p1, + PublicKey::Nist256p1Extended(_) => PublicKeyType::Nist256p1Extended, + PublicKey::Ed25519(_) => PublicKeyType::Ed25519, + PublicKey::Ed25519Blake2b(_) => PublicKeyType::Ed25519Blake2b, + PublicKey::Curve25519Waves(_) => PublicKeyType::Curve25519Waves, + PublicKey::Ed25519ExtendedCardano(_) => PublicKeyType::Ed25519ExtendedCardano, + PublicKey::Starkex(_) => PublicKeyType::Starkex, + } + } } diff --git a/rust/tw_misc/src/lib.rs b/rust/tw_misc/src/lib.rs index 314dedda6dc..a6017087f76 100644 --- a/rust/tw_misc/src/lib.rs +++ b/rust/tw_misc/src/lib.rs @@ -1,3 +1,9 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + pub mod macros; #[cfg(feature = "test-utils")] pub mod test_utils; diff --git a/rust/tw_misc/src/traits.rs b/rust/tw_misc/src/traits.rs index 1b8900eb019..36fb534853a 100644 --- a/rust/tw_misc/src/traits.rs +++ b/rust/tw_misc/src/traits.rs @@ -38,3 +38,7 @@ impl IntoOption for Option { self } } + +pub trait FromSlice: for<'a> TryFrom<&'a [u8]> {} + +impl FromSlice for T where for<'a> T: TryFrom<&'a [u8]> {} diff --git a/rust/tw_proto/src/common/google/mod.rs b/rust/tw_proto/src/common/google/mod.rs new file mode 100644 index 00000000000..a11a0995e85 --- /dev/null +++ b/rust/tw_proto/src/common/google/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod protobuf; diff --git a/rust/tw_proto/src/common/google/protobuf/any.proto b/rust/tw_proto/src/common/google/protobuf/any.proto new file mode 100644 index 00000000000..c7aa5a0baa1 --- /dev/null +++ b/rust/tw_proto/src/common/google/protobuf/any.proto @@ -0,0 +1,169 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Source (any): https://github.com/protocolbuffers/protobuf/blob/8bf4fe924a924e0ee290bf883977c108fc12f28d/src/google/protobuf/any.proto +// To recompile the file use the following command inside `wallet-core` directory: +// ``` +// cargo install pb-rs +// pb-rs --dont_use_cow --single-mod --output_directory rust/tw_proto/common_proto/google/protobuf/ rust/tw_proto/common_proto/google/protobuf/any.proto +// ``` + +syntax = "proto3"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/known/anypb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "AnyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// `Any` contains an arbitrary serialized protocol buffer message along with a +// URL that describes the type of the serialized message. +// +// Protobuf library provides support to pack/unpack Any values in the form +// of utility functions or additional generated methods of the Any type. +// +// Example 1: Pack and unpack a message in C++. +// +// Foo foo = ...; +// Any any; +// any.PackFrom(foo); +// ... +// if (any.UnpackTo(&foo)) { +// ... +// } +// +// Example 2: Pack and unpack a message in Java. +// +// Foo foo = ...; +// Any any = Any.pack(foo); +// ... +// if (any.is(Foo.class)) { +// foo = any.unpack(Foo.class); +// } +// // or ... +// if (any.isSameTypeAs(Foo.getDefaultInstance())) { +// foo = any.unpack(Foo.getDefaultInstance()); +// } +// +// Example 3: Pack and unpack a message in Python. +// +// foo = Foo(...) +// any = Any() +// any.Pack(foo) +// ... +// if any.Is(Foo.DESCRIPTOR): +// any.Unpack(foo) +// ... +// +// Example 4: Pack and unpack a message in Go +// +// foo := &pb.Foo{...} +// any, err := anypb.New(foo) +// if err != nil { +// ... +// } +// ... +// foo := &pb.Foo{} +// if err := any.UnmarshalTo(foo); err != nil { +// ... +// } +// +// The pack methods provided by protobuf library will by default use +// 'type.googleapis.com/full.type.name' as the type URL and the unpack +// methods only use the fully qualified type name after the last '/' +// in the type URL, for example "foo.bar.com/x/y.z" will yield type +// name "y.z". +// +// JSON +// ==== +// The JSON representation of an `Any` value uses the regular +// representation of the deserialized, embedded message, with an +// additional field `@type` which contains the type URL. Example: +// +// package google.profile; +// message Person { +// string first_name = 1; +// string last_name = 2; +// } +// +// { +// "@type": "type.googleapis.com/google.profile.Person", +// "firstName": , +// "lastName": +// } +// +// If the embedded message type is well-known and has a custom JSON +// representation, that representation will be embedded adding a field +// `value` which holds the custom JSON in addition to the `@type` +// field. Example (for message [google.protobuf.Duration][]): +// +// { +// "@type": "type.googleapis.com/google.protobuf.Duration", +// "value": "1.212s" +// } +// +message Any { + // A URL/resource name that uniquely identifies the type of the serialized + // protocol buffer message. This string must contain at least + // one "/" character. The last segment of the URL's path must represent + // the fully qualified name of the type (as in + // `path/google.protobuf.Duration`). The name should be in a canonical form + // (e.g., leading "." is not accepted). + // + // In practice, teams usually precompile into the binary all types that they + // expect it to use in the context of Any. However, for URLs which use the + // scheme `http`, `https`, or no scheme, one can optionally set up a type + // server that maps type URLs to message definitions as follows: + // + // * If no scheme is provided, `https` is assumed. + // * An HTTP GET on the URL must yield a [google.protobuf.Type][] + // value in binary format, or produce an error. + // * Applications are allowed to cache lookup results based on the + // URL, or have them precompiled into a binary to avoid any + // lookup. Therefore, binary compatibility needs to be preserved + // on changes to types. (Use versioned type names to manage + // breaking changes.) + // + // Note: this functionality is not currently available in the official + // protobuf release, and it is not used for type URLs beginning with + // type.googleapis.com. As of May 2023, there are no widely used type server + // implementations and no plans to implement one. + // + // Schemes other than `http`, `https` (or the empty scheme) might be + // used with implementation specific semantics. + // + string type_url = 1; + + // Must be a valid serialized protocol buffer of the above specified type. + bytes value = 2; +} diff --git a/rust/tw_proto/src/common/google/protobuf/any.rs b/rust/tw_proto/src/common/google/protobuf/any.rs new file mode 100644 index 00000000000..743b9b93b94 --- /dev/null +++ b/rust/tw_proto/src/common/google/protobuf/any.rs @@ -0,0 +1,51 @@ +// Automatically generated rust module for 'any.proto' file + +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(unused_imports)] +#![allow(unknown_lints)] +#![allow(clippy::all)] +#![cfg_attr(rustfmt, rustfmt_skip)] + + +use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result}; +use quick_protobuf::sizeofs::*; +use super::*; + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct Any { + pub type_url: String, + pub value: Vec, +} + +impl<'a> MessageRead<'a> for Any { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(10) => msg.type_url = r.read_string(bytes)?.to_owned(), + Ok(18) => msg.value = r.read_bytes(bytes)?.to_owned(), + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for Any { + fn get_size(&self) -> usize { + 0 + + if self.type_url == String::default() { 0 } else { 1 + sizeof_len((&self.type_url).len()) } + + if self.value.is_empty() { 0 } else { 1 + sizeof_len((&self.value).len()) } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if self.type_url != String::default() { w.write_with_tag(10, |w| w.write_string(&**&self.type_url))?; } + if !self.value.is_empty() { w.write_with_tag(18, |w| w.write_bytes(&**&self.value))?; } + Ok(()) + } +} + diff --git a/rust/tw_proto/src/common/google/protobuf/mod.rs b/rust/tw_proto/src/common/google/protobuf/mod.rs new file mode 100644 index 00000000000..3f4d90a7f08 --- /dev/null +++ b/rust/tw_proto/src/common/google/protobuf/mod.rs @@ -0,0 +1,11 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod any; +mod timestamp; + +pub use any::*; +pub use timestamp::*; diff --git a/rust/tw_proto/src/common/google/protobuf/timestamp.proto b/rust/tw_proto/src/common/google/protobuf/timestamp.proto new file mode 100644 index 00000000000..353b1632497 --- /dev/null +++ b/rust/tw_proto/src/common/google/protobuf/timestamp.proto @@ -0,0 +1,151 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Source: https://github.com/protocolbuffers/protobuf/blob/538a8e9a0d90b0bd8aea7b10f8e17ba76585b2e8/src/google/protobuf/timestamp.proto +// To recompile the file use the following command inside `wallet-core` directory: +// ``` +// cargo install pb-rs +// pb-rs --dont_use_cow --single-mod --output_directory rust/tw_proto/common_proto/google/protobuf/ rust/tw_proto/common_proto/google/protobuf/any.proto +// ``` + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/timestamppb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TimestampProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// A Timestamp represents a point in time independent of any time zone or local +// calendar, encoded as a count of seconds and fractions of seconds at +// nanosecond resolution. The count is relative to an epoch at UTC midnight on +// January 1, 1970, in the proleptic Gregorian calendar which extends the +// Gregorian calendar backwards to year one. +// +// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap +// second table is needed for interpretation, using a [24-hour linear +// smear](https://developers.google.com/time/smear). +// +// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By +// restricting to that range, we ensure that we can convert to and from [RFC +// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. +// +// # Examples +// +// Example 1: Compute Timestamp from POSIX `time()`. +// +// Timestamp timestamp; +// timestamp.set_seconds(time(NULL)); +// timestamp.set_nanos(0); +// +// Example 2: Compute Timestamp from POSIX `gettimeofday()`. +// +// struct timeval tv; +// gettimeofday(&tv, NULL); +// +// Timestamp timestamp; +// timestamp.set_seconds(tv.tv_sec); +// timestamp.set_nanos(tv.tv_usec * 1000); +// +// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. +// +// FILETIME ft; +// GetSystemTimeAsFileTime(&ft); +// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; +// +// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z +// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. +// Timestamp timestamp; +// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); +// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); +// +// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. +// +// long millis = System.currentTimeMillis(); +// +// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) +// .setNanos((int) ((millis % 1000) * 1000000)).build(); +// +// Example 5: Compute Timestamp from Java `Instant.now()`. +// +// Instant now = Instant.now(); +// +// Timestamp timestamp = +// Timestamp.newBuilder().setSeconds(now.getEpochSecond()) +// .setNanos(now.getNano()).build(); +// +// Example 6: Compute Timestamp from current time in Python. +// +// timestamp = Timestamp() +// timestamp.GetCurrentTime() +// +// # JSON Mapping +// +// In JSON format, the Timestamp type is encoded as a string in the +// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the +// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" +// where {year} is always expressed using four digits while {month}, {day}, +// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional +// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), +// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone +// is required. A proto3 JSON serializer should always use UTC (as indicated by +// "Z") when printing the Timestamp type and a proto3 JSON parser should be +// able to accept both UTC and other timezones (as indicated by an offset). +// +// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past +// 01:30 UTC on January 15, 2017. +// +// In JavaScript, one can convert a Date object to this format using the +// standard +// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) +// method. In Python, a standard `datetime.datetime` object can be converted +// to this format using +// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with +// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use +// the Joda Time's [`ISODateTimeFormat.dateTime()`]( +// http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() +// ) to obtain a formatter capable of generating timestamps in this format. +// +message Timestamp { + // Represents seconds of UTC time since Unix epoch + // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + // 9999-12-31T23:59:59Z inclusive. + int64 seconds = 1; + + // Non-negative fractions of a second at nanosecond resolution. Negative + // second values with fractions must still have non-negative nanos values + // that count forward in time. Must be from 0 to 999,999,999 + // inclusive. + int32 nanos = 2; +} diff --git a/rust/tw_proto/src/common/google/protobuf/timestamp.rs b/rust/tw_proto/src/common/google/protobuf/timestamp.rs new file mode 100644 index 00000000000..2e073dbe982 --- /dev/null +++ b/rust/tw_proto/src/common/google/protobuf/timestamp.rs @@ -0,0 +1,51 @@ +// Automatically generated rust module for 'timestamp.proto' file + +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(unused_imports)] +#![allow(unknown_lints)] +#![allow(clippy::all)] +#![cfg_attr(rustfmt, rustfmt_skip)] + + +use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result}; +use quick_protobuf::sizeofs::*; +use super::*; + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct Timestamp { + pub seconds: i64, + pub nanos: i32, +} + +impl<'a> MessageRead<'a> for Timestamp { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(8) => msg.seconds = r.read_int64(bytes)?, + Ok(16) => msg.nanos = r.read_int32(bytes)?, + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for Timestamp { + fn get_size(&self) -> usize { + 0 + + if self.seconds == 0i64 { 0 } else { 1 + sizeof_varint(*(&self.seconds) as u64) } + + if self.nanos == 0i32 { 0 } else { 1 + sizeof_varint(*(&self.nanos) as u64) } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if self.seconds != 0i64 { w.write_with_tag(8, |w| w.write_int64(*&self.seconds))?; } + if self.nanos != 0i32 { w.write_with_tag(16, |w| w.write_int32(*&self.nanos))?; } + Ok(()) + } +} + diff --git a/rust/tw_proto/src/common/mod.rs b/rust/tw_proto/src/common/mod.rs new file mode 100644 index 00000000000..972a22f676f --- /dev/null +++ b/rust/tw_proto/src/common/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod google; diff --git a/rust/tw_proto/src/lib.rs b/rust/tw_proto/src/lib.rs index cf592f95037..45eb41fac60 100644 --- a/rust/tw_proto/src/lib.rs +++ b/rust/tw_proto/src/lib.rs @@ -4,7 +4,11 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -use quick_protobuf::{BytesReader, Writer}; +use quick_protobuf::{BytesReader, MessageInfo, Writer}; + +#[allow(non_snake_case)] +#[rustfmt::skip] +mod common; #[allow(non_snake_case)] #[rustfmt::skip] @@ -12,6 +16,7 @@ mod generated { include!(concat!(env!("OUT_DIR"), "/proto/mod.rs")); } +pub use common::google; pub use generated::TW::*; pub use quick_protobuf::{ deserialize_from_slice as deserialize_prefixed, serialize_into_vec as serialize_prefixed, @@ -38,6 +43,15 @@ pub fn deserialize<'a, T: MessageRead<'a>>(data: &'a [u8]) -> ProtoResult { T::from_reader(&mut reader, data) } +pub fn to_any(message: &T) -> google::protobuf::Any +where + T: MessageInfo + MessageWrite, +{ + let value = serialize(message).expect("Protobuf serialization should never fail"); + let type_url = format!("/{}", T::PATH); + google::protobuf::Any { type_url, value } +} + /// There is no way to create an instance of the `NoMessage` enum as it doesn't has variants. pub enum NoMessage {} diff --git a/rust/tw_ronin/src/entry.rs b/rust/tw_ronin/src/entry.rs index 976a08e0eca..36bb8fb96b2 100644 --- a/rust/tw_ronin/src/entry.rs +++ b/rust/tw_ronin/src/entry.rs @@ -46,6 +46,15 @@ impl CoinEntry for RoninEntry { Address::from_str(address) } + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + fn derive_address( &self, _coin: &dyn CoinContext, diff --git a/rust/tw_ronin/tests/compiler.rs b/rust/tw_ronin/tests/compiler.rs index 53972303b5c..5c99e67b419 100644 --- a/rust/tw_ronin/tests/compiler.rs +++ b/rust/tw_ronin/tests/compiler.rs @@ -7,7 +7,7 @@ use std::borrow::Cow; use tw_coin_entry::coin_entry_ext::CoinEntryExt; use tw_coin_entry::error::SigningErrorType; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_encoding::hex::{DecodeHex, ToHex}; use tw_keypair::ecdsa::secp256k1; use tw_keypair::tw; @@ -19,6 +19,8 @@ use tw_ronin::entry::RoninEntry; #[test] fn test_ronin_preimage_hashes_and_compile() { + let coin = TestCoinContext::default(); + let transfer = Proto::mod_Transaction::Transfer { amount: U256::encode_be_compact(1_000_000_000_000_000_000), data: Cow::default(), @@ -38,7 +40,7 @@ fn test_ronin_preimage_hashes_and_compile() { let input_data = serialize(&input).unwrap(); let res = RoninEntry - .preimage_hashes(&EmptyCoinContext, &input_data) + .preimage_hashes(&coin, &input_data) .expect("!preimage_hashes"); let preimage: CompilerProto::PreSigningOutput = deserialize(res.as_slice()).expect("Coin entry returned an invalid output"); @@ -61,7 +63,7 @@ fn test_ronin_preimage_hashes_and_compile() { // Step 3: Compile transaction info let output_data = RoninEntry .compile( - &EmptyCoinContext, + &coin, &input_data, vec![signature], vec![public_key.to_bytes()], diff --git a/rust/tw_ronin/tests/signer.rs b/rust/tw_ronin/tests/signer.rs index 32dd2cb5338..d68396a34b4 100644 --- a/rust/tw_ronin/tests/signer.rs +++ b/rust/tw_ronin/tests/signer.rs @@ -7,7 +7,7 @@ use std::borrow::Cow; use tw_coin_entry::coin_entry_ext::CoinEntryExt; use tw_coin_entry::error::SigningErrorType; -use tw_coin_entry::test_utils::empty_context::EmptyCoinContext; +use tw_coin_entry::test_utils::test_context::TestCoinContext; use tw_encoding::hex::{DecodeHex, ToHex}; use tw_number::U256; use tw_proto::Ethereum::Proto; @@ -17,6 +17,8 @@ use tw_ronin::entry::RoninEntry; /// https://explorer.roninchain.com/tx/0xf13a2c4421700f8782ca73eaf16bb8baf82bcf093e23570a1ff062cdd8dbf6c3 #[test] fn test_ronin_signing() { + let coin = TestCoinContext::default(); + let private = "0x4646464646464646464646464646464646464646464646464646464646464646" .decode_hex() .unwrap(); @@ -40,9 +42,7 @@ fn test_ronin_signing() { }; let input_data = serialize(&input).unwrap(); - let output_data = RoninEntry - .sign(&EmptyCoinContext, &input_data) - .expect("!sign"); + let output_data = RoninEntry.sign(&coin, &input_data).expect("!sign"); let output: Proto::SigningOutput = deserialize(&output_data).expect("Coin entry returned an invalid output"); @@ -55,13 +55,15 @@ fn test_ronin_signing() { #[test] fn test_sign_json() { + let coin = TestCoinContext::default(); + let input_json = r#"{"chainId":"B+Q=","nonce":"AA==","gasPrice":"O5rKAA==","gasLimit":"Ugg=","toAddress":"ronin:c36edf48e21cf395b206352a1819de658fd7f988","privateKey":"RkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkY=","transaction":{"transfer":{"amount":"BDff"}}}"#; let private_key = "0x4646464646464646464646464646464646464646464646464646464646464646" .decode_hex() .unwrap(); RoninEntry - .sign_json(&EmptyCoinContext, input_json, private_key) + .sign_json(&coin, input_json, private_key) .expect_err("'EthEntry::sign_json' is not supported yet"); // Expected result - "f86880843b9aca0082520894c36edf48e21cf395b206352a1819de658fd7f988830437df80820feca0442aa06b0d0465bfecf84b28e2ce614a32a1ccc12735dc03a5799517d6659d7aa004e1bf2efa30743f1b6d49dbec2671e9fb5ead1e7da15e352ca1df6fb86a8ba7" diff --git a/rust/tw_utxo/Cargo.toml b/rust/tw_utxo/Cargo.toml index 5631960638f..738f2932771 100644 --- a/rust/tw_utxo/Cargo.toml +++ b/rust/tw_utxo/Cargo.toml @@ -3,8 +3,6 @@ name = "tw_utxo" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] tw_coin_entry = { path = "../tw_coin_entry" } tw_keypair = { path = "../tw_keypair" } diff --git a/src/Aptos/Entry.cpp b/src/Aptos/Entry.cpp deleted file mode 100644 index a0d362910ac..00000000000 --- a/src/Aptos/Entry.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Entry.h" - -namespace TW::Aptos { - -bool Entry::validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const { - return validateAddressRust(coin, address, addressPrefix); -} - -std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const { - return deriveAddressRust(coin, publicKey, derivation, addressPrefix); -} - -void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signRust(dataIn, coin, dataOut); -} - -Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { - return preImageHashesRust(coin, txInputData); -} - -void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { - compileRust(coin, txInputData, signatures, publicKeys, dataOut); -} - -} // namespace TW::Aptos diff --git a/src/Aptos/Entry.h b/src/Aptos/Entry.h index 2219499f5ac..750c5dba2a9 100644 --- a/src/Aptos/Entry.h +++ b/src/Aptos/Entry.h @@ -6,19 +6,13 @@ #pragma once -#include "../CoinEntry.h" +#include "rust/RustCoinEntry.h" namespace TW::Aptos { /// Entry point for implementation of Aptos coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry final : public CoinEntry { -public: - bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; - Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; - void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; +class Entry final : public Rust::RustCoinEntry { }; } // namespace TW::Aptos diff --git a/src/Coin.cpp b/src/Coin.cpp index 29b91e2e9a7..943324470a7 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -66,6 +66,8 @@ #include "Sui/Entry.h" #include "Greenfield/Entry.h" #include "InternetComputer/Entry.h" +#include "NativeEvmos/Entry.h" +#include "NativeInjective/Entry.h" // end_of_coin_includes_marker_do_not_modify using namespace TW; @@ -123,6 +125,8 @@ TheOpenNetwork::Entry tonDP; Sui::Entry SuiDP; Greenfield::Entry GreenfieldDP; InternetComputer::Entry InternetComputerDP; +NativeEvmos::Entry NativeEvmosDP; +NativeInjective::Entry NativeInjectiveDP; // end_of_coin_dipatcher_declarations_marker_do_not_modify CoinEntry* coinDispatcher(TWCoinType coinType) { @@ -182,6 +186,8 @@ CoinEntry* coinDispatcher(TWCoinType coinType) { case TWBlockchainSui: entry = &SuiDP; break; case TWBlockchainGreenfield: entry = &GreenfieldDP; break; case TWBlockchainInternetComputer: entry = &InternetComputerDP; break; + case TWBlockchainNativeEvmos: entry = &NativeEvmosDP; break; + case TWBlockchainNativeInjective: entry = &NativeInjectiveDP; break; // end_of_coin_dipatcher_switch_marker_do_not_modify default: entry = nullptr; break; diff --git a/src/CoinEntry.cpp b/src/CoinEntry.cpp index 56ba53c3f95..f8ce744377b 100644 --- a/src/CoinEntry.cpp +++ b/src/CoinEntry.cpp @@ -32,101 +32,4 @@ byte getFromPrefixPkhOrDefault(const PrefixVariant &prefix, TWCoinType coin) { return TW::p2pkhPrefix(coin); } -bool validateAddressRust(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) { - if (!std::holds_alternative(addressPrefix)) { - throw std::invalid_argument("`Rust::tw_any_address_is_valid_bech32`, `Rust::tw_any_address_is_valid_ss58` are not supported yet"); - } - - Rust::TWStringWrapper addressStr = address; - return Rust::tw_any_address_is_valid(addressStr.get(), static_cast(coin)); -} - -std::string normalizeAddressRust(TWCoinType coin, const std::string& address) { - Rust::TWStringWrapper addressStr = address; - - auto anyAddress = Rust::wrapTWAnyAddress( - Rust::tw_any_address_create_with_string(addressStr.get(), static_cast(coin))); - if (!anyAddress) { - return {}; - } - - Rust::TWStringWrapper normalized = Rust::tw_any_address_description(anyAddress.get()); - return normalized.toStringOrDefault(); -} - -std::string deriveAddressRust(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) { - if (!std::holds_alternative(addressPrefix)) { - throw std::invalid_argument("`Rust::tw_any_address_create_bech32_with_public_key`, " - "`Rust::tw_any_address_create_ss58_with_public_key`, " - "`Rust::tw_any_address_create_with_public_key_filecoin_address_type` are not supported yet"); - } - - auto *twPublicKeyRaw = Rust::tw_public_key_create_with_data(publicKey.bytes.data(), - publicKey.bytes.size(), - static_cast(publicKey.type)); - auto twPublicKey = Rust::wrapTWPublicKey(twPublicKeyRaw); - if (!twPublicKey) { - return {}; - } - - auto *anyAddressRaw = Rust::tw_any_address_create_with_public_key_derivation(twPublicKey.get(), - static_cast(coin), - static_cast(derivation)); - auto anyAddress = Rust::wrapTWAnyAddress(anyAddressRaw); - if (!anyAddress) { - return {}; - } - - Rust::TWStringWrapper derivedAddress = Rust::tw_any_address_description(anyAddress.get()); - return derivedAddress.toStringOrDefault(); -} - -Data addressToDataRust(TWCoinType coin, const std::string& address) { - Rust::TWStringWrapper addressStr = address; - - auto anyAddress = Rust::wrapTWAnyAddress( - Rust::tw_any_address_create_with_string(addressStr.get(), static_cast(coin))); - if (!anyAddress) { - return {}; - } - - Rust::TWDataWrapper data = Rust::tw_any_address_data(anyAddress.get()); - return data.toDataOrDefault(); -} - -void signRust(const Data& dataIn, TWCoinType coin, Data& dataOut) { - Rust::TWDataWrapper input = Rust::tw_data_create_with_bytes(dataIn.data(), dataIn.size()); - Rust::TWDataWrapper output = Rust::tw_any_signer_sign(input.get(), static_cast(coin)); - - dataOut = output.toDataOrDefault(); -} - -Data preImageHashesRust(TWCoinType coin, const Data& dataIn) { - Rust::TWDataWrapper input = dataIn; - Rust::TWDataWrapper output = Rust::tw_transaction_compiler_pre_image_hashes(static_cast(coin), input.get()); - - return output.toDataOrDefault(); -} - -void compileRust( - TWCoinType coin, - const Data& dataIn, - const std::vector& signatures, - const std::vector& publicKeys, - Data& dataOut -) { - Rust::TWDataWrapper input = dataIn; - - std::vector publicKeysData; - for (const auto& publicKey : publicKeys) { - publicKeysData.push_back(publicKey.bytes); - } - - Rust::TWDataVectorWrapper signaturesVec = signatures; - Rust::TWDataVectorWrapper publicKeysVec = publicKeysData; - - Rust::TWDataWrapper output = Rust::tw_transaction_compiler_compile(static_cast(coin), input.get(), signaturesVec.get(), publicKeysVec.get()); - dataOut = output.toDataOrDefault(); -} - } // namespace TW diff --git a/src/CoinEntry.h b/src/CoinEntry.h index fec4bd8f265..c8b0b81bbaf 100644 --- a/src/CoinEntry.h +++ b/src/CoinEntry.h @@ -83,33 +83,6 @@ void signTemplate(const Data& dataIn, Data& dataOut) { dataOut.insert(dataOut.end(), serializedOut.begin(), serializedOut.end()); } -// In each coin's Entry.cpp that is implemented in Rust, this function calls `tw_any_address_is_valid*`. -bool validateAddressRust(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix); - -// In each coin's Entry.cpp that is implemented in Rust, this function calls `tw_any_address_create_with_string*`. -std::string normalizeAddressRust(TWCoinType coin, const std::string& address); - -// In each coin's Entry.cpp that is implemented in Rust, this function calls `tw_any_address_create_with_public_key*`. -std::string deriveAddressRust(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix); - -// In each coin's Entry.cpp that is implemented in Rust, this function calls `tw_any_address_create_with_string*`. -Data addressToDataRust(TWCoinType coin, const std::string& address); - -// In each coin's Entry.cpp that is implemented in Rust, this function calls `tw_any_signer_sign`. -// Note: use output parameter to avoid unneeded copies -void signRust(const Data& dataIn, TWCoinType coin, Data& dataOut); - -// In each coin's Entry.cpp that is implemented in Rust, this function calls `tw_transaction_compiler_pre_image_hashes`. -Data preImageHashesRust(TWCoinType coin, const Data& dataIn); - -// In each coin's Entry.cpp that is implemented in Rust, this function calls `tw_transaction_compiler_compile`. -// Note: use output parameter to avoid unneeded copies -void compileRust(TWCoinType coin, - const Data& dataIn, - const std::vector& signatures, - const std::vector& publicKeys, - Data& dataOut); - // Note: use output parameter to avoid unneeded copies template void planTemplate(const Data& dataIn, Data& dataOut) { diff --git a/src/Cosmos/Entry.cpp b/src/Cosmos/Entry.cpp index 01b06421f25..2886108b0d9 100644 --- a/src/Cosmos/Entry.cpp +++ b/src/Cosmos/Entry.cpp @@ -5,72 +5,35 @@ // file LICENSE at the root of the source code distribution tree. #include "Entry.h" -#include #include "Address.h" -#include "Signer.h" + +#include +#include +#include using namespace TW; using namespace std; namespace TW::Cosmos { -bool Entry::validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const { - if (auto* hrp = std::get_if(&addressPrefix); hrp) { - return Address::isValid(address, *hrp); - } - return Address::isValid(coin, address); -} +// TODO call `signRustJSON` when it's done. +string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { + auto input = Proto::SigningInput(); + google::protobuf::util::JsonStringToMessage(json, &input); + input.set_private_key(key.data(), key.size()); -std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, const PrefixVariant& addressPrefix) const { - if (std::holds_alternative(addressPrefix)) { - const std::string hrp = std::get(addressPrefix); - if (!hrp.empty()) { - return Address(hrp, publicKey, coin).string(); - } - } - return Address(coin, publicKey).string(); -} + auto inputData = data(input.SerializeAsString()); + Data dataOut; + sign(coin, inputData, dataOut); -Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { - Address addr; - if (!Address::decode(address, addr)) { - return Data(); + if (dataOut.empty()) { + return {}; } - return addr.getKeyHash(); -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - auto input = Proto::SigningInput(); - input.ParseFromArray(dataIn.data(), (int)dataIn.size()); - auto serializedOut = Signer::sign(input, coin).SerializeAsString(); - dataOut.insert(dataOut.end(), serializedOut.begin(), serializedOut.end()); -} -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { - return Signer::signJSON(json, key, coin); -} - -Data Entry::preImageHashes(TWCoinType coin, const Data& txInputData) const { - return txCompilerTemplate( - txInputData, [&coin](const auto& input, auto& output) { - auto pkVec = Data(input.public_key().begin(), input.public_key().end()); - auto preimage = Signer().signaturePreimage(input, pkVec, coin); - auto isEvmCosmosChain = [coin]() { - return coin == TWCoinTypeNativeInjective || coin == TWCoinTypeNativeEvmos || coin == TWCoinTypeNativeCanto; - }; - auto imageHash = isEvmCosmosChain() ? Hash::keccak256(preimage) : Hash::sha256(preimage); - output.set_data(preimage.data(), preimage.size()); - output.set_data_hash(imageHash.data(), imageHash.size()); - }); -} + Proto::SigningOutput output; + output.ParseFromArray(dataOut.data(), static_cast(dataOut.size())); -void Entry::compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { - dataOut = txCompilerSingleTemplate( - txInputData, signatures, publicKeys, - [coin](const auto& input, auto& output, const auto& signature, const auto& publicKey) { - auto signedTx = Signer().encodeTransaction(input, signature, publicKey, coin); - output.set_serialized(signedTx.data(), signedTx.size()); - }); + return output.json(); } } // namespace TW::Cosmos diff --git a/src/Cosmos/Entry.h b/src/Cosmos/Entry.h index 4bc3261f9c5..66b40cb1403 100644 --- a/src/Cosmos/Entry.h +++ b/src/Cosmos/Entry.h @@ -6,23 +6,17 @@ #pragma once -#include "../CoinEntry.h" +#include "rust/RustCoinEntry.h" namespace TW::Cosmos { /// Entry point for implementation of Cosmos coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry : public CoinEntry { +class Entry : public Rust::RustCoinEntry { public: - bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const final; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; - Data addressToData(TWCoinType coin, const std::string& address) const final; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + ~Entry() override = default; bool supportsJSONSigning() const final { return true; } - std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const override; - - Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; - void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const final; }; } // namespace TW::Cosmos diff --git a/src/Cosmos/JsonSerialization.cpp b/src/Cosmos/JsonSerialization.cpp deleted file mode 100644 index d08c4fa362a..00000000000 --- a/src/Cosmos/JsonSerialization.cpp +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "JsonSerialization.h" -#include "ProtobufSerialization.h" -#include "../Cosmos/Address.h" -#include "../proto/Cosmos.pb.h" -#include "Base64.h" -#include "PrivateKey.h" - -using namespace TW; - -namespace TW::Cosmos::Json { - -using json = nlohmann::json; -using string = std::string; - -const string TYPE_PREFIX_MSG_SEND = "cosmos-sdk/MsgSend"; -const string TYPE_PREFIX_MSG_DELEGATE = "cosmos-sdk/MsgDelegate"; -const string TYPE_PREFIX_MSG_UNDELEGATE = "cosmos-sdk/MsgUndelegate"; -const string TYPE_PREFIX_MSG_REDELEGATE = "cosmos-sdk/MsgBeginRedelegate"; -const string TYPE_PREFIX_MSG_SET_WITHDRAW_ADDRESS = "cosmos-sdk/MsgSetWithdrawAddress"; -const string TYPE_PREFIX_MSG_WITHDRAW_REWARD = "cosmos-sdk/MsgWithdrawDelegationReward"; -const string TYPE_PREFIX_PUBLIC_KEY = "tendermint/PubKeySecp256k1"; -const string TYPE_EVMOS_PREFIX_PUBLIC_KEY = "ethermint/PubKeyEthSecp256k1"; -const string TYPE_PREFIX_WASM_MSG_EXECUTE = "wasm/MsgExecuteContract"; - -static inline std::string coinTypeToPrefixPublicKey(TWCoinType coin) noexcept { - if (coin == TWCoinTypeNativeEvmos) { - return TYPE_EVMOS_PREFIX_PUBLIC_KEY; - } - return TYPE_PREFIX_PUBLIC_KEY; -} - -static string broadcastMode(Proto::BroadcastMode mode) { - switch (mode) { - case Proto::BroadcastMode::BLOCK: - return "block"; - case Proto::BroadcastMode::ASYNC: - return "async"; - default: return "sync"; - } -} - -static json broadcastJSON(json& j, Proto::BroadcastMode mode) { - return { - {"tx", j}, - {"mode", broadcastMode(mode)} - }; -} - -static json amountJSON(const Proto::Amount& amount) { - return { - {"amount", amount.amount()}, - {"denom", amount.denom()} - }; -} - -static json amountsJSON(const ::google::protobuf::RepeatedPtrField& amounts) { - json j = json::array(); - for (auto& amount : amounts) { - j.push_back(amountJSON(amount)); - } - return j; -} - -static json feeJSON(const Proto::Fee& fee) { - json js = json::array(); - - for (auto& amount : fee.amounts()) { - js.push_back(amountJSON(amount)); - } - - return { - {"amount", js}, - {"gas", std::to_string(fee.gas())} - }; -} - -static json messageSend(const Proto::Message_Send& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_SEND : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"amount", amountsJSON(message.amounts())}, - {"from_address", message.from_address()}, - {"to_address", message.to_address()} - }} - }; -} - -static json messageDelegate(const Proto::Message_Delegate& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_DELEGATE : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"amount", amountJSON(message.amount())}, - {"delegator_address", message.delegator_address()}, - {"validator_address", message.validator_address()} - }} - }; -} - -static json messageUndelegate(const Proto::Message_Undelegate& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_UNDELEGATE : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"amount", amountJSON(message.amount())}, - {"delegator_address", message.delegator_address()}, - {"validator_address", message.validator_address()} - }} - }; -} - -static json messageRedelegate(const Proto::Message_BeginRedelegate& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_REDELEGATE : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"amount", amountJSON(message.amount())}, - {"delegator_address", message.delegator_address()}, - {"validator_src_address", message.validator_src_address()}, - {"validator_dst_address", message.validator_dst_address()}, - }} - }; -} - -static json messageWithdrawReward(const Proto::Message_WithdrawDelegationReward& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_WITHDRAW_REWARD : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"delegator_address", message.delegator_address()}, - {"validator_address", message.validator_address()} - }} - }; -} - -static json messageSetWithdrawAddress(const Proto::Message_SetWithdrawAddress& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_SET_WITHDRAW_ADDRESS : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"delegator_address", message.delegator_address()}, - {"withdraw_address", message.withdraw_address()} - }} - }; -} - - -// This method not only support token transfer, but also support all other types of contract call. -// https://docs.terra.money/Tutorials/Smart-contracts/Manage-CW20-tokens.html#interacting-with-cw20-contract -static json messageExecuteContract(const Proto::Message_ExecuteContract& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_WASM_MSG_EXECUTE : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"sender", message.sender()}, - {"contract", message.contract()}, - {"execute_msg", message.execute_msg()}, - {"coins", amountsJSON(message.coins())} - }} - }; -} - -json messageWasmTerraTransfer(const Proto::Message_WasmTerraExecuteContractTransfer& msg) { - return { - {"type", TYPE_PREFIX_WASM_MSG_EXECUTE}, - {"value", - { - {"sender", msg.sender_address()}, - {"contract", msg.contract_address()}, - {"execute_msg", Protobuf::wasmTerraExecuteTransferPayload(msg)}, - {"coins", json::array()} // used in case you are sending native tokens along with this message - } - } - }; -} - -static json messageRawJSON(const Proto::Message_RawJSON& message) { - return { - {"type", message.type()}, - {"value", json::parse(message.value())}, - }; -} - -static json messagesJSON(const Proto::SigningInput& input) { - json j = json::array(); - for (auto& msg : input.messages()) { - if (msg.has_send_coins_message()) { - j.push_back(messageSend(msg.send_coins_message())); - } else if (msg.has_stake_message()) { - j.push_back(messageDelegate(msg.stake_message())); - } else if (msg.has_unstake_message()) { - j.push_back(messageUndelegate(msg.unstake_message())); - } else if (msg.has_withdraw_stake_reward_message()) { - j.push_back(messageWithdrawReward(msg.withdraw_stake_reward_message())); - } else if (msg.has_set_withdraw_address_message()) { - j.push_back(messageSetWithdrawAddress(msg.set_withdraw_address_message())); - } else if (msg.has_restake_message()) { - j.push_back(messageRedelegate(msg.restake_message())); - } else if (msg.has_raw_json_message()) { - j.push_back(messageRawJSON(msg.raw_json_message())); - } else if (msg.has_execute_contract_message()) { - j.push_back(messageExecuteContract(msg.execute_contract_message())); - } else if (msg.has_transfer_tokens_message()) { - assert(false); // not suppored, use protobuf serialization - return json::array(); - } else if ((msg.has_wasm_terra_execute_contract_transfer_message())) { - j.push_back(messageWasmTerraTransfer(msg.wasm_terra_execute_contract_transfer_message())); - } else if (msg.has_transfer_tokens_message() || msg.has_wasm_terra_execute_contract_generic()) { - assert(false); // not supported, use protobuf serialization - return json::array(); - } - } - return j; -} - -json signatureJSON(const Data& signature, const Data& pubkey, TWCoinType coin) { - return { - {"pub_key", { - {"type", coinTypeToPrefixPublicKey(coin)}, - {"value", Base64::encode(pubkey)} - }}, - {"signature", Base64::encode(signature)} - }; -} - -json signaturePreimageJSON(const Proto::SigningInput& input) { - return { - {"account_number", std::to_string(input.account_number())}, - {"chain_id", input.chain_id()}, - {"fee", feeJSON(input.fee())}, - {"memo", input.memo()}, - {"msgs", messagesJSON(input)}, - {"sequence", std::to_string(input.sequence())} - }; -} - - -json transactionJSON(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin) { - json tx = { - {"fee", feeJSON(input.fee())}, - {"memo", input.memo()}, - {"msg", messagesJSON(input)}, - {"signatures", json::array({ - signatureJSON(signature, Data(publicKey.bytes), coin) - })} - }; - return broadcastJSON(tx, input.mode()); -} - -json transactionJSON(const Proto::SigningInput& input, const Data& signature, TWCoinType coin) { - auto privateKey = PrivateKey(input.private_key()); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - - return transactionJSON(input, publicKey, signature, coin); -} - -std::string buildJsonTxRaw(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin) { - return transactionJSON(input, publicKey, signature, coin).dump(); -} - -} // namespace TW::Cosmos diff --git a/src/Cosmos/JsonSerialization.h b/src/Cosmos/JsonSerialization.h deleted file mode 100644 index c8458bddf6b..00000000000 --- a/src/Cosmos/JsonSerialization.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Data.h" -#include "PublicKey.h" -#include "../proto/Cosmos.pb.h" -#include -#include - -extern const std::string TYPE_PREFIX_MSG_SEND; -extern const std::string TYPE_PREFIX_MSG_TRANSFER; -extern const std::string TYPE_PREFIX_MSG_DELEGATE; -extern const std::string TYPE_PREFIX_MSG_UNDELEGATE; -extern const std::string TYPE_PREFIX_MSG_REDELEGATE; -extern const std::string TYPE_PREFIX_MSG_SET_WITHDRAW_ADDRESS; -extern const std::string TYPE_PREFIX_MSG_WITHDRAW_REWARD; -extern const std::string TYPE_PREFIX_PUBLIC_KEY; - -namespace TW::Cosmos::Json { - -using string = std::string; -using json = nlohmann::json; - -json signaturePreimageJSON(const Proto::SigningInput& input); -json transactionJSON(const Proto::SigningInput& input, const Data& signature, TWCoinType coin); -json transactionJSON(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin); -std::string buildJsonTxRaw(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin); -json signatureJSON(const Data& signature, const Data& pubkey, TWCoinType coin); - -} // namespace TW::Cosmos::json diff --git a/src/Cosmos/Protobuf/tx.proto b/src/Cosmos/Protobuf/tx.proto index b7973cda4bf..74e17ba25d7 100644 --- a/src/Cosmos/Protobuf/tx.proto +++ b/src/Cosmos/Protobuf/tx.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package cosmos; +package cosmos.tx.v1beta1; // Src: https://github.com/cosmos/cosmos-sdk/blob/master/proto/cosmos/tx/v1beta1/tx.proto diff --git a/src/Cosmos/ProtobufSerialization.cpp b/src/Cosmos/ProtobufSerialization.cpp deleted file mode 100644 index 657fe49cbca..00000000000 --- a/src/Cosmos/ProtobufSerialization.cpp +++ /dev/null @@ -1,577 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "ProtobufSerialization.h" -#include "JsonSerialization.h" -#include "../proto/Cosmos.pb.h" -#include "Protobuf/coin.pb.h" -#include "Protobuf/bank_tx.pb.h" -#include "Protobuf/cosmwasm_wasm_v1_tx.pb.h" -#include "Protobuf/distribution_tx.pb.h" -#include "Protobuf/staking_tx.pb.h" -#include "Protobuf/authz_tx.pb.h" -#include "Protobuf/tx.pb.h" -#include "Protobuf/stride_liquid_staking.pb.h" -#include "Protobuf/gov_tx.pb.h" -#include "Protobuf/crypto_secp256k1_keys.pb.h" -#include "Protobuf/terra_wasm_v1beta1_tx.pb.h" -#include "Protobuf/ibc_applications_transfer_tx.pb.h" -#include "Protobuf/thorchain_bank_tx.pb.h" -#include "Protobuf/ethermint_keys.pb.h" -#include "Protobuf/injective_keys.pb.h" - -#include "PrivateKey.h" -#include "Data.h" -#include "Hash.h" -#include "Base64.h" -#include "uint256.h" - -using namespace TW; - -namespace TW::Cosmos::Protobuf { - -namespace internal { - -// Some of the Cosmos blockchains use different public key types for address deriving and transaction signing. -// `registry.json` contains the public key required to derive an address, -// while this function prepares the given public key to use it for transaction signing/compiling. -inline PublicKey preparePublicKey(const PublicKey& publicKey, TWCoinType coin) { - return coin == TWCoinTypeNativeEvmos ? publicKey.compressed() : publicKey; -} - -} // namespace internal - -using json = nlohmann::json; -using string = std::string; -const auto ProtobufAnyNamespacePrefix = ""; // to override default 'type.googleapis.com' - -static string broadcastMode(Proto::BroadcastMode mode) { - switch (mode) { - case Proto::BroadcastMode::BLOCK: - return "BROADCAST_MODE_BLOCK"; - case Proto::BroadcastMode::ASYNC: - return "BROADCAST_MODE_ASYNC"; - default: return "BROADCAST_MODE_SYNC"; - } -} - -static json broadcastJSON(std::string data, Proto::BroadcastMode mode) { - return { - {"tx_bytes", data}, - {"mode", broadcastMode(mode)} - }; -} - -cosmos::base::v1beta1::Coin convertCoin(const Proto::Amount& amount) { - cosmos::base::v1beta1::Coin coin; - coin.set_denom(amount.denom()); - coin.set_amount(amount.amount()); - return coin; -} - -// Convert messages from external protobuf to internal protobuf -google::protobuf::Any convertMessage(const Proto::Message& msg) { - google::protobuf::Any any; - switch (msg.message_oneof_case()) { - case Proto::Message::kSendCoinsMessage: - { - assert(msg.has_send_coins_message()); - const auto& send = msg.send_coins_message(); - auto msgSend = cosmos::bank::v1beta1::MsgSend(); - msgSend.set_from_address(send.from_address()); - msgSend.set_to_address(send.to_address()); - for (auto i = 0; i < send.amounts_size(); ++i) { - *msgSend.add_amount() = convertCoin(send.amounts(i)); - } - any.PackFrom(msgSend, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kTransferTokensMessage: - { - assert(msg.has_transfer_tokens_message()); - const auto& transfer = msg.transfer_tokens_message(); - auto msgTransfer = ibc::applications::transfer::v1::MsgTransfer(); - msgTransfer.set_source_port(transfer.source_port()); - msgTransfer.set_source_channel(transfer.source_channel()); - *msgTransfer.mutable_token() = convertCoin(transfer.token()); - msgTransfer.set_sender(transfer.sender()); - msgTransfer.set_receiver(transfer.receiver()); - msgTransfer.mutable_timeout_height()->set_revision_number(transfer.timeout_height().revision_number()); - msgTransfer.mutable_timeout_height()->set_revision_height(transfer.timeout_height().revision_height()); - msgTransfer.set_timeout_timestamp(transfer.timeout_timestamp()); - any.PackFrom(msgTransfer, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kStakeMessage: - { - assert(msg.has_stake_message()); - const auto& stake = msg.stake_message(); - auto msgDelegate = cosmos::staking::v1beta1::MsgDelegate(); - msgDelegate.set_delegator_address(stake.delegator_address()); - msgDelegate.set_validator_address(stake.validator_address()); - *msgDelegate.mutable_amount() = convertCoin(stake.amount()); - any.PackFrom(msgDelegate, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kUnstakeMessage: - { - assert(msg.has_unstake_message()); - const auto& unstake = msg.unstake_message(); - auto msgUndelegate = cosmos::staking::v1beta1::MsgUndelegate(); - msgUndelegate.set_delegator_address(unstake.delegator_address()); - msgUndelegate.set_validator_address(unstake.validator_address()); - *msgUndelegate.mutable_amount() = convertCoin(unstake.amount()); - any.PackFrom(msgUndelegate, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kRestakeMessage: - { - assert(msg.has_restake_message()); - const auto& restake = msg.restake_message(); - auto msgRedelegate = cosmos::staking::v1beta1::MsgBeginRedelegate(); - msgRedelegate.set_delegator_address(restake.delegator_address()); - msgRedelegate.set_validator_src_address(restake.validator_src_address()); - msgRedelegate.set_validator_dst_address(restake.validator_dst_address()); - *msgRedelegate.mutable_amount() = convertCoin(restake.amount()); - any.PackFrom(msgRedelegate, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWithdrawStakeRewardMessage: - { - assert(msg.has_withdraw_stake_reward_message()); - const auto& withdraw = msg.withdraw_stake_reward_message(); - auto msgWithdraw = cosmos::distribution::v1beta1::MsgWithdrawDelegatorReward(); - msgWithdraw.set_delegator_address(withdraw.delegator_address()); - msgWithdraw.set_validator_address(withdraw.validator_address()); - any.PackFrom(msgWithdraw, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kSetWithdrawAddressMessage: - { - assert(msg.has_set_withdraw_address_message()); - const auto& withdraw = msg.set_withdraw_address_message(); - auto msgWithdraw = cosmos::distribution::v1beta1::MsgSetWithdrawAddress(); - msgWithdraw.set_delegator_address(withdraw.delegator_address()); - msgWithdraw.set_withdraw_address(withdraw.withdraw_address()); - any.PackFrom(msgWithdraw, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kExecuteContractMessage: - { - assert(msg.has_execute_contract_message()); - const auto& execContract = msg.execute_contract_message(); - auto executeContractMsg = terra::wasm::v1beta1::MsgExecuteContract(); - executeContractMsg.set_sender(execContract.sender()); - executeContractMsg.set_contract(execContract.contract()); - executeContractMsg.set_execute_msg(execContract.execute_msg()); - for (auto i = 0; i < execContract.coins_size(); ++i){ - *executeContractMsg.add_coins() = convertCoin(execContract.coins(i)); - } - any.PackFrom(executeContractMsg, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWasmTerraExecuteContractTransferMessage: - { - assert(msg.has_wasm_terra_execute_contract_transfer_message()); - const auto& wasmExecute = msg.wasm_terra_execute_contract_transfer_message(); - auto msgExecute = terra::wasm::v1beta1::MsgExecuteContract(); - msgExecute.set_sender(wasmExecute.sender_address()); - msgExecute.set_contract(wasmExecute.contract_address()); - const std::string payload = wasmTerraExecuteTransferPayload(wasmExecute).dump(); - msgExecute.set_execute_msg(payload); - any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWasmTerraExecuteContractSendMessage: - { - assert(msg.has_wasm_terra_execute_contract_send_message()); - const auto& wasmExecute = msg.wasm_terra_execute_contract_send_message(); - auto msgExecute = terra::wasm::v1beta1::MsgExecuteContract(); - msgExecute.set_sender(wasmExecute.sender_address()); - msgExecute.set_contract(wasmExecute.contract_address()); - const std::string payload = wasmTerraExecuteSendPayload(wasmExecute).dump(); - msgExecute.set_execute_msg(payload); - any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kThorchainSendMessage: - { - assert(msg.has_thorchain_send_message()); - const auto& send = msg.thorchain_send_message(); - auto msgSend =types::MsgSend(); - msgSend.set_from_address(send.from_address()); - msgSend.set_to_address(send.to_address()); - for (auto i = 0; i < send.amounts_size(); ++i) { - *msgSend.add_amount() = convertCoin(send.amounts(i)); - } - any.PackFrom(msgSend, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWasmTerraExecuteContractGeneric: { - assert(msg.has_wasm_terra_execute_contract_generic()); - const auto& wasmExecute = msg.wasm_terra_execute_contract_generic(); - auto msgExecute = terra::wasm::v1beta1::MsgExecuteContract(); - msgExecute.set_sender(wasmExecute.sender_address()); - msgExecute.set_contract(wasmExecute.contract_address()); - msgExecute.set_execute_msg(wasmExecute.execute_msg()); - - for (auto i = 0; i < wasmExecute.coins_size(); ++i) { - *msgExecute.add_coins() = convertCoin(wasmExecute.coins(i)); - } - any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWasmExecuteContractTransferMessage: - { - assert(msg.has_wasm_execute_contract_transfer_message()); - const auto& wasmExecute = msg.wasm_execute_contract_transfer_message(); - auto msgExecute = cosmwasm::wasm::v1::MsgExecuteContract(); - msgExecute.set_sender(wasmExecute.sender_address()); - msgExecute.set_contract(wasmExecute.contract_address()); - const std::string payload = wasmExecuteTransferPayload(wasmExecute).dump(); - msgExecute.set_msg(payload); - any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWasmExecuteContractSendMessage: - { - assert(msg.has_wasm_execute_contract_send_message()); - const auto& wasmExecute = msg.wasm_execute_contract_send_message(); - auto msgExecute = cosmwasm::wasm::v1::MsgExecuteContract(); - msgExecute.set_sender(wasmExecute.sender_address()); - msgExecute.set_contract(wasmExecute.contract_address()); - const std::string payload = wasmExecuteSendPayload(wasmExecute).dump(); - msgExecute.set_msg(payload); - any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWasmExecuteContractGeneric: { - assert(msg.has_wasm_execute_contract_generic()); - const auto& wasmExecute = msg.wasm_execute_contract_generic(); - auto msgExecute = cosmwasm::wasm::v1::MsgExecuteContract(); - msgExecute.set_sender(wasmExecute.sender_address()); - msgExecute.set_contract(wasmExecute.contract_address()); - msgExecute.set_msg(wasmExecute.execute_msg()); - - for (auto i = 0; i < wasmExecute.coins_size(); ++i) { - *msgExecute.add_funds() = convertCoin(wasmExecute.coins(i)); - } - any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kAuthGrant: { - assert(msg.has_auth_grant()); - const auto& authGrant = msg.auth_grant(); - auto msgAuthGrant = cosmos::authz::v1beta1::MsgGrant(); - msgAuthGrant.set_grantee(authGrant.grantee()); - msgAuthGrant.set_granter(authGrant.granter()); - auto* mtAuth = msgAuthGrant.mutable_grant()->mutable_authorization(); - // There is multiple grant possibilities, but we add support staking/compounding only for now. - switch (authGrant.grant_type_case()) { - case Proto::Message_AuthGrant::kGrantStake: - mtAuth->PackFrom(authGrant.grant_stake(), ProtobufAnyNamespacePrefix); - mtAuth->set_type_url("/cosmos.staking.v1beta1.StakeAuthorization"); - break; - case Proto::Message_AuthGrant::GRANT_TYPE_NOT_SET: - break; - } - auto* mtExp = msgAuthGrant.mutable_grant()->mutable_expiration(); - mtExp->set_seconds(authGrant.expiration()); - any.PackFrom(msgAuthGrant, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kAuthRevoke: { - assert(msg.has_auth_revoke()); - const auto& authRevoke = msg.auth_revoke(); - auto msgAuthRevoke = cosmos::authz::v1beta1::MsgRevoke(); - msgAuthRevoke.set_granter(authRevoke.granter()); - msgAuthRevoke.set_grantee(authRevoke.grantee()); - msgAuthRevoke.set_msg_type_url(authRevoke.msg_type_url()); - any.PackFrom(msgAuthRevoke, ProtobufAnyNamespacePrefix); - return any; - } - case Proto::Message::kMsgVote: { - assert(msg.has_msg_vote()); - const auto& vote = msg.msg_vote(); - auto msgVote = cosmos::gov::v1beta1::MsgVote(); - // LCOV_EXCL_START - switch (vote.option()) { - case Proto::Message_VoteOption__UNSPECIFIED: - msgVote.set_option(cosmos::gov::v1beta1::VOTE_OPTION_UNSPECIFIED); - break; - case Proto::Message_VoteOption_YES: - msgVote.set_option(cosmos::gov::v1beta1::VOTE_OPTION_YES); - break; - case Proto::Message_VoteOption_ABSTAIN: - msgVote.set_option(cosmos::gov::v1beta1::VOTE_OPTION_ABSTAIN); - break; - case Proto::Message_VoteOption_NO: - msgVote.set_option(cosmos::gov::v1beta1::VOTE_OPTION_NO); - break; - case Proto::Message_VoteOption_NO_WITH_VETO: - msgVote.set_option(cosmos::gov::v1beta1::VOTE_OPTION_NO_WITH_VETO); - break; - case Proto::Message_VoteOption_Message_VoteOption_INT_MIN_SENTINEL_DO_NOT_USE_: - msgVote.set_option(cosmos::gov::v1beta1::VoteOption_INT_MIN_SENTINEL_DO_NOT_USE_); - break; - case Proto::Message_VoteOption_Message_VoteOption_INT_MAX_SENTINEL_DO_NOT_USE_: - msgVote.set_option(cosmos::gov::v1beta1::VoteOption_INT_MAX_SENTINEL_DO_NOT_USE_); - break; - } - // LCOV_EXCL_STOP - msgVote.set_proposal_id(vote.proposal_id()); - msgVote.set_voter(vote.voter()); - any.PackFrom(msgVote, ProtobufAnyNamespacePrefix); - return any; - } - case Proto::Message::kMsgStrideLiquidStakingStake: { - const auto& stride_liquid_staking_stake = msg.msg_stride_liquid_staking_stake(); - auto liquid_staking_msg = stride::stakeibc::MsgLiquidStake(); - liquid_staking_msg.set_creator(stride_liquid_staking_stake.creator()); - liquid_staking_msg.set_amount(stride_liquid_staking_stake.amount()); - liquid_staking_msg.set_host_denom(stride_liquid_staking_stake.host_denom()); - any.PackFrom(liquid_staking_msg, ProtobufAnyNamespacePrefix); - return any; - } - case Proto::Message::kMsgStrideLiquidStakingRedeem: { - const auto& stride_liquid_staking_redeem = msg.msg_stride_liquid_staking_redeem(); - auto liquid_staking_msg = stride::stakeibc::MsgRedeemStake(); - liquid_staking_msg.set_creator(stride_liquid_staking_redeem.creator()); - liquid_staking_msg.set_amount(stride_liquid_staking_redeem.amount()); - liquid_staking_msg.set_receiver(stride_liquid_staking_redeem.receiver()); - liquid_staking_msg.set_host_zone(stride_liquid_staking_redeem.host_zone()); - any.PackFrom(liquid_staking_msg, ProtobufAnyNamespacePrefix); - return any; - } - case Proto::Message::kThorchainDepositMessage: { - assert(msg.has_thorchain_deposit_message()); - const auto& deposit = msg.thorchain_deposit_message(); - types::MsgDeposit msgDeposit; - msgDeposit.set_memo(deposit.memo()); - msgDeposit.set_signer(deposit.signer()); - for (auto i = 0; i < deposit.coins_size(); ++i) { - auto* coin = msgDeposit.add_coins(); - auto originalAsset = deposit.coins(i).asset(); - coin->mutable_asset()->set_chain(originalAsset.chain()); - coin->mutable_asset()->set_symbol(originalAsset.symbol()); - coin->mutable_asset()->set_ticker(originalAsset.ticker()); - coin->mutable_asset()->set_synth(originalAsset.synth()); - coin->set_amount(deposit.coins(i).amount()); - coin->set_decimals(deposit.coins(i).decimals()); - } - any.PackFrom(msgDeposit, ProtobufAnyNamespacePrefix); - - return any; - } - - default: - throw std::invalid_argument(std::string("Message not supported ") + std::to_string(msg.message_oneof_case())); - } -} - -std::string buildProtoTxBody(const Proto::SigningInput& input) { - if (input.messages_size() >= 1 && input.messages(0).has_sign_direct_message()) { - return input.messages(0).sign_direct_message().body_bytes(); - } - - if (input.messages_size() < 1) { - throw std::invalid_argument("No message found"); - } - assert(input.messages_size() >= 1); - auto txBody = cosmos::TxBody(); - for (auto i = 0; i < input.messages_size(); ++i) { - const auto msgAny = convertMessage(input.messages(i)); - *txBody.add_messages() = msgAny; - } - txBody.set_memo(input.memo()); - txBody.set_timeout_height(0); - - return txBody.SerializeAsString(); -} - -std::string buildAuthInfo(const Proto::SigningInput& input, TWCoinType coin) { - // AuthInfo - const auto privateKey = PrivateKey(input.private_key()); - const auto publicKey = privateKey.getPublicKey(TWCoinTypePublicKeyType(coin)); - return buildAuthInfo(input, publicKey, coin); -} - -std::string buildAuthInfo(const Proto::SigningInput& input, const PublicKey& publicKey, TWCoinType coin) { - const auto pbk = internal::preparePublicKey(publicKey, coin); - - if (input.messages_size() >= 1 && input.messages(0).has_sign_direct_message()) { - return input.messages(0).sign_direct_message().auth_info_bytes(); - } - // AuthInfo - auto authInfo = cosmos::AuthInfo(); - auto* signerInfo = authInfo.add_signer_infos(); - - signerInfo->mutable_mode_info()->mutable_single()->set_mode(cosmos::signing::v1beta1::SIGN_MODE_DIRECT); - signerInfo->set_sequence(input.sequence()); - switch(coin) { - case TWCoinTypeNativeEvmos: { - auto pubKey = ethermint::crypto::v1::ethsecp256k1::PubKey(); - pubKey.set_key(pbk.bytes.data(), pbk.bytes.size()); - signerInfo->mutable_public_key()->PackFrom(pubKey, ProtobufAnyNamespacePrefix); - break; - } - case TWCoinTypeNativeInjective: { - auto pubKey = injective::crypto::v1beta1::ethsecp256k1::PubKey(); - pubKey.set_key(pbk.bytes.data(), pbk.bytes.size()); - signerInfo->mutable_public_key()->PackFrom(pubKey, ProtobufAnyNamespacePrefix); - break; - } - default: { - auto pubKey = cosmos::crypto::secp256k1::PubKey(); - pubKey.set_key(pbk.bytes.data(), pbk.bytes.size()); - signerInfo->mutable_public_key()->PackFrom(pubKey, ProtobufAnyNamespacePrefix); - } - } - - auto* fee = authInfo.mutable_fee(); - for (auto i = 0; i < input.fee().amounts_size(); ++i) { - *fee->add_amount() = convertCoin(input.fee().amounts(i)); - } - - fee->set_gas_limit(input.fee().gas()); - fee->set_payer(""); - fee->set_granter(""); - // tip is omitted - return authInfo.SerializeAsString(); -} - -Data buildSignature(const Proto::SigningInput& input, const std::string& serializedTxBody, const std::string& serializedAuthInfo, TWCoinType coin) { - // SignDoc Preimage - auto signDoc = cosmos::SignDoc(); - signDoc.set_body_bytes(serializedTxBody); - signDoc.set_auth_info_bytes(serializedAuthInfo); - signDoc.set_chain_id(input.chain_id()); - signDoc.set_account_number(input.account_number()); - const auto serializedSignDoc = signDoc.SerializeAsString(); - - Data hashToSign; - switch(coin) { - case TWCoinTypeNativeInjective: - case TWCoinTypeNativeEvmos: { - hashToSign = Hash::keccak256(serializedSignDoc); - break; - } - default: { - hashToSign = Hash::sha256(serializedSignDoc); - } - } - - const auto privateKey = PrivateKey(input.private_key()); - auto signedHash = privateKey.sign(hashToSign, TWCurveSECP256k1); - auto signature = Data(signedHash.begin(), signedHash.end() - 1); - return signature; -} - -std::string buildProtoTxRaw(const std::string& serializedTxBody, const std::string& serializedAuthInfo, const Data& signature) { - auto txRaw = cosmos::TxRaw(); - txRaw.set_body_bytes(serializedTxBody); - txRaw.set_auth_info_bytes(serializedAuthInfo); - *txRaw.add_signatures() = std::string(signature.begin(), signature.end()); - return txRaw.SerializeAsString(); -} - -std::string signaturePreimageProto(const Proto::SigningInput& input, const PublicKey& publicKey, TWCoinType coin) { - // SignDoc Preimage - const auto serializedTxBody = buildProtoTxBody(input); - const auto serializedAuthInfo = buildAuthInfo(input, publicKey, coin); - - auto signDoc = cosmos::SignDoc(); - signDoc.set_body_bytes(serializedTxBody); - signDoc.set_auth_info_bytes(serializedAuthInfo); - signDoc.set_chain_id(input.chain_id()); - signDoc.set_account_number(input.account_number()); - return signDoc.SerializeAsString(); -} - -std::string buildProtoTxRaw(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin) { - const auto serializedTxBody = buildProtoTxBody(input); - const auto serializedAuthInfo = buildAuthInfo(input, publicKey, coin); - - auto txRaw = cosmos::TxRaw(); - txRaw.set_body_bytes(serializedTxBody); - txRaw.set_auth_info_bytes(serializedAuthInfo); - *txRaw.add_signatures() = std::string(signature.begin(), signature.end()); - auto data = txRaw.SerializeAsString(); - return broadcastJSON(Base64::encode(Data(data.begin(), data.end())), input.mode()).dump(); -} - -std::string buildProtoTxJson(const Proto::SigningInput& input, const std::string& serializedTx) { - const string serializedBase64 = Base64::encode(TW::data(serializedTx)); - const json jsonSerialized = { - {"tx_bytes", serializedBase64}, - {"mode", broadcastMode(input.mode())} - }; - return jsonSerialized.dump(); -} - -json wasmExecuteTransferPayload(const Proto::Message_WasmExecuteContractTransfer& msg) { - return { - {"transfer", - { - {"amount", toString(load(data(msg.amount())))}, - {"recipient", msg.recipient_address()} - } - } - }; -} - -json wasmExecuteSendPayload(const Proto::Message_WasmExecuteContractSend& msg) { - return { - {"send", - { - {"amount", toString(load(data(msg.amount())))}, - {"contract", msg.recipient_contract_address()}, - {"msg", msg.msg()} - } - } - }; -} - -json wasmTerraExecuteTransferPayload(const Proto::Message_WasmTerraExecuteContractTransfer& msg) { - return { - {"transfer", - { - {"amount", toString(load(data(msg.amount())))}, - {"recipient", msg.recipient_address()} - } - } - }; -} - -json wasmTerraExecuteSendPayload(const Proto::Message_WasmTerraExecuteContractSend& msg) { - return { - {"send", - { - {"amount", toString(load(data(msg.amount())))}, - {"contract", msg.recipient_contract_address()}, - {"msg", msg.msg()} - } - } - }; -} - -} // namespace diff --git a/src/Cosmos/ProtobufSerialization.h b/src/Cosmos/ProtobufSerialization.h deleted file mode 100644 index 6eae28e431f..00000000000 --- a/src/Cosmos/ProtobufSerialization.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Data.h" -#include "PublicKey.h" -#include "../proto/Cosmos.pb.h" - -#include -#include - -#include - -namespace TW::Cosmos::Protobuf { - -std::string buildProtoTxBody(const Proto::SigningInput& input); - -std::string buildAuthInfo(const Proto::SigningInput& input, TWCoinType coin); -std::string buildAuthInfo(const Proto::SigningInput& input, const PublicKey& publicKey, TWCoinType coin); - -std::string signaturePreimageProto(const Proto::SigningInput& input, const PublicKey& publicKey, TWCoinType coin); - -std::string buildProtoTxRaw(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature, TWCoinType coin); -std::string buildProtoTxRaw(const std::string& serializedTxBody, const std::string& serializedAuthInfo, const Data& signature); -Data buildSignature(const Proto::SigningInput& input, const std::string& serializedTxBody, const std::string& serializedAuthInfo, TWCoinType coin); - -std::string buildProtoTxJson(const Proto::SigningInput& input, const std::string& serializedTx); - -nlohmann::json wasmExecuteSendPayload(const Proto::Message_WasmExecuteContractSend& msg); - -nlohmann::json wasmExecuteTransferPayload(const Proto::Message_WasmExecuteContractTransfer& msg); - -nlohmann::json wasmExecuteSendPayload(const Proto::Message_WasmExecuteContractSend& msg); - -nlohmann::json wasmTerraExecuteTransferPayload(const Proto::Message_WasmTerraExecuteContractTransfer& msg); - -nlohmann::json wasmTerraExecuteSendPayload(const Proto::Message_WasmTerraExecuteContractSend& msg); - -} // namespace TW::Cosmos::protobuf diff --git a/src/Cosmos/Signer.cpp b/src/Cosmos/Signer.cpp deleted file mode 100644 index bceb5b1bd45..00000000000 --- a/src/Cosmos/Signer.cpp +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Signer.h" -#include "JsonSerialization.h" -#include "ProtobufSerialization.h" - -#include "PrivateKey.h" -#include "Data.h" -#include - -namespace TW::Cosmos { - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input, TWCoinType coin) noexcept { - switch (input.signing_mode()) { - case Proto::JSON: - return signJsonSerialized(input, coin); - - case Proto::Protobuf: - default: - return signProtobuf(input, coin); - } -} - -std::string Signer::signaturePreimage(const Proto::SigningInput& input, const Data& publicKey, TWCoinType coin) const { - switch (input.signing_mode()) { - case Proto::JSON: - return Json::signaturePreimageJSON(input).dump(); - - case Proto::Protobuf: - default: - auto pbk = PublicKey(publicKey, TWCoinTypePublicKeyType(coin)); - return Protobuf::signaturePreimageProto(input, pbk, coin); - } -} - -Proto::SigningOutput Signer::signJsonSerialized(const Proto::SigningInput& input, TWCoinType coin) noexcept { - auto key = PrivateKey(input.private_key()); - auto preimage = Json::signaturePreimageJSON(input).dump(); - auto hash = Hash::sha256(preimage); - auto signedHash = key.sign(hash, TWCurveSECP256k1); - - auto output = Proto::SigningOutput(); - auto signature = Data(signedHash.begin(), signedHash.end() - 1); - auto txJson = Json::transactionJSON(input, signature, coin); - output.set_json(txJson.dump()); - output.set_signature(signature.data(), signature.size()); - output.set_serialized(""); - output.set_error_message(""); - output.set_signature_json(txJson["tx"]["signatures"].dump()); - return output; -} - -Proto::SigningOutput Signer::signProtobuf(const Proto::SigningInput& input, TWCoinType coin) noexcept { - using namespace Protobuf; - using namespace Json; - try { - const auto serializedTxBody = buildProtoTxBody(input); - const auto serializedAuthInfo = buildAuthInfo(input, coin); - const auto signature = buildSignature(input, serializedTxBody, serializedAuthInfo, coin); - auto serializedTxRaw = buildProtoTxRaw(serializedTxBody, serializedAuthInfo, signature); - - auto output = Proto::SigningOutput(); - const std::string jsonSerialized = Protobuf::buildProtoTxJson(input, serializedTxRaw); - auto publicKey = PrivateKey(input.private_key()).getPublicKey(TWPublicKeyTypeSECP256k1); - auto signatures = nlohmann::json::array({signatureJSON(signature, publicKey.bytes, coin)}); - output.set_serialized(jsonSerialized); - output.set_signature(signature.data(), signature.size()); - output.set_json(""); - output.set_error_message(""); - output.set_signature_json(signatures.dump()); - return output; - } catch (const std::exception& ex) { - auto output = Proto::SigningOutput(); - output.set_error(Common::Proto::Error_internal); - output.set_error_message(std::string("Error: ") + ex.what()); - return output; - } -} - -std::string Signer::signJSON(const std::string& json, const Data& key, TWCoinType coin) { - auto input = Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - auto output = Signer::sign(input, coin); - return output.json(); -} - -std::string Signer::encodeTransaction(const Proto::SigningInput& input, const Data& signature, const PublicKey& publicKey, TWCoinType coin) const { - switch (input.signing_mode()) { - case Proto::JSON: - return Json::buildJsonTxRaw(input, publicKey, signature, coin); - - case Proto::Protobuf: - default: - return Protobuf::buildProtoTxRaw(input, publicKey, signature, coin); - } -} -} // namespace TW::Cosmos diff --git a/src/Cosmos/Signer.h b/src/Cosmos/Signer.h deleted file mode 100644 index b63836fc2e7..00000000000 --- a/src/Cosmos/Signer.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Data.h" -#include "PublicKey.h" -#include "../proto/Cosmos.pb.h" - -#include - -namespace TW::Cosmos { - -/// Helper class that performs Cosmos transaction signing. -class Signer { - public: - /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input, TWCoinType coin) noexcept; - - std::string encodeTransaction(const Proto::SigningInput& input, const Data& signature, const PublicKey& publicKey, TWCoinType coin) const; - std::string signaturePreimage(const Proto::SigningInput& input, const Data& publicKey, TWCoinType coin) const; - - /// Signs a Proto::SigningInput transaction, using Json serialization - static Proto::SigningOutput signJsonSerialized(const Proto::SigningInput& input, TWCoinType coin) noexcept; - - /// Signs a Proto::SigningInput transaction, using binary Protobuf serialization - static Proto::SigningOutput signProtobuf(const Proto::SigningInput& input, TWCoinType coin) noexcept; - - /// Signs a json Proto::SigningInput with private key - static std::string signJSON(const std::string& json, const Data& key, TWCoinType coin); -}; - -} // namespace TW::Cosmos diff --git a/src/Ethereum/Entry.cpp b/src/Ethereum/Entry.cpp index 85c679900c4..78834004477 100644 --- a/src/Ethereum/Entry.cpp +++ b/src/Ethereum/Entry.cpp @@ -14,30 +14,8 @@ namespace TW::Ethereum { -using namespace std; - -bool Entry::validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const { - return validateAddressRust(coin, address, addressPrefix); -} - -string Entry::normalizeAddress(TWCoinType coin, const string& address) const { - return normalizeAddressRust(coin, address); -} - -std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const { - return deriveAddressRust(coin, publicKey, derivation, addressPrefix); -} - -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { - return addressToDataRust(coin, address); -} - -void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signRust(dataIn, coin, dataOut); -} - // TODO call `signRustJSON` when it's done. -string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { +std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { auto input = Proto::SigningInput(); google::protobuf::util::JsonStringToMessage(json, &input); input.set_private_key(key.data(), key.size()); @@ -56,12 +34,4 @@ string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json return hex(output.encoded()); } -Data Entry::preImageHashes(TWCoinType coin, const Data& txInputData) const { - return preImageHashesRust(coin, txInputData); -} - -void Entry::compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { - compileRust(coin, txInputData, signatures, publicKeys, dataOut); -} - } // namespace TW::Ethereum diff --git a/src/Ethereum/Entry.h b/src/Ethereum/Entry.h index 4d3c909df02..f2a5203a0d2 100644 --- a/src/Ethereum/Entry.h +++ b/src/Ethereum/Entry.h @@ -6,24 +6,16 @@ #pragma once -#include "../CoinEntry.h" +#include "rust/RustCoinEntry.h" namespace TW::Ethereum { /// Entry point for Ethereum and Ethereum-fork coins. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry final : public CoinEntry { +class Entry : public Rust::RustCoinEntry { public: - bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; - std::string normalizeAddress(TWCoinType coin, const std::string& address) const override; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; - Data addressToData(TWCoinType coin, const std::string& address) const override; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; - bool supportsJSONSigning() const override { return true; } - std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const override; - - Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; - void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; + bool supportsJSONSigning() const final { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const final; }; } // namespace TW::Ethereum diff --git a/src/Greenfield/ProtobufSerialization.cpp b/src/Greenfield/ProtobufSerialization.cpp index 82da978ccdf..45fdd59647f 100644 --- a/src/Greenfield/ProtobufSerialization.cpp +++ b/src/Greenfield/ProtobufSerialization.cpp @@ -88,7 +88,7 @@ SigningResult ProtobufSerialization::encodeTxProtobuf(const Proto::Signing } const auto serializedTxBody = txBodyResult.payload(); - auto txRaw = cosmos::TxRaw(); + auto txRaw = cosmos::tx::v1beta1::TxRaw(); txRaw.set_body_bytes(serializedTxBody.data(), serializedTxBody.size()); txRaw.set_auth_info_bytes(serializedAuthInfo.data(), serializedAuthInfo.size()); *txRaw.add_signatures() = std::string(signature.begin(), signature.end()); @@ -100,7 +100,7 @@ SigningResult ProtobufSerialization::encodeTxProtobuf(const Proto::Signing } SigningResult ProtobufSerialization::encodeTxBody(const Proto::SigningInput& input) { - cosmos::TxBody txBody; + cosmos::tx::v1beta1::TxBody txBody; // At this moment, we support only one message. if (input.messages_size() != 1) { @@ -120,7 +120,7 @@ SigningResult ProtobufSerialization::encodeTxBody(const Proto::SigningInpu Data ProtobufSerialization::encodeAuthInfo(const Proto::SigningInput& input, const PublicKey& publicKey) { // AuthInfo - auto authInfo = cosmos::AuthInfo(); + auto authInfo = cosmos::tx::v1beta1::AuthInfo(); auto* signerInfo = authInfo.add_signer_infos(); // At this moment, we support Eip712 signing mode only. diff --git a/src/InternetComputer/Entry.cpp b/src/InternetComputer/Entry.cpp deleted file mode 100644 index a4d7e6bff38..00000000000 --- a/src/InternetComputer/Entry.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Entry.h" - -namespace TW::InternetComputer { - -// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. - -bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { - return validateAddressRust(coin, address, addressPrefix); -} - -std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { - return deriveAddressRust(coin, publicKey, derivation, addressPrefix); -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signRust(dataIn, coin, dataOut); -} - -TW::Data Entry::preImageHashes(TWCoinType coin, const Data& txInputData) const { - return preImageHashesRust(coin, txInputData); -} - -void Entry::compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { - compileRust(coin, txInputData, signatures, publicKeys, dataOut); -} - -} // namespace TW::InternetComputer diff --git a/src/InternetComputer/Entry.h b/src/InternetComputer/Entry.h index e997bddb2d6..0d9081d60d1 100644 --- a/src/InternetComputer/Entry.h +++ b/src/InternetComputer/Entry.h @@ -6,22 +6,13 @@ #pragma once -#include "../CoinEntry.h" +#include "rust/RustCoinEntry.h" namespace TW::InternetComputer { /// Entry point for implementation of InternetComputer coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry final : public CoinEntry { -public: - bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - // normalizeAddress(): implement this if needed, e.g. Ethereum address is EIP55 checksummed - // plan(): implement this if the blockchain is UTXO based - - Data preImageHashes(TWCoinType coin, const Data& txInputData) const; - void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; +class Entry final : public Rust::RustCoinEntry { }; } // namespace TW::InternetComputer diff --git a/src/NativeEvmos/Entry.h b/src/NativeEvmos/Entry.h new file mode 100644 index 00000000000..bc44a9620e2 --- /dev/null +++ b/src/NativeEvmos/Entry.h @@ -0,0 +1,18 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Cosmos/Entry.h" + +namespace TW::NativeEvmos { + +/// Entry point for implementation of NativeEvmos coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public Cosmos::Entry { +}; + +} // namespace TW::NativeEvmos diff --git a/src/NativeInjective/Entry.h b/src/NativeInjective/Entry.h new file mode 100644 index 00000000000..1a55e88540b --- /dev/null +++ b/src/NativeInjective/Entry.h @@ -0,0 +1,18 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "Cosmos/Entry.h" + +namespace TW::NativeInjective { + +/// Entry point for implementation of NativeEvmos coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public Cosmos::Entry { +}; + +} // namespace TW::NativeInjective diff --git a/src/Ronin/Entry.cpp b/src/Ronin/Entry.cpp deleted file mode 100644 index 7889d28c786..00000000000 --- a/src/Ronin/Entry.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Entry.h" - -#include "Ethereum/Entry.h" -#include "HexCoding.h" - -namespace TW::Ronin { - -bool Entry::validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const { - return validateAddressRust(coin, address, addressPrefix); -} - -std::string Entry::normalizeAddress(TWCoinType coin, const std::string& address) const { - return normalizeAddressRust(coin, address); -} - -std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const { - return deriveAddressRust(coin, publicKey, derivation, addressPrefix); -} - -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { - return addressToDataRust(coin, address); -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signRust(dataIn, coin, dataOut); -} - -// TODO call `signRustJSON` when it's done. -std::string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { - return Ethereum::Entry().signJSON(coin, json, key); -} - -} // namespace TW::Ronin diff --git a/src/Ronin/Entry.h b/src/Ronin/Entry.h index 356c1940d7c..b9b5ab2c2d9 100644 --- a/src/Ronin/Entry.h +++ b/src/Ronin/Entry.h @@ -6,20 +6,12 @@ #pragma once -#include "../CoinEntry.h" +#include "Ethereum/Entry.h" namespace TW::Ronin { /// Entry point for Ronin (EVM side chain) -class Entry final : public CoinEntry { -public: - bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string normalizeAddress(TWCoinType coin, const std::string& address) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - Data addressToData(TWCoinType coin, const std::string& address) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - bool supportsJSONSigning() const { return true; } - std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; +class Entry final : public Ethereum::Entry { }; } // namespace TW::Ronin diff --git a/src/THORChain/Entry.cpp b/src/THORChain/Entry.cpp deleted file mode 100644 index ed96e9e3d9d..00000000000 --- a/src/THORChain/Entry.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Entry.h" - -#include "Signer.h" -#include "../proto/Cosmos.pb.h" - -using namespace std; - -namespace TW::THORChain { - -// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. - -void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - auto input = Cosmos::Proto::SigningInput(); - input.ParseFromArray(dataIn.data(), (int)dataIn.size()); - auto serializedOut = Signer::sign(input).SerializeAsString(); - dataOut.insert(dataOut.end(), serializedOut.begin(), serializedOut.end()); -} - -string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { - return Signer::signJSON(json, key); -} - -} // namespace TW::THORChain diff --git a/src/THORChain/Entry.h b/src/THORChain/Entry.h index aeb3707cd1f..551b26be00b 100644 --- a/src/THORChain/Entry.h +++ b/src/THORChain/Entry.h @@ -14,9 +14,6 @@ namespace TW::THORChain { /// Entry point for implementation of THORChain coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file class Entry final : public Cosmos::Entry { -public: - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; }; } // namespace TW::THORChain diff --git a/src/THORChain/Signer.cpp b/src/THORChain/Signer.cpp deleted file mode 100644 index 7554e242013..00000000000 --- a/src/THORChain/Signer.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Signer.h" -#include "../Cosmos/Signer.h" -#include "../proto/Cosmos.pb.h" - -#include -#include - -using namespace TW; - -namespace TW::THORChain { -const std::string TYPE_PREFIX_MSG_SEND = "thorchain/MsgSend"; - -Cosmos::Proto::SigningOutput Signer::sign(Cosmos::Proto::SigningInput& input) noexcept { - for (auto i = 0; i < input.messages_size(); ++i) { - if (input.messages(i).has_send_coins_message()) { - input.mutable_messages(i)->mutable_send_coins_message()->set_type_prefix(TYPE_PREFIX_MSG_SEND); - } - } - return Cosmos::Signer::sign(input, TWCoinTypeTHORChain); -} - -std::string Signer::signJSON(const std::string& json, const Data& key) { - auto input = Cosmos::Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - auto output = Signer::sign(input); - return output.json(); -} - -} // namespace TW::THORChain diff --git a/src/THORChain/Signer.h b/src/THORChain/Signer.h deleted file mode 100644 index 10a5214c784..00000000000 --- a/src/THORChain/Signer.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Data.h" -#include "../proto/Cosmos.pb.h" -#include - -namespace TW::THORChain { - -/// Helper class that performs THORChain transaction signing. -class Signer { - public: - /// Signs a Proto::SigningInput transaction - static Cosmos::Proto::SigningOutput sign(Cosmos::Proto::SigningInput& input) noexcept; - /// Signs a json Proto::SigningInput with private key - static std::string signJSON(const std::string& json, const Data& key); -}; - -} // namespace TW::THORChain diff --git a/src/proto/Cosmos.proto b/src/proto/Cosmos.proto index 264e811a8d9..323ac93bb08 100644 --- a/src/proto/Cosmos.proto +++ b/src/proto/Cosmos.proto @@ -117,14 +117,6 @@ message Message { string type_prefix = 3; } - message ExecuteContract { - string sender = 1; - string contract = 2; - string execute_msg = 3; - repeated Amount coins = 4; - string type_prefix = 5; - } - // transfer within wasm/MsgExecuteContract, used by Terra Classic message WasmTerraExecuteContractTransfer { // sender address @@ -224,6 +216,7 @@ message Message { } // execute within wasm/MsgExecuteContract + // TODO replaces `ExecuteContract`. message WasmExecuteContractGeneric { // sender address string sender_address = 1; @@ -353,7 +346,6 @@ message Message { WasmTerraExecuteContractTransfer wasm_terra_execute_contract_transfer_message = 8; WasmTerraExecuteContractSend wasm_terra_execute_contract_send_message = 9; THORChainSend thorchain_send_message = 10; - ExecuteContract execute_contract_message = 11; WasmTerraExecuteContractGeneric wasm_terra_execute_contract_generic = 12; WasmExecuteContractTransfer wasm_execute_contract_transfer_message = 13; WasmExecuteContractSend wasm_execute_contract_send_message = 14; diff --git a/src/rust/RustCoinEntry.cpp b/src/rust/RustCoinEntry.cpp new file mode 100644 index 00000000000..497edc57644 --- /dev/null +++ b/src/rust/RustCoinEntry.cpp @@ -0,0 +1,114 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "RustCoinEntry.h" +#include "Wrapper.h" + +namespace TW::Rust { + +bool RustCoinEntry::validateAddress(TWCoinType coin, const std::string &address, const PrefixVariant &addressPrefix) const { + Rust::TWStringWrapper addressStr = address; + + if (std::holds_alternative(addressPrefix)) { + return Rust::tw_any_address_is_valid(addressStr.get(), static_cast(coin)); + } else if (const auto* hrpPrefix = std::get_if(&addressPrefix); hrpPrefix) { + Rust::TWStringWrapper hrpStr = std::string(*hrpPrefix); + return Rust::tw_any_address_is_valid_bech32(addressStr.get(), static_cast(coin), hrpStr.get()); + } else { + throw std::invalid_argument("`Rust::tw_any_address_is_valid_ss58`, `Rust::tw_any_address_create_with_public_key_filecoin_address_type` are not supported yet"); + } +} + +std::string RustCoinEntry::normalizeAddress(TWCoinType coin, const std::string& address) const { + Rust::TWStringWrapper addressStr = address; + + // `CoinEntry::normalizeAddress` is used when a `TWAnyAddress` has been created already, therefore validated. + auto anyAddress = Rust::wrapTWAnyAddress( + Rust::tw_any_address_create_with_string_unchecked(addressStr.get(), static_cast(coin))); + if (!anyAddress) { + return {}; + } + + Rust::TWStringWrapper normalized = Rust::tw_any_address_description(anyAddress.get()); + return normalized.toStringOrDefault(); +} + +std::string RustCoinEntry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const { + auto *twPublicKeyRaw = Rust::tw_public_key_create_with_data(publicKey.bytes.data(), + publicKey.bytes.size(), + static_cast(publicKey.type)); + auto twPublicKey = Rust::wrapTWPublicKey(twPublicKeyRaw); + if (!twPublicKey) { + return {}; + } + + Rust::TWAnyAddress* anyAddressRaw = nullptr; + if (std::holds_alternative(addressPrefix)) { + anyAddressRaw = Rust::tw_any_address_create_with_public_key_derivation(twPublicKey.get(), + static_cast(coin), + static_cast(derivation)); + } else if (const auto* hrpPrefix = std::get_if(&addressPrefix); hrpPrefix) { + Rust::TWStringWrapper hrpStr = std::string(*hrpPrefix); + anyAddressRaw = Rust::tw_any_address_create_bech32_with_public_key(twPublicKey.get(), + static_cast(coin), + hrpStr.get()); + } else { + throw std::invalid_argument("`Rust::tw_any_address_is_valid_ss58`, `Rust::tw_any_address_create_with_public_key_filecoin_address_type` are not supported yet"); + } + + auto anyAddress = Rust::wrapTWAnyAddress(anyAddressRaw); + if (!anyAddress) { + return {}; + } + + Rust::TWStringWrapper derivedAddress = Rust::tw_any_address_description(anyAddress.get()); + return derivedAddress.toStringOrDefault(); +} + +Data RustCoinEntry::addressToData(TWCoinType coin, const std::string& address) const { + Rust::TWStringWrapper addressStr = address; + + // `CoinEntry::normalizeAddress` is used when a `TWAnyAddress` has been created already, therefore validated. + auto anyAddress = Rust::wrapTWAnyAddress( + Rust::tw_any_address_create_with_string_unchecked(addressStr.get(), static_cast(coin))); + if (!anyAddress) { + return {}; + } + + Rust::TWDataWrapper data = Rust::tw_any_address_data(anyAddress.get()); + return data.toDataOrDefault(); +} + +void RustCoinEntry::sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const { + Rust::TWDataWrapper input = Rust::tw_data_create_with_bytes(dataIn.data(), dataIn.size()); + Rust::TWDataWrapper output = Rust::tw_any_signer_sign(input.get(), static_cast(coin)); + + dataOut = output.toDataOrDefault(); +} + +Data RustCoinEntry::preImageHashes(TWCoinType coin, const Data& txInputData) const { + Rust::TWDataWrapper input = txInputData; + Rust::TWDataWrapper output = Rust::tw_transaction_compiler_pre_image_hashes(static_cast(coin), input.get()); + + return output.toDataOrDefault(); +} + +void RustCoinEntry::compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + Rust::TWDataWrapper input = txInputData; + + std::vector publicKeysData; + for (const auto& publicKey : publicKeys) { + publicKeysData.push_back(publicKey.bytes); + } + + Rust::TWDataVectorWrapper signaturesVec = signatures; + Rust::TWDataVectorWrapper publicKeysVec = publicKeysData; + + Rust::TWDataWrapper output = Rust::tw_transaction_compiler_compile(static_cast(coin), input.get(), signaturesVec.get(), publicKeysVec.get()); + dataOut = output.toDataOrDefault(); +} + +} // namespace TW::Rust diff --git a/src/rust/RustCoinEntry.h b/src/rust/RustCoinEntry.h new file mode 100644 index 00000000000..edcf926d9e2 --- /dev/null +++ b/src/rust/RustCoinEntry.h @@ -0,0 +1,26 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "CoinEntry.h" + +namespace TW::Rust { + +class RustCoinEntry : public CoinEntry { +public: + ~RustCoinEntry() noexcept override = default; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; +}; + +} // namespace TW::Rust diff --git a/swift/Tests/Blockchains/CosmosTests.swift b/swift/Tests/Blockchains/CosmosTests.swift index 6903c9487ee..7a7c906d5d0 100644 --- a/swift/Tests/Blockchains/CosmosTests.swift +++ b/swift/Tests/Blockchains/CosmosTests.swift @@ -102,7 +102,7 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) - XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}") + XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjogARI2CjRjb3Ntb3N2YWxvcGVyMWdqdHZseTlsZWw2enNrdnd0dmxnNXZod3B1OWM5d2F3N3N4end4EgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQEAN1nIfDawlHnep2bNEm14w+g7tYybJJT3htcGVS6s9D7va3ed1OUEIk9LZoc3G//VenJ+KLw26SRVBaRukgVI=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}") XCTAssertEqual(output.errorMessage, "") } diff --git a/tests/chains/Cosmos/CryptoOrg/SignerTests.cpp b/tests/chains/Cosmos/CryptoOrg/SignerTests.cpp index fa6793727c9..f71f90c5cff 100644 --- a/tests/chains/Cosmos/CryptoOrg/SignerTests.cpp +++ b/tests/chains/Cosmos/CryptoOrg/SignerTests.cpp @@ -5,11 +5,11 @@ // file LICENSE at the root of the source code distribution tree. #include "proto/Cosmos.pb.h" -#include "Cosmos/Signer.h" #include "Cosmos/Address.h" #include "HexCoding.h" #include "PublicKey.h" #include "TestUtilities.h" +#include "TrustWalletCore/TWAnySigner.h" #include #include @@ -72,7 +72,8 @@ TEST(CryptoorgSigner, SignTx_DDCCE4) { auto privateKey = parse_hex("200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Cosmos::Signer::sign(input, TWCoinTypeCryptoOrg); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCryptoOrg); assertJSONEqual(output.json(), R"( { @@ -125,7 +126,7 @@ TEST(CryptoorgSigner, SignJson) { auto inputJson = R"({"accountNumber":"125798","chainId":"crypto-org-chain-mainnet-1","fee":{"amounts":[{"denom":"basecro","amount":"5000"}],"gas":"200000"},"messages":[{"sendCoinsMessage":{"fromAddress":"cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0","toAddress":"cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus","amounts":[{"denom":"basecro","amount":"100000000"}]}}]})"; auto privateKey = parse_hex("200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d"); - auto outputJson = Cosmos::Signer::signJSON(inputJson, privateKey, TWCoinTypeCryptoOrg); + auto outputJson = TW::anySignJSON(TWCoinTypeCryptoOrg, inputJson, privateKey); assertJSONEqual(outputJson, R"( { diff --git a/tests/chains/Cosmos/Juno/TWAnySignerTests.cpp b/tests/chains/Cosmos/Juno/TWAnySignerTests.cpp index 13444945f39..dafa5aee10a 100644 --- a/tests/chains/Cosmos/Juno/TWAnySignerTests.cpp +++ b/tests/chains/Cosmos/Juno/TWAnySignerTests.cpp @@ -39,7 +39,9 @@ TEST(TWAnySignerJuno, Sign) { amountOfFee->set_amount("1000"); Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeCosmos); + ANY_SIGN(input, TWCoinTypeJuno); + + EXPECT_EQ(output.error(), Common::Proto::OK); // https://www.mintscan.io/juno/txs/3DCE6AAF19657BCF11D44FD6BE124D57B44E04CA34851DE0ECCE619F70ECC46F auto expectedJson = R"( diff --git a/tests/chains/Cosmos/NativeEvmos/TWCoinTypeTests.cpp b/tests/chains/Cosmos/NativeEvmos/TWCoinTypeTests.cpp index 822897296e8..2713fcbba7e 100644 --- a/tests/chains/Cosmos/NativeEvmos/TWCoinTypeTests.cpp +++ b/tests/chains/Cosmos/NativeEvmos/TWCoinTypeTests.cpp @@ -20,7 +20,7 @@ TEST(TWEvmosCoinType, TWCoinTypeNativeEvmos) { auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNativeEvmos)); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNativeEvmos), 18); - ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeNativeEvmos)); + ASSERT_EQ(TWBlockchainNativeEvmos, TWCoinTypeBlockchain(TWCoinTypeNativeEvmos)); assertStringsEqual(symbol, "EVMOS"); assertStringsEqual(txUrl, "https://mintscan.io/evmos/txs/A16C211C83AD1E684DE46F694FAAC17D8465C864BD7385A81EC062CDE0638811"); diff --git a/tests/chains/Cosmos/NativeInjective/SignerTests.cpp b/tests/chains/Cosmos/NativeInjective/SignerTests.cpp index 46ed18809ba..a103ff8d09a 100644 --- a/tests/chains/Cosmos/NativeInjective/SignerTests.cpp +++ b/tests/chains/Cosmos/NativeInjective/SignerTests.cpp @@ -8,12 +8,10 @@ #include "Base64.h" #include "proto/Cosmos.pb.h" #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" #include "TestUtilities.h" -#include +#include "TrustWalletCore/TWAnySigner.h" #include -#include namespace TW::Cosmos::nativeInjective::tests { @@ -46,7 +44,8 @@ TEST(NativeInjectiveSigner, Sign) { auto privateKey = parse_hex("9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeNativeInjective); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeNativeInjective); // https://www.mintscan.io/injective/txs/135DD2C4A1910E4334A9C0F15125DA992E724EBF23FEB9638FCB71218BB064A5 assertJSONEqual(output.serialized(), "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Co8BCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKKmluajEzdTZnN3ZxZ3cwNzRtZ21mMnplMmNhZHp2a3o5c25sd2NydHE4YRIqaW5qMXhtcGtteHI0YXMwMGVtMjN0YzJ6Z211eXkyZ3I0aDN3Z2NsNnZkGhIKA2luahILMTAwMDAwMDAwMDASngEKfgp0Ci0vaW5qZWN0aXZlLmNyeXB0by52MWJldGExLmV0aHNlY3AyNTZrMS5QdWJLZXkSQwpBBFoMa4O4vZgn5QcnDK20mbfjqQlSRvaiITKB94PYd8mLJWdCdBsGOfMXdo/k9MJ2JmDCESKDp2hdgVUH3uMikXMSBAoCCAEYARIcChYKA2luahIPMTAwMDAwMDAwMDAwMDAwELDbBhpAx2vkplmzeK7n3puCFGPWhLd0l/ZC/CYkGl+stH+3S3hiCvIe7uwwMpUlNaSwvT8HwF1kNUp+Sx2m0Uo1x5xcFw==\"}"); diff --git a/tests/chains/Cosmos/NativeInjective/TWCoinTypeTests.cpp b/tests/chains/Cosmos/NativeInjective/TWCoinTypeTests.cpp index 0b152ca68be..f92a2f9ca7e 100644 --- a/tests/chains/Cosmos/NativeInjective/TWCoinTypeTests.cpp +++ b/tests/chains/Cosmos/NativeInjective/TWCoinTypeTests.cpp @@ -28,7 +28,7 @@ TEST(TWNativeInjectiveCoinType, TWCoinType) { assertStringsEqual(name, "Native Injective"); assertStringsEqual(symbol, "INJ"); ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); - ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainNativeInjective); ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); assertStringsEqual(chainId, "injective-1"); diff --git a/tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp b/tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp index a7591f1b786..163b19dfaf1 100644 --- a/tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp +++ b/tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp @@ -5,7 +5,6 @@ // file LICENSE at the root of the source code distribution tree. #include "Base64.h" -#include "Cosmos/Signer.h" #include "HexCoding.h" #include "proto/Cosmos.pb.h" #include "proto/TransactionCompiler.pb.h" @@ -95,8 +94,7 @@ TEST(NativeInjectiveCompiler, CompileWithSignatures) { EXPECT_EQ(output.error(), Common::Proto::OK); EXPECT_EQ(output.serialized(), expectedTx); - EXPECT_EQ(output.signature(), ""); - EXPECT_EQ(hex(output.signature()), ""); + EXPECT_EQ(hex(output.signature()), "f7a9ec0a521170bb5566ca973d3c73a1b69b162d99ce022059189991ec440637333394ff1c9e75fad84eb114393969f20989b036f1dfed28949e906dc0077421"); } } diff --git a/tests/chains/Cosmos/Osmosis/SignerTests.cpp b/tests/chains/Cosmos/Osmosis/SignerTests.cpp index d7935f190de..b0ae1832083 100644 --- a/tests/chains/Cosmos/Osmosis/SignerTests.cpp +++ b/tests/chains/Cosmos/Osmosis/SignerTests.cpp @@ -5,10 +5,10 @@ // file LICENSE at the root of the source code distribution tree. #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" #include "HexCoding.h" #include "PublicKey.h" #include "proto/Cosmos.pb.h" +#include "TrustWalletCore/TWAnySigner.h" #include "TestUtilities.h" #include @@ -45,7 +45,8 @@ TEST(OsmosisSigner, SignTransfer_81B4) { auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeOsmosis); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeOsmosis); // https://www.mintscan.io/osmosis/txs/81B4F01BDE72AF7FF4536E5D7E66EB218E9FC9ACAA7C5EB5DB237DD0595D5F5F // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Co0B...rYVj", "mode": "BROADCAST_MODE_BLOCK"}' https://lcd-osmosis.keplr.app/cosmos/tx/v1beta1/txs diff --git a/tests/chains/Cosmos/ProtobufTests.cpp b/tests/chains/Cosmos/ProtobufTests.cpp deleted file mode 100644 index 009bf227afd..00000000000 --- a/tests/chains/Cosmos/ProtobufTests.cpp +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Base64.h" -#include "Cosmos/Address.h" -#include "Cosmos/Protobuf/authz_tx.pb.h" -#include "Cosmos/Protobuf/bank_tx.pb.h" -#include "Cosmos/Protobuf/tx.pb.h" -#include "Cosmos/ProtobufSerialization.h" -#include "Data.h" -#include "HexCoding.h" - -#include "Protobuf/Article.pb.h" -#include "TestUtilities.h" - -#include -#include - -#include - -namespace TW::Cosmos::tests { - -using json = nlohmann::json; - -TEST(CosmosProtobuf, SendMsg) { - auto msgSend = cosmos::bank::v1beta1::MsgSend(); - msgSend.set_from_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); - msgSend.set_to_address("cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"); - auto coin = msgSend.add_amount(); - coin->set_denom("muon"); - coin->set_amount("1"); - - auto txBody = cosmos::TxBody(); - txBody.add_messages()->PackFrom(msgSend); - txBody.set_memo(""); - txBody.set_timeout_height(0); - - const auto serialized = data(txBody.SerializeAsString()); - EXPECT_EQ(hex(serialized), "0a9c010a2f747970652e676f6f676c65617069732e636f6d2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a090a046d756f6e120131"); - EXPECT_EQ(Base64::encode(serialized), "CpwBCi90eXBlLmdvb2dsZWFwaXMuY29tL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZBJpCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISLWNvc21vczF6dDUwYXp1cGFucWxmYW01YWZodjNoZXh3eXV0bnVrZWg0YzU3MxoJCgRtdW9uEgEx"); - - std::string json; - google::protobuf::util::MessageToJsonString(txBody, &json); - assertJSONEqual(json, R"({"messages":[{"@type":"type.googleapis.com/cosmos.bank.v1beta1.MsgSend","amount":[{"amount":"1","denom":"muon"}],"fromAddress":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","toAddress":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}]})"); -} - -TEST(CosmosProtobuf, ExecuteContractMsg) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::JSON); // obsolete - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(8); - - auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); - auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_execute_contract_message(); - message.set_sender(fromAddress.string()); - message.set_contract(toAddress.string()); - message.set_execute_msg("transfer"); - auto* coin = message.add_coins(); - coin->set_denom("muon"); - coin->set_amount("1"); - - auto body = Protobuf::buildProtoTxBody(input); - - EXPECT_EQ(hex(body), "0a9d010a262f74657272612e7761736d2e763162657461312e4d736745786563757465436f6e747261637412730a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a087472616e736665722a090a046d756f6e120131"); -} - -TEST(CosmosProtobuf, DeterministicSerialization_Article) { - // https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-027-deterministic-protobuf-serialization.md - auto article = blog::Article(); - article.set_title("The world needs change 🌳"); - article.set_description(""); - article.set_created(1596806111080); - article.set_updated(0); - article.set_public_(true); - article.set_promoted(false); - article.set_type(blog::NEWS); - article.set_review(blog::REVIEW_UNSPECIFIED); - *article.add_comments() = "Nice one"; - *article.add_comments() = "Thank you"; - - const auto serialized = data(article.SerializeAsString()); - EXPECT_EQ(hex(serialized), "0a1b54686520776f726c64206e65656473206368616e676520f09f8cb318e8bebec8bc2e280138024a084e696365206f6e654a095468616e6b20796f75"); -} - -} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Secret/SignerTests.cpp b/tests/chains/Cosmos/Secret/SignerTests.cpp index 997ab2d3886..8ccb283c938 100644 --- a/tests/chains/Cosmos/Secret/SignerTests.cpp +++ b/tests/chains/Cosmos/Secret/SignerTests.cpp @@ -5,9 +5,10 @@ // file LICENSE at the root of the source code distribution tree. #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" +#include "TrustWalletCore/TWAnySigner.h" #include "HexCoding.h" #include "PrivateKey.h" +#include "proto/Cosmos.pb.h" #include "PublicKey.h" #include "TestUtilities.h" @@ -45,7 +46,8 @@ TEST(SecretSigner, Sign) { auto privateKey = parse_hex("87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeSecret); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeSecret); // https://www.mintscan.io/secret/txs/01F4BD2458BF966F287533775C8D67BBC7CA7214CAEB1752D270A90223E9E82F // curl -H 'Content-Type: application/json' --data-binary "{\"tx_bytes\":\"CpIB...c4o=\",\"mode\":\"BROADCAST_MODE_BLOCK\"}" https://scrt-lcd.blockpane.com/cosmos/tx/v1beta1/txs diff --git a/tests/chains/Cosmos/SignerTests.cpp b/tests/chains/Cosmos/SignerTests.cpp index dc45bc5cd1c..20ec6a027a7 100644 --- a/tests/chains/Cosmos/SignerTests.cpp +++ b/tests/chains/Cosmos/SignerTests.cpp @@ -9,7 +9,7 @@ #include "Base64.h" #include "proto/Cosmos.pb.h" #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" +#include "TrustWalletCore/TWAnySigner.h" #include "TestUtilities.h" #include "Cosmos/Protobuf/bank_tx.pb.h" #include "Cosmos/Protobuf/coin.pb.h" @@ -52,7 +52,8 @@ TEST(CosmosSigner, SignTxProtobuf) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47"); @@ -78,9 +79,11 @@ TEST(CosmosSigner, SignProtobuf_ErrorMissingMessage) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); - EXPECT_EQ(output.error_message(), "Error: No message found"); + // TODO +// EXPECT_EQ(output.error_message(), "Error: No message found"); EXPECT_EQ(output.serialized(), ""); EXPECT_EQ(output.json(), ""); EXPECT_EQ(hex(output.signature()), ""); @@ -119,7 +122,8 @@ TEST(CosmosSigner, SignTxJson) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); // the sample tx on testnet // https://hubble.figment.network/chains/gaia-13003/blocks/142933/transactions/3A9206598C3D2E75A5EC074FD33EA53EB18EC729357F0965971C1C51F812AEA3?format=json @@ -128,47 +132,6 @@ TEST(CosmosSigner, SignTxJson) { EXPECT_EQ(hex(output.signature()), "fc3ef899d206c88077fec42f21ba0b4df4bd3fd115fdf606ae01d9136fef363f57e9e33a7b9ec6ddab658cd07e3c0067470de94e4e75b979a1085a29f0efd926"); } -TEST(CosmosSigner, SignTxJsonWithExecuteContractMsg) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::JSON); // obsolete - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(8); - - auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); - auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_execute_contract_message(); - message.set_sender(fromAddress.string()); - message.set_contract(toAddress.string()); - message.set_execute_msg("transfer"); - auto* coin = message.add_coins(); - coin->set_denom("muon"); - coin->set_amount("1"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount("200"); - - std::string json; - google::protobuf::util::MessageToJsonString(input, &json); - - EXPECT_EQ("{\"accountNumber\":\"1037\",\"chainId\":\"gaia-13003\",\"fee\":{\"amounts\":[{\"denom\":\"muon\",\"amount\":\"200\"}],\"gas\":\"200000\"},\"sequence\":\"8\",\"messages\":[{\"executeContractMessage\":{\"sender\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\",\"contract\":\"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573\",\"executeMsg\":\"transfer\",\"coins\":[{\"denom\":\"muon\",\"amount\":\"1\"}]}}]}", json); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeCosmos); - - EXPECT_EQ("{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"200\",\"denom\":\"muon\"}],\"gas\":\"200000\"},\"memo\":\"\",\"msg\":[{\"type\":\"wasm/MsgExecuteContract\",\"value\":{\"coins\":[{\"amount\":\"1\",\"denom\":\"muon\"}],\"contract\":\"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573\",\"execute_msg\":\"transfer\",\"sender\":\"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"9iQTB1Jjw8FuYPwgzVLbs1cABGYFlk3JRKGQyojQcwY/ni+9D/ViNQMb+4UuokYi74GnpPZpH5RqbJ2ju6VL2g==\"}]}}", output.json()); - - EXPECT_EQ(hex(output.signature()), "f62413075263c3c16e60fc20cd52dbb35700046605964dc944a190ca88d073063f9e2fbd0ff56235031bfb852ea24622ef81a7a4f6691f946a6c9da3bba54bda"); -} - TEST(CosmosSigner, SignTxJsonWithRawJSONMsg) { auto input = Proto::SigningInput(); input.set_signing_mode(Proto::JSON); // obsolete @@ -199,7 +162,8 @@ TEST(CosmosSigner, SignTxJsonWithRawJSONMsg) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); EXPECT_EQ(output.json(), "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"200\",\"denom\":\"muon\"}],\"gas\":\"200000\"},\"memo\":\"\",\"msg\":[{\"type\":\"test\",\"value\":{\"test\":\"hello\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"qhxxCOMiVhP7e7Mx+98HUZI0t5DNOFXwzIqNQz+fT6hDKR/ebW0uocsYnE5CiBNEalmBcs5gSIJegNkHhgyEmA==\"}]}}"); @@ -236,13 +200,15 @@ TEST(CosmosSigner, SignTxJson_WithMode) { input.set_private_key(privateKey.data(), privateKey.size()); { - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); EXPECT_EQ(R"({"mode":"async","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); EXPECT_EQ(output.error_message(), ""); } input.set_mode(Proto::BroadcastMode::SYNC); { - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); EXPECT_EQ(R"({"mode":"sync","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); EXPECT_EQ(output.error_message(), ""); } @@ -281,7 +247,8 @@ TEST(CosmosSigner, SignIbcTransferProtobuf_817101) { EXPECT_EQ(Cosmos::Address(TWCoinTypeCosmos, PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); // real-world tx: https://www.mintscan.io/cosmos/txs/817101F3D96314AD028733248B28BAFAD535024D7D2C8875D3FE31DC159F096B // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Cr4BCr...1yKOU=", "mode": "BROADCAST_MODE_BLOCK"}' https://api.cosmos.network/cosmos/tx/v1beta1/txs @@ -314,7 +281,8 @@ TEST(CosmosSigner, SignDirect1) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47"); @@ -359,7 +327,8 @@ TEST(CosmosSigner, SignDirect_0a90010a) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CpMBCpABChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnAKLWNvc21vczFwa3B0cmU3ZmRrbDZnZnJ6bGVzamp2aHhobGMzcjRnbW1rOHJzNhItY29zbW9zMXF5cHF4cHE5cWNyc3N6ZzJwdnhxNnJzMHpxZzN5eWM1bHp2N3h1GhAKBXVjb3NtEgcxMjM0NTY3EiEKCgoAEgQKAggBGAESEwoNCgV1Y29zbRIEMjAwMBDAmgwaQEgXmSAlm4M5bz+OX1GtvvZ3fBV2wrZrp4A/Imd55KM7ASivB/siYJegmYiOKzQ82uwoEmFalNnG2BrHHDwDR2Y=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "48179920259b83396f3f8e5f51adbef6777c1576c2b66ba7803f226779e4a33b0128af07fb226097a099888e2b343cdaec2812615a94d9c6d81ac71c3c034766"); @@ -391,7 +360,9 @@ TEST(CosmosSigner, MsgVote) { auto privateKey = parse_hex("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + auto expected = R"( {"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"ClQKUgobL2Nvc21vcy5nb3YudjFiZXRhMS5Nc2dWb3RlEjMITRItY29zbW9zMW1yeTQ3cGtnYTV0ZHN3dGx1eTBtOHRlc2xwYWxrZHEwN3Bzd3U0GAESZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBEhMKDQoFdWF0b20SBDI0MTgQkfsFGkA+Nb3NULc38quGC1x+8ZXry4w9mMX3IA7wUjFboTv7kVOwPlleIc8UqIsjVvKTUFnUuW8dlGQzNR1KkvbvZ1NA"})"; assertJSONEqual(output.serialized(), expected); diff --git a/tests/chains/Cosmos/StakingTests.cpp b/tests/chains/Cosmos/StakingTests.cpp index 91e097e0342..ad800b54184 100644 --- a/tests/chains/Cosmos/StakingTests.cpp +++ b/tests/chains/Cosmos/StakingTests.cpp @@ -7,10 +7,10 @@ #include "Base64.h" #include "Coin.h" #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" #include "HexCoding.h" #include "proto/Cosmos.pb.h" #include "TestUtilities.h" +#include #include namespace TW::Cosmos::tests { @@ -42,11 +42,14 @@ TEST(CosmosStaking, CompoundingAuthz) { auto privateKey = parse_hex("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + // Please note the signature has been updated according to the serialization of the `StakeAuthorization` message. + // Previous: CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI= auto expected = R"( { "mode":"BROADCAST_MODE_BLOCK", - "tx_bytes":"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI=" + "tx_bytes":"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjogARI2CjRjb3Ntb3N2YWxvcGVyMWdqdHZseTlsZWw2enNrdnd0dmxnNXZod3B1OWM5d2F3N3N4end4EgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQEAN1nIfDawlHnep2bNEm14w+g7tYybJJT3htcGVS6s9D7va3ed1OUEIk9LZoc3G//VenJ+KLw26SRVBaRukgVI=" })"; assertJSONEqual(output.serialized(), expected); } @@ -75,7 +78,8 @@ TEST(CosmosStaking, RevokeCompoundingAuthz) { auto privateKey = parse_hex("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); auto expected = R"( { "mode":"BROADCAST_MODE_BLOCK", @@ -109,7 +113,8 @@ TEST(CosmosStaking, Staking) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CpsBCpgBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJxCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3AaCgoEbXVvbhICMTASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpA8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA==\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "f0ef499bf90be996b6237a680ece6fa4ca3060980dbd808905153fbf1023b3494d658b2ae34aa94dbc0e4db3918c903952343a6ae738d2feae0854f8ab8cfeb8"); @@ -117,7 +122,8 @@ TEST(CosmosStaking, Staking) { { // Json-serialization, for coverage (to be removed later) input.set_signing_mode(Proto::JSON); - output = Signer::sign(input, TWCoinTypeCosmos); + ANY_SIGN(input, TWCoinTypeCosmos); + ASSERT_EQ(hex(output.signature()), "c08bdf6c2b0b4428f37975e85d329f1cb19745b000994a743b5df81d57d573aa5f755349befcc848c1d1507818723b1288594bc91df685e89aff22e0303b4861"); EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(hex(output.serialized()), ""); @@ -149,7 +155,8 @@ TEST(CosmosStaking, Unstaking) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"Cp0BCpoBCiUvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dVbmRlbGVnYXRlEnEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBoKCgRtdW9uEgIxMBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBhlxHFnjBERxLtjLbMCKXcrDctaSZ9djtWCa3ely1bpV6m+6aAFjpr8aEZH+q2AtjJSEdgpQRJxP+9/gQsRTnZ\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "619711c59e30444712ed8cb6cc08a5dcac372d69267d763b5609adde972d5ba55ea6fba680163a6bf1a1191feab602d8c9484760a50449c4ffbdfe042c4539d9"); @@ -157,7 +164,7 @@ TEST(CosmosStaking, Unstaking) { { // Json-serialization, for coverage (to be removed later) input.set_signing_mode(Proto::JSON); - output = Signer::sign(input, TWCoinTypeCosmos); + ANY_SIGN(input, TWCoinTypeCosmos); ASSERT_EQ(hex(output.signature()), "8f85a9515a211881daebfb346c2beeca3ab5c2d406a9b3ad402cfddaa3d08e2b13378e13cfef8ecf1d6500fe85d0ce3e793034dd77aba90f216427807cbff79f"); EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(hex(output.serialized()), ""); @@ -191,7 +198,8 @@ TEST(CosmosStaking, Restaking) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CtIBCs8BCiovY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dCZWdpblJlZGVsZWdhdGUSoAEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBotY29zbW9zMWhzazZqcnl5cWpmaHA1ZGhjNTV0YzlqdGNreWd4MGVwaDZkZDAyIgoKBG11b24SAjEwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQJXKG7D830zVXu7qgALJ3RKyQI6qZZ8rnWhgdH/kfqdxRIECgIIARgHEhIKDAoEbXVvbhIEMTAxOBDZmgYaQJ52qO5xdtBkNUeFeWrnqUXkngyHFKCXnOPPClyVI0HrULdp5jbwGra2RujEOn4BrbFCb3JFnpc2o1iuLXbKQxg=\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "9e76a8ee7176d064354785796ae7a945e49e0c8714a0979ce3cf0a5c952341eb50b769e636f01ab6b646e8c43a7e01adb1426f72459e9736a358ae2d76ca4318"); @@ -199,7 +207,7 @@ TEST(CosmosStaking, Restaking) { { // Json-serialization, for coverage (to be removed later) input.set_signing_mode(Proto::JSON); - output = Signer::sign(input, TWCoinTypeCosmos); + ANY_SIGN(input, TWCoinTypeCosmos); ASSERT_EQ(hex(output.signature()), "e64d3761bd25a28befcda80c0a0e208d024fdb0a2b89955170e65a5c5d454aba2ce81d57e01f0c126de5a59c2b58124c109560c9803d65a17a14b548dd6c50db"); EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(hex(output.serialized()), ""); @@ -228,7 +236,8 @@ TEST(CosmosStaking, Withdraw) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CqMBCqABCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmUKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBW1Cd+0pNfMPEVXQtqG1VIijDjZP2UOiDlvUF478axnxlF8PaOAsY0S5OdUE3Wz7+nu8YVmrLZQS/8mlqLaK05\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); EXPECT_EQ(hex(output.signature()), "56d4277ed2935f30f1155d0b6a1b55488a30e364fd943a20e5bd4178efc6b19f1945f0f68e02c6344b939d504dd6cfbfa7bbc6159ab2d9412ffc9a5a8b68ad39"); @@ -236,7 +245,7 @@ TEST(CosmosStaking, Withdraw) { { // Json-serialization, for coverage (to be removed later) input.set_signing_mode(Proto::JSON); - output = Signer::sign(input, TWCoinTypeCosmos); + ANY_SIGN(input, TWCoinTypeCosmos); ASSERT_EQ(hex(output.signature()), "546f0d67356f6af94cfb5ab22b974e499c33123f2c2c292f4f0e64878e0e728f4643105fd771550beb3f2371f08880aaa38fa8f2334c103a779f1d82d2db98d6"); EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(hex(output.serialized()), ""); @@ -265,7 +274,8 @@ TEST(CosmosStaking, SetWithdrawAddress) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeCosmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); assertJSONEqual(output.serialized(), R"({"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Cp4BCpsBCjIvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1NldFdpdGhkcmF3QWRkcmVzcxJlCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3ASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpAkm2TJLw4FcIwN5bkqVaGbmAgkTSHeYD8sUkIyJHLa89cPvThkFO/lKlxBMl2UAMs06hL6cYcl4Px+B6rpFdBpA=="})"); EXPECT_EQ(hex(output.signature()), "926d9324bc3815c2303796e4a956866e60209134877980fcb14908c891cb6bcf5c3ef4e19053bf94a97104c97650032cd3a84be9c61c9783f1f81eaba45741a4"); @@ -273,7 +283,7 @@ TEST(CosmosStaking, SetWithdrawAddress) { { // Json-serialization, for coverage (to be removed later) input.set_signing_mode(Proto::JSON); - output = Signer::sign(input, TWCoinTypeCosmos); + ANY_SIGN(input, TWCoinTypeCosmos); ASSERT_EQ(hex(output.signature()), "22cfbcec33d06ed42623264049d11d6fb86566103d5621a23b1444022eb1aace3a0790a1c46b48c0218689616daf97f99ae72c3589966205de45b57194fbada2"); EXPECT_EQ(output.error_message(), ""); EXPECT_EQ(hex(output.serialized()), ""); diff --git a/tests/chains/Cosmos/THORChain/SignerTests.cpp b/tests/chains/Cosmos/THORChain/SignerTests.cpp index 75f86efda13..fcb07cae3a9 100644 --- a/tests/chains/Cosmos/THORChain/SignerTests.cpp +++ b/tests/chains/Cosmos/THORChain/SignerTests.cpp @@ -5,10 +5,12 @@ // file LICENSE at the root of the source code distribution tree. #include "proto/Cosmos.pb.h" -#include "THORChain/Signer.h" +#include "Coin.h" #include "HexCoding.h" #include "Bech32Address.h" #include "TestUtilities.h" +#include "TrustWalletCore/TWAnySigner.h" +#include "TrustWalletCore/TWCoinType.h" #include #include @@ -80,7 +82,8 @@ TEST(THORChainSigner, SignTx_Protobuf_7E480F) { auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = THORChain::Signer::sign(input); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTHORChain); // https://viewblock.io/thorchain/tx/7E480FA163F6C6AFA17593F214C7BBC218F69AE3BC72366E39042AF381BFE105 // curl -H 'Content-Type: application/json' --data-binary '{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"ClIKUAoO..89g="}' https:///cosmos/tx/v1beta1/txs @@ -158,7 +161,8 @@ TEST(THORChainSigner, SignTx_MsgDeposit) { auto privateKey = parse_hex("2659e41d54ebd449d68b9d58510d8eeeb837ee00d6ecc760b7a731238d8c3113"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = THORChain::Signer::sign(input); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTHORChain); // https://viewblock.io/thorchain/tx/0162213E7F9D85965B1C57FA3BF9603C655B542F358318303A7B00661AE42510 // curl -H 'Content-Type: application/json' --data-binary '{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CoUBCoIB..hiw="}' https:///cosmos/tx/v1beta1/txs @@ -225,7 +229,8 @@ TEST(THORChainSigner, SignTx_Json_Deprecated) { auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = THORChain::Signer::sign(input); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTHORChain); assertJSONEqual(output.json(), R"( { @@ -275,7 +280,7 @@ TEST(THORChainSigner, SignJson) { auto inputJson = R"({"fee":{"amounts":[{"denom":"rune","amount":"200"}],"gas":"2000000"},"memo":"memo1234","messages":[{"sendCoinsMessage":{"fromAddress":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","toAddress":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn","amounts":[{"denom":"rune","amount":"50000000"}]}}]})"; auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); - auto outputJson = THORChain::Signer::signJSON(inputJson, privateKey); + auto outputJson = TW::anySignJSON(TWCoinTypeTHORChain, inputJson, privateKey); EXPECT_EQ(R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"rune"}],"gas":"2000000"},"memo":"memo1234","msg":[{"type":"thorchain/MsgSend","value":{"amount":[{"amount":"50000000","denom":"rune"}],"from_address":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","to_address":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"12AaNC0v51Rhz8rBf7V7rpI6oksREWrjzba3RK1v1NNlqZq62sG0aXWvStp9zZXe07Pp2FviFBAx+uqWsO30NQ=="}]}})", outputJson); } diff --git a/tests/chains/Cosmos/Terra/SignerTests.cpp b/tests/chains/Cosmos/Terra/SignerTests.cpp index 2d6373de942..aa6004567da 100644 --- a/tests/chains/Cosmos/Terra/SignerTests.cpp +++ b/tests/chains/Cosmos/Terra/SignerTests.cpp @@ -8,10 +8,9 @@ #include "Base64.h" #include "proto/Cosmos.pb.h" #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" -#include "Cosmos/ProtobufSerialization.h" #include "uint256.h" #include "TestUtilities.h" +#include "TrustWalletCore/TWAnySigner.h" #include #include @@ -52,7 +51,8 @@ TEST(TerraClassicSigner, SignSendTx) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerra); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); assertJSONEqual(output.json(), R"( { @@ -161,7 +161,8 @@ TEST(TerraClassicSigner, SignWasmTransferTxProtobuf_9FF3F0) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerra); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); // https://finder.terra.money/mainnet/tx/9FF3F0A16879254C22EB90D8B4D6195467FE5014381FD36BD3C23CA6698FE94B // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "CogCCo..wld8"})' https:///cosmos/tx/v1beta1/txs @@ -206,7 +207,8 @@ TEST(TerraClassicSigner, SignWasmTransferTxJson_078E90) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerra); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); // https://finder.terra.money/mainnet/tx/078E90458061611F6FD8B708882B55FF5C1FFB3FCE61322107A0A0DE39FC0F3E // curl -H 'Content-Type: application/json' --data-binary '{"mode": "block","tx":{...}}' https:///txs @@ -284,7 +286,8 @@ TEST(TerraClassicSigner, SignWasmGeneric_EC4F85) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerra); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); // https://finder.terra.money/mainnet/tx/EC4F8532847E4D6AF016E6F6D3F027AE7FB6FF0B533C5132B01382D83B214A6F // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "Cu4BC...iVt"})' https:///cosmos/tx/v1beta1/txs @@ -333,7 +336,8 @@ TEST(TerraClassicSigner, SignWasmGenericWithCoins_6651FC) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerra); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); // https://finder.terra.money/mainnet/tx/6651FCE0EE5C6D6ACB655CC49A6FD5E939FB082862854616EA0642475BCDD0C9 // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "CrIBCq8B.....0NWg=="})' https:///cosmos/tx/v1beta1/txs @@ -413,7 +417,8 @@ TEST(TerraClassicSigner, SignWasmSendTxProtobuf) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerra); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); // https://finder.terra.money/mainnet/tx/9FF3F0A16879254C22EB90D8B4D6195467FE5014381FD36BD3C23CA6698FE94B // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "CogCCo..wld8"})' https:///cosmos/tx/v1beta1/txs @@ -427,23 +432,4 @@ TEST(TerraClassicSigner, SignWasmSendTxProtobuf) { EXPECT_EQ(output.json(), ""); } -TEST(TerraClassicSigner, SignWasmTerraTransferPayload) { - auto proto = Proto::Message_WasmTerraExecuteContractTransfer(); - proto.set_recipient_address("recipient=address"); - const auto amount = store(uint256_t(250000), 0); - proto.set_amount(amount.data(), amount.size()); - - const auto payload = Protobuf::wasmTerraExecuteTransferPayload(proto); - - assertJSONEqual(payload.dump(), R"( - { - "transfer": - { - "amount": "250000", - "recipient": "recipient=address" - } - } - )"); -} - } // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/TerraV2/SignerTests.cpp b/tests/chains/Cosmos/TerraV2/SignerTests.cpp index cfd8dd8dde4..b6f94de7972 100644 --- a/tests/chains/Cosmos/TerraV2/SignerTests.cpp +++ b/tests/chains/Cosmos/TerraV2/SignerTests.cpp @@ -8,9 +8,8 @@ #include "Base64.h" #include "proto/Cosmos.pb.h" #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" -#include "Cosmos/ProtobufSerialization.h" #include "uint256.h" +#include "TrustWalletCore/TWAnySigner.h" #include "TestUtilities.h" #include @@ -84,7 +83,8 @@ TEST(TerraSigner, SignSendTx) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerraV2); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerraV2); // similar tx: https://finder.terra.money/mainnet/tx/fbbe73ad2f0db3a13911dc424f8a34370dc4b7e8b66687f536797e68ee200ece assertJSONEqual(output.serialized(), R"( @@ -161,7 +161,8 @@ TEST(TerraSigner, SignWasmTransferTx) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerraV2); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerraV2); assertJSONEqual(output.serialized(), R"( { @@ -202,7 +203,8 @@ TEST(TerraSigner, SignWasmGeneric) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerraV2); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerraV2); assertJSONEqual(output.serialized(), R"( { @@ -248,7 +250,8 @@ TEST(TerraSigner, SignWasmGenericWithCoins) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerraV2); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerraV2); assertJSONEqual(output.serialized(), R"( { @@ -327,7 +330,8 @@ TEST(TerraSigner, SignWasmSendTx) { auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeTerraV2); + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerraV2); assertJSONEqual(output.serialized(), R"( { @@ -340,23 +344,4 @@ TEST(TerraSigner, SignWasmSendTx) { EXPECT_EQ(output.json(), ""); } -TEST(TerraSigner, SignWasmTransferPayload) { - auto proto = Proto::Message_WasmExecuteContractTransfer(); - proto.set_recipient_address("recipient=address"); - const auto amount = store(uint256_t(250000), 0); - proto.set_amount(amount.data(), amount.size()); - - const auto payload = Protobuf::wasmExecuteTransferPayload(proto); - - assertJSONEqual(payload.dump(), R"( - { - "transfer": - { - "amount": "250000", - "recipient": "recipient=address" - } - } - )"); -} - } // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/TransactionCompilerTests.cpp b/tests/chains/Cosmos/TransactionCompilerTests.cpp index 4500533ab7a..49284252185 100644 --- a/tests/chains/Cosmos/TransactionCompilerTests.cpp +++ b/tests/chains/Cosmos/TransactionCompilerTests.cpp @@ -139,7 +139,7 @@ TEST(CosmosCompiler, CompileWithSignatures) { Cosmos::Proto::SigningOutput output; ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); EXPECT_EQ(output.serialized().size(), 0ul); - EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); } /// Step 3: Obtain json preimage hash @@ -168,10 +168,6 @@ TEST(CosmosCompiler, CompileWithSignatures) { "0a31f6cd50f1a5c514929ba68a977e222a7df2dc11e8470e93118cc3545e6b37"); signature = Base64::decode("tTyOrburrHEHa14qiw78e9StoZyyGmoku98IxYrWCmtN8Qo5mTeKa0BKKDfgG4LmmNdwYcrXtqQQ7F4dL3c26g=="); - { - auto result = TW::anySignJSON(coin, jsonPreImage, privateKey.bytes); - EXPECT_EQ(result, "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[],\"gas\":\"0\"},\"memo\":\"\",\"msg\":[],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJ\"},\"signature\":\"tTyOrburrHEHa14qiw78e9StoZyyGmoku98IxYrWCmtN8Qo5mTeKa0BKKDfgG4LmmNdwYcrXtqQQ7F4dL3c26g==\"}]}}"); - } { // JSON const Data outputData = TransactionCompiler::compileWithSignatures( @@ -180,6 +176,6 @@ TEST(CosmosCompiler, CompileWithSignatures) { ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); EXPECT_EQ(output.error(), Common::Proto::OK); - EXPECT_EQ(hex(output.serialized()), "7b226d6f6465223a22626c6f636b222c227478223a7b22666565223a7b22616d6f756e74223a5b7b22616d6f756e74223a2231303030222c2264656e6f6d223a227561746f6d227d5d2c22676173223a22323030303030227d2c226d656d6f223a22222c226d7367223a5b7b2274797065223a22636f736d6f732d73646b2f4d736753656e64222c2276616c7565223a7b22616d6f756e74223a5b7b22616d6f756e74223a22343030303030222c2264656e6f6d223a227561746f6d227d5d2c2266726f6d5f61646472657373223a22636f736d6f73316d6b793639636e38656b74777930383435766563397570736470686b7478743033676b776c78222c22746f5f61646472657373223a22636f736d6f733138733068646e736c6c6763636c7765753961796d77346e676b7472326b30726b7967647a6470227d7d5d2c227369676e617475726573223a5b7b227075625f6b6579223a7b2274797065223a2274656e6465726d696e742f5075624b6579536563703235366b31222c2276616c7565223a2241757a76584f51336f774c47663556476a65537a487a62704566526e312b616c4b30484234543464566a5a4a227d2c227369676e6174757265223a227454794f72627572724845486131347169773738653953746f5a7979476d6f6b7539384978597257436d744e38516f356d54654b6130424b4b44666747344c6d6d4e64775963725874715151374634644c33633236673d3d227d5d7d7d"); + EXPECT_EQ(hex(output.json()), "7b226d6f6465223a22626c6f636b222c227478223a7b22666565223a7b22616d6f756e74223a5b7b22616d6f756e74223a2231303030222c2264656e6f6d223a227561746f6d227d5d2c22676173223a22323030303030227d2c226d656d6f223a22222c226d7367223a5b7b2274797065223a22636f736d6f732d73646b2f4d736753656e64222c2276616c7565223a7b22616d6f756e74223a5b7b22616d6f756e74223a22343030303030222c2264656e6f6d223a227561746f6d227d5d2c2266726f6d5f61646472657373223a22636f736d6f73316d6b793639636e38656b74777930383435766563397570736470686b7478743033676b776c78222c22746f5f61646472657373223a22636f736d6f733138733068646e736c6c6763636c7765753961796d77346e676b7472326b30726b7967647a6470227d7d5d2c227369676e617475726573223a5b7b227075625f6b6579223a7b2274797065223a2274656e6465726d696e742f5075624b6579536563703235366b31222c2276616c7565223a2241757a76584f51336f774c47663556476a65537a487a62704566526e312b616c4b30484234543464566a5a4a227d2c227369676e6174757265223a227454794f72627572724845486131347169773738653953746f5a7979476d6f6b7539384978597257436d744e38516f356d54654b6130424b4b44666747344c6d6d4e64775963725874715151374634644c33633236673d3d227d5d7d7d"); } } diff --git a/tests/chains/Evmos/SignerTests.cpp b/tests/chains/Evmos/SignerTests.cpp index 02ae7c9b45e..981bd727b27 100644 --- a/tests/chains/Evmos/SignerTests.cpp +++ b/tests/chains/Evmos/SignerTests.cpp @@ -8,9 +8,9 @@ #include "Base64.h" #include "proto/Cosmos.pb.h" #include "Cosmos/Address.h" -#include "Cosmos/Signer.h" #include "TestUtilities.h" +#include #include #include @@ -46,7 +46,8 @@ TEST(EvmosSigner, SignTxJsonEthermintKeyType) { auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeNativeEvmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeNativeEvmos); auto anotherExpectedJson =R"( { "mode":"block", @@ -64,7 +65,7 @@ TEST(EvmosSigner, SignTxJsonEthermintKeyType) { "type":"ethermint/PubKeyEthSecp256k1", "value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" }, - "signature":"RWt8aaBxdMAeEjym8toWskJ6WaJpEF9Ciucz2lAHkvNnTicGpzxwTUzJbJXRirSnGkejhISaYtDw2RBiq0vg5w==" + "signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg==" } ]} })"_json; @@ -82,7 +83,7 @@ TEST(EvmosSigner, SignTxJsonEthermintKeyType) { "type":"ethermint/PubKeyEthSecp256k1", "value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" }, - "signature":"RWt8aaBxdMAeEjym8toWskJ6WaJpEF9Ciucz2lAHkvNnTicGpzxwTUzJbJXRirSnGkejhISaYtDw2RBiq0vg5w==" + "signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg==" } ])"_json; EXPECT_EQ(signatures, expectedSignatures); @@ -115,10 +116,13 @@ TEST(EvmosSigner, CompoundingAuthz) { auto privateKey = parse_hex("79bcbded1a5678ab34e6d9db9ad78e4e480e7b22723cc5fbf59e843732e1a8e5"); input.set_private_key(privateKey.data(), privateKey.size()); - auto output = Signer::sign(input, TWCoinTypeNativeEvmos); + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeNativeEvmos); + // Please note the signature has been updated according to the serialization of the `StakeAuthorization` message. + // Previous: CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5EjUKM2V2bW9zdmFsb3BlcjF1bWs0MDdlZWQ3YWY2YW52dXQ2bGxnMnpldm5mMGRuMGZlcXFueSABEgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkAm17CZgB7m+CPVlITnrHosklMTL9zrUeGRs8FL8N0GcRami9zdJ+e3xuXOtJmwP7G6QNh85CRYjFj8a8lpmmJM auto expected = R"( { - "mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5EjUKM2V2bW9zdmFsb3BlcjF1bWs0MDdlZWQ3YWY2YW52dXQ2bGxnMnpldm5mMGRuMGZlcXFueSABEgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkAm17CZgB7m+CPVlITnrHosklMTL9zrUeGRs8FL8N0GcRami9zdJ+e3xuXOtJmwP7G6QNh85CRYjFj8a8lpmmJM" + "mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5IAESNQozZXZtb3N2YWxvcGVyMXVtazQwN2VlZDdhZjZhbnZ1dDZsbGcyemV2bmYwZG4wZmVxcW55EgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkBXaTo3nk5EMFW9Euheez5ADx2bWo7XisNJ5vuGj1fKXh6CGNJGfJj/q1XUkBzaCvPNg+EcFHgtJdVSyF4cJZTg" })"; assertJSONEqual(output.serialized(), expected); } diff --git a/tests/chains/Evmos/TransactionCompilerTests.cpp b/tests/chains/Evmos/TransactionCompilerTests.cpp index 72d8ab82606..c58e374fb1d 100644 --- a/tests/chains/Evmos/TransactionCompilerTests.cpp +++ b/tests/chains/Evmos/TransactionCompilerTests.cpp @@ -5,7 +5,6 @@ // file LICENSE at the root of the source code distribution tree. #include "Base64.h" -#include "Cosmos/Signer.h" #include "HexCoding.h" #include "proto/Cosmos.pb.h" #include "proto/TransactionCompiler.pb.h" @@ -95,8 +94,7 @@ TEST(EvmosCompiler, CompileWithSignatures) { EXPECT_EQ(output.error(), Common::Proto::OK); EXPECT_EQ(output.serialized(), expectedTx); - EXPECT_EQ(output.signature(), ""); - EXPECT_EQ(hex(output.signature()), ""); + EXPECT_EQ(hex(output.signature()), hex(signature)); } }